Renamed project
This commit is contained in:
31
src/cpl_core/dependency_injection/__init__.py
Normal file
31
src/cpl_core/dependency_injection/__init__.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
sh_cpl-core sh-edraft Common Python library
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
sh-edraft Common Python library
|
||||
|
||||
:copyright: (c) 2020 - 2021 sh-edraft.de
|
||||
:license: MIT, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
__title__ = 'cpl_core.dependency_injection'
|
||||
__author__ = 'Sven Heidemann'
|
||||
__license__ = 'MIT'
|
||||
__copyright__ = 'Copyright (c) 2020 - 2021 sh-edraft.de'
|
||||
__version__ = '2021.10.6'
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
# imports:
|
||||
from .service_collection import ServiceCollection
|
||||
from .service_collection_abc import ServiceCollectionABC
|
||||
from .service_descriptor import ServiceDescriptor
|
||||
from .service_lifetime_enum import ServiceLifetimeEnum
|
||||
from .service_provider import ServiceProvider
|
||||
from .service_provider_abc import ServiceProviderABC
|
||||
|
||||
VersionInfo = namedtuple('VersionInfo', 'major minor micro')
|
||||
version_info = VersionInfo(major='2021', minor='10', micro='6')
|
71
src/cpl_core/dependency_injection/service_collection.py
Normal file
71
src/cpl_core/dependency_injection/service_collection.py
Normal file
@@ -0,0 +1,71 @@
|
||||
from typing import Union, Type, Callable, Optional
|
||||
|
||||
from cpl.configuration.configuration_abc import ConfigurationABC
|
||||
from cpl.database.database_settings import DatabaseSettings
|
||||
from cpl.database.context.database_context_abc import DatabaseContextABC
|
||||
from cpl.dependency_injection.service_provider_abc import ServiceProviderABC
|
||||
from cpl.dependency_injection.service_collection_abc import ServiceCollectionABC
|
||||
from cpl.dependency_injection.service_descriptor import ServiceDescriptor
|
||||
from cpl.dependency_injection.service_lifetime_enum import ServiceLifetimeEnum
|
||||
from cpl.dependency_injection.service_provider import ServiceProvider
|
||||
from cpl.logging.logger_service import Logger
|
||||
from cpl.logging.logger_abc import LoggerABC
|
||||
from cpl.utils.credential_manager import CredentialManager
|
||||
|
||||
|
||||
class ServiceCollection(ServiceCollectionABC):
|
||||
r"""Representation of the collection of services"""
|
||||
|
||||
def __init__(self, config: ConfigurationABC):
|
||||
ServiceCollectionABC.__init__(self)
|
||||
self._configuration: ConfigurationABC = config
|
||||
|
||||
self._database_context: Optional[DatabaseContextABC] = None
|
||||
self._service_descriptors: list[ServiceDescriptor] = []
|
||||
|
||||
def _add_descriptor(self, service: Union[type, object], lifetime: ServiceLifetimeEnum):
|
||||
found = False
|
||||
for descriptor in self._service_descriptors:
|
||||
if isinstance(service, descriptor.service_type):
|
||||
found = True
|
||||
|
||||
if found:
|
||||
service_type = service
|
||||
if not isinstance(service, type):
|
||||
service_type = type(service)
|
||||
|
||||
raise Exception(f'Service of type {service_type} already exists')
|
||||
|
||||
self._service_descriptors.append(ServiceDescriptor(service, lifetime))
|
||||
|
||||
def add_db_context(self, db_context_type: Type[DatabaseContextABC], db_settings: DatabaseSettings):
|
||||
self._database_context = db_context_type(db_settings)
|
||||
self._database_context.connect(CredentialManager.build_string(db_settings.connection_string, db_settings.credentials))
|
||||
|
||||
def add_logging(self):
|
||||
self.add_singleton(LoggerABC, Logger)
|
||||
|
||||
def add_singleton(self, service_type: Union[type, object], service: Union[type, object] = None):
|
||||
impl = None
|
||||
if service is not None:
|
||||
if isinstance(service, type):
|
||||
impl = self.build_service_provider().build_service(service)
|
||||
|
||||
self._add_descriptor(impl, ServiceLifetimeEnum.singleton)
|
||||
else:
|
||||
if isinstance(service_type, type):
|
||||
impl = self.build_service_provider().build_service(service_type)
|
||||
|
||||
self._add_descriptor(impl, ServiceLifetimeEnum.singleton)
|
||||
|
||||
def add_scoped(self, service_type: Type, service: Callable = None):
|
||||
raise Exception('Not implemented')
|
||||
|
||||
def add_transient(self, service_type: Union[type], service: Union[type] = None):
|
||||
if service is not None:
|
||||
self._add_descriptor(service, ServiceLifetimeEnum.transient)
|
||||
else:
|
||||
self._add_descriptor(service_type, ServiceLifetimeEnum.transient)
|
||||
|
||||
def build_service_provider(self) -> ServiceProviderABC:
|
||||
return ServiceProvider(self._service_descriptors, self._configuration, self._database_context)
|
82
src/cpl_core/dependency_injection/service_collection_abc.py
Normal file
82
src/cpl_core/dependency_injection/service_collection_abc.py
Normal file
@@ -0,0 +1,82 @@
|
||||
from abc import abstractmethod, ABC
|
||||
from collections import Callable
|
||||
from typing import Type
|
||||
|
||||
from cpl.database.database_settings import DatabaseSettings
|
||||
from cpl.database.context.database_context_abc import DatabaseContextABC
|
||||
from cpl.dependency_injection.service_provider_abc import ServiceProviderABC
|
||||
|
||||
|
||||
class ServiceCollectionABC(ABC):
|
||||
r"""ABC for the class :class:`cpl.dependency_injection.service_collection.ServiceCollection`"""
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def add_db_context(self, db_context: Type[DatabaseContextABC], db_settings: DatabaseSettings):
|
||||
r"""Adds database context
|
||||
|
||||
Parameter
|
||||
---------
|
||||
db_context: Type[:class:`cpl.database.context.database_context_abc.DatabaseContextABC`]
|
||||
Database context
|
||||
db_settings: :class:`cpl.database.database_settings.DatabaseSettings`
|
||||
Database settings
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def add_logging(self):
|
||||
r"""Adds the CPL internal logger"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def add_transient(self, service_type: Type, service: Callable = None):
|
||||
r"""Adds a service with transient lifetime
|
||||
|
||||
Parameter
|
||||
---------
|
||||
service_type: :class:`Type`
|
||||
Type of the service
|
||||
service: :class:`Callable`
|
||||
Object of the service
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def add_scoped(self, service_type: Type, service: Callable = None):
|
||||
r"""Adds a service with scoped lifetime
|
||||
|
||||
Parameter
|
||||
---------
|
||||
service_type: :class:`Type`
|
||||
Type of the service
|
||||
service: :class:`Callable`
|
||||
Object of the service
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def add_singleton(self, service_type: Type, service: Callable = None):
|
||||
r"""Adds a service with singleton lifetime
|
||||
|
||||
Parameter
|
||||
---------
|
||||
service_type: :class:`Type`
|
||||
Type of the service
|
||||
service: :class:`Callable`
|
||||
Object of the service
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def build_service_provider(self) -> ServiceProviderABC:
|
||||
r"""Creates instance of the service provider
|
||||
|
||||
Returns
|
||||
-------
|
||||
Object of type :class:`cpl.dependency_injection.service_provider_abc.ServiceProviderABC`
|
||||
"""
|
||||
pass
|
42
src/cpl_core/dependency_injection/service_descriptor.py
Normal file
42
src/cpl_core/dependency_injection/service_descriptor.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from typing import Union, Optional
|
||||
|
||||
from cpl.dependency_injection.service_lifetime_enum import ServiceLifetimeEnum
|
||||
|
||||
|
||||
class ServiceDescriptor:
|
||||
r"""Descriptor of a service
|
||||
|
||||
Parameter
|
||||
---------
|
||||
implementation: Union[:class:`type`, Optional[:class:`object`]]
|
||||
Object or type of service
|
||||
lifetime: :class:`cpl.dependency_injection.service_lifetime_enum.ServiceLifetimeEnum`
|
||||
Lifetime of the service
|
||||
"""
|
||||
|
||||
def __init__(self, implementation: Union[type, Optional[object]], lifetime: ServiceLifetimeEnum):
|
||||
|
||||
self._service_type = implementation
|
||||
self._implementation = implementation
|
||||
self._lifetime = lifetime
|
||||
|
||||
if not isinstance(implementation, type):
|
||||
self._service_type = type(implementation)
|
||||
else:
|
||||
self._implementation = None
|
||||
|
||||
@property
|
||||
def service_type(self) -> type:
|
||||
return self._service_type
|
||||
|
||||
@property
|
||||
def implementation(self) -> Union[type, Optional[object]]:
|
||||
return self._implementation
|
||||
|
||||
@implementation.setter
|
||||
def implementation(self, implementation: Union[type, Optional[object]]):
|
||||
self._implementation = implementation
|
||||
|
||||
@property
|
||||
def lifetime(self) -> ServiceLifetimeEnum:
|
||||
return self._lifetime
|
@@ -0,0 +1,8 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class ServiceLifetimeEnum(Enum):
|
||||
|
||||
singleton = 0
|
||||
scoped = 1 # not supported yet
|
||||
transient = 2
|
101
src/cpl_core/dependency_injection/service_provider.py
Normal file
101
src/cpl_core/dependency_injection/service_provider.py
Normal file
@@ -0,0 +1,101 @@
|
||||
from collections import Callable
|
||||
from inspect import signature, Parameter
|
||||
from typing import Optional
|
||||
|
||||
from cpl.configuration.configuration_abc import ConfigurationABC
|
||||
from cpl.configuration.configuration_model_abc import ConfigurationModelABC
|
||||
from cpl.database.context.database_context_abc import DatabaseContextABC
|
||||
from cpl.dependency_injection.service_provider_abc import ServiceProviderABC
|
||||
from cpl.dependency_injection.service_descriptor import ServiceDescriptor
|
||||
from cpl.dependency_injection.service_lifetime_enum import ServiceLifetimeEnum
|
||||
from cpl.environment.application_environment_abc import ApplicationEnvironmentABC
|
||||
|
||||
|
||||
class ServiceProvider(ServiceProviderABC):
|
||||
r"""Provider for the services
|
||||
|
||||
Parameter
|
||||
---------
|
||||
service_descriptors: list[:class:`cpl.dependency_injection.service_descriptor.ServiceDescriptor`]
|
||||
Descriptor of the service
|
||||
config: :class:`cpl.configuration.configuration_abc.ConfigurationABC`
|
||||
CPL Configuration
|
||||
db_context: Optional[:class:`cpl.database.context.database_context_abc.DatabaseContextABC`]
|
||||
Database representation
|
||||
"""
|
||||
|
||||
def __init__(self, service_descriptors: list[ServiceDescriptor], config: ConfigurationABC, db_context: Optional[DatabaseContextABC]):
|
||||
ServiceProviderABC.__init__(self)
|
||||
|
||||
self._service_descriptors: list[ServiceDescriptor] = service_descriptors
|
||||
self._configuration: ConfigurationABC = config
|
||||
self._database_context = db_context
|
||||
|
||||
def _find_service(self, service_type: type) -> [ServiceDescriptor]:
|
||||
for descriptor in self._service_descriptors:
|
||||
if descriptor.service_type == service_type or issubclass(descriptor.service_type, service_type):
|
||||
return descriptor
|
||||
|
||||
return None
|
||||
|
||||
def _get_service(self, parameter: Parameter) -> object:
|
||||
for descriptor in self._service_descriptors:
|
||||
if descriptor.service_type == parameter.annotation or issubclass(descriptor.service_type, parameter.annotation):
|
||||
if descriptor.implementation is not None:
|
||||
return descriptor.implementation
|
||||
|
||||
implementation = self.build_service(descriptor.service_type)
|
||||
if descriptor.lifetime == ServiceLifetimeEnum.singleton:
|
||||
descriptor.implementation = implementation
|
||||
|
||||
return implementation
|
||||
|
||||
def build_service(self, service_type: type) -> object:
|
||||
for descriptor in self._service_descriptors:
|
||||
if descriptor.service_type == service_type or issubclass(descriptor.service_type, service_type):
|
||||
if descriptor.implementation is not None:
|
||||
service_type = type(descriptor.implementation)
|
||||
else:
|
||||
service_type = descriptor.service_type
|
||||
|
||||
break
|
||||
|
||||
sig = signature(service_type.__init__)
|
||||
params = []
|
||||
for param in sig.parameters.items():
|
||||
parameter = param[1]
|
||||
if parameter.name != 'self' and parameter.annotation != Parameter.empty:
|
||||
if issubclass(parameter.annotation, ServiceProviderABC):
|
||||
params.append(self)
|
||||
|
||||
elif issubclass(parameter.annotation, ApplicationEnvironmentABC):
|
||||
params.append(self._configuration.environment)
|
||||
|
||||
elif issubclass(parameter.annotation, DatabaseContextABC):
|
||||
params.append(self._database_context)
|
||||
|
||||
elif issubclass(parameter.annotation, ConfigurationModelABC):
|
||||
params.append(self._configuration.get_configuration(parameter.annotation))
|
||||
|
||||
elif issubclass(parameter.annotation, ConfigurationABC):
|
||||
params.append(self._configuration)
|
||||
|
||||
else:
|
||||
params.append(self._get_service(parameter))
|
||||
|
||||
return service_type(*params)
|
||||
|
||||
def get_service(self, service_type: type) -> Optional[Callable[object]]:
|
||||
result = self._find_service(service_type)
|
||||
|
||||
if result is None:
|
||||
return None
|
||||
|
||||
if result.implementation is not None:
|
||||
return result.implementation
|
||||
|
||||
implementation = self.build_service(service_type)
|
||||
if result.lifetime == ServiceLifetimeEnum.singleton:
|
||||
result.implementation = implementation
|
||||
|
||||
return implementation
|
41
src/cpl_core/dependency_injection/service_provider_abc.py
Normal file
41
src/cpl_core/dependency_injection/service_provider_abc.py
Normal file
@@ -0,0 +1,41 @@
|
||||
from abc import abstractmethod, ABC
|
||||
from collections import Callable
|
||||
from typing import Type, Optional
|
||||
|
||||
|
||||
class ServiceProviderABC(ABC):
|
||||
r"""ABC for the class :class:`cpl.dependency_injection.service_provider.ServiceProvider`"""
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def build_service(self, service_type: Type) -> object:
|
||||
r"""Creates instance of given type
|
||||
|
||||
Parameter
|
||||
---------
|
||||
instance_type: :class:`Type`
|
||||
The type of the searched instance
|
||||
|
||||
Returns
|
||||
-------
|
||||
Object of the given type
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_service(self, instance_type: Type) -> Optional[Callable[object]]:
|
||||
r"""Returns instance of given type
|
||||
|
||||
Parameter
|
||||
---------
|
||||
instance_type: :class:`Type`
|
||||
The type of the searched instance
|
||||
|
||||
Returns
|
||||
-------
|
||||
Object of type Optional[Callable[:class:`object`]]
|
||||
"""
|
||||
pass
|
Reference in New Issue
Block a user