Skip to content

Commit b03124f

Browse files
committed
Enable operation outside modulerouter to be computation
1 parent ed4e905 commit b03124f

File tree

13 files changed

+187
-215
lines changed

13 files changed

+187
-215
lines changed
Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@
44
RequestParameter,
55
WebSocketParameter,
66
)
7-
8-
from .http import HTTPConnection, Request
9-
from .websocket import WebSocket
7+
from starlette.requests import HTTPConnection, Request
8+
from starlette.websockets import WebSocket
109

1110
__all__ = ["HTTPConnection", "Request", "WebSocket"]
1211

ellar/core/router_builders/base.py

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
import inspect
12
import typing as t
23
from abc import abstractmethod
34

4-
from ellar.common.logging import logger
5-
from ellar.core.routing.mount import EllarMount
6-
from starlette.routing import Host, Mount
5+
from ellar.common.constants import NOT_SET
6+
from ellar.core.routing.mount import EllarControllerMount
7+
from starlette.routing import BaseRoute, Host, Mount
78

89
_router_builder_factory: t.Dict[t.Type, t.Type["RouterBuilder"]] = {}
910

@@ -15,28 +16,24 @@ def _register_controller_builder(
1516

1617

1718
def get_controller_builder_factory(
18-
controller_type: t.Type,
19+
controller_type: t.Union[t.Type, t.Any] = NOT_SET,
1920
) -> t.Type["RouterBuilder"]:
2021
res = _router_builder_factory.get(controller_type)
2122
if not res:
22-
logger.warning(
23-
f"Router Factory Builder was not found.\nUse `ControllerRouterBuilderFactory` "
24-
f"as an example create a FactoryBuilder for this type: {controller_type}"
25-
)
2623
return _DefaultRouterBuilder
2724
return res
2825

2926

3027
class RouterBuilder:
3128
"""
32-
A factory class that validates and converts a Controllers or ModuleRouter to Starlette Mount or EllarMount.
29+
A factory class that validates and converts a Controllers or ModuleRouter to Starlette Mount or EllarControllerMount.
3330
"""
3431

3532
@classmethod
3633
@abstractmethod
3734
def build(
3835
cls, controller_type: t.Union[t.Type, t.Any], **kwargs: t.Any
39-
) -> t.Union["EllarMount", Mount]:
36+
) -> t.Union["EllarControllerMount", Mount]:
4037
"""Build controller to Mount"""
4138

4239
@classmethod
@@ -58,10 +55,21 @@ def check_type(cls, controller_type: t.Union[t.Type, t.Any]) -> None:
5855
@classmethod
5956
def build(
6057
cls, controller_type: t.Union[t.Type, t.Any], **kwargs: t.Any
61-
) -> t.Union["EllarMount", Mount, t.Any]:
58+
) -> t.Union["EllarControllerMount", Mount, t.Any]:
6259
"""Build controller to Mount"""
60+
from ellar.core.router_builders.utils import build_route_handler
61+
62+
if inspect.isfunction(controller_type):
63+
operations: t.Any = build_route_handler(controller_type)
64+
65+
for op in operations or []:
66+
if not isinstance(op, BaseRoute):
67+
raise Exception(f"Unable to build function - {controller_type}")
68+
return operations
69+
6370
return controller_type
6471

6572

6673
_register_controller_builder(Host, _DefaultRouterBuilder)
67-
_register_controller_builder(EllarMount, _DefaultRouterBuilder)
74+
_register_controller_builder(EllarControllerMount, _DefaultRouterBuilder)
75+
_register_controller_builder(NOT_SET, _DefaultRouterBuilder)

