39 Commits

Author SHA1 Message Date
60a349f918 [WIP] Fixed imports 2023-04-16 22:01:15 +02:00
a155bbc468 [WIP] Fixed imports 2023-04-16 21:52:03 +02:00
2e8886e255 [WIP] Fixed imports 2023-04-16 21:48:03 +02:00
aabbfeaa92 [WIP] Added scheduler 2023-04-16 21:46:17 +02:00
39ca803d36 Added take until 2023-04-16 16:52:06 +02:00
b7d518022a Improved tests 2023-04-16 16:15:55 +02:00
2ec4af8bb3 Improved tests 2023-04-16 15:58:46 +02:00
30b163a440 Added take 2023-04-16 15:48:33 +02:00
82f23f237c [WIP] operator implement 2023-04-16 03:06:55 +02:00
79a6c1db8f Improved imports 2023-04-16 00:57:35 +02:00
51803bf5d1 Added interval 2023-04-16 00:49:38 +02:00
a463ac5274 Improved subjects 2023-04-15 22:00:59 +02:00
3ee617ee38 Build project 2023-04-15 21:49:34 +02:00
e5fd7df519 Fixed subjects 2023-04-15 21:41:49 +02:00
7001b23b31 Added improved models 2023-04-15 20:06:43 +02:00
e94ff1b26f Added improved models 2023-04-15 19:46:21 +02:00
d7d41b878c Added behavior subject 2023-04-15 19:21:11 +02:00
9d05d76cfa Added closed test 2023-04-15 19:14:36 +02:00
e6ee543a1d Added subject 2023-04-15 18:59:24 +02:00
efc9cf9c83 Added observables 2023-04-15 16:17:31 +02:00
8dee4d8f70 Fixed config type 2023-04-15 11:47:31 +02:00
315b8e631a Merge pull request '2023.4' (#168) from 2023.4 into master
Reviewed-on: #168
2023-04-12 13:57:50 +02:00
cbb1860f25 Fixed internal settings 2023-04-12 13:47:38 +02:00
9839bcaa14 Removed unused imports 2023-04-12 13:34:43 +02:00
da54337221 Fixed ProjectSettings 2023-04-12 13:31:59 +02:00
a6a1e764d1 Updated deps 2023-04-12 13:30:13 +02:00
eb6aa08c10 Updated deps 2023-04-12 12:57:58 +02:00
14a190a67f Merge pull request '#166' (#167) from #166 into 2023.4
Reviewed-on: #167
Closes #166
2023-04-11 14:49:21 +02:00
cf5ae89884 Fixed start test 2023-04-11 14:26:17 +02:00
558dfb8ced Deactivated test output 2023-04-11 12:51:49 +02:00
2ec8fc22b3 Improved generic type hints 2023-04-11 12:50:05 +02:00
c94700495b Merge pull request '#157' (#165) from #157 into 2023.4
Reviewed-on: #165
Closes #157
2023-04-11 11:48:48 +02:00
69a3bc5e31 Fixed cwd handling 2023-04-10 15:10:39 +02:00
d189f49418 Improved service_provider_tests 2023-04-10 14:53:59 +02:00
60fb416b67 Added service_provider_tests 2023-04-10 14:47:25 +02:00
792429d19d Added console argument tests 2023-04-10 13:35:11 +02:00
106975015e Improved config tests 2023-04-09 12:51:57 +02:00
1117735f2e Added configuration tests & improved json parsing by converting into target types 2023-04-09 12:31:31 +02:00
0378f8944a Added AppEnv tests 2023-04-09 12:05:05 +02:00
88 changed files with 2052 additions and 76 deletions

View File

@@ -87,7 +87,7 @@
Install the CPL package Install the CPL package
```sh ```sh
pip install cpl --extra-index-url https://pip.sh-edraft.de pip install cpl-core --extra-index-url https://pip.sh-edraft.de
``` ```
Install the CPL CLI Install the CPL CLI

View File

@@ -6,6 +6,7 @@
"cpl-core": "src/cpl_core/cpl-core.json", "cpl-core": "src/cpl_core/cpl-core.json",
"cpl-discord": "src/cpl_discord/cpl-discord.json", "cpl-discord": "src/cpl_discord/cpl-discord.json",
"cpl-query": "src/cpl_query/cpl-query.json", "cpl-query": "src/cpl_query/cpl-query.json",
"cpl-reactive-extensions": "src/cpl_reactive_extensions/cpl-reactive-extensions.json",
"cpl-translation": "src/cpl_translation/cpl-translation.json", "cpl-translation": "src/cpl_translation/cpl-translation.json",
"set-version": "tools/set_version/set-version.json", "set-version": "tools/set_version/set-version.json",
"set-pip-urls": "tools/set_pip_urls/set-pip-urls.json", "set-pip-urls": "tools/set_pip_urls/set-pip-urls.json",
@@ -14,7 +15,8 @@
"unittests_core": "unittests/unittests_core/unittests_core.json", "unittests_core": "unittests/unittests_core/unittests_core.json",
"unittests_query": "unittests/unittests_query/unittests_query.json", "unittests_query": "unittests/unittests_query/unittests_query.json",
"unittests_shared": "unittests/unittests_shared/unittests_shared.json", "unittests_shared": "unittests/unittests_shared/unittests_shared.json",
"unittests_translation": "unittests/unittests_translation/unittests_translation.json" "unittests_translation": "unittests/unittests_translation/unittests_translation.json",
"unittests_reactive_extenstions": "unittests/unittests_reactive_extenstions/unittests_reactive_extenstions.json"
}, },
"Scripts": { "Scripts": {
"hello-world": "echo 'Hello World'", "hello-world": "echo 'Hello World'",
@@ -40,12 +42,13 @@
"test": "cpl run unittests", "test": "cpl run unittests",
"pre-build-all": "cpl sv $ARGS; cpl spu $ARGS;", "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", "build-all": "cpl build-cli; cpl build-core; cpl build-discord; cpl build-query; cpl build-reactive-extensions; cpl build-translation; cpl build-set-pip-urls; cpl build-set-version",
"ba": "cpl build-all $ARGS", "ba": "cpl build-all $ARGS",
"build-cli": "echo 'Build cpl-cli'; cd ./src/cpl_cli; cpl build; cd ../../;", "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-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-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-query": "echo 'Build cpl-query'; cd ./src/cpl_query; cpl build; cd ../../;",
"build-reactive-extensions": "echo 'Build cpl-reactive-x'; cd ./src/cpl_reactive_extensions; cpl build; cd ../../;",
"build-translation": "echo 'Build cpl-translation'; cd ./src/cpl_translation; 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-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 ../../;", "build-set-version": "echo 'Build set-version'; cd ./tools/set_version; cpl build; cd ../../;",
@@ -57,6 +60,7 @@
"publish-core": "echo 'Publish cpl-core'; cd ./src/cpl_core; 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-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-query": "echo 'Publish cpl-query'; cd ./src/cpl_query; cpl publish; cd ../../;",
"publish-reactive-extensions": "echo 'Publish cpl-reactive-x'; cd ./src/cpl_reactive_extensions; cpl publish; cd ../../;",
"publish-translation": "echo 'Publish cpl-translation'; cd ./src/cpl_translation; 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;", "upload-prod-cli": "echo 'PROD Upload cpl-cli'; cpl upl-prod-cli;",
@@ -71,6 +75,9 @@
"upload-prod-query": "echo 'PROD Upload cpl-query'; cpl upl-prod-query;", "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/*", "upl-prod-query": "twine upload -r pip.sh-edraft.de dist/cpl-query/publish/setup/*",
"upload-prod-reactive-extensions": "echo 'PROD Upload cpl-reactive-extensions'; cpl upl-prod-query;",
"upl-prod-reactive-extensions": "twine upload -r pip.sh-edraft.de dist/cpl-reactive-extensions/publish/setup/*",
"upload-prod-translation": "echo 'PROD Upload cpl-translation'; cpl upl-prod-translation;", "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/*", "upl-prod-translation": "twine upload -r pip.sh-edraft.de dist/cpl-translation/publish/setup/*",
@@ -86,6 +93,9 @@
"upload-exp-query": "echo 'EXP Upload cpl-query'; cpl upl-exp-query;", "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/*", "upl-exp-query": "twine upload -r pip-exp.sh-edraft.de dist/cpl-query/publish/setup/*",
"upload-exp-reactive-extensions": "echo 'EXP Upload cpl-reactive-extensions'; cpl upl-exp-query;",
"upl-exp-reactive-extensions": "twine upload -r pip-exp.sh-edraft.de dist/cpl-reactive-extensions/publish/setup/*",
"upload-exp-translation": "echo 'EXP Upload cpl-translation'; cpl upl-exp-translation;", "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/*", "upl-exp-translation": "twine upload -r pip-exp.sh-edraft.de dist/cpl-translation/publish/setup/*",
@@ -101,6 +111,9 @@
"upload-dev-query": "echo 'DEV Upload cpl-query'; cpl upl-dev-query;", "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/*", "upl-dev-query": "twine upload -r pip-dev.sh-edraft.de dist/cpl-query/publish/setup/*",
"upload-dev-reactive-extensions": "echo 'DEV Upload cpl-reactive-extensions'; cpl upl-dev-query;",
"upl-dev-reactive-extensions": "twine upload -r pip-dev.sh-edraft.de dist/cpl-reactive-extensions/publish/setup/*",
"upload-dev-translation": "echo 'DEV Upload cpl-translation'; cpl upl-dev-translation;", "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/*", "upl-dev-translation": "twine upload -r pip-dev.sh-edraft.de dist/cpl-translation/publish/setup/*",

View File

@@ -1,9 +1,6 @@
import traceback
from typing import Optional from typing import Optional
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
from cpl_core.console.console import Console
from cpl_cli.cli_settings_name_enum import CLISettingsNameEnum
class CLISettings(ConfigurationModelABC): class CLISettings(ConfigurationModelABC):

View File

@@ -18,10 +18,10 @@ class BuildSettings(ConfigurationModelABC):
main: str = None, main: str = None,
entry_point: str = None, entry_point: str = None,
include_package_data: bool = None, include_package_data: bool = None,
included: list[str] = None, included: list = None,
excluded: list[str] = None, excluded: list = None,
package_data: dict[str, list[str]] = None, package_data: dict = None,
project_references: list[str] = None, project_references: list = None,
): ):
ConfigurationModelABC.__init__(self) ConfigurationModelABC.__init__(self)

View File

@@ -49,7 +49,7 @@ class ProjectSettings(ConfigurationModelABC):
self._python_executable: Optional[str] = python_executable self._python_executable: Optional[str] = python_executable
self._classifiers: Optional[list[str]] = [] if classifiers is None else classifiers self._classifiers: Optional[list[str]] = [] if classifiers is None else classifiers
if python_path is not None: if python_path is not None and sys.platform in python_path:
path = f"{python_path[sys.platform]}" path = f"{python_path[sys.platform]}"
if path == "" or path is None: if path == "" or path is None:

View File

@@ -15,7 +15,7 @@ __title__ = "cpl_core"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de"
__version__ = "2023.4.0" __version__ = "2023.4.dev170"
from collections import namedtuple from collections import namedtuple
@@ -23,4 +23,4 @@ from collections import namedtuple
# imports: # imports:
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="2023", minor="4", micro="0") version_info = VersionInfo(major="2023", minor="4", micro="dev170")

