Skip to content

Commit 7455f09

Browse files
committed
Refactored context creation to follow the contextvar switch
1 parent f4a062a commit 7455f09

File tree

21 files changed

+354
-262
lines changed

21 files changed

+354
-262
lines changed

ellar/core/connection/http.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,16 @@
88
from ellar.constants import SCOPE_SERVICE_PROVIDER
99

1010
if t.TYPE_CHECKING: # pragma: no cover
11-
from ellar.di.injector import RequestServiceProvider
11+
from ellar.di import EllarInjector
1212

1313

1414
class HTTPConnection(StarletteHTTPConnection):
1515
@property
16-
def service_provider(self) -> "RequestServiceProvider":
16+
def service_provider(self) -> "EllarInjector":
1717
assert (
1818
SCOPE_SERVICE_PROVIDER in self.scope
1919
), "RequestServiceProviderMiddleware must be installed to access request.service_provider"
20-
return t.cast("RequestServiceProvider", self.scope[SCOPE_SERVICE_PROVIDER])
20+
return t.cast("EllarInjector", self.scope[SCOPE_SERVICE_PROVIDER])
2121

2222

2323
class Request(StarletteRequest, HTTPConnection):

ellar/core/context/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
from .exceptions import HostContextException
22
from .execution import ExecutionContext
3+
from .factory import ExecutionContextFactory, HostContextFactory
34
from .host import HostContext
45
from .interface import (
56
IExecutionContext,
7+
IExecutionContextFactory,
68
IHostContext,
9+
IHostContextFactory,
710
IHTTPHostContext,
811
IWebSocketHostContext,
912
)
@@ -16,4 +19,8 @@
1619
"IWebSocketHostContext",
1720
"HostContext",
1821
"HostContextException",
22+
"IExecutionContextFactory",
23+
"IHostContextFactory",
24+
"ExecutionContextFactory",
25+
"HostContextFactory",
1926
]

ellar/core/context/execution.py

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,16 @@
11
import typing as t
22

33
from ellar.compatible import cached_property
4-
from ellar.constants import CONTROLLER_CLASS_KEY, SCOPE_SERVICE_PROVIDER
4+
from ellar.constants import CONTROLLER_CLASS_KEY
55
from ellar.di import injectable
6-
from ellar.di.providers import ModuleProvider
76
from ellar.services.reflector import Reflector
87
from ellar.types import TReceive, TScope, TSend
98

10-
from .exceptions import ExecutionContextException
119
from .host import HostContext
1210
from .interface import IExecutionContext
1311

1412
if t.TYPE_CHECKING: # pragma: no cover
1513
from ellar.core.controller import ControllerBase
16-
from ellar.core.routing import RouteOperationBase
17-
from ellar.di.injector import RequestServiceProvider
1814

1915

2016
@injectable()
@@ -49,33 +45,3 @@ def _get_class(self) -> t.Optional[t.Type["ControllerBase"]]:
4945

5046
def get_class(self) -> t.Optional[t.Type["ControllerBase"]]:
5147
return self._get_class # type: ignore
52-
53-
@classmethod
54-
def create_context(
55-
cls,
56-
*,
57-
scope: TScope,
58-
receive: TReceive,
59-
send: TSend,
60-
operation: "RouteOperationBase",
61-
) -> IExecutionContext:
62-
provider = ModuleProvider(
63-
cls,
64-
scope=scope,
65-
receive=receive,
66-
send=send,
67-
operation_handler=operation.endpoint,
68-
)
69-
request_provider: "RequestServiceProvider" = t.cast(
70-
"RequestServiceProvider", scope.get(SCOPE_SERVICE_PROVIDER)
71-
)
72-
if not request_provider:
73-
raise ExecutionContextException(
74-
"RequestServiceProvider is not configured. "
75-
"Please ensure `RequestServiceProviderMiddleware` is registered."
76-
)
77-
78-
request_provider.update_context(IExecutionContext, provider)
79-
execution_context = request_provider.get(IExecutionContext) # type: ignore
80-
81-
return execution_context

ellar/core/context/factory.py

Lines changed: 76 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,59 @@
11
import typing as t
22
from abc import ABC, abstractmethod
33

4+
from ellar.constants import ASGI_CONTEXT_VAR
5+
from ellar.di import injectable
6+
from ellar.di.exceptions import ServiceUnavailable
7+
from ellar.services import Reflector
48
from ellar.types import T
59

610
from .exceptions import HostContextException
11+
from .execution import ExecutionContext
12+
from .host import HostContext
713
from .http import HTTPHostContext
8-
from .interface import IHostContext
14+
from .interface import (
15+
IExecutionContext,
16+
IExecutionContextFactory,
17+
IHostContext,
18+
IHostContextFactory,
19+
)
920
from .websocket import WebSocketHostContext
1021

22+
if t.TYPE_CHECKING: # pragma: no cover
23+
from ellar.core.routing import RouteOperationBase
1124

