Skip to content

Commit 4f8f29a

Browse files
authored
Merge pull request #53 from eadwinCode/asgi_send_fix
Removed ASGI args from contextvar
2 parents 7a9d451 + 998ce74 commit 4f8f29a

Some content is hidden

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

71 files changed

+1843
-1006
lines changed

docs/index.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ then, start the app with:
5050
$(venv) ellar runserver --reload
5151
```
5252

53-
Open your browser and navigate to [`http://localhost:3000/`](http://localhost:3000/).
53+
Open your browser and navigate to [`http://localhost:8000/`](http://localhost:8000/).
5454
![Swagger UI](img/ellar_framework.png)
5555

5656
## Features Summary
@@ -76,6 +76,7 @@ Open your browser and navigate to [`http://localhost:3000/`](http://localhost:30
7676
Project is still in development
7777

7878
- Documentation - in progress
79+
- Interceptors - [Aspect Oriented Programming](https://en.wikipedia.org/wiki/Aspect-oriented_programming) (AOP) technique
7980
- Database Plugin with [Encode/ORM](https://github.com/encode/orm)
8081
- Caching
8182
- API Throttling

ellar/asgi_args.py

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

3-
from ellar.types import TReceive, TScope, TSend
4-
53
if t.TYPE_CHECKING: # pragma: no cover
64
from ellar.di.providers import Provider
75

86

9-
class ASGIArgs:
10-
__slots__ = ("scope", "receive", "send", "context")
7+
class RequestScopeContext:
8+
__slots__ = ("_injector_scoped_context",)
119

12-
def __init__(self, scope: TScope, receive: TReceive, send: TSend) -> None:
13-
self.scope = scope
14-
self.receive = receive
15-
self.send = send
16-
self.context: t.Dict[t.Type, "Provider"] = {}
10+
def __init__(self) -> None:
11+
self._injector_scoped_context: t.Dict[t.Type, "Provider"] = {}
1712

18-
def get_args(self) -> t.Tuple[TScope, TReceive, TSend]:
19-
return self.scope, self.receive, self.send
13+
@property
14+
def context(self) -> t.Dict[t.Type, "Provider"]:
15+
return self._injector_scoped_context

ellar/common/decorators/controller.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import inspect
22
import typing as t
33
from abc import ABC
4+
from types import FunctionType
45

56
from ellar.compatible import AttributeDict
67
from ellar.constants import (
@@ -14,16 +15,21 @@
1415
from ellar.core import ControllerBase
1516
from ellar.core.controller import ControllerType
1617
from ellar.core.exceptions import ImproperConfiguration
18+
from ellar.core.routing.controller import ControllerRouteOperationBase
1719
from ellar.di import RequestScope, injectable
1820
from ellar.reflect import reflect
1921

2022
if t.TYPE_CHECKING: # pragma: no cover
2123
from ellar.core.guard import GuardCanActivate
2224

2325

24-
def get_route_functions(cls: t.Type) -> t.Iterable[t.Callable]:
26+
def get_route_functions(
27+
cls: t.Type,
28+
) -> t.Iterable[t.Union[t.Callable, ControllerRouteOperationBase]]:
2529
for method in cls.__dict__.values():
26-
if hasattr(method, OPERATION_ENDPOINT_KEY):
30+
if hasattr(method, OPERATION_ENDPOINT_KEY) or isinstance(
31+
method, ControllerRouteOperationBase
32+
):
2733
yield method
2834

2935

@@ -33,8 +39,12 @@ def reflect_all_controller_type_routes(cls: t.Type[ControllerBase]) -> None:
3339
for base_cls in reversed(bases):
3440
if base_cls not in [ABC, ControllerBase, object]:
3541
for item in get_route_functions(base_cls):
36-
operation = reflect.get_metadata(CONTROLLER_OPERATION_HANDLER_KEY, item)
37-
reflect.define_metadata(CONTROLLER_CLASS_KEY, cls, item)
42+
operation = item
43+
if callable(item) and type(item) == FunctionType:
44+
operation = reflect.get_metadata( # type: ignore
45+
CONTROLLER_OPERATION_HANDLER_KEY, item
46+
)
47+
reflect.define_metadata(CONTROLLER_CLASS_KEY, cls, operation.endpoint) # type: ignore
3848
reflect.define_metadata(
3949
CONTROLLER_OPERATION_HANDLER_KEY,
4050
operation,

ellar/common/routing/params.py

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

3-
from pydantic.error_wrappers import ErrorWrapper
43
from pydantic.fields import Undefined
54
from starlette.responses import Response
65

76
from ellar.core.connection import HTTPConnection, Request, WebSocket
87
from ellar.core.context import IExecutionContext
98
from ellar.core.params import params
10-
from ellar.core.params.resolvers import (
11-
BaseRequestRouteParameterResolver,
12-
NonFieldRouteParameterResolver,
13-
ParameterInjectable,
9+
from ellar.core.params.resolvers.non_parameter import (
10+
ConnectionParam,
11+
ExecutionContextParameter,
12+
HostRequestParam,
13+
ProviderParameterInjector,
14+
RequestParameter,
15+
ResponseRequestParam,
16+
SessionRequestParam,
17+
WebSocketParameter,
1418
)
1519
from ellar.types import T
1620

@@ -341,108 +345,44 @@ def WsBody(
341345
)
342346

343347

344-
class _RequestParameter(NonFieldRouteParameterResolver):
345-
async def resolve(
346-
self, ctx: IExecutionContext, **kwargs: t.Any
347-
) -> t.Tuple[t.Dict, t.List]:
348-
try:
349-
request = ctx.switch_to_http_connection().get_request()
350-
return {self.parameter_name: request}, []
351-
except Exception as ex:
352-
return {}, [ErrorWrapper(ex, loc=self.parameter_name or "request")]
353-
354-
355-
class _WebSocketParameter(NonFieldRouteParameterResolver):
356-
async def resolve(
357-
self, ctx: IExecutionContext, **kwargs: t.Any
358-
) -> t.Tuple[t.Dict, t.List]:
359-
try:
360-
websocket = ctx.switch_to_websocket().get_client()
361-
return {self.parameter_name: websocket}, []
362-
except Exception as ex:
363-
return {}, [ErrorWrapper(ex, loc=self.parameter_name or "websocket")]
364-
365-
366-
class _ExecutionContextParameter(NonFieldRouteParameterResolver):
367-
async def resolve(
368-
self, ctx: IExecutionContext, **kwargs: t.Any
369-
) -> t.Tuple[t.Dict, t.List]:
370-
return {self.parameter_name: ctx}, []
371-
372-
373-
class _HostRequestParam(BaseRequestRouteParameterResolver):
374-
lookup_connection_field = None
375-
376-
async def get_value(self, ctx: IExecutionContext) -> t.Any:
377-
connection = ctx.switch_to_http_connection().get_client()
378-
if connection.client:
379-
return connection.client.host
380-
381-
382-
class _SessionRequestParam(BaseRequestRouteParameterResolver):
383-
lookup_connection_field = "session"
384-
385-
386-
class _ConnectionParam(NonFieldRouteParameterResolver):
387-
async def resolve(
388-
self, ctx: IExecutionContext, **kwargs: t.Any
389-
) -> t.Tuple[t.Dict, t.List]:
390-
try:
391-
connection = ctx.switch_to_http_connection().get_client()
392-
return {self.parameter_name: connection}, []
393-
except Exception as ex:
394-
return {}, [ErrorWrapper(ex, loc=self.parameter_name or "connection")]
395-
396-
397-
class _ResponseRequestParam(NonFieldRouteParameterResolver):
398-
async def resolve(
399-
self, ctx: IExecutionContext, **kwargs: t.Any
400-
) -> t.Tuple[t.Dict, t.List]:
401-
try:
402-
response = ctx.switch_to_http_connection().get_response()
403-
return {self.parameter_name: response}, []
404-
except Exception as ex:
405-
return {}, [ErrorWrapper(ex, loc=self.parameter_name or "response")]
406-
407-
408348
def Http() -> HTTPConnection:
409349
"""
410350
Route Function Parameter for retrieving Current Request Instance
411351
:return: Request
412352
"""
413-
return t.cast(Request, _ConnectionParam())
353+
return t.cast(Request, ConnectionParam())
414354

415355

416356
def Req() -> Request:
417357
"""
418358
Route Function Parameter for retrieving Current Request Instance
419359
:return: Request
420360
"""
421-
return t.cast(Request, _RequestParameter())
361+
return t.cast(Request, RequestParameter())
422362

423363

424364
def Ws() -> WebSocket:
425365
"""
426366
Route Function Parameter for retrieving Current WebSocket Instance
427367
:return: WebSocket
428368
"""
429-
return t.cast(WebSocket, _WebSocketParameter())
369+
return t.cast(WebSocket, WebSocketParameter())
430370

431371

432372
def Context() -> IExecutionContext:
433373
"""
434374
Route Function Parameter for retrieving Current IExecutionContext Instance
435375
:return: IExecutionContext
436376
"""
437-
return t.cast(IExecutionContext, _ExecutionContextParameter())
377+
return t.cast(IExecutionContext, ExecutionContextParameter())
438378

439379

440380
def Provide(service: t.Optional[t.Type[T]] = None) -> T:
441381
"""
442382
Route Function Parameter for resolving registered Provider
443383
:return: T
444384
"""
445-
return t.cast(T, ParameterInjectable(service))
385+
return t.cast(T, ProviderParameterInjector(service))
446386

447387

448388
def Session() -> t.Dict:
@@ -451,20 +391,20 @@ def Session() -> t.Dict:
451391
Ensure SessionMiddleware is registered to application middlewares
452392
:return: Dict
453393
"""
454-
return t.cast(t.Dict, _SessionRequestParam())
394+
return t.cast(t.Dict, SessionRequestParam())
455395

456396

457397
def Host() -> str:
458398
"""
459399
Route Function Parameter for resolving registered `HTTPConnection.client.host`
460400
:return: str
461401
"""
462-
return t.cast(str, _HostRequestParam())
402+
return t.cast(str, HostRequestParam())
463403

464404

465405
def Res() -> Response:
466406
"""
467407
Route Function Parameter for resolving registered Response
468408
:return: Response
469409
"""
470-
return t.cast(Response, _ResponseRequestParam())
410+
return t.cast(Response, ResponseRequestParam())

ellar/compatible/contextmanager.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import sys
22

33
if sys.version_info >= (3, 7): # pragma: no cover
4-
from contextlib import asynccontextmanager as asynccontextmanager # noqa
5-
else:
4+
from contextlib import asynccontextmanager as asynccontextmanager
5+
else: # pragma: no cover
66
from contextlib2 import asynccontextmanager as asynccontextmanager # noqa

ellar/compatible/dict.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,13 @@ def __getitem__(self, k: KT) -> VT:
4141
def __len__(self) -> int:
4242
return self._data.__len__()
4343

44-
def __iter__(self) -> t.Iterator[VT]:
44+
def __iter__(self) -> t.Iterator[VT]: # pragma: no cover
4545
return iter(self._data)
4646

4747

4848
class DataMutableMapper(DataMapper, t.MutableMapping[KT, VT]):
4949
def __setitem__(self, k: KT, v: VT) -> None:
5050
self._data.__setitem__(k, v)
5151

52-
def __delitem__(self, v: KT) -> None:
52+
def __delitem__(self, v: KT) -> None: # pragma: no cover
5353
self._data.__delitem__(v)

ellar/constants.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@
1111
SHAPE_TUPLE_ELLIPSIS,
1212
)
1313

14-
from .asgi_args import ASGIArgs
14+
from .asgi_args import RequestScopeContext
1515

16-
ASGI_CONTEXT_VAR: contextvars.ContextVar[Optional[ASGIArgs]] = contextvars.ContextVar(
17-
"ASGI-CONTEXT-VAR"
18-
)
19-
ASGI_CONTEXT_VAR.set(None)
16+
SCOPED_CONTEXT_VAR: contextvars.ContextVar[
17+
Optional[RequestScopeContext]
18+
] = contextvars.ContextVar("SCOPED-CONTEXT-VAR")
19+
SCOPED_CONTEXT_VAR.set(None)
2020

2121

2222
class _AnnotationToValue(type):
@@ -26,7 +26,7 @@ class _AnnotationToValue(type):
2626
def __new__(mcls, name, bases, namespace):
2727
cls = super().__new__(mcls, name, bases, namespace)
2828
annotations = dict()
29-
for base in reversed(bases):
29+
for base in reversed(bases): # pragma: no cover
3030
annotations.update(getattr(base, "__annotations__", {}))
3131
annotations.update(namespace.get("__annotations__", {}))
3232
cls.keys = []
@@ -49,9 +49,8 @@ def __new__(mcls, name, bases, namespace):
4949
ROUTE_METHODS = [POST, PUT, PATCH, DELETE, GET, HEAD, OPTIONS, TRACE]
5050

5151
SCOPE_SERVICE_PROVIDER = "SERVICE_PROVIDER"
52-
SCOPE_EXECUTION_CONTEXT_PROVIDER = "SERVICE_EXECUTION_CONTEXT_PROVIDER"
53-
SCOPE_HOST_CONTEXT_PROVIDER = "SCOPE_HOST_CONTEXT_PROVIDER"
54-
SCOPE_RESPONSE_STARTED = "response_started"
52+
SCOPE_RESPONSE_STARTED = "__response_started__"
53+
SCOPED_RESPONSE = "__response__"
5554
SCOPE_API_VERSIONING_RESOLVER = "API_VERSIONING_RESOLVER"
5655
SCOPE_API_VERSIONING_SCHEME = "API_VERSIONING_SCHEME"
5756
ELLAR_CONFIG_MODULE = "ELLAR_CONFIG_MODULE"

ellar/core/context/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
IExecutionContextFactory,
88
IHostContext,
99
IHostContextFactory,
10+
IHTTPConnectionContextFactory,
1011
IHTTPHostContext,
12+
IWebSocketContextFactory,
1113
IWebSocketHostContext,
1214
)
1315

@@ -23,4 +25,6 @@
2325
"IHostContextFactory",
2426
"ExecutionContextFactory",
2527
"HostContextFactory",
28+
"IHTTPConnectionContextFactory",
29+
"IWebSocketContextFactory",
2630
]

0 commit comments

Comments
 (0)