Added logic to handle scoped services

This commit is contained in:
Sven Heidemann 2021-11-14 11:56:42 +01:00
parent 7749d5a789
commit 98e343a108
18 changed files with 273 additions and 4 deletions

View File

@ -0,0 +1,18 @@
from cpl_core.console.console import Console
from cpl_core.dependency_injection.scope_abc import ScopeABC
from cpl_core.dependency_injection.service_provider_abc import ServiceProviderABC
class Scope(ScopeABC):
def __init__(self, service_provider: ServiceProviderABC):
self._service_provider = service_provider
self._service_provider.set_scope(self)
@property
def service_provider(self) -> ServiceProviderABC:
return self._service_provider
def dispose(self):
self._service_provider = None

View File

@ -0,0 +1,24 @@
from abc import ABC, abstractmethod
class ScopeABC(ABC):
r"""ABC for the class :class:`cpl_core.dependency_injection.scope.Scope`"""
def __init__(self):
pass
@property
@abstractmethod
def service_provider(self):
r"""Returns to service provider of scope
Returns
-------
Object of type :class:`cpl_core.dependency_injection.service_provider_abc.ServiceProviderABC`
"""
pass
@abstractmethod
def dispose():
r"""Sets service_provider to None
"""
pass

View File

@ -0,0 +1,19 @@
from cpl_core.dependency_injection.scope import Scope
from cpl_core.dependency_injection.scope_abc import ScopeABC
from cpl_core.dependency_injection.service_provider_abc import ServiceProviderABC
class ScopeBuilder:
r"""Class to build :class:`cpl_core.dependency_injection.scope.Scope`"""
def __init__(self, service_provider: ServiceProviderABC) -> None:
self._service_provider = service_provider
def build(self) -> ScopeABC:
r"""Returns scope
Returns
-------
Object of type :class:`cpl_core.dependency_injection.scope.Scope`
"""
return Scope(self._service_provider)

View File

@ -4,5 +4,5 @@ from enum import Enum
class ServiceLifetimeEnum(Enum): class ServiceLifetimeEnum(Enum):
singleton = 0 singleton = 0
scoped = 1 # not supported yet scoped = 1
transient = 2 transient = 2

View File

@ -1,10 +1,13 @@
from collections import Callable from collections import Callable
import copy
from inspect import signature, Parameter from inspect import signature, Parameter
from typing import Optional from typing import Optional
from cpl_core.configuration.configuration_abc import ConfigurationABC from cpl_core.configuration.configuration_abc import ConfigurationABC
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
from cpl_core.database.context.database_context_abc import DatabaseContextABC from cpl_core.database.context.database_context_abc import DatabaseContextABC
from cpl_core.dependency_injection.scope_abc import ScopeABC
from cpl_core.dependency_injection.scope_builder import ScopeBuilder
from cpl_core.dependency_injection.service_provider_abc import ServiceProviderABC from cpl_core.dependency_injection.service_provider_abc import ServiceProviderABC
from cpl_core.dependency_injection.service_descriptor import ServiceDescriptor from cpl_core.dependency_injection.service_descriptor import ServiceDescriptor
from cpl_core.dependency_injection.service_lifetime_enum import ServiceLifetimeEnum from cpl_core.dependency_injection.service_lifetime_enum import ServiceLifetimeEnum
@ -30,6 +33,7 @@ class ServiceProvider(ServiceProviderABC):
self._service_descriptors: list[ServiceDescriptor] = service_descriptors self._service_descriptors: list[ServiceDescriptor] = service_descriptors
self._configuration: ConfigurationABC = config self._configuration: ConfigurationABC = config
self._database_context = db_context self._database_context = db_context
self._scope: Optional[ScopeABC] = None
def _find_service(self, service_type: type) -> ServiceDescriptor: def _find_service(self, service_type: type) -> ServiceDescriptor:
for descriptor in self._service_descriptors: for descriptor in self._service_descriptors:
@ -84,8 +88,15 @@ class ServiceProvider(ServiceProviderABC):
params.append(self._get_service(parameter)) params.append(self._get_service(parameter))
return service_type(*params) return service_type(*params)
def set_scope(self, scope: ScopeABC):
self._scope = scope
def create_scope(self) -> ScopeABC:
sb = ScopeBuilder(ServiceProvider(copy.deepcopy(self._service_descriptors), self._configuration, self._database_context))
return sb.build()
def get_service(self, service_type: type) -> Optional[Callable]: def get_service(self, service_type: type) -> Optional[Callable[object]]:
result = self._find_service(service_type) result = self._find_service(service_type)
if result is None: if result is None:
@ -95,7 +106,7 @@ class ServiceProvider(ServiceProviderABC):
return result.implementation return result.implementation
implementation = self.build_service(service_type) implementation = self.build_service(service_type)
if result.lifetime == ServiceLifetimeEnum.singleton: if result.lifetime == ServiceLifetimeEnum.singleton or result.lifetime == ServiceLifetimeEnum.scoped and self._scope is not None:
result.implementation = implementation result.implementation = implementation
return implementation return implementation

View File

