WIP: dev into master #184
83
.gitea/workflows/build-dev.yaml
Normal file
83
.gitea/workflows/build-dev.yaml
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
name: Build on push
|
||||||
|
run-name: Build on push
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
prepare:
|
||||||
|
uses: ./.gitea/workflows/prepare.yaml
|
||||||
|
with:
|
||||||
|
version_suffix: 'dev'
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
api:
|
||||||
|
uses: ./.gitea/workflows/package.yaml
|
||||||
|
needs: [ prepare, application, auth, core, dependency ]
|
||||||
|
with:
|
||||||
|
working_directory: src/api
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
application:
|
||||||
|
uses: ./.gitea/workflows/package.yaml
|
||||||
|
needs: [ prepare, core, dependency ]
|
||||||
|
with:
|
||||||
|
working_directory: src/application
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
auth:
|
||||||
|
uses: ./.gitea/workflows/package.yaml
|
||||||
|
needs: [ prepare, core, dependency, database ]
|
||||||
|
with:
|
||||||
|
working_directory: src/auth
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
cli:
|
||||||
|
uses: ./.gitea/workflows/package.yaml
|
||||||
|
needs: [ prepare, core ]
|
||||||
|
with:
|
||||||
|
working_directory: src/cli
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
core:
|
||||||
|
uses: ./.gitea/workflows/package.yaml
|
||||||
|
needs: [prepare]
|
||||||
|
with:
|
||||||
|
working_directory: src/core
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
database:
|
||||||
|
uses: ./.gitea/workflows/package.yaml
|
||||||
|
needs: [ prepare, core, dependency ]
|
||||||
|
with:
|
||||||
|
working_directory: src/database
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
dependency:
|
||||||
|
uses: ./.gitea/workflows/package.yaml
|
||||||
|
needs: [ prepare, core ]
|
||||||
|
with:
|
||||||
|
working_directory: src/dependency
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
mail:
|
||||||
|
uses: ./.gitea/workflows/package.yaml
|
||||||
|
needs: [ prepare, core, dependency ]
|
||||||
|
with:
|
||||||
|
working_directory: src/mail
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
query:
|
||||||
|
uses: ./.gitea/workflows/package.yaml
|
||||||
|
needs: [prepare]
|
||||||
|
with:
|
||||||
|
working_directory: src/query
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
translation:
|
||||||
|
uses: ./.gitea/workflows/package.yaml
|
||||||
|
needs: [ prepare, core, dependency ]
|
||||||
|
with:
|
||||||
|
working_directory: src/translation
|
||||||
|
secrets: inherit
|
||||||
39
.gitea/workflows/build.yaml
Normal file
39
.gitea/workflows/build.yaml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
name: Build on push
|
||||||
|
run-name: Build on push
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
prepare:
|
||||||
|
uses: ./.gitea/workflows/prepare.yaml
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
core:
|
||||||
|
uses: ./.gitea/workflows/package.yaml
|
||||||
|
needs: [prepare]
|
||||||
|
with:
|
||||||
|
working_directory: src/cpl-core
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
query:
|
||||||
|
uses: ./.gitea/workflows/package.yaml
|
||||||
|
needs: [prepare]
|
||||||
|
with:
|
||||||
|
working_directory: src/cpl-query
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
translation:
|
||||||
|
uses: ./.gitea/workflows/package.yaml
|
||||||
|
needs: [ prepare, core ]
|
||||||
|
with:
|
||||||
|
working_directory: src/cpl-translation
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
mail:
|
||||||
|
uses: ./.gitea/workflows/package.yaml
|
||||||
|
needs: [ prepare, core ]
|
||||||
|
with:
|
||||||
|
working_directory: src/cpl-mail
|
||||||
|
secrets: inherit
|
||||||
71
.gitea/workflows/package.yaml
Normal file
71
.gitea/workflows/package.yaml
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
name: Build Package
|
||||||
|
run-name: Build Python Package
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
version_suffix:
|
||||||
|
description: 'Suffix for version (z.B. "dev", "alpha", "beta")'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
working_directory:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: [ runner ]
|
||||||
|
container: git.sh-edraft.de/sh-edraft.de/act-runner:latest
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: ${{ inputs.working_directory }}
|
||||||
|
steps:
|
||||||
|
- name: Clone Repository
|
||||||
|
uses: https://github.com/actions/checkout@v3
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.CI_ACCESS_TOKEN }}
|
||||||
|
|
||||||
|
- name: Download build version artifact
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: version
|
||||||
|
|
||||||
|
- name: Set version
|
||||||
|
run: |
|
||||||
|
sed -i -E "s/^version = \".*\"/version = \"$(cat /workspace/sh-edraft.de/cpl/version.txt)\"/" pyproject.toml
|
||||||
|
echo "Set version to $(cat /workspace/sh-edraft.de/cpl/version.txt)"
|
||||||
|
cat pyproject.toml
|
||||||
|
|
||||||
|
- name: Set package version
|
||||||
|
run: |
|
||||||
|
sed -i -E "s/^__version__ = \".*\"/__version__ = \"$(cat /workspace/sh-edraft.de/cpl/version.txt)\"/" cpl/*/__init__.py
|
||||||
|
echo "Set version to $(cat /workspace/sh-edraft.de/cpl/version.txt)"
|
||||||
|
cat cpl/*/__init__.py
|
||||||
|
|
||||||
|
- name: Set pip conf
|
||||||
|
run: |
|
||||||
|
cat > .pip.conf <<'EOF'
|
||||||
|
[global]
|
||||||
|
extra-index-url = https://git.sh-edraft.de/api/packages/sh-edraft.de/pypi/simple/
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: |
|
||||||
|
export PIP_CONFIG_FILE=".pip.conf"
|
||||||
|
pip install build
|
||||||
|
|
||||||
|
- name: Build Package
|
||||||
|
run: |
|
||||||
|
python -m build --outdir dist
|
||||||
|
|
||||||
|
- name: Login to registry git.sh-edraft.de
|
||||||
|
uses: https://github.com/docker/login-action@v1
|
||||||
|
with:
|
||||||
|
registry: git.sh-edraft.de
|
||||||
|
username: ${{ secrets.CI_USERNAME }}
|
||||||
|
password: ${{ secrets.CI_ACCESS_TOKEN }}
|
||||||
|
|
||||||
|
- name: Push image
|
||||||
|
run: |
|
||||||
|
pip install twine
|
||||||
|
python -m twine upload --repository-url https://git.sh-edraft.de/api/packages/sh-edraft.de/pypi -u ${{ secrets.CI_USERNAME }} -p ${{ secrets.CI_ACCESS_TOKEN }} ./dist/*
|
||||||
58
.gitea/workflows/prepare.yaml
Normal file
58
.gitea/workflows/prepare.yaml
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
name: Prepare Build
|
||||||
|
run-name: Prepare Build Version
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
version_suffix:
|
||||||
|
description: 'Suffix for version (z.B. "dev", "alpha", "beta")'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
prepare:
|
||||||
|
runs-on: [ runner ]
|
||||||
|
container: git.sh-edraft.de/sh-edraft.de/act-runner:latest
|
||||||
|
steps:
|
||||||
|
- name: Clone Repository
|
||||||
|
uses: https://github.com/actions/checkout@v3
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.CI_ACCESS_TOKEN }}
|
||||||
|
|
||||||
|
- name: Get Date and Build Number
|
||||||
|
run: |
|
||||||
|
git fetch --tags
|
||||||
|
git tag
|
||||||
|
DATE=$(date +'%Y.%m.%d')
|
||||||
|
TAG_COUNT=$(git tag -l "${DATE}.*" | wc -l)
|
||||||
|
if [ "$TAG_COUNT" -eq 0 ]; then
|
||||||
|
BUILD_NUMBER=0
|
||||||
|
else
|
||||||
|
BUILD_NUMBER=$(($TAG_COUNT + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
VERSION_SUFFIX=${{ inputs.version_suffix }}
|
||||||
|
if [ -n "$VERSION_SUFFIX" ] && [ "$VERSION_SUFFIX" = "dev" ]; then
|
||||||
|
BUILD_VERSION="${DATE}.dev${BUILD_NUMBER}"
|
||||||
|
elif [ -n "$VERSION_SUFFIX" ]; then
|
||||||
|
BUILD_VERSION="${DATE}.${BUILD_NUMBER}${VERSION_SUFFIX}"
|
||||||
|
else
|
||||||
|
BUILD_VERSION="${DATE}.${BUILD_NUMBER}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$BUILD_VERSION" > version.txt
|
||||||
|
echo "VERSION $BUILD_VERSION"
|
||||||
|
|
||||||
|
- name: Create Git Tag for Build
|
||||||
|
run: |
|
||||||
|
git config user.name "ci"
|
||||||
|
git config user.email "dev@sh-edraft.de"
|
||||||
|
echo "tag $(cat version.txt)"
|
||||||
|
git tag $(cat version.txt)
|
||||||
|
git push origin --tags
|
||||||
|
|
||||||
|
- name: Upload build version artifact
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: version
|
||||||
|
path: version.txt
|
||||||
26
.gitea/workflows/test_before_merge.yaml
Normal file
26
.gitea/workflows/test_before_merge.yaml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
name: Test before pr merge
|
||||||
|
run-name: Test before pr merge
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- edited
|
||||||
|
- reopened
|
||||||
|
- synchronize
|
||||||
|
- ready_for_review
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test-lint:
|
||||||
|
runs-on: [ runner ]
|
||||||
|
container: git.sh-edraft.de/sh-edraft.de/act-runner:latest
|
||||||
|
steps:
|
||||||
|
- name: Clone Repository
|
||||||
|
uses: https://github.com/actions/checkout@v3
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.CI_ACCESS_TOKEN }}
|
||||||
|
|
||||||
|
- name: Installing black
|
||||||
|
run: python3.12 -m pip install black
|
||||||
|
|
||||||
|
- name: Checking black
|
||||||
|
run: python3.12 -m black src --check
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -113,6 +113,7 @@ venv.bak/
|
|||||||
|
|
||||||
# Custom Environments
|
# Custom Environments
|
||||||
cpl-env/
|
cpl-env/
|
||||||
|
.secret
|
||||||
|
|
||||||
# Spyder project settings
|
# Spyder project settings
|
||||||
.spyderproject
|
.spyderproject
|
||||||
@@ -138,3 +139,6 @@ PythonImportHelper-v2-Completion.json
|
|||||||
|
|
||||||
# cpl unittest stuff
|
# cpl unittest stuff
|
||||||
unittests/test_*_playground
|
unittests/test_*_playground
|
||||||
|
|
||||||
|
# cpl logs
|
||||||
|
**/logs/*.jsonl
|
||||||
|
|||||||
2
.pip.conf
Normal file
2
.pip.conf
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[global]
|
||||||
|
extra-index-url = https://git.sh-edraft.de/api/packages/sh-edraft.de/pypi/simple/
|
||||||
163
README.md
163
README.md
@@ -1,153 +1,22 @@
|
|||||||
<h1 align="center">CPL - Common python library</h1>
|
## Prepare for development
|
||||||
|
|
||||||
<!-- Summary -->
|
After cloning the repository, run the following commands to set up your development environment:
|
||||||
<p align="center">
|
|
||||||
<!-- <img src="" alt="cpl-logo" width="120px" height="120px"/> -->
|
|
||||||
<br>
|
|
||||||
<i>
|
|
||||||
CPL is a development platform for python server applications
|
|
||||||
<br>using Python.</i>
|
|
||||||
<br>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
## Table of Contents
|
```bash
|
||||||
<!-- TABLE OF CONTENTS -->
|
python -m venv .venv
|
||||||
<ol>
|
source .venv/bin/activate
|
||||||
<li><a href="#Features">Features</a></li>
|
# On Windows use `.venv\Scripts\activate`
|
||||||
<li>
|
# On Windows with git bash use `source .venv/Scripts/activate`
|
||||||
<a href="#getting-started">Getting Started</a>
|
bash install.sh
|
||||||
<ul>
|
|
||||||
<li><a href="#prerequisites">Prerequisites</a></li>
|
|
||||||
<li><a href="#installation">Installation</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="#roadmap">Roadmap</a></li>
|
|
||||||
<li><a href="#contributing">Contributing</a></li>
|
|
||||||
<li><a href="#license">License</a></li>
|
|
||||||
<li><a href="#contact">Contact</a></li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
## Features
|
|
||||||
<!-- FEATURE OVERVIEW -->
|
|
||||||
- Expandle
|
|
||||||
- Application base
|
|
||||||
- Standardized application classes
|
|
||||||
- Application object builder
|
|
||||||
- Application extension classes
|
|
||||||
- Startup classes
|
|
||||||
- Startup extension classes
|
|
||||||
- Configuration
|
|
||||||
- Configure via object mapped JSON
|
|
||||||
- Console argument handling
|
|
||||||
- Console class for in and output
|
|
||||||
- Banner
|
|
||||||
- Spinner
|
|
||||||
- Options (menu)
|
|
||||||
- Table
|
|
||||||
- Write
|
|
||||||
- Write_at
|
|
||||||
- Write_line
|
|
||||||
- Write_line_at
|
|
||||||
- Dependency injection
|
|
||||||
- Service lifetimes: singleton, scoped and transient
|
|
||||||
- Providing of application environment
|
|
||||||
- Environment (development, staging, testing, production)
|
|
||||||
- Appname
|
|
||||||
- Customer
|
|
||||||
- Hostname
|
|
||||||
- Runtime directory
|
|
||||||
- Working directory
|
|
||||||
- Logging
|
|
||||||
- Standardized logger
|
|
||||||
- Log-level (FATAL, ERROR, WARN, INFO, DEBUG & TRACE)
|
|
||||||
- Mail handling
|
|
||||||
- Send mails
|
|
||||||
- Pipe classes
|
|
||||||
- Convert input
|
|
||||||
- Utils
|
|
||||||
- Credential manager
|
|
||||||
- Encryption via BASE64
|
|
||||||
- PIP wrapper class based on subprocess
|
|
||||||
- Run pip commands
|
|
||||||
- String converter to different variants
|
|
||||||
- to_lower_case
|
|
||||||
- to_camel_case
|
|
||||||
- ...
|
|
||||||
|
|
||||||
<!-- GETTING STARTED -->
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
[Get started with CPL][quickstart].
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
- Install [python] which includes [Pip installs packages][pip]
|
|
||||||
|
|
||||||
### Installation
|
|
||||||
|
|
||||||
Install the CPL package
|
|
||||||
```sh
|
|
||||||
pip install cpl-core --extra-index-url https://pip.sh-edraft.de
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Install the CPL CLI
|
Install cpl-cli as a development package:
|
||||||
```sh
|
|
||||||
pip install cpl-cli --extra-index-url https://pip.sh-edraft.de
|
```bash
|
||||||
|
pip install -e src/core
|
||||||
|
pip install -e src/cli
|
||||||
|
# test with:
|
||||||
|
cpl v
|
||||||
```
|
```
|
||||||
|
|
||||||
Create workspace:
|
When using Pycharm, mark all directories under `src/` as "Sources Root" and `exa` to ensure proper module resolution.
|
||||||
```sh
|
|
||||||
cpl new <console|library|unittest> <PROJECT NAME>
|
|
||||||
```
|
|
||||||
|
|
||||||
Run the application:
|
|
||||||
```sh
|
|
||||||
cd <PROJECT NAME>
|
|
||||||
cpl start
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
<!-- ROADMAP -->
|
|
||||||
## Roadmap
|
|
||||||
|
|
||||||
See the [open issues](https://git.sh-edraft.de/sh-edraft.de/sh_cpl/issues) for a list of proposed features (and known issues).
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- CONTRIBUTING -->
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
### Contributing Guidelines
|
|
||||||
|
|
||||||
Read through our [contributing guidelines][contributing] to learn about our submission process, coding rules and more.
|
|
||||||
|
|
||||||
### Want to Help?
|
|
||||||
|
|
||||||
Want to file a bug, contribute some code, or improve documentation? Excellent! Read up on our guidelines for [contributing][contributing].
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- LICENSE -->
|
|
||||||
## License
|
|
||||||
|
|
||||||
Distributed under the MIT License. See [LICENSE] for more information.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- CONTACT -->
|
|
||||||
## Contact
|
|
||||||
|
|
||||||
Sven Heidemann - sven.heidemann@sh-edraft.de
|
|
||||||
|
|
||||||
Project link: [https://git.sh-edraft.de/sh-edraft.de/sh_common_py_lib](https://git.sh-edraft.de/sh-edraft.de/sh_cpl)
|
|
||||||
|
|
||||||
<!-- External LINKS -->
|
|
||||||
[pip_url]: https://pip.sh-edraft.de
|
|
||||||
[python]: https://www.python.org/
|
|
||||||
[pip]: https://pypi.org/project/pip/
|
|
||||||
|
|
||||||
<!-- Internal LINKS -->
|
|
||||||
[project]: https://git.sh-edraft.de/sh-edraft.de/sh_cpl
|
|
||||||
[quickstart]: https://git.sh-edraft.de/sh-edraft.de/sh_cpl/wiki/quickstart
|
|
||||||
[contributing]: https://git.sh-edraft.de/sh-edraft.de/sh_cpl/wiki/contributing
|
|
||||||
[license]: LICENSE
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
{
|
|
||||||
"WorkspaceSettings": {
|
|
||||||
"DefaultProject": "cpl-core",
|
|
||||||
"Projects": {
|
|
||||||
"cpl-cli": "src/cpl_cli/cpl-cli.json",
|
|
||||||
"cpl-core": "src/cpl_core/cpl-core.json",
|
|
||||||
"cpl-discord": "src/cpl_discord/cpl-discord.json",
|
|
||||||
"cpl-query": "src/cpl_query/cpl-query.json",
|
|
||||||
"cpl-translation": "src/cpl_translation/cpl-translation.json",
|
|
||||||
"set-version": "tools/set_version/set-version.json",
|
|
||||||
"set-pip-urls": "tools/set_pip_urls/set-pip-urls.json",
|
|
||||||
"unittests": "unittests/unittests/unittests.json",
|
|
||||||
"unittests_cli": "unittests/unittests_cli/unittests_cli.json",
|
|
||||||
"unittests_core": "unittests/unittests_core/unittests_core.json",
|
|
||||||
"unittests_query": "unittests/unittests_query/unittests_query.json",
|
|
||||||
"unittests_shared": "unittests/unittests_shared/unittests_shared.json",
|
|
||||||
"unittests_translation": "unittests/unittests_translation/unittests_translation.json"
|
|
||||||
},
|
|
||||||
"Scripts": {
|
|
||||||
"hello-world": "echo 'Hello World'",
|
|
||||||
|
|
||||||
"format": "echo 'Formatting:'; black ./",
|
|
||||||
|
|
||||||
"sv": "cpl set-version",
|
|
||||||
"set-version": "cpl run set-version --dev $ARGS; echo '';",
|
|
||||||
|
|
||||||
"spu": "cpl set-pip-urls",
|
|
||||||
"set-pip-urls": "cpl run set-pip-urls --dev $ARGS; echo '';",
|
|
||||||
|
|
||||||
"docs-build": "cpl format; echo 'Build Documentation'; cpl db-core; cpl db-discord; cpl db-query; cpl db-translation; cd docs/; make clean; make html;",
|
|
||||||
"db-core": "cd docs/; sphinx-apidoc -o source/ ../src/cpl_core; cd ../",
|
|
||||||
"db-discord": "cd docs/; sphinx-apidoc -o source/ ../src/cpl_discord; cd ../",
|
|
||||||
"db-query": "cd docs/; sphinx-apidoc -o source/ ../src/cpl_query; cd ../",
|
|
||||||
"db-translation": "cd docs/; sphinx-apidoc -o source/ ../src/cpl_translation; cd ../",
|
|
||||||
"db": "cpl docs-build",
|
|
||||||
|
|
||||||
"docs-open": "xdg-open $PWD/docs/build/html/index.html &",
|
|
||||||
"do": "cpl docs-open",
|
|
||||||
|
|
||||||
"test": "cpl run unittests",
|
|
||||||
|
|
||||||
"pre-build-all": "cpl sv $ARGS; cpl spu $ARGS;",
|
|
||||||
"build-all": "cpl build-cli; cpl build-core; cpl build-discord; cpl build-query; cpl build-translation; cpl build-set-pip-urls; cpl build-set-version",
|
|
||||||
"ba": "cpl build-all $ARGS",
|
|
||||||
"build-cli": "echo 'Build cpl-cli'; cd ./src/cpl_cli; cpl build; cd ../../;",
|
|
||||||
"build-core": "echo 'Build cpl-core'; cd ./src/cpl_core; cpl build; cd ../../;",
|
|
||||||
"build-discord": "echo 'Build cpl-discord'; cd ./src/cpl_discord; cpl build; cd ../../;",
|
|
||||||
"build-query": "echo 'Build cpl-query'; cd ./src/cpl_query; cpl build; cd ../../;",
|
|
||||||
"build-translation": "echo 'Build cpl-translation'; cd ./src/cpl_translation; cpl build; cd ../../;",
|
|
||||||
"build-set-pip-urls": "echo 'Build set-pip-urls'; cd ./tools/set_pip_urls; cpl build; cd ../../;",
|
|
||||||
"build-set-version": "echo 'Build set-version'; cd ./tools/set_version; cpl build; cd ../../;",
|
|
||||||
|
|
||||||
"pre-publish-all": "cpl sv $ARGS; cpl spu $ARGS;",
|
|
||||||
"publish-all": "cpl publish-cli; cpl publish-core; cpl publish-discord; cpl publish-query; cpl publish-translation;",
|
|
||||||
"pa": "cpl publish-all $ARGS",
|
|
||||||
"publish-cli": "echo 'Publish cpl-cli'; cd ./src/cpl_cli; cpl publish; cd ../../;",
|
|
||||||
"publish-core": "echo 'Publish cpl-core'; cd ./src/cpl_core; cpl publish; cd ../../;",
|
|
||||||
"publish-discord": "echo 'Publish cpl-discord'; cd ./src/cpl_discord; cpl publish; cd ../../;",
|
|
||||||
"publish-query": "echo 'Publish cpl-query'; cd ./src/cpl_query; cpl publish; cd ../../;",
|
|
||||||
"publish-translation": "echo 'Publish cpl-translation'; cd ./src/cpl_translation; cpl publish; cd ../../;",
|
|
||||||
|
|
||||||
"upload-prod-cli": "echo 'PROD Upload cpl-cli'; cpl upl-prod-cli;",
|
|
||||||
"upl-prod-cli": "twine upload -r pip.sh-edraft.de dist/cpl-cli/publish/setup/*",
|
|
||||||
|
|
||||||
"upload-prod-core": "echo 'PROD Upload cpl-core'; cpl upl-prod-core;",
|
|
||||||
"upl-prod-core": "twine upload -r pip.sh-edraft.de dist/cpl-core/publish/setup/*",
|
|
||||||
|
|
||||||
"upload-prod-discord": "echo 'PROD Upload cpl-discord'; cpl upl-prod-discord;",
|
|
||||||
"upl-prod-discord": "twine upload -r pip.sh-edraft.de dist/cpl-discord/publish/setup/*",
|
|
||||||
|
|
||||||
"upload-prod-query": "echo 'PROD Upload cpl-query'; cpl upl-prod-query;",
|
|
||||||
"upl-prod-query": "twine upload -r pip.sh-edraft.de dist/cpl-query/publish/setup/*",
|
|
||||||
|
|
||||||
"upload-prod-translation": "echo 'PROD Upload cpl-translation'; cpl upl-prod-translation;",
|
|
||||||
"upl-prod-translation": "twine upload -r pip.sh-edraft.de dist/cpl-translation/publish/setup/*",
|
|
||||||
|
|
||||||
"upload-exp-cli": "echo 'EXP Upload cpl-cli'; cpl upl-exp-cli;",
|
|
||||||
"upl-exp-cli": "twine upload -r pip-exp.sh-edraft.de dist/cpl-cli/publish/setup/*",
|
|
||||||
|
|
||||||
"upload-exp-core": "echo 'EXP Upload cpl-core'; cpl upl-exp-core;",
|
|
||||||
"upl-exp-core": "twine upload -r pip-exp.sh-edraft.de dist/cpl-core/publish/setup/*",
|
|
||||||
|
|
||||||
"upload-exp-discord": "echo 'EXP Upload cpl-discord'; cpl upl-exp-discord;",
|
|
||||||
"upl-exp-discord": "twine upload -r pip-exp.sh-edraft.de dist/cpl-discord/publish/setup/*",
|
|
||||||
|
|
||||||
"upload-exp-query": "echo 'EXP Upload cpl-query'; cpl upl-exp-query;",
|
|
||||||
"upl-exp-query": "twine upload -r pip-exp.sh-edraft.de dist/cpl-query/publish/setup/*",
|
|
||||||
|
|
||||||
"upload-exp-translation": "echo 'EXP Upload cpl-translation'; cpl upl-exp-translation;",
|
|
||||||
"upl-exp-translation": "twine upload -r pip-exp.sh-edraft.de dist/cpl-translation/publish/setup/*",
|
|
||||||
|
|
||||||
"upload-dev-cli": "echo 'DEV Upload cpl-cli'; cpl upl-dev-cli;",
|
|
||||||
"upl-dev-cli": "twine upload -r pip-dev.sh-edraft.de dist/cpl-cli/publish/setup/*",
|
|
||||||
|
|
||||||
"upload-dev-core": "echo 'DEV Upload cpl-core'; cpl upl-dev-core;",
|
|
||||||
"upl-dev-core": "twine upload -r pip-dev.sh-edraft.de dist/cpl-core/publish/setup/*",
|
|
||||||
|
|
||||||
"upload-dev-discord": "echo 'DEV Upload cpl-discord'; cpl upl-dev-discord;",
|
|
||||||
"upl-dev-discord": "twine upload -r pip-dev.sh-edraft.de dist/cpl-discord/publish/setup/*",
|
|
||||||
|
|
||||||
"upload-dev-query": "echo 'DEV Upload cpl-query'; cpl upl-dev-query;",
|
|
||||||
"upl-dev-query": "twine upload -r pip-dev.sh-edraft.de dist/cpl-query/publish/setup/*",
|
|
||||||
|
|
||||||
"upload-dev-translation": "echo 'DEV Upload cpl-translation'; cpl upl-dev-translation;",
|
|
||||||
"upl-dev-translation": "twine upload -r pip-dev.sh-edraft.de dist/cpl-translation/publish/setup/*",
|
|
||||||
|
|
||||||
"pre-deploy-prod": "cpl sv $ARGS; cpl spu --environment=production;",
|
|
||||||
"deploy-prod": "cpl deploy-prod-cli; cpl deploy-prod-core; cpl deploy-prod-discord; cpl deploy-prod-query; cpl deploy-prod-translation;",
|
|
||||||
"dp": "cpl deploy-prod $ARGS",
|
|
||||||
"deploy-prod-cli": "cpl publish-cli; cpl upload-prod-cli",
|
|
||||||
"deploy-prod-core": "cpl publish-core; cpl upload-prod-core",
|
|
||||||
"deploy-prod-query": "cpl publish-query; cpl upload-prod-query",
|
|
||||||
"deploy-prod-discord": "cpl publish-discord; cpl upload-prod-discord",
|
|
||||||
"deploy-prod-translation": "cpl publish-translation; cpl upload-prod-translation",
|
|
||||||
|
|
||||||
"pre-deploy-exp": "cpl sv $ARGS; cpl spu --environment=staging;",
|
|
||||||
"deploy-exp": "cpl deploy-exp-cli; cpl deploy-exp-core; cpl deploy-exp-discord; cpl deploy-exp-query; cpl deploy-exp-translation;",
|
|
||||||
"de": "cpl deploy-exp $ARGS",
|
|
||||||
"deploy-exp-cli": "cpl publish-cli; cpl upload-exp-cli",
|
|
||||||
"deploy-exp-core": "cpl publish-core; cpl upload-exp-core",
|
|
||||||
"deploy-exp-discord": "cpl publish-discord; cpl upload-exp-discord",
|
|
||||||
"deploy-exp-query": "cpl publish-query; cpl upload-exp-query",
|
|
||||||
"deploy-exp-translation": "cpl publish-translation; cpl upload-exp-translation",
|
|
||||||
|
|
||||||
"pre-deploy-dev": "cpl sv $ARGS; cpl spu --environment=development;",
|
|
||||||
"deploy-dev": "cpl deploy-dev-cli; cpl deploy-dev-core; cpl deploy-dev-discord; cpl deploy-dev-query; cpl deploy-dev-translation;",
|
|
||||||
"dd": "cpl deploy-dev $ARGS",
|
|
||||||
"deploy-dev-cli": "cpl publish-cli; cpl upload-dev-cli",
|
|
||||||
"deploy-dev-core": "cpl publish-core; cpl upload-dev-core",
|
|
||||||
"deploy-dev-discord": "cpl publish-discord; cpl upload-dev-discord",
|
|
||||||
"deploy-dev-query": "cpl publish-query; cpl upload-dev-query",
|
|
||||||
"deploy-dev-translation": "cpl publish-query; cpl upload-dev-translation",
|
|
||||||
|
|
||||||
"dev-install": "cpl di-core; cpl di-cli; cpl di-query; cpl di-translation;",
|
|
||||||
"di": "cpl dev-install",
|
|
||||||
"di-core": "pip install cpl-core --pre --upgrade --extra-index-url https://pip-dev.sh-edraft.de",
|
|
||||||
"di-cli": "pip install cpl-cli --pre --upgrade --extra-index-url https://pip-dev.sh-edraft.de",
|
|
||||||
"di-discord": "pip install cpl-discord --pre --upgrade --extra-index-url https://pip-dev.sh-edraft.de",
|
|
||||||
"di-query": "pip install cpl-query --pre --upgrade --extra-index-url https://pip-dev.sh-edraft.de",
|
|
||||||
"di-translation": "pip install cpl-translation --pre --upgrade --extra-index-url https://pip-dev.sh-edraft.de",
|
|
||||||
|
|
||||||
"prod-install": "cpl pi-core; cpl pi-cli; cpl pi-query; cpl pi-translation;",
|
|
||||||
"pi": "cpl prod-install",
|
|
||||||
"pi-core": "pip install cpl-core --pre --upgrade --extra-index-url https://pip.sh-edraft.de",
|
|
||||||
"pi-cli": "pip install cpl-cli --pre --upgrade --extra-index-url https://pip.sh-edraft.de",
|
|
||||||
"pi-discord": "pip install cpl-discord --pre --upgrade --extra-index-url https://pip.sh-edraft.de",
|
|
||||||
"pi-query": "pip install cpl-query --pre --upgrade --extra-index-url https://pip.sh-edraft.de",
|
|
||||||
"pi-translation": "pip install cpl-translation --pre --upgrade --extra-index-url https://pip.sh-edraft.de"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
12
cpl.workspace.json
Normal file
12
cpl.workspace.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "cpl",
|
||||||
|
"projects": [
|
||||||
|
"src/cli/cpl.project.json",
|
||||||
|
"src/core/cpl.project.json",
|
||||||
|
"src/mail/cpl.project.json"
|
||||||
|
],
|
||||||
|
"defaultProject": "cpl-cli",
|
||||||
|
"scripts": {
|
||||||
|
"format": "black src"
|
||||||
|
}
|
||||||
|
}
|
||||||
8
example/api/src/appsettings.development.json
Normal file
8
example/api/src/appsettings.development.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"Path": "logs/",
|
||||||
|
"Filename": "log_$start_time.log",
|
||||||
|
"ConsoleLevel": "TRACE",
|
||||||
|
"Level": "TRACE"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,23 +1,24 @@
|
|||||||
{
|
{
|
||||||
"TimeFormatSettings": {
|
"TimeFormat": {
|
||||||
"DateFormat": "%Y-%m-%d",
|
"DateFormat": "%Y-%m-%d",
|
||||||
"TimeFormat": "%H:%M:%S",
|
"TimeFormat": "%H:%M:%S",
|
||||||
"DateTimeFormat": "%Y-%m-%d %H:%M:%S.%f",
|
"DateTimeFormat": "%Y-%m-%d %H:%M:%S.%f",
|
||||||
"DateTimeLogFormat": "%Y-%m-%d_%H-%M-%S"
|
"DateTimeLogFormat": "%Y-%m-%d_%H-%M-%S"
|
||||||
},
|
},
|
||||||
|
|
||||||
"LoggingSettings": {
|
"Log": {
|
||||||
"Path": "logs/",
|
"Path": "logs/",
|
||||||
"Filename": "log_$start_time.log",
|
"Filename": "log_$start_time.log",
|
||||||
"ConsoleLogLevel": "TRACE",
|
"ConsoleLevel": "TRACE",
|
||||||
"FileLogLevel": "TRACE"
|
"Level": "TRACE"
|
||||||
},
|
},
|
||||||
|
|
||||||
"DatabaseSettings": {
|
"Database": {
|
||||||
"Host": "localhost",
|
"Host": "localhost",
|
||||||
"User": "sh_cpl",
|
"User": "cpl",
|
||||||
"Password": "MHZhc0Y2bjhKc1VUMWV0Qw==",
|
"Port": 3306,
|
||||||
"Database": "sh_cpl",
|
"Password": "cpl",
|
||||||
|
"Database": "cpl",
|
||||||
"Charset": "utf8mb4",
|
"Charset": "utf8mb4",
|
||||||
"UseUnicode": "true",
|
"UseUnicode": "true",
|
||||||
"Buffered": "true"
|
"Buffered": "true"
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
"TimeFormatSettings": {
|
"TimeFormat": {
|
||||||
"DateFormat": "%Y-%m-%d",
|
"DateFormat": "%Y-%m-%d",
|
||||||
"TimeFormat": "%H:%M:%S",
|
"TimeFormat": "%H:%M:%S",
|
||||||
"DateTimeFormat": "%Y-%m-%d %H:%M:%S.%f",
|
"DateTimeFormat": "%Y-%m-%d %H:%M:%S.%f",
|
||||||
"DateTimeLogFormat": "%Y-%m-%d_%H-%M-%S"
|
"DateTimeLogFormat": "%Y-%m-%d_%H-%M-%S"
|
||||||
},
|
},
|
||||||
|
|
||||||
"LoggingSettings": {
|
"Log": {
|
||||||
"Path": "logs/",
|
"Path": "logs/",
|
||||||
"Filename": "log_$start_time.log",
|
"Filename": "log_$start_time.log",
|
||||||
"ConsoleLogLevel": "ERROR",
|
"ConsoleLevel": "ERROR",
|
||||||
"FileLogLevel": "WARN"
|
"Level": "WARNING"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
147
example/api/src/main.py
Normal file
147
example/api/src/main.py
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
from starlette.responses import JSONResponse
|
||||||
|
|
||||||
|
from cpl.dependency.event_bus import EventBusABC
|
||||||
|
from cpl.graphql.event_bus.memory import InMemoryEventBus
|
||||||
|
from queries.cities import CityGraphType, CityFilter, CitySort
|
||||||
|
from queries.hello import UserGraphType # , UserFilter, UserSort, UserGraphType
|
||||||
|
from queries.user import UserFilter, UserSort
|
||||||
|
from cpl.api.api_module import ApiModule
|
||||||
|
from cpl.application.application_builder import ApplicationBuilder
|
||||||
|
from cpl.auth.schema import User, Role
|
||||||
|
from cpl.core.configuration import Configuration
|
||||||
|
from cpl.core.console import Console
|
||||||
|
from cpl.core.environment import Environment
|
||||||
|
from cpl.core.utils.cache import Cache
|
||||||
|
from cpl.database.mysql.mysql_module import MySQLModule
|
||||||
|
from cpl.graphql.application.graphql_app import GraphQLApp
|
||||||
|
from cpl.graphql.auth.graphql_auth_module import GraphQLAuthModule
|
||||||
|
from cpl.graphql.graphql_module import GraphQLModule
|
||||||
|
from model.author_dao import AuthorDao
|
||||||
|
from model.author_query import AuthorGraphType, AuthorFilter, AuthorSort
|
||||||
|
from model.post_dao import PostDao
|
||||||
|
from model.post_query import PostFilter, PostSort, PostGraphType, PostMutation, PostSubscription
|
||||||
|
from permissions import PostPermissions
|
||||||
|
from queries.hello import HelloQuery
|
||||||
|
from scoped_service import ScopedService
|
||||||
|
from service import PingService
|
||||||
|
from test_data_seeder import TestDataSeeder
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
builder = ApplicationBuilder[GraphQLApp](GraphQLApp)
|
||||||
|
|
||||||
|
Configuration.add_json_file(f"appsettings.json")
|
||||||
|
Configuration.add_json_file(f"appsettings.{Environment.get_environment()}.json")
|
||||||
|
Configuration.add_json_file(f"appsettings.{Environment.get_host_name()}.json", optional=True)
|
||||||
|
|
||||||
|
# builder.services.add_logging()
|
||||||
|
(
|
||||||
|
builder.services.add_structured_logging()
|
||||||
|
.add_transient(PingService)
|
||||||
|
.add_module(MySQLModule)
|
||||||
|
.add_module(ApiModule)
|
||||||
|
.add_module(GraphQLModule)
|
||||||
|
.add_module(GraphQLAuthModule)
|
||||||
|
.add_scoped(ScopedService)
|
||||||
|
.add_singleton(EventBusABC, InMemoryEventBus)
|
||||||
|
.add_cache(User)
|
||||||
|
.add_cache(Role)
|
||||||
|
.add_transient(CityGraphType)
|
||||||
|
.add_transient(CityFilter)
|
||||||
|
.add_transient(CitySort)
|
||||||
|
.add_transient(UserGraphType)
|
||||||
|
.add_transient(UserFilter)
|
||||||
|
.add_transient(UserSort)
|
||||||
|
# .add_transient(UserGraphType)
|
||||||
|
# .add_transient(UserFilter)
|
||||||
|
# .add_transient(UserSort)
|
||||||
|
.add_transient(HelloQuery)
|
||||||
|
# test data
|
||||||
|
.add_singleton(TestDataSeeder)
|
||||||
|
# authors
|
||||||
|
.add_transient(AuthorDao)
|
||||||
|
.add_transient(AuthorGraphType)
|
||||||
|
.add_transient(AuthorFilter)
|
||||||
|
.add_transient(AuthorSort)
|
||||||
|
# posts
|
||||||
|
.add_transient(PostDao)
|
||||||
|
.add_transient(PostGraphType)
|
||||||
|
.add_transient(PostFilter)
|
||||||
|
.add_transient(PostSort)
|
||||||
|
.add_transient(PostMutation)
|
||||||
|
.add_transient(PostSubscription)
|
||||||
|
)
|
||||||
|
|
||||||
|
app = builder.build()
|
||||||
|
app.with_logging()
|
||||||
|
app.with_migrations("./scripts")
|
||||||
|
|
||||||
|
app.with_authentication()
|
||||||
|
app.with_authorization()
|
||||||
|
|
||||||
|
app.with_route(
|
||||||
|
path="/route1",
|
||||||
|
fn=lambda r: JSONResponse("route1"),
|
||||||
|
method="GET",
|
||||||
|
# authentication=True,
|
||||||
|
# permissions=[Permissions.administrator],
|
||||||
|
)
|
||||||
|
app.with_routes_directory("routes")
|
||||||
|
|
||||||
|
schema = app.with_graphql()
|
||||||
|
schema.query.string_field("ping", resolver=lambda: "pong")
|
||||||
|
schema.query.with_query("hello", HelloQuery)
|
||||||
|
schema.query.dao_collection_field(AuthorGraphType, AuthorDao, "authors", AuthorFilter, AuthorSort)
|
||||||
|
(
|
||||||
|
schema.query.dao_collection_field(PostGraphType, PostDao, "posts", PostFilter, PostSort)
|
||||||
|
# .with_require_any_permission(PostPermissions.read)
|
||||||
|
.with_public()
|
||||||
|
)
|
||||||
|
|
||||||
|
schema.mutation.with_mutation("post", PostMutation).with_public()
|
||||||
|
|
||||||
|
schema.subscription.with_subscription(PostSubscription)
|
||||||
|
|
||||||
|
app.with_auth_root_queries(True)
|
||||||
|
app.with_auth_root_mutations(True)
|
||||||
|
|
||||||
|
app.with_playground()
|
||||||
|
app.with_graphiql()
|
||||||
|
|
||||||
|
app.with_permissions(PostPermissions)
|
||||||
|
|
||||||
|
provider = builder.service_provider
|
||||||
|
user_cache = provider.get_service(Cache[User])
|
||||||
|
role_cache = provider.get_service(Cache[Role])
|
||||||
|
|
||||||
|
if role_cache == user_cache:
|
||||||
|
raise Exception("Cache service is not working")
|
||||||
|
|
||||||
|
s1 = provider.get_service(ScopedService)
|
||||||
|
s2 = provider.get_service(ScopedService)
|
||||||
|
|
||||||
|
if s1.name == s2.name:
|
||||||
|
raise Exception("Scoped service is not working")
|
||||||
|
|
||||||
|
with provider.create_scope() as scope:
|
||||||
|
s3 = scope.get_service(ScopedService)
|
||||||
|
s4 = scope.get_service(ScopedService)
|
||||||
|
|
||||||
|
if s3.name != s4.name:
|
||||||
|
raise Exception("Scoped service is not working")
|
||||||
|
|
||||||
|
if s1.name == s3.name:
|
||||||
|
raise Exception("Scoped service is not working")
|
||||||
|
|
||||||
|
Console.write_line(
|
||||||
|
s1.name,
|
||||||
|
s2.name,
|
||||||
|
s3.name,
|
||||||
|
s4.name,
|
||||||
|
)
|
||||||
|
|
||||||
|
app.run()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
30
example/api/src/model/author.py
Normal file
30
example/api/src/model/author.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from typing import Self
|
||||||
|
|
||||||
|
from cpl.core.typing import SerialId
|
||||||
|
from cpl.database.abc import DbModelABC
|
||||||
|
|
||||||
|
|
||||||
|
class Author(DbModelABC[Self]):
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
id: int,
|
||||||
|
first_name: str,
|
||||||
|
last_name: str,
|
||||||
|
deleted: bool = False,
|
||||||
|
editor_id: SerialId | None = None,
|
||||||
|
created: datetime | None = None,
|
||||||
|
updated: datetime | None = None,
|
||||||
|
):
|
||||||
|
DbModelABC.__init__(self, id, deleted, editor_id, created, updated)
|
||||||
|
self._first_name = first_name
|
||||||
|
self._last_name = last_name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def first_name(self) -> str:
|
||||||
|
return self._first_name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def last_name(self) -> str:
|
||||||
|
return self._last_name
|
||||||
11
example/api/src/model/author_dao.py
Normal file
11
example/api/src/model/author_dao.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from cpl.database.abc import DbModelDaoABC
|
||||||
|
from model.author import Author
|
||||||
|
|
||||||
|
|
||||||
|
class AuthorDao(DbModelDaoABC):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
DbModelDaoABC.__init__(self, Author, "authors")
|
||||||
|
|
||||||
|
self.attribute(Author.first_name, str, db_name="firstname")
|
||||||
|
self.attribute(Author.last_name, str, db_name="lastname")
|
||||||
37
example/api/src/model/author_query.py
Normal file
37
example/api/src/model/author_query.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
from cpl.graphql.schema.db_model_graph_type import DbModelGraphType
|
||||||
|
from cpl.graphql.schema.filter.db_model_filter import DbModelFilter
|
||||||
|
from cpl.graphql.schema.sort.sort import Sort
|
||||||
|
from cpl.graphql.schema.sort.sort_order import SortOrder
|
||||||
|
from model.author import Author
|
||||||
|
|
||||||
|
class AuthorFilter(DbModelFilter[Author]):
|
||||||
|
def __init__(self):
|
||||||
|
DbModelFilter.__init__(self, public=True)
|
||||||
|
self.int_field("id")
|
||||||
|
self.string_field("firstName")
|
||||||
|
self.string_field("lastName")
|
||||||
|
|
||||||
|
class AuthorSort(Sort[Author]):
|
||||||
|
def __init__(self):
|
||||||
|
Sort.__init__(self)
|
||||||
|
self.field("id", SortOrder)
|
||||||
|
self.field("firstName", SortOrder)
|
||||||
|
self.field("lastName", SortOrder)
|
||||||
|
|
||||||
|
class AuthorGraphType(DbModelGraphType[Author]):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
DbModelGraphType.__init__(self, public=True)
|
||||||
|
|
||||||
|
self.int_field(
|
||||||
|
"id",
|
||||||
|
resolver=lambda root: root.id,
|
||||||
|
).with_public(True)
|
||||||
|
self.string_field(
|
||||||
|
"firstName",
|
||||||
|
resolver=lambda root: root.first_name,
|
||||||
|
).with_public(True)
|
||||||
|
self.string_field(
|
||||||
|
"lastName",
|
||||||
|
resolver=lambda root: root.last_name,
|
||||||
|
).with_public(True)
|
||||||
44
example/api/src/model/post.py
Normal file
44
example/api/src/model/post.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from typing import Self
|
||||||
|
|
||||||
|
from cpl.core.typing import SerialId
|
||||||
|
from cpl.database.abc import DbModelABC
|
||||||
|
|
||||||
|
|
||||||
|
class Post(DbModelABC[Self]):
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
id: int,
|
||||||
|
author_id: SerialId,
|
||||||
|
title: str,
|
||||||
|
content: str,
|
||||||
|
deleted: bool = False,
|
||||||
|
editor_id: SerialId | None = None,
|
||||||
|
created: datetime | None = None,
|
||||||
|
updated: datetime | None = None,
|
||||||
|
):
|
||||||
|
DbModelABC.__init__(self, id, deleted, editor_id, created, updated)
|
||||||
|
self._author_id = author_id
|
||||||
|
self._title = title
|
||||||
|
self._content = content
|
||||||
|
|
||||||
|
@property
|
||||||
|
def author_id(self) -> SerialId:
|
||||||
|
return self._author_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def title(self) -> str:
|
||||||
|
return self._title
|
||||||
|
|
||||||
|
@title.setter
|
||||||
|
def title(self, value: str):
|
||||||
|
self._title = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def content(self) -> str:
|
||||||
|
return self._content
|
||||||
|
|
||||||
|
@content.setter
|
||||||
|
def content(self, value: str):
|
||||||
|
self._content = value
|
||||||
15
example/api/src/model/post_dao.py
Normal file
15
example/api/src/model/post_dao.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
from cpl.database.abc import DbModelDaoABC
|
||||||
|
from model.author_dao import AuthorDao
|
||||||
|
from model.post import Post
|
||||||
|
|
||||||
|
|
||||||
|
class PostDao(DbModelDaoABC[Post]):
|
||||||
|
|
||||||
|
def __init__(self, authors: AuthorDao):
|
||||||
|
DbModelDaoABC.__init__(self, Post, "posts")
|
||||||
|
|
||||||
|
self.attribute(Post.author_id, int, db_name="authorId")
|
||||||
|
self.reference("author", "id", Post.author_id, "authors", authors)
|
||||||
|
|
||||||
|
self.attribute(Post.title, str)
|
||||||
|
self.attribute(Post.content, str)
|
||||||
148
example/api/src/model/post_query.py
Normal file
148
example/api/src/model/post_query.py
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
from cpl.dependency.event_bus import EventBusABC
|
||||||
|
from cpl.graphql.query_context import QueryContext
|
||||||
|
from cpl.graphql.schema.db_model_graph_type import DbModelGraphType
|
||||||
|
from cpl.graphql.schema.filter.db_model_filter import DbModelFilter
|
||||||
|
from cpl.graphql.schema.input import Input
|
||||||
|
from cpl.graphql.schema.mutation import Mutation
|
||||||
|
from cpl.graphql.schema.sort.sort import Sort
|
||||||
|
from cpl.graphql.schema.sort.sort_order import SortOrder
|
||||||
|
from cpl.graphql.schema.subscription import Subscription
|
||||||
|
from model.author_dao import AuthorDao
|
||||||
|
from model.author_query import AuthorGraphType, AuthorFilter
|
||||||
|
from model.post import Post
|
||||||
|
from model.post_dao import PostDao
|
||||||
|
|
||||||
|
|
||||||
|
class PostFilter(DbModelFilter[Post]):
|
||||||
|
def __init__(self):
|
||||||
|
DbModelFilter.__init__(self, public=True)
|
||||||
|
self.int_field("id")
|
||||||
|
self.filter_field("author", AuthorFilter)
|
||||||
|
self.string_field("title")
|
||||||
|
self.string_field("content")
|
||||||
|
|
||||||
|
|
||||||
|
class PostSort(Sort[Post]):
|
||||||
|
def __init__(self):
|
||||||
|
Sort.__init__(self)
|
||||||
|
self.field("id", SortOrder)
|
||||||
|
self.field("title", SortOrder)
|
||||||
|
self.field("content", SortOrder)
|
||||||
|
|
||||||
|
|
||||||
|
class PostGraphType(DbModelGraphType[Post]):
|
||||||
|
|
||||||
|
def __init__(self, authors: AuthorDao):
|
||||||
|
DbModelGraphType.__init__(self, public=True)
|
||||||
|
|
||||||
|
self.int_field(
|
||||||
|
"id",
|
||||||
|
resolver=lambda root: root.id,
|
||||||
|
).with_optional().with_public(True)
|
||||||
|
|
||||||
|
async def _a(root: Post):
|
||||||
|
return await authors.get_by_id(root.author_id)
|
||||||
|
|
||||||
|
def r_name(ctx: QueryContext):
|
||||||
|
return ctx.user.username == "admin"
|
||||||
|
|
||||||
|
self.object_field("author", AuthorGraphType, resolver=_a).with_public(True) # .with_require_any([], [r_name]))
|
||||||
|
self.string_field(
|
||||||
|
"title",
|
||||||
|
resolver=lambda root: root.title,
|
||||||
|
).with_public(True)
|
||||||
|
self.string_field(
|
||||||
|
"content",
|
||||||
|
resolver=lambda root: root.content,
|
||||||
|
).with_public(True)
|
||||||
|
|
||||||
|
|
||||||
|
class PostCreateInput(Input[Post]):
|
||||||
|
title: str
|
||||||
|
content: str
|
||||||
|
author_id: int
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
Input.__init__(self)
|
||||||
|
self.string_field("title").with_required()
|
||||||
|
self.string_field("content").with_required()
|
||||||
|
self.int_field("author_id").with_required()
|
||||||
|
|
||||||
|
|
||||||
|
class PostUpdateInput(Input[Post]):
|
||||||
|
title: str
|
||||||
|
content: str
|
||||||
|
author_id: int
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
Input.__init__(self)
|
||||||
|
self.int_field("id").with_required()
|
||||||
|
self.string_field("title").with_required(False)
|
||||||
|
self.string_field("content").with_required(False)
|
||||||
|
|
||||||
|
|
||||||
|
class PostSubscription(Subscription):
|
||||||
|
def __init__(self, bus: EventBusABC):
|
||||||
|
Subscription.__init__(self)
|
||||||
|
self._bus = bus
|
||||||
|
|
||||||
|
def selector(event: Post, info) -> bool:
|
||||||
|
return event.id == 101
|
||||||
|
|
||||||
|
self.subscription_field("postChange", PostGraphType, selector).with_public()
|
||||||
|
|
||||||
|
|
||||||
|
class PostMutation(Mutation):
|
||||||
|
|
||||||
|
def __init__(self, posts: PostDao, authors: AuthorDao, bus: EventBusABC):
|
||||||
|
Mutation.__init__(self)
|
||||||
|
|
||||||
|
self._posts = posts
|
||||||
|
self._authors = authors
|
||||||
|
self._bus = bus
|
||||||
|
|
||||||
|
self.field("create", int, resolver=self.create_post).with_public().with_required().with_argument(
|
||||||
|
"input",
|
||||||
|
PostCreateInput,
|
||||||
|
).with_required()
|
||||||
|
self.field("update", bool, resolver=self.update_post).with_public().with_required().with_argument(
|
||||||
|
"input",
|
||||||
|
PostUpdateInput,
|
||||||
|
).with_required()
|
||||||
|
self.field("delete", bool, resolver=self.delete_post).with_public().with_required().with_argument(
|
||||||
|
"id",
|
||||||
|
int,
|
||||||
|
).with_required()
|
||||||
|
self.field("restore", bool, resolver=self.restore_post).with_public().with_required().with_argument(
|
||||||
|
"id",
|
||||||
|
int,
|
||||||
|
).with_required()
|
||||||
|
|
||||||
|
async def create_post(self, input: PostCreateInput) -> int:
|
||||||
|
return await self._posts.create(Post(0, input.author_id, input.title, input.content))
|
||||||
|
|
||||||
|
async def update_post(self, input: PostUpdateInput) -> bool:
|
||||||
|
post = await self._posts.get_by_id(input.id)
|
||||||
|
if post is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
post.title = input.title if input.title is not None else post.title
|
||||||
|
post.content = input.content if input.content is not None else post.content
|
||||||
|
|
||||||
|
await self._posts.update(post)
|
||||||
|
await self._bus.publish("postChange", post)
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def delete_post(self, id: int) -> bool:
|
||||||
|
post = await self._posts.get_by_id(id)
|
||||||
|
if post is None:
|
||||||
|
return False
|
||||||
|
await self._posts.delete(post)
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def restore_post(self, id: int) -> bool:
|
||||||
|
post = await self._posts.get_by_id(id)
|
||||||
|
if post is None:
|
||||||
|
return False
|
||||||
|
await self._posts.restore(post)
|
||||||
|
return True
|
||||||
8
example/api/src/permissions.py
Normal file
8
example/api/src/permissions.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class PostPermissions(Enum):
|
||||||
|
|
||||||
|
read = "post.read"
|
||||||
|
write = "post.write"
|
||||||
|
delete = "post.delete"
|
||||||
39
example/api/src/queries/cities.py
Normal file
39
example/api/src/queries/cities.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
from cpl.graphql.schema.filter.filter import Filter
|
||||||
|
from cpl.graphql.schema.graph_type import GraphType
|
||||||
|
|
||||||
|
from cpl.graphql.schema.sort.sort import Sort
|
||||||
|
from cpl.graphql.schema.sort.sort_order import SortOrder
|
||||||
|
|
||||||
|
|
||||||
|
class City:
|
||||||
|
def __init__(self, id: int, name: str):
|
||||||
|
self.id = id
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
|
||||||
|
class CityFilter(Filter[City]):
|
||||||
|
def __init__(self):
|
||||||
|
Filter.__init__(self)
|
||||||
|
self.field("id", int)
|
||||||
|
self.field("name", str)
|
||||||
|
|
||||||
|
|
||||||
|
class CitySort(Sort[City]):
|
||||||
|
def __init__(self):
|
||||||
|
Sort.__init__(self)
|
||||||
|
self.field("id", SortOrder)
|
||||||
|
self.field("name", SortOrder)
|
||||||
|
|
||||||
|
|
||||||
|
class CityGraphType(GraphType[City]):
|
||||||
|
def __init__(self):
|
||||||
|
GraphType.__init__(self)
|
||||||
|
|
||||||
|
self.int_field(
|
||||||
|
"id",
|
||||||
|
resolver=lambda root: root.id,
|
||||||
|
)
|
||||||
|
self.string_field(
|
||||||
|
"name",
|
||||||
|
resolver=lambda root: root.name,
|
||||||
|
)
|
||||||
70
example/api/src/queries/hello.py
Normal file
70
example/api/src/queries/hello.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
from queries.cities import CityFilter, CitySort, CityGraphType, City
|
||||||
|
from queries.user import User, UserFilter, UserSort, UserGraphType
|
||||||
|
from cpl.api.middleware.request import get_request
|
||||||
|
from cpl.auth.schema import UserDao, User
|
||||||
|
from cpl.graphql.schema.filter.filter import Filter
|
||||||
|
from cpl.graphql.schema.graph_type import GraphType
|
||||||
|
from cpl.graphql.schema.query import Query
|
||||||
|
from cpl.graphql.schema.sort.sort import Sort
|
||||||
|
from cpl.graphql.schema.sort.sort_order import SortOrder
|
||||||
|
|
||||||
|
users = [User(i, f"User {i}") for i in range(1, 101)]
|
||||||
|
cities = [City(i, f"City {i}") for i in range(1, 101)]
|
||||||
|
|
||||||
|
# class UserFilter(Filter[User]):
|
||||||
|
# def __init__(self):
|
||||||
|
# Filter.__init__(self)
|
||||||
|
# self.field("id", int)
|
||||||
|
# self.field("username", str)
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# class UserSort(Sort[User]):
|
||||||
|
# def __init__(self):
|
||||||
|
# Sort.__init__(self)
|
||||||
|
# self.field("id", SortOrder)
|
||||||
|
# self.field("username", SortOrder)
|
||||||
|
#
|
||||||
|
# class UserGraphType(GraphType[User]):
|
||||||
|
#
|
||||||
|
# def __init__(self):
|
||||||
|
# GraphType.__init__(self)
|
||||||
|
#
|
||||||
|
# self.int_field(
|
||||||
|
# "id",
|
||||||
|
# resolver=lambda root: root.id,
|
||||||
|
# )
|
||||||
|
# self.string_field(
|
||||||
|
# "username",
|
||||||
|
# resolver=lambda root: root.username,
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
||||||
|
class HelloQuery(Query):
|
||||||
|
def __init__(self):
|
||||||
|
Query.__init__(self)
|
||||||
|
self.string_field(
|
||||||
|
"message",
|
||||||
|
resolver=lambda name: f"Hello {name} {get_request().state.request_id}",
|
||||||
|
).with_argument("name", str, "Name to greet", "world")
|
||||||
|
|
||||||
|
self.collection_field(
|
||||||
|
UserGraphType,
|
||||||
|
"users",
|
||||||
|
UserFilter,
|
||||||
|
UserSort,
|
||||||
|
resolver=lambda: users,
|
||||||
|
)
|
||||||
|
self.collection_field(
|
||||||
|
CityGraphType,
|
||||||
|
"cities",
|
||||||
|
CityFilter,
|
||||||
|
CitySort,
|
||||||
|
resolver=lambda: cities,
|
||||||
|
)
|
||||||
|
# self.dao_collection_field(
|
||||||
|
# UserGraphType,
|
||||||
|
# UserDao,
|
||||||
|
# "Users",
|
||||||
|
# UserFilter,
|
||||||
|
# UserSort,
|
||||||
|
# )
|
||||||
39
example/api/src/queries/user.py
Normal file
39
example/api/src/queries/user.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
from cpl.graphql.schema.filter.filter import Filter
|
||||||
|
from cpl.graphql.schema.graph_type import GraphType
|
||||||
|
from cpl.graphql.schema.sort.sort import Sort
|
||||||
|
from cpl.graphql.schema.sort.sort_order import SortOrder
|
||||||
|
|
||||||
|
|
||||||
|
class User:
|
||||||
|
def __init__(self, id: int, name: str):
|
||||||
|
self.id = id
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
|
||||||
|
class UserFilter(Filter[User]):
|
||||||
|
def __init__(self):
|
||||||
|
Filter.__init__(self)
|
||||||
|
self.field("id", int)
|
||||||
|
self.field("name", str)
|
||||||
|
|
||||||
|
|
||||||
|
class UserSort(Sort[User]):
|
||||||
|
def __init__(self):
|
||||||
|
Sort.__init__(self)
|
||||||
|
self.field("id", SortOrder)
|
||||||
|
self.field("name", SortOrder)
|
||||||
|
|
||||||
|
|
||||||
|
class UserGraphType(GraphType[User]):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
GraphType.__init__(self)
|
||||||
|
|
||||||
|
self.int_field(
|
||||||
|
"id",
|
||||||
|
resolver=lambda root: root.id,
|
||||||
|
)
|
||||||
|
self.string_field(
|
||||||
|
"name",
|
||||||
|
resolver=lambda root: root.name,
|
||||||
|
)
|
||||||
21
example/api/src/routes/ping.py
Normal file
21
example/api/src/routes/ping.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from urllib.request import Request
|
||||||
|
|
||||||
|
from service import PingService
|
||||||
|
from starlette.responses import JSONResponse
|
||||||
|
|
||||||
|
from cpl.api import APILogger
|
||||||
|
from cpl.api.router import Router
|
||||||
|
from cpl.core.console import Console
|
||||||
|
from cpl.dependency import ServiceProvider
|
||||||
|
from scoped_service import ScopedService
|
||||||
|
|
||||||
|
|
||||||
|
@Router.authenticate()
|
||||||
|
# @Router.authorize(permissions=[Permissions.administrator])
|
||||||
|
# @Router.authorize(policies=["test"])
|
||||||
|
@Router.get(f"/ping")
|
||||||
|
async def ping(r: Request, ping: PingService, logger: APILogger, provider: ServiceProvider, scoped: ScopedService):
|
||||||
|
logger.info(f"Ping: {ping}")
|
||||||
|
|
||||||
|
Console.write_line(scoped.name)
|
||||||
|
return JSONResponse(ping.ping(r))
|
||||||
14
example/api/src/scoped_service.py
Normal file
14
example/api/src/scoped_service.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from cpl.core.console.console import Console
|
||||||
|
from cpl.core.utils.string import String
|
||||||
|
|
||||||
|
|
||||||
|
class ScopedService:
|
||||||
|
def __init__(self):
|
||||||
|
self._name = String.random(8)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
Console.write_line(f"Im {self._name}")
|
||||||
22
example/api/src/scripts/0-posts.sql
Normal file
22
example/api/src/scripts/0-posts.sql
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS `authors` (
|
||||||
|
`id` INT(30) NOT NULL AUTO_INCREMENT,
|
||||||
|
`firstname` VARCHAR(64) NOT NULL,
|
||||||
|
`lastname` VARCHAR(64) NOT NULL,
|
||||||
|
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
editorId INT NULL,
|
||||||
|
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY(`id`)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `posts` (
|
||||||
|
`id` INT(30) NOT NULL AUTO_INCREMENT,
|
||||||
|
`authorId` INT(30) NOT NULL REFERENCES `authors`(`id`) ON DELETE CASCADE,
|
||||||
|
`title` TEXT NOT NULL,
|
||||||
|
`content` TEXT NOT NULL,
|
||||||
|
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
editorId INT NULL,
|
||||||
|
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY(`id`)
|
||||||
|
);
|
||||||
4
example/api/src/service.py
Normal file
4
example/api/src/service.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
class PingService:
|
||||||
|
|
||||||
|
def ping(self, r):
|
||||||
|
return "pong"
|
||||||
48
example/api/src/test_data_seeder.py
Normal file
48
example/api/src/test_data_seeder.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
from faker import Faker
|
||||||
|
|
||||||
|
from cpl.database.abc import DataSeederABC
|
||||||
|
from cpl.query import Enumerable
|
||||||
|
from model.author import Author
|
||||||
|
from model.author_dao import AuthorDao
|
||||||
|
from model.post import Post
|
||||||
|
from model.post_dao import PostDao
|
||||||
|
|
||||||
|
|
||||||
|
fake = Faker()
|
||||||
|
|
||||||
|
|
||||||
|
class TestDataSeeder(DataSeederABC):
|
||||||
|
|
||||||
|
def __init__(self, authors: AuthorDao, posts: PostDao):
|
||||||
|
DataSeederABC.__init__(self)
|
||||||
|
|
||||||
|
self._authors = authors
|
||||||
|
self._posts = posts
|
||||||
|
|
||||||
|
async def seed(self):
|
||||||
|
if await self._authors.count() == 0:
|
||||||
|
await self._seed_authors()
|
||||||
|
|
||||||
|
if await self._posts.count() == 0:
|
||||||
|
await self._seed_posts()
|
||||||
|
|
||||||
|
async def _seed_authors(self):
|
||||||
|
authors = Enumerable.range(0, 35).select(
|
||||||
|
lambda x: Author(
|
||||||
|
0,
|
||||||
|
fake.first_name(),
|
||||||
|
fake.last_name(),
|
||||||
|
)
|
||||||
|
).to_list()
|
||||||
|
await self._authors.create_many(authors, skip_editor=True)
|
||||||
|
|
||||||
|
async def _seed_posts(self):
|
||||||
|
posts = Enumerable.range(0, 100).select(
|
||||||
|
lambda x: Post(
|
||||||
|
id=0,
|
||||||
|
author_id=fake.random_int(min=1, max=35),
|
||||||
|
title=fake.sentence(nb_words=6),
|
||||||
|
content=fake.paragraph(nb_sentences=6),
|
||||||
|
)
|
||||||
|
).to_list()
|
||||||
|
await self._posts.create_many(posts, skip_editor=True)
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import time
|
import time
|
||||||
from cpl_core.console import Console, ForegroundColorEnum
|
from cpl.core.console import Console, ForegroundColorEnum
|
||||||
|
|
||||||
|
|
||||||
def test_spinner():
|
def test_spinner():
|
||||||
@@ -15,31 +15,27 @@ def test_console():
|
|||||||
Console.write_line("Hello World")
|
Console.write_line("Hello World")
|
||||||
Console.write("\nName: ")
|
Console.write("\nName: ")
|
||||||
Console.write_line(" Hello", Console.read_line())
|
Console.write_line(" Hello", Console.read_line())
|
||||||
Console.clear()
|
|
||||||
Console.write_at(5, 5, "at 5, 5")
|
Console.write_at(5, 5, "at 5, 5")
|
||||||
Console.write_at(10, 10, "at 10, 10")
|
Console.write_at(10, 10, "at 10, 10")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
Console.write_line("Hello World\n")
|
Console.write_line("Hello World\n")
|
||||||
|
Console.clear()
|
||||||
Console.spinner(
|
Console.spinner(
|
||||||
"Test:", test_spinner, spinner_foreground_color=ForegroundColorEnum.cyan, text_foreground_color="green"
|
"Test:", test_spinner, spinner_foreground_color=ForegroundColorEnum.cyan, text_foreground_color="green"
|
||||||
)
|
)
|
||||||
# opts = [
|
test_console()
|
||||||
# 'Option 1',
|
Console.write_line("HOLD BACK")
|
||||||
# 'Option 2',
|
opts = ["Option 1", "Option 2", "Option 3", "Option 4"]
|
||||||
# 'Option 3',
|
selected = Console.select(
|
||||||
# 'Option 4'
|
">",
|
||||||
# ]
|
"Select item:",
|
||||||
# selected = Console.select(
|
opts,
|
||||||
# '>',
|
header_foreground_color=ForegroundColorEnum.blue,
|
||||||
# 'Select item:',
|
option_foreground_color=ForegroundColorEnum.green,
|
||||||
# opts,
|
cursor_foreground_color=ForegroundColorEnum.red,
|
||||||
# header_foreground_color=ForegroundColorEnum.blue,
|
)
|
||||||
# option_foreground_color=ForegroundColorEnum.green,
|
Console.write_line(f"You selected: {selected}")
|
||||||
# cursor_foreground_color=ForegroundColorEnum.red
|
|
||||||
# )
|
Console.write_line()
|
||||||
# Console.write_line(f'You selected: {selected}')
|
|
||||||
# # test_console()
|
|
||||||
#
|
|
||||||
# Console.write_line()
|
|
||||||
40
example/database/src/application.py
Normal file
40
example/database/src/application.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
from cpl.application.abc import ApplicationABC
|
||||||
|
from cpl.auth.keycloak import KeycloakAdmin
|
||||||
|
from cpl.core.console import Console
|
||||||
|
from cpl.core.environment import Environment
|
||||||
|
from cpl.core.log import LoggerABC
|
||||||
|
from cpl.dependency import ServiceProvider
|
||||||
|
from cpl.dependency.typing import Modules
|
||||||
|
from model.city import City
|
||||||
|
from model.city_dao import CityDao
|
||||||
|
from model.user import User
|
||||||
|
from model.user_dao import UserDao
|
||||||
|
|
||||||
|
|
||||||
|
class Application(ApplicationABC):
|
||||||
|
def __init__(self, services: ServiceProvider, modules: Modules):
|
||||||
|
ApplicationABC.__init__(self, services, modules)
|
||||||
|
|
||||||
|
self._logger = services.get_service(LoggerABC)
|
||||||
|
|
||||||
|
async def test_daos(self):
|
||||||
|
userDao: UserDao = self._services.get_service(UserDao)
|
||||||
|
cityDao: CityDao = self._services.get_service(CityDao)
|
||||||
|
|
||||||
|
Console.write_line(await userDao.get_all())
|
||||||
|
|
||||||
|
if len(await cityDao.get_all()) == 0:
|
||||||
|
city_id = await cityDao.create(City(0, "Haren", "49733"))
|
||||||
|
await userDao.create(User(0, "NewUser", city_id))
|
||||||
|
|
||||||
|
Console.write_line(await userDao.get_all())
|
||||||
|
|
||||||
|
async def main(self):
|
||||||
|
self._logger.debug(f"Host: {Environment.get_host_name()}")
|
||||||
|
self._logger.debug(f"Environment: {Environment.get_environment()}")
|
||||||
|
|
||||||
|
await self.test_daos()
|
||||||
|
|
||||||
|
kc_admin: KeycloakAdmin = self._services.get_service(KeycloakAdmin)
|
||||||
|
x = kc_admin.get_users()
|
||||||
|
Console.write_line(x)
|
||||||
8
example/database/src/appsettings.development.json
Normal file
8
example/database/src/appsettings.development.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"Path": "logs/",
|
||||||
|
"Filename": "log_$start_time.log",
|
||||||
|
"ConsoleLevel": "TRACE",
|
||||||
|
"Level": "TRACE"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +1,22 @@
|
|||||||
{
|
{
|
||||||
"TimeFormatSettings": {
|
"TimeFormat": {
|
||||||
"DateFormat": "%Y-%m-%d",
|
"DateFormat": "%Y-%m-%d",
|
||||||
"TimeFormat": "%H:%M:%S",
|
"TimeFormat": "%H:%M:%S",
|
||||||
"DateTimeFormat": "%Y-%m-%d %H:%M:%S.%f",
|
"DateTimeFormat": "%Y-%m-%d %H:%M:%S.%f",
|
||||||
"DateTimeLogFormat": "%Y-%m-%d_%H-%M-%S"
|
"DateTimeLogFormat": "%Y-%m-%d_%H-%M-%S"
|
||||||
},
|
},
|
||||||
|
|
||||||
"LoggingSettings": {
|
"Log": {
|
||||||
"Path": "logs/",
|
"Path": "logs/",
|
||||||
"Filename": "log_$start_time.log",
|
"Filename": "log_$start_time.log",
|
||||||
"ConsoleLogLevel": "TRACE",
|
"ConsoleLevel": "TRACE",
|
||||||
"FileLogLevel": "TRACE"
|
"Level": "TRACE"
|
||||||
},
|
},
|
||||||
|
|
||||||
"DatabaseSettings": {
|
"Database": {
|
||||||
"AuthPlugin": "mysql_native_password",
|
"AuthPlugin": "mysql_native_password",
|
||||||
"ConnectionString": "mysql+mysqlconnector://sh_cpl:$credentials@localhost/sh_cpl",
|
"ConnectionString": "mysql+mysqlconnector://cpl:$credentials@localhost/cpl",
|
||||||
"Credentials": "MHZhc0Y2bjhKc1VUMWV0Qw==",
|
"Credentials": "Y3Bs",
|
||||||
"Encoding": "utf8mb4"
|
"Encoding": "utf8mb4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
26
example/database/src/appsettings.edrafts-pc.json
Normal file
26
example/database/src/appsettings.edrafts-pc.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"TimeFormat": {
|
||||||
|
"DateFormat": "%Y-%m-%d",
|
||||||
|
"TimeFormat": "%H:%M:%S",
|
||||||
|
"DateTimeFormat": "%Y-%m-%d %H:%M:%S.%f",
|
||||||
|
"DateTimeLogFormat": "%Y-%m-%d_%H-%M-%S"
|
||||||
|
},
|
||||||
|
|
||||||
|
"Log": {
|
||||||
|
"Path": "logs/",
|
||||||
|
"Filename": "log_$start_time.log",
|
||||||
|
"ConsoleLevel": "TRACE",
|
||||||
|
"Level": "TRACE"
|
||||||
|
},
|
||||||
|
|
||||||
|
"Database": {
|
||||||
|
"Host": "localhost",
|
||||||
|
"User": "cpl",
|
||||||
|
"Port": 3306,
|
||||||
|
"Password": "cpl",
|
||||||
|
"Database": "cpl",
|
||||||
|
"Charset": "utf8mb4",
|
||||||
|
"UseUnicode": "true",
|
||||||
|
"Buffered": "true"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
"TimeFormatSettings": {
|
"TimeFormat": {
|
||||||
"DateFormat": "%Y-%m-%d",
|
"DateFormat": "%Y-%m-%d",
|
||||||
"TimeFormat": "%H:%M:%S",
|
"TimeFormat": "%H:%M:%S",
|
||||||
"DateTimeFormat": "%Y-%m-%d %H:%M:%S.%f",
|
"DateTimeFormat": "%Y-%m-%d %H:%M:%S.%f",
|
||||||
"DateTimeLogFormat": "%Y-%m-%d_%H-%M-%S"
|
"DateTimeLogFormat": "%Y-%m-%d_%H-%M-%S"
|
||||||
},
|
},
|
||||||
|
|
||||||
"LoggingSettings": {
|
"Log": {
|
||||||
"Path": "logs/",
|
"Path": "logs/",
|
||||||
"Filename": "log_$start_time.log",
|
"Filename": "log_$start_time.log",
|
||||||
"ConsoleLogLevel": "ERROR",
|
"ConsoleLevel": "ERROR",
|
||||||
"FileLogLevel": "WARN"
|
"Level": "WARNING"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
5
example/database/src/custom_permissions.py
Normal file
5
example/database/src/custom_permissions.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class CustomPermissions(Enum):
|
||||||
|
test = "test"
|
||||||
19
example/database/src/main.py
Normal file
19
example/database/src/main.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
from application import Application
|
||||||
|
from cpl.application import ApplicationBuilder
|
||||||
|
from custom_permissions import CustomPermissions
|
||||||
|
from startup import Startup
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
builder = ApplicationBuilder(Application).with_startup(Startup)
|
||||||
|
app = builder.build()
|
||||||
|
|
||||||
|
app.with_logging()
|
||||||
|
app.with_permissions(CustomPermissions)
|
||||||
|
app.with_migrations("./scripts")
|
||||||
|
app.with_seeders()
|
||||||
|
app.run()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
27
example/database/src/main_simplified.py
Normal file
27
example/database/src/main_simplified.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
from application import Application
|
||||||
|
from cpl.application import ApplicationBuilder
|
||||||
|
from cpl.auth.permission.permissions_registry import PermissionsRegistry
|
||||||
|
from cpl.core.console import Console
|
||||||
|
from cpl.core.log import LogLevel
|
||||||
|
from cpl.database import DatabaseModule
|
||||||
|
from custom_permissions import CustomPermissions
|
||||||
|
from startup import Startup
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
builder = ApplicationBuilder(Application).with_startup(Startup)
|
||||||
|
builder.services.add_logging()
|
||||||
|
app = builder.build()
|
||||||
|
|
||||||
|
app.with_logging(LogLevel.trace)
|
||||||
|
app.with_permissions(CustomPermissions)
|
||||||
|
app.with_migrations("./scripts")
|
||||||
|
# app.with_seeders()
|
||||||
|
|
||||||
|
Console.write_line(CustomPermissions.test.value in PermissionsRegistry.get())
|
||||||
|
app.run()
|
||||||
|
Console.write_line("Hello from main_simplified.py!")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
29
example/database/src/model/city.py
Normal file
29
example/database/src/model/city.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from cpl.core.typing import SerialId
|
||||||
|
from cpl.database.abc.db_model_abc import DbModelABC
|
||||||
|
|
||||||
|
|
||||||
|
class City(DbModelABC[Self]):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
id: int,
|
||||||
|
name: str,
|
||||||
|
zip: str,
|
||||||
|
deleted: bool = False,
|
||||||
|
editor_id: SerialId | None = None,
|
||||||
|
created: datetime | None= None,
|
||||||
|
updated: datetime | None= None,
|
||||||
|
):
|
||||||
|
DbModelABC.__init__(self, id, deleted, editor_id, created, updated)
|
||||||
|
self._name = name
|
||||||
|
self._zip = zip
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def zip(self) -> str:
|
||||||
|
return self._zip
|
||||||
11
example/database/src/model/city_dao.py
Normal file
11
example/database/src/model/city_dao.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from cpl.database.abc import DbModelDaoABC
|
||||||
|
from model.city import City
|
||||||
|
|
||||||
|
|
||||||
|
class CityDao(DbModelDaoABC[City]):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
DbModelDaoABC.__init__(self, City, "city")
|
||||||
|
|
||||||
|
self.attribute(City.name, str)
|
||||||
|
self.attribute(City.zip, int)
|
||||||
30
example/database/src/model/user.py
Normal file
30
example/database/src/model/user.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from cpl.core.typing import SerialId
|
||||||
|
from cpl.database.abc.db_model_abc import DbModelABC
|
||||||
|
|
||||||
|
|
||||||
|
class User(DbModelABC[Self]):
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
id: int,
|
||||||
|
name: str,
|
||||||
|
city_id: int = 0,
|
||||||
|
deleted: bool = False,
|
||||||
|
editor_id: SerialId | None = None,
|
||||||
|
created: datetime | None= None,
|
||||||
|
updated: datetime | None= None,
|
||||||
|
):
|
||||||
|
DbModelABC.__init__(self, id, deleted, editor_id, created, updated)
|
||||||
|
self._name = name
|
||||||
|
self._city_id = city_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def city_id(self) -> int:
|
||||||
|
return self._city_id
|
||||||
13
example/database/src/model/user_dao.py
Normal file
13
example/database/src/model/user_dao.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
from cpl.database.abc import DbModelDaoABC
|
||||||
|
from model.user import User
|
||||||
|
|
||||||
|
|
||||||
|
class UserDao(DbModelDaoABC[User]):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
DbModelDaoABC.__init__(self, User, "users")
|
||||||
|
|
||||||
|
self.attribute(User.name, str)
|
||||||
|
self.attribute(User.city_id, int, db_name="CityId")
|
||||||
|
|
||||||
|
self.reference("city", "id", User.city_id, "city")
|
||||||
22
example/database/src/scripts/0-initial.sql
Normal file
22
example/database/src/scripts/0-initial.sql
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS `city` (
|
||||||
|
`id` INT(30) NOT NULL AUTO_INCREMENT,
|
||||||
|
`name` VARCHAR(64) NOT NULL,
|
||||||
|
`zip` VARCHAR(5) NOT NULL,
|
||||||
|
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
editorId INT NULL,
|
||||||
|
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY(`id`)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `users` (
|
||||||
|
`id` INT(30) NOT NULL AUTO_INCREMENT,
|
||||||
|
`name` VARCHAR(64) NOT NULL,
|
||||||
|
`cityId` INT(30),
|
||||||
|
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
editorId INT NULL,
|
||||||
|
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (`cityId`) REFERENCES city(`id`),
|
||||||
|
PRIMARY KEY(`id`)
|
||||||
|
);
|
||||||
35
example/database/src/startup.py
Normal file
35
example/database/src/startup.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
from cpl import auth
|
||||||
|
from cpl.application.abc.startup_abc import StartupABC
|
||||||
|
from cpl.auth import permission
|
||||||
|
from cpl.auth.auth_module import AuthModule
|
||||||
|
from cpl.auth.permission.permission_module import PermissionsModule
|
||||||
|
from cpl.core.configuration import Configuration
|
||||||
|
from cpl.core.environment import Environment
|
||||||
|
from cpl.core.log import Logger, LoggerABC
|
||||||
|
from cpl.database import mysql, DatabaseModule
|
||||||
|
from cpl.database.abc.data_access_object_abc import DataAccessObjectABC
|
||||||
|
from cpl.database.mysql.mysql_module import MySQLModule
|
||||||
|
from cpl.dependency import ServiceCollection
|
||||||
|
from model.city_dao import CityDao
|
||||||
|
from model.user_dao import UserDao
|
||||||
|
|
||||||
|
|
||||||
|
class Startup(StartupABC):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def configure_configuration():
|
||||||
|
Configuration.add_json_file(f"appsettings.json")
|
||||||
|
Configuration.add_json_file(f"appsettings.{Environment.get_environment()}.json")
|
||||||
|
Configuration.add_json_file(f"appsettings.{Environment.get_host_name()}.json", optional=True)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def configure_services(services: ServiceCollection):
|
||||||
|
services.add_module(MySQLModule)
|
||||||
|
services.add_module(DatabaseModule)
|
||||||
|
services.add_module(AuthModule)
|
||||||
|
services.add_module(PermissionsModule)
|
||||||
|
|
||||||
|
services.add_transient(DataAccessObjectABC, UserDao)
|
||||||
|
services.add_transient(DataAccessObjectABC, CityDao)
|
||||||
|
|
||||||
|
services.add_singleton(LoggerABC, Logger)
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
"TimeFormatSettings": {
|
"TimeFormat": {
|
||||||
"DateFormat": "%Y-%m-%d",
|
"DateFormat": "%Y-%m-%d",
|
||||||
"TimeFormat": "%H:%M:%S",
|
"TimeFormat": "%H:%M:%S",
|
||||||
"DateTimeFormat": "%Y-%m-%d %H:%M:%S.%f",
|
"DateTimeFormat": "%Y-%m-%d %H:%M:%S.%f",
|
||||||
"DateTimeLogFormat": "%Y-%m-%d_%H-%M-%S"
|
"DateTimeLogFormat": "%Y-%m-%d_%H-%M-%S"
|
||||||
},
|
},
|
||||||
|
|
||||||
"LoggingSettings": {
|
"Logging": {
|
||||||
"Path": "logs/",
|
"Path": "logs/",
|
||||||
"Filename": "log_$start_time.log",
|
"Filename": "log_$start_time.log",
|
||||||
"ConsoleLogLevel": "ERROR",
|
"ConsoleLevel": "ERROR",
|
||||||
"FileLogLevel": "WARN"
|
"Level": "WARN"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
45
example/di/src/application.py
Normal file
45
example/di/src/application.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
from cpl.application.abc import ApplicationABC
|
||||||
|
from cpl.core.console.console import Console
|
||||||
|
from cpl.dependency import ServiceProvider
|
||||||
|
from test_abc import TestABC
|
||||||
|
from test_service import TestService
|
||||||
|
from di_tester_service import DITesterService
|
||||||
|
from tester import Tester
|
||||||
|
|
||||||
|
|
||||||
|
class Application(ApplicationABC):
|
||||||
|
def __init__(self, services: ServiceProvider):
|
||||||
|
ApplicationABC.__init__(self, services)
|
||||||
|
|
||||||
|
def _part_of_scoped(self):
|
||||||
|
ts: TestService = self._services.get_service(TestService)
|
||||||
|
ts.run()
|
||||||
|
|
||||||
|
def main(self):
|
||||||
|
with self._services.create_scope() as scope:
|
||||||
|
Console.write_line("Scope1")
|
||||||
|
ts: TestService = scope.get_service(TestService)
|
||||||
|
ts.run()
|
||||||
|
dit: DITesterService = scope.get_service(DITesterService)
|
||||||
|
dit.run()
|
||||||
|
|
||||||
|
if ts.name != dit.name:
|
||||||
|
raise Exception("DI is broken!")
|
||||||
|
|
||||||
|
with self._services.create_scope() as scope:
|
||||||
|
Console.write_line("Scope2")
|
||||||
|
ts: TestService = scope.get_service(TestService)
|
||||||
|
ts.run()
|
||||||
|
dit: DITesterService = scope.get_service(DITesterService)
|
||||||
|
dit.run()
|
||||||
|
|
||||||
|
if ts.name != dit.name:
|
||||||
|
raise Exception("DI is broken!")
|
||||||
|
|
||||||
|
Console.write_line("Global")
|
||||||
|
self._part_of_scoped()
|
||||||
|
#from static_test import StaticTest
|
||||||
|
#StaticTest.test()
|
||||||
|
|
||||||
|
self._services.get_service(Tester)
|
||||||
|
Console.write_line(self._services.get_services(TestABC))
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"ProjectSettings": {
|
"Project": {
|
||||||
"Name": "di",
|
"Name": "di",
|
||||||
"Version": {
|
"Version": {
|
||||||
"Major": "0",
|
"Major": "0",
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
"PythonPath": {},
|
"PythonPath": {},
|
||||||
"Classifiers": []
|
"Classifiers": []
|
||||||
},
|
},
|
||||||
"BuildSettings": {
|
"Build": {
|
||||||
"ProjectType": "console",
|
"ProjectType": "console",
|
||||||
"SourcePath": "",
|
"SourcePath": "",
|
||||||
"OutputPath": "../../dist",
|
"OutputPath": "../../dist",
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
from cpl_core.console.console import Console
|
from cpl.core.console.console import Console
|
||||||
from di.test_service import TestService
|
from test_service import TestService
|
||||||
|
|
||||||
|
|
||||||
class DITesterService:
|
class DITesterService:
|
||||||
def __init__(self, ts: TestService):
|
def __init__(self, ts: TestService):
|
||||||
self._ts = ts
|
self._ts = ts
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return self._ts.name
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
Console.write_line("DIT: ")
|
Console.write_line("DIT: ")
|
||||||
self._ts.run()
|
self._ts.run()
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from cpl_core.application import ApplicationBuilder
|
from cpl.application import ApplicationBuilder
|
||||||
|
|
||||||
from application import Application
|
from application import Application
|
||||||
from startup import Startup
|
from startup import Startup
|
||||||
@@ -6,7 +6,7 @@ from startup import Startup
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
app_builder = ApplicationBuilder(Application)
|
app_builder = ApplicationBuilder(Application)
|
||||||
app_builder.use_startup(Startup)
|
app_builder.with_startup(Startup)
|
||||||
app_builder.build().run()
|
app_builder.build().run()
|
||||||
|
|
||||||
|
|
||||||
27
example/di/src/startup.py
Normal file
27
example/di/src/startup.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
from cpl.application.abc import StartupABC
|
||||||
|
from cpl.dependency import ServiceProvider, ServiceCollection
|
||||||
|
from di_tester_service import DITesterService
|
||||||
|
from test1_service import Test1Service
|
||||||
|
from test2_service import Test2Service
|
||||||
|
from test_abc import TestABC
|
||||||
|
from test_service import TestService
|
||||||
|
from tester import Tester
|
||||||
|
|
||||||
|
|
||||||
|
class Startup(StartupABC):
|
||||||
|
def __init__(self):
|
||||||
|
StartupABC.__init__(self)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def configure_configuration(): ...
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def configure_services(services: ServiceCollection) -> ServiceProvider:
|
||||||
|
services.add_scoped(TestService)
|
||||||
|
services.add_scoped(DITesterService)
|
||||||
|
|
||||||
|
services.add_singleton(TestABC, Test1Service)
|
||||||
|
services.add_singleton(TestABC, Test2Service)
|
||||||
|
services.add_singleton(Tester)
|
||||||
|
|
||||||
|
return services.build()
|
||||||
10
example/di/src/static_test.py
Normal file
10
example/di/src/static_test.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from cpl.dependency import ServiceProvider, ServiceProvider
|
||||||
|
from cpl.dependency.inject import inject
|
||||||
|
from test_service import TestService
|
||||||
|
|
||||||
|
|
||||||
|
class StaticTest:
|
||||||
|
@staticmethod
|
||||||
|
@inject
|
||||||
|
def test(services: ServiceProvider, t1: TestService):
|
||||||
|
t1.run()
|
||||||
12
example/di/src/test1_service.py
Normal file
12
example/di/src/test1_service.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import string
|
||||||
|
from cpl.core.console.console import Console
|
||||||
|
from cpl.core.utils.string import String
|
||||||
|
from test_abc import TestABC
|
||||||
|
|
||||||
|
|
||||||
|
class Test1Service(TestABC):
|
||||||
|
def __init__(self):
|
||||||
|
TestABC.__init__(self, String.random(8))
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
Console.write_line(f"Im {self._name}")
|
||||||
12
example/di/src/test2_service.py
Normal file
12
example/di/src/test2_service.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import string
|
||||||
|
from cpl.core.console.console import Console
|
||||||
|
from cpl.core.utils.string import String
|
||||||
|
from test_abc import TestABC
|
||||||
|
|
||||||
|
|
||||||
|
class Test2Service(TestABC):
|
||||||
|
def __init__(self):
|
||||||
|
TestABC.__init__(self, String.random(8))
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
Console.write_line(f"Im {self._name}")
|
||||||
14
example/di/src/test_service.py
Normal file
14
example/di/src/test_service.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from cpl.core.console.console import Console
|
||||||
|
from cpl.core.utils.string import String
|
||||||
|
|
||||||
|
|
||||||
|
class TestService:
|
||||||
|
def __init__(self):
|
||||||
|
self._name = String.random(8)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
Console.write_line(f"Im {self._name}")
|
||||||
7
example/di/src/tester.py
Normal file
7
example/di/src/tester.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from cpl.core.console.console import Console
|
||||||
|
from test_abc import TestABC
|
||||||
|
|
||||||
|
|
||||||
|
class Tester:
|
||||||
|
def __init__(self, t1: TestABC, t2: TestABC, t3: TestABC, t: list[TestABC]):
|
||||||
|
Console.write_line("Tester:", t, t1, t2, t3)
|
||||||
77
example/general/src/application.py
Normal file
77
example/general/src/application.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import asyncio
|
||||||
|
import time
|
||||||
|
|
||||||
|
from cpl.application.abc import ApplicationABC
|
||||||
|
from cpl.core.configuration import Configuration
|
||||||
|
from cpl.core.console import Console
|
||||||
|
from cpl.core.environment import Environment
|
||||||
|
from cpl.core.log import LoggerABC
|
||||||
|
from cpl.core.pipes import IPAddressPipe
|
||||||
|
from cpl.dependency import ServiceProvider
|
||||||
|
from cpl.dependency.typing import Modules
|
||||||
|
from cpl.mail import EMail, EMailClientABC
|
||||||
|
from cpl.query import List
|
||||||
|
from scoped_service import ScopedService
|
||||||
|
from test_service import TestService
|
||||||
|
from test_settings import TestSettings
|
||||||
|
|
||||||
|
|
||||||
|
class Application(ApplicationABC):
|
||||||
|
|
||||||
|
def __init__(self, services: ServiceProvider, modules: Modules):
|
||||||
|
ApplicationABC.__init__(self, services, modules)
|
||||||
|
self._logger = self._services.get_service(LoggerABC)
|
||||||
|
self._mailer = self._services.get_service(EMailClientABC)
|
||||||
|
|
||||||
|
def test_send_mail(self):
|
||||||
|
mail = EMail()
|
||||||
|
mail.add_header("Mime-Version: 1.0")
|
||||||
|
mail.add_header("Content-Type: text/plain; charset=utf-8")
|
||||||
|
mail.add_header("Content-Transfer-Encoding: quoted-printable")
|
||||||
|
mail.add_receiver("sven.heidemann@sh-edraft.de")
|
||||||
|
mail.subject = f"Test - {Environment.get_host_name()}"
|
||||||
|
mail.body = "Dies ist ein Test :D"
|
||||||
|
self._mailer.send_mail(mail)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _wait(time_ms: int):
|
||||||
|
time.sleep(time_ms)
|
||||||
|
|
||||||
|
async def main(self):
|
||||||
|
self._logger.debug(f"Host: {Environment.get_host_name()}")
|
||||||
|
self._logger.debug(f"Environment: {Environment.get_environment()}")
|
||||||
|
Console.write_line(List(range(0, 10)).select(lambda x: f"x={x}").to_list())
|
||||||
|
Console.spinner("Test", self._wait, 2, spinner_foreground_color="red")
|
||||||
|
test: TestService = self._services.get_service(TestService)
|
||||||
|
ip_pipe: IPAddressPipe = self._services.get_service(IPAddressPipe)
|
||||||
|
test.run()
|
||||||
|
test2: TestService = self._services.get_service(TestService)
|
||||||
|
ip_pipe2: IPAddressPipe = self._services.get_service(IPAddressPipe)
|
||||||
|
Console.write_line(f"DI working: {test == test2 and ip_pipe != ip_pipe2}")
|
||||||
|
Console.write_line(self._services.get_service(LoggerABC))
|
||||||
|
|
||||||
|
root_scoped_service = self._services.get_service(ScopedService)
|
||||||
|
with self._services.create_scope() as scope:
|
||||||
|
s_srvc1 = scope.get_service(ScopedService)
|
||||||
|
s_srvc2 = scope.get_service(ScopedService)
|
||||||
|
|
||||||
|
Console.write_line(root_scoped_service)
|
||||||
|
Console.write_line(s_srvc1)
|
||||||
|
Console.write_line(s_srvc2)
|
||||||
|
if root_scoped_service == s_srvc1 or s_srvc1 != s_srvc2:
|
||||||
|
raise Exception("Root scoped service should not be equal to scoped service")
|
||||||
|
|
||||||
|
root_scoped_service2 = self._services.get_service(ScopedService)
|
||||||
|
Console.write_line(root_scoped_service2)
|
||||||
|
if root_scoped_service == root_scoped_service2:
|
||||||
|
raise Exception("Root scoped service should be equal to root scoped service 2")
|
||||||
|
|
||||||
|
test_settings = Configuration.get(TestSettings)
|
||||||
|
Console.write_line(test_settings.value)
|
||||||
|
Console.write_line("reload config")
|
||||||
|
Configuration.add_json_file(f"appsettings.json")
|
||||||
|
Configuration.add_json_file(f"appsettings.{Environment.get_environment()}.json")
|
||||||
|
Configuration.add_json_file(f"appsettings.{Environment.get_host_name()}.json", optional=True)
|
||||||
|
test_settings1 = Configuration.get(TestSettings)
|
||||||
|
Console.write_line(test_settings1.value)
|
||||||
|
# self.test_send_mail()
|
||||||
8
example/general/src/appsettings.development.json
Normal file
8
example/general/src/appsettings.development.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"Path": "logs/",
|
||||||
|
"Filename": "log_$start_time.log",
|
||||||
|
"ConsoleLevel": "TRACE",
|
||||||
|
"Level": "TRACE"
|
||||||
|
}
|
||||||
|
}
|
||||||
20
example/general/src/appsettings.edrafts-lapi.json
Normal file
20
example/general/src/appsettings.edrafts-lapi.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"TimeFormat": {
|
||||||
|
"DateFormat": "%Y-%m-%d",
|
||||||
|
"TimeFormat": "%H:%M:%S",
|
||||||
|
"DateTimeFormat": "%Y-%m-%d %H:%M:%S.%f",
|
||||||
|
"DateTimeLogFormat": "%Y-%m-%d_%H-%M-%S"
|
||||||
|
},
|
||||||
|
"Logging": {
|
||||||
|
"Path": "logs/",
|
||||||
|
"Filename": "log_$start_time.log",
|
||||||
|
"ConsoleLevel": "TRACE",
|
||||||
|
"Level": "TRACE"
|
||||||
|
},
|
||||||
|
"EMailClient": {
|
||||||
|
"Host": "mail.sh-edraft.de",
|
||||||
|
"Port": "587",
|
||||||
|
"UserName": "dev-srv@sh-edraft.de",
|
||||||
|
"Credentials": "RmBOQX1eNFYiYjgsSid3fV1nelc2WA=="
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,26 +1,26 @@
|
|||||||
{
|
{
|
||||||
"TimeFormatSettings": {
|
"TimeFormat": {
|
||||||
"DateFormat": "%Y-%m-%d",
|
"DateFormat": "%Y-%m-%d",
|
||||||
"TimeFormat": "%H:%M:%S",
|
"TimeFormat": "%H:%M:%S",
|
||||||
"DateTimeFormat": "%Y-%m-%d %H:%M:%S.%f",
|
"DateTimeFormat": "%Y-%m-%d %H:%M:%S.%f",
|
||||||
"DateTimeLogFormat": "%Y-%m-%d_%H-%M-%S"
|
"DateTimeLogFormat": "%Y-%m-%d_%H-%M-%S"
|
||||||
},
|
},
|
||||||
|
|
||||||
"LoggingSettings": {
|
"Logging": {
|
||||||
"Path": "logs/",
|
"Path": "logs/",
|
||||||
"Filename": "log_$start_time.log",
|
"Filename": "log_$start_time.log",
|
||||||
"ConsoleLogLevel": "TRACE",
|
"ConsoleLevel": "TRACE",
|
||||||
"FileLogLevel": "TRACE"
|
"Level": "TRACE"
|
||||||
},
|
},
|
||||||
|
|
||||||
"EMailClientSettings": {
|
"EMailClient": {
|
||||||
"Host": "mail.sh-edraft.de",
|
"Host": "mail.sh-edraft.de",
|
||||||
"Port": "587",
|
"Port": "587",
|
||||||
"UserName": "dev-srv@sh-edraft.de",
|
"UserName": "dev-srv@sh-edraft.de",
|
||||||
"Credentials": "RmBOQX1eNFYiYjgsSid3fV1nelc2WA=="
|
"Credentials": "RmBOQX1eNFYiYjgsSid3fV1nelc2WA=="
|
||||||
},
|
},
|
||||||
|
|
||||||
"DatabaseSettings": {
|
"Database": {
|
||||||
"Host": "localhost",
|
"Host": "localhost",
|
||||||
"User": "sh_cpl",
|
"User": "sh_cpl",
|
||||||
"Password": "MHZhc0Y2bjhKc1VUMWV0Qw==",
|
"Password": "MHZhc0Y2bjhKc1VUMWV0Qw==",
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
"AuthPlugin": "mysql_native_password"
|
"AuthPlugin": "mysql_native_password"
|
||||||
},
|
},
|
||||||
|
|
||||||
"TestSettings": {
|
"Test": {
|
||||||
"Value": 20
|
"Value": 20
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
"TimeFormatSettings": {
|
"TimeFormat": {
|
||||||
"DateFormat": "%Y-%m-%d",
|
"DateFormat": "%Y-%m-%d",
|
||||||
"TimeFormat": "%H:%M:%S",
|
"TimeFormat": "%H:%M:%S",
|
||||||
"DateTimeFormat": "%Y-%m-%d %H:%M:%S.%f",
|
"DateTimeFormat": "%Y-%m-%d %H:%M:%S.%f",
|
||||||
"DateTimeLogFormat": "%Y-%m-%d_%H-%M-%S"
|
"DateTimeLogFormat": "%Y-%m-%d_%H-%M-%S"
|
||||||
},
|
},
|
||||||
|
|
||||||
"LoggingSettings": {
|
"Logging": {
|
||||||
"Path": "logs/",
|
"Path": "logs/",
|
||||||
"Filename": "log_$start_time.log",
|
"Filename": "log_$start_time.log",
|
||||||
"ConsoleLogLevel": "ERROR",
|
"ConsoleLevel": "ERROR",
|
||||||
"FileLogLevel": "WARN"
|
"Level": "WARN"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
1
example/general/src/db/__init__.py
Normal file
1
example/general/src/db/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
30
example/general/src/hosted_service.py
Normal file
30
example/general/src/hosted_service.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import asyncio
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from cpl.core.console import Console
|
||||||
|
from cpl.core.time.cron import Cron
|
||||||
|
from cpl.core.service.cronjob import CronjobABC
|
||||||
|
from cpl.core.service.hosted_service import HostedService
|
||||||
|
|
||||||
|
|
||||||
|
class Hosted(HostedService):
|
||||||
|
def __init__(self):
|
||||||
|
self._stopped = False
|
||||||
|
|
||||||
|
async def start(self):
|
||||||
|
Console.write_line("Hosted Service Started")
|
||||||
|
while not self._stopped:
|
||||||
|
Console.write_line("Hosted Service Running")
|
||||||
|
await asyncio.sleep(5)
|
||||||
|
|
||||||
|
async def stop(self):
|
||||||
|
Console.write_line("Hosted Service Stopped")
|
||||||
|
self._stopped = True
|
||||||
|
|
||||||
|
|
||||||
|
class MyCronJob(CronjobABC):
|
||||||
|
def __init__(self):
|
||||||
|
CronjobABC.__init__(self, Cron("*/1 * * * *")) # Every minute
|
||||||
|
|
||||||
|
async def loop(self):
|
||||||
|
Console.write_line(f"[{datetime.now()}] Hello from Cronjob!")
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
from application import Application
|
from application import Application
|
||||||
from cpl_core.application import ApplicationBuilder
|
from cpl.application import ApplicationBuilder
|
||||||
|
from cpl.core.console import Console
|
||||||
from test_extension import TestExtension
|
from test_extension import TestExtension
|
||||||
from startup import Startup
|
from startup import Startup
|
||||||
from test_startup_extension import TestStartupExtension
|
from test_startup_extension import TestStartupExtension
|
||||||
from parameter_startup import ParameterStartup
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
Console.write_line("\n\n--- Application Starting ---\n")
|
||||||
app_builder = ApplicationBuilder(Application)
|
app_builder = ApplicationBuilder(Application)
|
||||||
app_builder.use_startup(Startup)
|
app_builder.with_startup(Startup)
|
||||||
app_builder.use_extension(ParameterStartup)
|
app_builder.with_extension(TestStartupExtension)
|
||||||
app_builder.use_extension(TestStartupExtension)
|
app_builder.with_extension(TestExtension)
|
||||||
app_builder.use_extension(TestExtension)
|
|
||||||
app_builder.build().run()
|
app_builder.build().run()
|
||||||
|
|
||||||
|
|
||||||
10
example/general/src/scoped_service.py
Normal file
10
example/general/src/scoped_service.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from cpl.core.console import Console
|
||||||
|
|
||||||
|
|
||||||
|
class ScopedService:
|
||||||
|
def __init__(self):
|
||||||
|
self.value = "I am a scoped service"
|
||||||
|
Console.write_line(self.value, self)
|
||||||
|
|
||||||
|
def get_value(self):
|
||||||
|
return self.value
|
||||||
28
example/general/src/startup.py
Normal file
28
example/general/src/startup.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
from cpl.application.abc import StartupABC
|
||||||
|
from cpl.core.configuration import Configuration
|
||||||
|
from cpl.core.environment import Environment
|
||||||
|
from cpl.core.pipes import IPAddressPipe
|
||||||
|
from cpl.dependency import ServiceCollection
|
||||||
|
from cpl.mail.mail_module import MailModule
|
||||||
|
from hosted_service import Hosted, MyCronJob
|
||||||
|
from scoped_service import ScopedService
|
||||||
|
from test_service import TestService
|
||||||
|
|
||||||
|
|
||||||
|
class Startup(StartupABC):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def configure_configuration():
|
||||||
|
Configuration.add_json_file(f"appsettings.json")
|
||||||
|
Configuration.add_json_file(f"appsettings.{Environment.get_environment()}.json")
|
||||||
|
Configuration.add_json_file(f"appsettings.{Environment.get_host_name()}.json", optional=True)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def configure_services(services: ServiceCollection):
|
||||||
|
services.add_logging()
|
||||||
|
services.add_module(MailModule)
|
||||||
|
services.add_transient(IPAddressPipe)
|
||||||
|
services.add_singleton(TestService)
|
||||||
|
services.add_scoped(ScopedService)
|
||||||
|
services.add_hosted_service(Hosted)
|
||||||
|
services.add_hosted_service(MyCronJob)
|
||||||
10
example/general/src/test_extension.py
Normal file
10
example/general/src/test_extension.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from cpl.application.abc import ApplicationExtensionABC
|
||||||
|
from cpl.core.console import Console
|
||||||
|
from cpl.dependency import ServiceProvider
|
||||||
|
|
||||||
|
|
||||||
|
class TestExtension(ApplicationExtensionABC):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(services: ServiceProvider):
|
||||||
|
Console.write_line("Hello World from App Extension")
|
||||||
13
example/general/src/test_service.py
Normal file
13
example/general/src/test_service.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
from cpl.core.console.console import Console
|
||||||
|
from cpl.dependency import ServiceProvider
|
||||||
|
from cpl.core.pipes.ip_address_pipe import IPAddressPipe
|
||||||
|
|
||||||
|
|
||||||
|
class TestService:
|
||||||
|
def __init__(self, provider: ServiceProvider):
|
||||||
|
self._provider = provider
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
Console.write_line("Hello World!", self._provider)
|
||||||
|
ip = [192, 168, 178, 30]
|
||||||
|
Console.write_line(ip, IPAddressPipe.to_str(ip))
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from cpl_core.configuration import ConfigurationModelABC
|
from cpl.core.configuration import ConfigurationModelABC
|
||||||
|
|
||||||
|
|
||||||
class TestSettings(ConfigurationModelABC):
|
class TestSettings(ConfigurationModelABC):
|
||||||
14
example/general/src/test_startup_extension.py
Normal file
14
example/general/src/test_startup_extension.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from cpl.application.abc import StartupExtensionABC
|
||||||
|
from cpl.core.console import Console
|
||||||
|
from cpl.dependency import ServiceCollection
|
||||||
|
|
||||||
|
|
||||||
|
class TestStartupExtension(StartupExtensionABC):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def configure_configuration():
|
||||||
|
Console.write_line("config")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def configure_services(services: ServiceCollection):
|
||||||
|
Console.write_line("services")
|
||||||
60
example/query/main.py
Normal file
60
example/query/main.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
from cpl.core.console import Console
|
||||||
|
from cpl.core.utils.benchmark import Benchmark
|
||||||
|
from cpl.query.enumerable import Enumerable
|
||||||
|
from cpl.query.immutable_list import ImmutableList
|
||||||
|
from cpl.query.list import List
|
||||||
|
from cpl.query.set import Set
|
||||||
|
|
||||||
|
|
||||||
|
def _default():
|
||||||
|
Console.write_line(Enumerable.empty().to_list())
|
||||||
|
|
||||||
|
Console.write_line(Enumerable.range(0, 100).length)
|
||||||
|
Console.write_line(Enumerable.range(0, 100).to_list())
|
||||||
|
|
||||||
|
Console.write_line(Enumerable.range(0, 100).where(lambda x: x % 2 == 0).length)
|
||||||
|
Console.write_line(
|
||||||
|
Enumerable.range(0, 100).where(lambda x: x % 2 == 0).to_list().select(lambda x: str(x)).to_list()
|
||||||
|
)
|
||||||
|
Console.write_line(List)
|
||||||
|
|
||||||
|
s =Enumerable.range(0, 10).to_set()
|
||||||
|
Console.write_line(s)
|
||||||
|
s.add(1)
|
||||||
|
Console.write_line(s)
|
||||||
|
|
||||||
|
data = Enumerable(
|
||||||
|
[
|
||||||
|
{"name": "Alice", "age": 30},
|
||||||
|
{"name": "Dave", "age": 35},
|
||||||
|
{"name": "Charlie", "age": 25},
|
||||||
|
{"name": "Bob", "age": 25},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
Console.write_line(data.order_by(lambda x: x["age"]).to_list())
|
||||||
|
Console.write_line(data.order_by(lambda x: x["age"]).then_by(lambda x: x["name"]).to_list())
|
||||||
|
Console.write_line(data.order_by(lambda x: x["name"]).then_by(lambda x: x["age"]).to_list())
|
||||||
|
|
||||||
|
|
||||||
|
def t_benchmark(data: list):
|
||||||
|
Benchmark.all("Enumerable", lambda: Enumerable(data).where(lambda x: x % 2 == 0).select(lambda x: x * 2).to_list())
|
||||||
|
Benchmark.all("Set", lambda: Set(data).where(lambda x: x % 2 == 0).select(lambda x: x * 2).to_list())
|
||||||
|
Benchmark.all("List", lambda: List(data).where(lambda x: x % 2 == 0).select(lambda x: x * 2).to_list())
|
||||||
|
Benchmark.all(
|
||||||
|
"ImmutableList", lambda: ImmutableList(data).where(lambda x: x % 2 == 0).select(lambda x: x * 2).to_list()
|
||||||
|
)
|
||||||
|
Benchmark.all("List comprehension", lambda: [x * 2 for x in data if x % 2 == 0])
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
N = 1_000_000
|
||||||
|
data = list(range(N))
|
||||||
|
t_benchmark(data)
|
||||||
|
|
||||||
|
Console.write_line()
|
||||||
|
_default()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"WorkspaceSettings": {
|
"Workspace": {
|
||||||
"DefaultProject": "translation",
|
"DefaultProject": "translation",
|
||||||
"Projects": {
|
"Projects": {
|
||||||
"translation": "src/translation/translation.json"
|
"translation": "src/translation/translation.json"
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
from cpl_core.application import ApplicationABC
|
from cpl.application import ApplicationABC
|
||||||
from cpl_core.configuration import ConfigurationABC
|
from cpl.core.configuration import ConfigurationABC
|
||||||
from cpl_core.console import Console
|
from cpl.core.console import Console
|
||||||
from cpl_core.dependency_injection import ServiceProviderABC
|
from cpl.dependency import ServiceProvider
|
||||||
from cpl_translation.translate_pipe import TranslatePipe
|
from cpl.translation.translate_pipe import TranslatePipe
|
||||||
from cpl_translation.translation_service_abc import TranslationServiceABC
|
from cpl.translation.translation_service_abc import TranslationServiceABC
|
||||||
from cpl_translation.translation_settings import TranslationSettings
|
from cpl.translation.translation_settings import TranslationSettings
|
||||||
|
|
||||||
|
|
||||||
class Application(ApplicationABC):
|
class Application(ApplicationABC):
|
||||||
def __init__(self, config: ConfigurationABC, services: ServiceProviderABC):
|
def __init__(self, config: ConfigurationABC, services: ServiceProvider):
|
||||||
ApplicationABC.__init__(self, config, services)
|
ApplicationABC.__init__(self, config, services)
|
||||||
|
|
||||||
self._translate: TranslatePipe = services.get_service(TranslatePipe)
|
self._translate: TranslatePipe = services.get_service(TranslatePipe)
|
||||||
@@ -18,8 +18,7 @@ class Application(ApplicationABC):
|
|||||||
self._translation.load_by_settings(config.get_configuration(TranslationSettings))
|
self._translation.load_by_settings(config.get_configuration(TranslationSettings))
|
||||||
self._translation.set_default_lang("de")
|
self._translation.set_default_lang("de")
|
||||||
|
|
||||||
def configure(self):
|
def configure(self): ...
|
||||||
pass
|
|
||||||
|
|
||||||
def main(self):
|
def main(self):
|
||||||
Console.write_line(self._translate.transform("main.text.hello_world"))
|
Console.write_line(self._translate.transform("main.text.hello_world"))
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"TimeFormatSettings": {
|
"TimeFormat": {
|
||||||
"DateFormat": "%Y-%m-%d",
|
"DateFormat": "%Y-%m-%d",
|
||||||
"TimeFormat": "%H:%M:%S",
|
"TimeFormat": "%H:%M:%S",
|
||||||
"DateTimeFormat": "%Y-%m-%d %H:%M:%S.%f",
|
"DateTimeFormat": "%Y-%m-%d %H:%M:%S.%f",
|
||||||
"DateTimeLogFormat": "%Y-%m-%d_%H-%M-%S"
|
"DateTimeLogFormat": "%Y-%m-%d_%H-%M-%S"
|
||||||
},
|
},
|
||||||
|
|
||||||
"LoggingSettings": {
|
"Logging": {
|
||||||
"Path": "logs/",
|
"Path": "logs/",
|
||||||
"Filename": "log_$start_time.log",
|
"Filename": "log_$start_time.log",
|
||||||
"ConsoleLogLevel": "ERROR",
|
"ConsoleLevel": "ERROR",
|
||||||
"FileLogLevel": "WARN"
|
"Level": "WARN"
|
||||||
},
|
},
|
||||||
|
|
||||||
"Translation": {
|
"Translation": {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from cpl_core.application import ApplicationBuilder
|
from cpl.application import ApplicationBuilder
|
||||||
|
|
||||||
from translation.application import Application
|
from translation.application import Application
|
||||||
from translation.startup import Startup
|
from translation.startup import Startup
|
||||||
@@ -6,7 +6,7 @@ from translation.startup import Startup
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
app_builder = ApplicationBuilder(Application)
|
app_builder = ApplicationBuilder(Application)
|
||||||
app_builder.use_startup(Startup)
|
app_builder.with_startup(Startup)
|
||||||
app_builder.build().run()
|
app_builder.build().run()
|
||||||
|
|
||||||
|
|
||||||
17
example/translation/src/startup.py
Normal file
17
example/translation/src/startup.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from cpl.application import StartupABC
|
||||||
|
from cpl.core.configuration import ConfigurationABC
|
||||||
|
from cpl.dependency import ServiceProvider, ServiceCollection
|
||||||
|
from cpl.core.environment import Environment
|
||||||
|
|
||||||
|
|
||||||
|
class Startup(StartupABC):
|
||||||
|
def __init__(self):
|
||||||
|
StartupABC.__init__(self)
|
||||||
|
|
||||||
|
def configure_configuration(self, configuration: ConfigurationABC, environment: Environment) -> ConfigurationABC:
|
||||||
|
configuration.add_json_file("appsettings.json")
|
||||||
|
return configuration
|
||||||
|
|
||||||
|
def configure_services(self, services: ServiceCollection, environment: Environment) -> ServiceProvider:
|
||||||
|
services.add_translation()
|
||||||
|
return services.build()
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"ProjectSettings": {
|
"Project": {
|
||||||
"Name": "translation",
|
"Name": "translation",
|
||||||
"Version": {
|
"Version": {
|
||||||
"Major": "0",
|
"Major": "0",
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
"PythonPath": {},
|
"PythonPath": {},
|
||||||
"Classifiers": []
|
"Classifiers": []
|
||||||
},
|
},
|
||||||
"BuildSettings": {
|
"Build": {
|
||||||
"ProjectType": "console",
|
"ProjectType": "console",
|
||||||
"SourcePath": "",
|
"SourcePath": "",
|
||||||
"OutputPath": "../../dist",
|
"OutputPath": "../../dist",
|
||||||
61
install.sh
Normal file
61
install.sh
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Find and combine requirements from src/*/requirements.txt,
|
||||||
|
# filtering out lines whose *package name* starts with "cpl-".
|
||||||
|
# Works with pinned versions, extras, markers, editable installs, and VCS refs.
|
||||||
|
|
||||||
|
shopt -s nullglob
|
||||||
|
|
||||||
|
req_files=(src/*/requirements.txt)
|
||||||
|
if ((${#req_files[@]} == 0)); then
|
||||||
|
echo "No requirements files found at src/*/requirements.txt" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmp_combined="$(mktemp)"
|
||||||
|
trap 'rm -f "$tmp_combined"' EXIT
|
||||||
|
|
||||||
|
# Concatenate, trim comments/whitespace, filter out cpl-* packages, dedupe.
|
||||||
|
# We keep non-package options/flags/constraints as-is.
|
||||||
|
awk '
|
||||||
|
function trim(s){ sub(/^[[:space:]]+/,"",s); sub(/[[:space:]]+$/,"",s); return s }
|
||||||
|
|
||||||
|
{
|
||||||
|
line=$0
|
||||||
|
# drop full-line comments and strip inline comments
|
||||||
|
if (line ~ /^[[:space:]]*#/) next
|
||||||
|
sub(/#[^!].*$/,"",line) # strip trailing comment (simple heuristic)
|
||||||
|
line=trim(line)
|
||||||
|
if (line == "") next
|
||||||
|
|
||||||
|
# Determine the package *name* even for "-e", extras, pins, markers, or VCS "@"
|
||||||
|
e = line
|
||||||
|
sub(/^-e[[:space:]]+/,"",e) # remove editable prefix
|
||||||
|
# Tokenize up to the first of these separators: space, [ < > = ! ~ ; @
|
||||||
|
token = e
|
||||||
|
sub(/\[.*/,"",token) # remove extras quickly
|
||||||
|
n = split(token, a, /[<>=!~;@[:space:]]/)
|
||||||
|
name = tolower(a[1])
|
||||||
|
|
||||||
|
# If the first token (name) starts with "cpl-", skip this requirement
|
||||||
|
if (name ~ /^cpl-/) next
|
||||||
|
|
||||||
|
print line
|
||||||
|
}
|
||||||
|
' "${req_files[@]}" | sort -u > "$tmp_combined"
|
||||||
|
|
||||||
|
if ! [ -s "$tmp_combined" ]; then
|
||||||
|
echo "Nothing to install after filtering out cpl-* packages." >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Installing dependencies (excluding cpl-*) from:"
|
||||||
|
printf ' - %s\n' "${req_files[@]}"
|
||||||
|
echo
|
||||||
|
echo "Final set to install:"
|
||||||
|
cat "$tmp_combined"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Use python -m pip for reliability; change to python3 if needed.
|
||||||
|
python -m pip install -r "$tmp_combined"
|
||||||
6
src/api/cpl/api/__init__.py
Normal file
6
src/api/cpl/api/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from .error import APIError, AlreadyExists, EndpointNotImplemented, Forbidden, NotFound, Unauthorized
|
||||||
|
from .logger import APILogger
|
||||||
|
from .settings import ApiSettings
|
||||||
|
from .api_module import ApiModule
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
||||||
1
src/api/cpl/api/abc/__init__.py
Normal file
1
src/api/cpl/api/abc/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .asgi_middleware_abc import ASGIMiddleware
|
||||||
15
src/api/cpl/api/abc/asgi_middleware_abc.py
Normal file
15
src/api/cpl/api/abc/asgi_middleware_abc.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
from starlette.types import Scope, Receive, Send
|
||||||
|
|
||||||
|
|
||||||
|
class ASGIMiddleware(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def __init__(self, app):
|
||||||
|
self._app = app
|
||||||
|
|
||||||
|
def _call_next(self, scope: Scope, receive: Receive, send: Send):
|
||||||
|
return self._app(scope, receive, send)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def __call__(self, scope: Scope, receive: Receive, send: Send): ...
|
||||||
45
src/api/cpl/api/abc/web_app_abc.py
Normal file
45
src/api/cpl/api/abc/web_app_abc.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
from abc import ABC
|
||||||
|
from enum import Enum
|
||||||
|
from typing import Self
|
||||||
|
|
||||||
|
from starlette.applications import Starlette
|
||||||
|
|
||||||
|
from cpl.api.model.api_route import ApiRoute
|
||||||
|
from cpl.api.model.validation_match import ValidationMatch
|
||||||
|
from cpl.api.typing import HTTPMethods, PartialMiddleware, TEndpoint, PolicyInput
|
||||||
|
from cpl.application.abc.application_abc import ApplicationABC
|
||||||
|
from cpl.dependency.service_provider import ServiceProvider
|
||||||
|
from cpl.dependency.typing import Modules
|
||||||
|
|
||||||
|
|
||||||
|
class WebAppABC(ApplicationABC, ABC):
|
||||||
|
|
||||||
|
def __init__(self, services: ServiceProvider, modules: Modules, required_modules: list[str | object] = None):
|
||||||
|
ApplicationABC.__init__(self, services, modules, required_modules)
|
||||||
|
|
||||||
|
def with_routes_directory(self, directory: str) -> Self: ...
|
||||||
|
def with_app(self, app: Starlette) -> Self: ...
|
||||||
|
def with_routes(
|
||||||
|
self,
|
||||||
|
routes: list[ApiRoute],
|
||||||
|
method: HTTPMethods,
|
||||||
|
authentication: bool = False,
|
||||||
|
roles: list[str | Enum] = None,
|
||||||
|
permissions: list[str | Enum] = None,
|
||||||
|
policies: list[str] = None,
|
||||||
|
match: ValidationMatch = None,
|
||||||
|
) -> Self: ...
|
||||||
|
def with_route(
|
||||||
|
self,
|
||||||
|
path: str,
|
||||||
|
fn: TEndpoint,
|
||||||
|
method: HTTPMethods,
|
||||||
|
authentication: bool = False,
|
||||||
|
roles: list[str | Enum] = None,
|
||||||
|
permissions: list[str | Enum] = None,
|
||||||
|
policies: list[str] = None,
|
||||||
|
match: ValidationMatch = None,
|
||||||
|
) -> Self: ...
|
||||||
|
def with_middleware(self, middleware: PartialMiddleware) -> Self: ...
|
||||||
|
def with_authentication(self) -> Self: ...
|
||||||
|
def with_authorization(self, *policies: list[PolicyInput] | PolicyInput) -> Self: ...
|
||||||
22
src/api/cpl/api/api_module.py
Normal file
22
src/api/cpl/api/api_module.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
from cpl.api import ApiSettings
|
||||||
|
from cpl.api.registry.policy import PolicyRegistry
|
||||||
|
from cpl.api.registry.route import RouteRegistry
|
||||||
|
from cpl.auth.auth_module import AuthModule
|
||||||
|
from cpl.auth.permission.permission_module import PermissionsModule
|
||||||
|
from cpl.database.database_module import DatabaseModule
|
||||||
|
from cpl.dependency import ServiceCollection
|
||||||
|
from cpl.dependency.module.module import Module
|
||||||
|
|
||||||
|
|
||||||
|
class ApiModule(Module):
|
||||||
|
config = [ApiSettings]
|
||||||
|
singleton = [
|
||||||
|
PolicyRegistry,
|
||||||
|
RouteRegistry,
|
||||||
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def register(collection: ServiceCollection):
|
||||||
|
collection.add_module(DatabaseModule)
|
||||||
|
collection.add_module(AuthModule)
|
||||||
|
collection.add_module(PermissionsModule)
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user