45 Commits

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

View File

@@ -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

View File

@@ -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/*",

View File

@@ -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):

View File

@@ -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)

View File

@@ -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:

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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

View File

@@ -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:

View File

@@ -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")

View File

@@ -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": []
},

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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

View File

@@ -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")

View File

@@ -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

View File

@@ -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:

View File

@@ -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:

View File

@@ -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

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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:]}"

View File

@@ -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:]}"

View File

@@ -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:

View File

@@ -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

View File

@@ -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()

View File

@@ -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")

View File

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

View File

@@ -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")

View File

@@ -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:

View File

@@ -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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,6 +8,7 @@ from cpl_core.dependency_injection import ServiceProviderABC
from cpl_core.logging import LoggerABC
from cpl_core.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()

View File

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

View File

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

View File

@@ -16,8 +16,8 @@
"LicenseName": "MIT",
"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",

View File

@@ -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())

View File

@@ -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:

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,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())

View File

View File

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

View File

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

View File

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

View 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))

View 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]))

View 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}))

View File

@@ -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

View File

@@ -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=="),
)

View 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"])

View File

@@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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