@ -2,6 +2,8 @@ from abc import abstractmethod, ABC
from collections import Callable from collections import Callable
from typing import Type, Optional from typing import Type, Optional
from cpl_core.dependency_injection.scope_abc import ScopeABC
class ServiceProviderABC(ABC): class ServiceProviderABC(ABC):
r"""ABC for the class :class:`cpl_core.dependency_injection.service_provider.ServiceProvider`""" r"""ABC for the class :class:`cpl_core.dependency_injection.service_provider.ServiceProvider`"""
@ -24,9 +26,30 @@ class ServiceProviderABC(ABC):
Object of the given type Object of the given type
""" """
pass pass
@abstractmethod
def set_scope(self, scope: ScopeABC):
r"""Sets the scope of service provider
Parameter
---------
scope :class:`cpl_core.dependency_injection.scope.Scope`
Service scope
"""
pass
@abstractmethod
def create_scope(self) -> ScopeABC:
r"""Creates a service scope
Returns
-------
Object of type :class:`cpl_core.dependency_injection.scope.Scope`
"""
pass
@abstractmethod @abstractmethod
def get_service(self, instance_type: Type) -> Optional[Callable]: def get_service(self, instance_type: Type) -> Optional[Callable[object]]:
r"""Returns instance of given type r"""Returns instance of given type
Parameter Parameter

View File

View File

View File

@ -0,0 +1,15 @@
{
"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/",
"Filename": "log_$start_time.log",
"ConsoleLogLevel": "ERROR",
"FileLogLevel": "WARN"
}
}

View File

@ -0,0 +1,9 @@
{
"WorkspaceSettings": {
"DefaultProject": "di",
"Projects": {
"di": "src/di/di.json"
},
"Scripts": {}
}
}

View File

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

View File

@ -0,0 +1,45 @@
from cpl_core.application import ApplicationABC
from cpl_core.configuration import ConfigurationABC
from cpl_core.console.console import Console
from cpl_core.dependency_injection import ServiceProviderABC
from cpl_core.dependency_injection.scope import Scope
from test_service_service import TestService
from di_tester_service import DITesterService
class Application(ApplicationABC):
def __init__(self, config: ConfigurationABC, services: ServiceProviderABC):
ApplicationABC.__init__(self, config, services)
def _part_of_scoped(self):
ts: TestService = self._services.get_service(TestService)
ts.run()
def configure(self):
pass
def main(self):
Console.write_line('Scope1')
scope1: Scope = self._services.create_scope()
ts: TestService = scope1.service_provider.get_service(TestService)
ts.run()
dit: DITesterService = scope1.service_provider.get_service(DITesterService)
dit.run()
t = scope1
b = t.service_provider
scope1.dispose()
Console.write_line('Disposed:')
ts1: TestService = scope1.service_provider.get_service(TestService)
ts1.run()
Console.write_line('Scope2')
scope2: Scope = self._services.create_scope()
ts: TestService = scope2.service_provider.get_service(TestService)
ts.run()
dit: DITesterService = scope2.service_provider.get_service(DITesterService)
dit.run()
Console.write_line('Global')
self._part_of_scoped()

View File

@ -0,0 +1,43 @@
{
"ProjectSettings": {
"Name": "di",
"Version": {
"Major": "0",
"Minor": "0",
"Micro": "0"
},
"Author": "",
"AuthorEmail": "",
"Description": "",
"LongDescription": "",
"URL": "",
"CopyrightDate": "",
"CopyrightName": "",
"LicenseName": "",
"LicenseDescription": "",
"Dependencies": [
"sh_cpl>=2021.10.0.post1"
],
"PythonVersion": ">=3.9.2",
"PythonPath": {
"linux": ""
},
"Classifiers": []
},
"BuildSettings": {
"ProjectType": "console",
"SourcePath": "",
"OutputPath": "../../dist",
"Main": "di.main",
"EntryPoint": "di",
"IncludePackageData": false,
"Included": [],
"Excluded": [
"*/__pycache__",
"*/logs",
"*/tests"
],
"PackageData": {},
"ProjectReferences": []
}
}

View File

@ -0,0 +1,12 @@
from cpl_core.console.console import Console
from test_service_service import TestService
class DITesterService:
def __init__(self, ts: TestService):
self._ts = ts
def run(self):
Console.write_line('DIT: ')
self._ts.run()

View File

@ -0,0 +1,14 @@
from cpl_core.application import ApplicationBuilder
from di.application import Application
from di.startup import Startup
def main():
app_builder = ApplicationBuilder(Application)
app_builder.use_startup(Startup)
app_builder.build().run()
if __name__ == '__main__':
main()

View File

@ -0,0 +1,21 @@
from cpl_core.application import StartupABC
from cpl_core.configuration import ConfigurationABC
from cpl_core.dependency_injection import ServiceProviderABC, ServiceCollectionABC
from cpl_core.environment import ApplicationEnvironment
from test_service_service import TestService
from di_tester_service import DITesterService
class Startup(StartupABC):
def __init__(self):
StartupABC.__init__(self)
def configure_configuration(self, configuration: ConfigurationABC, environment: ApplicationEnvironment) -> ConfigurationABC:
return configuration
def configure_services(self, services: ServiceCollectionABC, environment: ApplicationEnvironment) -> ServiceProviderABC:
services.add_scoped(TestService)
services.add_scoped(DITesterService)
return services.build_service_provider()

View File

@ -0,0 +1,13 @@
import string
from cpl_core.console.console import Console
from cpl_core.utils.string import String
class TestService:
def __init__(self):
self._name = String.random_string(string.ascii_lowercase, 8)
def run(self):
Console.write_line(f'Im {self._name}')

View File

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