Skip to content

Commit a6a4bc9

Browse files
authored
Merge pull request #219 from python-ellar/module_builder
Module Builder Refactor
2 parents 5ef2b6c + 27256df commit a6a4bc9

File tree

12 files changed

+79
-81
lines changed

12 files changed

+79
-81
lines changed

ellar/common/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
EXTRA_ROUTE_ARGS_KEY = "EXTRA_ROUTE_ARGS"
3131
RESPONSE_OVERRIDE_KEY = "RESPONSE_OVERRIDE"
3232
EXCEPTION_HANDLERS_KEY = "EXCEPTION_HANDLERS"
33-
33+
MODULE_DECORATOR_ITEM = "MODULE_DECORATOR_ITEM"
3434
TEMPLATE_GLOBAL_KEY = "TEMPLATE_GLOBAL_FILTERS"
3535
TEMPLATE_FILTER_KEY = "TEMPLATE_FILTERS"
3636
NESTED_ROUTERS_KEY = "NESTED_ROUTERS_KEY"

ellar/common/decorators/exception.py

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

3-
from ellar.common.constants import EXCEPTION_HANDLERS_KEY
3+
from ellar.common.constants import EXCEPTION_HANDLERS_KEY, MODULE_DECORATOR_ITEM
44
from ellar.pydantic import BaseModel
55

66

@@ -16,6 +16,7 @@ def _add_exception_handler(
1616
validator = ValidateExceptionHandler(key=exc_class_or_status_code, value=handler)
1717
exception_handlers = {validator.key: validator.value}
1818
setattr(handler, EXCEPTION_HANDLERS_KEY, exception_handlers)
19+
setattr(handler, MODULE_DECORATOR_ITEM, EXCEPTION_HANDLERS_KEY)
1920

2021

2122
def exception_handler(

ellar/common/decorators/html.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import warnings
55

66
from ellar.common.constants import (
7+
MODULE_DECORATOR_ITEM,
78
NOT_SET,
89
RESPONSE_OVERRIDE_KEY,
910
TEMPLATE_FILTER_KEY,
@@ -113,6 +114,7 @@ def decorator(f: TemplateFilterCallable) -> TemplateFilterCallable:
113114
TEMPLATE_FILTER_KEY,
114115
TemplateFunctionData(func=f, name=name or get_name(f)),
115116
)
117+
setattr(f, MODULE_DECORATOR_ITEM, TEMPLATE_FILTER_KEY)
116118
return f
117119

118120
return decorator
@@ -140,6 +142,7 @@ def decorator(f: TemplateGlobalCallable) -> TemplateGlobalCallable:
140142
TEMPLATE_GLOBAL_KEY,
141143
TemplateFunctionData(func=f, name=name or get_name(f)),
142144
)
145+
setattr(f, MODULE_DECORATOR_ITEM, TEMPLATE_GLOBAL_KEY)
143146
return f
144147

145148
return decorator

ellar/common/decorators/middleware.py

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

33
from ellar.common.compatible import AttributeDict
4-
from ellar.common.constants import MIDDLEWARE_HANDLERS_KEY
4+
from ellar.common.constants import MIDDLEWARE_HANDLERS_KEY, MODULE_DECORATOR_ITEM
55

66

77
def _add_middleware(dispatch: t.Callable, **options: t.Any) -> None:
@@ -10,6 +10,7 @@ def _add_middleware(dispatch: t.Callable, **options: t.Any) -> None:
1010
MIDDLEWARE_HANDLERS_KEY,
1111
AttributeDict(dispatch=dispatch, options=options),
1212
)
13+
setattr(dispatch, MODULE_DECORATOR_ITEM, MIDDLEWARE_HANDLERS_KEY)
1314

1415

1516
def middleware() -> t.Callable:

ellar/core/middleware/middleware.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,16 @@
1212

