Module dependencies as static var
Some checks failed
Test before pr merge / test-lint (pull_request) Failing after 6s
Build on push / prepare (push) Successful in 10s
Build on push / query (push) Successful in 19s
Build on push / core (push) Successful in 20s
Build on push / dependency (push) Successful in 17s
Build on push / application (push) Successful in 15s
Build on push / database (push) Successful in 16s
Build on push / mail (push) Successful in 18s
Build on push / translation (push) Successful in 22s
Build on push / auth (push) Successful in 18s
Build on push / api (push) Successful in 17s
Some checks failed
Test before pr merge / test-lint (pull_request) Failing after 6s
Build on push / prepare (push) Successful in 10s
Build on push / query (push) Successful in 19s
Build on push / core (push) Successful in 20s
Build on push / dependency (push) Successful in 17s
Build on push / application (push) Successful in 15s
Build on push / database (push) Successful in 16s
Build on push / mail (push) Successful in 18s
Build on push / translation (push) Successful in 22s
Build on push / auth (push) Successful in 18s
Build on push / api (push) Successful in 17s
This commit is contained in:
@@ -3,6 +3,7 @@ from starlette.responses import JSONResponse
|
||||
from cpl.api.api_module import ApiModule
|
||||
from cpl.api.application.web_app import WebApp
|
||||
from cpl.application.application_builder import ApplicationBuilder
|
||||
from cpl.auth import AuthModule
|
||||
from cpl.auth.permission.permissions import Permissions
|
||||
from cpl.auth.schema import AuthUser, Role
|
||||
from cpl.core.configuration import Configuration
|
||||
@@ -39,7 +40,13 @@ def main():
|
||||
app.with_authentication()
|
||||
app.with_authorization()
|
||||
|
||||
app.with_route(path="/route1", fn=lambda r: JSONResponse("route1"), method="GET", authentication=True, permissions=[Permissions.administrator])
|
||||
app.with_route(
|
||||
path="/route1",
|
||||
fn=lambda r: JSONResponse("route1"),
|
||||
method="GET",
|
||||
authentication=True,
|
||||
permissions=[Permissions.administrator],
|
||||
)
|
||||
app.with_routes_directory("routes")
|
||||
|
||||
provider = builder.service_provider
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
from cpl.auth.auth_module import AuthModule
|
||||
from cpl.auth.permission.permission_module import PermissionsModule
|
||||
from cpl.database.database_module import DatabaseModule
|
||||
from cpl.dependency.module import Module, TModule
|
||||
from cpl.dependency.module import Module
|
||||
from cpl.dependency.service_collection import ServiceCollection
|
||||
|
||||
|
||||
class ApiModule(Module):
|
||||
dependencies = []
|
||||
|
||||
@staticmethod
|
||||
def dependencies() -> list[TModule]:
|
||||
return [AuthModule, DatabaseModule, PermissionsModule]
|
||||
|
||||
@staticmethod
|
||||
def register(collection: "ServiceCollection"):
|
||||
def register(collection: ServiceCollection):
|
||||
from cpl.api.registry.policy import PolicyRegistry
|
||||
from cpl.api.registry.route import RouteRegistry
|
||||
|
||||
collection.add_module(DatabaseModule)
|
||||
|
||||
collection.add_module(AuthModule)
|
||||
collection.add_module(PermissionsModule)
|
||||
|
||||
|
||||
@@ -2,15 +2,15 @@ import os
|
||||
|
||||
from cpl.database.database_module import DatabaseModule
|
||||
from cpl.database.model.server_type import ServerType, ServerTypes
|
||||
from cpl.database.mysql.mysql_module import MySQLModule
|
||||
from cpl.database.postgres.postgres_module import PostgresModule
|
||||
from cpl.database.service.migration_service import MigrationService
|
||||
from cpl.dependency.module import Module, TModule
|
||||
from cpl.dependency.module import Module
|
||||
from cpl.dependency.service_collection import ServiceCollection
|
||||
|
||||
|
||||
class AuthModule(Module):
|
||||
@staticmethod
|
||||
def dependencies() -> list[TModule]:
|
||||
return [DatabaseModule]
|
||||
dependencies = [DatabaseModule, (MySQLModule, PostgresModule)]
|
||||
|
||||
@staticmethod
|
||||
def register(collection: ServiceCollection):
|
||||
|
||||
@@ -3,16 +3,13 @@ from cpl.auth.permission.permission_seeder import PermissionSeeder
|
||||
from cpl.auth.permission.permissions import Permissions
|
||||
from cpl.auth.permission.permissions_registry import PermissionsRegistry
|
||||
from cpl.database.abc.data_seeder_abc import DataSeederABC
|
||||
from cpl.dependency.module import Module, TModule
|
||||
from cpl.database.database_module import DatabaseModule
|
||||
from cpl.dependency.module import Module
|
||||
from cpl.dependency.service_collection import ServiceCollection
|
||||
|
||||
|
||||
class PermissionsModule(Module):
|
||||
@staticmethod
|
||||
def dependencies() -> list[TModule]:
|
||||
from cpl.database.database_module import DatabaseModule
|
||||
|
||||
return [DatabaseModule, AuthModule]
|
||||
dependencies = [DatabaseModule, AuthModule]
|
||||
|
||||
@staticmethod
|
||||
def register(collection: ServiceCollection):
|
||||
|
||||
@@ -16,7 +16,7 @@ def dependency_error(src: str, package_name: str, e: ImportError = None) -> None
|
||||
|
||||
|
||||
def module_dependency_error(src: str, module: str, e: ImportError = None) -> None:
|
||||
Console.error(f"'{module}' is required to use feature: {src}. Please initialize it with `add_module({module})`.")
|
||||
Console.error(f"'{module}' is required by '{src}'. Please initialize it with `add_module({module})`.")
|
||||
tb = traceback.format_exc()
|
||||
if not tb.startswith("NoneType: None"):
|
||||
Console.write_line("->", tb)
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
from cpl.core.errors import module_dependency_error
|
||||
from cpl.database.model.server_type import ServerType
|
||||
from cpl.dependency.module import Module, TModule
|
||||
from cpl.database.mysql.mysql_module import MySQLModule
|
||||
from cpl.database.postgres.postgres_module import PostgresModule
|
||||
from cpl.dependency.module import Module
|
||||
from cpl.dependency.service_collection import ServiceCollection
|
||||
|
||||
|
||||
class DatabaseModule(Module):
|
||||
@staticmethod
|
||||
def dependencies() -> list[TModule]:
|
||||
if not ServerType.has_server_type:
|
||||
module_dependency_error(__name__, "MySQLModule or PostgresModule")
|
||||
|
||||
return []
|
||||
dependencies = [(MySQLModule, PostgresModule)]
|
||||
|
||||
@staticmethod
|
||||
def register(collection: ServiceCollection):
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
from cpl.core.configuration.configuration import Configuration
|
||||
from cpl.database.model.server_type import ServerTypes, ServerType
|
||||
from cpl.dependency.module import Module, TModule
|
||||
from cpl.dependency.module import Module
|
||||
from cpl.dependency.service_collection import ServiceCollection
|
||||
|
||||
|
||||
class MySQLModule(Module):
|
||||
@staticmethod
|
||||
def dependencies() -> list[TModule]:
|
||||
return []
|
||||
dependencies = []
|
||||
|
||||
@staticmethod
|
||||
def register(collection: ServiceCollection):
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
from cpl.core.configuration.configuration import Configuration
|
||||
from cpl.database.database_module import DatabaseModule
|
||||
from cpl.database.model.server_type import ServerTypes, ServerType
|
||||
from cpl.dependency.module import Module, TModule
|
||||
from cpl.dependency.module import Module
|
||||
from cpl.dependency.service_collection import ServiceCollection
|
||||
|
||||
|
||||
class PostgresModule(Module):
|
||||
@staticmethod
|
||||
def dependencies() -> list[TModule]:
|
||||
return [DatabaseModule]
|
||||
dependencies = []
|
||||
|
||||
@staticmethod
|
||||
def register(collection: ServiceCollection):
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
from abc import abstractmethod, ABC
|
||||
from typing import Type
|
||||
|
||||
TModule = Type["Module"]
|
||||
from inspect import isclass
|
||||
|
||||
|
||||
class Module(ABC):
|
||||
__REQUIRED_VARS = ["dependencies"]
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def dependencies() -> list[TModule]: ...
|
||||
def __init_subclass__(cls):
|
||||
super().__init_subclass__()
|
||||
for var in cls.__REQUIRED_VARS:
|
||||
if not hasattr(cls, var):
|
||||
raise TypeError(f"Can't instantiate abstract class {cls.__name__} without {var} attribute")
|
||||
|
||||
if not isinstance(getattr(cls, var), list):
|
||||
raise TypeError(f"Can't instantiate abstract class {cls.__name__} with non-list {var} attribute")
|
||||
|
||||
for dep in getattr(cls, var):
|
||||
if not isinstance(dep, (list, tuple)) and not isclass(dep):
|
||||
raise TypeError(f"Can't instantiate abstract class {cls.__name__} with invalid dependency {dep}")
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from inspect import isclass
|
||||
from typing import Union, Type, Callable, Self
|
||||
from typing import Union, Callable, Self, Type
|
||||
|
||||
from cpl.core.errors import module_dependency_error
|
||||
from cpl.core.log.logger_abc import LoggerABC
|
||||
from cpl.core.typing import T, Service
|
||||
from cpl.core.utils.cache import Cache
|
||||
@@ -9,6 +10,7 @@ from cpl.dependency.module import Module
|
||||
from cpl.dependency.service_descriptor import ServiceDescriptor
|
||||
from cpl.dependency.service_lifetime import ServiceLifetimeEnum
|
||||
from cpl.dependency.service_provider import ServiceProvider
|
||||
from cpl.dependency.typing import TModule, TService, TStartupTask
|
||||
|
||||
|
||||
class ServiceCollection:
|
||||
@@ -16,11 +18,6 @@ class ServiceCollection:
|
||||
|
||||
_modules: dict[str, Callable] = {}
|
||||
|
||||
@classmethod
|
||||
def with_module(cls, func: Callable, name: str = None) -> type[Self]:
|
||||
# cls._modules[func.__name__ if name is None else name] = func
|
||||
return cls
|
||||
|
||||
def __init__(self):
|
||||
self._service_descriptors: list[ServiceDescriptor] = []
|
||||
self._loaded_modules: set[str] = set()
|
||||
@@ -29,6 +26,47 @@ class ServiceCollection:
|
||||
def loaded_modules(self) -> set[str]:
|
||||
return self._loaded_modules
|
||||
|
||||
def _check_dependency(self, module: TModule, dependency: TModule | TService, optional: bool = False) -> bool:
|
||||
if not issubclass(dependency, Module):
|
||||
found_services = [
|
||||
x
|
||||
for x in self._service_descriptors
|
||||
if x.service_type == dependency or x.base_type == dependency or isinstance(x.implementation, dependency)
|
||||
]
|
||||
|
||||
if len(found_services) > 0:
|
||||
return True
|
||||
|
||||
if optional:
|
||||
return False
|
||||
|
||||
module_dependency_error(module.__name__, dependency.__name__)
|
||||
|
||||
if dependency.__name__ not in self._loaded_modules:
|
||||
if optional:
|
||||
return False
|
||||
|
||||
module_dependency_error(module.__name__, dependency.__name__)
|
||||
|
||||
return True
|
||||
|
||||
def _check_dependencies(self, module: TModule):
|
||||
dependencies: list[TModule | Type] = getattr(module, "dependencies", [])
|
||||
for dependency in dependencies:
|
||||
if isinstance(dependency, (list, tuple)):
|
||||
deps_exists = [self._check_dependency(module, dep, optional=True) for dep in dependency]
|
||||
|
||||
if not any(deps_exists):
|
||||
if len(dependency) > 1:
|
||||
names = ", ".join([dep.__name__ for dep in dependency[:-1]]) + f" or {dependency[-1].__name__}"
|
||||
else:
|
||||
names = dependency[0].__name__
|
||||
|
||||
module_dependency_error(module.__name__, names)
|
||||
continue
|
||||
|
||||
self._check_dependency(module, dependency)
|
||||
|
||||
def _add_descriptor(self, service: Union[type, object], lifetime: ServiceLifetimeEnum, base_type: Callable = None):
|
||||
found = False
|
||||
for descriptor in self._service_descriptors:
|
||||
@@ -44,7 +82,9 @@ class ServiceCollection:
|
||||
|
||||
self._service_descriptors.append(ServiceDescriptor(service, lifetime, base_type))
|
||||
|
||||
def _add_descriptor_by_lifetime(self, service_type: Type, lifetime: ServiceLifetimeEnum, service: Callable = None):
|
||||
def _add_descriptor_by_lifetime(
|
||||
self, service_type: TService | T, lifetime: ServiceLifetimeEnum, service: Callable = None
|
||||
):
|
||||
if service is not None:
|
||||
self._add_descriptor(service, lifetime, service_type)
|
||||
else:
|
||||
@@ -52,19 +92,19 @@ class ServiceCollection:
|
||||
|
||||
return self
|
||||
|
||||
def add_singleton(self, service_type: T, service: Service = None) -> Self:
|
||||
def add_singleton(self, service_type: TService | T, service: Service = None) -> Self:
|
||||
self._add_descriptor_by_lifetime(service_type, ServiceLifetimeEnum.singleton, service)
|
||||
return self
|
||||
|
||||
def add_scoped(self, service_type: T, service: Service = None) -> Self:
|
||||
def add_scoped(self, service_type: TService | T, service: Service = None) -> Self:
|
||||
self._add_descriptor_by_lifetime(service_type, ServiceLifetimeEnum.scoped, service)
|
||||
return self
|
||||
|
||||
def add_transient(self, service_type: T, service: Service = None) -> Self:
|
||||
def add_transient(self, service_type: TService | T, service: Service = None) -> Self:
|
||||
self._add_descriptor_by_lifetime(service_type, ServiceLifetimeEnum.transient, service)
|
||||
return self
|
||||
|
||||
def add_startup_task(self, task: Type[StartupTask]) -> Self:
|
||||
def add_startup_task(self, task: TStartupTask) -> Self:
|
||||
self.add_singleton(StartupTask, task)
|
||||
return self
|
||||
|
||||
@@ -76,17 +116,15 @@ class ServiceCollection:
|
||||
sp = ServiceProvider(self._service_descriptors)
|
||||
return sp
|
||||
|
||||
def add_module(self, module: Type[Module]) -> Self:
|
||||
def add_module(self, module: TModule) -> Self:
|
||||
assert isclass(module), "Module must be a Module"
|
||||
assert issubclass(module, Module), f"Module must be subclass of {Module.__name__}"
|
||||
|
||||
name = module.__name__
|
||||
if module in self._modules:
|
||||
raise ValueError(f"Module {module} not found")
|
||||
raise ValueError(f"Module {name} is already registered")
|
||||
|
||||
for dependency in module.dependencies():
|
||||
if dependency.__name__ not in self._loaded_modules:
|
||||
self.add_module(dependency)
|
||||
self._check_dependencies(module)
|
||||
|
||||
module().register(self)
|
||||
|
||||
@@ -114,6 +152,6 @@ class ServiceCollection:
|
||||
self.add_transient(wrapper)
|
||||
return self
|
||||
|
||||
def add_cache(self, t: Type[T]):
|
||||
def add_cache(self, t: TService):
|
||||
self._service_descriptors.append(ServiceDescriptor(Cache(t=t), ServiceLifetimeEnum.singleton, Cache[t]))
|
||||
return self
|
||||
|
||||
9
src/cpl-dependency/cpl/dependency/typing.py
Normal file
9
src/cpl-dependency/cpl/dependency/typing.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from typing import Type
|
||||
|
||||
from cpl.core.typing import T
|
||||
from cpl.dependency.hosted import StartupTask
|
||||
from cpl.dependency.module import Module
|
||||
|
||||
TModule = Type[Module]
|
||||
TService = Type[T]
|
||||
TStartupTask = Type[StartupTask]
|
||||
@@ -1,21 +1,6 @@
|
||||
from cpl.dependency import ServiceCollection as _ServiceCollection
|
||||
from .abc.email_client_abc import EMailClientABC
|
||||
from .email_client import EMailClient
|
||||
from .email_client_settings import EMailClientSettings
|
||||
from .email_model import EMail
|
||||
from .mail_module import MailModule
|
||||
from .logger import MailLogger
|
||||
|
||||
|
||||
def add_mail(collection: _ServiceCollection):
|
||||
from cpl.core.console import Console
|
||||
from cpl.core.log import LoggerABC
|
||||
|
||||
try:
|
||||
collection.add_singleton(EMailClientABC, EMailClient)
|
||||
collection.add_transient(LoggerABC, MailLogger)
|
||||
except ImportError as e:
|
||||
Console.error("cpl-translation is not installed", str(e))
|
||||
|
||||
|
||||
_ServiceCollection.with_module(add_mail, __name__)
|
||||
from .mail_module import MailModule
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
from cpl.dependency import ServiceCollection
|
||||
from cpl.dependency.service_collection import ServiceCollection
|
||||
from cpl.dependency.module import Module, TModule
|
||||
|
||||
|
||||
class MailModule(Module):
|
||||
@staticmethod
|
||||
def dependencies() -> list[TModule]:
|
||||
return []
|
||||
dependencies = []
|
||||
|
||||
@staticmethod
|
||||
def register(collection: ServiceCollection):
|
||||
|
||||
@@ -1,23 +1,5 @@
|
||||
from cpl.dependency import ServiceCollection as _ServiceCollection
|
||||
from .translate_pipe import TranslatePipe
|
||||
from .translation_module import TranslationModule
|
||||
from .translation_service import TranslationService
|
||||
from .translation_service_abc import TranslationServiceABC
|
||||
from .translation_settings import TranslationSettings
|
||||
|
||||
|
||||
def add_translation(collection: _ServiceCollection):
|
||||
from cpl.core.console import Console
|
||||
from cpl.core.pipes import PipeABC
|
||||
from cpl.translation.translate_pipe import TranslatePipe
|
||||
from cpl.translation.translation_service import TranslationService
|
||||
from cpl.translation.translation_service_abc import TranslationServiceABC
|
||||
|
||||
try:
|
||||
collection.add_singleton(TranslationServiceABC, TranslationService)
|
||||
collection.add_transient(PipeABC, TranslatePipe)
|
||||
except ImportError as e:
|
||||
Console.error("cpl-translation is not installed", str(e))
|
||||
|
||||
|
||||
_ServiceCollection.with_module(add_translation, __name__)
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
from cpl.dependency import ServiceCollection
|
||||
from cpl.dependency.module import Module, TModule
|
||||
from cpl.dependency.service_collection import ServiceCollection
|
||||
from cpl.dependency.module import Module
|
||||
from cpl.translation.translation_service_abc import TranslationServiceABC
|
||||
|
||||
|
||||
class TranslationModule(Module):
|
||||
@staticmethod
|
||||
def dependencies() -> list[TModule]:
|
||||
return []
|
||||
dependencies = []
|
||||
|
||||
@staticmethod
|
||||
def register(collection: ServiceCollection):
|
||||
|
||||
Reference in New Issue
Block a user