View File

@@ -15,7 +15,7 @@ __title__ = "cpl_core.application"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de"
__version__ = "2023.4.0" __version__ = "2023.4.dev170"
from collections import namedtuple from collections import namedtuple
@@ -29,4 +29,4 @@ from .startup_abc import StartupABC
from .startup_extension_abc import StartupExtensionABC from .startup_extension_abc import StartupExtensionABC
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="2023", minor="4", micro="0") version_info = VersionInfo(major="2023", minor="4", micro="dev170")

View File

@@ -15,7 +15,7 @@ __title__ = "cpl_core.configuration"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de"
__version__ = "2023.4.0" __version__ = "2023.4.dev170"
from collections import namedtuple from collections import namedtuple
@@ -35,4 +35,4 @@ from .validator_abc import ValidatorABC
from .variable_argument import VariableArgument from .variable_argument import VariableArgument
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="2023", minor="4", micro="0") version_info = VersionInfo(major="2023", minor="4", micro="dev170")

View File

@@ -294,7 +294,7 @@ class Configuration(ConfigurationABC):
self.add_configuration(sub, configuration) self.add_configuration(sub, configuration)
def add_configuration(self, key_type: T, value: any): def add_configuration(self, key_type: Type[T], value: any):
self._config[key_type] = value self._config[key_type] = value
def create_console_argument( def create_console_argument(
@@ -314,7 +314,7 @@ class Configuration(ConfigurationABC):
for arg in self._argument_types: for arg in self._argument_types:
call(arg) call(arg)
def get_configuration(self, search_type: Type[T]) -> Optional[T]: def get_configuration(self, search_type: T) -> Optional[T]:
if type(search_type) is str: if type(search_type) is str:
if search_type == ConfigurationVariableNameEnum.environment.value: if search_type == ConfigurationVariableNameEnum.environment.value:
return self._application_environment.environment_name return self._application_environment.environment_name

View File

@@ -77,7 +77,7 @@ class ConfigurationABC(ABC):
pass pass
@abstractmethod @abstractmethod
def add_configuration(self, key_type: T, value: any): def add_configuration(self, key_type: Type[T], value: any):
r"""Add configuration object r"""Add configuration object
Parameter: Parameter:

View File

@@ -15,7 +15,7 @@ __title__ = "cpl_core.console"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de"
__version__ = "2023.4.0" __version__ = "2023.4.dev170"
from collections import namedtuple from collections import namedtuple
@@ -28,4 +28,4 @@ from .foreground_color_enum import ForegroundColorEnum
from .spinner_thread import SpinnerThread from .spinner_thread import SpinnerThread
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="2023", minor="4", micro="0") version_info = VersionInfo(major="2023", minor="4", micro="dev170")

View File

@@ -4,7 +4,7 @@
"Version": { "Version": {
"Major": "2023", "Major": "2023",
"Minor": "4", "Minor": "4",
"Micro": "0" "Micro": "dev170"
}, },
"Author": "Sven Heidemann", "Author": "Sven Heidemann",
"AuthorEmail": "sven.heidemann@sh-edraft.de", "AuthorEmail": "sven.heidemann@sh-edraft.de",
@@ -16,17 +16,17 @@
"LicenseName": "MIT", "LicenseName": "MIT",
"LicenseDescription": "MIT, see LICENSE for more details.", "LicenseDescription": "MIT, see LICENSE for more details.",
"Dependencies": [ "Dependencies": [
"art==5.9", "art>=5.9",
"colorama==0.4.6", "colorama>=0.4.6",
"mysql-connector==2.2.9", "mysql-connector>=2.2.9",
"psutil==5.9.4", "psutil>=5.9.4",
"packaging==23.0", "packaging>=23.0",
"pynput==1.7.6", "pynput>=1.7.6",
"setuptools==67.6.1", "setuptools>=67.6.1",
"tabulate==0.9.0", "tabulate>=0.9.0",
"termcolor==2.2.0", "termcolor>=2.2.0",
"watchdog==3.0.0", "watchdog>=3.0.0",
"wheel==0.40.0" "wheel>=0.40.0"
], ],
"DevDependencies": [ "DevDependencies": [
"Sphinx==5.0.2", "Sphinx==5.0.2",
@@ -36,7 +36,7 @@
"sphinx-markdown-builder==0.5.5", "sphinx-markdown-builder==0.5.5",
"pygount==1.5.1" "pygount==1.5.1"
], ],
"PythonVersion": ">=3.11", "PythonVersion": ">=3.10",
"PythonPath": {}, "PythonPath": {},
"Classifiers": [] "Classifiers": []
}, },

View File

@@ -15,7 +15,7 @@ __title__ = "cpl_core.database"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de"
__version__ = "2023.4.0" __version__ = "2023.4.dev170"
from collections import namedtuple from collections import namedtuple
@@ -26,4 +26,4 @@ from .database_settings import DatabaseSettings
from .table_abc import TableABC from .table_abc import TableABC
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="2023", minor="4", micro="0") version_info = VersionInfo(major="2023", minor="4", micro="dev170")

View File

@@ -15,7 +15,7 @@ __title__ = "cpl_core.database.connection"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de"
__version__ = "2023.4.0" __version__ = "2023.4.dev170"
from collections import namedtuple from collections import namedtuple
@@ -25,4 +25,4 @@ from .database_connection import DatabaseConnection
from .database_connection_abc import DatabaseConnectionABC from .database_connection_abc import DatabaseConnectionABC
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="2023", minor="4", micro="0") version_info = VersionInfo(major="2023", minor="4", micro="dev170")

View File

@@ -15,7 +15,7 @@ __title__ = "cpl_core.database.context"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de"
__version__ = "2023.4.0" __version__ = "2023.4.dev170"
from collections import namedtuple from collections import namedtuple
@@ -25,4 +25,4 @@ from .database_context import DatabaseContext
from .database_context_abc import DatabaseContextABC from .database_context_abc import DatabaseContextABC
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="2023", minor="4", micro="0") version_info = VersionInfo(major="2023", minor="4", micro="dev170")

View File

@@ -12,11 +12,11 @@ class DatabaseSettings(ConfigurationModelABC):
port: int = None, port: int = None,
user: str = None, user: str = None,
password: str = None, password: str = None,
databse: str = None, database: str = None,
charset: str = None, charset: str = None,
use_unicode: bool = None, use_unicode: bool = None,
buffered: bool = None, buffered: bool = None,
auth_plugin: bool = None, auth_plugin: str = None,
): ):
ConfigurationModelABC.__init__(self) ConfigurationModelABC.__init__(self)
@@ -24,7 +24,7 @@ class DatabaseSettings(ConfigurationModelABC):
self._port: Optional[int] = port self._port: Optional[int] = port
self._user: Optional[str] = user self._user: Optional[str] = user
self._password: Optional[str] = password self._password: Optional[str] = password
self._databse: Optional[str] = databse self._databse: Optional[str] = database
self._charset: Optional[str] = charset self._charset: Optional[str] = charset
self._use_unicode: Optional[bool] = use_unicode self._use_unicode: Optional[bool] = use_unicode
self._buffered: Optional[bool] = buffered self._buffered: Optional[bool] = buffered

View File

@@ -15,7 +15,7 @@ __title__ = "cpl_core.dependency_injection"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de"
__version__ = "2023.4.0" __version__ = "2023.4.dev170"
from collections import namedtuple from collections import namedtuple
@@ -31,4 +31,4 @@ from .service_provider import ServiceProvider
from .service_provider_abc import ServiceProviderABC from .service_provider_abc import ServiceProviderABC
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="2023", minor="4", micro="0") version_info = VersionInfo(major="2023", minor="4", micro="dev170")

View File

@@ -61,15 +61,15 @@ class ServiceCollection(ServiceCollectionABC):
self.add_transient(PipeABC, pipe) self.add_transient(PipeABC, pipe)
return self return self
def add_singleton(self, service_type: T, service: T = None): def add_singleton(self, service_type: Type[T], service: T = None):
self._add_descriptor_by_lifetime(service_type, ServiceLifetimeEnum.singleton, service) self._add_descriptor_by_lifetime(service_type, ServiceLifetimeEnum.singleton, service)
return self return self
def add_scoped(self, service_type: T, service: Callable = None): def add_scoped(self, service_type: Type[T], service: Callable = None):
self._add_descriptor_by_lifetime(service_type, ServiceLifetimeEnum.scoped, service) self._add_descriptor_by_lifetime(service_type, ServiceLifetimeEnum.scoped, service)
return self return self
def add_transient(self, service_type: T, service: T = None): def add_transient(self, service_type: Type[T], service: T = None):
self._add_descriptor_by_lifetime(service_type, ServiceLifetimeEnum.transient, service) self._add_descriptor_by_lifetime(service_type, ServiceLifetimeEnum.transient, service)
return self return self

View File

@@ -46,7 +46,7 @@ class ServiceCollectionABC(ABC):
pass pass
@abstractmethod @abstractmethod
def add_transient(self, service_type: T, service: T = None) -> "ServiceCollectionABC": def add_transient(self, service_type: Type[T], service: T = None) -> "ServiceCollectionABC":
r"""Adds a service with transient lifetime r"""Adds a service with transient lifetime
Parameter: Parameter:
@@ -61,7 +61,7 @@ class ServiceCollectionABC(ABC):
pass pass
@abstractmethod @abstractmethod
def add_scoped(self, service_type: T, service: T = None) -> "ServiceCollectionABC": def add_scoped(self, service_type: Type[T], service: T = None) -> "ServiceCollectionABC":
r"""Adds a service with scoped lifetime r"""Adds a service with scoped lifetime
Parameter: Parameter:
@@ -76,7 +76,7 @@ class ServiceCollectionABC(ABC):
pass pass
@abstractmethod @abstractmethod
def add_singleton(self, service_type: T, service: T = None) -> "ServiceCollectionABC": def add_singleton(self, service_type: Type[T], service: T = None) -> "ServiceCollectionABC":
r"""Adds a service with singleton lifetime r"""Adds a service with singleton lifetime
Parameter: Parameter:

View File

