Skip to content

Commit d9ca03f

Browse files
authored
Merge pull request #217 from python-ellar/application_context
fix: Application Context Round Usage
2 parents 47996ec + dc58bcc commit d9ca03f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+299
-273
lines changed

ellar/app/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from .context import config, current_injector
1+
from ellar.core.context import config, current_injector
2+
23
from .factory import AppFactory
34
from .main import App
45

ellar/app/factory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@
77
from ellar.common.constants import MODULE_METADATA, MODULE_WATERMARK
88
from ellar.common.models import GuardCanActivate
99
from ellar.core import Config, DynamicModule, LazyModuleImport, ModuleBase, ModuleSetup
10+
from ellar.core.context import ApplicationContext
1011
from ellar.core.modules import ModuleRefBase
1112
from ellar.di import EllarInjector, ProviderConfig
1213
from ellar.reflect import reflect
1314
from ellar.threading.sync_worker import execute_async_context_manager
1415
from ellar.utils import get_name, get_unique_type
1516
from starlette.routing import BaseRoute, Host, Mount
1617

17-
from .context import ApplicationContext
1818
from .main import App
1919
from .services import EllarAppService
2020

ellar/app/main.py

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@
33
import logging.config
44
import typing as t
55

6-
from ellar.app.context import ApplicationContext
76
from ellar.auth.handlers import AuthenticationHandlerType
87
from ellar.auth.middleware import IdentityMiddleware, SessionMiddleware
9-
from ellar.common import GlobalGuard, IIdentitySchemes
8+
from ellar.common import (
9+
GlobalGuard,
10+
IHostContext,
11+
IHostContextFactory,
12+
IIdentitySchemes,
13+
)
1014
from ellar.common.compatible import cached_property
1115
from ellar.common.constants import (
1216
ELLAR_LOG_FMT_STRING,
@@ -21,12 +25,13 @@
2125
from ellar.common.types import ASGIApp, TReceive, TScope, TSend
2226
from ellar.core.conf import Config
2327
from ellar.core.connection import Request
28+
from ellar.core.context import ApplicationContext
2429
from ellar.core.middleware import (
2530
CORSMiddleware,
2631
ExceptionMiddleware,
2732
Middleware,
28-
RequestServiceProviderMiddleware,
2933
RequestVersioningMiddleware,
34+
ServerErrorMiddleware,
3035
TrustedHostMiddleware,
3136
)
3237
from ellar.core.routing import ApplicationRouter, AppStaticFileMount
@@ -151,6 +156,7 @@ def debug(self, value: bool) -> None:
151156
def build_middleware_stack(self) -> ASGIApp:
152157
service_middleware = self.injector.get(IExceptionMiddlewareService)
153158
service_middleware.build_exception_handlers(*self._exception_handlers)
159+
154160
error_handler = service_middleware.get_500_error_handler()
155161
allowed_hosts = self.config.ALLOWED_HOSTS
156162

@@ -175,7 +181,7 @@ def build_middleware_stack(self) -> ASGIApp:
175181
max_age=self.config.CORS_MAX_AGE,
176182
),
177183
Middleware(
178-
RequestServiceProviderMiddleware,
184+
ServerErrorMiddleware,
179185
debug=self.debug,
180186
handler=error_handler,
181187
),
@@ -219,19 +225,25 @@ def application_context(self) -> ApplicationContext:
219225
return ApplicationContext.create(app=self)
220226

221227
async def __call__(self, scope: TScope, receive: TReceive, send: TSend) -> None:
222-
async with self.application_context():
228+
async with self.application_context() as context:
223229
scope["app"] = self
224230

225-
if self.middleware_stack is None:
226-
self.middleware_stack = self.build_middleware_stack()
231+
async with context.injector.create_asgi_args() as service_provider:
232+
context_factory = service_provider.get(IHostContextFactory)
233+
service_provider.update_scoped_context(
234+
IHostContext, context_factory.create_context(scope)
235+
)
236+
237+
if self.middleware_stack is None:
238+
self.middleware_stack = self.build_middleware_stack()
227239

228-
if (
229-
self.config.STATIC_MOUNT_PATH
230-
and self.config.STATIC_MOUNT_PATH not in self.router.routes
231-
):
232-
self.router.append(AppStaticFileMount(self))
240+
if (
241+
self.config.STATIC_MOUNT_PATH
242+
and self.config.STATIC_MOUNT_PATH not in self.router.routes
243+
):
244+
self.router.add_route(AppStaticFileMount(self))
233245

234-
await self.middleware_stack(scope, receive, send)
246+
await self.middleware_stack(scope, receive, send)
235247

236248
@property
237249
def routes(self) -> t.List[BaseRoute]:
@@ -294,6 +306,7 @@ def get_module_loaders(self) -> t.Generator[ModuleTemplating, None, None]:
294306
yield loader
295307

296308
def _create_jinja_environment(self) -> Environment:
309+
# TODO: rename to `create_jinja_environment`
297310
def select_jinja_auto_escape(filename: str) -> bool:
298311
if filename is None: # pragma: no cover
299312
return True

