diff --git a/example/general/src/application.py b/example/general/src/application.py index 5447c054..8f9b44b7 100644 --- a/example/general/src/application.py +++ b/example/general/src/application.py @@ -1,3 +1,4 @@ +import asyncio import time from cpl.application.abc import ApplicationABC @@ -36,7 +37,7 @@ class Application(ApplicationABC): def _wait(time_ms: int): time.sleep(time_ms) - def main(self): + async def main(self): self._logger.debug(f"Host: {Environment.get_host_name()}") self._logger.debug(f"Environment: {Environment.get_environment()}") Console.write_line(List(range(0, 10)).select(lambda x: f"x={x}").to_list()) @@ -76,7 +77,7 @@ class Application(ApplicationABC): # self.test_send_mail() x = 0 - while x < 5: + while x < 500: Console.write_line("Running...") x += 1 - time.sleep(5) + await asyncio.sleep(5) diff --git a/example/general/src/hosted_service.py b/example/general/src/hosted_service.py index 9ec96afc..f2fbf762 100644 --- a/example/general/src/hosted_service.py +++ b/example/general/src/hosted_service.py @@ -1,7 +1,9 @@ import asyncio -import time +from datetime import datetime from cpl.core.console import Console +from cpl.core.time.cron import Cron +from cpl.dependency.hosted.cronjob import CronjobABC from cpl.dependency.hosted.hosted_service import HostedService @@ -17,4 +19,12 @@ class Hosted(HostedService): async def stop(self): Console.write_line("Hosted Service Stopped") - self._stopped = True \ No newline at end of file + self._stopped = True + + +class MyCronJob(CronjobABC): + def __init__(self): + CronjobABC.__init__(self, Cron("*/1 * * * *")) # Every minute + + async def loop(self): + Console.write_line(f"[{datetime.now()}] Hello from Cronjob!") diff --git a/example/general/src/startup.py b/example/general/src/startup.py index 1e6271c2..c5d80e40 100644 --- a/example/general/src/startup.py +++ b/example/general/src/startup.py @@ -1,11 +1,10 @@ -from cpl import mail from cpl.application.abc import StartupABC from cpl.core.configuration import Configuration from cpl.core.environment import Environment from cpl.core.pipes import IPAddressPipe from cpl.dependency import ServiceCollection from cpl.mail.mail_module import MailModule -from hosted_service import Hosted +from hosted_service import Hosted, MyCronJob from scoped_service import ScopedService from test_service import TestService @@ -26,3 +25,4 @@ class Startup(StartupABC): services.add_singleton(TestService) services.add_scoped(ScopedService) services.add_hosted_service(Hosted) + services.add_hosted_service(MyCronJob) diff --git a/src/cpl-application/cpl/application/host.py b/src/cpl-application/cpl/application/host.py index 80dae147..f1309beb 100644 --- a/src/cpl-application/cpl/application/host.py +++ b/src/cpl-application/cpl/application/host.py @@ -57,14 +57,9 @@ class Host: async def runner(): try: if asyncio.iscoroutinefunction(func): - app_task = asyncio.create_task(func(*args, **kwargs)) + await func(*args, **kwargs) else: - app_task = cls.get_loop().run_in_executor(None, func, *args, **kwargs) - - await asyncio.wait( - [app_task, *cls._tasks.values()], - return_when=asyncio.FIRST_COMPLETED, - ) + func(*args, **kwargs) except (KeyboardInterrupt, asyncio.CancelledError): pass finally: diff --git a/src/cpl-core/cpl/core/time/__init__.py b/src/cpl-core/cpl/core/time/__init__.py index ff9dad0b..5ff10935 100644 --- a/src/cpl-core/cpl/core/time/__init__.py +++ b/src/cpl-core/cpl/core/time/__init__.py @@ -1,2 +1,2 @@ from .time_format_settings import TimeFormatSettings -from .time_format_settings_names_enum import TimeFormatSettingsNamesEnum +from .cron import Cron diff --git a/src/cpl-core/cpl/core/time/cron.py b/src/cpl-core/cpl/core/time/cron.py new file mode 100644 index 00000000..0f49de5c --- /dev/null +++ b/src/cpl-core/cpl/core/time/cron.py @@ -0,0 +1,13 @@ +from datetime import datetime + +import croniter + + +class Cron: + def __init__(self, cron_expression: str, start_time: datetime = None): + self._cron_expression = cron_expression + self._start_time = start_time or datetime.now() + self._iter = croniter.croniter(cron_expression, self._start_time) + + def next(self) -> datetime: + return self._iter.get_next(datetime) diff --git a/src/cpl-core/cpl/core/time/time_format_settings.py b/src/cpl-core/cpl/core/time/time_format_settings.py index 7c9cded0..24c5f81f 100644 --- a/src/cpl-core/cpl/core/time/time_format_settings.py +++ b/src/cpl-core/cpl/core/time/time_format_settings.py @@ -13,7 +13,7 @@ class TimeFormatSettings(ConfigurationModelABC): date_time_format: str = None, date_time_log_format: str = None, ): - ConfigurationModelABC.__init__(self) + ConfigurationModelABC.__init__(self, readonly=False) self._date_format: Optional[str] = date_format self._time_format: Optional[str] = time_format self._date_time_format: Optional[str] = date_time_format diff --git a/src/cpl-core/cpl/core/time/time_format_settings_names_enum.py b/src/cpl-core/cpl/core/time/time_format_settings_names_enum.py deleted file mode 100644 index 33a7c4f1..00000000 --- a/src/cpl-core/cpl/core/time/time_format_settings_names_enum.py +++ /dev/null @@ -1,8 +0,0 @@ -from enum import Enum - - -class TimeFormatSettingsNamesEnum(Enum): - date_format = "DateFormat" - time_format = "TimeFormat" - date_time_format = "DateTimeFormat" - date_time_log_format = "DateTimeLogFormat" diff --git a/src/cpl-core/requirements.txt b/src/cpl-core/requirements.txt index 8b07c2b2..a0bd7805 100644 --- a/src/cpl-core/requirements.txt +++ b/src/cpl-core/requirements.txt @@ -3,3 +3,4 @@ colorama==0.4.6 tabulate==0.9.0 termcolor==3.1.0 pynput==1.8.1 +croniter==6.0.0 \ No newline at end of file diff --git a/src/cpl-dependency/cpl/dependency/hosted/__init__.py b/src/cpl-dependency/cpl/dependency/hosted/__init__.py index 369503e1..1422b030 100644 --- a/src/cpl-dependency/cpl/dependency/hosted/__init__.py +++ b/src/cpl-dependency/cpl/dependency/hosted/__init__.py @@ -1,2 +1,3 @@ from .hosted_service import HostedService from .startup_task import StartupTask +from .cronjob import CronjobABC diff --git a/src/cpl-dependency/cpl/dependency/hosted/cronjob.py b/src/cpl-dependency/cpl/dependency/hosted/cronjob.py new file mode 100644 index 00000000..84926f97 --- /dev/null +++ b/src/cpl-dependency/cpl/dependency/hosted/cronjob.py @@ -0,0 +1,40 @@ +import asyncio +from abc import ABC, abstractmethod +from datetime import datetime + +from cpl.core.time.cron import Cron +from cpl.dependency.hosted import HostedService + + +class CronjobABC(HostedService, ABC): + def __init__(self, cron: Cron): + self._cron = cron + self._task: asyncio.Task | None = None + self._running = False + + async def start(self): + self._running = True + self._task = asyncio.create_task(self._run_loop()) + + async def stop(self): + self._running = False + if self._task: + self._task.cancel() + try: + await self._task + except asyncio.CancelledError: + pass + + async def _run_loop(self): + while self._running: + next_run = self._cron.next() + now = datetime.now() + delay = (next_run - now).total_seconds() + if delay > 0: + await asyncio.sleep(delay) + if not self._running: + break + await self.loop() + + @abstractmethod + async def loop(self): ...