Compare commits

..

1 Commits

Author SHA1 Message Date
2a927ebdd5 Added enumerable order & added array & removed collection
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 / core (push) Successful in 18s
Build on push / query (push) Successful in 19s
Build on push / dependency (push) Successful in 18s
Build on push / application (push) Successful in 16s
Build on push / database (push) Successful in 19s
Build on push / mail (push) Successful in 19s
Build on push / translation (push) Successful in 20s
Build on push / auth (push) Successful in 15s
Build on push / api (push) Has been cancelled
2025-09-24 19:39:28 +02:00
59 changed files with 427 additions and 325 deletions

View File

@@ -6,10 +6,8 @@ from cpl.application import ApplicationBuilder
from cpl.auth.permission.permissions import Permissions from cpl.auth.permission.permissions import Permissions
from cpl.auth.schema import AuthUser, Role from cpl.auth.schema import AuthUser, Role
from cpl.core.configuration import Configuration from cpl.core.configuration import Configuration
from cpl.core.console import Console
from cpl.core.environment import Environment from cpl.core.environment import Environment
from cpl.core.utils.cache import Cache from cpl.core.utils.cache import Cache
from custom.api.src.scoped_service import ScopedService
from service import PingService from service import PingService
@@ -25,8 +23,6 @@ def main():
builder.services.add_transient(PingService) builder.services.add_transient(PingService)
builder.services.add_module(api) builder.services.add_module(api)
builder.services.add_scoped(ScopedService)
builder.services.add_cache(AuthUser) builder.services.add_cache(AuthUser)
builder.services.add_cache(Role) builder.services.add_cache(Role)
@@ -44,32 +40,6 @@ def main():
user_cache = provider.get_service(Cache[AuthUser]) user_cache = provider.get_service(Cache[AuthUser])
role_cache = provider.get_service(Cache[Role]) role_cache = provider.get_service(Cache[Role])
if role_cache == user_cache:
raise Exception("Cache service is not working")
s1 = provider.get_service(ScopedService)
s2 = provider.get_service(ScopedService)
if s1.name == s2.name:
raise Exception("Scoped service is not working")
with provider.create_scope() as scope:
s3 = scope.get_service(ScopedService)
s4 = scope.get_service(ScopedService)
if s3.name != s4.name:
raise Exception("Scoped service is not working")
if s1.name == s3.name:
raise Exception("Scoped service is not working")
Console.write_line(
s1.name,
s2.name,
s3.name,
s4.name,
)
app.run() app.run()

View File

@@ -5,17 +5,12 @@ from starlette.responses import JSONResponse
from cpl.api import APILogger from cpl.api import APILogger
from cpl.api.router import Router from cpl.api.router import Router
from cpl.core.console import Console
from cpl.dependency import ServiceProvider
from custom.api.src.scoped_service import ScopedService
@Router.authenticate() @Router.authenticate()
# @Router.authorize(permissions=[Permissions.administrator]) # @Router.authorize(permissions=[Permissions.administrator])
# @Router.authorize(policies=["test"]) # @Router.authorize(policies=["test"])
@Router.get(f"/ping") @Router.get(f"/ping")
async def ping(r: Request, ping: PingService, logger: APILogger, provider: ServiceProvider, scoped: ScopedService): async def ping(r: Request, ping: PingService, logger: APILogger):
logger.info(f"Ping: {ping}") logger.info(f"Ping: {ping}")
Console.write_line(scoped.name)
return JSONResponse(ping.ping(r)) return JSONResponse(ping.ping(r))

View File

@@ -1,14 +0,0 @@
from cpl.core.console.console import Console
from cpl.core.utils.string import String
class ScopedService:
def __init__(self):
self._name = String.random(8)
@property
def name(self) -> str:
return self._name
def run(self):
Console.write_line(f"Im {self._name}")

View File

@@ -3,7 +3,7 @@ from cpl.auth.keycloak import KeycloakAdmin
from cpl.core.console import Console from cpl.core.console import Console
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.dependency import ServiceProvider from cpl.dependency import ServiceProviderABC
from model.city import City from model.city import City
from model.city_dao import CityDao from model.city_dao import CityDao
from model.user import User from model.user import User
@@ -11,7 +11,7 @@ from model.user_dao import UserDao
class Application(ApplicationABC): class Application(ApplicationABC):
def __init__(self, services: ServiceProvider): def __init__(self, services: ServiceProviderABC):
ApplicationABC.__init__(self, services) ApplicationABC.__init__(self, services)
self._logger = services.get_service(LoggerABC) self._logger = services.get_service(LoggerABC)

View File

@@ -1,6 +1,7 @@
from cpl.application.abc import ApplicationABC from cpl.application.abc import ApplicationABC
from cpl.core.console.console import Console from cpl.core.console.console import Console
from cpl.dependency import ServiceProvider from cpl.dependency import ServiceProviderABC
from cpl.dependency.scope import Scope
from di.static_test import StaticTest from di.static_test import StaticTest
from di.test_abc import TestABC from di.test_abc import TestABC
from di.test_service import TestService from di.test_service import TestService
@@ -9,37 +10,33 @@ from di.tester import Tester
class Application(ApplicationABC): class Application(ApplicationABC):
def __init__(self, services: ServiceProvider): def __init__(self, services: ServiceProviderABC):
ApplicationABC.__init__(self, services) ApplicationABC.__init__(self, services)
def _part_of_scoped(self): def _part_of_scoped(self):
ts: TestService = self._services.get_service(TestService) ts: TestService = self._services.get_service(TestService)
ts.run() ts.run()
def configure(self): ...
def main(self): def main(self):
with self._services.create_scope() as scope: with self._services.create_scope() as scope:
Console.write_line("Scope1") Console.write_line("Scope1")
ts: TestService = scope.get_service(TestService) ts: TestService = scope.service_provider.get_service(TestService)
ts.run() ts.run()
dit: DITesterService = scope.get_service(DITesterService) dit: DITesterService = scope.service_provider.get_service(DITesterService)
dit.run() dit.run()
if ts.name != dit.name:
raise Exception("DI is broken!")
with self._services.create_scope() as scope: with self._services.create_scope() as scope:
Console.write_line("Scope2") Console.write_line("Scope2")
ts: TestService = scope.get_service(TestService) ts: TestService = scope.service_provider.get_service(TestService)
ts.run() ts.run()
dit: DITesterService = scope.get_service(DITesterService) dit: DITesterService = scope.service_provider.get_service(DITesterService)
dit.run() dit.run()
if ts.name != dit.name:
raise Exception("DI is broken!")
Console.write_line("Global") Console.write_line("Global")
self._part_of_scoped() self._part_of_scoped()
StaticTest.test() StaticTest.test()
self._services.get_service(Tester) self._services.get_service(Tester)
Console.write_line(self._services.get_services(TestABC)) Console.write_line(self._services.get_services(list[TestABC]))

View File

@@ -6,10 +6,6 @@ class DITesterService:
def __init__(self, ts: TestService): def __init__(self, ts: TestService):
self._ts = ts self._ts = ts
@property
def name(self) -> str:
return self._ts.name
def run(self): def run(self):
Console.write_line("DIT: ") Console.write_line("DIT: ")
self._ts.run() self._ts.run()

View File

@@ -1,5 +1,5 @@
from cpl.application.abc import StartupABC from cpl.application.abc import StartupABC
from cpl.dependency import ServiceProvider, ServiceCollection from cpl.dependency import ServiceProviderABC, ServiceCollection
from di.di_tester_service import DITesterService from di.di_tester_service import DITesterService
from di.test1_service import Test1Service from di.test1_service import Test1Service
from di.test2_service import Test2Service from di.test2_service import Test2Service
@@ -12,11 +12,9 @@ class Startup(StartupABC):
def __init__(self): def __init__(self):
StartupABC.__init__(self) StartupABC.__init__(self)
@staticmethod def configure_configuration(self): ...
def configure_configuration(): ...
@staticmethod def configure_services(self, services: ServiceCollection) -> ServiceProviderABC:
def configure_services(services: ServiceCollection) -> ServiceProvider:
services.add_scoped(TestService) services.add_scoped(TestService)
services.add_scoped(DITesterService) services.add_scoped(DITesterService)

View File

@@ -1,10 +1,9 @@
from cpl.dependency import ServiceProvider, ServiceProvider from cpl.dependency import ServiceProvider, ServiceProviderABC
from cpl.dependency.inject import inject
from di.test_service import TestService from di.test_service import TestService
class StaticTest: class StaticTest:
@staticmethod @staticmethod
@inject @ServiceProvider.inject
def test(services: ServiceProvider, t1: TestService): def test(services: ServiceProviderABC, t1: TestService):
t1.run() t1.run()

View File