@@ -138,7 +138,7 @@ class ServiceProvider(ServiceProviderABC):
sb = ScopeBuilder(ServiceProvider(descriptors, self._configuration, self._database_context)) sb = ScopeBuilder(ServiceProvider(descriptors, self._configuration, self._database_context))
return sb.build() return sb.build()
def get_service(self, service_type: T, *args, **kwargs) -> Optional[T]: def get_service(self, service_type: typing.Type[T], *args, **kwargs) -> Optional[T]:
result = self._find_service(service_type) result = self._find_service(service_type)
if result is None: if result is None:
@@ -157,7 +157,7 @@ class ServiceProvider(ServiceProviderABC):
return implementation return implementation
def get_services(self, service_type: T, *args, **kwargs) -> list[Optional[T]]: def get_services(self, service_type: typing.Type[T], *args, **kwargs) -> list[Optional[T]]:
implementations = [] implementations = []
if typing.get_origin(service_type) != list: if typing.get_origin(service_type) != list:

View File

@@ -1,7 +1,7 @@
import functools import functools
from abc import abstractmethod, ABC from abc import abstractmethod, ABC
from inspect import Signature, signature from inspect import Signature, signature
from typing import Type, Optional from typing import Optional, Type
from cpl_core.dependency_injection.scope_abc import ScopeABC from cpl_core.dependency_injection.scope_abc import ScopeABC
from cpl_core.type import T from cpl_core.type import T
@@ -61,7 +61,7 @@ class ServiceProviderABC(ABC):
pass pass
@abstractmethod @abstractmethod
def get_service(self, instance_type: T, *args, **kwargs) -> Optional[T]: def get_service(self, instance_type: Type[T], *args, **kwargs) -> Optional[T]:
r"""Returns instance of given type r"""Returns instance of given type
Parameter Parameter
@@ -76,7 +76,7 @@ class ServiceProviderABC(ABC):
pass pass
@abstractmethod @abstractmethod
def get_services(self, service_type: T, *args, **kwargs) -> list[Optional[T]]: def get_services(self, service_type: Type[T], *args, **kwargs) -> list[Optional[T]]:
r"""Returns instance of given type r"""Returns instance of given type
Parameter Parameter

View File

@@ -15,7 +15,7 @@ __title__ = "cpl_core.environment"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de"
__version__ = "2023.4.0" __version__ = "2023.4.dev170"
from collections import namedtuple from collections import namedtuple
@@ -26,4 +26,4 @@ from .environment_name_enum import EnvironmentNameEnum
from .application_environment import ApplicationEnvironment from .application_environment import ApplicationEnvironment
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="2023", minor="4", micro="0") version_info = VersionInfo(major="2023", minor="4", micro="dev170")

View File

@@ -15,7 +15,7 @@ __title__ = "cpl_core.logging"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de"
__version__ = "2023.4.0" __version__ = "2023.4.dev170"
from collections import namedtuple from collections import namedtuple
@@ -28,4 +28,4 @@ from .logging_settings import LoggingSettings
from .logging_settings_name_enum import LoggingSettingsNameEnum from .logging_settings_name_enum import LoggingSettingsNameEnum
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="2023", minor="4", micro="0") version_info = VersionInfo(major="2023", minor="4", micro="dev170")

View File

@@ -15,7 +15,7 @@ __title__ = "cpl_core.mailing"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de"
__version__ = "2023.4.0" __version__ = "2023.4.dev170"
from collections import namedtuple from collections import namedtuple
@@ -28,4 +28,4 @@ from .email_client_settings import EMailClientSettings
from .email_client_settings_name_enum import EMailClientSettingsNameEnum from .email_client_settings_name_enum import EMailClientSettingsNameEnum
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="2023", minor="4", micro="0") version_info = VersionInfo(major="2023", minor="4", micro="dev170")

View File

@@ -15,14 +15,15 @@ __title__ = "cpl_core.pipes"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de"
__version__ = "2023.4.0" __version__ = "2023.4.dev170"
from collections import namedtuple from collections import namedtuple
# imports: # imports:
from .bool_pipe import BoolPipe from .bool_pipe import BoolPipe
from .ip_address_pipe import IPAddressPipe from .ip_address_pipe import IPAddressPipe
from .pipe_abc import PipeABC from .pipe_abc import PipeABC
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="2023", minor="4", micro="0") version_info = VersionInfo(major="2023", minor="4", micro="dev170")

View File

@@ -15,7 +15,7 @@ __title__ = "cpl_core.time"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de"
__version__ = "2023.4.0" __version__ = "2023.4.dev170"
from collections import namedtuple from collections import namedtuple
@@ -25,4 +25,4 @@ from .time_format_settings import TimeFormatSettings
from .time_format_settings_names_enum import TimeFormatSettingsNamesEnum from .time_format_settings_names_enum import TimeFormatSettingsNamesEnum
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="2023", minor="4", micro="0") version_info = VersionInfo(major="2023", minor="4", micro="dev170")

View File

@@ -1,3 +1,4 @@
from typing import TypeVar from typing import TypeVar, Union
T = TypeVar("T") T = TypeVar("T")
Number = Union[int, float]

View File

@@ -15,7 +15,7 @@ __title__ = "cpl_core.utils"
__author__ = "Sven Heidemann" __author__ = "Sven Heidemann"
__license__ = "MIT" __license__ = "MIT"
__copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de" __copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de"
__version__ = "2023.4.0" __version__ = "2023.4.dev170"
from collections import namedtuple from collections import namedtuple
@@ -26,4 +26,4 @@ from .string import String
from .pip import Pip from .pip import Pip
VersionInfo = namedtuple("VersionInfo", "major minor micro") VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="2023", minor="4", micro="0") version_info = VersionInfo(major="2023", minor="4", micro="dev170")

View File

@@ -17,12 +17,14 @@ class JSONProcessor:
name = String.first_to_upper(String.convert_to_camel_case(parameter.name)) name = String.first_to_upper(String.convert_to_camel_case(parameter.name))
name_first_lower = String.first_to_lower(name) name_first_lower = String.first_to_lower(name)
if name in values or name_first_lower in values: if name in values or name_first_lower in values or name.upper() in values:
value = "" value = ""
if name in values: if name in values:
value = values[name] value = values[name]
else: elif name_first_lower in values:
value = values[name_first_lower] value = values[name_first_lower]
else:
value = values[name.upper()]
if isinstance(value, dict) and not issubclass(parameter.annotation, dict): if isinstance(value, dict) and not issubclass(parameter.annotation, dict):
value = JSONProcessor.process(parameter.annotation, value) value = JSONProcessor.process(parameter.annotation, value)
@@ -30,6 +32,9 @@ class JSONProcessor:
if issubclass(parameter.annotation, enum.Enum): if issubclass(parameter.annotation, enum.Enum):
value = parameter.annotation[value] value = parameter.annotation[value]
if type(value) != parameter.annotation:
value = parameter.annotation(value)
args.append(value) args.append(value)
elif parameter.default != Parameter.empty: elif parameter.default != Parameter.empty:

View File

@@ -19,6 +19,7 @@ __version__ = "2023.4.0"
from collections import namedtuple from collections import namedtuple
# imports: # imports:
from .default_lambda import default_lambda from .default_lambda import default_lambda
from .ordered_queryable import OrderedQueryable from .ordered_queryable import OrderedQueryable

View File

@@ -17,7 +17,7 @@
"LicenseDescription": "MIT, see LICENSE for more details.", "LicenseDescription": "MIT, see LICENSE for more details.",
"Dependencies": [], "Dependencies": [],
"DevDependencies": [], "DevDependencies": [],
"PythonVersion": ">=3.11", "PythonVersion": ">=3.10",
"PythonPath": {}, "PythonPath": {},
"Classifiers": [] "Classifiers": []
}, },

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
"""
cpl-reactive-extensions CPL Simple ReactiveX implementation
~~~~~~~~~~~~~~~~~~~
CPL Simple ReactiveX implementation, see RxJS and RxPy for detailed implementation.
:copyright: (c) 2023 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = "cpl_reactive_extensions"
__author__ = "Sven Heidemann"
__license__ = "MIT"
__copyright__ = "Copyright (c) 2023 sh-edraft.de"
__version__ = "2023.4.dev170"
from collections import namedtuple
# imports
VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="2023", minor="4", micro="dev170")

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
"""
cpl-reactive-extensions CPL Simple ReactiveX implementation
~~~~~~~~~~~~~~~~~~~
CPL Simple ReactiveX implementation, see RxJS and RxPy for detailed implementation.
:copyright: (c) 2023 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = "cpl_reactive_extensions.abc"
__author__ = "Sven Heidemann"
__license__ = "MIT"
__copyright__ = "Copyright (c) 2023 sh-edraft.de"
__version__ = "2023.4.dev170"
from collections import namedtuple
# imports
VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="2023", minor="4", micro="dev170")

View File

@@ -0,0 +1,20 @@
from abc import abstractmethod
from cpl_core.type import T
class Observer:
def __init__(self):
pass
@abstractmethod
def next(self, value: T):
pass
@abstractmethod
def error(self, ex: Exception):
pass
@abstractmethod
def complete(self):
pass

View File

@@ -0,0 +1,8 @@
from typing import Any
from cpl_reactive_extensions.internal.subscriber import Subscriber
class Operator:
def call(self, subscriber: Subscriber, source: Any):
pass

View File

@@ -0,0 +1,10 @@
from abc import ABC, abstractmethod
from cpl_core.type import T, Number
from cpl_reactive_extensions.internal.subscription import Subscription
class SchedulerAction(ABC):
@abstractmethod
def schedule(self, state: T = None, delay: Number = None) -> Subscription:
pass

View File

@@ -0,0 +1,12 @@
from abc import ABC, abstractmethod
from typing import Callable, Optional
from cpl_core.type import Number, T
from cpl_reactive_extensions.internal.subscription import Subscription
from cpl_reactive_extensions.abc.scheduler_action import SchedulerAction
class SchedulerLike(ABC):
@abstractmethod
def schedule(self, work: Callable[[SchedulerAction, Optional[T]], None], delay: Number, state: T) -> Subscription:
pass

View File

@@ -0,0 +1,16 @@
from abc import ABC, abstractmethod
from typing import Union, Callable
from cpl_reactive_extensions.abc.observer import Observer
from cpl_reactive_extensions.abc.unsubscribable import Unsubscribable
class Subscribable(ABC):
def __init__(self):
ABC.__init__(self)
@abstractmethod
def subscribe(
self, observer_or_next: Union[Callable, Observer], on_error: Callable = None, on_complete: Callable = None
) -> Unsubscribable:
pass

View File

@@ -0,0 +1,10 @@
from abc import ABC, abstractmethod
class Unsubscribable(ABC):
def __init__(self):
ABC.__init__(self)
@abstractmethod
def unsubscribe(self):
pass

View File