1313
class EllarMiddleware(Middleware, IEllarMiddleware):
1414
@t.no_type_check
15-
def __init__(self, cls: t.Type[T], **options: t.Any) -> None:
15+
def __init__(
16+
self,
17+
cls: t.Type[T],
18+
provider_token: t.Optional[str] = None,
19+
**options: t.Any,
20+
) -> None:
1621
super().__init__(cls, **options)
1722
injectable()(self.cls)
1823
self.kwargs = build_init_kwargs(self.cls, self.kwargs)
24+
self.provider_token = provider_token
1925

2026
def __iter__(self) -> t.Iterator[t.Any]:
2127
as_tuple = (self, self.args, self.kwargs)

ellar/core/modules/base.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,19 @@
1212

1313

1414
class ModuleBaseMeta(type):
15-
__MODULE_FIELDS__: t.Dict = {}
15+
MODULE_FIELDS: t.Dict = {}
16+
17+
def build_module_actions(cls) -> None:
18+
ModuleBaseBuilder(cls).build()
1619

1720
@t.no_type_check
1821
def __init__(cls, name, bases, namespace) -> None:
1922
super().__init__(name, bases, namespace)
20-
cls.__MODULE_FIELDS__: t.Dict = {}
23+
setattr(cls, MODULE_FIELDS, {})
2124

22-
for base in reversed(bases):
23-
ModuleBaseBuilder(cls).build(getattr(base, MODULE_FIELDS, base.__dict__))
24-
ModuleBaseBuilder(cls).build(namespace)
25+
# for base in reversed(bases):
26+
# ModuleBaseBuilder(cls).build(getattr(base, MODULE_FIELDS, base.__dict__))
27+
# ModuleBaseBuilder(cls).build(namespace)
2528

2629

2730
class ModuleBase(_InjectorModule, metaclass=ModuleBaseMeta):

ellar/core/modules/builder.py

Lines changed: 26 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@
44
from ellar.common.constants import (
55
EXCEPTION_HANDLERS_KEY,
66
MIDDLEWARE_HANDLERS_KEY,
7-
MODULE_FIELDS,
7+
MODULE_DECORATOR_ITEM,
8+
MODULE_METADATA,
89
TEMPLATE_FILTER_KEY,
910
TEMPLATE_GLOBAL_KEY,
1011
)
1112
from ellar.common.exceptions import CallableExceptionHandler
1213
from ellar.core.middleware import FunctionBasedMiddleware, Middleware
1314
from ellar.reflect import reflect
1415

16+
from ...utils import get_functions_with_tag
1517
from .helper import module_callable_factory
1618

1719
if t.TYPE_CHECKING: # pragma: no cover
@@ -24,9 +26,6 @@ class ModuleBaseBuilder:
2426

