Skip to content

Commit e5b59b8

Browse files
authored
Merge pull request #6 from eadwinCode/module_overall
Module Decorator overhaul to reflect meta data
2 parents 5d64049 + 3119154 commit e5b59b8

File tree

116 files changed

+2741
-2413
lines changed

Some content is hidden

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

116 files changed

+2741
-2413
lines changed

ellar/common/__init__.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
from ellar.core.params.params import Param, ParamTypes
22
from ellar.core.routing import ModuleRouter
33

4-
from .decorators.application import on_app_init, on_app_started
54
from .decorators.base import set_meta
65
from .decorators.controller import Controller
76
from .decorators.exception import exception_handler
87
from .decorators.guards import guards
98
from .decorators.html import Render, template_filter, template_global
109
from .decorators.middleware import middleware
11-
from .decorators.modules import ApplicationModule, Module
10+
from .decorators.modules import Module
1211
from .decorators.openapi import openapi
1312
from .decorators.request import on_shutdown, on_startup
1413
from .decorators.serializer import serializer_filter
@@ -49,7 +48,6 @@
4948
"ParamTypes",
5049
"set_meta",
5150
"Controller",
52-
"ApplicationModule",
5351
"openapi",
5452
"version",
5553
"Delete",
@@ -77,10 +75,8 @@
7775
"middleware",
7876
"exception_handler",
7977
"serializer_filter",
80-
"on_app_init",
8178
"on_shutdown",
8279
"on_startup",
83-
"on_app_started",
8480
"template_filter",
8581
"template_global",
8682
]

ellar/common/decorators/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
from .application import on_app_init, on_app_started # noqa
21
from .base import set_meta # noqa
32
from .controller import Controller # noqa
43
from .exception import exception_handler # noqa
54
from .guards import guards # noqa
65
from .html import Render, template_filter, template_global # noqa
76
from .middleware import middleware # noqa
8-
from .modules import ApplicationModule, Module # noqa
7+
from .modules import Module # noqa
98
from .openapi import openapi # noqa
109
from .request import on_shutdown, on_startup # noqa
1110
from .serializer import serializer_filter # noqa

ellar/common/decorators/application.py

Lines changed: 0 additions & 32 deletions
This file was deleted.

ellar/common/decorators/base.py

Lines changed: 8 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,19 @@
22
from functools import partial
33

44
from ellar.constants import NOT_SET
5-
from ellar.core.operation_meta import OperationMeta
5+
from ellar.reflect import reflect
66

77

8-
def _operation_wrapper(
9-
meta_key: str, meta_value: t.Any, target: t.Union[t.Callable, t.Any]
10-
) -> t.Union[t.Callable, t.Any]:
11-
_meta = getattr(target, "_meta", OperationMeta())
12-
_meta_values: t.Any = [meta_value]
13-
existing = _meta.get(meta_key)
14-
if existing:
15-
if isinstance(existing, (list, tuple)):
16-
_meta_values.extend(existing)
17-
if isinstance(existing, tuple):
18-
_meta_values = tuple(_meta_values)
19-
elif isinstance(existing, set):
20-
_meta_values.extend(existing)
21-
_meta_values = set(_meta_values)
22-
elif isinstance(existing, dict):
23-
_meta_values = dict(meta_value)
24-
_meta_values.update(existing)
25-
else:
26-
_meta_values = meta_value
27-
_meta.update({meta_key: _meta_values})
28-
setattr(target, "_meta", _meta)
29-
return target
30-
31-
32-
def set_meta(meta_key: t.Any, meta_value: t.Optional[t.Any] = NOT_SET) -> t.Callable:
8+
def set_meta(
9+
meta_key: t.Any,
10+
meta_value: t.Optional[t.Any] = NOT_SET,
11+
default_value: t.Any = None,
12+
) -> t.Callable:
3313
if meta_value is NOT_SET:
3414
return partial(set_meta, meta_key)
3515

3616
def _decorator(target: t.Union[t.Callable, t.Any]) -> t.Union[t.Callable, t.Any]:
37-
return _operation_wrapper(meta_key, meta_value, target)
17+
reflect.define_metadata(meta_key, meta_value, target, default_value)
18+
return target
3819

3920
return _decorator

ellar/common/decorators/controller.py

Lines changed: 98 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,52 @@
1+
import inspect
12
import typing as t
3+
from abc import ABC
24

3-
from ellar.core.routing.controller import ControllerDecorator
5+
from ellar.compatible import AttributeDict
6+
from ellar.constants import (
7+
CONTROLLER_CLASS_KEY,
8+
CONTROLLER_METADATA,
9+
CONTROLLER_WATERMARK,
10+
NOT_SET,
11+
OPERATION_ENDPOINT_KEY,
12+
OPERATION_HANDLER_KEY,
13+
)
14+
from ellar.core import ControllerBase
15+
from ellar.core.controller import ControllerType
16+
from ellar.di import RequestScope, injectable
17+
from ellar.exceptions import ImproperConfiguration
18+
from ellar.reflect import reflect
419

5-
if t.TYPE_CHECKING:
20+
if t.TYPE_CHECKING: # pragma: no cover
621
from ellar.core.guard import GuardCanActivate
722

823