@@ -0,0 +1,44 @@
{
"ProjectSettings": {
"Name": "cpl-reactive-extensions",
"Version": {
"Major": "2023",
"Minor": "4",
"Micro": "dev170"
},
"Author": "Sven Heidemann",
"AuthorEmail": "sven.heidemann@sh-edraft.de",
"Description": "CPL Simple ReactiveX implementation",
"LongDescription": "CPL Simple ReactiveX implementation, see RxJS and RxPy for detailed implementation.",
"URL": "https://www.sh-edraft.de",
"CopyrightDate": "2023",
"CopyrightName": "sh-edraft.de",
"LicenseName": "MIT",
"LicenseDescription": "MIT, see LICENSE for more details.",
"Dependencies": [
"cpl-core>=2023.4.dev170"
],
"DevDependencies": [
"cpl-cli>=2023.4.0"
],
"PythonVersion": ">=3.10.4",
"PythonPath": {},
"Classifiers": []
},
"BuildSettings": {
"ProjectType": "library",
"SourcePath": "",
"OutputPath": "../../dist",
"Main": "",
"EntryPoint": "",
"IncludePackageData": false,
"Included": [],
"Excluded": [
"*/__pycache__",
"*/logs",
"*/tests"
],
"PackageData": {},
"ProjectReferences": []
}
}

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
"""
cpl-reactive-extensions CPL Simple ReactiveX implementation
~~~~~~~~~~~~~~~~~~~
CPL Simple ReactiveX implementation, see RxJS and RxPy for detailed implementation.
:copyright: (c) 2023 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = "cpl_reactive_extensions.helper"
__author__ = "Sven Heidemann"
__license__ = "MIT"
__copyright__ = "Copyright (c) 2023 sh-edraft.de"
__version__ = "2023.4.dev170"
from collections import namedtuple
# imports:
VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="2023", minor="4", micro="dev170")

View File

@@ -0,0 +1,11 @@
def bind(instance, func, *args, as_name=None):
"""
Bind the function *func* to *instance*, with either provided name *as_name*
or the existing name of *func*. The provided *func* should accept the
instance as the first argument, i.e. "self".
"""
if as_name is None:
as_name = func.__name__
bound_method = func.__get__(instance, instance.__class__)
setattr(instance, as_name, bound_method)
return bound_method

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
"""
cpl-reactive-extensions CPL Simple ReactiveX implementation
~~~~~~~~~~~~~~~~~~~
CPL Simple ReactiveX implementation, see RxJS and RxPy for detailed implementation.
:copyright: (c) 2023 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = "cpl_reactive_extensions.internal"
__author__ = "Sven Heidemann"
__license__ = "MIT"
__copyright__ = "Copyright (c) 2023 sh-edraft.de"
__version__ = "2023.4.dev170"
from collections import namedtuple
# imports:
VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="2023", minor="4", micro="dev170")

View File

@@ -0,0 +1,10 @@
from cpl_core.type import T, Number
from cpl_reactive_extensions.internal.subscription import Subscription
class Action(Subscription):
def __init__(self, scheduler, work):
Subscription.__init__(self)
def schedule(self, state: T = None, delay: Number = 0) -> Subscription:
return self

View File

@@ -0,0 +1,103 @@
from typing import Optional
from cpl_core.type import T, Number
from cpl_reactive_extensions.internal.action import Action
from cpl_reactive_extensions.internal.subscription import Subscription
from cpl_reactive_extensions.timer import Timer
class AsyncAction(Action):
def __init__(self, scheduler, work):
from cpl_reactive_extensions.scheduler.async_scheduler import AsyncScheduler
Action.__init__(self, scheduler, work)
self._scheduler: AsyncScheduler = scheduler
self._work = work
self.timer = None
self.state: Optional[T] = None
self.delay: Number = 0
self._pending = False
def schedule(self, state: T = None, delay: Number = 0) -> Subscription:
if self.closed:
return self
self.state = state
timer = self.timer
scheduler = self._scheduler
if timer is not None:
self.timer = self.recycle_async_timer(scheduler, timer, delay)
self._pending = True
self.delay = delay
self.timer = self.timer if self.timer is not None else self.request_async_timer(scheduler, delay)
return self
def request_async_timer(self, scheduler, delay: Number = 0):
from cpl_reactive_extensions.scheduler.async_scheduler import AsyncScheduler
scheduler: AsyncScheduler = scheduler
return Timer(delay, lambda: scheduler.flush(self))
def recycle_async_timer(self, scheduler, timer=None, delay: Number = None):
from cpl_reactive_extensions.scheduler.async_scheduler import AsyncScheduler
scheduler: AsyncScheduler = scheduler
if delay is None and self.delay == delay and not self._pending:
return timer
if timer is not None:
timer.clear()
return None
def execute(self, state: T, delay: Number):
if self.closed:
return Exception("Executing cancelled action")
self._pending = False
error = self._execute(state, delay)
if error is not None:
return error
elif not self._pending and self.timer is not None:
self._timer = self.recycle_async_timer(self._scheduler, self.timer, None)
def _execute(self, state: T, delay: Number):
errored = False
ex = None
try:
self._work(state)
except Exception as e:
errored = True
ex = e
if errored:
self.unsubscribe()
return ex
def unsubscribe(self):
if self.closed:
return
timer = self.timer
scheduler = self._scheduler
actions = self._scheduler.actions
self._work = None
self.state = None
self._scheduler = None
self._pending = False
self.delay = None
if self in actions:
actions.remove(self)
if self.timer is not None:
self.timer = self.recycle_async_timer(scheduler, timer, None)
Action.unsubscribe(self)

View File

@@ -0,0 +1,55 @@
from typing import Callable
from cpl_core.type import T
from cpl_reactive_extensions.abc.observer import Observer
from cpl_reactive_extensions.internal.subscriber import Subscriber
class OperatorSubscriber(Subscriber, Observer):
def __init__(
self,
destination: Subscriber,
on_next: Callable = None,
on_error: Callable = None,
on_complete: Callable = None,
on_finalize: Callable = None,
should_unsubscribe: Callable = None,
):
Subscriber.__init__(self, destination)
self._on_finalize = on_finalize
self._should_unsubscribe = should_unsubscribe
def on_next_wrapper(value: T):
try:
on_next(value)
except Exception as e:
destination.error(e)
self._on_next = on_next_wrapper if on_next is not None else self._on_next
def on_error_wrapper(value: T):
try:
on_error(value)
except Exception as e:
destination.error(e)
finally:
self.unsubscribe()
self._on_error = on_error_wrapper if on_error is not None else self._on_error
def on_complete_wrapper(value: T):
try:
on_complete(value)
except Exception as e:
destination.error(e)
finally:
self.unsubscribe()
self._on_complete = on_complete_wrapper if on_complete is not None else self._on_complete
def unsubscribe(self):
if self._should_unsubscribe is not None and not self._should_unsubscribe():
return
Subscriber.unsubscribe(self)
if not self.closed and self._on_finalize is not None:
self._on_finalize()

View File

@@ -0,0 +1,65 @@
from typing import Callable
from cpl_core.type import T
from cpl_reactive_extensions.abc.observer import Observer
from cpl_reactive_extensions.internal.subscription import Subscription
from cpl_reactive_extensions.type import ObserverOrCallable
class Subscriber(Subscription, Observer):
def __init__(
self, on_next_or_observer: ObserverOrCallable, on_error: Callable = None, on_complete: Callable = None
):
self.is_stopped = False
Subscription.__init__(self)
if isinstance(on_next_or_observer, Observer):
self._on_next = on_next_or_observer.next
self._on_error = on_next_or_observer.error
self._on_complete = on_next_or_observer.complete
else:
self._on_next = on_next_or_observer
self._on_error = on_error
self._on_complete = on_complete
def _next(self, value: T):
self._on_next(value)
def next(self, value: T):
if self.is_stopped:
raise Exception("Observer is closed")
self._next(value)
def _error(self, ex: Exception):
try:
self._on_error(ex)
except TypeError:
pass
finally:
self.unsubscribe()
def error(self, ex: Exception):
self._error(ex)
def _complete(self):
try:
self._on_complete()
except TypeError:
pass
finally:
self.unsubscribe()
def complete(self):
if self.is_stopped:
return
self.is_stopped = True
self._complete()
def unsubscribe(self):
if self._closed:
return
self.is_stopped = True
Subscription.unsubscribe(self)
self._on_next = None

View File

@@ -0,0 +1,96 @@
from __future__ import annotations
import traceback
from typing import Union, Callable, Optional
from cpl_core.console import Console
from cpl_reactive_extensions.abc.unsubscribable import Unsubscribable
class Subscription(Unsubscribable):
@staticmethod
def empty():
empty = Subscription()
empty.closed = True
return empty
def __init__(self, initial_teardown: Optional[Callable] = None):
Unsubscribable.__init__(self)
self._initial_teardown = initial_teardown
self._closed = False
self._parentage: list[Subscription] = []
self._finalizers: list[Subscription] = []
@property
def closed(self) -> bool:
return self._closed
@closed.setter
def closed(self, value: bool):
self._closed = value
def _add_parent(self, parent: Subscription):
self._parentage.append(parent)
def _remove_parent(self, parent: Subscription):
if self == parent:
self._parentage.clear()
return
self._parentage.remove(parent)
def _has_parent(self, parent: Subscription) -> bool:
return parent in self._parentage
def _exec_finalizer(self, finalizer: Union[Callable, Unsubscribable]):
if isinstance(finalizer, Callable):
finalizer()
else:
finalizer.unsubscribe()
def unsubscribe(self):
if not self._closed:
self._closed = True
for parent in self._parentage:
parent.remove(self)
if self._initial_teardown is not None:
try:
self._initial_teardown()
except Exception as e:
Console.error(e, traceback.format_exc())
finalizers = self._finalizers
self._finalizers = None
for finalizer in finalizers:
try:
self._exec_finalizer(finalizer)
except Exception as e:
Console.error(e, traceback.format_exc())
def add(self, tear_down: Union[Subscription, Unsubscribable]):
if tear_down is None or tear_down == self:
return
if self.closed:
self._exec_finalizer(tear_down)
return
if isinstance(tear_down, Subscription):
if tear_down.closed or tear_down._has_parent(self):
return
tear_down._add_parent(self)
self._finalizers.append(tear_down)
def remove(self, tear_down: Union[Subscription, Unsubscribable]):
if self._finalizers is not None:
self._finalizers.remove(tear_down)
if isinstance(tear_down, Subscription):
tear_down._remove_parent(self)

View File

@@ -0,0 +1,10 @@
from typing import Callable
from cpl_core.type import Number
from cpl_reactive_extensions.timer import Timer
class TimerProvider:
@staticmethod
def set_timer(handler: Callable, timeout: Number = None, *args):
return Timer(timeout, handler, *args)

View File

@@ -0,0 +1,20 @@
from typing import Callable
from cpl_reactive_extensions.observable import Observable
from cpl_reactive_extensions.internal.subscriber import Subscriber
def operate(init: Callable[[Observable, Subscriber], None]):
def observable(source: Observable):
def create(self: Subscriber, lifted_source: Observable):
try:
return init(lifted_source, self)
except Exception as e:
self.error(e)
if "lift" not in dir(source):
raise TypeError("Unable to lift unknown Observable type")
return source.lift(create)
return observable

View File

