WIP: dev into master #184

Draft
edraft wants to merge 121 commits from dev into master
11 changed files with 115 additions and 18 deletions
Showing only changes of commit 0ca5e5757a - Show all commits

View File

@@ -1,13 +1,12 @@
import time import time
from typing import Optional
from cpl.application.abc import ApplicationABC from cpl.application.abc import ApplicationABC
from cpl.core.configuration import Configuration from cpl.core.configuration import Configuration
from cpl.core.console import Console from cpl.core.console import Console
from cpl.dependency import ServiceProvider
from cpl.core.environment import Environment from cpl.core.environment import Environment
from cpl.core.log import LoggerABC from cpl.core.log import LoggerABC
from cpl.core.pipes import IPAddressPipe from cpl.core.pipes import IPAddressPipe
from cpl.dependency import ServiceProvider
from cpl.mail import EMail, EMailClientABC from cpl.mail import EMail, EMailClientABC
from cpl.query import List from cpl.query import List
from scoped_service import ScopedService from scoped_service import ScopedService
@@ -74,3 +73,9 @@ class Application(ApplicationABC):
test_settings1 = Configuration.get(TestSettings) test_settings1 = Configuration.get(TestSettings)
Console.write_line(test_settings1.value) Console.write_line(test_settings1.value)
# self.test_send_mail() # self.test_send_mail()
x = 0
while x < 5:
Console.write_line("Running...")
x += 1
time.sleep(5)

View File

@@ -0,0 +1,20 @@
import asyncio
import time
from cpl.core.console import Console
from cpl.dependency.hosted.hosted_service import HostedService
class Hosted(HostedService):
def __init__(self):
self._stopped = False
async def start(self):
Console.write_line("Hosted Service Started")
while not self._stopped:
Console.write_line("Hosted Service Running")
await asyncio.sleep(5)
async def stop(self):
Console.write_line("Hosted Service Stopped")
self._stopped = True

View File

@@ -4,6 +4,7 @@ from cpl.core.configuration import Configuration
from cpl.core.environment import Environment from cpl.core.environment import Environment
from cpl.core.pipes import IPAddressPipe from cpl.core.pipes import IPAddressPipe
from cpl.dependency import ServiceCollection from cpl.dependency import ServiceCollection
from example.custom.general.src.hosted_service import Hosted
from scoped_service import ScopedService from scoped_service import ScopedService
from test_service import TestService from test_service import TestService
@@ -23,3 +24,4 @@ class Startup(StartupABC):
services.add_transient(IPAddressPipe) services.add_transient(IPAddressPipe)
services.add_singleton(TestService) services.add_singleton(TestService)
services.add_scoped(ScopedService) services.add_scoped(ScopedService)
services.add_hosted_service(Hosted)

View File

@@ -1 +1,2 @@
from .application_builder import ApplicationBuilder from .application_builder import ApplicationBuilder
from .host import Host

View File

@@ -84,7 +84,7 @@ class ApplicationABC(ABC):
Called by custom Application.main Called by custom Application.main
""" """
try: try:
Host.run(self.main) Host.run_app(self.main)
except KeyboardInterrupt: except KeyboardInterrupt:
pass pass

View File

@@ -6,26 +6,77 @@ from cpl.dependency.hosted.startup_task import StartupTask
class Host: class Host:
_loop = asyncio.get_event_loop() _loop: asyncio.AbstractEventLoop | None = None
_tasks: dict = {}
@classmethod @classmethod
def get_loop(cls): def get_loop(cls) -> asyncio.AbstractEventLoop:
if cls._loop is None:
cls._loop = asyncio.new_event_loop()
asyncio.set_event_loop(cls._loop)
return cls._loop return cls._loop
@classmethod @classmethod
def run_start_tasks(cls): def run_start_tasks(cls):
provider = get_provider() provider = get_provider()
tasks = provider.get_services(StartupTask) tasks = provider.get_services(StartupTask)
loop = cls.get_loop()
for task in tasks: for task in tasks:
if asyncio.iscoroutinefunction(task.run): if asyncio.iscoroutinefunction(task.run):
cls._loop.run_until_complete(task.run()) loop.run_until_complete(task.run())
else: else:
task.run() task.run()
@classmethod @classmethod
def run(cls, func: Callable, *args, **kwargs): def run_hosted_services(cls):
cls.run_start_tasks() provider = get_provider()
services = provider.get_hosted_services()
loop = cls.get_loop()
for service in services:
if asyncio.iscoroutinefunction(service.start):
cls._tasks[service] = loop.create_task(service.start())
@classmethod
async def _stop_all(cls):
for service in cls._tasks.keys():
if asyncio.iscoroutinefunction(service.stop):
await service.stop()
for task in cls._tasks.values():
task.cancel()
cls._tasks.clear()
@classmethod
def run_app(cls, func: Callable, *args, **kwargs):
loop = cls.get_loop()
cls.run_start_tasks()
cls.run_hosted_services()
async def runner():
try:
if asyncio.iscoroutinefunction(func):
app_task = asyncio.create_task(func(*args, **kwargs))
else:
loop = asyncio.get_running_loop()
app_task = loop.run_in_executor(None, func, *args, **kwargs)
await asyncio.wait(
[app_task, *cls._tasks.values()],
return_when=asyncio.FIRST_COMPLETED,
)
except (KeyboardInterrupt, asyncio.CancelledError):
pass
finally:
await cls._stop_all()
loop.run_until_complete(runner())
@classmethod
def run(cls, func: Callable, *args, **kwargs):
if asyncio.iscoroutinefunction(func): if asyncio.iscoroutinefunction(func):
return cls._loop.run_until_complete(func(*args, **kwargs)) return cls._loop.run_until_complete(func(*args, **kwargs))