12-
class HostContextFactory(t.Generic[T], ABC):
25+
26+
class SubHostContextFactory(t.Generic[T], ABC):
1327
"""
1428
Factory for creating HostContext types and validating them.
1529
"""
1630

1731
context_type: t.Type[T]
1832

19-
__slots__ = ("context", "_context_type", "validate_func")
20-
21-
def __init__(
22-
self,
23-
context: IHostContext,
24-
) -> None:
25-
self.context = context
33+
__slots__ = ()
2634

27-
def __call__(self) -> T:
28-
self.validate()
29-
return self.create_context_type()
35+
@injectable()
36+
def __call__(self, context: IHostContext) -> T:
37+
self.validate(context)
38+
return self.create_context_type(context)
3039

3140
@abstractmethod
32-
def validate(self) -> None:
41+
def validate(self, context: IHostContext) -> None:
3342
pass
3443

35-
def create_context_type(self) -> T:
36-
scope, receive, send = self.context.get_args()
44+
def create_context_type(self, context: IHostContext) -> T:
45+
scope, receive, send = context.get_args()
3746
return self.context_type( # type:ignore
3847
scope=scope,
3948
receive=receive,
4049
send=send,
4150
)
4251

4352

44-
class HTTPConnectionContextFactory(HostContextFactory[HTTPHostContext]):
53+
class HTTPConnectionContextFactory(SubHostContextFactory[HTTPHostContext]):
4554
context_type = HTTPHostContext
4655