@@ -0,0 +1,47 @@
import sched
import threading
import time
from typing import Callable
from cpl_reactive_extensions.internal.subscriber import Subscriber
from cpl_reactive_extensions.observable import Observable
class Interval(Observable):
def __init__(self, interval: float, callback: Callable = None, not_in_background=False):
self._interval = interval
callback = callback if callback is not None else self._default_callback
def schedule(x: Subscriber):
scheduler = sched.scheduler(time.time, time.sleep)
scheduler.enter(
self._interval,
1,
self._run,
(scheduler, x, callback),
)
scheduler.run()
def thread(x: Subscriber):
t = threading.Thread(target=schedule, args=(x,))
t.start()
Observable.__init__(self, schedule if not_in_background else thread)
self._i = 0
def _run(self, scheduler, x: Subscriber, callback: Callable):
if x.closed:
x.complete()
return
scheduler.enter(
self._interval,
1,
self._run,
(scheduler, x, callback),
)
callback(x)
def _default_callback(self, x: Subscriber):
x.next(self._i)
self._i += 1

View File

@@ -0,0 +1,114 @@
from __future__ import annotations
from typing import Callable, Any, Optional
from cpl_core.type import T
from cpl_reactive_extensions.abc.observer import Observer
from cpl_reactive_extensions.abc.subscribable import Subscribable
from cpl_reactive_extensions.internal.subscriber import Subscriber
from cpl_reactive_extensions.internal.subscription import Subscription
from cpl_reactive_extensions.type import ObserverOrCallable
class Observable(Subscribable):
def __init__(self, subscribe: Callable = None):
Subscribable.__init__(self)
if subscribe is not None:
self._subscribe = subscribe
self._source: Optional[Observable] = None
self._operator: Optional[Callable] = None
@staticmethod
def from_observable(obs: Observable):
def inner(subscriber: Subscriber):
if "subscribe" not in dir(obs):
raise TypeError("Unable to lift unknown Observable type")
return obs.subscribe(subscriber)
return Observable(inner)
@staticmethod
def from_list(values: list):
i = 0
def callback(x: Subscriber):
nonlocal i
if i == len(values):
i = 0
x.complete()
else:
x.next(values[i])
i += 1
if not x.closed:
callback(x)
observable = Observable(callback)
return observable
def lift(self, operator: Callable) -> Observable:
observable = Observable()
observable._source = self
observable._operator = operator
return observable
@staticmethod
def _is_observer(value: Any) -> bool:
return isinstance(value, Observer)
@staticmethod
def _is_subscription(value: Any) -> bool:
return isinstance(value, Subscription)
@staticmethod
def _is_subscriber(value: Any) -> bool:
return isinstance(value, Subscriber) or Observable._is_observer(value) and Observable._is_subscription(value)
def _subscribe(self, subscriber: Subscriber) -> Subscription:
return self._source.subscribe(subscriber)
def _try_subscribe(self, subscriber: Subscriber) -> Subscription:
try:
return self._subscribe(subscriber)
except Exception as e:
subscriber.error(e)
def subscribe(
self, observer_or_next: ObserverOrCallable, on_error: Callable = None, on_complete: Callable = None
) -> Subscription:
subscriber = (
observer_or_next
if Observable._is_subscriber(observer_or_next)
else Subscriber(observer_or_next, on_error, on_complete)
)
subscriber.add(
self._operator(subscriber, self._source)
if self._operator is not None
else self._subscribe(subscriber)
if self._source is not None
else self._try_subscribe(subscriber)
)
return subscriber
def pipe(self, *args) -> Observable:
return self._pipe_from_array(args)(self)
def _pipe_from_array(self, args):
if len(args) == 0:
return lambda x: x
if len(args) == 1:
return args[0]
def piped(input: T):
return Observable._reduce(lambda prev, fn: fn(prev), input)
return piped
@staticmethod
def _reduce(func: Callable, input: T):
return func(input)

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
"""
cpl-reactive-extensions CPL Simple ReactiveX implementation
~~~~~~~~~~~~~~~~~~~
CPL Simple ReactiveX implementation, see RxJS and RxPy for detailed implementation.
:copyright: (c) 2023 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = "cpl_reactive_extensions.operators"
__author__ = "Sven Heidemann"
__license__ = "MIT"
__copyright__ = "Copyright (c) 2023 sh-edraft.de"
__version__ = "2023.4.dev170"
from collections import namedtuple
# imports:
VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="2023", minor="4", micro="dev170")

View File

@@ -0,0 +1,62 @@
from typing import Optional
from cpl_core.type import T, Number
from cpl_reactive_extensions.abc.scheduler_action import SchedulerAction
from cpl_reactive_extensions.internal.operator_subscriber import OperatorSubscriber
from cpl_reactive_extensions.internal.subscriber import Subscriber
from cpl_reactive_extensions.internal.subscription import Subscription
from cpl_reactive_extensions.internal.utils import operate
from cpl_reactive_extensions.observable import Observable
from cpl_reactive_extensions.scheduler.async_scheduler import async_scheduler
def debounce_time(time: Number, scheduler=async_scheduler):
def init(source: Observable, subscriber: Subscriber):
active_task: Optional[Subscription] = None
last_value: Optional[T] = None
last_time: Optional[Number] = None
def emit():
nonlocal active_task, last_value
if active_task is None:
return
active_task.unsubscribe()
active_task = None
value = last_value
last_value = None
subscriber.next(value)
def emit_when_idle(action: SchedulerAction):
nonlocal active_task, last_time
target_time = last_time + time
now = scheduler.now
if now < target_time:
active_task = action.schedule(None, target_time - now)
subscriber.add(active_task)
return
emit()
def on_next(value: T):
nonlocal active_task, last_value
last_value = value
if active_task is None:
active_task = scheduler.schedule(emit_when_idle, time)
subscriber.add(active_task)
def on_complete():
emit()
subscriber.complete()
def on_finalize():
nonlocal active_task, last_value
last_value = None
active_task = None
sub = source.subscribe(OperatorSubscriber(subscriber, on_next, None, on_complete, on_finalize))
return operate(init)

View File

@@ -0,0 +1,29 @@
from cpl_core.type import T
from cpl_reactive_extensions.internal.subscriber import Subscriber
from cpl_reactive_extensions.observable import Observable
from cpl_reactive_extensions.internal.operator_subscriber import OperatorSubscriber
from cpl_reactive_extensions.internal.utils import operate
def take(count: int):
if count <= 0:
return Observable()
def init(source: Observable, subscriber: Subscriber):
seen = 0
def on_next(value: T):
nonlocal seen
if seen + 1 <= count:
seen += 1
subscriber.next(value)
if count <= seen:
subscriber.complete()
else:
sub.unsubscribe()
sub = source.subscribe(OperatorSubscriber(subscriber, on_next))
return operate(init)

View File

@@ -0,0 +1,14 @@
from cpl_reactive_extensions.observable import Observable
from cpl_reactive_extensions.internal.operator_subscriber import OperatorSubscriber
from cpl_reactive_extensions.internal.subscriber import Subscriber
from cpl_reactive_extensions.internal.utils import operate
def take_until(notifier: Observable):
def init(source: Observable, subscriber: Subscriber):
Observable.from_observable(notifier).subscribe(OperatorSubscriber(subscriber, lambda: subscriber.complete()))
if not subscriber.closed:
source.subscribe(subscriber)
return operate(init)

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
"""
cpl-reactive-extensions CPL Simple ReactiveX implementation
~~~~~~~~~~~~~~~~~~~
CPL Simple ReactiveX implementation, see RxJS and RxPy for detailed implementation.
:copyright: (c) 2023 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = "cpl_reactive_extensions.scheduler"
__author__ = "Sven Heidemann"
__license__ = "MIT"
__copyright__ = "Copyright (c) 2023 sh-edraft.de"
__version__ = "2023.4.dev170"
from collections import namedtuple
# imports:
VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="2023", minor="4", micro="dev170")

View File

@@ -0,0 +1,36 @@
from typing import Type
from cpl_reactive_extensions.internal.action import Action
from cpl_reactive_extensions.internal.async_action import AsyncAction
from cpl_reactive_extensions.scheduler.scheduler import Scheduler
class AsyncScheduler(Scheduler):
def __init__(self, scheduler_action_ctor: Type[Action], now=None):
Scheduler.__init__(self, scheduler_action_ctor, now)
self.actions: list[AsyncAction] = []
self._active = False
def flush(self, action: AsyncAction):
if self._active:
self.actions.append(action)
return
error = None
self._active = True
for action in self.actions:
error = action.execute(action.state, action.delay)
if error:
break
self._active = False
if error is not None:
for action in self.actions:
action.unsubscribe()
raise error
async_scheduler = AsyncScheduler(AsyncAction)

View File

@@ -0,0 +1,29 @@
from datetime import datetime
from typing import Callable, Optional, Type
from cpl_core.type import T, Number
from cpl_reactive_extensions.abc.scheduler_action import SchedulerAction
from cpl_reactive_extensions.abc.scheduler_like import SchedulerLike
from cpl_reactive_extensions.internal.action import Action
from cpl_reactive_extensions.internal.subscription import Subscription
class Scheduler(SchedulerLike):
@staticmethod
@property
def _get_now(self=None) -> Number:
return int(datetime.now().strftime("%s"))
now = _get_now
def __init__(self, scheduler_action_ctor: Type[Action], now=None):
self.now = self._get_now if now is None else now
self._scheduler_action_ctor = scheduler_action_ctor
def schedule(
self, work: Callable[[SchedulerAction, Optional[T]], None], delay: Number, state: T = None
) -> Subscription:
action = self._scheduler_action_ctor(self, work)
x = action.schedule(state, delay)
return x

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
"""
cpl-reactive-extensions CPL Simple ReactiveX implementation
~~~~~~~~~~~~~~~~~~~
CPL Simple ReactiveX implementation, see RxJS and RxPy for detailed implementation.
:copyright: (c) 2023 sh-edraft.de
:license: MIT, see LICENSE for more details.
"""
__title__ = "cpl_reactive_extensions.subject"
__author__ = "Sven Heidemann"
__license__ = "MIT"
__copyright__ = "Copyright (c) 2023 sh-edraft.de"
__version__ = "2023.4.dev170"
from collections import namedtuple
# imports:
VersionInfo = namedtuple("VersionInfo", "major minor micro")
version_info = VersionInfo(major="2023", minor="4", micro="dev170")

View File

@@ -0,0 +1,24 @@
from typing import Type
from cpl_core.type import T
from cpl_reactive_extensions.subject.subject import Subject
class BehaviorSubject(Subject):
def __init__(self, _t: Type[T], value: T):
Subject.__init__(self, _t)
if not isinstance(value, _t):
raise TypeError(f"Expected {_t.__name__} not {type(value).__name__}")
self._t = _t
self._value = value
@property
def value(self) -> T:
return self._value
def next(self, value: T):
super().next(value)
self._value = value

View File