View File

@@ -0,0 +1,2 @@
from .hosted_service import HostedService
from .startup_task import StartupTask

View File

@@ -0,0 +1,9 @@
from abc import ABC, abstractmethod
class HostedService(ABC):
@abstractmethod
async def start(self): ...
@abstractmethod
async def stop(self): ...

View File

@@ -66,6 +66,10 @@ class ServiceCollection:
self.add_singleton(StartupTask, task) self.add_singleton(StartupTask, task)
return self return self
def add_hosted_service(self, service_type: T, service: Service = None) -> Self:
self._add_descriptor_by_lifetime(service_type, ServiceLifetimeEnum.hosted, service)
return self
def build(self) -> ServiceProvider: def build(self) -> ServiceProvider:
sp = ServiceProvider(self._service_descriptors) sp = ServiceProvider(self._service_descriptors)
return sp return sp

View File

@@ -5,3 +5,4 @@ class ServiceLifetimeEnum(Enum):
singleton = auto() singleton = auto()
scoped = auto() scoped = auto()
transient = auto() transient = auto()
hosted = auto()

View File

@@ -7,7 +7,7 @@ from typing import Optional, Type
from cpl.core.configuration import Configuration from cpl.core.configuration import Configuration
from cpl.core.configuration.configuration_model_abc import ConfigurationModelABC from cpl.core.configuration.configuration_model_abc import ConfigurationModelABC
from cpl.core.environment import Environment from cpl.core.environment import Environment
from cpl.core.typing import T, R, Source from cpl.core.typing import T, Source
from cpl.dependency import use_provider from cpl.dependency import use_provider
from cpl.dependency.service_descriptor import ServiceDescriptor from cpl.dependency.service_descriptor import ServiceDescriptor
from cpl.dependency.service_lifetime import ServiceLifetimeEnum from cpl.dependency.service_lifetime import ServiceLifetimeEnum
@@ -54,9 +54,7 @@ class ServiceProvider:
return implementation return implementation
# raise Exception(f'Service {parameter.annotation} not found') def _get_services(self, t: type, *args, service_type: type = None, **kwargs) -> list[Optional[object]]:
def _get_services(self, t: type, service_type: type = None, **kwargs) -> list[Optional[object]]:
implementations = [] implementations = []
for descriptor in self._service_descriptors: for descriptor in self._service_descriptors:
if descriptor.service_type == t or issubclass(descriptor.service_type, t): if descriptor.service_type == t or issubclass(descriptor.service_type, t):
@@ -65,7 +63,7 @@ class ServiceProvider:
continue continue
implementation = self._build_service( implementation = self._build_service(
descriptor.service_type, origin_service_type=service_type, **kwargs descriptor.service_type, *args, origin_service_type=service_type, **kwargs
) )
if descriptor.lifetime in (ServiceLifetimeEnum.singleton, ServiceLifetimeEnum.scoped): if descriptor.lifetime in (ServiceLifetimeEnum.singleton, ServiceLifetimeEnum.scoped):
descriptor.implementation = implementation descriptor.implementation = implementation
@@ -74,7 +72,7 @@ class ServiceProvider:
return implementations return implementations
def _build_by_signature(self, sig: Signature, origin_service_type: type = None) -> list[R]: def _build_by_signature(self, sig: Signature, origin_service_type: type = None) -> list[T]:
params = [] params = []
for param in sig.parameters.items(): for param in sig.parameters.items():
parameter = param[1] parameter = param[1]
@@ -132,7 +130,11 @@ class ServiceProvider:
with use_provider(scoped_provider): with use_provider(scoped_provider):
yield scoped_provider yield scoped_provider
def get_service(self, service_type: T, *args, **kwargs) -> Optional[R]: def get_hosted_services(self) -> list[Optional[T]]:
hosted_services = [self.get_service(d.service_type) for d in self._service_descriptors if d.lifetime == ServiceLifetimeEnum.hosted]
return hosted_services
def get_service(self, service_type: Type[T], *args, **kwargs) -> Optional[T]:
result = self._find_service(service_type) result = self._find_service(service_type)
if result is None: if result is None:
return None return None
@@ -155,11 +157,11 @@ class ServiceProvider:
return descriptor.service_type return descriptor.service_type
return None return None
def get_services(self, service_type: T, *args, **kwargs) -> list[Optional[R]]: def get_services(self, service_type: Type[T], *args, **kwargs) -> list[Optional[T]]:
implementations = [] implementations = []
if typing.get_origin(service_type) == list: if typing.get_origin(service_type) == list:
raise Exception(f"Invalid type {service_type}! Expected single type not list of type") raise Exception(f"Invalid type {service_type}! Expected single type not list of type")
implementations.extend(self._get_services(service_type)) implementations.extend(self._get_services(service_type, None, *args, **kwargs))
return implementations return implementations
def get_service_types(self, service_type: Type[T]) -> list[Type[T]]: def get_service_types(self, service_type: Type[T]) -> list[Type[T]]: