Compare commits
13 Commits
master
...
2025.09.16
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ad3e3bdb4 | |||
| b97bc0a3ed | |||
| 5f25400bcd | |||
| 5edabbf8c1 | |||
| 62b6435470 | |||
| d9d7802f33 | |||
| 25b4ca0696 | |||
| 3b120370b8 | |||
| aac038ef63 | |||
| 784632a0b4 | |||
| 4719c32457 | |||
| 516fa3fb7e | |||
| 2d2bb86720 |
41
.gitea/workflows/build-dev.yaml
Normal file
41
.gitea/workflows/build-dev.yaml
Normal file
@@ -0,0 +1,41 @@
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
65
.gitea/workflows/package.yaml
Normal file
65
.gitea/workflows/package.yaml
Normal file
@@ -0,0 +1,65 @@
|
||||
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 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/*
|
||||
54
.gitea/workflows/prepare.yaml
Normal file
54
.gitea/workflows/prepare.yaml
Normal file
@@ -0,0 +1,54 @@
|
||||
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)
|
||||
BUILD_NUMBER=$(($TAG_COUNT + 1))
|
||||
|
||||
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
|
||||
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/
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
6
src/cpl-application/cpl/application/__init__.py
Normal file
6
src/cpl-application/cpl/application/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from .application_abc import ApplicationABC
|
||||
from .application_builder import ApplicationBuilder
|
||||
from .application_builder_abc import ApplicationBuilderABC
|
||||
from .application_extension_abc import ApplicationExtensionABC
|
||||
from .startup_abc import StartupABC
|
||||
from .startup_extension_abc import StartupExtensionABC
|
||||
@@ -1,26 +1,23 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Optional
|
||||
|
||||
from cpl_core.configuration.configuration_abc import ConfigurationABC
|
||||
from cpl_core.console.console import Console
|
||||
from cpl_core.dependency_injection.service_provider_abc import ServiceProviderABC
|
||||
from cpl_core.environment.application_environment_abc import ApplicationEnvironmentABC
|
||||
from cpl.dependency.service_provider_abc import ServiceProviderABC
|
||||
|
||||
from cpl.core.console.console import Console
|
||||
|
||||
|
||||
class ApplicationABC(ABC):
|
||||
r"""ABC for the Application class
|
||||
|
||||
Parameters:
|
||||
config: :class:`cpl_core.configuration.configuration_abc.ConfigurationABC`
|
||||
config: :class:`cpl.core.configuration.configuration_abc.ConfigurationABC`
|
||||
Contains object loaded from appsettings
|
||||
services: :class:`cpl_core.dependency_injection.service_provider_abc.ServiceProviderABC`
|
||||
services: :class:`cpl.dependency.service_provider_abc.ServiceProviderABC`
|
||||
Contains instances of prepared objects
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self, config: ConfigurationABC, services: ServiceProviderABC):
|
||||
self._configuration: Optional[ConfigurationABC] = config
|
||||
self._environment: Optional[ApplicationEnvironmentABC] = self._configuration.environment
|
||||
def __init__(self, services: ServiceProviderABC):
|
||||
self._services: Optional[ServiceProviderABC] = services
|
||||
|
||||
def run(self):
|
||||
@@ -49,14 +46,12 @@ class ApplicationABC(ABC):
|
||||
def configure(self):
|
||||
r"""Configure the application
|
||||
|
||||
Called by :class:`cpl_core.application.application_abc.ApplicationABC.run`
|
||||
Called by :class:`cpl.application.application_abc.ApplicationABC.run`
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def main(self):
|
||||
r"""Custom entry point
|
||||
|
||||
Called by :class:`cpl_core.application.application_abc.ApplicationABC.run`
|
||||
Called by :class:`cpl.application.application_abc.ApplicationABC.run`
|
||||
"""
|
||||
pass
|
||||
97
src/cpl-application/cpl/application/application_builder.py
Normal file
97
src/cpl-application/cpl/application/application_builder.py
Normal file
@@ -0,0 +1,97 @@
|
||||
from typing import Type, Optional, Callable, Union
|
||||
|
||||
from cpl.application.application_abc import ApplicationABC
|
||||
from cpl.application.application_builder_abc import ApplicationBuilderABC
|
||||
from cpl.application.application_extension_abc import ApplicationExtensionABC
|
||||
from cpl.application.async_application_extension_abc import AsyncApplicationExtensionABC
|
||||
from cpl.application.async_startup_abc import AsyncStartupABC
|
||||
from cpl.application.async_startup_extension_abc import AsyncStartupExtensionABC
|
||||
from cpl.application.startup_abc import StartupABC
|
||||
from cpl.application.startup_extension_abc import StartupExtensionABC
|
||||
from cpl.core.configuration.configuration import Configuration
|
||||
from cpl.dependency.service_collection import ServiceCollection
|
||||
from cpl.core.environment import Environment
|
||||
|
||||
|
||||
class ApplicationBuilder(ApplicationBuilderABC):
|
||||
r"""This is class is used to build an object of :class:`cpl.application.application_abc.ApplicationABC`
|
||||
|
||||
Parameter:
|
||||
app: Type[:class:`cpl.application.application_abc.ApplicationABC`]
|
||||
Application to build
|
||||
"""
|
||||
|
||||
def __init__(self, app: Type[ApplicationABC]):
|
||||
ApplicationBuilderABC.__init__(self)
|
||||
self._app = app
|
||||
self._startup: Optional[StartupABC | AsyncStartupABC] = None
|
||||
|
||||
self._services = ServiceCollection()
|
||||
|
||||
self._app_extensions: list[Type[ApplicationExtensionABC | AsyncApplicationExtensionABC]] = []
|
||||
self._startup_extensions: list[Type[StartupExtensionABC | AsyncStartupABC]] = []
|
||||
|
||||
def use_startup(self, startup: Type[StartupABC | AsyncStartupABC]) -> "ApplicationBuilder":
|
||||
self._startup = startup()
|
||||
return self
|
||||
|
||||
def use_extension(
|
||||
self,
|
||||
extension: Type[
|
||||
ApplicationExtensionABC | AsyncApplicationExtensionABC | StartupExtensionABC | AsyncStartupExtensionABC
|
||||
],
|
||||
) -> "ApplicationBuilder":
|
||||
if (
|
||||
issubclass(extension, ApplicationExtensionABC) or issubclass(extension, AsyncApplicationExtensionABC)
|
||||
) and extension not in self._app_extensions:
|
||||
self._app_extensions.append(extension)
|
||||
elif (
|
||||
issubclass(extension, StartupExtensionABC) or issubclass(extension, AsyncStartupExtensionABC)
|
||||
) and extension not in self._startup_extensions:
|
||||
self._startup_extensions.append(extension)
|
||||
|
||||
return self
|
||||
|
||||
def _build_startup(self):
|
||||
for ex in self._startup_extensions:
|
||||
extension = ex()
|
||||
extension.configure_configuration(Configuration, Environment)
|
||||
extension.configure_services(self._services, Environment)
|
||||
|
||||
if self._startup is not None:
|
||||
self._startup.configure_configuration(Configuration, Environment)
|
||||
self._startup.configure_services(self._services, Environment)
|
||||
|
||||
async def _build_async_startup(self):
|
||||
for ex in self._startup_extensions:
|
||||
extension = ex()
|
||||
await extension.configure_configuration(Configuration, Environment)
|
||||
await extension.configure_services(self._services, Environment)
|
||||
|
||||
if self._startup is not None:
|
||||
await self._startup.configure_configuration(Configuration, Environment)
|
||||
await self._startup.configure_services(self._services, Environment)
|
||||
|
||||
def build(self) -> ApplicationABC:
|
||||
self._build_startup()
|
||||
|
||||
config = Configuration
|
||||
services = self._services.build_service_provider()
|
||||
|
||||
for ex in self._app_extensions:
|
||||
extension = ex()
|
||||
extension.run(config, services)
|
||||
|
||||
return self._app(services)
|
||||
|
||||
async def build_async(self) -> ApplicationABC:
|
||||
await self._build_async_startup()
|
||||
|
||||
config = Configuration
|
||||
services = self._services.build_service_provider()
|
||||
|
||||
for ex in self._app_extensions:
|
||||
extension = ex()
|
||||
await extension.run(config, services)
|
||||
|
||||
return self._app(services)
|
||||
@@ -1,12 +1,12 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Type
|
||||
|
||||
from cpl_core.application.application_abc import ApplicationABC
|
||||
from cpl_core.application.startup_abc import StartupABC
|
||||
from cpl.application.application_abc import ApplicationABC
|
||||
from cpl.application.startup_abc import StartupABC
|
||||
|
||||
|
||||
class ApplicationBuilderABC(ABC):
|
||||
r"""ABC for the :class:`cpl_core.application.application_builder.ApplicationBuilder`"""
|
||||
r"""ABC for the :class:`cpl.application.application_builder.ApplicationBuilder`"""
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self, *args):
|
||||
@@ -17,35 +17,31 @@ class ApplicationBuilderABC(ABC):
|
||||
r"""Sets the custom startup class to use
|
||||
|
||||
Parameter:
|
||||
startup: Type[:class:`cpl_core.application.startup_abc.StartupABC`]
|
||||
startup: Type[:class:`cpl.application.startup_abc.StartupABC`]
|
||||
Startup class to use
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def use_startup(self, startup: Type[StartupABC]):
|
||||
r"""Sets the custom startup class to use async
|
||||
|
||||
Parameter:
|
||||
startup: Type[:class:`cpl_core.application.startup_abc.StartupABC`]
|
||||
startup: Type[:class:`cpl.application.startup_abc.StartupABC`]
|
||||
Startup class to use
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def build(self) -> ApplicationABC:
|
||||
r"""Creates custom application object
|
||||
|
||||
Returns:
|
||||
Object of :class:`cpl_core.application.application_abc.ApplicationABC`
|
||||
Object of :class:`cpl.application.application_abc.ApplicationABC`
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def build_async(self) -> ApplicationABC:
|
||||
r"""Creates custom application object async
|
||||
|
||||
Returns:
|
||||
Object of :class:`cpl_core.application.application_abc.ApplicationABC`
|
||||
Object of :class:`cpl.application.application_abc.ApplicationABC`
|
||||
"""
|
||||
pass
|
||||
@@ -0,0 +1,14 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from cpl.core.configuration.configuration import Configuration
|
||||
from cpl.dependency import ServiceProviderABC
|
||||
|
||||
|
||||
class ApplicationExtensionABC(ABC):
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def run(self, config: Configuration, services: ServiceProviderABC):
|
||||
pass
|
||||
@@ -0,0 +1,14 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from cpl.core.configuration.configuration import Configuration
|
||||
from cpl.dependency import ServiceProviderABC
|
||||
|
||||
|
||||
class AsyncApplicationExtensionABC(ABC):
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def run(self, config: Configuration, services: ServiceProviderABC):
|
||||
pass
|
||||
23
src/cpl-application/cpl/application/async_startup_abc.py
Normal file
23
src/cpl-application/cpl/application/async_startup_abc.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from cpl.dependency.service_collection_abc import ServiceCollectionABC
|
||||
|
||||
|
||||
class AsyncStartupABC(ABC):
|
||||
r"""ABC for the startup class"""
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def configure_configuration(self):
|
||||
r"""Creates configuration of application"""
|
||||
|
||||
@abstractmethod
|
||||
async def configure_services(self, service: ServiceCollectionABC):
|
||||
r"""Creates service provider
|
||||
|
||||
Parameter:
|
||||
services: :class:`cpl.dependency.service_collection_abc`
|
||||
"""
|
||||
@@ -0,0 +1,31 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from cpl.core.configuration.configuration import Configuration
|
||||
from cpl.dependency.service_collection_abc import ServiceCollectionABC
|
||||
from cpl.core.environment.environment import Environment
|
||||
|
||||
|
||||
class AsyncStartupExtensionABC(ABC):
|
||||
r"""ABC for startup extension classes"""
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def configure_configuration(self, config: Configuration, env: Environment):
|
||||
r"""Creates configuration of application
|
||||
|
||||
Parameter:
|
||||
config: :class:`cpl.core.configuration.configuration_abc.Configuration`
|
||||
env: :class:`cpl.core.environment.application_environment_abc`
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
async def configure_services(self, service: ServiceCollectionABC, env: Environment):
|
||||
r"""Creates service provider
|
||||
|
||||
Parameter:
|
||||
services: :class:`cpl.dependency.service_collection_abc`
|
||||
env: :class:`cpl.core.environment.application_environment_abc`
|
||||
"""
|
||||
31
src/cpl-application/cpl/application/startup_abc.py
Normal file
31
src/cpl-application/cpl/application/startup_abc.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from cpl.core.configuration import Configuration
|
||||
from cpl.dependency.service_collection_abc import ServiceCollectionABC
|
||||
from cpl.core.environment import Environment
|
||||
|
||||
|
||||
class StartupABC(ABC):
|
||||
r"""ABC for the startup class"""
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def configure_configuration(self, config: Configuration, env: Environment):
|
||||
r"""Creates configuration of application
|
||||
|
||||
Parameter:
|
||||
config: :class:`cpl.core.configuration.configuration_abc.ConfigurationABC`
|
||||
env: :class:`cpl.core.environment.application_environment_abc`
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def configure_services(self, service: ServiceCollectionABC, env: Environment):
|
||||
r"""Creates service provider
|
||||
|
||||
Parameter:
|
||||
services: :class:`cpl.dependency.service_collection_abc`
|
||||
env: :class:`cpl.core.environment.application_environment_abc`
|
||||
"""
|
||||
33
src/cpl-application/cpl/application/startup_extension_abc.py
Normal file
33
src/cpl-application/cpl/application/startup_extension_abc.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
from cpl.core.configuration import Configuration
|
||||
from cpl.dependency.service_collection_abc import ServiceCollectionABC
|
||||
|
||||
from cpl.core.environment.environment import Environment
|
||||
|
||||
|
||||
class StartupExtensionABC(ABC):
|
||||
r"""ABC for startup extension classes"""
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def configure_configuration(self, config: Configuration, env: Environment):
|
||||
r"""Creates configuration of application
|
||||
|
||||
Parameter:
|
||||
config: :class:`cpl.core.configuration.configuration_abc.ConfigurationABC`
|
||||
env: :class:`cpl.core.environment.application_environment_abc`
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def configure_services(self, service: ServiceCollectionABC, env: Environment):
|
||||
r"""Creates service provider
|
||||
|
||||
Parameter:
|
||||
services: :class:`cpl.dependency.service_collection_abc`
|
||||
env: :class:`cpl.core.environment.application_environment_abc`
|
||||
"""
|
||||
30
src/cpl-application/pyproject.toml
Normal file
30
src/cpl-application/pyproject.toml
Normal file
@@ -0,0 +1,30 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=70.1.0", "wheel>=0.43.0"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "cpl-application"
|
||||
version = "2024.7.0"
|
||||
description = "CPL application"
|
||||
readme ="CPL application package"
|
||||
requires-python = ">=3.12"
|
||||
license = { text = "MIT" }
|
||||
authors = [
|
||||
{ name = "Sven Heidemann", email = "sven.heidemann@sh-edraft.de" }
|
||||
]
|
||||
keywords = ["cpl", "application", "backend", "shared", "library"]
|
||||
|
||||
dynamic = ["dependencies", "optional-dependencies"]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://www.sh-edraft.de"
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["."]
|
||||
include = ["cpl*"]
|
||||
|
||||
[tool.setuptools.dynamic]
|
||||
dependencies = { file = ["requirements.txt"] }
|
||||
optional-dependencies.dev = { file = ["requirements.dev.txt"] }
|
||||
|
||||
|
||||
1
src/cpl-application/requirements.dev.txt
Normal file
1
src/cpl-application/requirements.dev.txt
Normal file
@@ -0,0 +1 @@
|
||||
black==25.1.0
|
||||
2
src/cpl-application/requirements.txt
Normal file
2
src/cpl-application/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
cpl-core
|
||||
cpl-dependency
|
||||
1
src/cpl-core/cpl/core/__init__.py
Normal file
1
src/cpl-core/cpl/core/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
2
src/cpl-core/cpl/core/configuration/__init__.py
Normal file
2
src/cpl-core/cpl/core/configuration/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .configuration import Configuration
|
||||
from .configuration_model_abc import ConfigurationModelABC
|
||||
134
src/cpl-core/cpl/core/configuration/configuration.py
Normal file
134
src/cpl-core/cpl/core/configuration/configuration.py
Normal file
@@ -0,0 +1,134 @@
|
||||
import inspect
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from typing import Any
|
||||
|
||||
from cpl.core.configuration.configuration_model_abc import ConfigurationModelABC
|
||||
from cpl.core.console.console import Console
|
||||
from cpl.core.console.foreground_color_enum import ForegroundColorEnum
|
||||
from cpl.core.environment.environment import Environment
|
||||
from cpl.core.typing import D, T
|
||||
from cpl.core.utils.json_processor import JSONProcessor
|
||||
|
||||
|
||||
class Configuration:
|
||||
_config = {}
|
||||
|
||||
@staticmethod
|
||||
def _print_info(message: str):
|
||||
r"""Prints an info message
|
||||
|
||||
Parameter:
|
||||
name: :class:`str`
|
||||
Info name
|
||||
message: :class:`str`
|
||||
Info message
|
||||
"""
|
||||
Console.set_foreground_color(ForegroundColorEnum.green)
|
||||
Console.write_line(f"[CONFIG] {message}")
|
||||
Console.set_foreground_color(ForegroundColorEnum.default)
|
||||
|
||||
@staticmethod
|
||||
def _print_warn(message: str):
|
||||
r"""Prints a warning
|
||||
|
||||
Parameter:
|
||||
name: :class:`str`
|
||||
Warning name
|
||||
message: :class:`str`
|
||||
Warning message
|
||||
"""
|
||||
Console.set_foreground_color(ForegroundColorEnum.yellow)
|
||||
Console.write_line(f"[CONFIG] {message}")
|
||||
Console.set_foreground_color(ForegroundColorEnum.default)
|
||||
|
||||
@staticmethod
|
||||
def _print_error(message: str):
|
||||
r"""Prints an error
|
||||
|
||||
Parameter:
|
||||
name: :class:`str`
|
||||
Error name
|
||||
message: :class:`str`
|
||||
Error message
|
||||
"""
|
||||
Console.set_foreground_color(ForegroundColorEnum.red)
|
||||
Console.write_line(f"[CONFIG] {message}")
|
||||
Console.set_foreground_color(ForegroundColorEnum.default)
|
||||
|
||||
@classmethod
|
||||
def _load_json_file(cls, file: str, output: bool) -> dict:
|
||||
r"""Reads the json file
|
||||
|
||||
Parameter:
|
||||
file: :class:`str`
|
||||
Name of the file
|
||||
output: :class:`bool`
|
||||
Specifies whether an output should take place
|
||||
|
||||
Returns:
|
||||
Object of :class:`dict`
|
||||
"""
|
||||
try:
|
||||
# open config file, create if not exists
|
||||
with open(file, encoding="utf-8") as cfg:
|
||||
# load json
|
||||
json_cfg = json.load(cfg)
|
||||
if output:
|
||||
cls._print_info(f"Loaded config file: {file}")
|
||||
|
||||
return json_cfg
|
||||
except Exception as e:
|
||||
cls._print_error(f"Cannot load config file: {file}! -> {e}")
|
||||
return {}
|
||||
|
||||
@classmethod
|
||||
def add_json_file(cls, name: str, optional: bool = None, output: bool = True, path: str = None):
|
||||
if os.path.isabs(name):
|
||||
file_path = name
|
||||
else:
|
||||
path_root = Environment.get_cwd()
|
||||
if path is not None:
|
||||
path_root = path
|
||||
|
||||
if str(path_root).endswith("/") and not name.startswith("/"):
|
||||
file_path = f"{path_root}{name}"
|
||||
else:
|
||||
file_path = f"{path_root}/{name}"
|
||||
|
||||
if not os.path.isfile(file_path):
|
||||
if optional is not True:
|
||||
if output:
|
||||
cls._print_error(f"File not found: {file_path}")
|
||||
|
||||
sys.exit()
|
||||
|
||||
if output:
|
||||
cls._print_warn(f"Not Loaded config file: {file_path}")
|
||||
|
||||
return None
|
||||
|
||||
config_from_file = cls._load_json_file(file_path, output)
|
||||
for sub in ConfigurationModelABC.__subclasses__():
|
||||
for key, value in config_from_file.items():
|
||||
if sub.__name__ != key and sub.__name__.replace("Settings", "") != key:
|
||||
continue
|
||||
|
||||
configuration = JSONProcessor.process(sub, value)
|
||||
|
||||
cls.set(sub, configuration)
|
||||
|
||||
@classmethod
|
||||
def set(cls, key: Any, value: T):
|
||||
if inspect.isclass(key):
|
||||
key = key.__name__
|
||||
|
||||
cls._config[key] = value
|
||||
|
||||
@classmethod
|
||||
def get(cls, key: Any, default: D = None) -> T | D:
|
||||
if inspect.isclass(key):
|
||||
key = key.__name__
|
||||
|
||||
return cls._config.get(key, default)
|
||||
@@ -0,0 +1,5 @@
|
||||
from abc import ABC
|
||||
|
||||
|
||||
class ConfigurationModelABC(ABC):
|
||||
pass
|
||||
5
src/cpl-core/cpl/core/console/__init__.py
Normal file
5
src/cpl-core/cpl/core/console/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from .background_color_enum import BackgroundColorEnum
|
||||
from .console import Console
|
||||
from .console_call import ConsoleCall
|
||||
from .foreground_color_enum import ForegroundColorEnum
|
||||
from .spinner_thread import SpinnerThread
|
||||
@@ -9,14 +9,15 @@ import colorama
|
||||
from tabulate import tabulate
|
||||
from termcolor import colored
|
||||
|
||||
from cpl_core.console.background_color_enum import BackgroundColorEnum
|
||||
from cpl_core.console.console_call import ConsoleCall
|
||||
from cpl_core.console.foreground_color_enum import ForegroundColorEnum
|
||||
from cpl_core.console.spinner_thread import SpinnerThread
|
||||
from cpl.core.console.background_color_enum import BackgroundColorEnum
|
||||
from cpl.core.console.console_call import ConsoleCall
|
||||
from cpl.core.console.foreground_color_enum import ForegroundColorEnum
|
||||
from cpl.core.console.spinner_thread import SpinnerThread
|
||||
|
||||
|
||||
class Console:
|
||||
r"""Useful functions for handling with input and output"""
|
||||
|
||||
colorama.init()
|
||||
_is_first_write = True
|
||||
|
||||
@@ -61,7 +62,7 @@ class Console:
|
||||
r"""Sets the background color
|
||||
|
||||
Parameter:
|
||||
color: Union[:class:`cpl_core.console.background_color_enum.BackgroundColorEnum`, :class:`str`]
|
||||
color: Union[:class:`cpl.core.console.background_color_enum.BackgroundColorEnum`, :class:`str`]
|
||||
Background color of the console
|
||||
"""
|
||||
if type(color) is str:
|
||||
@@ -74,7 +75,7 @@ class Console:
|
||||
r"""Sets the foreground color
|
||||
|
||||
Parameter:
|
||||
color: Union[:class:`cpl_core.console.background_color_enum.BackgroundColorEnum`, :class:`str`]
|
||||
color: Union[:class:`cpl.core.console.background_color_enum.BackgroundColorEnum`, :class:`str`]
|
||||
Foreground color of the console
|
||||
"""
|
||||
if type(color) is str:
|
||||
@@ -365,17 +366,17 @@ class Console:
|
||||
Message or header of the selection
|
||||
options: List[:class:`str`]
|
||||
Selectable options
|
||||
header_foreground_color: Union[:class:`str`, :class:`cpl_core.console.foreground_color_enum.ForegroundColorEnum`]
|
||||
header_foreground_color: Union[:class:`str`, :class:`cpl.core.console.foreground_color_enum.ForegroundColorEnum`]
|
||||
Foreground color of the header
|
||||
header_background_color: Union[:class:`str`, :class:`cpl_core.console.background_color_enum.BackgroundColorEnum`]
|
||||
header_background_color: Union[:class:`str`, :class:`cpl.core.console.background_color_enum.BackgroundColorEnum`]
|
||||
Background color of the header
|
||||
option_foreground_color: Union[:class:`str`, :class:`cpl_core.console.foreground_color_enum.ForegroundColorEnum`]
|
||||
option_foreground_color: Union[:class:`str`, :class:`cpl.core.console.foreground_color_enum.ForegroundColorEnum`]
|
||||
Foreground color of the options
|
||||
option_background_color: Union[:class:`str`, :class:`cpl_core.console.background_color_enum.BackgroundColorEnum`]
|
||||
option_background_color: Union[:class:`str`, :class:`cpl.core.console.background_color_enum.BackgroundColorEnum`]
|
||||
Background color of the options
|
||||
cursor_foreground_color: Union[:class:`str`, :class:`cpl_core.console.foreground_color_enum.ForegroundColorEnum`]
|
||||
cursor_foreground_color: Union[:class:`str`, :class:`cpl.core.console.foreground_color_enum.ForegroundColorEnum`]
|
||||
Foreground color of the cursor
|
||||
cursor_background_color: Union[:class:`str`, :class:`cpl_core.console.background_color_enum.BackgroundColorEnum`]
|
||||
cursor_background_color: Union[:class:`str`, :class:`cpl.core.console.background_color_enum.BackgroundColorEnum`]
|
||||
Background color of the cursor
|
||||
|
||||
Returns:
|
||||
@@ -429,13 +430,13 @@ class Console:
|
||||
Function to call
|
||||
args: :class:`list`
|
||||
Arguments of the function
|
||||
text_foreground_color: Union[:class:`str`, :class:`cpl_core.console.foreground_color_enum.ForegroundColorEnum`]
|
||||
text_foreground_color: Union[:class:`str`, :class:`cpl.core.console.foreground_color_enum.ForegroundColorEnum`]
|
||||
Foreground color of the text
|
||||
spinner_foreground_color: Union[:class:`str`, :class:`cpl_core.console.foreground_color_enum.ForegroundColorEnum`]
|
||||
spinner_foreground_color: Union[:class:`str`, :class:`cpl.core.console.foreground_color_enum.ForegroundColorEnum`]
|
||||
Foreground color of the spinner
|
||||
text_background_color: Union[:class:`str`, :class:`cpl_core.console.background_color_enum.BackgroundColorEnum`]
|
||||
text_background_color: Union[:class:`str`, :class:`cpl.core.console.background_color_enum.BackgroundColorEnum`]
|
||||
Background color of the text
|
||||
spinner_background_color: Union[:class:`str`, :class:`cpl_core.console.background_color_enum.BackgroundColorEnum`]
|
||||
spinner_background_color: Union[:class:`str`, :class:`cpl.core.console.background_color_enum.BackgroundColorEnum`]
|
||||
Background color of the spinner
|
||||
kwargs: :class:`dict`
|
||||
Keyword arguments of the call
|
||||
@@ -5,8 +5,8 @@ import time
|
||||
|
||||
from termcolor import colored
|
||||
|
||||
from cpl_core.console.background_color_enum import BackgroundColorEnum
|
||||
from cpl_core.console.foreground_color_enum import ForegroundColorEnum
|
||||
from cpl.core.console.background_color_enum import BackgroundColorEnum
|
||||
from cpl.core.console.foreground_color_enum import ForegroundColorEnum
|
||||
|
||||
|
||||
class SpinnerThread(threading.Thread):
|
||||
@@ -15,9 +15,9 @@ class SpinnerThread(threading.Thread):
|
||||
Parameter:
|
||||
msg_len: :class:`int`
|
||||
Length of the message
|
||||
foreground_color: :class:`cpl_core.console.foreground_color.ForegroundColorEnum`
|
||||
foreground_color: :class:`cpl.core.console.foreground_color.ForegroundColorEnum`
|
||||
Foreground color of the spinner
|
||||
background_color: :class:`cpl_core.console.background_color.BackgroundColorEnum`
|
||||
background_color: :class:`cpl.core.console.background_color.BackgroundColorEnum`
|
||||
Background color of the spinner
|
||||
"""
|
||||
|
||||
2
src/cpl-core/cpl/core/environment/__init__.py
Normal file
2
src/cpl-core/cpl/core/environment/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .environment_enum import EnvironmentEnum
|
||||
from .environment import Environment
|
||||
68
src/cpl-core/cpl/core/environment/environment.py
Normal file
68
src/cpl-core/cpl/core/environment/environment.py
Normal file
@@ -0,0 +1,68 @@
|
||||
import os
|
||||
from socket import gethostname
|
||||
from typing import Optional, Type
|
||||
|
||||
from cpl.core.environment.environment_enum import EnvironmentEnum
|
||||
from cpl.core.typing import T
|
||||
from cpl.core.utils.get_value import get_value
|
||||
|
||||
|
||||
class Environment:
|
||||
r"""Represents environment of the application
|
||||
|
||||
Parameter:
|
||||
name: :class:`cpl.core.environment.environment_name_enum.EnvironmentNameEnum`
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def get_environment(cls):
|
||||
return cls.get("ENVIRONMENT", str, EnvironmentEnum.production.value)
|
||||
|
||||
@classmethod
|
||||
def set_environment(cls, environment: str):
|
||||
assert environment is not None and environment != "", "environment must not be None or empty"
|
||||
assert environment.lower() in [
|
||||
e.value for e in EnvironmentEnum
|
||||
], f"environment must be one of {[e.value for e in EnvironmentEnum]}"
|
||||
cls.set("ENVIRONMENT", environment.lower())
|
||||
|
||||
@classmethod
|
||||
def get_app_name(cls) -> str:
|
||||
return cls.get("APP_NAME", str)
|
||||
|
||||
@classmethod
|
||||
def set_app_name(cls, app_name: str):
|
||||
cls.set("APP_NAME", app_name)
|
||||
|
||||
@staticmethod
|
||||
def get_host_name() -> str:
|
||||
return gethostname()
|
||||
|
||||
@staticmethod
|
||||
def get_cwd() -> str:
|
||||
return os.getcwd()
|
||||
|
||||
@staticmethod
|
||||
def set_cwd(working_directory: str):
|
||||
assert working_directory is not None and working_directory != "", "working_directory must not be None or empty"
|
||||
|
||||
os.chdir(working_directory)
|
||||
|
||||
@staticmethod
|
||||
def set(key: str, value: T):
|
||||
assert key is not None and key != "", "key must not be None or empty"
|
||||
|
||||
os.environ[key] = str(value)
|
||||
|
||||
@staticmethod
|
||||
def get(key: str, cast_type: Type[T], default: Optional[T] = None) -> Optional[T]:
|
||||
"""
|
||||
Get an environment variable and cast it to a specified type.
|
||||
:param str key: The name of the environment variable.
|
||||
:param Type[T] cast_type: A callable to cast the variable's value.
|
||||
:param Optional[T] default: The default value to return if the variable is not found. Defaults to None.The default value to return if the variable is not found. Defaults to None.
|
||||
:return: The casted value, or None if the variable is not found.
|
||||
:rtype: Optional[T]
|
||||
"""
|
||||
|
||||
return get_value(dict(os.environ), key, cast_type, default)
|
||||
@@ -1,7 +1,7 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class EnvironmentNameEnum(Enum):
|
||||
class EnvironmentEnum(Enum):
|
||||
production = "production"
|
||||
staging = "staging"
|
||||
testing = "testing"
|
||||
4
src/cpl-core/cpl/core/log/__init__.py
Normal file
4
src/cpl-core/cpl/core/log/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from .logger import Logger
|
||||
from .logger_abc import LoggerABC
|
||||
from .log_level_enum import LogLevelEnum
|
||||
from .logging_settings import LogSettings
|
||||
92
src/cpl-core/cpl/core/log/_log_writer.py
Normal file
92
src/cpl-core/cpl/core/log/_log_writer.py
Normal file
@@ -0,0 +1,92 @@
|
||||
import multiprocessing
|
||||
import os
|
||||
from datetime import datetime
|
||||
from typing import Self
|
||||
|
||||
from cpl.core.console import Console
|
||||
from cpl.core.log.log_level_enum import LogLevelEnum
|
||||
|
||||
|
||||
class LogWriter:
|
||||
_instance = None
|
||||
|
||||
# ANSI color codes for different log levels
|
||||
_COLORS = {
|
||||
LogLevelEnum.trace: "\033[37m", # Light Gray
|
||||
LogLevelEnum.debug: "\033[94m", # Blue
|
||||
LogLevelEnum.info: "\033[92m", # Green
|
||||
LogLevelEnum.warning: "\033[93m", # Yellow
|
||||
LogLevelEnum.error: "\033[91m", # Red
|
||||
LogLevelEnum.fatal: "\033[95m", # Magenta
|
||||
}
|
||||
|
||||
def __init__(self, file_prefix: str, level: LogLevelEnum = LogLevelEnum.info):
|
||||
self._file_prefix = file_prefix
|
||||
self._level = level
|
||||
|
||||
self._queue = multiprocessing.Queue()
|
||||
self._process = multiprocessing.Process(target=self._log_worker, daemon=True)
|
||||
|
||||
self._create_log_dir()
|
||||
self._process.start()
|
||||
|
||||
@property
|
||||
def level(self) -> LogLevelEnum:
|
||||
return self._level
|
||||
|
||||
@level.setter
|
||||
def level(self, value: LogLevelEnum):
|
||||
assert isinstance(value, LogLevelEnum), "Log level must be an instance of LogLevelEnum"
|
||||
self._level = value
|
||||
|
||||
@classmethod
|
||||
def get_instance(cls, file_prefix: str, level: LogLevelEnum = LogLevelEnum.info) -> Self:
|
||||
if cls._instance is None:
|
||||
cls._instance = LogWriter(file_prefix, level)
|
||||
return cls._instance
|
||||
|
||||
@staticmethod
|
||||
def _create_log_dir():
|
||||
if os.path.exists("logs"):
|
||||
return
|
||||
|
||||
os.makedirs("logs")
|
||||
|
||||
def _log_worker(self):
|
||||
"""Worker process that writes log messages from the queue to the file."""
|
||||
while True:
|
||||
content = self._queue.get()
|
||||
if content is None: # Shutdown signal
|
||||
break
|
||||
self._write_log_to_file(content)
|
||||
Console.write_line(f"{self._COLORS.get(self._level, '\033[0m')}{content}\033[0m")
|
||||
|
||||
@property
|
||||
def log_file(self):
|
||||
return f"logs/{self._file_prefix}_{datetime.now().strftime('%Y-%m-%d')}.log"
|
||||
|
||||
def _ensure_file_size(self):
|
||||
log_file = self.log_file
|
||||
if not os.path.exists(log_file) or os.path.getsize(log_file) <= 0.5 * 1024 * 1024:
|
||||
return
|
||||
|
||||
# if exists and size is greater than 300MB, create a new file
|
||||
os.rename(
|
||||
log_file,
|
||||
f"{log_file.split('.log')[0]}_{datetime.now().strftime('%H-%M-%S')}.log",
|
||||
)
|
||||
|
||||
def _write_log_to_file(self, content: str):
|
||||
self._ensure_file_size()
|
||||
with open(self.log_file, "a") as log_file:
|
||||
log_file.write(content + "\n")
|
||||
log_file.close()
|
||||
|
||||
def log(self, content: str):
|
||||
"""Enqueue log message without blocking main app."""
|
||||
self._queue.put(content)
|
||||
|
||||
def close(self):
|
||||
"""Gracefully stop the logging process."""
|
||||
self._queue.put(None)
|
||||
self._process.join()
|
||||
11
src/cpl-core/cpl/core/log/log_level_enum.py
Normal file
11
src/cpl-core/cpl/core/log/log_level_enum.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class LogLevelEnum(Enum):
|
||||
off = "OFF" # Nothing
|
||||
trace = "TRC" # Detailed app information's
|
||||
debug = "DEB" # Detailed app state
|
||||
info = "INF" # Normal information's
|
||||
warning = "WAR" # Error that can later be fatal
|
||||
error = "ERR" # Non fatal error
|
||||
fatal = "FAT" # Error that cause exit
|
||||
78
src/cpl-core/cpl/core/log/logger.py
Normal file
78
src/cpl-core/cpl/core/log/logger.py
Normal file
@@ -0,0 +1,78 @@
|
||||
import os
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
|
||||
from cpl.core.console import Console
|
||||
from cpl.core.log._log_writer import LogWriter
|
||||
from cpl.core.log.log_level_enum import LogLevelEnum
|
||||
from cpl.core.log.logger_abc import LoggerABC
|
||||
from cpl.core.typing import Messages, Source
|
||||
|
||||
|
||||
class Logger(LoggerABC):
|
||||
_level = LogLevelEnum.info
|
||||
_levels = [x for x in LogLevelEnum]
|
||||
|
||||
def __init__(self, source: Source, file_prefix: str = None):
|
||||
LoggerABC.__init__(self)
|
||||
assert source is not None and source != "", "Source cannot be None or empty"
|
||||
self._source = source
|
||||
|
||||
if file_prefix is None:
|
||||
file_prefix = "app"
|
||||
|
||||
self._file_prefix = file_prefix
|
||||
self._writer = LogWriter.get_instance(self._file_prefix)
|
||||
|
||||
@classmethod
|
||||
def set_level(cls, level: LogLevelEnum):
|
||||
if level in cls._levels:
|
||||
cls._level = level
|
||||
else:
|
||||
raise ValueError(f"Invalid log level: {level}")
|
||||
|
||||
def _log(self, level: LogLevelEnum, *messages: Messages):
|
||||
try:
|
||||
if self._levels.index(level) < self._levels.index(self._level):
|
||||
return
|
||||
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
|
||||
formatted_message = self._format_message(level.value, timestamp, *messages)
|
||||
|
||||
self._writer.log(formatted_message)
|
||||
except Exception as e:
|
||||
print(f"Error while logging: {e} -> {traceback.format_exc()}")
|
||||
|
||||
def _format_message(self, level: str, timestamp, *messages: Messages) -> str:
|
||||
if isinstance(messages, tuple):
|
||||
messages = list(messages)
|
||||
|
||||
if not isinstance(messages, list):
|
||||
messages = [messages]
|
||||
|
||||
messages = [str(message) for message in messages if message is not None]
|
||||
|
||||
return f"<{timestamp}> [{level.upper():^3}] [{self._file_prefix}] - [{self._source}]: {' '.join(messages)}"
|
||||
|
||||
def header(self, string: str):
|
||||
self._log(LogLevelEnum.info, string)
|
||||
|
||||
def trace(self, *messages: Messages):
|
||||
self._log(LogLevelEnum.trace, *messages)
|
||||
|
||||
def debug(self, *messages: Messages):
|
||||
self._log(LogLevelEnum.debug, *messages)
|
||||
|
||||
def info(self, *messages: Messages):
|
||||
self._log(LogLevelEnum.info, *messages)
|
||||
|
||||
def warning(self, *messages: Messages):
|
||||
self._log(LogLevelEnum.warning, *messages)
|
||||
|
||||
def error(self, message, e: Exception = None):
|
||||
self._log(LogLevelEnum.error, message, f"{e} -> {traceback.format_exc()}" if e else None)
|
||||
|
||||
def fatal(self, message, e: Exception = None, prevent_quit: bool = False):
|
||||
self._log(LogLevelEnum.fatal, message, f"{e} -> {traceback.format_exc()}" if e else None)
|
||||
if not prevent_quit:
|
||||
exit(-1)
|
||||
@@ -1,12 +1,18 @@
|
||||
from abc import abstractmethod, ABC
|
||||
|
||||
from cpl.core.typing import Messages
|
||||
|
||||
|
||||
class LoggerABC(ABC):
|
||||
r"""ABC for :class:`cpl_core.logging.logger_service.Logger`"""
|
||||
r"""ABC for :class:`cpl.core.log.logger_service.Logger`"""
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
ABC.__init__(self)
|
||||
def set_level(self, level: str):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _format_message(self, level: str, timestamp, *messages: Messages) -> str:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def header(self, string: str):
|
||||
@@ -16,10 +22,9 @@ class LoggerABC(ABC):
|
||||
string: :class:`str`
|
||||
String to write as header
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def trace(self, name: str, message: str):
|
||||
def trace(self, *messages: Messages):
|
||||
r"""Writes a trace message
|
||||
|
||||
Parameter:
|
||||
@@ -28,10 +33,9 @@ class LoggerABC(ABC):
|
||||
message: :class:`str`
|
||||
Message string
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def debug(self, name: str, message: str):
|
||||
def debug(self, *messages: Messages):
|
||||
r"""Writes a debug message
|
||||
|
||||
Parameter:
|
||||
@@ -40,10 +44,9 @@ class LoggerABC(ABC):
|
||||
message: :class:`str`
|
||||
Message string
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def info(self, name: str, message: str):
|
||||
def info(self, *messages: Messages):
|
||||
r"""Writes an information
|
||||
|
||||
Parameter:
|
||||
@@ -52,10 +55,9 @@ class LoggerABC(ABC):
|
||||
message: :class:`str`
|
||||
Message string
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def warn(self, name: str, message: str):
|
||||
def warning(self, *messages: Messages):
|
||||
r"""Writes an warning
|
||||
|
||||
Parameter:
|
||||
@@ -64,10 +66,9 @@ class LoggerABC(ABC):
|
||||
message: :class:`str`
|
||||
Message string
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def error(self, name: str, message: str, ex: Exception = None):
|
||||
def error(self, messages: str, e: Exception = None):
|
||||
r"""Writes an error
|
||||
|
||||
Parameter:
|
||||
@@ -78,10 +79,9 @@ class LoggerABC(ABC):
|
||||
ex: :class:`Exception`
|
||||
Thrown exception
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def fatal(self, name: str, message: str, ex: Exception = None):
|
||||
def fatal(self, messages: str, e: Exception = None):
|
||||
r"""Writes an error and ends the program
|
||||
|
||||
Parameter:
|
||||
@@ -92,4 +92,3 @@ class LoggerABC(ABC):
|
||||
ex: :class:`Exception`
|
||||
Thrown exception
|
||||
"""
|
||||
pass
|
||||
@@ -1,28 +1,24 @@
|
||||
import traceback
|
||||
from typing import Optional
|
||||
|
||||
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
|
||||
from cpl_core.console.console import Console
|
||||
from cpl_core.console.foreground_color_enum import ForegroundColorEnum
|
||||
from cpl_core.logging.logging_level_enum import LoggingLevelEnum
|
||||
from cpl_core.logging.logging_settings_name_enum import LoggingSettingsNameEnum
|
||||
from cpl.core.configuration.configuration_model_abc import ConfigurationModelABC
|
||||
from cpl.core.log.log_level_enum import LogLevelEnum
|
||||
|
||||
|
||||
class LoggingSettings(ConfigurationModelABC):
|
||||
class LogSettings(ConfigurationModelABC):
|
||||
r"""Representation of logging settings"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
path: str = None,
|
||||
filename: str = None,
|
||||
console_log_level: LoggingLevelEnum = None,
|
||||
file_log_level: LoggingLevelEnum = None,
|
||||
console_log_level: LogLevelEnum = None,
|
||||
file_log_level: LogLevelEnum = None,
|
||||
):
|
||||
ConfigurationModelABC.__init__(self)
|
||||
self._path: Optional[str] = path
|
||||
self._filename: Optional[str] = filename
|
||||
self._console: Optional[LoggingLevelEnum] = console_log_level
|
||||
self._level: Optional[LoggingLevelEnum] = file_log_level
|
||||
self._console: Optional[LogLevelEnum] = console_log_level
|
||||
self._level: Optional[LogLevelEnum] = file_log_level
|
||||
|
||||
@property
|
||||
def path(self) -> str:
|
||||
@@ -41,17 +37,17 @@ class LoggingSettings(ConfigurationModelABC):
|
||||
self._filename = filename
|
||||
|
||||
@property
|
||||
def console(self) -> LoggingLevelEnum:
|
||||
def console(self) -> LogLevelEnum:
|
||||
return self._console
|
||||
|
||||
@console.setter
|
||||
def console(self, console: LoggingLevelEnum) -> None:
|
||||
def console(self, console: LogLevelEnum) -> None:
|
||||
self._console = console
|
||||
|
||||
@property
|
||||
def level(self) -> LoggingLevelEnum:
|
||||
def level(self) -> LogLevelEnum:
|
||||
return self._level
|
||||
|
||||
@level.setter
|
||||
def level(self, level: LoggingLevelEnum) -> None:
|
||||
def level(self, level: LogLevelEnum) -> None:
|
||||
self._level = level
|
||||
3
src/cpl-core/cpl/core/pipes/__init__.py
Normal file
3
src/cpl-core/cpl/core/pipes/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .bool_pipe import BoolPipe
|
||||
from .ip_address_pipe import IPAddressPipe
|
||||
from .pipe_abc import PipeABC
|
||||
13
src/cpl-core/cpl/core/pipes/bool_pipe.py
Normal file
13
src/cpl-core/cpl/core/pipes/bool_pipe.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from cpl.core.pipes.pipe_abc import PipeABC
|
||||
from cpl.core.typing import T
|
||||
|
||||
|
||||
class BoolPipe[bool](PipeABC):
|
||||
|
||||
@staticmethod
|
||||
def to_str(value: T, *args):
|
||||
return str(value).lower()
|
||||
|
||||
@staticmethod
|
||||
def from_str(value: str, *args) -> T:
|
||||
return value in ("True", "true", "1", "yes", "y", "Y")
|
||||
38
src/cpl-core/cpl/core/pipes/ip_address_pipe.py
Normal file
38
src/cpl-core/cpl/core/pipes/ip_address_pipe.py
Normal file
@@ -0,0 +1,38 @@
|
||||
from cpl.core.pipes.pipe_abc import PipeABC
|
||||
from cpl.core.typing import T
|
||||
|
||||
|
||||
class IPAddressPipe[list](PipeABC):
|
||||
@staticmethod
|
||||
def to_str(value: T, *args) -> str:
|
||||
string = ""
|
||||
|
||||
if len(value) != 4:
|
||||
raise ValueError("Invalid IP")
|
||||
|
||||
for i in range(0, len(value)):
|
||||
byte = value[i]
|
||||
if not 0 <= byte <= 255:
|
||||
raise ValueError("Invalid IP")
|
||||
|
||||
if i == len(value) - 1:
|
||||
string += f"{byte}"
|
||||
else:
|
||||
string += f"{byte}."
|
||||
|
||||
return string
|
||||
|
||||
@staticmethod
|
||||
def from_str(value: str, *args) -> T:
|
||||
parts = value.split(".")
|
||||
if len(parts) != 4:
|
||||
raise Exception("Invalid IP")
|
||||
|
||||
result = []
|
||||
for part in parts:
|
||||
byte = int(part)
|
||||
if not 0 <= byte <= 255:
|
||||
raise Exception("Invalid IP")
|
||||
result.append(byte)
|
||||
|
||||
return result
|
||||
16
src/cpl-core/cpl/core/pipes/pipe_abc.py
Normal file
16
src/cpl-core/cpl/core/pipes/pipe_abc.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Generic
|
||||
|
||||
from cpl.core.typing import T
|
||||
|
||||
|
||||
class PipeABC(ABC, Generic[T]):
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def to_str(value: T, *args) -> str:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def from_str(value: str, *args) -> T:
|
||||
pass
|
||||
2
src/cpl-core/cpl/core/time/__init__.py
Normal file
2
src/cpl-core/cpl/core/time/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .time_format_settings import TimeFormatSettings
|
||||
from .time_format_settings_names_enum import TimeFormatSettingsNamesEnum
|
||||
@@ -1,10 +1,6 @@
|
||||
import traceback
|
||||
from typing import Optional
|
||||
|
||||
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
|
||||
from cpl_core.console.console import Console
|
||||
from cpl_core.console.foreground_color_enum import ForegroundColorEnum
|
||||
from cpl_core.time.time_format_settings_names_enum import TimeFormatSettingsNamesEnum
|
||||
from cpl.core.configuration.configuration_model_abc import ConfigurationModelABC
|
||||
|
||||
|
||||
class TimeFormatSettings(ConfigurationModelABC):
|
||||
10
src/cpl-core/cpl/core/typing.py
Normal file
10
src/cpl-core/cpl/core/typing.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from typing import TypeVar, Any
|
||||
|
||||
T = TypeVar("T")
|
||||
D = TypeVar("D")
|
||||
R = TypeVar("R")
|
||||
|
||||
Service = TypeVar("Service")
|
||||
Source = TypeVar("Source")
|
||||
|
||||
Messages = list[Any] | Any
|
||||
5
src/cpl-core/cpl/core/utils/__init__.py
Normal file
5
src/cpl-core/cpl/core/utils/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from .b64 import B64
|
||||
from .credential_manager import CredentialManager
|
||||
from .json_processor import JSONProcessor
|
||||
from .pip import Pip
|
||||
from .string import String
|
||||
43
src/cpl-core/cpl/core/utils/b64.py
Normal file
43
src/cpl-core/cpl/core/utils/b64.py
Normal file
@@ -0,0 +1,43 @@
|
||||
import base64
|
||||
from typing import Union
|
||||
|
||||
|
||||
class B64:
|
||||
|
||||
@staticmethod
|
||||
def encode(string: str) -> str:
|
||||
"""
|
||||
Encode a string with base64
|
||||
:param string:
|
||||
:return:
|
||||
"""
|
||||
return base64.b64encode(string.encode("utf-8")).decode("utf-8")
|
||||
|
||||
@staticmethod
|
||||
def decode(string: str) -> str:
|
||||
"""
|
||||
Decode a string with base64
|
||||
:param string:
|
||||
:return:
|
||||
"""
|
||||
return base64.b64decode(string).decode("utf-8")
|
||||
|
||||
@staticmethod
|
||||
def is_b64(sb: Union[str, bytes]) -> bool:
|
||||
"""
|
||||
Check if a string is base64 encoded
|
||||
:param Union[str, bytes] sb:
|
||||
:return:
|
||||
:rtype: bool
|
||||
"""
|
||||
try:
|
||||
if isinstance(sb, str):
|
||||
# If there's any unicode here, an exception will be thrown and the function will return false
|
||||
sb_bytes = bytes(sb, "ascii")
|
||||
elif isinstance(sb, bytes):
|
||||
sb_bytes = sb
|
||||
else:
|
||||
raise ValueError("Argument must be string or bytes")
|
||||
return base64.b64encode(base64.b64decode(sb_bytes)) == sb_bytes
|
||||
except ValueError:
|
||||
return False
|
||||
56
src/cpl-core/cpl/core/utils/get_value.py
Normal file
56
src/cpl-core/cpl/core/utils/get_value.py
Normal file
@@ -0,0 +1,56 @@
|
||||
from typing import Type, Optional
|
||||
|
||||
from cpl.core.typing import T
|
||||
|
||||
|
||||
def get_value(
|
||||
source: dict,
|
||||
key: str,
|
||||
cast_type: Type[T],
|
||||
default: Optional[T] = None,
|
||||
list_delimiter: str = ",",
|
||||
) -> Optional[T]:
|
||||
"""
|
||||
Get value from source dictionary and cast it to a specified type.
|
||||
:param dict source: The source dictionary.
|
||||
:param str key: The name of the environment variable.
|
||||
:param Type[T] cast_type: A callable to cast the variable's value.
|
||||
:param Optional[T] default: The default value to return if the variable is not found. Defaults to None.
|
||||
:param str list_delimiter: The delimiter to split the value into a list. Defaults to ",".
|
||||
:return: The casted value, or None if the key is not found.
|
||||
:rtype: Optional[T]
|
||||
"""
|
||||
|
||||
if key not in source:
|
||||
return default
|
||||
|
||||
value = source[key]
|
||||
if isinstance(
|
||||
value,
|
||||
cast_type if not hasattr(cast_type, "__origin__") else cast_type.__origin__,
|
||||
):
|
||||
# Handle list[int] case explicitly
|
||||
if hasattr(cast_type, "__origin__") and cast_type.__origin__ == list:
|
||||
subtype = cast_type.__args__[0] if hasattr(cast_type, "__args__") else None
|
||||
if subtype is not None:
|
||||
return [subtype(item) for item in value]
|
||||
return value
|
||||
|
||||
try:
|
||||
if cast_type == bool:
|
||||
return value.lower() in ["true", "1"]
|
||||
|
||||
if (cast_type if not hasattr(cast_type, "__origin__") else cast_type.__origin__) == list:
|
||||
if not (value.startswith("[") and value.endswith("]")) and list_delimiter not in value:
|
||||
raise ValueError("List values must be enclosed in square brackets or use a delimiter.")
|
||||
|
||||
if value.startswith("[") and value.endswith("]"):
|
||||
value = value[1:-1]
|
||||
|
||||
value = value.split(list_delimiter)
|
||||
subtype = cast_type.__args__[0] if hasattr(cast_type, "__args__") else None
|
||||
return [subtype(item) if subtype is not None else item for item in value]
|
||||
|
||||
return cast_type(value)
|
||||
except (ValueError, TypeError):
|
||||
return default
|
||||
@@ -1,7 +1,7 @@
|
||||
import enum
|
||||
from inspect import signature, Parameter
|
||||
|
||||
from cpl_core.utils import String
|
||||
from cpl.core.utils.string import String
|
||||
|
||||
|
||||
class JSONProcessor:
|
||||
@@ -16,7 +16,7 @@ class JSONProcessor:
|
||||
if parameter.name == "self" or parameter.annotation == Parameter.empty:
|
||||
continue
|
||||
|
||||
name = String.first_to_upper(String.convert_to_camel_case(parameter.name))
|
||||
name = String.first_to_upper(String.to_camel_case(parameter.name))
|
||||
name_first_lower = String.first_to_lower(name)
|
||||
if name in values or name_first_lower in values or name.upper() in values:
|
||||
value = ""
|
||||
@@ -4,11 +4,10 @@ import sys
|
||||
from contextlib import suppress
|
||||
from typing import Optional
|
||||
|
||||
from cpl_core.console import Console
|
||||
|
||||
|
||||
class Pip:
|
||||
r"""Executes pip commands"""
|
||||
|
||||
_executable = sys.executable
|
||||
_env = os.environ
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import random
|
||||
import re
|
||||
import string
|
||||
import random
|
||||
|
||||
|
||||
class String:
|
||||
r"""Useful functions for strings"""
|
||||
|
||||
@staticmethod
|
||||
def convert_to_camel_case(chars: str) -> str:
|
||||
def to_camel_case(s: str) -> str:
|
||||
r"""Converts string to camel case
|
||||
|
||||
Parameter:
|
||||
@@ -17,16 +17,10 @@ class String:
|
||||
Returns:
|
||||
String converted to CamelCase
|
||||
"""
|
||||
converted_name = chars
|
||||
char_set = string.punctuation + " "
|
||||
for char in char_set:
|
||||
if char in converted_name:
|
||||
converted_name = "".join(word.title() for word in converted_name.split(char))
|
||||
|
||||
return converted_name
|
||||
return re.sub(r"(?<!^)(?=[A-Z])", "_", s).lower()
|
||||
|
||||
@staticmethod
|
||||
def convert_to_snake_case(chars: str) -> str:
|
||||
def to_snake_case(chars: str) -> str:
|
||||
r"""Converts string to snake case
|
||||
|
||||
Parameter:
|
||||
@@ -56,7 +50,7 @@ class String:
|
||||
return re.sub(pattern2, r"\1_\2", file_name).lower()
|
||||
|
||||
@staticmethod
|
||||
def first_to_upper(chars: str) -> str:
|
||||
def first_to_upper(s: str) -> str:
|
||||
r"""Converts first char to upper
|
||||
|
||||
Parameter:
|
||||
@@ -66,10 +60,10 @@ class String:
|
||||
Returns:
|
||||
String with first char as upper
|
||||
"""
|
||||
return f"{chars[0].upper()}{chars[1:]}"
|
||||
return s[0].upper() + s[1:] if s else s
|
||||
|
||||
@staticmethod
|
||||
def first_to_lower(chars: str) -> str:
|
||||
def first_to_lower(s: str) -> str:
|
||||
r"""Converts first char to lower
|
||||
|
||||
Parameter:
|
||||
@@ -79,14 +73,24 @@ class String:
|
||||
Returns:
|
||||
String with first char as lower
|
||||
"""
|
||||
return f"{chars[0].lower()}{chars[1:]}"
|
||||
return s[0].lower() + s[1:] if s else s
|
||||
|
||||
@staticmethod
|
||||
def random_string(chars: str, length: int) -> str:
|
||||
def random(length: int, letters=True, digits=False, special_characters=False) -> str:
|
||||
r"""Creates random string by given chars and length
|
||||
|
||||
Returns:
|
||||
String of random chars
|
||||
"""
|
||||
|
||||
return "".join(random.choice(chars) for _ in range(length))
|
||||
characters = []
|
||||
if letters:
|
||||
characters.append(string.ascii_letters)
|
||||
|
||||
if digits:
|
||||
characters.append(string.digits)
|
||||
|
||||
if special_characters:
|
||||
characters.append(string.punctuation)
|
||||
|
||||
return "".join(random.choice(characters) for _ in range(length)) if characters else ""
|
||||
29
src/cpl-core/pyproject.toml
Normal file
29
src/cpl-core/pyproject.toml
Normal file
@@ -0,0 +1,29 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=70.1.0", "wheel>=0.43.0"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "cpl-core"
|
||||
version = "2024.7.0"
|
||||
description = "CPL core"
|
||||
readme = "CPL core package"
|
||||
requires-python = ">=3.12"
|
||||
license = "MIT"
|
||||
authors = [
|
||||
{ name = "Sven Heidemann", email = "sven.heidemann@sh-edraft.de" }
|
||||
]
|
||||
keywords = ["cpl", "core", "backend", "shared", "library"]
|
||||
|
||||
dynamic = ["dependencies", "optional-dependencies"]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://www.sh-edraft.de"
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["."]
|
||||
include = ["cpl*"]
|
||||
|
||||
[tool.setuptools.dynamic]
|
||||
dependencies = { file = ["requirements.txt"] }
|
||||
optional-dependencies.dev = { file = ["requirements.dev.txt"] }
|
||||
|
||||
1
src/cpl-core/requirements.dev.txt
Normal file
1
src/cpl-core/requirements.dev.txt
Normal file
@@ -0,0 +1 @@
|
||||
black==25.1.0
|
||||
6
src/cpl-core/requirements.txt
Normal file
6
src/cpl-core/requirements.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
art==6.5
|
||||
colorama==0.4.6
|
||||
tabulate==0.9.0
|
||||
termcolor==3.1.0
|
||||
mysql-connector-python==9.4.0
|
||||
pynput==1.8.1
|
||||
3
src/cpl-database/cpl/database/__init__.py
Normal file
3
src/cpl-database/cpl/database/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .database_settings_name_enum import DatabaseSettingsNameEnum
|
||||
from .database_settings import DatabaseSettings
|
||||
from .table_abc import TableABC
|
||||
2
src/cpl-database/cpl/database/connection/__init__.py
Normal file
2
src/cpl-database/cpl/database/connection/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .database_connection import DatabaseConnection
|
||||
from .database_connection_abc import DatabaseConnectionABC
|
||||
@@ -4,9 +4,9 @@ import mysql.connector as sql
|
||||
from mysql.connector.abstracts import MySQLConnectionAbstract
|
||||
from mysql.connector.cursor import MySQLCursorBuffered
|
||||
|
||||
from cpl_core.database.connection.database_connection_abc import DatabaseConnectionABC
|
||||
from cpl_core.database.database_settings import DatabaseSettings
|
||||
from cpl_core.utils.credential_manager import CredentialManager
|
||||
from cpl.database.connection.database_connection_abc import DatabaseConnectionABC
|
||||
from cpl.database.database_settings import DatabaseSettings
|
||||
from cpl.core.utils.credential_manager import CredentialManager
|
||||
|
||||
|
||||
class DatabaseConnection(DatabaseConnectionABC):
|
||||
@@ -1,12 +1,12 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from cpl_core.database.database_settings import DatabaseSettings
|
||||
from cpl.database.database_settings import DatabaseSettings
|
||||
from mysql.connector.abstracts import MySQLConnectionAbstract
|
||||
from mysql.connector.cursor import MySQLCursorBuffered
|
||||
|
||||
|
||||
class DatabaseConnectionABC(ABC):
|
||||
r"""ABC for the :class:`cpl_core.database.connection.database_connection.DatabaseConnection`"""
|
||||
r"""ABC for the :class:`cpl.database.connection.database_connection.DatabaseConnection`"""
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
@@ -30,4 +30,3 @@ class DatabaseConnectionABC(ABC):
|
||||
connection_string: :class:`str`
|
||||
Database connection string, see: https://docs.sqlalchemy.org/en/14/core/engines.html
|
||||
"""
|
||||
pass
|
||||
2
src/cpl-database/cpl/database/context/__init__.py
Normal file
2
src/cpl-database/cpl/database/context/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .database_context import DatabaseContext
|
||||
from .database_context_abc import DatabaseContextABC
|
||||
@@ -1,12 +1,10 @@
|
||||
from typing import Optional
|
||||
|
||||
import mysql
|
||||
|
||||
from cpl_core.database.connection.database_connection import DatabaseConnection
|
||||
from cpl_core.database.connection.database_connection_abc import DatabaseConnectionABC
|
||||
from cpl_core.database.context.database_context_abc import DatabaseContextABC
|
||||
from cpl_core.database.database_settings import DatabaseSettings
|
||||
from cpl_core.database.table_abc import TableABC
|
||||
from cpl.database.connection.database_connection import DatabaseConnection
|
||||
from cpl.database.connection.database_connection_abc import DatabaseConnectionABC
|
||||
from cpl.database.context.database_context_abc import DatabaseContextABC
|
||||
from cpl.database.database_settings import DatabaseSettings
|
||||
from mysql.connector.cursor import MySQLCursorBuffered
|
||||
|
||||
|
||||
@@ -14,7 +12,7 @@ class DatabaseContext(DatabaseContextABC):
|
||||
r"""Representation of the database context
|
||||
|
||||
Parameter:
|
||||
database_settings: :class:`cpl_core.database.database_settings.DatabaseSettings`
|
||||
database_settings: :class:`cpl.database.database_settings.DatabaseSettings`
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
@@ -31,7 +29,7 @@ class DatabaseContext(DatabaseContextABC):
|
||||
def _ping_and_reconnect(self):
|
||||
try:
|
||||
self._db.server.ping(reconnect=True, attempts=3, delay=5)
|
||||
except Exception as err:
|
||||
except Exception:
|
||||
# reconnect your cursor as you did in __init__ or wherever
|
||||
if self._settings is None:
|
||||
raise Exception("Call DatabaseContext.connect first")
|
||||
@@ -1,11 +1,11 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from cpl_core.database.database_settings import DatabaseSettings
|
||||
from cpl.database.database_settings import DatabaseSettings
|
||||
from mysql.connector.cursor import MySQLCursorBuffered
|
||||
|
||||
|
||||
class DatabaseContextABC(ABC):
|
||||
r"""ABC for the :class:`cpl_core.database.context.database_context.DatabaseContext`"""
|
||||
r"""ABC for the :class:`cpl.database.context.database_context.DatabaseContext`"""
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self, *args):
|
||||
@@ -21,14 +21,12 @@ class DatabaseContextABC(ABC):
|
||||
r"""Connects to a database by connection settings
|
||||
|
||||
Parameter:
|
||||
database_settings :class:`cpl_core.database.database_settings.DatabaseSettings`
|
||||
database_settings :class:`cpl.database.database_settings.DatabaseSettings`
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def save_changes(self):
|
||||
r"""Saves changes of the database"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def select(self, statement: str) -> list[tuple]:
|
||||
@@ -40,4 +38,3 @@ class DatabaseContextABC(ABC):
|
||||
Returns:
|
||||
list: Fetched list of selected elements
|
||||
"""
|
||||
pass
|
||||
@@ -1,6 +1,6 @@
|
||||
from typing import Optional
|
||||
|
||||
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
|
||||
from cpl.core.configuration.configuration_model_abc import ConfigurationModelABC
|
||||
|
||||
|
||||
class DatabaseSettings(ConfigurationModelABC):
|
||||
8
src/cpl-database/cpl/database/db_logger.py
Normal file
8
src/cpl-database/cpl/database/db_logger.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from cpl.core.log import Logger
|
||||
from cpl.core.typing import Source
|
||||
|
||||
|
||||
class DBLogger(Logger):
|
||||
|
||||
def __init__(self, source: Source):
|
||||
Logger.__init__(self, source, "db")
|
||||
30
src/cpl-database/pyproject.toml
Normal file
30
src/cpl-database/pyproject.toml
Normal file
@@ -0,0 +1,30 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=70.1.0", "wheel>=0.43.0"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "cpl-database"
|
||||
version = "2024.7.0"
|
||||
description = "CPL database"
|
||||
readme ="CPL database package"
|
||||
requires-python = ">=3.12"
|
||||
license = { text = "MIT" }
|
||||
authors = [
|
||||
{ name = "Sven Heidemann", email = "sven.heidemann@sh-edraft.de" }
|
||||
]
|
||||
keywords = ["cpl", "database", "backend", "shared", "library"]
|
||||
|
||||
dynamic = ["dependencies", "optional-dependencies"]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://www.sh-edraft.de"
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["."]
|
||||
include = ["cpl*"]
|
||||
|
||||
[tool.setuptools.dynamic]
|
||||
dependencies = { file = ["requirements.txt"] }
|
||||
optional-dependencies.dev = { file = ["requirements.dev.txt"] }
|
||||
|
||||
|
||||
1
src/cpl-database/requirements.dev.txt
Normal file
1
src/cpl-database/requirements.dev.txt
Normal file
@@ -0,0 +1 @@
|
||||
black==25.1.0
|
||||
2
src/cpl-database/requirements.txt
Normal file
2
src/cpl-database/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
cpl-core
|
||||
cpl-dependency
|
||||
8
src/cpl-dependency/cpl/dependency/__init__.py
Normal file
8
src/cpl-dependency/cpl/dependency/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from .scope import Scope
|
||||
from .scope_abc import ScopeABC
|
||||
from .service_collection import ServiceCollection
|
||||
from .service_collection_abc import ServiceCollectionABC
|
||||
from .service_descriptor import ServiceDescriptor
|
||||
from .service_lifetime_enum import ServiceLifetimeEnum
|
||||
from .service_provider import ServiceProvider
|
||||
from .service_provider_abc import ServiceProviderABC
|
||||
@@ -1,6 +1,5 @@
|
||||
from cpl_core.console.console import Console
|
||||
from cpl_core.dependency_injection.scope_abc import ScopeABC
|
||||
from cpl_core.dependency_injection.service_provider_abc import ServiceProviderABC
|
||||
from cpl.dependency.scope_abc import ScopeABC
|
||||
from cpl.dependency.service_provider_abc import ServiceProviderABC
|
||||
|
||||
|
||||
class Scope(ScopeABC):
|
||||
@@ -2,7 +2,7 @@ from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class ScopeABC(ABC):
|
||||
r"""ABC for the class :class:`cpl_core.dependency_injection.scope.Scope`"""
|
||||
r"""ABC for the class :class:`cpl.dependency.scope.Scope`"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
@@ -13,11 +13,9 @@ class ScopeABC(ABC):
|
||||
r"""Returns to service provider of scope
|
||||
|
||||
Returns:
|
||||
Object of type :class:`cpl_core.dependency_injection.service_provider_abc.ServiceProviderABC`
|
||||
Object of type :class:`cpl.dependency.service_provider_abc.ServiceProviderABC`
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def dispose(self):
|
||||
r"""Sets service_provider to None"""
|
||||
pass
|
||||
18
src/cpl-dependency/cpl/dependency/scope_builder.py
Normal file
18
src/cpl-dependency/cpl/dependency/scope_builder.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from cpl.dependency.scope import Scope
|
||||
from cpl.dependency.scope_abc import ScopeABC
|
||||
from cpl.dependency.service_provider_abc import ServiceProviderABC
|
||||
|
||||
|
||||
class ScopeBuilder:
|
||||
r"""Class to build :class:`cpl.dependency.scope.Scope`"""
|
||||
|
||||
def __init__(self, service_provider: ServiceProviderABC) -> None:
|
||||
self._service_provider = service_provider
|
||||
|
||||
def build(self) -> ScopeABC:
|
||||
r"""Returns scope
|
||||
|
||||
Returns:
|
||||
Object of type :class:`cpl.dependency.scope.Scope`
|
||||
"""
|
||||
return Scope(self._service_provider)
|
||||
@@ -1,26 +1,23 @@
|
||||
from typing import Union, Type, Callable, Optional
|
||||
|
||||
from cpl_core.configuration.configuration_abc import ConfigurationABC
|
||||
from cpl_core.database.context.database_context_abc import DatabaseContextABC
|
||||
from cpl_core.database.database_settings import DatabaseSettings
|
||||
from cpl_core.dependency_injection.service_collection_abc import ServiceCollectionABC
|
||||
from cpl_core.dependency_injection.service_descriptor import ServiceDescriptor
|
||||
from cpl_core.dependency_injection.service_lifetime_enum import ServiceLifetimeEnum
|
||||
from cpl_core.dependency_injection.service_provider import ServiceProvider
|
||||
from cpl_core.dependency_injection.service_provider_abc import ServiceProviderABC
|
||||
from cpl_core.logging.logger_abc import LoggerABC
|
||||
from cpl_core.logging.logger_service import Logger
|
||||
from cpl_core.pipes.pipe_abc import PipeABC
|
||||
from cpl_core.type import T
|
||||
from cpl.database.context.database_context_abc import DatabaseContextABC
|
||||
from cpl.database.database_settings import DatabaseSettings
|
||||
from cpl.dependency.service_collection_abc import ServiceCollectionABC
|
||||
from cpl.dependency.service_descriptor import ServiceDescriptor
|
||||
from cpl.dependency.service_lifetime_enum import ServiceLifetimeEnum
|
||||
from cpl.dependency.service_provider import ServiceProvider
|
||||
from cpl.dependency.service_provider_abc import ServiceProviderABC
|
||||
from cpl.core.log.logger import Logger
|
||||
from cpl.core.log.logger_abc import LoggerABC
|
||||
from cpl.core.pipes.pipe_abc import PipeABC
|
||||
from cpl.core.typing import T, Service
|
||||
|
||||
|
||||
class ServiceCollection(ServiceCollectionABC):
|
||||
r"""Representation of the collection of services"""
|
||||
|
||||
def __init__(self, config: ConfigurationABC):
|
||||
def __init__(self):
|
||||
ServiceCollectionABC.__init__(self)
|
||||
self._configuration: ConfigurationABC = config
|
||||
|
||||
self._database_context: Optional[DatabaseContextABC] = None
|
||||
self._service_descriptors: list[ServiceDescriptor] = []
|
||||
|
||||
@@ -53,7 +50,7 @@ class ServiceCollection(ServiceCollectionABC):
|
||||
self._database_context.connect(db_settings)
|
||||
|
||||
def add_logging(self):
|
||||
self.add_singleton(LoggerABC, Logger)
|
||||
self.add_transient(LoggerABC, Logger)
|
||||
return self
|
||||
|
||||
def add_pipes(self):
|
||||
@@ -61,19 +58,19 @@ class ServiceCollection(ServiceCollectionABC):
|
||||
self.add_transient(PipeABC, pipe)
|
||||
return self
|
||||
|
||||
def add_singleton(self, service_type: T, service: T = None):
|
||||
def add_singleton(self, service_type: T, service: Service = None):
|
||||
self._add_descriptor_by_lifetime(service_type, ServiceLifetimeEnum.singleton, service)
|
||||
return self
|
||||
|
||||
def add_scoped(self, service_type: T, service: T = None):
|
||||
def add_scoped(self, service_type: T, service: Service = None):
|
||||
self._add_descriptor_by_lifetime(service_type, ServiceLifetimeEnum.scoped, service)
|
||||
return self
|
||||
|
||||
def add_transient(self, service_type: T, service: T = None):
|
||||
def add_transient(self, service_type: T, service: Service = None):
|
||||
self._add_descriptor_by_lifetime(service_type, ServiceLifetimeEnum.transient, service)
|
||||
return self
|
||||
|
||||
def build_service_provider(self) -> ServiceProviderABC:
|
||||
sp = ServiceProvider(self._service_descriptors, self._configuration, self._database_context)
|
||||
sp = ServiceProvider(self._service_descriptors, self._database_context)
|
||||
ServiceProviderABC.set_global_provider(sp)
|
||||
return sp
|
||||
@@ -1,15 +1,14 @@
|
||||
from abc import abstractmethod, ABC
|
||||
from collections.abc import Callable
|
||||
from typing import Type
|
||||
|
||||
from cpl_core.database.database_settings import DatabaseSettings
|
||||
from cpl_core.database.context.database_context_abc import DatabaseContextABC
|
||||
from cpl_core.dependency_injection.service_provider_abc import ServiceProviderABC
|
||||
from cpl_core.type import T
|
||||
from cpl.database.context.database_context_abc import DatabaseContextABC
|
||||
from cpl.database.database_settings import DatabaseSettings
|
||||
from cpl.dependency.service_provider_abc import ServiceProviderABC
|
||||
from cpl.core.typing import T, Source
|
||||
|
||||
|
||||
class ServiceCollectionABC(ABC):
|
||||
r"""ABC for the class :class:`cpl_core.dependency_injection.service_collection.ServiceCollection`"""
|
||||
r"""ABC for the class :class:`cpl.dependency.service_collection.ServiceCollection`"""
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
@@ -20,30 +19,25 @@ class ServiceCollectionABC(ABC):
|
||||
r"""Adds database context
|
||||
|
||||
Parameter:
|
||||
db_context: Type[:class:`cpl_core.database.context.database_context_abc.DatabaseContextABC`]
|
||||
db_context: Type[:class:`cpl.database.context.database_context_abc.DatabaseContextABC`]
|
||||
Database context
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def add_logging(self):
|
||||
r"""Adds the CPL internal logger"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def add_pipes(self):
|
||||
r"""Adds the CPL internal pipes as transient"""
|
||||
pass
|
||||
|
||||
def add_discord(self):
|
||||
r"""Adds the CPL discord"""
|
||||
raise NotImplementedError("You should install and use the cpl-discord package")
|
||||
pass
|
||||
|
||||
def add_translation(self):
|
||||
r"""Adds the CPL translation"""
|
||||
raise NotImplementedError("You should install and use the cpl-translation package")
|
||||
pass
|
||||
|
||||
def add_mail(self):
|
||||
r"""Adds the CPL mail"""
|
||||
raise NotImplementedError("You should install and use the cpl-mail package")
|
||||
|
||||
@abstractmethod
|
||||
def add_transient(self, service_type: T, service: T = None) -> "ServiceCollectionABC":
|
||||
@@ -56,9 +50,8 @@ class ServiceCollectionABC(ABC):
|
||||
Object of the service
|
||||
|
||||
Returns:
|
||||
self: :class:`cpl_core.dependency_injection.service_collection_abc.ServiceCollectionABC`
|
||||
self: :class:`cpl.dependency.service_collection_abc.ServiceCollectionABC`
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def add_scoped(self, service_type: T, service: T = None) -> "ServiceCollectionABC":
|
||||
@@ -71,9 +64,8 @@ class ServiceCollectionABC(ABC):
|
||||
Object of the service
|
||||
|
||||
Returns:
|
||||
self: :class:`cpl_core.dependency_injection.service_collection_abc.ServiceCollectionABC`
|
||||
self: :class:`cpl.dependency.service_collection_abc.ServiceCollectionABC`
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def add_singleton(self, service_type: T, service: T = None) -> "ServiceCollectionABC":
|
||||
@@ -86,15 +78,13 @@ class ServiceCollectionABC(ABC):
|
||||
Object of the service
|
||||
|
||||
Returns:
|
||||
self: :class:`cpl_core.dependency_injection.service_collection_abc.ServiceCollectionABC`
|
||||
self: :class:`cpl.dependency.service_collection_abc.ServiceCollectionABC`
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def build_service_provider(self) -> ServiceProviderABC:
|
||||
r"""Creates instance of the service provider
|
||||
|
||||
Returns:
|
||||
Object of type :class:`cpl_core.dependency_injection.service_provider_abc.ServiceProviderABC`
|
||||
Object of type :class:`cpl.dependency.service_provider_abc.ServiceProviderABC`
|
||||
"""
|
||||
pass
|
||||
@@ -1,7 +1,6 @@
|
||||
from typing import Union, Optional
|
||||
|
||||
from cpl_core.console import Console
|
||||
from cpl_core.dependency_injection.service_lifetime_enum import ServiceLifetimeEnum
|
||||
from cpl.dependency.service_lifetime_enum import ServiceLifetimeEnum
|
||||
|
||||
|
||||
class ServiceDescriptor:
|
||||
@@ -10,7 +9,7 @@ class ServiceDescriptor:
|
||||
Parameter:
|
||||
implementation: Union[:class:`type`, Optional[:class:`object`]]
|
||||
Object or type of service
|
||||
lifetime: :class:`cpl_core.dependency_injection.service_lifetime_enum.ServiceLifetimeEnum`
|
||||
lifetime: :class:`cpl.dependency.service_lifetime_enum.ServiceLifetimeEnum`
|
||||
Lifetime of the service
|
||||
"""
|
||||
|
||||
@@ -3,16 +3,16 @@ import typing
|
||||
from inspect import signature, Parameter, Signature
|
||||
from typing import Optional
|
||||
|
||||
from cpl_core.configuration.configuration_abc import ConfigurationABC
|
||||
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
|
||||
from cpl_core.database.context.database_context_abc import DatabaseContextABC
|
||||
from cpl_core.dependency_injection.scope_abc import ScopeABC
|
||||
from cpl_core.dependency_injection.scope_builder import ScopeBuilder
|
||||
from cpl_core.dependency_injection.service_descriptor import ServiceDescriptor
|
||||
from cpl_core.dependency_injection.service_lifetime_enum import ServiceLifetimeEnum
|
||||
from cpl_core.dependency_injection.service_provider_abc import ServiceProviderABC
|
||||
from cpl_core.environment.application_environment_abc import ApplicationEnvironmentABC
|
||||
from cpl_core.type import T, R
|
||||
from cpl.core.configuration import Configuration
|
||||
from cpl.core.configuration.configuration_model_abc import ConfigurationModelABC
|
||||
from cpl.database.context.database_context_abc import DatabaseContextABC
|
||||
from cpl.core.environment import Environment
|
||||
from cpl.core.typing import T, R, Source
|
||||
from cpl.dependency.scope_abc import ScopeABC
|
||||
from cpl.dependency.scope_builder import ScopeBuilder
|
||||
from cpl.dependency.service_descriptor import ServiceDescriptor
|
||||
from cpl.dependency.service_lifetime_enum import ServiceLifetimeEnum
|
||||
from cpl.dependency.service_provider_abc import ServiceProviderABC
|
||||
|
||||
|
||||
class ServiceProvider(ServiceProviderABC):
|
||||
@@ -20,24 +20,22 @@ class ServiceProvider(ServiceProviderABC):
|
||||
|
||||
Parameter
|
||||
---------
|
||||
service_descriptors: list[:class:`cpl_core.dependency_injection.service_descriptor.ServiceDescriptor`]
|
||||
service_descriptors: list[:class:`cpl.dependency.service_descriptor.ServiceDescriptor`]
|
||||
Descriptor of the service
|
||||
config: :class:`cpl_core.configuration.configuration_abc.ConfigurationABC`
|
||||
config: :class:`cpl.core.configuration.configuration_abc.ConfigurationABC`
|
||||
CPL Configuration
|
||||
db_context: Optional[:class:`cpl_core.database.context.database_context_abc.DatabaseContextABC`]
|
||||
db_context: Optional[:class:`cpl.database.context.database_context_abc.DatabaseContextABC`]
|
||||
Database representation
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
service_descriptors: list[ServiceDescriptor],
|
||||
config: ConfigurationABC,
|
||||
db_context: Optional[DatabaseContextABC],
|
||||
):
|
||||
ServiceProviderABC.__init__(self)
|
||||
|
||||
self._service_descriptors: list[ServiceDescriptor] = service_descriptors
|
||||
self._configuration: ConfigurationABC = config
|
||||
self._database_context = db_context
|
||||
self._scope: Optional[ScopeABC] = None
|
||||
|
||||
@@ -48,7 +46,7 @@ class ServiceProvider(ServiceProviderABC):
|
||||
|
||||
return None
|
||||
|
||||
def _get_service(self, parameter: Parameter) -> Optional[object]:
|
||||
def _get_service(self, parameter: Parameter, origin_service_type: type = None) -> Optional[object]:
|
||||
for descriptor in self._service_descriptors:
|
||||
if descriptor.service_type == parameter.annotation or issubclass(
|
||||
descriptor.service_type, parameter.annotation
|
||||
@@ -56,7 +54,7 @@ class ServiceProvider(ServiceProviderABC):
|
||||
if descriptor.implementation is not None:
|
||||
return descriptor.implementation
|
||||
|
||||
implementation = self.build_service(descriptor.service_type)
|
||||
implementation = self._build_service(descriptor.service_type, origin_service_type=origin_service_type)
|
||||
if descriptor.lifetime == ServiceLifetimeEnum.singleton:
|
||||
descriptor.implementation = implementation
|
||||
|
||||
@@ -64,7 +62,7 @@ class ServiceProvider(ServiceProviderABC):
|
||||
|
||||
# raise Exception(f'Service {parameter.annotation} not found')
|
||||
|
||||
def _get_services(self, t: type, *args, **kwargs) -> list[Optional[object]]:
|
||||
def _get_services(self, t: type, *args, service_type: type = None, **kwargs) -> list[Optional[object]]:
|
||||
implementations = []
|
||||
for descriptor in self._service_descriptors:
|
||||
if descriptor.service_type == t or issubclass(descriptor.service_type, t):
|
||||
@@ -72,7 +70,7 @@ class ServiceProvider(ServiceProviderABC):
|
||||
implementations.append(descriptor.implementation)
|
||||
continue
|
||||
|
||||
implementation = self.build_service(descriptor.service_type, *args, **kwargs)
|
||||
implementation = self._build_service(descriptor.service_type, *args, service_type, **kwargs)
|
||||
if descriptor.lifetime == ServiceLifetimeEnum.singleton:
|
||||
descriptor.implementation = implementation
|
||||
|
||||
@@ -80,35 +78,41 @@ class ServiceProvider(ServiceProviderABC):
|
||||
|
||||
return implementations
|
||||
|
||||
def build_by_signature(self, sig: Signature) -> list[R]:
|
||||
def _build_by_signature(self, sig: Signature, origin_service_type: type) -> list[R]:
|
||||
params = []
|
||||
for param in sig.parameters.items():
|
||||
parameter = param[1]
|
||||
if parameter.name != "self" and parameter.annotation != Parameter.empty:
|
||||
if typing.get_origin(parameter.annotation) == list:
|
||||
params.append(self._get_services(typing.get_args(parameter.annotation)[0]))
|
||||
params.append(self._get_services(typing.get_args(parameter.annotation)[0], origin_service_type))
|
||||
|
||||
elif parameter.annotation == Source:
|
||||
params.append(origin_service_type.__name__)
|
||||
|
||||
elif issubclass(parameter.annotation, ServiceProviderABC):
|
||||
params.append(self)
|
||||
|
||||
elif issubclass(parameter.annotation, ApplicationEnvironmentABC):
|
||||
params.append(self._configuration.environment)
|
||||
elif issubclass(parameter.annotation, Environment):
|
||||
params.append(Environment)
|
||||
|
||||
elif issubclass(parameter.annotation, DatabaseContextABC):
|
||||
params.append(self._database_context)
|
||||
|
||||
elif issubclass(parameter.annotation, ConfigurationModelABC):
|
||||
params.append(self._configuration.get_configuration(parameter.annotation))
|
||||
params.append(Configuration.get(parameter.annotation))
|
||||
|
||||
elif issubclass(parameter.annotation, ConfigurationABC):
|
||||
params.append(self._configuration)
|
||||
elif issubclass(parameter.annotation, Configuration):
|
||||
params.append(Configuration)
|
||||
|
||||
else:
|
||||
params.append(self._get_service(parameter))
|
||||
params.append(self._get_service(parameter, origin_service_type))
|
||||
|
||||
return params
|
||||
|
||||
def build_service(self, service_type: type, *args, **kwargs) -> object:
|
||||
def _build_service(self, service_type: type, *args, origin_service_type: type = None, **kwargs) -> object:
|
||||
if origin_service_type is None:
|
||||
origin_service_type = service_type
|
||||
|
||||
for descriptor in self._service_descriptors:
|
||||
if descriptor.service_type == service_type or issubclass(descriptor.service_type, service_type):
|
||||
if descriptor.implementation is not None:
|
||||
@@ -119,7 +123,7 @@ class ServiceProvider(ServiceProviderABC):
|
||||
break
|
||||
|
||||
sig = signature(service_type.__init__)
|
||||
params = self.build_by_signature(sig)
|
||||
params = self._build_by_signature(sig, origin_service_type)
|
||||
|
||||
return service_type(*params, *args, **kwargs)
|
||||
|
||||
@@ -135,7 +139,7 @@ class ServiceProvider(ServiceProviderABC):
|
||||
else:
|
||||
descriptors.append(copy.deepcopy(descriptor))
|
||||
|
||||
sb = ScopeBuilder(ServiceProvider(descriptors, self._configuration, self._database_context))
|
||||
sb = ScopeBuilder(ServiceProvider(descriptors, self._database_context))
|
||||
return sb.build()
|
||||
|
||||
def get_service(self, service_type: T, *args, **kwargs) -> Optional[R]:
|
||||
@@ -147,7 +151,7 @@ class ServiceProvider(ServiceProviderABC):
|
||||
if result.implementation is not None:
|
||||
return result.implementation
|
||||
|
||||
implementation = self.build_service(service_type, *args, **kwargs)
|
||||
implementation = self._build_service(service_type, *args, **kwargs)
|
||||
if (
|
||||
result.lifetime == ServiceLifetimeEnum.singleton
|
||||
or result.lifetime == ServiceLifetimeEnum.scoped
|
||||
@@ -1,14 +1,14 @@
|
||||
import functools
|
||||
from abc import abstractmethod, ABC
|
||||
from inspect import Signature, signature
|
||||
from typing import Optional, Type
|
||||
from typing import Optional
|
||||
|
||||
from cpl_core.dependency_injection.scope_abc import ScopeABC
|
||||
from cpl_core.type import T, R
|
||||
from cpl.dependency.scope_abc import ScopeABC
|
||||
from cpl.core.typing import T, R
|
||||
|
||||
|
||||
class ServiceProviderABC(ABC):
|
||||
r"""ABC for the class :class:`cpl_core.dependency_injection.service_provider.ServiceProvider`"""
|
||||
r"""ABC for the class :class:`cpl.dependency.service_provider.ServiceProvider`"""
|
||||
|
||||
_provider: Optional["ServiceProviderABC"] = None
|
||||
|
||||
@@ -21,11 +21,11 @@ class ServiceProviderABC(ABC):
|
||||
cls._provider = provider
|
||||
|
||||
@abstractmethod
|
||||
def build_by_signature(self, sig: Signature) -> list[R]:
|
||||
def _build_by_signature(self, sig: Signature, origin_service_type: type) -> list[R]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def build_service(self, service_type: type, *args, **kwargs) -> object:
|
||||
def _build_service(self, service_type: type, *args, **kwargs) -> object:
|
||||
r"""Creates instance of given type
|
||||
|
||||
Parameter
|
||||
@@ -37,7 +37,6 @@ class ServiceProviderABC(ABC):
|
||||
-------
|
||||
Object of the given type
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def set_scope(self, scope: ScopeABC):
|
||||
@@ -45,10 +44,9 @@ class ServiceProviderABC(ABC):
|
||||
|
||||
Parameter
|
||||
---------
|
||||
Object of type :class:`cpl_core.dependency_injection.scope_abc.ScopeABC`
|
||||
Object of type :class:`cpl.dependency.scope_abc.ScopeABC`
|
||||
Service scope
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def create_scope(self) -> ScopeABC:
|
||||
@@ -56,9 +54,8 @@ class ServiceProviderABC(ABC):
|
||||
|
||||
Returns
|
||||
-------
|
||||
Object of type :class:`cpl_core.dependency_injection.scope_abc.ScopeABC`
|
||||
Object of type :class:`cpl.dependency.scope_abc.ScopeABC`
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_service(self, instance_type: T, *args, **kwargs) -> Optional[R]:
|
||||
@@ -66,14 +63,13 @@ class ServiceProviderABC(ABC):
|
||||
|
||||
Parameter
|
||||
---------
|
||||
instance_type: :class:`cpl_core.type.T`
|
||||
instance_type: :class:`cpl.core.type.T`
|
||||
The type of the searched instance
|
||||
|
||||
Returns
|
||||
-------
|
||||
Object of type Optional[:class:`cpl_core.type.T`]
|
||||
Object of type Optional[:class:`cpl.core.type.T`]
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_services(self, service_type: T, *args, **kwargs) -> list[Optional[R]]:
|
||||
@@ -81,14 +77,13 @@ class ServiceProviderABC(ABC):
|
||||
|
||||
Parameter
|
||||
---------
|
||||
service_type: :class:`cpl_core.type.T`
|
||||
service_type: :class:`cpl.core.type.T`
|
||||
The type of the searched instance
|
||||
|
||||
Returns
|
||||
-------
|
||||
Object of type list[Optional[:class:`cpl_core.type.T`]
|
||||
Object of type list[Optional[:class:`cpl.core.type.T`]
|
||||
"""
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def inject(cls, f=None):
|
||||
@@ -110,7 +105,7 @@ class ServiceProviderABC(ABC):
|
||||
if cls._provider is None:
|
||||
raise Exception(f"{cls.__name__} not build!")
|
||||
|
||||
injection = [x for x in cls._provider.build_by_signature(signature(f)) if x is not None]
|
||||
injection = [x for x in cls._provider._build_by_signature(signature(f)) if x is not None]
|
||||
return f(*args, *injection, **kwargs)
|
||||
|
||||
return inner
|
||||
30
src/cpl-dependency/pyproject.toml
Normal file
30
src/cpl-dependency/pyproject.toml
Normal file
@@ -0,0 +1,30 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=70.1.0", "wheel>=0.43.0"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "cpl-dependency"
|
||||
version = "2024.7.0"
|
||||
description = "CPL dependency"
|
||||
readme ="CPL dependency package"
|
||||
requires-python = ">=3.12"
|
||||
license = { text = "MIT" }
|
||||
authors = [
|
||||
{ name = "Sven Heidemann", email = "sven.heidemann@sh-edraft.de" }
|
||||
]
|
||||
keywords = ["cpl", "dependency", "backend", "shared", "library"]
|
||||
|
||||
dynamic = ["dependencies", "optional-dependencies"]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://www.sh-edraft.de"
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["."]
|
||||
include = ["cpl*"]
|
||||
|
||||
[tool.setuptools.dynamic]
|
||||
dependencies = { file = ["requirements.txt"] }
|
||||
optional-dependencies.dev = { file = ["requirements.dev.txt"] }
|
||||
|
||||
|
||||
1
src/cpl-dependency/requirements.dev.txt
Normal file
1
src/cpl-dependency/requirements.dev.txt
Normal file
@@ -0,0 +1 @@
|
||||
black==25.1.0
|
||||
1
src/cpl-dependency/requirements.txt
Normal file
1
src/cpl-dependency/requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
cpl-core
|
||||
26
src/cpl-mail/cpl/mail/__init__.py
Normal file
26
src/cpl-mail/cpl/mail/__init__.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from .abc.email_client_abc import EMailClientABC
|
||||
from .email_client import EMailClient
|
||||
from .email_client_settings import EMailClientSettings
|
||||
from .email_client_settings_name_enum import EMailClientSettingsNameEnum
|
||||
from .email_model import EMail
|
||||
from .mail_logger import MailLogger
|
||||
|
||||
|
||||
def add_mail(self):
|
||||
from cpl.core.console import Console
|
||||
from cpl.core.log import LoggerABC
|
||||
|
||||
try:
|
||||
self.add_singleton(EMailClientABC, EMailClient)
|
||||
self.add_transient(LoggerABC, MailLogger)
|
||||
except ImportError as e:
|
||||
Console.error("cpl-translation is not installed", str(e))
|
||||
|
||||
|
||||
def init():
|
||||
from cpl.dependency import ServiceCollection
|
||||
|
||||
ServiceCollection.add_mail = add_mail
|
||||
|
||||
|
||||
init()
|
||||
@@ -1,10 +1,10 @@
|
||||
from abc import abstractmethod, ABC
|
||||
|
||||
from cpl_core.mailing.email import EMail
|
||||
from cpl.mail.email_model import EMail
|
||||
|
||||
|
||||
class EMailClientABC(ABC):
|
||||
"""ABC of :class:`cpl_core.mailing.email_client_service.EMailClient`"""
|
||||
"""ABC of :class:`cpl.mail.email_client_service.EMailClient`"""
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
@@ -13,14 +13,12 @@ class EMailClientABC(ABC):
|
||||
@abstractmethod
|
||||
def connect(self):
|
||||
r"""Connects to server"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def send_mail(self, email: EMail):
|
||||
r"""Sends email
|
||||
|
||||
Parameter:
|
||||
email: :class:`cpl_core.mailing.email.EMail`
|
||||
email: :class:`cpl.mail.email.EMail`
|
||||
Object of the E-Mail to send
|
||||
"""
|
||||
pass
|
||||
88
src/cpl-mail/cpl/mail/email_client.py
Normal file
88
src/cpl-mail/cpl/mail/email_client.py
Normal file
@@ -0,0 +1,88 @@
|
||||
import ssl
|
||||
from smtplib import SMTP
|
||||
from typing import Optional
|
||||
|
||||
from cpl.core.utils.credential_manager import CredentialManager
|
||||
from cpl.mail.abc.email_client_abc import EMailClientABC
|
||||
from cpl.mail.email_client_settings import EMailClientSettings
|
||||
from cpl.mail.email_model import EMail
|
||||
from cpl.mail.mail_logger import MailLogger
|
||||
|
||||
|
||||
class EMailClient(EMailClientABC):
|
||||
r"""Service to send emails
|
||||
|
||||
Parameter:
|
||||
environment: :class:`cpl.core.environment.application_environment_abc.ApplicationEnvironmentABC`
|
||||
Environment of the application
|
||||
logger: :class:`cpl.core.log.logger_abc.LoggerABC`
|
||||
The logger to use
|
||||
mail_settings: :class:`cpl.mail.email_client_settings.EMailClientSettings`
|
||||
Settings for mailing
|
||||
"""
|
||||
|
||||
def __init__(self, logger: MailLogger, mail_settings: EMailClientSettings):
|
||||
EMailClientABC.__init__(self)
|
||||
|
||||
assert mail_settings is not None, "mail_settings must not be None"
|
||||
|
||||
self._mail_settings = mail_settings
|
||||
self._logger = logger
|
||||
|
||||
self._server: Optional[SMTP] = None
|
||||
|
||||
self.create()
|
||||
|
||||
def create(self):
|
||||
r"""Creates connection"""
|
||||
self._logger.trace(f"Started {__name__}.create")
|
||||
self.connect()
|
||||
self._logger.trace(f"Stopped {__name__}.create")
|
||||
|
||||
def connect(self):
|
||||
self._logger.trace(f"Started {__name__}.connect")
|
||||
try:
|
||||
self._logger.debug(f"Try to connect to {self._mail_settings.host}:{self._mail_settings.port}")
|
||||
self._server = SMTP(self._mail_settings.host, self._mail_settings.port)
|
||||
self._logger.info(f"Connected to {self._mail_settings.host}:{self._mail_settings.port}")
|
||||
|
||||
self._logger.debug("Try to start tls")
|
||||
self._server.starttls(context=ssl.create_default_context())
|
||||
self._logger.info("Started tls")
|
||||
except Exception as e:
|
||||
self._logger.error("Cannot connect to mail server", e)
|
||||
|
||||
self._logger.trace(f"Stopped {__name__}.connect")
|
||||
|
||||
def login(self):
|
||||
r"""Login to server"""
|
||||
self._logger.trace(f"Started {__name__}.login")
|
||||
try:
|
||||
self._logger.debug(
|
||||
__name__,
|
||||
f"Try to login {self._mail_settings.user_name}@{self._mail_settings.host}:{self._mail_settings.port}",
|
||||
)
|
||||
self._server.login(
|
||||
self._mail_settings.user_name, CredentialManager.decrypt(self._mail_settings.credentials)
|
||||
)
|
||||
self._logger.info(
|
||||
__name__,
|
||||
f"Logged on as {self._mail_settings.user_name} to {self._mail_settings.host}:{self._mail_settings.port}",
|
||||
)
|
||||
except Exception as e:
|
||||
self._logger.error("Cannot login to mail server", e)
|
||||
|
||||
self._logger.trace(f"Stopped {__name__}.login")
|
||||
|
||||
def send_mail(self, email: EMail):
|
||||
self._logger.trace(f"Started {__name__}.send_mail")
|
||||
try:
|
||||
self.login()
|
||||
self._logger.debug(f"Try to send email to {email.receiver_list}")
|
||||
self._server.sendmail(
|
||||
self._mail_settings.user_name, email.receiver_list, email.get_content(self._mail_settings.user_name)
|
||||
)
|
||||
self._logger.info(f"Sent email to {email.receiver_list}")
|
||||
except Exception as e:
|
||||
self._logger.error(f"Cannot send mail to {email.receiver_list}", e)
|
||||
self._logger.trace(f"Stopped {__name__}.send_mail")
|
||||
@@ -1,4 +1,4 @@
|
||||
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
|
||||
from cpl.core.configuration.configuration_model_abc import ConfigurationModelABC
|
||||
|
||||
|
||||
class EMailClientSettings(ConfigurationModelABC):
|
||||
8
src/cpl-mail/cpl/mail/mail_logger.py
Normal file
8
src/cpl-mail/cpl/mail/mail_logger.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from cpl.core.log.logger import Logger
|
||||
from cpl.core.typing import Source
|
||||
|
||||
|
||||
class MailLogger(Logger):
|
||||
|
||||
def __init__(self, source: Source):
|
||||
Logger.__init__(self, source, "mail")
|
||||
29
src/cpl-mail/pyproject.toml
Normal file
29
src/cpl-mail/pyproject.toml
Normal file
@@ -0,0 +1,29 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=70.1.0", "wheel>=0.43.0"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "cpl-mail"
|
||||
version = "2024.7.0"
|
||||
description = "CPL mail"
|
||||
readme = "CPL mail package"
|
||||
requires-python = ">=3.12"
|
||||
license = { text = "MIT" }
|
||||
authors = [
|
||||
{ name = "Sven Heidemann", email = "sven.heidemann@sh-edraft.de" }
|
||||
]
|
||||
keywords = ["cpl", "mail", "backend", "shared", "library"]
|
||||
|
||||
dynamic = ["dependencies", "optional-dependencies"]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://www.sh-edraft.de"
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["."]
|
||||
include = ["cpl*"]
|
||||
|
||||
[tool.setuptools.dynamic]
|
||||
dependencies = { file = ["requirements.txt"] }
|
||||
optional-dependencies.dev = { file = ["requirements.dev.txt"] }
|
||||
|
||||
1
src/cpl-mail/requirements.dev.txt
Normal file
1
src/cpl-mail/requirements.dev.txt
Normal file
@@ -0,0 +1 @@
|
||||
black==25.1.0
|
||||
1
src/cpl-mail/requirements.txt
Normal file
1
src/cpl-mail/requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
cpl-core
|
||||
1
src/cpl-query/cpl/query/__init__.py
Normal file
1
src/cpl-query/cpl/query/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
5
src/cpl-query/cpl/query/base/__init__.py
Normal file
5
src/cpl-query/cpl/query/base/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from .default_lambda import default_lambda
|
||||
from .ordered_queryable import OrderedQueryable
|
||||
from .sequence import Sequence
|
||||
from .ordered_queryable_abc import OrderedQueryableABC
|
||||
from .queryable_abc import QueryableABC
|
||||
@@ -1,14 +1,13 @@
|
||||
from collections.abc import Callable
|
||||
|
||||
from cpl_query.base.queryable_abc import QueryableABC
|
||||
from cpl_query.base.ordered_queryable_abc import OrderedQueryableABC
|
||||
from cpl_query.exceptions import ArgumentNoneException, ExceptionArgument
|
||||
from cpl.query.base.ordered_queryable_abc import OrderedQueryableABC
|
||||
from cpl.query.exceptions import ArgumentNoneException, ExceptionArgument
|
||||
|
||||
|
||||
class OrderedQueryable(OrderedQueryableABC):
|
||||
r"""Implementation of :class: `cpl_query.base.ordered_queryable_abc.OrderedQueryableABC`"""
|
||||
r"""Implementation of :class: `cpl.query.base.ordered_queryable_abc.OrderedQueryableABC`"""
|
||||
|
||||
def __init__(self, _t: type, _values: QueryableABC = None, _func: Callable = None):
|
||||
def __init__(self, _t: type, _values: OrderedQueryableABC = None, _func: Callable = None):
|
||||
OrderedQueryableABC.__init__(self, _t, _values, _func)
|
||||
|
||||
def then_by(self, _func: Callable) -> OrderedQueryableABC:
|
||||
@@ -4,7 +4,7 @@ from abc import abstractmethod
|
||||
from collections.abc import Callable
|
||||
from typing import Iterable
|
||||
|
||||
from cpl_query.base.queryable_abc import QueryableABC
|
||||
from cpl.query.base.queryable_abc import QueryableABC
|
||||
|
||||
|
||||
class OrderedQueryableABC(QueryableABC):
|
||||
@@ -23,9 +23,8 @@ class OrderedQueryableABC(QueryableABC):
|
||||
func: :class:`Callable`
|
||||
|
||||
Returns:
|
||||
list of :class:`cpl_query.base.ordered_queryable_abc.OrderedQueryableABC`
|
||||
list of :class:`cpl.query.base.ordered_queryable_abc.OrderedQueryableABC`
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def then_by_descending(self, func: Callable) -> OrderedQueryableABC:
|
||||
@@ -35,6 +34,5 @@ class OrderedQueryableABC(QueryableABC):
|
||||
func: :class:`Callable`
|
||||
|
||||
Returns:
|
||||
list of :class:`cpl_query.base.ordered_queryable_abc.OrderedQueryableABC`
|
||||
list of :class:`cpl.query.base.ordered_queryable_abc.OrderedQueryableABC`
|
||||
"""
|
||||
pass
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user