@@ -0,0 +1,101 @@
from types import NoneType
from typing import Any, Optional, Type
from cpl_core.type import T
from cpl_reactive_extensions.abc.observer import Observer
from cpl_reactive_extensions.observable import Observable
from cpl_reactive_extensions.internal.subscriber import Subscriber
from cpl_reactive_extensions.internal.subscription import Subscription
class Subject(Observable, Observer):
def __init__(self, _t: Type[T]):
Observable.__init__(self)
self.is_closed = False
self._t = _t if _t is not None else NoneType
self._current_observers: Optional[list[Observer]] = None
self.closed = False
self.observers: list[Observer] = []
self.is_stopped = False
self.has_error = False
self.raised_error: Any = None
@property
def observed(self) -> bool:
return len(self.observers) > 0
def _raise_if_closed(self):
if not self.closed:
return
raise Exception("Subject is unsubscribed!")
def next(self, value: T):
self._raise_if_closed()
if not isinstance(value, self._t):
raise TypeError(f"Expected {self._t.__name__} not {type(value).__name__}")
if self.is_stopped:
return
if self._current_observers is None:
self._current_observers = self.observers
for observer in self._current_observers:
observer.next(value)
def error(self, error: Exception):
self._raise_if_closed()
if self.is_stopped:
return
self.is_stopped = True
self.has_error = self.is_stopped
for observer in self.observers:
observer.error(error)
def complete(self):
self._raise_if_closed()
if self.is_stopped:
return
self.is_stopped = True
for observer in self.observers:
observer.complete()
def unsubscribe(self):
self.is_stopped = True
self.is_closed = True
self._current_observers = None
self.observers = []
def _try_subscribe(self, subscriber: Subscriber):
self._raise_if_closed()
return super()._try_subscribe(subscriber)
def _subscribe(self, subscriber: Subscriber) -> Subscription:
self._raise_if_closed()
self._check_finalized_statuses(subscriber)
return self._inner_subscribe(subscriber)
def _check_finalized_statuses(self, subscriber: Subscriber):
if self.has_error:
subscriber.error(self.raised_error)
elif self.is_stopped:
subscriber.complete()
def _inner_subscribe(self, subscriber: Subscriber) -> Optional[Subscription]:
if self.has_error or self.is_stopped:
return Subscription.empty()
self._current_observers = None
self.observers.append(subscriber)
def _initial():
self._current_observers = None
self.observers.remove(subscriber)
return Subscription(_initial)

View File

@@ -0,0 +1,24 @@
import threading
import time
from typing import Callable
from cpl_core.type import Number
class Timer:
def __init__(self, interval: Number, action: Callable, *args):
self._interval = interval / 1000
self._action = action
self._args = args
self.stop_event = threading.Event()
thread = threading.Thread(target=self.__set_interval)
thread.start()
def __set_interval(self):
next_time = time.time() + self._interval
while not self.stop_event.wait(next_time - time.time()):
next_time += self._interval
self._action(*self._args)
def clear(self):
self.stop_event.set()

View File

@@ -0,0 +1,5 @@
from typing import Callable, Union
from cpl_reactive_extensions.abc.observer import Observer
ObserverOrCallable = Union[Callable, Observer]

View File

@@ -8,6 +8,7 @@ from cpl_core.dependency_injection import ServiceProviderABC
from cpl_core.logging import LoggerABC from cpl_core.logging import LoggerABC
from cpl_core.mailing import EMailClientABC, EMail from cpl_core.mailing import EMailClientABC, EMail
from cpl_core.pipes import IPAddressPipe from cpl_core.pipes import IPAddressPipe
from general.test_settings import TestSettings
from test_service import TestService from test_service import TestService
@@ -57,4 +58,13 @@ class Application(ApplicationABC):
Console.write_line("scope", scope) Console.write_line("scope", scope)
with self._services.create_scope() as s: with self._services.create_scope() as s:
Console.write_line("with scope", s) Console.write_line("with scope", s)
test_settings = self._configuration.get_configuration(TestSettings)
Console.write_line(test_settings.value)
Console.write_line("reload config")
self._configuration.add_json_file(f"appsettings.json")
self._configuration.add_json_file(f"appsettings.{self._environment.environment_name}.json")
self._configuration.add_json_file(f"appsettings.{self._environment.host_name}.json", optional=True)
test_settings1 = self._configuration.get_configuration(TestSettings)
Console.write_line(test_settings1.value)
# self.test_send_mail() # self.test_send_mail()

View File

@@ -29,5 +29,9 @@
"UseUnicode": "true", "UseUnicode": "true",
"Buffered": "true", "Buffered": "true",
"AuthPlugin": "mysql_native_password" "AuthPlugin": "mysql_native_password"
},
"TestSettings": {
"Value": 20
} }
} }

View File

@@ -0,0 +1,6 @@
from cpl_core.configuration import ConfigurationModelABC
class TestSettings(ConfigurationModelABC):
def __init__(self, value: int = None):
self.value = value

View File

@@ -16,8 +16,8 @@
"LicenseName": "MIT", "LicenseName": "MIT",
"LicenseDescription": "MIT, see LICENSE for more details.", "LicenseDescription": "MIT, see LICENSE for more details.",
"Dependencies": [ "Dependencies": [
"cpl-core==2022.12.0", "cpl-core>=2022.12.0",
"GitPython==3.1.29" "GitPython>=3.1.29"
], ],
"DevDependencies": [], "DevDependencies": [],
"PythonVersion": ">=3.10.4", "PythonVersion": ">=3.10.4",

View File

@@ -6,6 +6,7 @@ from cpl_core.dependency_injection import ServiceProviderABC
from unittests_cli.cli_test_suite import CLITestSuite from unittests_cli.cli_test_suite import CLITestSuite
from unittests_core.core_test_suite import CoreTestSuite from unittests_core.core_test_suite import CoreTestSuite
from unittests_query.query_test_suite import QueryTestSuite from unittests_query.query_test_suite import QueryTestSuite
from unittests_reactive_extenstions.reactive_test_suite import ReactiveTestSuite
from unittests_translation.translation_test_suite import TranslationTestSuite from unittests_translation.translation_test_suite import TranslationTestSuite
@@ -21,4 +22,5 @@ class Application(ApplicationABC):
runner.run(CoreTestSuite()) runner.run(CoreTestSuite())
runner.run(CLITestSuite()) runner.run(CLITestSuite())
runner.run(QueryTestSuite()) runner.run(QueryTestSuite())
runner.run(ReactiveTestSuite())
runner.run(TranslationTestSuite()) runner.run(TranslationTestSuite())

View File

@@ -8,6 +8,7 @@ from unittests_cli.constants import PLAYGROUND_PATH
class CommandTestCase(unittest.TestCase): class CommandTestCase(unittest.TestCase):
_skip_tear_down = False _skip_tear_down = False
_cwd = os.getcwd()
def __init__(self, method_name: str): def __init__(self, method_name: str):
unittest.TestCase.__init__(self, method_name) unittest.TestCase.__init__(self, method_name)
@@ -32,6 +33,7 @@ class CommandTestCase(unittest.TestCase):
if cls._skip_tear_down: if cls._skip_tear_down:
return return
try: try:
os.chdir(cls._cwd)
if os.path.exists(PLAYGROUND_PATH): if os.path.exists(PLAYGROUND_PATH):
shutil.rmtree(os.path.abspath(os.path.join(PLAYGROUND_PATH))) shutil.rmtree(os.path.abspath(os.path.join(PLAYGROUND_PATH)))
except Exception as e: except Exception as e:

View File

@@ -0,0 +1,68 @@
import os
import sys
import unittest
from unittest.mock import Mock, MagicMock
from cpl_core.configuration import Configuration, ArgumentTypeEnum
from cpl_core.database import DatabaseSettings
from cpl_core.dependency_injection import ServiceProvider, ServiceCollection
from cpl_core.mailing import EMailClientSettings
class ConfigurationTestCase(unittest.TestCase):
def setUp(self):
self._config = Configuration()
def test_env_vars(self):
os.environ["CPLT_TESTVAR"] = "Hello World"
os.environ["CPL_NOT_EXISTING"] = "Hello World"
self._config.add_environment_variables("CPLT_")
self.assertEqual(self._config.get_configuration("TESTVAR"), "Hello World")
self.assertEqual(self._config.get_configuration("TESTVAR"), "Hello World")
self.assertEqual(self._config.get_configuration("NOT_EXISTING"), None)
def test_add_json_file(self):
self._config.add_json_file("unittests_core/configuration/test-settings.json")
db = self._config.get_configuration(DatabaseSettings)
self.assertIsNotNone(db)
self.assertEqual("localhost", db.host)
self.assertEqual("local", db.user)
self.assertEqual("bG9jYWw=", db.password)
self.assertEqual("local", db.database)
self.assertEqual(int, type(db.port))
self.assertEqual(3306, db.port)
self.assertEqual("utf8mb4", db.charset)
self.assertTrue(db.use_unicode)
self.assertTrue(db.buffered)
self.assertEqual("mysql_native_password", db.auth_plugin)
self.assertIsNone(self._config.get_configuration(EMailClientSettings))
def test_add_config(self):
self.assertIsNone(self._config.get_configuration("Test"))
self._config.add_configuration("Test", "Hello World")
self.assertIsNotNone(self._config.get_configuration("Test"))
self.assertEqual("Hello World", self._config.get_configuration("Test"))
def test_console_argument(self):
sc = ServiceCollection(self._config)
self.assertEqual([], sys.argv[1:])
sys.argv.append("flag")
sys.argv.append("exec")
sys.argv.append("var=test")
self.assertNotEqual([], sys.argv[1:])
self._config.create_console_argument(ArgumentTypeEnum.Flag, "", "flag", [])
mocked_exec = Mock()
mocked_exec.run = MagicMock()
sc.add_transient(mocked_exec)
self._config.create_console_argument(ArgumentTypeEnum.Executable, "", "exec", [], Mock)
self._config.create_console_argument(ArgumentTypeEnum.Variable, "", "var", [], "=")
self.assertIsNone(self._config.get_configuration("var"))
self._config.parse_console_arguments(sc.build_service_provider())
mocked_exec.run.assert_called()
self.assertEqual("test", self._config.get_configuration("var"))
self.assertIn("flag", self._config.additional_arguments)

View File

