From aabbfeaa920a8d829984ca8b69644473a6438766 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Sun, 16 Apr 2023 21:46:17 +0200 Subject: [PATCH] [WIP] Added scheduler --- src/cpl_core/__init__.py | 4 +- src/cpl_core/application/__init__.py | 4 +- src/cpl_core/configuration/__init__.py | 4 +- src/cpl_core/console/__init__.py | 4 +- src/cpl_core/cpl-core.json | 2 +- src/cpl_core/database/__init__.py | 4 +- src/cpl_core/database/connection/__init__.py | 4 +- src/cpl_core/database/context/__init__.py | 4 +- src/cpl_core/dependency_injection/__init__.py | 4 +- src/cpl_core/environment/__init__.py | 4 +- src/cpl_core/logging/__init__.py | 4 +- src/cpl_core/mailing/__init__.py | 4 +- src/cpl_core/pipes/__init__.py | 4 +- src/cpl_core/time/__init__.py | 4 +- src/cpl_core/type.py | 3 +- src/cpl_core/utils/__init__.py | 4 +- src/cpl_reactive_extensions/__init__.py | 8 +- src/cpl_reactive_extensions/abc/__init__.py | 5 - src/cpl_reactive_extensions/abc/operator.py | 2 +- .../abc/scheduler_action.py | 10 ++ .../abc/scheduler_like.py | 12 +++ .../cpl-reactive-extensions.json | 2 +- .../helper/__init__.py | 26 +++++ src/cpl_reactive_extensions/helper/bind.py | 11 +++ .../internal/__init__.py | 26 +++++ .../internal/action.py | 10 ++ .../internal/async_action.py | 97 +++++++++++++++++++ .../{ => internal}/operator_subscriber.py | 2 +- .../{ => internal}/subscriber.py | 2 +- .../{ => internal}/subscription.py | 0 .../internal/timer_provider.py | 10 ++ .../{ => internal}/utils.py | 3 +- src/cpl_reactive_extensions/interval.py | 2 +- src/cpl_reactive_extensions/observable.py | 7 +- .../operators/__init__.py | 26 +++++ .../operators/debounce_time.py | 62 ++++++++++++ src/cpl_reactive_extensions/operators/take.py | 7 +- .../operators/take_until.py | 8 +- .../scheduler/__init__.py | 26 +++++ .../scheduler/async_scheduler.py | 36 +++++++ .../scheduler/scheduler.py | 29 ++++++ .../subject/__init__.py | 26 +++++ .../{ => subject}/behavior_subject.py | 6 +- .../{ => subject}/subject.py | 4 +- src/cpl_reactive_extensions/timer.py | 24 +++++ ...or.py => observable_operator_test_case.py} | 18 +++- .../reactive_test_case.py | 6 +- .../reactive_test_suite.py | 4 +- .../scheduler_test_case.py | 36 +++++++ .../unittests_reactive_extenstions/test.py | 21 ++++ .../unittests_reactive_extenstions.json | 8 +- 51 files changed, 572 insertions(+), 71 deletions(-) create mode 100644 src/cpl_reactive_extensions/abc/scheduler_action.py create mode 100644 src/cpl_reactive_extensions/abc/scheduler_like.py create mode 100644 src/cpl_reactive_extensions/helper/__init__.py create mode 100644 src/cpl_reactive_extensions/helper/bind.py create mode 100644 src/cpl_reactive_extensions/internal/__init__.py create mode 100644 src/cpl_reactive_extensions/internal/action.py create mode 100644 src/cpl_reactive_extensions/internal/async_action.py rename src/cpl_reactive_extensions/{ => internal}/operator_subscriber.py (96%) rename src/cpl_reactive_extensions/{ => internal}/subscriber.py (95%) rename src/cpl_reactive_extensions/{ => internal}/subscription.py (100%) create mode 100644 src/cpl_reactive_extensions/internal/timer_provider.py rename src/cpl_reactive_extensions/{ => internal}/utils.py (79%) create mode 100644 src/cpl_reactive_extensions/operators/debounce_time.py create mode 100644 src/cpl_reactive_extensions/scheduler/__init__.py create mode 100644 src/cpl_reactive_extensions/scheduler/async_scheduler.py create mode 100644 src/cpl_reactive_extensions/scheduler/scheduler.py create mode 100644 src/cpl_reactive_extensions/subject/__init__.py rename src/cpl_reactive_extensions/{ => subject}/behavior_subject.py (76%) rename src/cpl_reactive_extensions/{ => subject}/subject.py (95%) create mode 100644 src/cpl_reactive_extensions/timer.py rename unittests/unittests_reactive_extenstions/{observable_operator.py => observable_operator_test_case.py} (74%) create mode 100644 unittests/unittests_reactive_extenstions/scheduler_test_case.py create mode 100644 unittests/unittests_reactive_extenstions/test.py diff --git a/src/cpl_core/__init__.py b/src/cpl_core/__init__.py index 33f82aae..072bdd31 100644 --- a/src/cpl_core/__init__.py +++ b/src/cpl_core/__init__.py @@ -15,7 +15,7 @@ __title__ = "cpl_core" __author__ = "Sven Heidemann" __license__ = "MIT" __copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de" -__version__ = "2023.4.0.post1" +__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.post1") +version_info = VersionInfo(major="2023", minor="4", micro="dev170") diff --git a/src/cpl_core/application/__init__.py b/src/cpl_core/application/__init__.py index 3a87cd69..e53006c1 100644 --- a/src/cpl_core/application/__init__.py +++ b/src/cpl_core/application/__init__.py @@ -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.post1" +__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.post1") +version_info = VersionInfo(major="2023", minor="4", micro="dev170") diff --git a/src/cpl_core/configuration/__init__.py b/src/cpl_core/configuration/__init__.py index 1c84582e..91bdf545 100644 --- a/src/cpl_core/configuration/__init__.py +++ b/src/cpl_core/configuration/__init__.py @@ -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.post1" +__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.post1") +version_info = VersionInfo(major="2023", minor="4", micro="dev170") diff --git a/src/cpl_core/console/__init__.py b/src/cpl_core/console/__init__.py index 1f85043b..fedef234 100644 --- a/src/cpl_core/console/__init__.py +++ b/src/cpl_core/console/__init__.py @@ -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.post1" +__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.post1") +version_info = VersionInfo(major="2023", minor="4", micro="dev170") diff --git a/src/cpl_core/cpl-core.json b/src/cpl_core/cpl-core.json index eccc2894..5f207a4e 100644 --- a/src/cpl_core/cpl-core.json +++ b/src/cpl_core/cpl-core.json @@ -4,7 +4,7 @@ "Version": { "Major": "2023", "Minor": "4", - "Micro": "0.post1" + "Micro": "dev170" }, "Author": "Sven Heidemann", "AuthorEmail": "sven.heidemann@sh-edraft.de", diff --git a/src/cpl_core/database/__init__.py b/src/cpl_core/database/__init__.py index 2ddbf2ce..e700c92c 100644 --- a/src/cpl_core/database/__init__.py +++ b/src/cpl_core/database/__init__.py @@ -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.post1" +__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.post1") +version_info = VersionInfo(major="2023", minor="4", micro="dev170") diff --git a/src/cpl_core/database/connection/__init__.py b/src/cpl_core/database/connection/__init__.py index 5354b745..d2f31ae4 100644 --- a/src/cpl_core/database/connection/__init__.py +++ b/src/cpl_core/database/connection/__init__.py @@ -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.post1" +__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.post1") +version_info = VersionInfo(major="2023", minor="4", micro="dev170") diff --git a/src/cpl_core/database/context/__init__.py b/src/cpl_core/database/context/__init__.py index 7b72b757..a1e122fd 100644 --- a/src/cpl_core/database/context/__init__.py +++ b/src/cpl_core/database/context/__init__.py @@ -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.post1" +__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.post1") +version_info = VersionInfo(major="2023", minor="4", micro="dev170") diff --git a/src/cpl_core/dependency_injection/__init__.py b/src/cpl_core/dependency_injection/__init__.py index b5474ba1..05e6a176 100644 --- a/src/cpl_core/dependency_injection/__init__.py +++ b/src/cpl_core/dependency_injection/__init__.py @@ -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.post1" +__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.post1") +version_info = VersionInfo(major="2023", minor="4", micro="dev170") diff --git a/src/cpl_core/environment/__init__.py b/src/cpl_core/environment/__init__.py index 2e0a30cb..629f82c1 100644 --- a/src/cpl_core/environment/__init__.py +++ b/src/cpl_core/environment/__init__.py @@ -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.post1" +__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.post1") +version_info = VersionInfo(major="2023", minor="4", micro="dev170") diff --git a/src/cpl_core/logging/__init__.py b/src/cpl_core/logging/__init__.py index 5c812d5a..89d50924 100644 --- a/src/cpl_core/logging/__init__.py +++ b/src/cpl_core/logging/__init__.py @@ -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.post1" +__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.post1") +version_info = VersionInfo(major="2023", minor="4", micro="dev170") diff --git a/src/cpl_core/mailing/__init__.py b/src/cpl_core/mailing/__init__.py index 2ae416dd..42bfec59 100644 --- a/src/cpl_core/mailing/__init__.py +++ b/src/cpl_core/mailing/__init__.py @@ -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.post1" +__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.post1") +version_info = VersionInfo(major="2023", minor="4", micro="dev170") diff --git a/src/cpl_core/pipes/__init__.py b/src/cpl_core/pipes/__init__.py index 956b2267..18cf42d0 100644 --- a/src/cpl_core/pipes/__init__.py +++ b/src/cpl_core/pipes/__init__.py @@ -15,7 +15,7 @@ __title__ = "cpl_core.pipes" __author__ = "Sven Heidemann" __license__ = "MIT" __copyright__ = "Copyright (c) 2020 - 2023 sh-edraft.de" -__version__ = "2023.4.0.post1" +__version__ = "2023.4.dev170" from collections import namedtuple @@ -26,4 +26,4 @@ from .ip_address_pipe import IPAddressPipe from .pipe_abc import PipeABC VersionInfo = namedtuple("VersionInfo", "major minor micro") -version_info = VersionInfo(major="2023", minor="4", micro="0.post1") +version_info = VersionInfo(major="2023", minor="4", micro="dev170") diff --git a/src/cpl_core/time/__init__.py b/src/cpl_core/time/__init__.py index 100defe5..cfa94114 100644 --- a/src/cpl_core/time/__init__.py +++ b/src/cpl_core/time/__init__.py @@ -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.post1" +__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.post1") +version_info = VersionInfo(major="2023", minor="4", micro="dev170") diff --git a/src/cpl_core/type.py b/src/cpl_core/type.py index b186d5ad..bc2b5aa8 100644 --- a/src/cpl_core/type.py +++ b/src/cpl_core/type.py @@ -1,3 +1,4 @@ -from typing import TypeVar +from typing import TypeVar, Union T = TypeVar("T") +Number = Union[int, float] diff --git a/src/cpl_core/utils/__init__.py b/src/cpl_core/utils/__init__.py index 27e32634..83f2b99d 100644 --- a/src/cpl_core/utils/__init__.py +++ b/src/cpl_core/utils/__init__.py @@ -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.post1" +__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.post1") +version_info = VersionInfo(major="2023", minor="4", micro="dev170") diff --git a/src/cpl_reactive_extensions/__init__.py b/src/cpl_reactive_extensions/__init__.py index ee1370bd..2b4c06ff 100644 --- a/src/cpl_reactive_extensions/__init__.py +++ b/src/cpl_reactive_extensions/__init__.py @@ -19,14 +19,8 @@ __version__ = "2023.4.dev170" from collections import namedtuple + # imports -from .behavior_subject import BehaviorSubject -from .interval import Interval -from .observable import Observable -from .subject import Subject -from .subscriber import Subscriber -from .subscription import Subscription -from .type import ObserverOrCallable VersionInfo = namedtuple("VersionInfo", "major minor micro") version_info = VersionInfo(major="2023", minor="4", micro="dev170") diff --git a/src/cpl_reactive_extensions/abc/__init__.py b/src/cpl_reactive_extensions/abc/__init__.py index 7cedfcf7..41ecc2b3 100644 --- a/src/cpl_reactive_extensions/abc/__init__.py +++ b/src/cpl_reactive_extensions/abc/__init__.py @@ -21,11 +21,6 @@ from collections import namedtuple # imports -from .observer import Observer -from .operator import Operator -from .subscribable import Subscribable -from .unsubscribable import Unsubscribable - VersionInfo = namedtuple("VersionInfo", "major minor micro") version_info = VersionInfo(major="2023", minor="4", micro="dev170") diff --git a/src/cpl_reactive_extensions/abc/operator.py b/src/cpl_reactive_extensions/abc/operator.py index 8ac9ee04..e2bb15a9 100644 --- a/src/cpl_reactive_extensions/abc/operator.py +++ b/src/cpl_reactive_extensions/abc/operator.py @@ -1,6 +1,6 @@ from typing import Any -from cpl_reactive_extensions.subscriber import Subscriber +from cpl_reactive_extensions.internal.subscriber import Subscriber class Operator: diff --git a/src/cpl_reactive_extensions/abc/scheduler_action.py b/src/cpl_reactive_extensions/abc/scheduler_action.py new file mode 100644 index 00000000..82aed704 --- /dev/null +++ b/src/cpl_reactive_extensions/abc/scheduler_action.py @@ -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 diff --git a/src/cpl_reactive_extensions/abc/scheduler_like.py b/src/cpl_reactive_extensions/abc/scheduler_like.py new file mode 100644 index 00000000..d3740cd0 --- /dev/null +++ b/src/cpl_reactive_extensions/abc/scheduler_like.py @@ -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 diff --git a/src/cpl_reactive_extensions/cpl-reactive-extensions.json b/src/cpl_reactive_extensions/cpl-reactive-extensions.json index 39cd9308..c02adb45 100644 --- a/src/cpl_reactive_extensions/cpl-reactive-extensions.json +++ b/src/cpl_reactive_extensions/cpl-reactive-extensions.json @@ -16,7 +16,7 @@ "LicenseName": "MIT", "LicenseDescription": "MIT, see LICENSE for more details.", "Dependencies": [ - "cpl-core>=2023.4.0" + "cpl-core>=2023.4.dev170" ], "DevDependencies": [ "cpl-cli>=2023.4.0" diff --git a/src/cpl_reactive_extensions/helper/__init__.py b/src/cpl_reactive_extensions/helper/__init__.py new file mode 100644 index 00000000..0fa9697f --- /dev/null +++ b/src/cpl_reactive_extensions/helper/__init__.py @@ -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") diff --git a/src/cpl_reactive_extensions/helper/bind.py b/src/cpl_reactive_extensions/helper/bind.py new file mode 100644 index 00000000..f617d601 --- /dev/null +++ b/src/cpl_reactive_extensions/helper/bind.py @@ -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 diff --git a/src/cpl_reactive_extensions/internal/__init__.py b/src/cpl_reactive_extensions/internal/__init__.py new file mode 100644 index 00000000..7b09b429 --- /dev/null +++ b/src/cpl_reactive_extensions/internal/__init__.py @@ -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") diff --git a/src/cpl_reactive_extensions/internal/action.py b/src/cpl_reactive_extensions/internal/action.py new file mode 100644 index 00000000..ce67cc75 --- /dev/null +++ b/src/cpl_reactive_extensions/internal/action.py @@ -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 diff --git a/src/cpl_reactive_extensions/internal/async_action.py b/src/cpl_reactive_extensions/internal/async_action.py new file mode 100644 index 00000000..4b5b18a0 --- /dev/null +++ b/src/cpl_reactive_extensions/internal/async_action.py @@ -0,0 +1,97 @@ +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 + + self._scheduler.actions.remove(self) + + if self._timer is not None: + self._timer = self.recycle_async_timer(self._scheduler, self.timer, None) + + self._work = None + self.state = None + self._scheduler = None + self._pending = False + self.delay = None + Action.unsubscribe(self) diff --git a/src/cpl_reactive_extensions/operator_subscriber.py b/src/cpl_reactive_extensions/internal/operator_subscriber.py similarity index 96% rename from src/cpl_reactive_extensions/operator_subscriber.py rename to src/cpl_reactive_extensions/internal/operator_subscriber.py index f93ceac6..f81be949 100644 --- a/src/cpl_reactive_extensions/operator_subscriber.py +++ b/src/cpl_reactive_extensions/internal/operator_subscriber.py @@ -1,8 +1,8 @@ from typing import Callable from cpl_core.type import T -from cpl_reactive_extensions import Subscriber from cpl_reactive_extensions.abc import Observer +from cpl_reactive_extensions.internal.subscriber import Subscriber class OperatorSubscriber(Subscriber, Observer): diff --git a/src/cpl_reactive_extensions/subscriber.py b/src/cpl_reactive_extensions/internal/subscriber.py similarity index 95% rename from src/cpl_reactive_extensions/subscriber.py rename to src/cpl_reactive_extensions/internal/subscriber.py index 6dd577b7..e2daeba3 100644 --- a/src/cpl_reactive_extensions/subscriber.py +++ b/src/cpl_reactive_extensions/internal/subscriber.py @@ -2,7 +2,7 @@ from typing import Callable from cpl_core.type import T from cpl_reactive_extensions.abc.observer import Observer -from cpl_reactive_extensions.subscription import Subscription +from cpl_reactive_extensions.internal.subscription import Subscription from cpl_reactive_extensions.type import ObserverOrCallable diff --git a/src/cpl_reactive_extensions/subscription.py b/src/cpl_reactive_extensions/internal/subscription.py similarity index 100% rename from src/cpl_reactive_extensions/subscription.py rename to src/cpl_reactive_extensions/internal/subscription.py diff --git a/src/cpl_reactive_extensions/internal/timer_provider.py b/src/cpl_reactive_extensions/internal/timer_provider.py new file mode 100644 index 00000000..aafd0314 --- /dev/null +++ b/src/cpl_reactive_extensions/internal/timer_provider.py @@ -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) diff --git a/src/cpl_reactive_extensions/utils.py b/src/cpl_reactive_extensions/internal/utils.py similarity index 79% rename from src/cpl_reactive_extensions/utils.py rename to src/cpl_reactive_extensions/internal/utils.py index 1be7dc42..b02a7bbb 100644 --- a/src/cpl_reactive_extensions/utils.py +++ b/src/cpl_reactive_extensions/internal/utils.py @@ -1,6 +1,7 @@ from typing import Callable -from cpl_reactive_extensions import Observable, Subscriber +from cpl_reactive_extensions.observable import Observable +from cpl_reactive_extensions.internal.subscriber import Subscriber def operate(init: Callable[[Observable, Subscriber], None]): diff --git a/src/cpl_reactive_extensions/interval.py b/src/cpl_reactive_extensions/interval.py index 9fe9163a..464dcd45 100644 --- a/src/cpl_reactive_extensions/interval.py +++ b/src/cpl_reactive_extensions/interval.py @@ -3,8 +3,8 @@ import threading import time from typing import Callable +from cpl_reactive_extensions.internal.subscriber import Subscriber from cpl_reactive_extensions.observable import Observable -from cpl_reactive_extensions.subscriber import Subscriber class Interval(Observable): diff --git a/src/cpl_reactive_extensions/observable.py b/src/cpl_reactive_extensions/observable.py index 77561a5b..dfec039b 100644 --- a/src/cpl_reactive_extensions/observable.py +++ b/src/cpl_reactive_extensions/observable.py @@ -1,11 +1,12 @@ from __future__ import annotations -from typing import Callable, Any, Optional, Type +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.subscriber import Observer, Subscriber -from cpl_reactive_extensions.subscription import Subscription +from cpl_reactive_extensions.internal.subscriber import Subscriber +from cpl_reactive_extensions.internal.subscription import Subscription from cpl_reactive_extensions.type import ObserverOrCallable diff --git a/src/cpl_reactive_extensions/operators/__init__.py b/src/cpl_reactive_extensions/operators/__init__.py index e69de29b..45eda886 100644 --- a/src/cpl_reactive_extensions/operators/__init__.py +++ b/src/cpl_reactive_extensions/operators/__init__.py @@ -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") diff --git a/src/cpl_reactive_extensions/operators/debounce_time.py b/src/cpl_reactive_extensions/operators/debounce_time.py new file mode 100644 index 00000000..8ea1c421 --- /dev/null +++ b/src/cpl_reactive_extensions/operators/debounce_time.py @@ -0,0 +1,62 @@ +from typing import Optional + +from cpl_core.type import T, Number +from cpl_reactive_extensions.abc 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) diff --git a/src/cpl_reactive_extensions/operators/take.py b/src/cpl_reactive_extensions/operators/take.py index ec93f8b0..43c34cc0 100644 --- a/src/cpl_reactive_extensions/operators/take.py +++ b/src/cpl_reactive_extensions/operators/take.py @@ -1,7 +1,8 @@ from cpl_core.type import T -from cpl_reactive_extensions import Subscriber, Observable -from cpl_reactive_extensions.operator_subscriber import OperatorSubscriber -from cpl_reactive_extensions.utils import operate +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): diff --git a/src/cpl_reactive_extensions/operators/take_until.py b/src/cpl_reactive_extensions/operators/take_until.py index a2a51f4e..9363291f 100644 --- a/src/cpl_reactive_extensions/operators/take_until.py +++ b/src/cpl_reactive_extensions/operators/take_until.py @@ -1,7 +1,7 @@ -from cpl_core.type import T -from cpl_reactive_extensions import Subscriber, Observable -from cpl_reactive_extensions.operator_subscriber import OperatorSubscriber -from cpl_reactive_extensions.utils import operate +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): diff --git a/src/cpl_reactive_extensions/scheduler/__init__.py b/src/cpl_reactive_extensions/scheduler/__init__.py new file mode 100644 index 00000000..a7c22f9c --- /dev/null +++ b/src/cpl_reactive_extensions/scheduler/__init__.py @@ -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") diff --git a/src/cpl_reactive_extensions/scheduler/async_scheduler.py b/src/cpl_reactive_extensions/scheduler/async_scheduler.py new file mode 100644 index 00000000..7636abd9 --- /dev/null +++ b/src/cpl_reactive_extensions/scheduler/async_scheduler.py @@ -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) diff --git a/src/cpl_reactive_extensions/scheduler/scheduler.py b/src/cpl_reactive_extensions/scheduler/scheduler.py new file mode 100644 index 00000000..894a62b7 --- /dev/null +++ b/src/cpl_reactive_extensions/scheduler/scheduler.py @@ -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 diff --git a/src/cpl_reactive_extensions/subject/__init__.py b/src/cpl_reactive_extensions/subject/__init__.py new file mode 100644 index 00000000..e01be62a --- /dev/null +++ b/src/cpl_reactive_extensions/subject/__init__.py @@ -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") diff --git a/src/cpl_reactive_extensions/behavior_subject.py b/src/cpl_reactive_extensions/subject/behavior_subject.py similarity index 76% rename from src/cpl_reactive_extensions/behavior_subject.py rename to src/cpl_reactive_extensions/subject/behavior_subject.py index 67121644..5a9c76dc 100644 --- a/src/cpl_reactive_extensions/behavior_subject.py +++ b/src/cpl_reactive_extensions/subject/behavior_subject.py @@ -1,9 +1,11 @@ +from typing import Type + from cpl_core.type import T -from cpl_reactive_extensions.subject import Subject +from cpl_reactive_extensions.subject.subject import Subject class BehaviorSubject(Subject): - def __init__(self, _t: type, value: T): + def __init__(self, _t: Type[T], value: T): Subject.__init__(self, _t) if not isinstance(value, _t): diff --git a/src/cpl_reactive_extensions/subject.py b/src/cpl_reactive_extensions/subject/subject.py similarity index 95% rename from src/cpl_reactive_extensions/subject.py rename to src/cpl_reactive_extensions/subject/subject.py index 95714d2e..0e157f5d 100644 --- a/src/cpl_reactive_extensions/subject.py +++ b/src/cpl_reactive_extensions/subject/subject.py @@ -4,8 +4,8 @@ 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.subscriber import Subscriber -from cpl_reactive_extensions.subscription import Subscription +from cpl_reactive_extensions.internal.subscriber import Subscriber +from cpl_reactive_extensions.internal.subscription import Subscription class Subject(Observable, Observer): diff --git a/src/cpl_reactive_extensions/timer.py b/src/cpl_reactive_extensions/timer.py new file mode 100644 index 00000000..4b3a688d --- /dev/null +++ b/src/cpl_reactive_extensions/timer.py @@ -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() diff --git a/unittests/unittests_reactive_extenstions/observable_operator.py b/unittests/unittests_reactive_extenstions/observable_operator_test_case.py similarity index 74% rename from unittests/unittests_reactive_extenstions/observable_operator.py rename to unittests/unittests_reactive_extenstions/observable_operator_test_case.py index 760cbd04..413a82a7 100644 --- a/unittests/unittests_reactive_extenstions/observable_operator.py +++ b/unittests/unittests_reactive_extenstions/observable_operator_test_case.py @@ -3,7 +3,9 @@ import traceback import unittest from cpl_core.console import Console -from cpl_reactive_extensions import Subject +from cpl_reactive_extensions.observable import Observable +from cpl_reactive_extensions.operators 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 @@ -65,3 +67,17 @@ class ObservableOperatorTestCase(unittest.TestCase): 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) + + observable.pipe(debounce_time(600)).subscribe(lambda x: Console.write_line("Hey", x)) diff --git a/unittests/unittests_reactive_extenstions/reactive_test_case.py b/unittests/unittests_reactive_extenstions/reactive_test_case.py index 758f15de..28619411 100644 --- a/unittests/unittests_reactive_extenstions/reactive_test_case.py +++ b/unittests/unittests_reactive_extenstions/reactive_test_case.py @@ -5,11 +5,11 @@ from datetime import datetime from threading import Timer from cpl_core.console import Console -from cpl_reactive_extensions.behavior_subject import BehaviorSubject +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 import Subject -from cpl_reactive_extensions.subscriber import Observer +from cpl_reactive_extensions.subject.subject import Subject +from cpl_reactive_extensions.internal.subscriber import Observer class ReactiveTestCase(unittest.TestCase): diff --git a/unittests/unittests_reactive_extenstions/reactive_test_suite.py b/unittests/unittests_reactive_extenstions/reactive_test_suite.py index e95e8cbf..d0d541b7 100644 --- a/unittests/unittests_reactive_extenstions/reactive_test_suite.py +++ b/unittests/unittests_reactive_extenstions/reactive_test_suite.py @@ -1,7 +1,8 @@ import unittest -from unittests_reactive_extenstions.observable_operator import ObservableOperatorTestCase +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): @@ -11,6 +12,7 @@ class ReactiveTestSuite(unittest.TestSuite): 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) diff --git a/unittests/unittests_reactive_extenstions/scheduler_test_case.py b/unittests/unittests_reactive_extenstions/scheduler_test_case.py new file mode 100644 index 00000000..09cec0c3 --- /dev/null +++ b/unittests/unittests_reactive_extenstions/scheduler_test_case.py @@ -0,0 +1,36 @@ +import time +import unittest +from datetime import datetime + +from cpl_core.console import Console +from cpl_reactive_extensions.scheduler.async_scheduler import async_scheduler +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) diff --git a/unittests/unittests_reactive_extenstions/test.py b/unittests/unittests_reactive_extenstions/test.py new file mode 100644 index 00000000..f620d934 --- /dev/null +++ b/unittests/unittests_reactive_extenstions/test.py @@ -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() diff --git a/unittests/unittests_reactive_extenstions/unittests_reactive_extenstions.json b/unittests/unittests_reactive_extenstions/unittests_reactive_extenstions.json index 8919eb59..bc23ec7a 100644 --- a/unittests/unittests_reactive_extenstions/unittests_reactive_extenstions.json +++ b/unittests/unittests_reactive_extenstions/unittests_reactive_extenstions.json @@ -2,9 +2,9 @@ "ProjectSettings": { "Name": "unittests_reactive_extenstions", "Version": { - "Major": "0", - "Minor": "0", - "Micro": "0" + "Major": "2023", + "Minor": "4", + "Micro": "dev170" }, "Author": "", "AuthorEmail": "", @@ -16,7 +16,7 @@ "LicenseName": "", "LicenseDescription": "", "Dependencies": [ - "cpl-core>=2023.4.0" + "cpl-core>=2023.4.dev170" ], "DevDependencies": [ "cpl-cli>=2023.4.0"