1+ import inspect
12import 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
1047def 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
0 commit comments