@@ -6,7 +6,7 @@ from di.test_abc import TestABC
class Test1Service(TestABC): class Test1Service(TestABC):
def __init__(self): def __init__(self):
TestABC.__init__(self, String.random(8)) TestABC.__init__(self, String.random_string(string.ascii_lowercase, 8))
def run(self): def run(self):
Console.write_line(f"Im {self._name}") Console.write_line(f"Im {self._name}")

View File

@@ -6,7 +6,7 @@ from di.test_abc import TestABC
class Test2Service(TestABC): class Test2Service(TestABC):
def __init__(self): def __init__(self):
TestABC.__init__(self, String.random(8)) TestABC.__init__(self, String.random_string(string.ascii_lowercase, 8))
def run(self): def run(self):
Console.write_line(f"Im {self._name}") Console.write_line(f"Im {self._name}")

View File

@@ -1,3 +1,5 @@
import string
from cpl.core.console.console import Console from cpl.core.console.console import Console
from cpl.core.utils.string import String from cpl.core.utils.string import String
@@ -6,9 +8,5 @@ class TestService:
def __init__(self): def __init__(self):
self._name = String.random(8) self._name = String.random(8)
@property
def name(self) -> str:
return self._name
def run(self): def run(self):
Console.write_line(f"Im {self._name}") Console.write_line(f"Im {self._name}")

View File

@@ -4,20 +4,19 @@ 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.dependency import ServiceProviderABC
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.mail import EMail, EMailClientABC from cpl.mail import EMail, EMailClientABC
from cpl.query import List from cpl.query.extension.list import List
from general.scoped_service import ScopedService
from test_service import TestService from test_service import TestService
from test_settings import TestSettings from test_settings import TestSettings
class Application(ApplicationABC): class Application(ApplicationABC):
def __init__(self, services: ServiceProvider): def __init__(self, services: ServiceProviderABC):
ApplicationABC.__init__(self, services) ApplicationABC.__init__(self, services)
self._logger = self._services.get_service(LoggerABC) self._logger = self._services.get_service(LoggerABC)
self._mailer = self._services.get_service(EMailClientABC) self._mailer = self._services.get_service(EMailClientABC)
@@ -39,7 +38,7 @@ class Application(ApplicationABC):
def main(self): def main(self):
self._logger.debug(f"Host: {Environment.get_host_name()}") self._logger.debug(f"Host: {Environment.get_host_name()}")
self._logger.debug(f"Environment: {Environment.get_environment()}") self._logger.debug(f"Environment: {Environment.get_environment()}")
Console.write_line(List(range(0, 10)).select(lambda x: f"x={x}").to_list()) Console.write_line(List(int, range(0, 10)).select(lambda x: f"x={x}").to_list())
Console.spinner("Test", self._wait, 2, spinner_foreground_color="red") Console.spinner("Test", self._wait, 2, spinner_foreground_color="red")
test: TestService = self._services.get_service(TestService) test: TestService = self._services.get_service(TestService)
ip_pipe: IPAddressPipe = self._services.get_service(IPAddressPipe) ip_pipe: IPAddressPipe = self._services.get_service(IPAddressPipe)
@@ -49,21 +48,10 @@ class Application(ApplicationABC):
Console.write_line(f"DI working: {test == test2 and ip_pipe != ip_pipe2}") Console.write_line(f"DI working: {test == test2 and ip_pipe != ip_pipe2}")
Console.write_line(self._services.get_service(LoggerABC)) Console.write_line(self._services.get_service(LoggerABC))
root_scoped_service = self._services.get_service(ScopedService) scope = self._services.create_scope()
with self._services.create_scope() as scope: Console.write_line("scope", scope)
s_srvc1 = scope.get_service(ScopedService) with self._services.create_scope() as s:
s_srvc2 = scope.get_service(ScopedService) Console.write_line("with scope", s)
Console.write_line(root_scoped_service)
Console.write_line(s_srvc1)
Console.write_line(s_srvc2)
if root_scoped_service == s_srvc1 or s_srvc1 != s_srvc2:
raise Exception("Root scoped service should not be equal to scoped service")
root_scoped_service2 = self._services.get_service(ScopedService)
Console.write_line(root_scoped_service2)
if root_scoped_service == root_scoped_service2:
raise Exception("Root scoped service should be equal to root scoped service 2")
test_settings = Configuration.get(TestSettings) test_settings = Configuration.get(TestSettings)
Console.write_line(test_settings.value) Console.write_line(test_settings.value)

View File

@@ -1,10 +0,0 @@
from cpl.core.console import Console
class ScopedService:
def __init__(self):
self.value = "I am a scoped service"
Console.write_line(self.value, self)
def get_value(self):
return self.value

View File

@@ -4,7 +4,6 @@ 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 general.scoped_service import ScopedService
from test_service import TestService from test_service import TestService
@@ -22,4 +21,3 @@ class Startup(StartupABC):
services.add_module(mail) services.add_module(mail)
services.add_transient(IPAddressPipe) services.add_transient(IPAddressPipe)
services.add_singleton(TestService) services.add_singleton(TestService)
services.add_scoped(ScopedService)

View File

@@ -1,10 +1,10 @@
from cpl.application.abc import ApplicationExtensionABC from cpl.application.abc import ApplicationExtensionABC
from cpl.core.console import Console from cpl.core.console import Console
from cpl.dependency import ServiceProvider from cpl.dependency import ServiceProviderABC
class TestExtension(ApplicationExtensionABC): class TestExtension(ApplicationExtensionABC):
@staticmethod @staticmethod
def run(services: ServiceProvider): def run(services: ServiceProviderABC):
Console.write_line("Hello World from App Extension") Console.write_line("Hello World from App Extension")

View File

@@ -1,10 +1,10 @@
from cpl.core.console.console import Console from cpl.core.console.console import Console
from cpl.dependency import ServiceProvider from cpl.dependency import ServiceProviderABC
from cpl.core.pipes.ip_address_pipe import IPAddressPipe from cpl.core.pipes.ip_address_pipe import IPAddressPipe
class TestService: class TestService:
def __init__(self, provider: ServiceProvider): def __init__(self, provider: ServiceProviderABC):
self._provider = provider self._provider = provider
def run(self): def run(self):

View File

@@ -1,14 +1,14 @@
from cpl.application import ApplicationABC from cpl.application import ApplicationABC
from cpl.core.configuration import ConfigurationABC from cpl.core.configuration import ConfigurationABC
from cpl.core.console import Console from cpl.core.console import Console
from cpl.dependency import ServiceProvider from cpl.dependency import ServiceProviderABC
from cpl.translation.translate_pipe import TranslatePipe from cpl.translation.translate_pipe import TranslatePipe
from cpl.translation.translation_service_abc import TranslationServiceABC from cpl.translation.translation_service_abc import TranslationServiceABC
from cpl.translation.translation_settings import TranslationSettings from cpl.translation.translation_settings import TranslationSettings
class Application(ApplicationABC): class Application(ApplicationABC):
def __init__(self, config: ConfigurationABC, services: ServiceProvider): def __init__(self, config: ConfigurationABC, services: ServiceProviderABC):
ApplicationABC.__init__(self, config, services) ApplicationABC.__init__(self, config, services)
self._translate: TranslatePipe = services.get_service(TranslatePipe) self._translate: TranslatePipe = services.get_service(TranslatePipe)

View File

@@ -1,6 +1,6 @@
from cpl.application import StartupABC from cpl.application import StartupABC
from cpl.core.configuration import ConfigurationABC from cpl.core.configuration import ConfigurationABC
from cpl.dependency import ServiceProvider, ServiceCollection from cpl.dependency import ServiceProviderABC, ServiceCollection
from cpl.core.environment import Environment from cpl.core.environment import Environment
@@ -12,6 +12,6 @@ class Startup(StartupABC):
configuration.add_json_file("appsettings.json") configuration.add_json_file("appsettings.json")
return configuration return configuration
def configure_services(self, services: ServiceCollection, environment: Environment) -> ServiceProvider: def configure_services(self, services: ServiceCollection, environment: Environment) -> ServiceProviderABC:
services.add_translation() services.add_translation()
return services.build() return services.build()

View File