24+
def get_route_functions(cls: t.Type) -> t.Iterable[t.Callable]:
25+
for method in cls.__dict__.values():
26+
if hasattr(method, OPERATION_ENDPOINT_KEY):
27+
yield method
28+
29+
30+
def reflect_all_controller_type_routes(cls: t.Type[ControllerBase]) -> None:
31+
bases = inspect.getmro(cls)
32+
33+
for base_cls in reversed(bases):
34+
if base_cls not in [ABC, ControllerBase, object]:
35+
for item in get_route_functions(base_cls):
36+
operation = reflect.get_metadata(OPERATION_HANDLER_KEY, item)
37+
reflect.define_metadata(CONTROLLER_CLASS_KEY, cls, item)
38+
reflect.define_metadata(
39+
OPERATION_HANDLER_KEY,
40+
operation,
41+
cls,
42+
default_value=[],
43+
)
44+
45+
946
@t.overload
1047
def Controller(
1148
prefix: t.Optional[str] = None,
12-
) -> ControllerDecorator: # pragma: no cover
49+
) -> t.Union[t.Type[ControllerBase], t.Callable[..., t.Any], t.Any]: # pragma: no cover
1350
...
1451

1552

@@ -26,7 +63,8 @@ def Controller(
2663
guards: t.Optional[
2764
t.List[t.Union[t.Type["GuardCanActivate"], "GuardCanActivate"]]
2865
] = None,
29-
) -> ControllerDecorator: # pragma: no cover
66+
include_in_schema: bool = True,
67+
) -> t.Union[t.Type[ControllerBase], t.Callable[..., t.Any], t.Any]: # pragma: no cover
3068
...
3169

3270

@@ -42,21 +80,66 @@ def Controller(
4280
guards: t.Optional[
4381
t.List[t.Union[t.Type["GuardCanActivate"], "GuardCanActivate"]]
4482
] = None,
45-
) -> t.Union[ControllerDecorator, t.Callable]:
46-
if isinstance(prefix, type):
47-
return ControllerDecorator("")(prefix)
83+
include_in_schema: bool = True,
84+
) -> t.Union[t.Type[ControllerBase], t.Callable[..., t.Any], t.Any]:
85+
_prefix: t.Optional[t.Any] = prefix or NOT_SET
86+
if prefix and isinstance(prefix, type):
87+
_prefix = NOT_SET
88+
89+
if _prefix is not NOT_SET:
90+
assert _prefix == "" or str(_prefix).startswith(
91+
"/"
92+
), "Controller Prefix must start with '/'"
4893

49-
def _decorator(cls: t.Type) -> ControllerDecorator:
50-
_controller = ControllerDecorator(
51-
prefix=prefix,
94+
kwargs = AttributeDict(
95+
openapi=AttributeDict(
5296
tag=tag,
5397
description=description,
5498
external_doc_description=external_doc_description,
5599
external_doc_url=external_doc_url,
56-
name=name,
57-
version=version,
58-
guards=guards,
59-
)
60-
return _controller(cls)
100+
),
101+
path=_prefix,
102+
name=name,
103+
version=set([version] if isinstance(version, str) else version),
104+
guards=guards or [],
105+
include_in_schema=include_in_schema,
106+
)
107+
108+
def _decorator(cls: t.Type) -> t.Type[ControllerBase]:
109+
if not isinstance(cls, type):
110+
raise ImproperConfiguration(f"Controller is a class decorator - {cls}")
111+
112+
if type(cls) is not ControllerType:
113+
# We force the cls to inherit from `ControllerBase` by creating another type.
114+
cls = type(cls.__name__, (cls, ControllerBase), {})
115+
116+
_controller_type = t.cast(t.Type[ControllerBase], cls)
117+
118+
_tag = _controller_type.controller_class_name()
119+
120+
if not kwargs.openapi.tag: # type: ignore
121+
kwargs.openapi.tag = _tag # type: ignore
122+
123+
if kwargs["path"] is NOT_SET:
124+
kwargs["path"] = f"/{_tag}"
125+
126+
if not kwargs["name"]:
127+
kwargs["name"] = (
128+
str(_controller_type.controller_class_name())
129+
.lower()
130+
.replace("controller", "")
131+
)
132+
133+
if not reflect.get_metadata(CONTROLLER_WATERMARK, _controller_type):
134+
reflect.define_metadata(CONTROLLER_WATERMARK, True, _controller_type)
135+
reflect_all_controller_type_routes(_controller_type)
136+
injectable(RequestScope)(cls)
137+
138+
for key in CONTROLLER_METADATA.keys:
139+
reflect.define_metadata(key, kwargs[key], _controller_type)
140+
141+
return _controller_type
61142

143+
if callable(prefix):
144+
return _decorator(prefix)
62145
return _decorator

ellar/common/decorators/guards.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44

55
from .base import set_meta
66

7-
if t.TYPE_CHECKING:
7+
if t.TYPE_CHECKING: # pragma: no cover
88
from ellar.core.guard import GuardCanActivate
99

1010

1111
def guards(
1212
*_guards: t.Union[t.Type["GuardCanActivate"], "GuardCanActivate"]
1313
) -> t.Callable:
14-
return set_meta(GUARDS_KEY, _guards)
14+
return set_meta(GUARDS_KEY, _guards, default_value=[])

0 commit comments

Comments
 (0)