ellar/core/router_builders/controller.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from ellar.core.routing import (
1515
ControllerRouteOperation,
1616
ControllerWebsocketRouteOperation,
17-
EllarMount,
17+
EllarControllerMount,
1818
)
1919
from ellar.reflect import reflect
2020
from starlette.routing import BaseRoute
@@ -41,10 +41,12 @@ def process_controller_routes(controller: t.Type[ControllerBase]) -> t.List[Base
4141

4242
for parameter in parameters:
4343
if isinstance(parameter, RouteParameters):
44-
operation = ControllerRouteOperation(controller, **parameter.dict())
44+
operation = ControllerRouteOperation(
45+
**parameter.dict(), controller_class=controller
46+
)
4547
elif isinstance(parameter, WsRouteParameters):
4648
operation = ControllerWebsocketRouteOperation(
47-
controller, **parameter.dict()
49+
**parameter.dict(), controller_class=controller
4850
)
4951
else: # pragma: no cover
5052
logger.warning(
@@ -66,9 +68,11 @@ class ControllerRouterBuilder(RouterBuilder, controller_type=type(ControllerBase
6668
def build(
6769
cls,
6870
controller_type: t.Union[t.Type[ControllerBase], t.Any],
69-
base_route_type: t.Type[t.Union[EllarMount, T_]] = EllarMount,
71+
base_route_type: t.Type[
72+
t.Union[EllarControllerMount, T_]
73+
] = EllarControllerMount,
7074
**kwargs: t.Any,
71-
) -> t.Union[T_, EllarMount]:
75+
) -> t.Union[T_, EllarControllerMount]:
7276
routes = process_controller_routes(controller_type)
7377
routes.extend(process_nested_routes(controller_type))
7478

@@ -80,7 +84,7 @@ def build(
8084
CONTROLLER_METADATA.MIDDLEWARE, controller_type
8185
)
8286

83-
kwargs.setdefault("middleware", middleware)
87+
kwargs.update(middleware=middleware)
8488

8589
path = reflect.get_metadata_or_raise_exception(
8690
CONTROLLER_METADATA.PATH, controller_type

ellar/core/router_builders/module_router.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from ellar.common.compatible import AttributeDict
55
from ellar.common.constants import CONTROLLER_METADATA, CONTROLLER_OPERATION_HANDLER_KEY
66
from ellar.common.shortcuts import normalize_path
7-
from ellar.core.routing import EllarMount
7+
from ellar.core.routing import EllarControllerMount
88
from ellar.reflect import reflect
99

1010
from .base import RouterBuilder
@@ -18,20 +18,23 @@ class ModuleRouterBuilder(RouterBuilder, controller_type=ModuleRouter):
1818
def build(
1919
cls,
2020
controller_type: t.Union[ModuleRouter, t.Any],
21-
base_route_type: t.Type[t.Union[EllarMount, T_]] = EllarMount,
21+
base_route_type: t.Type[
22+
t.Union[EllarControllerMount, T_]
23+
] = EllarControllerMount,
2224
**kwargs: t.Any,
23-
) -> t.Union[T_, EllarMount]:
25+
) -> t.Union[T_, EllarControllerMount]:
2426
if reflect.get_metadata(
25-
CONTROLLER_METADATA.PROCESSED, controller_type.control_type
27+
CONTROLLER_METADATA.PROCESSED, controller_type.get_controller_type()
2628
):
2729
routes = reflect.get_metadata(
28-
CONTROLLER_OPERATION_HANDLER_KEY, controller_type.control_type
30+
CONTROLLER_OPERATION_HANDLER_KEY, controller_type.get_controller_type()
2931
)
3032
else:
3133
routes = build_route_parameters(
32-
controller_type.get_pre_build_routes(), controller_type.control_type
34+
controller_type.get_pre_build_routes(),
35+
controller_class=controller_type.get_controller_type(),
3336
)
34-
routes.extend(process_nested_routes(controller_type.control_type))
37+
routes.extend(process_nested_routes(controller_type.get_controller_type()))
3538

3639
init_kwargs = AttributeDict(controller_type.get_mount_init())
3740

@@ -43,7 +46,7 @@ def build(
4346
router = base_route_type(**init_kwargs, routes=routes) # type:ignore[call-arg]
4447

4548
reflect.define_metadata(
46-
CONTROLLER_METADATA.PROCESSED, True, controller_type.control_type
49+
CONTROLLER_METADATA.PROCESSED, True, controller_type.get_controller_type()
4750
)
4851
return router
4952

ellar/core/router_builders/utils.py

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
from types import FunctionType
33

44
from ellar.common.constants import (
5-
CONTROLLER_CLASS_KEY,
65
CONTROLLER_METADATA,
76
CONTROLLER_OPERATION_HANDLER_KEY,
87
NESTED_ROUTERS_KEY,
8+
NOT_SET,
99
ROUTE_OPERATION_PARAMETERS,
1010
)
1111
from ellar.common.exceptions import ImproperConfiguration
@@ -18,7 +18,6 @@
1818
WebsocketRouteOperation,
1919
)
2020
from ellar.reflect import reflect
21-
from ellar.utils import get_unique_type
2221
from starlette.routing import BaseRoute
2322

2423
from .base import get_controller_builder_factory
@@ -72,19 +71,16 @@ def build_route_handler(
7271
) -> t.Optional[t.List[RouteOperationBase]]:
7372
_item: t.Any = item
7473

75-
if callable(item) and type(item) == FunctionType:
74+
if callable(item) and type(item) is FunctionType:
7675
_item = reflect.get_metadata(CONTROLLER_OPERATION_HANDLER_KEY, item)
7776

78-
if not _item and not reflect.has_metadata(CONTROLLER_CLASS_KEY, item):
79-
reflect.define_metadata(CONTROLLER_CLASS_KEY, get_unique_type(), item)
80-
8177
if not _item and hasattr(item, ROUTE_OPERATION_PARAMETERS):
8278
parameters = item.__dict__[ROUTE_OPERATION_PARAMETERS]
8379

8480
if not isinstance(parameters, list):
8581
parameters = [parameters]
8682

87-
operations = build_route_parameters(parameters, get_unique_type())
83+
operations = build_route_parameters(parameters)
8884
if isinstance(operations, list):
8985
return operations
9086

@@ -96,15 +92,14 @@ def build_route_handler(
9692

9793
@t.no_type_check
9894
def build_route_parameters(
99-
items: t.List[t.Union[RouteParameters, WsRouteParameters]],
100-
control_type: t.Type[t.Any],
95+
items: t.List[t.Union[RouteParameters, WsRouteParameters]], **kwargs: t.Any
10196
) -> t.List[t.Union[RouteOperationBase, t.Any]]:
10297
results = []
10398
for item in items:
10499
if isinstance(item, RouteParameters):
105-
operation = RouteOperation(**item.dict())
100+
operation = RouteOperation(**item.dict(), **kwargs)
106101
elif isinstance(item, WsRouteParameters):
107-
operation = WebsocketRouteOperation(**item.dict())
102+
operation = WebsocketRouteOperation(**item.dict(), **kwargs)
108103
else: # pragma: no cover
109104
logger.warning(
110105
f"Parameter type is not recognized. {type(item) if not isinstance(item, type) else item}"
@@ -115,10 +110,11 @@ def build_route_parameters(
115110
if ROUTE_OPERATION_PARAMETERS in item.endpoint.__dict__:
116111
del item.endpoint.__dict__[ROUTE_OPERATION_PARAMETERS]
117112

118-
reflect.define_metadata(
119-
CONTROLLER_OPERATION_HANDLER_KEY,
120-
[operation],
121-
control_type,
122-
)
113+
if operation.get_controller_type() is not NOT_SET:
114+
reflect.define_metadata(
115+
CONTROLLER_OPERATION_HANDLER_KEY,
116+
[operation],
117+
operation.get_controller_type(),
118+
)
123119

124120
return results

ellar/core/routing/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
ControllerWebsocketRouteOperation,
1111
)
1212
from .file_mount import AppStaticFileMount, ASGIFileMount
13-
from .mount import ApplicationRouter, EllarMount
13+
from .mount import ApplicationRouter, EllarControllerMount
1414
from .route import RouteOperation
1515
from .route_collections import RouteCollection
1616
from .websocket import WebsocketRouteOperation
@@ -19,7 +19,7 @@
1919
"Param",
2020
"ParamTypes",
2121
"RouteCollection",
22-
"EllarMount",
22+
"EllarControllerMount",
2323
"RouteOperation",
2424
"RouteOperationBase",
2525
"WebsocketRouteOperation",

ellar/core/routing/base.py

Lines changed: 12 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import typing as t
22
from abc import ABC, abstractmethod
3-
from contextlib import asynccontextmanager
43

54
from ellar.common.constants import (
6-
CONTROLLER_CLASS_KEY,
5+
NOT_SET,
76
SCOPE_API_VERSIONING_RESOLVER,
87
VERSIONING_KEY,
98
)
@@ -15,7 +14,8 @@
1514
)
1615
from ellar.common.logging import request_logger
1716
from ellar.common.types import TReceive, TScope, TSend
18-
from ellar.core.context import current_injector
17+
from ellar.core.execution_context import current_injector
18+
from ellar.di import register_request_scope_context
1919
from ellar.reflect import reflect
2020
from starlette.routing import Match
2121

@@ -33,13 +33,11 @@ class RouteOperationBase:
3333
methods: t.Set[str]
3434
_controller_type: t.Optional[t.Union[t.Type, t.Type["ControllerBase"]]] = None
3535

36-
def __init__(self, endpoint: t.Callable) -> None:
36+
def __init__(
37+
self, endpoint: t.Callable, controller_class: t.Optional[t.Type] = None
38+
) -> None:
3739
self.endpoint = endpoint
38-
_controller_type: t.Type = self.get_controller_type()
39-
40-
self._controller_type: t.Union[t.Type, t.Type["ControllerBase"]] = t.cast(
41-
t.Union[t.Type, t.Type["ControllerBase"]], _controller_type
42-
)
40+
self._controller_type = controller_class or NOT_SET
4341

4442
# @t.no_type_check
4543
# def __call__(
@@ -51,23 +49,6 @@ def __init__(self, endpoint: t.Callable) -> None:
5149
def _load_model(self) -> None:
5250
"""compute route models"""
5351

54-
@asynccontextmanager
55-
async def _ensure_dependency_availability(
56-
self, context: IExecutionContext
57-
) -> t.AsyncGenerator:
58-
controller_type = context.get_class()
59-
module_scope_owner = next(
60-
context.get_app().injector.tree_manager.find_module(
61-
lambda data: controller_type in data.providers
62-
or controller_type in data.exports
63-
)
64-
)
65-
if module_scope_owner and module_scope_owner.is_ready:
66-
async with module_scope_owner.value.context():
67-
yield
68-
else:
69-
yield
70-
7152
async def app(self, scope: TScope, receive: TReceive, send: TSend) -> None:
7253
request_logger.debug(
7354
f"Started Computing Execution Context - '{self.__class__.__name__}'"
@@ -77,19 +58,19 @@ async def app(self, scope: TScope, receive: TReceive, send: TSend) -> None:
7758
context = execution_context_factory.create_context(
7859
operation=self, scope=scope, receive=receive, send=send
7960
)
80-
current_injector.update_scoped_context(IExecutionContext, context)
61+
register_request_scope_context(IExecutionContext, context)
8162

8263
interceptor_consumer = current_injector.get(IInterceptorsConsumer)
8364
guard_consumer = current_injector.get(IGuardsConsumer)
8465

8566
request_logger.debug(
8667
f"Running Guards and Interceptors - '{self.__class__.__name__}'"
8768
)
88-
async with self._ensure_dependency_availability(context):
89-
await guard_consumer.execute(context, self)
90-
await interceptor_consumer.execute(context, self)
9169

92-
def get_controller_type(self) -> t.Type:
70+
await guard_consumer.execute(context, self)
71+
await interceptor_consumer.execute(context, self)
72+
73+
def get_controller_type(self) -> t.Any:
9374
"""
9475
For operation under a controller, `get_control_type` and `get_class` will return the same result
9576
For operation under ModuleRouter, this will return a unique type created for the router for tracking some properties
@@ -98,11 +79,6 @@ def get_controller_type(self) -> t.Type:
9879
request_logger.debug(
9980
f"Resolving Endpoint Handler Controller Type - '{self.__class__.__name__}'"
10081
)
101-
if not self._controller_type:
102-
_controller_type = reflect.get_metadata(CONTROLLER_CLASS_KEY, self.endpoint)
103-
if _controller_type is None or not isinstance(_controller_type, type):
104-
raise Exception("Operation must have a single control type.")
105-
return t.cast(t.Type, _controller_type)
10682

10783
return self._controller_type
10884

ellar/core/routing/controller/base.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,8 @@
66

77

88
class ControllerRouteOperationBase:
9-
_controller_type: t.Optional[t.Type["ControllerBase"]] = None
10-
endpoint: t.Callable
119
get_controller_type: t.Callable
1210

13-
def __init__(
14-
self, controller_type: t.Type[ControllerBase], *args: t.Any, **kwargs: t.Any
15-
) -> None:
16-
self._controller_type = controller_type
17-
18-
super().__init__(*args, **kwargs)
19-
2011
def _get_controller_instance(self, ctx: IExecutionContext) -> ControllerBase:
2112
request_logger.debug("Getting Controller Instance")
2213
controller_type: t.Type[ControllerBase] = self.get_controller_type()

0 commit comments

Comments
 (0)