@@ -27,14 +27,14 @@ from cpl.api.settings import ApiSettings
from cpl.api.typing import HTTPMethods, PartialMiddleware, PolicyResolver from cpl.api.typing import HTTPMethods, PartialMiddleware, PolicyResolver
from cpl.application.abc.application_abc import ApplicationABC from cpl.application.abc.application_abc import ApplicationABC
from cpl.core.configuration import Configuration from cpl.core.configuration import Configuration
from cpl.dependency.inject import inject from cpl.dependency.service_provider_abc import ServiceProviderABC
from cpl.dependency.service_provider import ServiceProvider
PolicyInput = Union[dict[str, PolicyResolver], Policy] PolicyInput = Union[dict[str, PolicyResolver], Policy]
class WebApp(ApplicationABC): class WebApp(ApplicationABC):
def __init__(self, services: ServiceProvider): def __init__(self, services: ServiceProviderABC):
super().__init__(services, [auth, api]) super().__init__(services, [auth, api])
self._app: Starlette | None = None self._app: Starlette | None = None
@@ -44,15 +44,15 @@ class WebApp(ApplicationABC):
self._policies = services.get_service(PolicyRegistry) self._policies = services.get_service(PolicyRegistry)
self._routes = services.get_service(RouteRegistry) self._routes = services.get_service(RouteRegistry)
self._middleware: list[Middleware] = [] self._middleware: list[Middleware] = [
Middleware(RequestMiddleware),
Middleware(LoggingMiddleware),
]
self._exception_handlers: Mapping[Any, ExceptionHandler] = { self._exception_handlers: Mapping[Any, ExceptionHandler] = {
Exception: self._handle_exception, Exception: self._handle_exception,
APIError: self._handle_exception, APIError: self._handle_exception,
} }
self.with_middleware(RequestMiddleware)
self.with_middleware(LoggingMiddleware)
async def _handle_exception(self, request: Request, exc: Exception): async def _handle_exception(self, request: Request, exc: Exception):
if isinstance(exc, APIError): if isinstance(exc, APIError):
self._logger.error(exc) self._logger.error(exc)
@@ -168,9 +168,9 @@ class WebApp(ApplicationABC):
self._check_for_app() self._check_for_app()
if isinstance(middleware, Middleware): if isinstance(middleware, Middleware):
self._middleware.append(inject(middleware)) self._middleware.append(middleware)
elif callable(middleware): elif callable(middleware):
self._middleware.append(Middleware(inject(middleware))) self._middleware.append(Middleware(middleware))
else: else:
raise ValueError("middleware must be of type starlette.middleware.Middleware or a callable") raise ValueError("middleware must be of type starlette.middleware.Middleware or a callable")
@@ -220,7 +220,7 @@ class WebApp(ApplicationABC):
self._validate_policies() self._validate_policies()
if self._app is None: if self._app is None:
routes = [route.to_starlette(inject) for route in self._routes.all()] routes = [route.to_starlette(self._services.inject) for route in self._routes.all()]
app = Starlette( app = Starlette(
routes=routes, routes=routes,

View File

@@ -9,10 +9,12 @@ from cpl.api.router import Router
from cpl.auth.keycloak import KeycloakClient from cpl.auth.keycloak import KeycloakClient
from cpl.auth.schema import AuthUserDao, AuthUser from cpl.auth.schema import AuthUserDao, AuthUser
from cpl.core.ctx import set_user from cpl.core.ctx import set_user
from cpl.dependency import ServiceProviderABC
class AuthenticationMiddleware(ASGIMiddleware): class AuthenticationMiddleware(ASGIMiddleware):
@ServiceProviderABC.inject
def __init__(self, app, logger: APILogger, keycloak: KeycloakClient, user_dao: AuthUserDao): def __init__(self, app, logger: APILogger, keycloak: KeycloakClient, user_dao: AuthUserDao):
ASGIMiddleware.__init__(self, app) ASGIMiddleware.__init__(self, app)

View File

@@ -9,10 +9,12 @@ from cpl.api.registry.policy import PolicyRegistry
from cpl.api.router import Router from cpl.api.router import Router
from cpl.auth.schema._administration.auth_user_dao import AuthUserDao from cpl.auth.schema._administration.auth_user_dao import AuthUserDao
from cpl.core.ctx.user_context import get_user from cpl.core.ctx.user_context import get_user
from cpl.dependency.service_provider_abc import ServiceProviderABC
class AuthorizationMiddleware(ASGIMiddleware): class AuthorizationMiddleware(ASGIMiddleware):
@ServiceProviderABC.inject
def __init__(self, app, logger: APILogger, policies: PolicyRegistry, user_dao: AuthUserDao): def __init__(self, app, logger: APILogger, policies: PolicyRegistry, user_dao: AuthUserDao):
ASGIMiddleware.__init__(self, app) ASGIMiddleware.__init__(self, app)

View File

@@ -6,10 +6,12 @@ from starlette.types import Receive, Scope, Send
from cpl.api.abc.asgi_middleware_abc import ASGIMiddleware from cpl.api.abc.asgi_middleware_abc import ASGIMiddleware
from cpl.api.logger import APILogger from cpl.api.logger import APILogger
from cpl.api.middleware.request import get_request from cpl.api.middleware.request import get_request
from cpl.dependency import ServiceProviderABC
class LoggingMiddleware(ASGIMiddleware): class LoggingMiddleware(ASGIMiddleware):
@ServiceProviderABC.inject
def __init__(self, app, logger: APILogger): def __init__(self, app, logger: APILogger):
ASGIMiddleware.__init__(self, app) ASGIMiddleware.__init__(self, app)

View File

@@ -9,18 +9,17 @@ from starlette.types import Scope, Receive, Send
from cpl.api.abc.asgi_middleware_abc import ASGIMiddleware from cpl.api.abc.asgi_middleware_abc import ASGIMiddleware
from cpl.api.logger import APILogger from cpl.api.logger import APILogger
from cpl.api.typing import TRequest from cpl.api.typing import TRequest
from cpl.dependency.inject import inject from cpl.dependency import ServiceProviderABC
from cpl.dependency.service_provider import ServiceProvider
_request_context: ContextVar[Union[TRequest, None]] = ContextVar("request", default=None) _request_context: ContextVar[Union[TRequest, None]] = ContextVar("request", default=None)
class RequestMiddleware(ASGIMiddleware): class RequestMiddleware(ASGIMiddleware):
def __init__(self, app, provider: ServiceProvider, logger: APILogger): @ServiceProviderABC.inject
def __init__(self, app, logger: APILogger):
ASGIMiddleware.__init__(self, app) ASGIMiddleware.__init__(self, app)
self._provider = provider
self._logger = logger self._logger = logger
self._ctx_token = None self._ctx_token = None
@@ -30,8 +29,7 @@ class RequestMiddleware(ASGIMiddleware):
await self.set_request_data(request) await self.set_request_data(request)
try: try:
with self._provider.create_scope(): await self._app(scope, receive, send)
inject(await self._app(scope, receive, send))
finally: finally:
await self.clean_request_data() await self.clean_request_data()

View File

@@ -3,7 +3,6 @@ from enum import Enum
from cpl.api.model.validation_match import ValidationMatch from cpl.api.model.validation_match import ValidationMatch
from cpl.api.registry.route import RouteRegistry from cpl.api.registry.route import RouteRegistry
from cpl.api.typing import HTTPMethods from cpl.api.typing import HTTPMethods
from cpl.dependency import get_provider
class Router: class Router:
@@ -96,7 +95,9 @@ class Router:
from cpl.api.model.api_route import ApiRoute from cpl.api.model.api_route import ApiRoute
if not registry: if not registry:
routes = get_provider().get_service(RouteRegistry) from cpl.dependency.service_provider_abc import ServiceProviderABC
routes = ServiceProviderABC.get_global_service(RouteRegistry)
else: else:
routes = registry routes = registry
@@ -143,8 +144,9 @@ class Router:
""" """
from cpl.api.model.api_route import ApiRoute from cpl.api.model.api_route import ApiRoute
from cpl.dependency.service_provider_abc import ServiceProviderABC
routes = get_provider().get_service(RouteRegistry) routes = ServiceProviderABC.get_global_service(RouteRegistry)
def inner(fn): def inner(fn):
path = getattr(fn, "_route_path", None) path = getattr(fn, "_route_path", None)

View File

@@ -5,7 +5,7 @@ from cpl.application.host import Host
from cpl.core.log.log_level import LogLevel from cpl.core.log.log_level import LogLevel
from cpl.core.log.log_settings import LogSettings from cpl.core.log.log_settings import LogSettings
from cpl.core.log.logger_abc import LoggerABC from cpl.core.log.logger_abc import LoggerABC
from cpl.dependency.service_provider import ServiceProvider from cpl.dependency.service_provider_abc import ServiceProviderABC
def __not_implemented__(package: str, func: Callable): def __not_implemented__(package: str, func: Callable):
@@ -16,12 +16,12 @@ class ApplicationABC(ABC):
r"""ABC for the Application class r"""ABC for the Application class
Parameters: Parameters:
services: :class:`cpl.dependency.service_provider.ServiceProvider` services: :class:`cpl.dependency.service_provider_abc.ServiceProviderABC`
Contains instances of prepared objects Contains instances of prepared objects
""" """
@abstractmethod @abstractmethod
def __init__(self, services: ServiceProvider, required_modules: list[str | object] = None): def __init__(self, services: ServiceProviderABC, required_modules: list[str | object] = None):
self._services = services self._services = services
self._required_modules = ( self._required_modules = (
[x.__name__ if not isinstance(x, str) else x for x in required_modules] if required_modules else [] [x.__name__ if not isinstance(x, str) else x for x in required_modules] if required_modules else []

View File

@@ -1,10 +1,10 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from cpl.dependency.service_provider import ServiceProvider from cpl.dependency.service_provider_abc import ServiceProviderABC
class ApplicationExtensionABC(ABC): class ApplicationExtensionABC(ABC):
@staticmethod @staticmethod
@abstractmethod @abstractmethod
def run(services: ServiceProvider): ... def run(services: ServiceProviderABC): ...

View File

@@ -7,7 +7,6 @@ from cpl.application.abc.startup_abc import StartupABC
from cpl.application.abc.startup_extension_abc import StartupExtensionABC from cpl.application.abc.startup_extension_abc import StartupExtensionABC
from cpl.application.host import Host from cpl.application.host import Host
from cpl.core.errors import dependency_error from cpl.core.errors import dependency_error
from cpl.dependency.context import get_provider, use_root_provider
from cpl.dependency.service_collection import ServiceCollection from cpl.dependency.service_collection import ServiceCollection
TApp = TypeVar("TApp", bound=ApplicationABC) TApp = TypeVar("TApp", bound=ApplicationABC)
@@ -22,7 +21,6 @@ class ApplicationBuilder(Generic[TApp]):
self._app = app if app is not None else ApplicationABC self._app = app if app is not None else ApplicationABC
self._services = ServiceCollection() self._services = ServiceCollection()
use_root_provider(self._services.build())
self._startup: Optional[StartupABC] = None self._startup: Optional[StartupABC] = None
self._app_extensions: list[Type[ApplicationExtensionABC]] = [] self._app_extensions: list[Type[ApplicationExtensionABC]] = []
@@ -36,12 +34,7 @@ class ApplicationBuilder(Generic[TApp]):
@property @property
def service_provider(self): def service_provider(self):
provider = get_provider() return self._services.build()
if provider is None:
provider = self._services.build()
use_root_provider(provider)
return provider
def validate_app_required_modules(self, app: ApplicationABC): def validate_app_required_modules(self, app: ApplicationABC):
for module in app.required_modules: for module in app.required_modules:

View File

@@ -1,5 +1,5 @@
from cpl.core.utils.get_value import get_value from cpl.core.utils.get_value import get_value
from cpl.dependency import ServiceProvider from cpl.dependency import ServiceProviderABC
class KeycloakUser: class KeycloakUser:
@@ -32,5 +32,5 @@ class KeycloakUser:
def id(self) -> str: def id(self) -> str:
from cpl.auth import KeycloakAdmin from cpl.auth import KeycloakAdmin
keycloak_admin: KeycloakAdmin = get_provider().get_service(KeycloakAdmin) keycloak_admin: KeycloakAdmin = ServiceProviderABC.get_global_service(KeycloakAdmin)
return keycloak_admin.get_user_id(self._username) return keycloak_admin.get_user_id(self._username)

View File

@@ -10,8 +10,7 @@ from cpl.core.log.logger import Logger
from cpl.core.typing import Id, SerialId from cpl.core.typing import Id, SerialId
from cpl.core.utils.credential_manager import CredentialManager from cpl.core.utils.credential_manager import CredentialManager
from cpl.database.abc.db_model_abc import DbModelABC from cpl.database.abc.db_model_abc import DbModelABC
from cpl.dependency import get_provider from cpl.dependency.service_provider_abc import ServiceProviderABC
from cpl.dependency.service_provider import ServiceProvider
_logger = Logger(__name__) _logger = Logger(__name__)
@@ -48,7 +47,7 @@ class ApiKey(DbModelABC):
async def permissions(self): async def permissions(self):
from cpl.auth.schema._permission.api_key_permission_dao import ApiKeyPermissionDao from cpl.auth.schema._permission.api_key_permission_dao import ApiKeyPermissionDao
apiKeyPermissionDao = get_provider().get_service(ApiKeyPermissionDao) apiKeyPermissionDao = ServiceProviderABC.get_global_provider().get_service(ApiKeyPermissionDao)
return [await x.permission for x in await apiKeyPermissionDao.find_by_api_key_id(self.id)] return [await x.permission for x in await apiKeyPermissionDao.find_by_api_key_id(self.id)]

View File

@@ -10,7 +10,7 @@ from cpl.auth.permission.permissions import Permissions
from cpl.core.typing import SerialId from cpl.core.typing import SerialId
from cpl.database.abc import DbModelABC from cpl.database.abc import DbModelABC
from cpl.database.logger import DBLogger from cpl.database.logger import DBLogger
from cpl.dependency import ServiceProvider from cpl.dependency import ServiceProviderABC
class AuthUser(DbModelABC): class AuthUser(DbModelABC):
@@ -36,12 +36,12 @@ class AuthUser(DbModelABC):
return "ANONYMOUS" return "ANONYMOUS"
try: try:
keycloak = get_provider().get_service(KeycloakAdmin) keycloak = ServiceProviderABC.get_global_service(KeycloakAdmin)
return keycloak.get_user(self._keycloak_id).get("username") return keycloak.get_user(self._keycloak_id).get("username")
except KeycloakGetError as e: except KeycloakGetError as e:
return "UNKNOWN" return "UNKNOWN"
except Exception as e: except Exception as e:
logger = get_provider().get_service(DBLogger) logger = ServiceProviderABC.get_global_service(DBLogger)
logger.error(f"Failed to get user {self._keycloak_id} from Keycloak", e) logger.error(f"Failed to get user {self._keycloak_id} from Keycloak", e)
return "UNKNOWN" return "UNKNOWN"
@@ -51,12 +51,12 @@ class AuthUser(DbModelABC):
return "ANONYMOUS" return "ANONYMOUS"
try: try:
keycloak = get_provider().get_service(KeycloakAdmin) keycloak = ServiceProviderABC.get_global_service(KeycloakAdmin)
return keycloak.get_user(self._keycloak_id).get("email") return keycloak.get_user(self._keycloak_id).get("email")
except KeycloakGetError as e: except KeycloakGetError as e:
return "UNKNOWN" return "UNKNOWN"
except Exception as e: except Exception as e:
logger = get_provider().get_service(DBLogger) logger = ServiceProviderABC.get_global_service(DBLogger)
logger.error(f"Failed to get user {self._keycloak_id} from Keycloak", e) logger.error(f"Failed to get user {self._keycloak_id} from Keycloak", e)
return "UNKNOWN" return "UNKNOWN"
@@ -64,26 +64,26 @@ class AuthUser(DbModelABC):
async def roles(self): async def roles(self):
from cpl.auth.schema._permission.role_user_dao import RoleUserDao from cpl.auth.schema._permission.role_user_dao import RoleUserDao
role_user_dao: RoleUserDao = get_provider().get_service(RoleUserDao) role_user_dao: RoleUserDao = ServiceProviderABC.get_global_service(RoleUserDao)
return [await x.role for x in await role_user_dao.get_by_user_id(self.id)] return [await x.role for x in await role_user_dao.get_by_user_id(self.id)]
@async_property @async_property
async def permissions(self): async def permissions(self):
from cpl.auth.schema._administration.auth_user_dao import AuthUserDao from cpl.auth.schema._administration.auth_user_dao import AuthUserDao
auth_user_dao: AuthUserDao = get_provider().get_service(AuthUserDao) auth_user_dao: AuthUserDao = ServiceProviderABC.get_global_service(AuthUserDao)
return await auth_user_dao.get_permissions(self.id) return await auth_user_dao.get_permissions(self.id)
async def has_permission(self, permission: Permissions) -> bool: async def has_permission(self, permission: Permissions) -> bool:
from cpl.auth.schema._administration.auth_user_dao import AuthUserDao from cpl.auth.schema._administration.auth_user_dao import AuthUserDao
auth_user_dao: AuthUserDao = get_provider().get_service(AuthUserDao) auth_user_dao: AuthUserDao = ServiceProviderABC.get_global_service(AuthUserDao)
return await auth_user_dao.has_permission(self.id, permission) return await auth_user_dao.has_permission(self.id, permission)
async def anonymize(self): async def anonymize(self):
from cpl.auth.schema._administration.auth_user_dao import AuthUserDao from cpl.auth.schema._administration.auth_user_dao import AuthUserDao
auth_user_dao: AuthUserDao = get_provider().get_service(AuthUserDao) auth_user_dao: AuthUserDao = ServiceProviderABC.get_global_service(AuthUserDao)
self._keycloak_id = str(uuid.UUID(int=0)) self._keycloak_id = str(uuid.UUID(int=0))
await auth_user_dao.update(self) await auth_user_dao.update(self)

View File

@@ -5,7 +5,7 @@ from cpl.auth.schema._administration.auth_user import AuthUser
from cpl.database import TableManager from cpl.database import TableManager
from cpl.database.abc import DbModelDaoABC from cpl.database.abc import DbModelDaoABC
from cpl.database.external_data_temp_table_builder import ExternalDataTempTableBuilder from cpl.database.external_data_temp_table_builder import ExternalDataTempTableBuilder
from cpl.dependency import ServiceProvider from cpl.dependency import ServiceProviderABC
class AuthUserDao(DbModelDaoABC[AuthUser]): class AuthUserDao(DbModelDaoABC[AuthUser]):
@@ -36,7 +36,7 @@ class AuthUserDao(DbModelDaoABC[AuthUser]):
async def has_permission(self, user_id: int, permission: Union[Permissions, str]) -> bool: async def has_permission(self, user_id: int, permission: Union[Permissions, str]) -> bool:
from cpl.auth.schema._permission.permission_dao import PermissionDao from cpl.auth.schema._permission.permission_dao import PermissionDao
permission_dao: PermissionDao = get_provider().get_service(PermissionDao) permission_dao: PermissionDao = ServiceProviderABC.get_global_service(PermissionDao)
p = await permission_dao.get_by_name(permission if isinstance(permission, str) else permission.value) p = await permission_dao.get_by_name(permission if isinstance(permission, str) else permission.value)
result = await self._db.select_map( result = await self._db.select_map(
f""" f"""

View File

@@ -5,7 +5,7 @@ from async_property import async_property
from cpl.core.typing import SerialId from cpl.core.typing import SerialId
from cpl.database.abc import DbJoinModelABC from cpl.database.abc import DbJoinModelABC
from cpl.dependency import ServiceProvider from cpl.dependency import ServiceProviderABC
class ApiKeyPermission(DbJoinModelABC): class ApiKeyPermission(DbJoinModelABC):
@@ -31,7 +31,7 @@ class ApiKeyPermission(DbJoinModelABC):
async def api_key(self): async def api_key(self):
from cpl.auth.schema._administration.api_key_dao import ApiKeyDao from cpl.auth.schema._administration.api_key_dao import ApiKeyDao
api_key_dao: ApiKeyDao = get_provider().get_service(ApiKeyDao) api_key_dao: ApiKeyDao = ServiceProviderABC.get_global_service(ApiKeyDao)
return await api_key_dao.get_by_id(self._api_key_id) return await api_key_dao.get_by_id(self._api_key_id)
@property @property
@@ -42,5 +42,5 @@ class ApiKeyPermission(DbJoinModelABC):
async def permission(self): async def permission(self):
from cpl.auth.schema._permission.permission_dao import PermissionDao from cpl.auth.schema._permission.permission_dao import PermissionDao
permission_dao: PermissionDao = get_provider().get_service(PermissionDao) permission_dao: PermissionDao = ServiceProviderABC.get_global_service(PermissionDao)
return await permission_dao.get_by_id(self._permission_id) return await permission_dao.get_by_id(self._permission_id)

View File

@@ -6,7 +6,7 @@ from async_property import async_property
from cpl.auth.permission.permissions import Permissions from cpl.auth.permission.permissions import Permissions
from cpl.core.typing import SerialId from cpl.core.typing import SerialId
from cpl.database.abc import DbModelABC from cpl.database.abc import DbModelABC
from cpl.dependency import ServiceProvider from cpl.dependency import ServiceProviderABC
class Role(DbModelABC): class Role(DbModelABC):
@@ -44,22 +44,22 @@ class Role(DbModelABC):
async def permissions(self): async def permissions(self):
from cpl.auth.schema._permission.role_permission_dao import RolePermissionDao from cpl.auth.schema._permission.role_permission_dao import RolePermissionDao
role_permission_dao: RolePermissionDao = get_provider().get_service(RolePermissionDao) role_permission_dao: RolePermissionDao = ServiceProviderABC.get_global_service(RolePermissionDao)
return [await x.permission for x in await role_permission_dao.get_by_role_id(self.id)] return [await x.permission for x in await role_permission_dao.get_by_role_id(self.id)]
@async_property @async_property
async def users(self): async def users(self):
from cpl.auth.schema._permission.role_user_dao import RoleUserDao from cpl.auth.schema._permission.role_user_dao import RoleUserDao
role_user_dao: RoleUserDao = get_provider().get_service(RoleUserDao) role_user_dao: RoleUserDao = ServiceProviderABC.get_global_service(RoleUserDao)
return [await x.user for x in await role_user_dao.get_by_role_id(self.id)] return [await x.user for x in await role_user_dao.get_by_role_id(self.id)]
async def has_permission(self, permission: Permissions) -> bool: async def has_permission(self, permission: Permissions) -> bool:
from cpl.auth.schema._permission.permission_dao import PermissionDao from cpl.auth.schema._permission.permission_dao import PermissionDao
from cpl.auth.schema._permission.role_permission_dao import RolePermissionDao from cpl.auth.schema._permission.role_permission_dao import RolePermissionDao
permission_dao: PermissionDao = get_provider().get_service(PermissionDao) permission_dao: PermissionDao = ServiceProviderABC.get_global_service(PermissionDao)
role_permission_dao: RolePermissionDao = get_provider().get_service(RolePermissionDao) role_permission_dao: RolePermissionDao = ServiceProviderABC.get_global_service(RolePermissionDao)
p = await permission_dao.get_by_name(permission.value) p = await permission_dao.get_by_name(permission.value)

View File

@@ -5,7 +5,7 @@ from async_property import async_property
from cpl.core.typing import SerialId from cpl.core.typing import SerialId
from cpl.database.abc import DbModelABC from cpl.database.abc import DbModelABC
from cpl.dependency import ServiceProvider from cpl.dependency import ServiceProviderABC
class RolePermission(DbModelABC): class RolePermission(DbModelABC):
@@ -31,7 +31,7 @@ class RolePermission(DbModelABC):
async def role(self): async def role(self):
from cpl.auth.schema._permission.role_dao import RoleDao from cpl.auth.schema._permission.role_dao import RoleDao
role_dao: RoleDao = get_provider().get_service(RoleDao) role_dao: RoleDao = ServiceProviderABC.get_global_service(RoleDao)
return await role_dao.get_by_id(self._role_id) return await role_dao.get_by_id(self._role_id)
@property @property
@@ -42,5 +42,5 @@ class RolePermission(DbModelABC):
async def permission(self): async def permission(self):
from cpl.auth.schema._permission.permission_dao import PermissionDao from cpl.auth.schema._permission.permission_dao import PermissionDao
permission_dao: PermissionDao = get_provider().get_service(PermissionDao) permission_dao: PermissionDao = ServiceProviderABC.get_global_service(PermissionDao)
return await permission_dao.get_by_id(self._permission_id) return await permission_dao.get_by_id(self._permission_id)

View File

@@ -5,7 +5,7 @@ from async_property import async_property
from cpl.core.typing import SerialId from cpl.core.typing import SerialId
from cpl.database.abc import DbJoinModelABC from cpl.database.abc import DbJoinModelABC
from cpl.dependency import ServiceProvider from cpl.dependency import ServiceProviderABC
class RoleUser(DbJoinModelABC): class RoleUser(DbJoinModelABC):
@@ -31,7 +31,7 @@ class RoleUser(DbJoinModelABC):
async def user(self): async def user(self):
from cpl.auth.schema._administration.auth_user_dao import AuthUserDao from cpl.auth.schema._administration.auth_user_dao import AuthUserDao
auth_user_dao: AuthUserDao = get_provider().get_service(AuthUserDao) auth_user_dao: AuthUserDao = ServiceProviderABC.get_global_service(AuthUserDao)
return await auth_user_dao.get_by_id(self._user_id) return await auth_user_dao.get_by_id(self._user_id)
@property @property
@@ -42,5 +42,5 @@ class RoleUser(DbJoinModelABC):
async def role(self): async def role(self):
from cpl.auth.schema._permission.role_dao import RoleDao from cpl.auth.schema._permission.role_dao import RoleDao
role_dao: RoleDao = get_provider().get_service(RoleDao) role_dao: RoleDao = ServiceProviderABC.get_global_service(RoleDao)
return await role_dao.get_by_id(self._role_id) return await role_dao.get_by_id(self._role_id)

View File

@@ -2,15 +2,15 @@ from contextvars import ContextVar
from typing import Optional from typing import Optional
from cpl.auth.schema._administration.auth_user import AuthUser from cpl.auth.schema._administration.auth_user import AuthUser
from cpl.dependency import get_provider
_user_context: ContextVar[Optional[AuthUser]] = ContextVar("user", default=None) _user_context: ContextVar[Optional[AuthUser]] = ContextVar("user", default=None)
def set_user(user: Optional[AuthUser]): def set_user(user: Optional[AuthUser]):
from cpl.dependency.service_provider_abc import ServiceProviderABC
from cpl.core.log.logger_abc import LoggerABC from cpl.core.log.logger_abc import LoggerABC
logger = get_provider().get_service(LoggerABC) logger = ServiceProviderABC.get_global_service(LoggerABC)
logger.trace("Setting user context", user.id) logger.trace("Setting user context", user.id)
_user_context.set(user) _user_context.set(user)

View File

@@ -9,7 +9,6 @@ from starlette.requests import Request
from cpl.core.log.log_level import LogLevel from cpl.core.log.log_level import LogLevel
from cpl.core.log.logger import Logger from cpl.core.log.logger import Logger
from cpl.core.typing import Source, Messages from cpl.core.typing import Source, Messages
from cpl.dependency import get_provider
class StructuredLogger(Logger): class StructuredLogger(Logger):
@@ -100,9 +99,10 @@ class StructuredLogger(Logger):
if user is None: if user is None:
return return
from cpl.dependency.service_provider_abc import ServiceProviderABC
from cpl.auth.keycloak.keycloak_admin import KeycloakAdmin from cpl.auth.keycloak.keycloak_admin import KeycloakAdmin
keycloak = get_provider().get_service(KeycloakAdmin) keycloak = ServiceProviderABC.get_global_service(KeycloakAdmin)
kc_user = keycloak.get_user(user.keycloak_id) kc_user = keycloak.get_user(user.keycloak_id)
message["user"] = { message["user"] = {
"id": str(user.id), "id": str(user.id),

View File

@@ -3,8 +3,7 @@ from typing import Type
from cpl.core.log import LoggerABC, LogLevel from cpl.core.log import LoggerABC, LogLevel
from cpl.core.typing import Messages from cpl.core.typing import Messages
from cpl.dependency.inject import inject from cpl.dependency.service_provider_abc import ServiceProviderABC
from cpl.dependency.service_provider import ServiceProvider
class WrappedLogger(LoggerABC): class WrappedLogger(LoggerABC):
@@ -18,13 +17,13 @@ class WrappedLogger(LoggerABC):
self._set_logger() self._set_logger()
@inject @ServiceProviderABC.inject
def _set_logger(self, services: ServiceProvider): def _set_logger(self, services: ServiceProviderABC):
from cpl.core.log import Logger from cpl.core.log import Logger
t_logger: Type[Logger] = services.get_service_type(LoggerABC) t_logger: Type[Logger] = services.get_service_type(LoggerABC)
if t_logger is None: if t_logger is None:
raise Exception("No LoggerABC service registered in ServiceProvider") raise Exception("No LoggerABC service registered in ServiceProviderABC")
self._logger = t_logger(self._source, self._file_prefix) self._logger = t_logger(self._source, self._file_prefix)
@@ -43,8 +42,8 @@ class WrappedLogger(LoggerABC):
from cpl.dependency import ServiceCollection from cpl.dependency import ServiceCollection
ignore_classes = [ ignore_classes = [
ServiceProvider, ServiceProviderABC,
ServiceProvider.__subclasses__(), ServiceProviderABC.__subclasses__(),
ServiceCollection, ServiceCollection,
WrappedLogger, WrappedLogger,
WrappedLogger.__subclasses__(), WrappedLogger.__subclasses__(),

View File

@@ -114,15 +114,12 @@ class String:
characters = [] characters = []
if letters: if letters:
characters.extend(string.ascii_letters) characters.append(string.ascii_letters)
if digits: if digits:
characters.extend(string.digits) characters.append(string.digits)
if special_characters: if special_characters:
characters.extend(string.punctuation) characters.append(string.punctuation)
x = "".join(random.choice(list(characters)) for _ in range(length)) if characters else "" return "".join(random.choice(characters) for _ in range(length)) if characters else ""
if len(x) != length:
raise Exception("No characters selected to generate random string")
return x

View File

@@ -9,19 +9,21 @@ from cpl.core.utils.get_value import get_value
from cpl.core.utils.string import String from cpl.core.utils.string import String
from cpl.database.abc.db_context_abc import DBContextABC from cpl.database.abc.db_context_abc import DBContextABC
from cpl.database.const import DATETIME_FORMAT from cpl.database.const import DATETIME_FORMAT
from cpl.database.external_data_temp_table_builder import ExternalDataTempTableBuilder
from cpl.database.logger import DBLogger from cpl.database.logger import DBLogger
from cpl.database.external_data_temp_table_builder import ExternalDataTempTableBuilder
from cpl.database.postgres.sql_select_builder import SQLSelectBuilder from cpl.database.postgres.sql_select_builder import SQLSelectBuilder
from cpl.database.typing import T_DBM, Attribute, AttributeFilters, AttributeSorts from cpl.database.typing import T_DBM, Attribute, AttributeFilters, AttributeSorts
from cpl.dependency import get_provider
class DataAccessObjectABC(ABC, Generic[T_DBM]): class DataAccessObjectABC(ABC, Generic[T_DBM]):
@abstractmethod @abstractmethod
def __init__(self, model_type: Type[T_DBM], table_name: str): def __init__(self, model_type: Type[T_DBM], table_name: str):
self._db = get_provider().get_service(DBContextABC) from cpl.dependency.service_provider_abc import ServiceProviderABC
self._logger = get_provider().get_service(DBLogger)
self._db = ServiceProviderABC.get_global_service(DBContextABC)
self._logger = ServiceProviderABC.get_global_service(DBLogger)
self._model_type = model_type self._model_type = model_type
self._table_name = table_name self._table_name = table_name

View File

@@ -6,7 +6,7 @@ from mysql.connector.aio import MySQLConnectionPool
from cpl.core.environment import Environment from cpl.core.environment import Environment
from cpl.database.logger import DBLogger from cpl.database.logger import DBLogger
from cpl.database.model import DatabaseSettings from cpl.database.model import DatabaseSettings
from cpl.dependency import ServiceProvider from cpl.dependency import ServiceProviderABC
class MySQLPool: class MySQLPool:
@@ -35,7 +35,7 @@ class MySQLPool:
await cursor.execute("SELECT 1") await cursor.execute("SELECT 1")
await cursor.fetchall() await cursor.fetchall()
except Exception as e: except Exception as e:
logger = get_provider().get_service(DBLogger) logger = ServiceProviderABC.get_global_service(DBLogger)
logger.fatal(f"Error connecting to the database: {e}") logger.fatal(f"Error connecting to the database: {e}")
finally: finally:
await con.close() await con.close()

View File

@@ -7,7 +7,7 @@ from psycopg_pool import AsyncConnectionPool, PoolTimeout
from cpl.core.environment import Environment from cpl.core.environment import Environment
from cpl.database.logger import DBLogger from cpl.database.logger import DBLogger
from cpl.database.model import DatabaseSettings from cpl.database.model import DatabaseSettings
from cpl.dependency import ServiceProvider from cpl.dependency import ServiceProviderABC
class PostgresPool: class PostgresPool:
@@ -37,7 +37,7 @@ class PostgresPool:
await pool.check_connection(con) await pool.check_connection(con)
except PoolTimeout as e: except PoolTimeout as e:
await pool.close() await pool.close()
logger = get_provider().get_service(DBLogger) logger = ServiceProviderABC.get_global_service(DBLogger)
logger.fatal(f"Failed to connect to the database", e) logger.fatal(f"Failed to connect to the database", e)
self._pool = pool self._pool = pool

View File

@@ -1,11 +1,11 @@
from cpl.database.abc.data_seeder_abc import DataSeederABC from cpl.database.abc.data_seeder_abc import DataSeederABC
from cpl.database.logger import DBLogger from cpl.database.logger import DBLogger
from cpl.dependency import ServiceProvider from cpl.dependency import ServiceProviderABC
class SeederService: class SeederService:
def __init__(self, provider: ServiceProvider): def __init__(self, provider: ServiceProviderABC):
self._provider = provider self._provider = provider
self._logger = provider.get_service(DBLogger) self._logger = provider.get_service(DBLogger)

View File

@@ -1,7 +1,7 @@
from .context import get_provider, use_provider from .scope import Scope
from .inject import inject from .scope_abc import ScopeABC
from .service_collection import ServiceCollection from .service_collection import ServiceCollection
from .service_descriptor import ServiceDescriptor from .service_descriptor import ServiceDescriptor
from .service_lifetime import ServiceLifetimeEnum from .service_lifetime_enum import ServiceLifetimeEnum
from .service_provider import ServiceProvider
from .service_provider import ServiceProvider from .service_provider import ServiceProvider
from .service_provider_abc import ServiceProviderABC

View File

@@ -1,21 +0,0 @@
import contextvars
from contextlib import contextmanager
_current_provider = contextvars.ContextVar("current_provider", default=None)
def use_root_provider(provider):
_current_provider.set(provider)
@contextmanager
def use_provider(provider):
token = _current_provider.set(provider)
try:
yield
finally:
_current_provider.reset(token)
def get_provider():
return _current_provider.get()

View File

@@ -1,42 +0,0 @@
import functools
from asyncio import iscoroutinefunction
from inspect import signature
from cpl.dependency.context import get_provider
def inject(f=None):
if f is None:
return functools.partial(inject)
if iscoroutinefunction(f):
@functools.wraps(f)
async def async_inner(*args, **kwargs):
from cpl.dependency.service_provider import ServiceProvider
provider: ServiceProvider | None = get_provider()
if provider is None:
raise ValueError(
"No provider in current context. Use 'with use_provider(provider):' to set the provider in the current context."
)
injection = [x for x in provider._build_by_signature(signature(f)) if x is not None]
return await f(*args, *injection, **kwargs)
return async_inner
@functools.wraps(f)
def inner(*args, **kwargs):
from cpl.dependency.service_provider import ServiceProvider
provider: ServiceProvider | None = get_provider()
if provider is None:
raise ValueError(
"No provider in current context. Use 'with use_provider(provider):' to set the provider in the current context."
)
injection = [x for x in provider._build_by_signature(signature(f)) if x is not None]
return f(*args, *injection, **kwargs)
return inner

View File

@@ -0,0 +1,22 @@
from cpl.dependency.scope_abc import ScopeABC
from cpl.dependency.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)
ScopeABC.__init__(self)
def __enter__(self):
return self
def __exit__(self, *args):
self.dispose()
@property
def service_provider(self) -> ServiceProviderABC:
return self._service_provider
def dispose(self):
self._service_provider = None

View File

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

View File

@@ -0,0 +1,18 @@
from cpl.dependency.scope import Scope
from cpl.dependency.scope_abc import ScopeABC
from cpl.dependency.service_provider_abc import ServiceProviderABC
class ScopeBuilder:
r"""Class to build :class:`cpl.dependency.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.dependency.scope.Scope`
"""
return Scope(self._service_provider)

View File

@@ -4,8 +4,9 @@ from cpl.core.log.logger_abc import LoggerABC
from cpl.core.typing import T, Service from cpl.core.typing import T, Service
from cpl.core.utils.cache import Cache from cpl.core.utils.cache import Cache
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_enum import ServiceLifetimeEnum
from cpl.dependency.service_provider import ServiceProvider from cpl.dependency.service_provider import ServiceProvider
from cpl.dependency.service_provider_abc import ServiceProviderABC
class ServiceCollection: class ServiceCollection:
@@ -61,8 +62,9 @@ class ServiceCollection:
self._add_descriptor_by_lifetime(service_type, ServiceLifetimeEnum.transient, service) self._add_descriptor_by_lifetime(service_type, ServiceLifetimeEnum.transient, service)
return self return self
def build(self) -> ServiceProvider: def build(self) -> ServiceProviderABC:
sp = ServiceProvider(self._service_descriptors) sp = ServiceProvider(self._service_descriptors)
ServiceProviderABC.set_global_provider(sp)
return sp return sp
def add_module(self, module: str | object) -> Self: def add_module(self, module: str | object) -> Self:

View File

@@ -1,6 +1,6 @@
from typing import Union, Optional from typing import Union, Optional
from cpl.dependency.service_lifetime import ServiceLifetimeEnum from cpl.dependency.service_lifetime_enum import ServiceLifetimeEnum
class ServiceDescriptor: class ServiceDescriptor:

View File

@@ -1,7 +0,0 @@
from enum import Enum, auto
class ServiceLifetimeEnum(Enum):
singleton = auto()
scoped = auto()
transient = auto()

View File

@@ -0,0 +1,7 @@
from enum import Enum
class ServiceLifetimeEnum(Enum):
singleton = 0
scoped = 1
transient = 2

View File

@@ -1,6 +1,5 @@
import copy import copy
import typing import typing
from contextlib import contextmanager
from inspect import signature, Parameter, Signature from inspect import signature, Parameter, Signature
from typing import Optional, Type from typing import Optional, Type
@@ -8,15 +7,34 @@ 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, R, Source
from cpl.dependency import use_provider from cpl.dependency.scope_abc import ScopeABC
from cpl.dependency.scope_builder import ScopeBuilder
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_enum import ServiceLifetimeEnum
from cpl.dependency.service_provider_abc import ServiceProviderABC
class ServiceProvider: class ServiceProvider(ServiceProviderABC):
def __init__(self, service_descriptors: list[ServiceDescriptor], is_scope: bool = False): r"""Provider for the services
Parameter
---------
service_descriptors: list[:class:`cpl.dependency.service_descriptor.ServiceDescriptor`]
Descriptor of the service
config: :class:`cpl.core.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],
):
ServiceProviderABC.__init__(self)
self._service_descriptors: list[ServiceDescriptor] = service_descriptors self._service_descriptors: list[ServiceDescriptor] = service_descriptors
self._is_scope = is_scope self._scope: Optional[ScopeABC] = None
def _find_service(self, service_type: type) -> Optional[ServiceDescriptor]: def _find_service(self, service_type: type) -> Optional[ServiceDescriptor]:
origin_type = typing.get_origin(service_type) or service_type origin_type = typing.get_origin(service_type) or service_type
@@ -49,7 +67,7 @@ class ServiceProvider:
return descriptor.implementation return descriptor.implementation
implementation = self._build_service(descriptor.service_type, origin_service_type=origin_service_type) implementation = self._build_service(descriptor.service_type, origin_service_type=origin_service_type)
if descriptor.lifetime in (ServiceLifetimeEnum.singleton, ServiceLifetimeEnum.scoped): if descriptor.lifetime == ServiceLifetimeEnum.singleton:
descriptor.implementation = implementation descriptor.implementation = implementation
return implementation return implementation
@@ -67,7 +85,7 @@ class ServiceProvider:
implementation = self._build_service( implementation = self._build_service(
descriptor.service_type, origin_service_type=service_type, **kwargs descriptor.service_type, origin_service_type=service_type, **kwargs
) )
if descriptor.lifetime in (ServiceLifetimeEnum.singleton, ServiceLifetimeEnum.scoped): if descriptor.lifetime == ServiceLifetimeEnum.singleton:
descriptor.implementation = implementation descriptor.implementation = implementation
implementations.append(implementation) implementations.append(implementation)
@@ -85,7 +103,7 @@ class ServiceProvider:
elif parameter.annotation == Source: elif parameter.annotation == Source:
params.append(origin_service_type.__name__) params.append(origin_service_type.__name__)
elif issubclass(parameter.annotation, ServiceProvider): elif issubclass(parameter.annotation, ServiceProviderABC):
params.append(self) params.append(self)
elif issubclass(parameter.annotation, Environment): elif issubclass(parameter.annotation, Environment):
@@ -113,27 +131,32 @@ class ServiceProvider:
service_type = type(descriptor.implementation) service_type = type(descriptor.implementation)
else: else:
service_type = descriptor.service_type service_type = descriptor.service_type
break break
sig = signature(service_type.__init__) sig = signature(service_type.__init__)
params = self._build_by_signature(sig, origin_service_type) params = self._build_by_signature(sig, origin_service_type)
return service_type(*params, *args, **kwargs) return service_type(*params, *args, **kwargs)
@contextmanager def set_scope(self, scope: ScopeABC):
def create_scope(self): self._scope = scope
scoped_descriptors = []
for d in self._service_descriptors:
if d.lifetime == ServiceLifetimeEnum.singleton:
scoped_descriptors.append(d)
else:
scoped_descriptors.append(copy.deepcopy(d))
scoped_provider = ServiceProvider(scoped_descriptors, is_scope=True) def create_scope(self) -> ScopeABC:
with use_provider(scoped_provider): descriptors = []
yield scoped_provider
for descriptor in self._service_descriptors:
if descriptor.lifetime == ServiceLifetimeEnum.singleton:
descriptors.append(descriptor)
else:
descriptors.append(copy.deepcopy(descriptor))
sb = ScopeBuilder(ServiceProvider(descriptors))
return sb.build()
def get_service(self, service_type: T, *args, **kwargs) -> Optional[R]: def get_service(self, service_type: T, *args, **kwargs) -> Optional[R]:
result = self._find_service(service_type) result = self._find_service(service_type)
if result is None: if result is None:
return None return None
@@ -141,10 +164,11 @@ class ServiceProvider:
return result.implementation return result.implementation
implementation = self._build_service(service_type, *args, **kwargs) implementation = self._build_service(service_type, *args, **kwargs)
if (
if result.lifetime == ServiceLifetimeEnum.singleton: result.lifetime == ServiceLifetimeEnum.singleton
result.implementation = implementation or result.lifetime == ServiceLifetimeEnum.scoped
elif result.lifetime == ServiceLifetimeEnum.scoped and self._is_scope: and self._scope is not None
):
result.implementation = implementation result.implementation = implementation
return implementation return implementation
@@ -157,9 +181,12 @@ class ServiceProvider:
def get_services(self, service_type: T, *args, **kwargs) -> list[Optional[R]]: def get_services(self, service_type: T, *args, **kwargs) -> list[Optional[R]]:
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))
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]]:

View File

@@ -0,0 +1,165 @@
import functools
from abc import abstractmethod, ABC
from inspect import Signature, signature, iscoroutinefunction
from typing import Optional, Type
from cpl.core.typing import T, R
from cpl.dependency.scope_abc import ScopeABC
class ServiceProviderABC(ABC):
r"""ABC for the class :class:`cpl.dependency.service_provider.ServiceProvider`"""
_provider: Optional["ServiceProviderABC"] = None
@abstractmethod
def __init__(self): ...
@classmethod
def set_global_provider(cls, provider: "ServiceProviderABC"):
cls._provider = provider
@classmethod
def get_global_provider(cls) -> Optional["ServiceProviderABC"]:
return cls._provider
@classmethod
def get_global_service(cls, instance_type: Type[T], *args, **kwargs) -> Optional[T]:
if cls._provider is None:
return None
return cls._provider.get_service(instance_type, *args, **kwargs)
@classmethod
def get_global_services(cls, instance_type: Type[T], *args, **kwargs) -> list[Optional[T]]:
if cls._provider is None:
return []
return cls._provider.get_services(instance_type, *args, **kwargs)
@abstractmethod
def _build_by_signature(self, sig: Signature, origin_service_type: type = None) -> list[T]: ...
@abstractmethod
def _build_service(self, service_type: type, *args, **kwargs) -> object:
r"""Creates instance of given type
Parameter
---------
instance_type: :class:`type`
The type of the searched instance
Returns
-------
Object of the given type
"""
@abstractmethod
def set_scope(self, scope: ScopeABC):
r"""Sets the scope of service provider
Parameter
---------
Object of type :class:`cpl.dependency.scope_abc.ScopeABC`
Service scope
"""
@abstractmethod
def create_scope(self) -> ScopeABC:
r"""Creates a service scope
Returns
-------
Object of type :class:`cpl.dependency.scope_abc.ScopeABC`
"""
@abstractmethod
def get_service(self, instance_type: Type[T], *args, **kwargs) -> Optional[T]:
r"""Returns instance of given type
Parameter
---------
instance_type: :class:`cpl.core.type.T`
The type of the searched instance
Returns
-------
Object of type Optional[:class:`cpl.core.type.T`]
"""
@abstractmethod
def get_service_type(self, instance_type: Type[T]) -> Optional[Type[T]]:
r"""Returns the registered service type for loggers
Parameter
---------
instance_type: :class:`cpl.core.type.T`
The type of the searched instance
Returns
-------
Object of type Optional[:class:`type`]
"""
@abstractmethod
def get_services(self, service_type: Type[T], *args, **kwargs) -> list[Optional[T]]:
r"""Returns instance of given type
Parameter
---------
service_type: :class:`cpl.core.type.T`
The type of the searched instance
Returns
-------
Object of type list[Optional[:class:`cpl.core.type.T`]
"""
@abstractmethod
def get_service_types(self, service_type: Type[T]) -> list[Type[T]]:
r"""Returns all registered service types
Parameter
---------
service_type: :class:`cpl.core.type.T`
The type of the searched instance
Returns
-------
Object of type list[:class:`type`]
"""
@classmethod
def inject(cls, f=None):
r"""Decorator to allow injection into static and class methods
Parameter
---------
f: Callable
Returns
-------
function
"""
if f is None:
return functools.partial(cls.inject)
if iscoroutinefunction(f):
@functools.wraps(f)
async def async_inner(*args, **kwargs):
if cls._provider is None:
raise Exception(f"{cls.__name__} not build!")
injection = [x for x in cls._provider._build_by_signature(signature(f)) if x is not None]
return await f(*args, *injection, **kwargs)
return async_inner
@functools.wraps(f)
def inner(*args, **kwargs):
if cls._provider is None:
raise Exception(f"{cls.__name__} not build!")
injection = [x for x in cls._provider._build_by_signature(signature(f)) if x is not None]
return f(*args, *injection, **kwargs)
return inner

View File

@@ -1 +1 @@
from .sequence import Sequence from .sequence import Sequence

View File

@@ -2,7 +2,7 @@ import unittest
from cpl.application import ApplicationABC from cpl.application import ApplicationABC
from cpl.core.configuration import ConfigurationABC from cpl.core.configuration import ConfigurationABC
from cpl.dependency import ServiceProvider from cpl.dependency import ServiceProviderABC
from unittests_cli.cli_test_suite import CLITestSuite from unittests_cli.cli_test_suite import CLITestSuite
from unittests_core.core_test_suite import CoreTestSuite from unittests_core.core_test_suite import CoreTestSuite
from unittests_query.query_test_suite import QueryTestSuite from unittests_query.query_test_suite import QueryTestSuite
@@ -10,7 +10,7 @@ from unittests_translation.translation_test_suite import TranslationTestSuite
class Application(ApplicationABC): class Application(ApplicationABC):
def __init__(self, config: ConfigurationABC, services: ServiceProvider): def __init__(self, config: ConfigurationABC, services: ServiceProviderABC):
ApplicationABC.__init__(self, config, services) ApplicationABC.__init__(self, config, services)
def configure(self): ... def configure(self): ...

View File

@@ -2,7 +2,7 @@ import unittest
from unittest.mock import Mock from unittest.mock import Mock
from cpl.core.configuration import Configuration from cpl.core.configuration import Configuration
from cpl.dependency import ServiceCollection, ServiceLifetimeEnum, ServiceProvider from cpl.dependency import ServiceCollection, ServiceLifetimeEnum, ServiceProviderABC
class ServiceCollectionTestCase(unittest.TestCase): class ServiceCollectionTestCase(unittest.TestCase):
@@ -51,6 +51,6 @@ class ServiceCollectionTestCase(unittest.TestCase):
service = self._sc._service_descriptors[0] service = self._sc._service_descriptors[0]
self.assertIsNone(service.implementation) self.assertIsNone(service.implementation)
sp = self._sc.build() sp = self._sc.build()
self.assertTrue(isinstance(sp, ServiceProvider)) self.assertTrue(isinstance(sp, ServiceProviderABC))
self.assertTrue(isinstance(sp.get_service(Mock), Mock)) self.assertTrue(isinstance(sp.get_service(Mock), Mock))
self.assertIsNotNone(service.implementation) self.assertIsNotNone(service.implementation)

View File

@@ -1,7 +1,7 @@
import unittest import unittest
from cpl.core.configuration import Configuration from cpl.core.configuration import Configuration
from cpl.dependency import ServiceCollection, ServiceProvider from cpl.dependency import ServiceCollection, ServiceProviderABC
class ServiceCount: class ServiceCount:
@@ -10,21 +10,21 @@ class ServiceCount:
class TestService: class TestService:
def __init__(self, sp: ServiceProvider, count: ServiceCount): def __init__(self, sp: ServiceProviderABC, count: ServiceCount):
count.count += 1 count.count += 1
self.sp = sp self.sp = sp
self.id = count.count self.id = count.count
class DifferentService: class DifferentService:
def __init__(self, sp: ServiceProvider, count: ServiceCount): def __init__(self, sp: ServiceProviderABC, count: ServiceCount):
count.count += 1 count.count += 1
self.sp = sp self.sp = sp
self.id = count.count self.id = count.count
class MoreDifferentService: class MoreDifferentService:
def __init__(self, sp: ServiceProvider, count: ServiceCount): def __init__(self, sp: ServiceProviderABC, count: ServiceCount):
count.count += 1 count.count += 1
self.sp = sp self.sp = sp
self.id = count.count self.id = count.count
@@ -72,7 +72,7 @@ class ServiceProviderTestCase(unittest.TestCase):
singleton = self._services.get_service(TestService) singleton = self._services.get_service(TestService)
transient = self._services.get_service(DifferentService) transient = self._services.get_service(DifferentService)
with self._services.create_scope() as scope: with self._services.create_scope() as scope:
sp: ServiceProvider = scope.service_provider sp: ServiceProviderABC = scope.service_provider
self.assertNotEqual(sp, self._services) self.assertNotEqual(sp, self._services)
y = sp.get_service(DifferentService) y = sp.get_service(DifferentService)
self.assertIsNotNone(y) self.assertIsNotNone(y)