47-
def validate(self) -> None:
56+
def validate(self, context: IHostContext) -> None:
4857
"""
4958
Validation is skipped here because HTTPConnection is compatible with websocket and http
5059
During websocket connection, we can still get HTTPConnection available,
@@ -53,11 +62,57 @@ def validate(self) -> None:
5362
pass
5463

5564

56-
class WebSocketContextFactory(HostContextFactory[WebSocketHostContext]):
65+
class WebSocketContextFactory(SubHostContextFactory[WebSocketHostContext]):
5766
context_type = WebSocketHostContext
5867

59-
def validate(self) -> None:
60-
if self.context.get_type() != "websocket":
68+
def validate(self, context: IHostContext) -> None:
69+
if context.get_type() != "websocket":
6170
raise HostContextException(
62-
f"WebsocketConnection Context creation is not allow for scope[type]={self.context.get_type()}"
71+
f"WebsocketConnection Context creation is not allow for scope[type]={context.get_type()}"
6372
)
73+
74+
75+
@injectable()
76+
class HostContextFactory(IHostContextFactory):
77+
__slots__ = ()
78+
79+
def create_context(self) -> IHostContext:
80+
scoped_request_args = ASGI_CONTEXT_VAR.get()
81+
82+
if not scoped_request_args:
83+
raise ServiceUnavailable()
84+
85+
scope, receive, send = scoped_request_args.get_args()
86+
host_context = HostContext(scope=scope, receive=receive, send=send)
87+
host_context.get_service_provider().update_scoped_context(
88+
IHostContext, host_context # type: ignore
89+
)
90+
return host_context
91+
92+
93+
@injectable()
94+
class ExecutionContextFactory(IExecutionContextFactory):
95+
__slots__ = ("reflector",)
96+
97+
def __init__(self, reflector: Reflector) -> None:
98+
self.reflector = reflector
99+
100+
def create_context(self, operation: "RouteOperationBase") -> IExecutionContext:
101+
scoped_request_args = ASGI_CONTEXT_VAR.get()
102+
103+
if not scoped_request_args:
104+
raise ServiceUnavailable()
105+
106+
scope, receive, send = scoped_request_args.get_args()
107+
i_execution_context = ExecutionContext(
108+
scope=scope,
109+
receive=receive,
110+
send=send,
111+
operation_handler=operation.endpoint,
112+
reflector=self.reflector,
113+
)
114+
i_execution_context.get_service_provider().update_scoped_context(
115+
IExecutionContext, i_execution_context # type: ignore
116+
)
117+
118+
return i_execution_context

ellar/core/context/host.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
if t.TYPE_CHECKING: # pragma: no cover
1010
from ellar.core.main import App
11-
from ellar.di.injector import RequestServiceProvider
11+
from ellar.di import EllarInjector
1212

1313

1414
class HostContext(IHostContext):
@@ -29,11 +29,11 @@ def __init__(
2929
self.receive = receive
3030
self.send = send
3131

32-
def get_service_provider(self) -> "RequestServiceProvider":
32+
def get_service_provider(self) -> "EllarInjector":
3333
return self._service_provider # type:ignore
3434

3535
@cached_property
36-
def _service_provider(self) -> "RequestServiceProvider":
36+
def _service_provider(self) -> "EllarInjector":
3737
return self.scope[SCOPE_SERVICE_PROVIDER] # type:ignore
3838

3939
def switch_to_http_connection(self) -> IHTTPHostContext:

ellar/core/context/interface.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
if t.TYPE_CHECKING: # pragma: no cover
99
from ellar.core import ControllerBase
1010
from ellar.core.main import App
11-
from ellar.di.injector import RequestServiceProvider
11+
from ellar.core.routing import RouteOperationBase
12+
from ellar.di.injector import EllarInjector
1213

1314

1415
class IHTTPHostContext(ABC):
@@ -38,7 +39,7 @@ def get_client(self) -> WebSocket:
3839

3940
class IHostContext(ABC, metaclass=ABCMeta):
4041
@abstractmethod
41-
def get_service_provider(self) -> "RequestServiceProvider":
42+
def get_service_provider(self) -> "EllarInjector":
4243
"""Gets RequestServiceProvider instance"""
4344

4445
@abstractmethod
@@ -70,3 +71,15 @@ def get_handler(self) -> t.Callable:
7071
@abstractmethod
7172
def get_class(self) -> t.Optional[t.Type["ControllerBase"]]:
7273
"""Gets operation handler class"""
74+
75+
76+
class IHostContextFactory(ABC):
77+
@abstractmethod
78+
def create_context(self) -> IHostContext:
79+
pass
80+
81+
82+
class IExecutionContextFactory(ABC):
83+
@abstractmethod
84+
def create_context(self, operation: "RouteOperationBase") -> IExecutionContext:
85+
pass

ellar/core/factory.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@
77

88
from ellar.constants import MODULE_METADATA, MODULE_WATERMARK
99
from ellar.core import Config
10-
from ellar.core.exceptions.interfaces import IExceptionMiddlewareService
11-
from ellar.core.exceptions.service import ExceptionMiddlewareService
1210
from ellar.core.main import App
1311
from ellar.core.modules import ModuleBase
1412
from ellar.core.modules.ref import create_module_ref_factor
1513
from ellar.di import EllarInjector, ProviderConfig
1614
from ellar.reflect import reflect
1715

16+
from .services import CoreServiceRegistration
17+
1818
if t.TYPE_CHECKING: # pragma: no cover
1919
from ellar.commands import EllarTyper
2020
from ellar.core import GuardCanActivate
@@ -97,9 +97,8 @@ def _create_app(
9797

9898
config = Config(app_configured=True, config_module=config_module)
9999
injector = EllarInjector(auto_bind=config.INJECTOR_AUTO_BIND)
100-
injector.container.register(
101-
IExceptionMiddlewareService, ExceptionMiddlewareService
102-
)
100+
101+
CoreServiceRegistration(injector).register_all()
103102
injector.container.register_instance(config, concrete_type=Config)
104103
cls._build_modules(app_module=module, injector=injector, config=config)
105104

ellar/core/main.py

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
from ellar.constants import LOG_LEVELS
77
from ellar.core.conf import Config
8-
from ellar.core.context import IExecutionContext, IHostContext
98
from ellar.core.datastructures import State, URLPath
109
from ellar.core.events import EventHandler, RouterEventManager
1110
from ellar.core.exceptions.interfaces import (
@@ -27,9 +26,7 @@
2726
from ellar.core.templating import AppTemplating, Environment
2827
from ellar.core.versioning import VERSIONING, BaseAPIVersioning
2928
from ellar.di.injector import EllarInjector
30-
from ellar.di.providers import ServiceUnavailableProvider
3129
from ellar.logger import logger
32-
from ellar.services.reflector import Reflector
3330
from ellar.types import ASGIApp, T, TReceive, TScope, TSend
3431

3532

@@ -181,9 +178,7 @@ def config(self) -> Config: # type: ignore
181178
return self._config
182179

183180
def build_middleware_stack(self) -> ASGIApp:
184-
service_middleware = self.injector.get(
185-
IExceptionMiddlewareService # type:ignore
186-
)
181+
service_middleware = self.injector.get(IExceptionMiddlewareService)
187182
service_middleware.build_exception_handlers(*self._exception_handlers)
188183
error_handler = service_middleware.get_500_error_handler()
189184
allowed_hosts = self.config.ALLOWED_HOSTS
@@ -264,19 +259,9 @@ def _run_module_application_ready(
264259
module_ref.run_application_ready(self)
265260

266261
def _finalize_app_initialization(self) -> None:
267-
268262
self.injector.container.register_instance(self)
269-
self.injector.container.register_instance(Reflector())
270263
self.injector.container.register_instance(self.config, Config)
271264
self.injector.container.register_instance(self.jinja_environment, Environment)
272-
self.injector.container.register_scoped(
273-
IHostContext,
274-
ServiceUnavailableProvider("Service Unavailable at the current context."),
275-
)
276-
self.injector.container.register_scoped(
277-
IExecutionContext,
278-
ServiceUnavailableProvider("Service Unavailable at the current context."),
279-
)
280265
self._run_module_application_ready()
281266

282267
def add_exception_handler(

0 commit comments

Comments
 (0)