2527
def __init__(self, cls: t.Union[t.Type["ModuleBase"], "ModuleBaseMeta"]) -> None:
2628
self._cls = cls
27-
self._cls.__MODULE_FIELDS__ = t.cast(
28-
t.Dict, getattr(self._cls, MODULE_FIELDS, None) or {}
29-
)
3029
self._actions: t.Dict[str, t.Callable[[t.Any], None]] = {}
3130
self._actions.update(
3231
{
@@ -39,50 +38,47 @@ def __init__(self, cls: t.Union[t.Type["ModuleBase"], "ModuleBaseMeta"]) -> None
3938

4039
def exception_config(self, exception_dict: t.Dict) -> None:
4140
for k, v in exception_dict.items():
42-
func = CallableExceptionHandler(
43-
self._cls, callable_exception_handler=v, exc_class_or_status_code=k
41+
self._cls.MODULE_FIELDS.setdefault(EXCEPTION_HANDLERS_KEY, []).append(
42+
CallableExceptionHandler(
43+
callable_exception_handler=module_callable_factory(v, self._cls),
44+
exc_class_or_status_code=k,
45+
)
4446
)
45-
reflect.define_metadata(EXCEPTION_HANDLERS_KEY, [func], self._cls)
4647

4748
@t.no_type_check
4849
def middleware_config(self, middleware: AttributeDict) -> None:
4950
dispatch = module_callable_factory(middleware.dispatch, self._cls)
50-
reflect.define_metadata(
51-
MIDDLEWARE_HANDLERS_KEY,
52-
[
53-
Middleware(
54-
FunctionBasedMiddleware, dispatch=dispatch, **middleware.options
55-
)
56-
],
57-
self._cls,
51+
self._cls.MODULE_FIELDS.setdefault(MIDDLEWARE_HANDLERS_KEY, []).append(
52+
Middleware(
53+
FunctionBasedMiddleware,
54+
dispatch=dispatch,
55+
**middleware.options,
56+
provider_token=reflect.get_metadata(MODULE_METADATA.NAME, self._cls),
57+
)
5858
)
5959

6060
def template_filter_config(self, template_filter: "TemplateFunctionData") -> None:
61-
reflect.define_metadata(
62-
TEMPLATE_FILTER_KEY,
61+
self._cls.MODULE_FIELDS.setdefault(TEMPLATE_FILTER_KEY, {}).update(
6362
{
6463
template_filter.name: module_callable_factory(
6564
template_filter.func, self._cls
6665
)
67-
},
68-
self._cls,
66+
}
6967
)
7068

7169
def template_global_config(self, template_filter: "TemplateFunctionData") -> None:
72-
reflect.define_metadata(
73-
TEMPLATE_GLOBAL_KEY,
70+
self._cls.MODULE_FIELDS.setdefault(TEMPLATE_GLOBAL_KEY, {}).update(
7471
{
7572
template_filter.name: module_callable_factory(
7673
template_filter.func, self._cls
7774
)
78-
},
79-
self._cls,
75+
}
8076
)
8177

82-
def build(self, namespace: t.Dict) -> None:
83-
for name, item in namespace.items():
84-
for k, func in self._actions.items():
85-
if hasattr(item, k):
86-
value = item.__dict__.pop(k)
87-
func(value)
88-
self._cls.__MODULE_FIELDS__[name] = item
78+
def build(self) -> None:
79+
self._cls.MODULE_FIELDS = {}
80+
81+
for _, item in get_functions_with_tag(self._cls, MODULE_DECORATOR_ITEM):
82+
action_name = getattr(item, MODULE_DECORATOR_ITEM)
83+
handler = self._actions[action_name]
84+
handler(item.__dict__[action_name])

ellar/core/modules/ref.py

Lines changed: 14 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,11 @@ def __init__(
178178
# self._flatten_routes: t.List[BaseRoute] = []
179179

180180
if isinstance(self.module, type) and issubclass(self.module, ModuleBase):
181+
self.module.build_module_actions()
182+
181183
self.scan_templating_filters()
182184
self.scan_exceptions_handlers()
185+
183186
self.scan_middleware()
184187

185188
self.register_providers()
@@ -224,39 +227,22 @@ def _register_module(self) -> None:
224227
)
225228

226229
def scan_templating_filters(self) -> None:
227-
templating_filter = (
228-
reflect.get_metadata(TEMPLATE_FILTER_KEY, self._module_type) or {}
229-
)
230-
if not self._config.get(TEMPLATE_FILTER_KEY) or not isinstance(
231-
self._config[TEMPLATE_FILTER_KEY], dict
232-
):
233-
self._config[TEMPLATE_FILTER_KEY] = {}
234-
self._config[TEMPLATE_FILTER_KEY].update(templating_filter)
235-
236-
templating_global_filter = (
237-
reflect.get_metadata(TEMPLATE_GLOBAL_KEY, self._module_type) or {}
230+
templating_filter = self.module.MODULE_FIELDS.get(TEMPLATE_FILTER_KEY, {})
231+
self.config.setdefault(TEMPLATE_FILTER_KEY, {}).update(templating_filter)
232+
233+
templating_global_filter = self.module.MODULE_FIELDS.get(
234+
TEMPLATE_GLOBAL_KEY, {}
238235
)
239-
if not self._config.get(TEMPLATE_GLOBAL_KEY) or not isinstance(
240-
self._config[TEMPLATE_GLOBAL_KEY], dict
241-
):
242-
self._config[TEMPLATE_GLOBAL_KEY] = {}
243-
self._config[TEMPLATE_GLOBAL_KEY].update(templating_global_filter)
236+
self.config.setdefault(TEMPLATE_GLOBAL_KEY, {}).update(templating_global_filter)
244237

245238
def scan_exceptions_handlers(self) -> None:
246-
exception_handlers = (
247-
reflect.get_metadata(EXCEPTION_HANDLERS_KEY, self._module_type) or []
248-
)
249-
self._config[EXCEPTION_HANDLERS_KEY].extend(exception_handlers)
239+
exception_handlers = self.module.MODULE_FIELDS.get(EXCEPTION_HANDLERS_KEY, [])
240+
241+
self.config.setdefault(EXCEPTION_HANDLERS_KEY, []).extend(exception_handlers)
250242

251243
def scan_middleware(self) -> None:
252-
middleware = (
253-
reflect.get_metadata(MIDDLEWARE_HANDLERS_KEY, self._module_type) or []
254-
)
255-
if not self._config.get(MIDDLEWARE_HANDLERS_KEY) or not isinstance(
256-
self._config[MIDDLEWARE_HANDLERS_KEY], list
257-
):
258-
self._config[MIDDLEWARE_HANDLERS_KEY] = []
259-
self._config[MIDDLEWARE_HANDLERS_KEY].extend(middleware)
244+
middleware = self.module.MODULE_FIELDS.get(MIDDLEWARE_HANDLERS_KEY, [])
245+
self.config.setdefault(MIDDLEWARE_HANDLERS_KEY, []).extend(middleware)
260246

261247
def register_providers(self) -> None:
262248
providers = (

ellar/core/router_builders/controller.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
CONTROLLER_METADATA,
55
CONTROLLER_OPERATION_HANDLER_KEY,
66
CONTROLLER_WATERMARK,
7+
OPERATION_ENDPOINT_KEY,
78
ROUTE_OPERATION_PARAMETERS,
89
)
910
from ellar.common.logging import logger
@@ -18,8 +19,9 @@
1819
from ellar.reflect import reflect
1920
from starlette.routing import BaseRoute
2021

22+
from ...utils import get_functions_with_tag
2123
from .base import RouterBuilder
22-
from .utils import get_route_functions, process_nested_routes
24+
from .utils import process_nested_routes
2325

2426
T_ = t.TypeVar("T_")
2527

@@ -30,7 +32,7 @@ def process_controller_routes(controller: t.Type[ControllerBase]) -> t.List[Base
3032
if reflect.get_metadata(CONTROLLER_METADATA.PROCESSED, controller):
3133
return reflect.get_metadata(CONTROLLER_OPERATION_HANDLER_KEY, controller) or []
3234

33-
for _, item in get_route_functions(controller):
35+
for _, item in get_functions_with_tag(controller, tag=OPERATION_ENDPOINT_KEY):
3436
parameters = item.__dict__[ROUTE_OPERATION_PARAMETERS]
3537
operation: t.Union[ControllerRouteOperation, ControllerWebsocketRouteOperation]
3638

ellar/core/router_builders/utils.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import inspect
21
import typing as t
32
from types import FunctionType
43

@@ -7,7 +6,6 @@
76
CONTROLLER_METADATA,
87
CONTROLLER_OPERATION_HANDLER_KEY,
98
NESTED_ROUTERS_KEY,
10-
OPERATION_ENDPOINT_KEY,
119
ROUTE_OPERATION_PARAMETERS,
1210
)
1311
from ellar.common.exceptions import ImproperConfiguration
@@ -30,14 +28,6 @@
3028
_stack_cycle: t.Tuple = ()
3129

3230

33-
def get_route_functions(
34-
klass: t.Type, key: str = OPERATION_ENDPOINT_KEY
35-
) -> t.Iterable[t.Tuple[str, t.Callable]]:
36-
for method_name, method in inspect.getmembers(klass, predicate=inspect.isfunction):
37-
if hasattr(method, key):
38-
yield method_name, method
39-
40-
4131
def process_nested_routes(controller: t.Type[t.Any]) -> t.List[BaseRoute]:
4232
global _stack_cycle
4333

0 commit comments

Comments
 (0)