@@ -0,0 +1,75 @@
import sys
import unittest
from unittest.mock import Mock, MagicMock
from cpl_core.configuration import Configuration, ArgumentTypeEnum
from cpl_core.dependency_injection import ServiceCollection
class ConsoleArgumentsTestCase(unittest.TestCase):
def setUp(self):
self._config = Configuration()
self._config.create_console_argument(ArgumentTypeEnum.Flag, "", "flag", [])
self._config.create_console_argument(ArgumentTypeEnum.Variable, "", "var", [], "=")
self._config.create_console_argument(ArgumentTypeEnum.Executable, "", "exec", [], Mock).add_console_argument(
ArgumentTypeEnum.Flag, "--", "dev", ["d", "D"]
).add_console_argument(ArgumentTypeEnum.Flag, "--", "virtual", ["v", "V"]).add_console_argument(
ArgumentTypeEnum.Variable, "", "var1", [], "="
)
self._config.for_each_argument(
lambda a: a.add_console_argument(ArgumentTypeEnum.Flag, "--", "help", ["h", "H"])
)
self._sc = ServiceCollection(self._config)
self._mocked_exec = Mock()
self._mocked_exec.run = MagicMock()
self._sc.add_transient(self._mocked_exec)
def test_flag(self):
sys.argv.append("flag")
self._config.parse_console_arguments(self._sc.build_service_provider())
self.assertIn("flag", self._config.additional_arguments)
def test_var(self):
sys.argv.append("var=1")
sys.argv.append("var2=1")
self._config.parse_console_arguments(self._sc.build_service_provider())
self.assertEqual("1", self._config.get_configuration("var"))
self.assertIsNone(self._config.get_configuration("var1"))
def test_exec(self):
sys.argv.append("exec")
self._config.parse_console_arguments(self._sc.build_service_provider())
self._mocked_exec.run.assert_called()
def test_exec_with_one_flag(self):
sys.argv.append("exec")
sys.argv.append("--dev")
self._config.parse_console_arguments(self._sc.build_service_provider())
self._mocked_exec.run.assert_called()
self.assertIn("dev", self._config.additional_arguments)
def test_exec_with_one_flag_alias(self):
sys.argv.append("exec")
sys.argv.append("--d")
self._config.parse_console_arguments(self._sc.build_service_provider())
self._mocked_exec.run.assert_called()
self.assertIn("dev", self._config.additional_arguments)
def test_exec_with_two_flags(self):
sys.argv.append("exec")
sys.argv.append("--dev")
sys.argv.append("--virtual")
self._config.parse_console_arguments(self._sc.build_service_provider())
self._mocked_exec.run.assert_called()
self.assertIn("dev", self._config.additional_arguments)
self.assertIn("virtual", self._config.additional_arguments)

View File

@@ -0,0 +1,52 @@
import os
import unittest
from _socket import gethostname
from cpl_core.configuration import Configuration
from cpl_core.environment import ApplicationEnvironment, ApplicationEnvironmentABC
from cpl_core.environment import application_environment
class EnvironmentTestCase(unittest.TestCase):
def setUp(self):
self._config = Configuration()
self._env = self._config.environment
def test_app_env_created(self):
self.assertTrue(isinstance(self._env, ApplicationEnvironment))
self.assertTrue(issubclass(type(self._env), ApplicationEnvironmentABC))
def test_app_env_values_correct_when_default(self):
self.assertEqual(self._env.environment_name, "production")
self.assertEqual(self._env.application_name, "")
self.assertEqual(self._env.customer, "")
self.assertEqual(self._env.host_name, gethostname())
self.assertEqual(self._env.working_directory, os.getcwd())
self.assertEqual(
self._env.runtime_directory,
os.path.dirname(os.path.dirname(os.path.abspath(application_environment.__file__))),
)
def test_app_env_values_correct_when_read_from_env(self):
os.environ["CPLT_ENVIRONMENT"] = "development"
os.environ["CPLT_NAME"] = "Core Tests"
os.environ["CPLT_CUSTOMER"] = "sh-edraft.de"
self._config.add_environment_variables("CPLT_")
self.assertEqual(self._env.environment_name, "development")
self.assertEqual(self._env.application_name, "Core Tests")
self.assertEqual(self._env.customer, "sh-edraft.de")
self.assertEqual(self._env.host_name, gethostname())
self.assertEqual(self._env.working_directory, os.getcwd())
self.assertEqual(
self._env.runtime_directory,
os.path.dirname(os.path.dirname(os.path.abspath(application_environment.__file__))),
)
def test_app_env_set_dirs(self):
new_cwd = os.path.join(os.getcwd(), "../")
self._env.set_working_directory(new_cwd)
self.assertEqual(self._env.working_directory, new_cwd)
self._env.set_runtime_directory(new_cwd)
self.assertEqual(self._env.runtime_directory, new_cwd)

View File

@@ -0,0 +1,25 @@
{
"TimeFormatSettings": {
"DateFormat": "%Y-%m-%d",
"TimeFormat": "%H:%M:%S",
"DateTimeFormat": "%Y-%m-%d %H:%M:%S.%f",
"DateTimeLogFormat": "%Y-%m-%d_%H-%M-%S"
},
"LoggingSettings": {
"Path": "logs/$date_now/",
"Filename": "bot.log",
"ConsoleLogLevel": "TRACE",
"FileLogLevel": "TRACE"
},
"DatabaseSettings": {
"Host": "localhost",
"User": "local",
"Password": "bG9jYWw=",
"Database": "local",
"Port": "3306",
"Charset": "utf8mb4",
"UseUnicode": "true",
"Buffered": "true",
"AuthPlugin": "mysql_native_password"
}
}

View File

@@ -1,5 +1,10 @@
import unittest import unittest
from unittests_core.configuration.console_arguments_test_case import ConsoleArgumentsTestCase
from unittests_core.configuration.configuration_test_case import ConfigurationTestCase
from unittests_core.configuration.environment_test_case import EnvironmentTestCase
from unittests_core.di.service_collection_test_case import ServiceCollectionTestCase
from unittests_core.di.service_provider_test_case import ServiceProviderTestCase
from unittests_core.pipes.bool_pipe_test_case import BoolPipeTestCase from unittests_core.pipes.bool_pipe_test_case import BoolPipeTestCase
from unittests_core.pipes.ip_address_pipe_test_case import IPAddressTestCase from unittests_core.pipes.ip_address_pipe_test_case import IPAddressTestCase
from unittests_core.pipes.version_pipe_test_case import VersionPipeTestCase from unittests_core.pipes.version_pipe_test_case import VersionPipeTestCase
@@ -14,6 +19,13 @@ class CoreTestSuite(unittest.TestSuite):
loader = unittest.TestLoader() loader = unittest.TestLoader()
tests = [ tests = [
# config
ConfigurationTestCase,
ConsoleArgumentsTestCase,
EnvironmentTestCase,
# di
ServiceCollectionTestCase,
ServiceProviderTestCase,
# pipes # pipes
BoolPipeTestCase, BoolPipeTestCase,
IPAddressTestCase, IPAddressTestCase,

View File

View File

@@ -0,0 +1,56 @@
import unittest
from unittest.mock import Mock
from cpl_core.configuration import Configuration
from cpl_core.dependency_injection import ServiceCollection, ServiceLifetimeEnum, ServiceProviderABC
class ServiceCollectionTestCase(unittest.TestCase):
def setUp(self):
self._sc = ServiceCollection(Configuration())
def test_add_singleton_type(self):
self._sc.add_singleton(Mock)
service = self._sc._service_descriptors[0]
self.assertEqual(ServiceLifetimeEnum.singleton, service.lifetime)
self.assertEqual(Mock, service.service_type)
self.assertEqual(Mock, service.base_type)
self.assertIsNone(service.implementation)
def test_add_singleton_instance(self):
mock = Mock()
self._sc.add_singleton(mock)
service = self._sc._service_descriptors[0]
self.assertEqual(ServiceLifetimeEnum.singleton, service.lifetime)
self.assertEqual(type(mock), service.service_type)
self.assertEqual(type(mock), service.base_type)
self.assertIsNotNone(service.implementation)
def test_add_transient_type(self):
self._sc.add_transient(Mock)
service = self._sc._service_descriptors[0]
self.assertEqual(ServiceLifetimeEnum.transient, service.lifetime)
self.assertEqual(Mock, service.service_type)
self.assertEqual(Mock, service.base_type)
self.assertIsNone(service.implementation)
def test_add_scoped_type(self):
self._sc.add_scoped(Mock)
service = self._sc._service_descriptors[0]
self.assertEqual(ServiceLifetimeEnum.scoped, service.lifetime)
self.assertEqual(Mock, service.service_type)
self.assertEqual(Mock, service.base_type)
self.assertIsNone(service.implementation)
def test_build_service_provider(self):
self._sc.add_singleton(Mock)
service = self._sc._service_descriptors[0]
self.assertIsNone(service.implementation)
sp = self._sc.build_service_provider()
self.assertTrue(isinstance(sp, ServiceProviderABC))
self.assertTrue(isinstance(sp.get_service(Mock), Mock))
self.assertIsNotNone(service.implementation)

View File

@@ -0,0 +1,98 @@
import unittest
from cpl_core.configuration import Configuration
from cpl_core.dependency_injection import ServiceCollection, ServiceProviderABC
class ServiceCount:
def __init__(self):
self.count = 0
class TestService:
def __init__(self, sp: ServiceProviderABC, count: ServiceCount):
count.count += 1
self.sp = sp
self.id = count.count
class DifferentService:
def __init__(self, sp: ServiceProviderABC, count: ServiceCount):
count.count += 1
self.sp = sp
self.id = count.count
class MoreDifferentService:
def __init__(self, sp: ServiceProviderABC, count: ServiceCount):
count.count += 1
self.sp = sp
self.id = count.count
class ServiceProviderTestCase(unittest.TestCase):
def setUp(self):
self._services = (
ServiceCollection(Configuration())
.add_singleton(ServiceCount)
.add_singleton(TestService)
.add_singleton(TestService)
.add_transient(DifferentService)
.add_scoped(MoreDifferentService)
.build_service_provider()
)
count = self._services.get_service(ServiceCount)
def test_get_singleton(self):
x = self._services.get_service(TestService)
self.assertIsNotNone(x)
self.assertEqual(1, x.id)
self.assertEqual(x, self._services.get_service(TestService))
self.assertEqual(x, self._services.get_service(TestService))
self.assertEqual(x, self._services.get_service(TestService))
def test_get_singletons(self):
x = self._services.get_services(list[TestService])
self.assertEqual(2, len(x))
self.assertEqual(1, x[0].id)
self.assertEqual(2, x[1].id)
self.assertNotEqual(x[0], x[1])
def test_get_transient(self):
x = self._services.get_service(DifferentService)
self.assertIsNotNone(x)
self.assertEqual(1, x.id)
self.assertNotEqual(x, self._services.get_service(DifferentService))
self.assertNotEqual(x, self._services.get_service(DifferentService))
self.assertNotEqual(x, self._services.get_service(DifferentService))
def test_scoped(self):
scoped_id = 0
singleton = self._services.get_service(TestService)
transient = self._services.get_service(DifferentService)
with self._services.create_scope() as scope:
sp: ServiceProviderABC = scope.service_provider
self.assertNotEqual(sp, self._services)
y = sp.get_service(DifferentService)
self.assertIsNotNone(y)
self.assertEqual(3, y.id)
x = sp.get_service(MoreDifferentService)
self.assertIsNotNone(x)
self.assertEqual(4, x.id)
scoped_id = 4
self.assertEqual(singleton.sp, self._services)
self.assertEqual(transient.sp, self._services)
self.assertEqual(x.sp, sp)
self.assertNotEqual(x.sp, singleton.sp)
transient_in_scope = sp.get_service(DifferentService)
self.assertEqual(transient_in_scope.sp, sp)
self.assertNotEqual(transient.sp, transient_in_scope.sp)
self.assertEqual(x.id, sp.get_service(MoreDifferentService).id)
self.assertEqual(x.id, sp.get_service(MoreDifferentService).id)
self.assertNotEqual(x, self._services.get_service(MoreDifferentService))
self.assertEqual(singleton, self._services.get_service(TestService))
self.assertIsNone(scope.service_provider)
self.assertNotEqual(scoped_id, self._services.get_service(MoreDifferentService).id)

View File

@@ -26,7 +26,7 @@ class JSONProcessorTestCase(unittest.TestCase):
"i": 10, "i": 10,
"s": "Hello World", "s": "Hello World",
"d": {"test": "Test"}, "d": {"test": "Test"},
"l": range(0, 11), "l": list(range(0, 11)),
"value": {"value": "Hello World"}, "value": {"value": "Hello World"},
} }
test: TestClass = JSONProcessor.process(TestClass, test_dict) test: TestClass = JSONProcessor.process(TestClass, test_dict)

