Compare commits
45 Commits
1b60debba7
...
#170
| Author | SHA1 | Date | |
|---|---|---|---|
| 60a349f918 | |||
| a155bbc468 | |||
| 2e8886e255 | |||
| aabbfeaa92 | |||
| 39ca803d36 | |||
| b7d518022a | |||
| 2ec4af8bb3 | |||
| 30b163a440 | |||
| 82f23f237c | |||
| 79a6c1db8f | |||
| 51803bf5d1 | |||
| a463ac5274 | |||
| 3ee617ee38 | |||
| e5fd7df519 | |||
| 7001b23b31 | |||
| e94ff1b26f | |||
| d7d41b878c | |||
| 9d05d76cfa | |||
| e6ee543a1d | |||
| efc9cf9c83 | |||
| 8dee4d8f70 | |||
| 315b8e631a | |||
| cbb1860f25 | |||
| 9839bcaa14 | |||
| da54337221 | |||
| a6a1e764d1 | |||
| eb6aa08c10 | |||
| 14a190a67f | |||
| cf5ae89884 | |||
| 558dfb8ced | |||
| 2ec8fc22b3 | |||
| c94700495b | |||
| 69a3bc5e31 | |||
| d189f49418 | |||
| 60fb416b67 | |||
| 792429d19d | |||
| 106975015e | |||
| 1117735f2e | |||
| 0378f8944a | |||
| 75fde0f444 | |||
| 04f610c799 | |||
| 3178b59147 | |||
| 9c7008e179 | |||
| 7ff7dbc56b | |||
| 823d524a81 |
@@ -87,7 +87,7 @@
|
||||
|
||||
Install the CPL package
|
||||
```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
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"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-reactive-extensions": "src/cpl_reactive_extensions/cpl-reactive-extensions.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",
|
||||
@@ -14,7 +15,8 @@
|
||||
"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"
|
||||
"unittests_translation": "unittests/unittests_translation/unittests_translation.json",
|
||||
"unittests_reactive_extenstions": "unittests/unittests_reactive_extenstions/unittests_reactive_extenstions.json"
|
||||
},
|
||||
"Scripts": {
|
||||
"hello-world": "echo 'Hello World'",
|
||||
@@ -40,12 +42,13 @@
|
||||
"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",
|
||||
"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",
|
||||
"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-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-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 ../../;",
|
||||
@@ -57,6 +60,7 @@
|
||||
"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-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 ../../;",
|
||||
|
||||
"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;",
|
||||
"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;",
|
||||
"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;",
|
||||
"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;",
|
||||
"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;",
|
||||
"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;",
|
||||
"upl-dev-translation": "twine upload -r pip-dev.sh-edraft.de dist/cpl-translation/publish/setup/*",
|
||||
|
||||
|
||||
@@ -1,9 +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_cli.cli_settings_name_enum import CLISettingsNameEnum
|
||||
|
||||
|
||||
class CLISettings(ConfigurationModelABC):
|
||||
|
||||
@@ -18,10 +18,10 @@ class BuildSettings(ConfigurationModelABC):
|
||||
main: str = None,
|
||||
entry_point: str = None,
|
||||
include_package_data: bool = None,
|
||||
included: list[str] = None,
|
||||
excluded: list[str] = None,
|
||||
package_data: dict[str, list[str]] = None,
|
||||
project_references: list[str] = None,
|
||||
included: list = None,
|
||||
excluded: list = None,
|
||||
package_data: dict = None,
|
||||
project_references: list = None,
|
||||
):
|
||||
ConfigurationModelABC.__init__(self)
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ class ProjectSettings(ConfigurationModelABC):
|
||||
self._python_executable: Optional[str] = python_executable
|
||||
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]}"
|
||||
|
||||
if path == "" or path is None:
|
||||
|
||||
@@ -15,7 +15,7 @@ __title__ = "cpl_core"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de"
|
||||
__version__ = "2023.4.0"
|
||||
__version__ = "2023.4.dev170"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
@@ -23,4 +23,4 @@ from collections import namedtuple
|
||||
# imports:
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="2023", minor="4", micro="0")
|
||||
version_info = VersionInfo(major="2023", minor="4", micro="dev170")
|
||||
|
||||
@@ -15,7 +15,7 @@ __title__ = "cpl_core.application"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de"
|
||||
__version__ = "2023.4.0"
|
||||
__version__ = "2023.4.dev170"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
@@ -29,4 +29,4 @@ from .startup_abc import StartupABC
|
||||
from .startup_extension_abc import StartupExtensionABC
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="2023", minor="4", micro="0")
|
||||
version_info = VersionInfo(major="2023", minor="4", micro="dev170")
|
||||
|
||||
@@ -15,7 +15,7 @@ __title__ = "cpl_core.configuration"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de"
|
||||
__version__ = "2023.4.0"
|
||||
__version__ = "2023.4.dev170"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
@@ -35,4 +35,4 @@ from .validator_abc import ValidatorABC
|
||||
from .variable_argument import VariableArgument
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="2023", minor="4", micro="0")
|
||||
version_info = VersionInfo(major="2023", minor="4", micro="dev170")
|
||||
|
||||
@@ -294,7 +294,7 @@ class Configuration(ConfigurationABC):
|
||||
|
||||
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
|
||||
|
||||
def create_console_argument(
|
||||
@@ -314,7 +314,7 @@ class Configuration(ConfigurationABC):
|
||||
for arg in self._argument_types:
|
||||
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 search_type == ConfigurationVariableNameEnum.environment.value:
|
||||
return self._application_environment.environment_name
|
||||
|
||||
@@ -77,7 +77,7 @@ class ConfigurationABC(ABC):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def add_configuration(self, key_type: T, value: any):
|
||||
def add_configuration(self, key_type: Type[T], value: any):
|
||||
r"""Add configuration object
|
||||
|
||||
Parameter:
|
||||
|
||||
@@ -15,7 +15,7 @@ __title__ = "cpl_core.console"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de"
|
||||
__version__ = "2023.4.0"
|
||||
__version__ = "2023.4.dev170"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
@@ -28,4 +28,4 @@ from .foreground_color_enum import ForegroundColorEnum
|
||||
from .spinner_thread import SpinnerThread
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="2023", minor="4", micro="0")
|
||||
version_info = VersionInfo(major="2023", minor="4", micro="dev170")
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"Version": {
|
||||
"Major": "2023",
|
||||
"Minor": "4",
|
||||
"Micro": "0"
|
||||
"Micro": "dev170"
|
||||
},
|
||||
"Author": "Sven Heidemann",
|
||||
"AuthorEmail": "sven.heidemann@sh-edraft.de",
|
||||
@@ -16,17 +16,17 @@
|
||||
"LicenseName": "MIT",
|
||||
"LicenseDescription": "MIT, see LICENSE for more details.",
|
||||
"Dependencies": [
|
||||
"art==5.9",
|
||||
"colorama==0.4.6",
|
||||
"mysql-connector==2.2.9",
|
||||
"psutil==5.9.4",
|
||||
"packaging==23.0",
|
||||
"pynput==1.7.6",
|
||||
"setuptools==67.6.1",
|
||||
"tabulate==0.9.0",
|
||||
"termcolor==2.2.0",
|
||||
"watchdog==3.0.0",
|
||||
"wheel==0.40.0"
|
||||
"art>=5.9",
|
||||
"colorama>=0.4.6",
|
||||
"mysql-connector>=2.2.9",
|
||||
"psutil>=5.9.4",
|
||||
"packaging>=23.0",
|
||||
"pynput>=1.7.6",
|
||||
"setuptools>=67.6.1",
|
||||
"tabulate>=0.9.0",
|
||||
"termcolor>=2.2.0",
|
||||
"watchdog>=3.0.0",
|
||||
"wheel>=0.40.0"
|
||||
],
|
||||
"DevDependencies": [
|
||||
"Sphinx==5.0.2",
|
||||
@@ -36,7 +36,7 @@
|
||||
"sphinx-markdown-builder==0.5.5",
|
||||
"pygount==1.5.1"
|
||||
],
|
||||
"PythonVersion": ">=3.11",
|
||||
"PythonVersion": ">=3.10",
|
||||
"PythonPath": {},
|
||||
"Classifiers": []
|
||||
},
|
||||
|
||||
@@ -15,7 +15,7 @@ __title__ = "cpl_core.database"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de"
|
||||
__version__ = "2023.4.0"
|
||||
__version__ = "2023.4.dev170"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
@@ -26,4 +26,4 @@ from .database_settings import DatabaseSettings
|
||||
from .table_abc import TableABC
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="2023", minor="4", micro="0")
|
||||
version_info = VersionInfo(major="2023", minor="4", micro="dev170")
|
||||
|
||||
@@ -15,7 +15,7 @@ __title__ = "cpl_core.database.connection"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de"
|
||||
__version__ = "2023.4.0"
|
||||
__version__ = "2023.4.dev170"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
@@ -25,4 +25,4 @@ from .database_connection import DatabaseConnection
|
||||
from .database_connection_abc import DatabaseConnectionABC
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="2023", minor="4", micro="0")
|
||||
version_info = VersionInfo(major="2023", minor="4", micro="dev170")
|
||||
|
||||
@@ -15,7 +15,7 @@ __title__ = "cpl_core.database.context"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de"
|
||||
__version__ = "2023.4.0"
|
||||
__version__ = "2023.4.dev170"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
@@ -25,4 +25,4 @@ from .database_context import DatabaseContext
|
||||
from .database_context_abc import DatabaseContextABC
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="2023", minor="4", micro="0")
|
||||
version_info = VersionInfo(major="2023", minor="4", micro="dev170")
|
||||
|
||||
@@ -12,11 +12,11 @@ class DatabaseSettings(ConfigurationModelABC):
|
||||
port: int = None,
|
||||
user: str = None,
|
||||
password: str = None,
|
||||
databse: str = None,
|
||||
database: str = None,
|
||||
charset: str = None,
|
||||
use_unicode: bool = None,
|
||||
buffered: bool = None,
|
||||
auth_plugin: bool = None,
|
||||
auth_plugin: str = None,
|
||||
):
|
||||
ConfigurationModelABC.__init__(self)
|
||||
|
||||
@@ -24,7 +24,7 @@ class DatabaseSettings(ConfigurationModelABC):
|
||||
self._port: Optional[int] = port
|
||||
self._user: Optional[str] = user
|
||||
self._password: Optional[str] = password
|
||||
self._databse: Optional[str] = databse
|
||||
self._databse: Optional[str] = database
|
||||
self._charset: Optional[str] = charset
|
||||
self._use_unicode: Optional[bool] = use_unicode
|
||||
self._buffered: Optional[bool] = buffered
|
||||
|
||||
@@ -15,7 +15,7 @@ __title__ = "cpl_core.dependency_injection"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de"
|
||||
__version__ = "2023.4.0"
|
||||
__version__ = "2023.4.dev170"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
@@ -31,4 +31,4 @@ from .service_provider import ServiceProvider
|
||||
from .service_provider_abc import ServiceProviderABC
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="2023", minor="4", micro="0")
|
||||
version_info = VersionInfo(major="2023", minor="4", micro="dev170")
|
||||
|
||||
@@ -61,15 +61,15 @@ 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: Type[T], service: T = None):
|
||||
self._add_descriptor_by_lifetime(service_type, ServiceLifetimeEnum.singleton, service)
|
||||
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)
|
||||
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)
|
||||
return self
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ class ServiceCollectionABC(ABC):
|
||||
pass
|
||||
|
||||
@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
|
||||
|
||||
Parameter:
|
||||
@@ -61,7 +61,7 @@ class ServiceCollectionABC(ABC):
|
||||
pass
|
||||
|
||||
@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
|
||||
|
||||
Parameter:
|
||||
@@ -76,7 +76,7 @@ class ServiceCollectionABC(ABC):
|
||||
pass
|
||||
|
||||
@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
|
||||
|
||||
Parameter:
|
||||
|
||||
@@ -138,7 +138,7 @@ class ServiceProvider(ServiceProviderABC):
|
||||
sb = ScopeBuilder(ServiceProvider(descriptors, self._configuration, self._database_context))
|
||||
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)
|
||||
|
||||
if result is None:
|
||||
@@ -157,7 +157,7 @@ class ServiceProvider(ServiceProviderABC):
|
||||
|
||||
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 = []
|
||||
|
||||
if typing.get_origin(service_type) != list:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import functools
|
||||
from abc import abstractmethod, ABC
|
||||
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.type import T
|
||||
@@ -61,7 +61,7 @@ class ServiceProviderABC(ABC):
|
||||
pass
|
||||
|
||||
@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
|
||||
|
||||
Parameter
|
||||
@@ -76,7 +76,7 @@ class ServiceProviderABC(ABC):
|
||||
pass
|
||||
|
||||
@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
|
||||
|
||||
Parameter
|
||||
|
||||
@@ -15,7 +15,7 @@ __title__ = "cpl_core.environment"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de"
|
||||
__version__ = "2023.4.0"
|
||||
__version__ = "2023.4.dev170"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
@@ -26,4 +26,4 @@ from .environment_name_enum import EnvironmentNameEnum
|
||||
from .application_environment import ApplicationEnvironment
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="2023", minor="4", micro="0")
|
||||
version_info = VersionInfo(major="2023", minor="4", micro="dev170")
|
||||
|
||||
@@ -15,7 +15,7 @@ __title__ = "cpl_core.logging"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de"
|
||||
__version__ = "2023.4.0"
|
||||
__version__ = "2023.4.dev170"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
@@ -28,4 +28,4 @@ from .logging_settings import LoggingSettings
|
||||
from .logging_settings_name_enum import LoggingSettingsNameEnum
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="2023", minor="4", micro="0")
|
||||
version_info = VersionInfo(major="2023", minor="4", micro="dev170")
|
||||
|
||||
@@ -15,7 +15,7 @@ __title__ = "cpl_core.mailing"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de"
|
||||
__version__ = "2023.4.0"
|
||||
__version__ = "2023.4.dev170"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
@@ -28,4 +28,4 @@ from .email_client_settings import EMailClientSettings
|
||||
from .email_client_settings_name_enum import EMailClientSettingsNameEnum
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="2023", minor="4", micro="0")
|
||||
version_info = VersionInfo(major="2023", minor="4", micro="dev170")
|
||||
|
||||
@@ -15,19 +15,15 @@ __title__ = "cpl_core.pipes"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de"
|
||||
__version__ = "2023.4.0"
|
||||
__version__ = "2023.4.dev170"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports:
|
||||
from .bool_pipe import BoolPipe
|
||||
from .first_char_to_lower_pipe import FirstCharToLowerPipe
|
||||
from .first_to_upper_pipe import FirstToUpperPipe
|
||||
from .ip_address_pipe import IPAddressPipe
|
||||
from .pipe_abc import PipeABC
|
||||
from .to_camel_case_pipe import ToCamelCasePipe
|
||||
from .to_snake_case_pipe import ToSnakeCasePipe
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="2023", minor="4", micro="0")
|
||||
version_info = VersionInfo(major="2023", minor="4", micro="dev170")
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
from cpl_core.pipes.pipe_abc import PipeABC
|
||||
|
||||
|
||||
class FirstCharToLowerPipe(PipeABC):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def transform(self, value: any, *args):
|
||||
r"""Converts first char to lower
|
||||
|
||||
Parameter:
|
||||
value: :class:`str`
|
||||
String to convert
|
||||
|
||||
Returns:
|
||||
String with first char as lower
|
||||
"""
|
||||
return f"{value[0].lower()}{value[1:]}"
|
||||
@@ -1,18 +0,0 @@
|
||||
from cpl_core.pipes.pipe_abc import PipeABC
|
||||
|
||||
|
||||
class FirstToUpperPipe(PipeABC):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def transform(self, value: str, *args):
|
||||
r"""Converts first char to upper
|
||||
|
||||
Parameter:
|
||||
chars: :class:`str`
|
||||
String to convert
|
||||
|
||||
Returns:
|
||||
String with first char as upper
|
||||
"""
|
||||
return f"{value[0].upper()}{value[1:]}"
|
||||
@@ -13,7 +13,7 @@ class IPAddressPipe(PipeABC):
|
||||
|
||||
for i in range(0, len(value)):
|
||||
byte = value[i]
|
||||
if byte > 255:
|
||||
if byte > 255 or byte < 0:
|
||||
raise Exception("Invalid IP")
|
||||
|
||||
if i == len(value) - 1:
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import string
|
||||
|
||||
from cpl_core.pipes import PipeABC
|
||||
|
||||
|
||||
class ToCamelCasePipe(PipeABC):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def transform(self, value: str, *args) -> str:
|
||||
r"""Converts string to camel case
|
||||
|
||||
Parameter:
|
||||
chars: :class:`str`
|
||||
String to convert
|
||||
|
||||
Returns:
|
||||
String converted to CamelCase
|
||||
"""
|
||||
converted_name = value
|
||||
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
|
||||
@@ -1,27 +0,0 @@
|
||||
import re
|
||||
|
||||
from cpl_core.pipes import PipeABC
|
||||
|
||||
|
||||
class ToSnakeCasePipe(PipeABC):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def transform(self, value: str, *args) -> str:
|
||||
r"""Converts string to snake case
|
||||
|
||||
Parameter:
|
||||
chars: :class:`str`
|
||||
String to convert
|
||||
|
||||
Returns:
|
||||
String converted to snake_case
|
||||
"""
|
||||
# convert to train-case to CamelCase
|
||||
if "-" in value:
|
||||
value = "".join(word.title() for word in value.split("-"))
|
||||
|
||||
pattern1 = re.compile(r"(.)([A-Z][a-z]+)")
|
||||
pattern2 = re.compile(r"([a-z0-9])([A-Z])")
|
||||
file_name = re.sub(pattern1, r"\1_\2", value)
|
||||
return re.sub(pattern2, r"\1_\2", file_name).lower()
|
||||
@@ -15,7 +15,7 @@ __title__ = "cpl_core.time"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de"
|
||||
__version__ = "2023.4.0"
|
||||
__version__ = "2023.4.dev170"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
@@ -25,4 +25,4 @@ from .time_format_settings import TimeFormatSettings
|
||||
from .time_format_settings_names_enum import TimeFormatSettingsNamesEnum
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="2023", minor="4", micro="0")
|
||||
version_info = VersionInfo(major="2023", minor="4", micro="dev170")
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from typing import TypeVar
|
||||
from typing import TypeVar, Union
|
||||
|
||||
T = TypeVar("T")
|
||||
Number = Union[int, float]
|
||||
|
||||
@@ -15,7 +15,7 @@ __title__ = "cpl_core.utils"
|
||||
__author__ = "Sven Heidemann"
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de"
|
||||
__version__ = "2023.4.0"
|
||||
__version__ = "2023.4.dev170"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
@@ -26,4 +26,4 @@ from .string import String
|
||||
from .pip import Pip
|
||||
|
||||
VersionInfo = namedtuple("VersionInfo", "major minor micro")
|
||||
version_info = VersionInfo(major="2023", minor="4", micro="0")
|
||||
version_info = VersionInfo(major="2023", minor="4", micro="dev170")
|
||||
|
||||
@@ -17,12 +17,14 @@ class JSONProcessor:
|
||||
|
||||
name = String.first_to_upper(String.convert_to_camel_case(parameter.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 = ""
|
||||
if name in values:
|
||||
value = values[name]
|
||||
else:
|
||||
elif name_first_lower in values:
|
||||
value = values[name_first_lower]
|
||||
else:
|
||||
value = values[name.upper()]
|
||||
|
||||
if isinstance(value, dict) and not issubclass(parameter.annotation, dict):
|
||||
value = JSONProcessor.process(parameter.annotation, value)
|
||||
@@ -30,6 +32,9 @@ class JSONProcessor:
|
||||
if issubclass(parameter.annotation, enum.Enum):
|
||||
value = parameter.annotation[value]
|
||||
|
||||
if type(value) != parameter.annotation:
|
||||
value = parameter.annotation(value)
|
||||
|
||||
args.append(value)
|
||||
|
||||
elif parameter.default != Parameter.empty:
|
||||
|
||||
@@ -37,9 +37,19 @@ class String:
|
||||
String converted to snake_case
|
||||
"""
|
||||
# convert to train-case to CamelCase
|
||||
if "_" in chars:
|
||||
chars = chars.replace("_", "-")
|
||||
|
||||
if "-" in chars:
|
||||
chars = "".join(word.title() for word in chars.split("-"))
|
||||
|
||||
if " " in chars:
|
||||
new_chars = ""
|
||||
for word in chars.split(" "):
|
||||
new_chars += String.first_to_upper(word)
|
||||
|
||||
chars = new_chars
|
||||
|
||||
pattern1 = re.compile(r"(.)([A-Z][a-z]+)")
|
||||
pattern2 = re.compile(r"([a-z0-9])([A-Z])")
|
||||
file_name = re.sub(pattern1, r"\1_\2", chars)
|
||||
|
||||
@@ -19,6 +19,7 @@ __version__ = "2023.4.0"
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
# imports:
|
||||
from .default_lambda import default_lambda
|
||||
from .ordered_queryable import OrderedQueryable
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"LicenseDescription": "MIT, see LICENSE for more details.",
|
||||
"Dependencies": [],
|
||||
"DevDependencies": [],
|
||||
"PythonVersion": ">=3.11",
|
||||
"PythonVersion": ">=3.10",
|
||||
"PythonPath": {},
|
||||
"Classifiers": []
|
||||
},
|
||||
|
||||
26
src/cpl_reactive_extensions/__init__.py
Normal file
26
src/cpl_reactive_extensions/__init__.py
Normal 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")
|
||||
26
src/cpl_reactive_extensions/abc/__init__.py
Normal file
26
src/cpl_reactive_extensions/abc/__init__.py
Normal 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")
|
||||
20
src/cpl_reactive_extensions/abc/observer.py
Normal file
20
src/cpl_reactive_extensions/abc/observer.py
Normal 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
|
||||
8
src/cpl_reactive_extensions/abc/operator.py
Normal file
8
src/cpl_reactive_extensions/abc/operator.py
Normal 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
|
||||
10
src/cpl_reactive_extensions/abc/scheduler_action.py
Normal file
10
src/cpl_reactive_extensions/abc/scheduler_action.py
Normal 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
|
||||
12
src/cpl_reactive_extensions/abc/scheduler_like.py
Normal file
12
src/cpl_reactive_extensions/abc/scheduler_like.py
Normal 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
|
||||
16
src/cpl_reactive_extensions/abc/subscribable.py
Normal file
16
src/cpl_reactive_extensions/abc/subscribable.py
Normal 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
|
||||
10
src/cpl_reactive_extensions/abc/unsubscribable.py
Normal file
10
src/cpl_reactive_extensions/abc/unsubscribable.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class Unsubscribable(ABC):
|
||||
def __init__(self):
|
||||
ABC.__init__(self)
|
||||
|
||||
@abstractmethod
|
||||
def unsubscribe(self):
|
||||
pass
|
||||
44
src/cpl_reactive_extensions/cpl-reactive-extensions.json
Normal file
44
src/cpl_reactive_extensions/cpl-reactive-extensions.json
Normal 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": []
|
||||
}
|
||||
}
|
||||
26
src/cpl_reactive_extensions/helper/__init__.py
Normal file
26
src/cpl_reactive_extensions/helper/__init__.py
Normal 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")
|
||||
11
src/cpl_reactive_extensions/helper/bind.py
Normal file
11
src/cpl_reactive_extensions/helper/bind.py
Normal 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
|
||||
26
src/cpl_reactive_extensions/internal/__init__.py
Normal file
26
src/cpl_reactive_extensions/internal/__init__.py
Normal 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")
|
||||
10
src/cpl_reactive_extensions/internal/action.py
Normal file
10
src/cpl_reactive_extensions/internal/action.py
Normal 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
|
||||
103
src/cpl_reactive_extensions/internal/async_action.py
Normal file
103
src/cpl_reactive_extensions/internal/async_action.py
Normal 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)
|
||||
55
src/cpl_reactive_extensions/internal/operator_subscriber.py
Normal file
55
src/cpl_reactive_extensions/internal/operator_subscriber.py
Normal 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()
|
||||
65
src/cpl_reactive_extensions/internal/subscriber.py
Normal file
65
src/cpl_reactive_extensions/internal/subscriber.py
Normal 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
|
||||
96
src/cpl_reactive_extensions/internal/subscription.py
Normal file
96
src/cpl_reactive_extensions/internal/subscription.py
Normal 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)
|
||||
10
src/cpl_reactive_extensions/internal/timer_provider.py
Normal file
10
src/cpl_reactive_extensions/internal/timer_provider.py
Normal 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)
|
||||
20
src/cpl_reactive_extensions/internal/utils.py
Normal file
20
src/cpl_reactive_extensions/internal/utils.py
Normal 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
|
||||
47
src/cpl_reactive_extensions/interval.py
Normal file
47
src/cpl_reactive_extensions/interval.py
Normal 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
|
||||
114
src/cpl_reactive_extensions/observable.py
Normal file
114
src/cpl_reactive_extensions/observable.py
Normal 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)
|
||||
26
src/cpl_reactive_extensions/operators/__init__.py
Normal file
26
src/cpl_reactive_extensions/operators/__init__.py
Normal 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")
|
||||
62
src/cpl_reactive_extensions/operators/debounce_time.py
Normal file
62
src/cpl_reactive_extensions/operators/debounce_time.py
Normal 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)
|
||||
29
src/cpl_reactive_extensions/operators/take.py
Normal file
29
src/cpl_reactive_extensions/operators/take.py
Normal 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)
|
||||
14
src/cpl_reactive_extensions/operators/take_until.py
Normal file
14
src/cpl_reactive_extensions/operators/take_until.py
Normal 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)
|
||||
26
src/cpl_reactive_extensions/scheduler/__init__.py
Normal file
26
src/cpl_reactive_extensions/scheduler/__init__.py
Normal 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")
|
||||
36
src/cpl_reactive_extensions/scheduler/async_scheduler.py
Normal file
36
src/cpl_reactive_extensions/scheduler/async_scheduler.py
Normal 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)
|
||||
29
src/cpl_reactive_extensions/scheduler/scheduler.py
Normal file
29
src/cpl_reactive_extensions/scheduler/scheduler.py
Normal 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
|
||||
26
src/cpl_reactive_extensions/subject/__init__.py
Normal file
26
src/cpl_reactive_extensions/subject/__init__.py
Normal 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")
|
||||
24
src/cpl_reactive_extensions/subject/behavior_subject.py
Normal file
24
src/cpl_reactive_extensions/subject/behavior_subject.py
Normal 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
|
||||
101
src/cpl_reactive_extensions/subject/subject.py
Normal file
101
src/cpl_reactive_extensions/subject/subject.py
Normal 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)
|
||||
24
src/cpl_reactive_extensions/timer.py
Normal file
24
src/cpl_reactive_extensions/timer.py
Normal 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()
|
||||
5
src/cpl_reactive_extensions/type.py
Normal file
5
src/cpl_reactive_extensions/type.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from typing import Callable, Union
|
||||
|
||||
from cpl_reactive_extensions.abc.observer import Observer
|
||||
|
||||
ObserverOrCallable = Union[Callable, Observer]
|
||||
@@ -8,6 +8,7 @@ from cpl_core.dependency_injection import ServiceProviderABC
|
||||
from cpl_core.logging import LoggerABC
|
||||
from cpl_core.mailing import EMailClientABC, EMail
|
||||
from cpl_core.pipes import IPAddressPipe
|
||||
from general.test_settings import TestSettings
|
||||
from test_service import TestService
|
||||
|
||||
|
||||
@@ -57,4 +58,13 @@ class Application(ApplicationABC):
|
||||
Console.write_line("scope", scope)
|
||||
with self._services.create_scope() as 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()
|
||||
|
||||
@@ -29,5 +29,9 @@
|
||||
"UseUnicode": "true",
|
||||
"Buffered": "true",
|
||||
"AuthPlugin": "mysql_native_password"
|
||||
},
|
||||
|
||||
"TestSettings": {
|
||||
"Value": 20
|
||||
}
|
||||
}
|
||||
6
tests/custom/general/src/general/test_settings.py
Normal file
6
tests/custom/general/src/general/test_settings.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from cpl_core.configuration import ConfigurationModelABC
|
||||
|
||||
|
||||
class TestSettings(ConfigurationModelABC):
|
||||
def __init__(self, value: int = None):
|
||||
self.value = value
|
||||
@@ -16,8 +16,8 @@
|
||||
"LicenseName": "MIT",
|
||||
"LicenseDescription": "MIT, see LICENSE for more details.",
|
||||
"Dependencies": [
|
||||
"cpl-core==2022.12.0",
|
||||
"GitPython==3.1.29"
|
||||
"cpl-core>=2022.12.0",
|
||||
"GitPython>=3.1.29"
|
||||
],
|
||||
"DevDependencies": [],
|
||||
"PythonVersion": ">=3.10.4",
|
||||
|
||||
@@ -4,7 +4,9 @@ from cpl_core.application import ApplicationABC
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.dependency_injection import ServiceProviderABC
|
||||
from unittests_cli.cli_test_suite import CLITestSuite
|
||||
from unittests_core.core_test_suite import CoreTestSuite
|
||||
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
|
||||
|
||||
|
||||
@@ -17,6 +19,8 @@ class Application(ApplicationABC):
|
||||
|
||||
def main(self):
|
||||
runner = unittest.TextTestRunner()
|
||||
runner.run(CoreTestSuite())
|
||||
runner.run(CLITestSuite())
|
||||
runner.run(QueryTestSuite())
|
||||
runner.run(ReactiveTestSuite())
|
||||
runner.run(TranslationTestSuite())
|
||||
|
||||
@@ -8,6 +8,7 @@ from unittests_cli.constants import PLAYGROUND_PATH
|
||||
|
||||
class CommandTestCase(unittest.TestCase):
|
||||
_skip_tear_down = False
|
||||
_cwd = os.getcwd()
|
||||
|
||||
def __init__(self, method_name: str):
|
||||
unittest.TestCase.__init__(self, method_name)
|
||||
@@ -32,6 +33,7 @@ class CommandTestCase(unittest.TestCase):
|
||||
if cls._skip_tear_down:
|
||||
return
|
||||
try:
|
||||
os.chdir(cls._cwd)
|
||||
if os.path.exists(PLAYGROUND_PATH):
|
||||
shutil.rmtree(os.path.abspath(os.path.join(PLAYGROUND_PATH)))
|
||||
except Exception as e:
|
||||
|
||||
0
unittests/unittests_core/configuration/__init__.py
Normal file
0
unittests/unittests_core/configuration/__init__.py
Normal 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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
25
unittests/unittests_core/configuration/test-settings.json
Normal file
25
unittests/unittests_core/configuration/test-settings.json
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,43 @@
|
||||
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.ip_address_pipe_test_case import IPAddressTestCase
|
||||
from unittests_core.pipes.version_pipe_test_case import VersionPipeTestCase
|
||||
from unittests_core.utils.credential_manager_test_case import CredentialManagerTestCase
|
||||
from unittests_core.utils.json_processor_test_case import JSONProcessorTestCase
|
||||
from unittests_core.utils.string_test_case import StringTestCase
|
||||
from unittests_query.enumerable_query_test_case import EnumerableQueryTestCase
|
||||
from unittests_query.enumerable_test_case import EnumerableTestCase
|
||||
from unittests_query.iterable_query_test_case import IterableQueryTestCase
|
||||
from unittests_query.iterable_test_case import IterableTestCase
|
||||
from unittests_query.sequence_test_case import SequenceTestCase
|
||||
|
||||
|
||||
class QueryTestSuite(unittest.TestSuite):
|
||||
class CoreTestSuite(unittest.TestSuite):
|
||||
def __init__(self):
|
||||
unittest.TestSuite.__init__(self)
|
||||
|
||||
loader = unittest.TestLoader()
|
||||
self.addTests(loader.loadTestsFromTestCase(StringTestCase))
|
||||
tests = [
|
||||
# config
|
||||
ConfigurationTestCase,
|
||||
ConsoleArgumentsTestCase,
|
||||
EnvironmentTestCase,
|
||||
# di
|
||||
ServiceCollectionTestCase,
|
||||
ServiceProviderTestCase,
|
||||
# pipes
|
||||
BoolPipeTestCase,
|
||||
IPAddressTestCase,
|
||||
VersionPipeTestCase,
|
||||
# utils
|
||||
CredentialManagerTestCase,
|
||||
JSONProcessorTestCase,
|
||||
StringTestCase,
|
||||
]
|
||||
|
||||
for test in tests:
|
||||
self.addTests(loader.loadTestsFromTestCase(test))
|
||||
|
||||
def run(self, *args):
|
||||
super().run(*args)
|
||||
@@ -21,4 +45,4 @@ class QueryTestSuite(unittest.TestSuite):
|
||||
|
||||
if __name__ == "__main__":
|
||||
runner = unittest.TextTestRunner()
|
||||
runner.run(QueryTestSuite())
|
||||
runner.run(CoreTestSuite())
|
||||
|
||||
0
unittests/unittests_core/di/__init__.py
Normal file
0
unittests/unittests_core/di/__init__.py
Normal file
56
unittests/unittests_core/di/service_collection_test_case.py
Normal file
56
unittests/unittests_core/di/service_collection_test_case.py
Normal 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)
|
||||
98
unittests/unittests_core/di/service_provider_test_case.py
Normal file
98
unittests/unittests_core/di/service_provider_test_case.py
Normal 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)
|
||||
1
unittests/unittests_core/pipes/__init__.py
Normal file
1
unittests/unittests_core/pipes/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# imports
|
||||
14
unittests/unittests_core/pipes/bool_pipe_test_case.py
Normal file
14
unittests/unittests_core/pipes/bool_pipe_test_case.py
Normal file
@@ -0,0 +1,14 @@
|
||||
import unittest
|
||||
|
||||
from cpl_core.pipes import BoolPipe
|
||||
|
||||
|
||||
class BoolPipeTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def test_transform(self):
|
||||
pipe = BoolPipe()
|
||||
|
||||
self.assertEqual("True", pipe.transform(True))
|
||||
self.assertEqual("False", pipe.transform(False))
|
||||
20
unittests/unittests_core/pipes/ip_address_pipe_test_case.py
Normal file
20
unittests/unittests_core/pipes/ip_address_pipe_test_case.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import unittest
|
||||
|
||||
from cpl_core.pipes import IPAddressPipe
|
||||
|
||||
|
||||
class IPAddressTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def test_transform(self):
|
||||
pipe = IPAddressPipe()
|
||||
|
||||
self.assertEqual("192.168.178.1", pipe.transform([192, 168, 178, 1]))
|
||||
self.assertEqual("255.255.255.255", pipe.transform([255, 255, 255, 255]))
|
||||
self.assertEqual("0.0.0.0", pipe.transform([0, 0, 0, 0]))
|
||||
|
||||
self.assertRaises(Exception, lambda: pipe.transform([-192, 168, 178, 1]))
|
||||
self.assertRaises(Exception, lambda: pipe.transform([256, 168, 178, 1]))
|
||||
self.assertRaises(Exception, lambda: pipe.transform([256, 168, 178]))
|
||||
self.assertRaises(Exception, lambda: pipe.transform([256, 168, 178, 1, 1]))
|
||||
16
unittests/unittests_core/pipes/version_pipe_test_case.py
Normal file
16
unittests/unittests_core/pipes/version_pipe_test_case.py
Normal file
@@ -0,0 +1,16 @@
|
||||
import unittest
|
||||
|
||||
from cpl_core.pipes.version_pipe import VersionPipe
|
||||
|
||||
|
||||
class VersionPipeTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def test_transform(self):
|
||||
pipe = VersionPipe()
|
||||
|
||||
self.assertEqual("1.1.1", pipe.transform({"Major": 1, "Minor": 1, "Micro": 1}))
|
||||
self.assertEqual("0.1.1", pipe.transform({"Major": 0, "Minor": 1, "Micro": 1}))
|
||||
self.assertEqual("0.0.1", pipe.transform({"Major": 0, "Minor": 0, "Micro": 1}))
|
||||
self.assertEqual("0.0.0", pipe.transform({"Major": 0, "Minor": 0, "Micro": 0}))
|
||||
@@ -1,16 +0,0 @@
|
||||
from cpl_core.application.startup_extension_abc import StartupExtensionABC
|
||||
from cpl_core.configuration.configuration_abc import ConfigurationABC
|
||||
from cpl_core.dependency_injection.service_collection_abc import ServiceCollectionABC
|
||||
from cpl_core.environment.application_environment_abc import ApplicationEnvironmentABC
|
||||
|
||||
|
||||
class TestStartup_extension(StartupExtensionABC):
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def configure_configuration(self, config: ConfigurationABC, env: ApplicationEnvironmentABC):
|
||||
pass
|
||||
|
||||
def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC):
|
||||
pass
|
||||
@@ -1,10 +1,40 @@
|
||||
import unittest
|
||||
|
||||
from cpl_core.utils import CredentialManager
|
||||
|
||||
|
||||
class CredentialManagerTestCase(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def test_equal(self):
|
||||
pass
|
||||
def test_encrypt(self):
|
||||
self.assertEqual("ZkVjSkplQUx4aW1zWHlPbA==", CredentialManager.encrypt("fEcJJeALximsXyOl"))
|
||||
self.assertEqual("QmtVd1l4dW5Sck9jRmVTQQ==", CredentialManager.encrypt("BkUwYxunRrOcFeSA"))
|
||||
self.assertEqual("c2FtaHF1VkNSdmZpSGxDcQ==", CredentialManager.encrypt("samhquVCRvfiHlCq"))
|
||||
self.assertEqual("S05aWHBPYW9DbkRSV01rWQ==", CredentialManager.encrypt("KNZXpOaoCnDRWMkY"))
|
||||
self.assertEqual("QmtUV0Zsb3h1Y254UkJWeg==", CredentialManager.encrypt("BkTWFloxucnxRBVz"))
|
||||
self.assertEqual("VFdNTkRuYXB1b1dndXNKdw==", CredentialManager.encrypt("TWMNDnapuoWgusJw"))
|
||||
self.assertEqual("WVRiQXVSZXRMblpicWNrcQ==", CredentialManager.encrypt("YTbAuRetLnZbqckq"))
|
||||
self.assertEqual("bmN4aExackxhYUVVdnV2VA==", CredentialManager.encrypt("ncxhLZrLaaEUvuvT"))
|
||||
self.assertEqual("dmpNT0J5U0lLQmFrc0pIYQ==", CredentialManager.encrypt("vjMOBySIKBaksJHa"))
|
||||
self.assertEqual("ZHd6WHFzSlFvQlhRbGtVZw==", CredentialManager.encrypt("dwzXqsJQoBXQlkUg"))
|
||||
self.assertEqual("Q0lmUUhOREtiUmxnY2VCbQ==", CredentialManager.encrypt("CIfQHNDKbRlgceBm"))
|
||||
|
||||
def test_decrypt(self):
|
||||
self.assertEqual("fEcJJeALximsXyOl", CredentialManager.decrypt("ZkVjSkplQUx4aW1zWHlPbA=="))
|
||||
self.assertEqual("BkUwYxunRrOcFeSA", CredentialManager.decrypt("QmtVd1l4dW5Sck9jRmVTQQ=="))
|
||||
self.assertEqual("samhquVCRvfiHlCq", CredentialManager.decrypt("c2FtaHF1VkNSdmZpSGxDcQ=="))
|
||||
self.assertEqual("KNZXpOaoCnDRWMkY", CredentialManager.decrypt("S05aWHBPYW9DbkRSV01rWQ=="))
|
||||
self.assertEqual("BkTWFloxucnxRBVz", CredentialManager.decrypt("QmtUV0Zsb3h1Y254UkJWeg=="))
|
||||
self.assertEqual("TWMNDnapuoWgusJw", CredentialManager.decrypt("VFdNTkRuYXB1b1dndXNKdw=="))
|
||||
self.assertEqual("YTbAuRetLnZbqckq", CredentialManager.decrypt("WVRiQXVSZXRMblpicWNrcQ=="))
|
||||
self.assertEqual("ncxhLZrLaaEUvuvT", CredentialManager.decrypt("bmN4aExackxhYUVVdnV2VA=="))
|
||||
self.assertEqual("vjMOBySIKBaksJHa", CredentialManager.decrypt("dmpNT0J5U0lLQmFrc0pIYQ=="))
|
||||
self.assertEqual("dwzXqsJQoBXQlkUg", CredentialManager.decrypt("ZHd6WHFzSlFvQlhRbGtVZw=="))
|
||||
self.assertEqual("CIfQHNDKbRlgceBm", CredentialManager.decrypt("Q0lmUUhOREtiUmxnY2VCbQ=="))
|
||||
|
||||
def test_build_string(self):
|
||||
self.assertEqual(
|
||||
"TestStringWithCredentialsfEcJJeALximsXyOlHere",
|
||||
CredentialManager.build_string("TestStringWithCredentials$credentialsHere", "ZkVjSkplQUx4aW1zWHlPbA=="),
|
||||
)
|
||||
|
||||
38
unittests/unittests_core/utils/json_processor_test_case.py
Normal file
38
unittests/unittests_core/utils/json_processor_test_case.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import unittest
|
||||
|
||||
from cpl_core.utils.json_processor import JSONProcessor
|
||||
|
||||
|
||||
class SubTestClass:
|
||||
def __init__(self, value: str = None):
|
||||
self.value = value
|
||||
|
||||
|
||||
class TestClass:
|
||||
def __init__(self, i: int = None, s: str = None, d: dict = None, l: list = None, value: SubTestClass = None):
|
||||
self.i = i
|
||||
self.s = s
|
||||
self.d = d
|
||||
self.l = l
|
||||
self.value = value
|
||||
|
||||
|
||||
class JSONProcessorTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def test_process(self):
|
||||
test_dict = {
|
||||
"i": 10,
|
||||
"s": "Hello World",
|
||||
"d": {"test": "Test"},
|
||||
"l": list(range(0, 11)),
|
||||
"value": {"value": "Hello World"},
|
||||
}
|
||||
test: TestClass = JSONProcessor.process(TestClass, test_dict)
|
||||
|
||||
self.assertEqual(test.i, test_dict["i"])
|
||||
self.assertEqual(test.s, test_dict["s"])
|
||||
self.assertEqual(test.d, test_dict["d"])
|
||||
self.assertEqual(test.l, test_dict["l"])
|
||||
self.assertEqual(test.value.value, test_dict["value"]["value"])
|
||||
@@ -1,10 +1,58 @@
|
||||
import string
|
||||
import unittest
|
||||
|
||||
from cpl_core.utils import String
|
||||
|
||||
|
||||
class StringTestCase(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def test_equal(self):
|
||||
pass
|
||||
def test_convert_to_camel_case(self):
|
||||
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("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"))
|
||||
self.assertEqual(expected, String.convert_to_camel_case("hello world"))
|
||||
|
||||
def test_convert_to_snake_case(self):
|
||||
expected = "hello_world"
|
||||
|
||||
self.assertEqual(expected, String.convert_to_snake_case("Hello World"))
|
||||
self.assertEqual(expected, String.convert_to_snake_case("hello-world"))
|
||||
self.assertEqual(expected, String.convert_to_snake_case("hello_world"))
|
||||
self.assertEqual(expected, String.convert_to_snake_case("helloWorld"))
|
||||
self.assertEqual(expected, String.convert_to_snake_case("Hello_world"))
|
||||
self.assertEqual(expected, String.convert_to_snake_case("Hello_World"))
|
||||
self.assertEqual(expected, String.convert_to_snake_case("hello world"))
|
||||
|
||||
def test_first_to_upper(self):
|
||||
expected = "HelloWorld"
|
||||
|
||||
self.assertEqual(expected, String.first_to_upper("helloWorld"))
|
||||
self.assertEqual(expected, String.first_to_upper("HelloWorld"))
|
||||
|
||||
def test_first_to_lower(self):
|
||||
expected = "helloWorld"
|
||||
|
||||
self.assertEqual(expected, String.first_to_lower("helloWorld"))
|
||||
self.assertEqual(expected, String.first_to_lower("HelloWorld"))
|
||||
|
||||
def test_random_string(self):
|
||||
expected = ""
|
||||
|
||||
for x in range(0, 100):
|
||||
rstr = String.random_string(string.ascii_letters, 4)
|
||||
self.assertNotEqual(expected, rstr)
|
||||
self.assertEqual(4, len(rstr))
|
||||
expected = rstr
|
||||
|
||||
for x in range(0, 100):
|
||||
rstr = String.random_string(string.ascii_letters, 16)
|
||||
self.assertNotEqual(expected, rstr)
|
||||
self.assertEqual(16, len(rstr))
|
||||
expected = rstr
|
||||
|
||||
1
unittests/unittests_reactive_extenstions/__init__.py
Normal file
1
unittests/unittests_reactive_extenstions/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# imports
|
||||
@@ -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()
|
||||
176
unittests/unittests_reactive_extenstions/reactive_test_case.py
Normal file
176
unittests/unittests_reactive_extenstions/reactive_test_case.py
Normal 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)
|
||||
@@ -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())
|
||||
@@ -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)
|
||||
21
unittests/unittests_reactive_extenstions/test.py
Normal file
21
unittests/unittests_reactive_extenstions/test.py
Normal 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()
|
||||
@@ -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": []
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user