New implementation of scopes #186

This commit is contained in:
2025-09-24 21:27:28 +02:00
parent 4c8cd988cc
commit 287f5e3149
12 changed files with 92 additions and 25 deletions

View File

@@ -1,7 +1,6 @@
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 ServiceProvider
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
@@ -17,26 +16,30 @@ class Application(ApplicationABC):
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.service_provider.get_service(TestService) ts: TestService = scope.get_service(TestService)
ts.run() ts.run()
dit: DITesterService = scope.service_provider.get_service(DITesterService) dit: DITesterService = scope.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.service_provider.get_service(TestService) ts: TestService = scope.get_service(TestService)
ts.run() ts.run()
dit: DITesterService = scope.service_provider.get_service(DITesterService) dit: DITesterService = scope.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(list[TestABC])) Console.write_line(self._services.get_services(TestABC))

View File

@@ -6,6 +6,10 @@ 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

@@ -12,9 +12,11 @@ class Startup(StartupABC):
def __init__(self): def __init__(self):
StartupABC.__init__(self) StartupABC.__init__(self)
def configure_configuration(self): ... @staticmethod
def configure_configuration(): ...
def configure_services(self, services: ServiceCollection) -> ServiceProvider: @staticmethod
def configure_services(services: ServiceCollection) -> ServiceProvider:
services.add_scoped(TestService) services.add_scoped(TestService)
services.add_scoped(DITesterService) services.add_scoped(DITesterService)

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_string(string.ascii_lowercase, 8)) TestABC.__init__(self, String.random(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_string(string.ascii_lowercase, 8)) TestABC.__init__(self, String.random(8))
def run(self): def run(self):
Console.write_line(f"Im {self._name}") Console.write_line(f"Im {self._name}")

View File

@@ -1,5 +1,3 @@
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
@@ -8,5 +6,9 @@ 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

@@ -9,7 +9,8 @@ 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.extension.list import List from cpl.query 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
@@ -38,7 +39,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(int, range(0, 10)).select(lambda x: f"x={x}").to_list()) Console.write_line(List(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)
@@ -48,10 +49,21 @@ 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))
scope = self._services.create_scope() root_scoped_service = self._services.get_service(ScopedService)
Console.write_line("scope", scope) with self._services.create_scope() as scope:
with self._services.create_scope() as s: s_srvc1 = scope.get_service(ScopedService)
Console.write_line("with scope", s) s_srvc2 = scope.get_service(ScopedService)
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

@@ -0,0 +1,10 @@
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,6 +4,7 @@ from cpl.core.configuration import Configuration
from cpl.core.environment import Environment from cpl.core.environment import Environment
from cpl.core.pipes import IPAddressPipe from cpl.core.pipes import IPAddressPipe
from cpl.dependency import ServiceCollection from cpl.dependency import ServiceCollection
from general.scoped_service import ScopedService
from test_service import TestService from test_service import TestService
@@ -21,3 +22,4 @@ 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

@@ -0,0 +1,13 @@
from cpl.api.abc import ASGIMiddleware
from cpl.dependency.service_provider import ServiceProvider
class ScopeMiddleware(ASGIMiddleware):
def __init__(self, app, provider: ServiceProvider):
ASGIMiddleware.__init__(self, app)
self._app = app
self._provider = provider
async def __call__(self, scope, receive, send):
with self._provider.create_scope():
await self._app(scope, receive, send)

View File

@@ -114,12 +114,15 @@ class String:
characters = [] characters = []
if letters: if letters:
characters.append(string.ascii_letters) characters.extend(string.ascii_letters)
if digits: if digits:
characters.append(string.digits) characters.extend(string.digits)
if special_characters: if special_characters:
characters.append(string.punctuation) characters.extend(string.punctuation)
return "".join(random.choice(characters) for _ in range(length)) if characters else "" x = "".join(random.choice(list(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

@@ -1,4 +1,6 @@
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
@@ -6,6 +8,7 @@ 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.service_descriptor import ServiceDescriptor from cpl.dependency.service_descriptor import ServiceDescriptor
from cpl.dependency.service_lifetime import ServiceLifetimeEnum from cpl.dependency.service_lifetime import ServiceLifetimeEnum
@@ -132,6 +135,19 @@ class ServiceProvider:
return service_type(*params, *args, **kwargs) return service_type(*params, *args, **kwargs)
@contextmanager
def create_scope(self):
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)
with use_provider(scoped_provider):
yield scoped_provider
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)
@@ -143,7 +159,7 @@ class ServiceProvider:
implementation = self._build_service(service_type, *args, **kwargs) implementation = self._build_service(service_type, *args, **kwargs)
if ( if (
result.lifetime == ServiceLifetimeEnum.singleton result.lifetime in (ServiceLifetimeEnum.singleton, ServiceLifetimeEnum.scoped)
): ):
result.implementation = implementation result.implementation = implementation