ellar/core/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from .conf import Config, ConfigDefaultTypesMixin
44
from .connection import HTTPConnection, Request, WebSocket
5+
from .context import ApplicationContext, config, current_injector
56
from .execution_context import ExecutionContext, HostContext
67
from .guards import GuardConsumer
78
from .interceptors import EllarInterceptorConsumer
@@ -29,6 +30,9 @@
2930
"VersioningSchemes",
3031
"mount",
3132
"host",
33+
"current_injector",
34+
"config",
35+
"ApplicationContext",
3236
]
3337

3438

ellar/core/conf/app_settings_models.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,12 @@
1111
from ellar.common.serializer import Serializer, SerializerFilter
1212
from ellar.common.templating import JinjaLoaderType
1313
from ellar.common.types import ASGIApp, TReceive, TScope, TSend
14-
from ellar.core.versioning import DefaultAPIVersioning
14+
from ellar.core.conf.mixins import ConfigDefaultTypesMixin
1515
from ellar.pydantic import ENCODERS_BY_TYPE as encoders_by_type
1616
from ellar.pydantic import field_validator
1717
from starlette.exceptions import HTTPException as StarletteHTTPException
1818
from starlette.websockets import WebSocketClose
1919

20-
from .mixins import ConfigDefaultTypesMixin
21-
2220
if t.TYPE_CHECKING: # pragma: no cover
2321
from ellar.app.main import App
2422

@@ -69,7 +67,7 @@ class ConfigValidationSchema(Serializer, ConfigDefaultTypesMixin):
6967
JINJA_LOADERS: t.List[JinjaLoaderType] = []
7068

7169
# Application route versioning scheme
72-
VERSIONING_SCHEME: IAPIVersioning = DefaultAPIVersioning()
70+
VERSIONING_SCHEME: t.Optional[IAPIVersioning] = None
7371

7472
REDIRECT_SLASHES: bool = False
7573

ellar/core/conf/config.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@
33
from ellar.common.compatible.dict import AttributeDictAccessMixin
44
from ellar.common.constants import ELLAR_CONFIG_MODULE
55
from ellar.common.types import VT
6+
from ellar.core.conf.app_settings_models import ConfigValidationSchema
7+
from ellar.core.conf.mixins import ConfigDefaultTypesMixin
68
from ellar.utils.importer import import_from_string
79
from starlette.config import environ
810

9-
from .app_settings_models import ConfigValidationSchema
10-
from .mixins import ConfigDefaultTypesMixin
11-
1211

1312
class ConfigRuntimeError(RuntimeError):
1413
pass

ellar/core/conf/mixins.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class ConfigDefaultTypesMixin:
3131
JINJA_LOADERS: t.List[t.Union[JinjaLoaderType, t.Any]]
3232

3333
# Application route versioning scheme
34-
VERSIONING_SCHEME: IAPIVersioning
34+
VERSIONING_SCHEME: t.Optional[IAPIVersioning]
3535

3636
# Enable or Disable Application Router route searching by appending backslash
3737
REDIRECT_SLASHES: bool

ellar/core/connection/http.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import typing as t
22

33
from ellar.common import Identity
4-
from ellar.common.constants import SCOPE_SERVICE_PROVIDER
4+
from ellar.core.context import current_injector
55
from starlette.requests import (
66
HTTPConnection as StarletteHTTPConnection,
77
)
@@ -16,10 +16,7 @@
1616
class HTTPConnection(StarletteHTTPConnection):
1717
@property
1818
def service_provider(self) -> "EllarInjector":
19-
assert (
20-
SCOPE_SERVICE_PROVIDER in self.scope
21-
), "RequestServiceProviderMiddleware must be installed to access request.service_provider"
22-
return t.cast("EllarInjector", self.scope[SCOPE_SERVICE_PROVIDER])
19+
return current_injector
2320

2421
@property
2522
def user(self) -> Identity:
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from ellar.common.constants import ELLAR_CONFIG_MODULE
77
from ellar.common.logging import logger
8-
from ellar.core import Config
8+
from ellar.core.conf import Config
99
from ellar.di import EllarInjector
1010
from ellar.events import app_context_started, app_context_teardown
1111
from ellar.utils.functional import SimpleLazyObject, empty

ellar/core/execution_context/host.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@
88
IWebSocketHostContext,
99
)
1010
from ellar.common.compatible import cached_property
11-
from ellar.common.constants import SCOPE_SERVICE_PROVIDER
1211
from ellar.common.interfaces import IHostContext
1312
from ellar.common.types import TReceive, TScope, TSend
13+
from ellar.core.context import current_injector
1414

1515
if t.TYPE_CHECKING: # pragma: no cover
16-
from ellar.core.main import App
16+
from ellar.app.main import App
1717
from ellar.di import EllarInjector
1818

1919

@@ -36,11 +36,7 @@ def __init__(
3636
self.send = send
3737

3838
def get_service_provider(self) -> "EllarInjector":
39-
return self._service_provider
40-
41-
@cached_property
42-
def _service_provider(self) -> "EllarInjector":
43-
return self.scope[SCOPE_SERVICE_PROVIDER] # type:ignore[no-any-return]
39+
return current_injector
4440

4541
@cached_property
4642
def _get_websocket_context(self) -> IWebSocketHostContext:

0 commit comments

Comments
 (0)