API scoped requests #186
Some checks failed
Test before pr merge / test-lint (pull_request) Failing after 6s
Build on push / prepare (push) Successful in 9s
Build on push / query (push) Successful in 18s
Build on push / core (push) Successful in 21s
Build on push / dependency (push) Successful in 14s
Build on push / api (push) Has been cancelled
Build on push / auth (push) Has been cancelled
Build on push / application (push) Has been cancelled
Build on push / database (push) Has been cancelled
Build on push / mail (push) Has been cancelled
Build on push / translation (push) Has been cancelled
Some checks failed
Test before pr merge / test-lint (pull_request) Failing after 6s
Build on push / prepare (push) Successful in 9s
Build on push / query (push) Successful in 18s
Build on push / core (push) Successful in 21s
Build on push / dependency (push) Successful in 14s
Build on push / api (push) Has been cancelled
Build on push / auth (push) Has been cancelled
Build on push / application (push) Has been cancelled
Build on push / database (push) Has been cancelled
Build on push / mail (push) Has been cancelled
Build on push / translation (push) Has been cancelled
This commit is contained in:
@@ -6,8 +6,10 @@ 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
|
||||||
|
|
||||||
|
|
||||||
@@ -23,6 +25,8 @@ 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)
|
||||||
|
|
||||||
@@ -40,6 +44,32 @@ 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()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,17 @@ 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):
|
async def ping(r: Request, ping: PingService, logger: APILogger, provider: ServiceProvider, scoped: ScopedService):
|
||||||
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))
|
||||||
|
|||||||
14
example/custom/api/src/scoped_service.py
Normal file
14
example/custom/api/src/scoped_service.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
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}")
|
||||||
@@ -62,7 +62,7 @@ class Application(ApplicationABC):
|
|||||||
|
|
||||||
root_scoped_service2 = self._services.get_service(ScopedService)
|
root_scoped_service2 = self._services.get_service(ScopedService)
|
||||||
Console.write_line(root_scoped_service2)
|
Console.write_line(root_scoped_service2)
|
||||||
if root_scoped_service != root_scoped_service2:
|
if root_scoped_service == root_scoped_service2:
|
||||||
raise Exception("Root scoped service should be equal to root scoped service 2")
|
raise Exception("Root scoped service should be equal to root scoped service 2")
|
||||||
|
|
||||||
test_settings = Configuration.get(TestSettings)
|
test_settings = Configuration.get(TestSettings)
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ from cpl.core.configuration import Configuration
|
|||||||
from cpl.dependency.inject import inject
|
from cpl.dependency.inject import inject
|
||||||
from cpl.dependency.service_provider import ServiceProvider
|
from cpl.dependency.service_provider import ServiceProvider
|
||||||
|
|
||||||
|
|
||||||
PolicyInput = Union[dict[str, PolicyResolver], Policy]
|
PolicyInput = Union[dict[str, PolicyResolver], Policy]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
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)
|
|
||||||
@@ -9,15 +9,18 @@ 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.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, logger: APILogger):
|
def __init__(self, app, provider: ServiceProvider, 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
|
||||||
@@ -27,7 +30,8 @@ class RequestMiddleware(ASGIMiddleware):
|
|||||||
await self.set_request_data(request)
|
await self.set_request_data(request)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self._app(scope, receive, send)
|
with self._provider.create_scope():
|
||||||
|
inject(await self._app(scope, receive, send))
|
||||||
finally:
|
finally:
|
||||||
await self.clean_request_data()
|
await self.clean_request_data()
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ def inject(f=None):
|
|||||||
return functools.partial(inject)
|
return functools.partial(inject)
|
||||||
|
|
||||||
if iscoroutinefunction(f):
|
if iscoroutinefunction(f):
|
||||||
|
|
||||||
@functools.wraps(f)
|
@functools.wraps(f)
|
||||||
async def async_inner(*args, **kwargs):
|
async def async_inner(*args, **kwargs):
|
||||||
from cpl.dependency.service_provider import ServiceProvider
|
from cpl.dependency.service_provider import ServiceProvider
|
||||||
|
|||||||
@@ -14,23 +14,9 @@ from cpl.dependency.service_lifetime import ServiceLifetimeEnum
|
|||||||
|
|
||||||
|
|
||||||
class ServiceProvider:
|
class ServiceProvider:
|
||||||
r"""Provider for the services
|
def __init__(self, service_descriptors: list[ServiceDescriptor], is_scope: bool = False):
|
||||||
|
|
||||||
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],
|
|
||||||
):
|
|
||||||
self._service_descriptors: list[ServiceDescriptor] = service_descriptors
|
self._service_descriptors: list[ServiceDescriptor] = service_descriptors
|
||||||
|
self._is_scope = is_scope
|
||||||
|
|
||||||
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
|
||||||
@@ -57,13 +43,13 @@ class ServiceProvider:
|
|||||||
def _get_service(self, parameter: Parameter, origin_service_type: type = None) -> Optional[object]:
|
def _get_service(self, parameter: Parameter, origin_service_type: type = None) -> Optional[object]:
|
||||||
for descriptor in self._service_descriptors:
|
for descriptor in self._service_descriptors:
|
||||||
if descriptor.service_type == parameter.annotation or issubclass(
|
if descriptor.service_type == parameter.annotation or issubclass(
|
||||||
descriptor.service_type, parameter.annotation
|
descriptor.service_type, parameter.annotation
|
||||||
):
|
):
|
||||||
if descriptor.implementation is not None:
|
if descriptor.implementation is not None:
|
||||||
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 == ServiceLifetimeEnum.singleton:
|
if descriptor.lifetime in (ServiceLifetimeEnum.singleton, ServiceLifetimeEnum.scoped):
|
||||||
descriptor.implementation = implementation
|
descriptor.implementation = implementation
|
||||||
|
|
||||||
return implementation
|
return implementation
|
||||||
@@ -81,7 +67,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 == ServiceLifetimeEnum.singleton:
|
if descriptor.lifetime in (ServiceLifetimeEnum.singleton, ServiceLifetimeEnum.scoped):
|
||||||
descriptor.implementation = implementation
|
descriptor.implementation = implementation
|
||||||
|
|
||||||
implementations.append(implementation)
|
implementations.append(implementation)
|
||||||
@@ -127,12 +113,10 @@ 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
|
@contextmanager
|
||||||
@@ -144,13 +128,12 @@ class ServiceProvider:
|
|||||||
else:
|
else:
|
||||||
scoped_descriptors.append(copy.deepcopy(d))
|
scoped_descriptors.append(copy.deepcopy(d))
|
||||||
|
|
||||||
scoped_provider = ServiceProvider(scoped_descriptors)
|
scoped_provider = ServiceProvider(scoped_descriptors, is_scope=True)
|
||||||
with use_provider(scoped_provider):
|
with use_provider(scoped_provider):
|
||||||
yield 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)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -158,9 +141,10 @@ 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 (
|
|
||||||
result.lifetime in (ServiceLifetimeEnum.singleton, ServiceLifetimeEnum.scoped)
|
if result.lifetime == ServiceLifetimeEnum.singleton:
|
||||||
):
|
result.implementation = implementation
|
||||||
|
elif result.lifetime == ServiceLifetimeEnum.scoped and self._is_scope:
|
||||||
result.implementation = implementation
|
result.implementation = implementation
|
||||||
|
|
||||||
return implementation
|
return implementation
|
||||||
@@ -173,12 +157,9 @@ 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]]:
|
||||||
|
|||||||
Reference in New Issue
Block a user