View File

@@ -12,6 +12,7 @@ class StringTestCase(unittest.TestCase):
expected = "HelloWorld" expected = "HelloWorld"
self.assertEqual(expected, String.convert_to_camel_case("hello-world")) self.assertEqual(expected, String.convert_to_camel_case("hello-world"))
self.assertEqual(expected, String.convert_to_camel_case("hello-World"))
self.assertEqual(expected, String.convert_to_camel_case("hello_world")) self.assertEqual(expected, String.convert_to_camel_case("hello_world"))
self.assertEqual("helloWorld", String.convert_to_camel_case("helloWorld")) self.assertEqual("helloWorld", String.convert_to_camel_case("helloWorld"))
self.assertEqual(expected, String.convert_to_camel_case("Hello_world")) self.assertEqual(expected, String.convert_to_camel_case("Hello_world"))

View File

@@ -0,0 +1 @@
# imports

View File

@@ -0,0 +1,88 @@
import time
import traceback
import unittest
from cpl_core.console import Console
from cpl_reactive_extensions.observable import Observable
from cpl_reactive_extensions.operators.debounce_time import debounce_time
from cpl_reactive_extensions.subject.subject import Subject
from cpl_reactive_extensions.interval import Interval
from cpl_reactive_extensions.operators.take import take
from cpl_reactive_extensions.operators.take_until import take_until
class ObservableOperatorTestCase(unittest.TestCase):
def setUp(self):
self._error = False
self._completed = False
def _on_error(self, ex: Exception):
tb = traceback.format_exc()
Console.error(f"Got error from observable: {ex}", tb)
self._error = True
def _on_complete(self):
self._completed = True
def test_take_two(self):
count = 0
def sub(x):
nonlocal count
count += 1
observable = Interval(0.1)
sub = observable.pipe(take(2)).subscribe(sub)
time.sleep(0.5)
self.assertEqual(count, 2)
sub.unsubscribe()
def test_take_five(self):
count = 0
def sub(x):
nonlocal count
count += 1
observable = Interval(0.1)
sub = observable.pipe(take(5)).subscribe(sub)
time.sleep(1)
self.assertEqual(count, 5)
sub.unsubscribe()
def test_take_until(self):
count = 0
unsubscriber = Subject(None)
def sub(x):
nonlocal count
count += 1
observable = Interval(0.1)
observable.pipe(take_until(unsubscriber)).subscribe(sub)
timer = 2
time.sleep(timer)
unsubscriber.next(None)
unsubscriber.complete()
self.assertEqual(count, timer * 10 - 1)
# def test_debounce_time(self):
# def call(x):
# x.next(1)
# x.next(2)
# x.next(3)
# x.next(4)
# x.next(5)
# x.next(6)
# x.complete()
#
# observable = Observable(call)
#
# sub = observable.pipe(debounce_time(600)).subscribe(lambda x: Console.write_line("Hey", x))
#
# time.sleep(2)
# sub.unsubscribe()

View File

@@ -0,0 +1,176 @@
import time
import traceback
import unittest
from datetime import datetime
from threading import Timer
from cpl_core.console import Console
from cpl_reactive_extensions.subject.behavior_subject import BehaviorSubject
from cpl_reactive_extensions.interval import Interval
from cpl_reactive_extensions.observable import Observable
from cpl_reactive_extensions.subject.subject import Subject
from cpl_reactive_extensions.internal.subscriber import Observer
class ReactiveTestCase(unittest.TestCase):
def setUp(self):
self._error = False
self._completed = False
def _on_error(self, ex: Exception):
tb = traceback.format_exc()
Console.error(f"Got error from observable: {ex}", tb)
self._error = True
def _on_complete(self):
self._completed = True
def test_observer(self):
called = 0
test_x = 1
def callback(observer: Observer):
nonlocal test_x
observer.next(test_x)
test_x += 1
observer.next(test_x)
test_x += 1
observer.next(test_x)
def complete():
nonlocal test_x
test_x += 1
observer.next(test_x)
observer.complete()
Timer(1.0, complete).start()
observable = Observable(callback)
def on_next(x):
nonlocal called
called += 1
self.assertEqual(test_x, x)
self.assertEqual(called, 0)
self.assertFalse(self._error)
self.assertFalse(self._completed)
observable.subscribe(
on_next,
self._on_error,
self._on_complete,
)
self.assertEqual(called, 3)
self.assertFalse(self._error)
self.assertFalse(self._completed)
def complete():
self.assertEqual(called, 4)
self.assertFalse(self._error)
self.assertTrue(self._completed)
Timer(1.0, complete).start()
def test_observer_completed(self):
def _test_complete(x: Observer):
x.next(1)
x.next(2)
x.complete()
x.next(3)
observable = Observable(_test_complete)
observable.subscribe(lambda x: Console.write_line(1, x), self._on_error)
self.assertTrue(self._error)
def test_observable_from(self):
expected_x = 1
def _next(x):
nonlocal expected_x
self.assertEqual(expected_x, x)
expected_x += 1
observable = Observable.from_list([1, 2, 3, 4])
sub = observable.subscribe(
_next,
self._on_error,
)
self.assertFalse(self._error)
sub.unsubscribe()
def test_subject(self):
expected_x = 1
def _next(calc, x):
nonlocal expected_x
self.assertEqual(expected_x, x)
if not calc:
return
expected_x += 1
if expected_x == 4:
expected_x = 1
subject = Subject(int)
subject.subscribe(lambda x: _next(False, x), self._on_error)
subject.subscribe(lambda x: _next(True, x), self._on_error)
observable = Observable.from_list([1, 2, 3])
sub = observable.subscribe(subject, self._on_error)
self.assertFalse(self._error)
sub.unsubscribe()
def test_behavior_subject(self):
subject = BehaviorSubject(int, 0)
subject.subscribe(lambda x: Console.write_line("a", x))
subject.next(1)
subject.next(2)
subject.subscribe(lambda x: Console.write_line("b", x))
subject.next(3)
subject.unsubscribe()
def test_interval_default(self):
wait = 10
i = 0
def test_sub(x):
nonlocal i
self.assertEqual(x, i)
i += 1
observable = Interval(1.0)
sub = observable.subscribe(test_sub)
start = datetime.now()
time.sleep(wait)
sub.unsubscribe()
end = datetime.now()
self.assertEqual(round((end - start).total_seconds()), wait)
def test_interval_custom(self):
wait = 10
i = 0
n = 0
def callback(x: Observer):
nonlocal n
x.next(n)
n += 1
def test_sub(x):
nonlocal i
self.assertEqual(x, i)
i += 1
observable = Interval(1.0, callback)
sub = observable.subscribe(test_sub)
start = datetime.now()
time.sleep(wait)
sub.unsubscribe()
end = datetime.now()
self.assertEqual(round((end - start).total_seconds()), wait)

View File

@@ -0,0 +1,23 @@
import unittest
from unittests_reactive_extenstions.observable_operator_test_case import ObservableOperatorTestCase
from unittests_reactive_extenstions.reactive_test_case import ReactiveTestCase
from unittests_reactive_extenstions.scheduler_test_case import SchedulerTestCase
class ReactiveTestSuite(unittest.TestSuite):
def __init__(self):
unittest.TestSuite.__init__(self)
loader = unittest.TestLoader()
self.addTests(loader.loadTestsFromTestCase(ReactiveTestCase))
self.addTests(loader.loadTestsFromTestCase(ObservableOperatorTestCase))
self.addTests(loader.loadTestsFromTestCase(SchedulerTestCase))
def run(self, *args):
super().run(*args)
if __name__ == "__main__":
runner = unittest.TextTestRunner()
runner.run(ReactiveTestSuite())

View File

@@ -0,0 +1,35 @@
import time
import unittest
from datetime import datetime
from cpl_core.console import Console
from cpl_reactive_extensions.timer import Timer
class SchedulerTestCase(unittest.TestCase):
def setUp(self):
pass
def test_timer(self):
count = 0
def task():
nonlocal count
Console.write_line(datetime.now(), "Hello world")
count += 1
timer = Timer(100, task)
time.sleep(0.25)
self.assertEqual(count, 2)
timer.clear()
def test_schedule(self):
count = 0
def task():
nonlocal count
Console.write_line(datetime.now(), "Hello world")
count += 1
# async_scheduler.schedule(task, 100)
time.sleep(2)

View File

@@ -0,0 +1,21 @@
import time
from datetime import datetime
from cpl_core.console import Console
from cpl_reactive_extensions.timer import Timer
def test_timer():
is_working = False
def task():
nonlocal is_working
Console.write_line(datetime.now(), "Hello world")
is_working = True
timer = Timer(100, task)
time.sleep(0.2)
timer.clear()
test_timer()

View File

@@ -0,0 +1,46 @@
{
"ProjectSettings": {
"Name": "unittests_reactive_extenstions",
"Version": {
"Major": "2023",
"Minor": "4",
"Micro": "dev170"
},
"Author": "",
"AuthorEmail": "",
"Description": "",
"LongDescription": "",
"URL": "",
"CopyrightDate": "",
"CopyrightName": "",
"LicenseName": "",
"LicenseDescription": "",
"Dependencies": [
"cpl-core>=2023.4.dev170"
],
"DevDependencies": [
"cpl-cli>=2023.4.0"
],
"PythonVersion": ">=3.10.4",
"PythonPath": {
"linux": ""
},
"Classifiers": []
},
"BuildSettings": {
"ProjectType": "unittest",
"SourcePath": "",
"OutputPath": "../../dist",
"Main": "unittests_reactive_extenstions.main",
"EntryPoint": "unittests_reactive_extenstions",
"IncludePackageData": false,
"Included": [],
"Excluded": [
"*/__pycache__",
"*/logs",
"*/tests"
],
"PackageData": {},
"ProjectReferences": []
}
}