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.core.console.console import Console
from cpl.dependency import ServiceProvider
from cpl.dependency.scope import Scope
from di.static_test import StaticTest
from di.test_abc import TestABC
from di.test_service import TestService
@@ -17,26 +16,30 @@ class Application(ApplicationABC):
ts: TestService = self._services.get_service(TestService)
ts.run()
def configure(self): ...
def main(self):
with self._services.create_scope() as scope:
Console.write_line("Scope1")
ts: TestService = scope.service_provider.get_service(TestService)
ts: TestService = scope.get_service(TestService)
ts.run()
dit: DITesterService = scope.service_provider.get_service(DITesterService)
dit: DITesterService = scope.get_service(DITesterService)
dit.run()
if ts.name != dit.name:
raise Exception("DI is broken!")
with self._services.create_scope() as scope:
Console.write_line("Scope2")
ts: TestService = scope.service_provider.get_service(TestService)
ts: TestService = scope.get_service(TestService)
ts.run()
dit: DITesterService = scope.service_provider.get_service(DITesterService)
dit: DITesterService = scope.get_service(DITesterService)
dit.run()
if ts.name != dit.name:
raise Exception("DI is broken!")
Console.write_line("Global")
self._part_of_scoped()
StaticTest.test()
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):
self._ts = ts
@property
def name(self) -> str:
return self._ts.name
def run(self):
Console.write_line("DIT: ")
self._ts.run()

View File

@@ -12,9 +12,11 @@ class Startup(StartupABC):
def __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(DITesterService)

View File

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

View File

@@ -6,7 +6,7 @@ from di.test_abc import TestABC
class Test2Service(TestABC):
def __init__(self):
TestABC.__init__(self, String.random_string(string.ascii_lowercase, 8))
TestABC.__init__(self, String.random(8))
def run(self):
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.utils.string import String
@@ -8,5 +6,9 @@ class TestService:
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

@@ -9,7 +9,8 @@ from cpl.core.environment import Environment
from cpl.core.log import LoggerABC
from cpl.core.pipes import IPAddressPipe
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_settings import TestSettings
@@ -38,7 +39,7 @@ class Application(ApplicationABC):
def main(self):
self._logger.debug(f"Host: {Environment.get_host_name()}")
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")
test: TestService = self._services.get_service(TestService)
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(self._services.get_service(LoggerABC))
scope = self._services.create_scope()
Console.write_line("scope", scope)
with self._services.create_scope() as s:
Console.write_line("with scope", s)
root_scoped_service = self._services.get_service(ScopedService)
with self._services.create_scope() as scope:
s_srvc1 = scope.get_service(ScopedService)
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)
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.pipes import IPAddressPipe
from cpl.dependency import ServiceCollection
from general.scoped_service import ScopedService
from test_service import TestService
@@ -21,3 +22,4 @@ class Startup(StartupABC):
services.add_module(mail)
services.add_transient(IPAddressPipe)
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 = []
if letters:
characters.append(string.ascii_letters)
characters.extend(string.ascii_letters)
if digits:
characters.append(string.digits)
characters.extend(string.digits)
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
from contextlib import contextmanager
from inspect import signature, Parameter, Signature
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.environment import Environment
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_lifetime import ServiceLifetimeEnum
@@ -132,6 +135,19 @@ class ServiceProvider:
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]:
result = self._find_service(service_type)
@@ -143,7 +159,7 @@ class ServiceProvider:
implementation = self._build_service(service_type, *args, **kwargs)
if (
result.lifetime == ServiceLifetimeEnum.singleton
result.lifetime in (ServiceLifetimeEnum.singleton, ServiceLifetimeEnum.scoped)
):
result.implementation = implementation