Skip to content

Commit 7f163e9

Browse files
committed
Merge branch 'module_configuration' into module_configuration_2
2 parents df022e5 + 5b84e7a commit 7f163e9

File tree

26 files changed

+794
-200
lines changed

26 files changed

+794
-200
lines changed

ellar/common/cache.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from .routing.params import Context, Provide
1212

1313

14-
class CacheDecorator:
14+
class _CacheDecorator:
1515
__slots__ = (
1616
"_is_async",
1717
"_key_prefix",
@@ -136,7 +136,7 @@ def cache(
136136
] = None,
137137
) -> t.Callable:
138138
def _wraps(func: t.Callable) -> t.Callable:
139-
cache_decorator = CacheDecorator(
139+
cache_decorator = _CacheDecorator(
140140
func,
141141
timeout,
142142
key_prefix=key_prefix,

ellar/common/decorators/modules.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,11 @@ def Module(
8282
8383
:param static_folder: Module static folder name
8484
85-
:param modules: List of Module Types - t.Type[MODULEBASE]
85+
:param modules: List of Module Types - t.Type[ModuleBase]
8686
8787
:param commands: List of Command Decorated functions and EllarTyper
8888
89-
:return: t.TYPE[MODULEBASE]
89+
:return: t.TYPE[ModuleBase]
9090
"""
9191
kwargs = AttributeDict(
9292
name=name,

ellar/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ def __new__(mcls, name, bases, namespace):
9393
class MODULE_REF_TYPES(metaclass=_AnnotationToValue):
9494
PLAIN: str
9595
TEMPLATE: str
96+
DYNAMIC: str
97+
APP_DEPENDENT: str
9698

9799

98100
class MODULE_METADATA(metaclass=_AnnotationToValue):

ellar/core/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from .factory import AppFactory
88
from .guard import BaseAPIKey, BaseAuthGuard, BaseHttpAuth, GuardCanActivate
99
from .main import App
10-
from .modules import ModuleBase
10+
from .modules import DynamicModule, IModuleSetup, ModuleBase, ModuleSetup
1111
from .response import (
1212
FileResponse,
1313
HTMLResponse,
@@ -36,6 +36,7 @@
3636
"ModuleBase",
3737
"BaseAPIKey",
3838
"BaseAuthGuard",
39+
"IModuleSetup",
3940
"BaseHttpAuth",
4041
"GuardCanActivate",
4142
"Config",
@@ -52,6 +53,8 @@
5253
"Response",
5354
"Request",
5455
"WebSocket",
56+
"ModuleSetup",
57+
"DynamicModule",
5558
]
5659

5760

ellar/core/factory.py

Lines changed: 71 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@
33
from pathlib import Path
44
from uuid import uuid4
55

6-
from starlette.routing import Host, Mount
6+
from starlette.routing import BaseRoute, Host, Mount
77

88
from ellar.constants import MODULE_METADATA, MODULE_WATERMARK
99
from ellar.core import Config
1010
from ellar.core.main import App
11-
from ellar.core.modules import ModuleBase
12-
from ellar.core.modules.ref import create_module_ref_factor
11+
from ellar.core.modules import DynamicModule, ModuleBase, ModuleSetup
1312
from ellar.di import EllarInjector, ProviderConfig
1413
from ellar.reflect import reflect
1514

@@ -27,31 +26,38 @@ class AppFactory:
2726
"""
2827

2928
@classmethod
30-
def get_all_modules(
31-
cls, module: t.Type[t.Union[ModuleBase, t.Any]]
32-
) -> t.List[t.Type[ModuleBase]]:
29+
def get_all_modules(cls, module_config: ModuleSetup) -> t.List[ModuleSetup]:
3330
"""
3431
Gets all registered modules from a particular module in their order of dependencies
35-
:param module: Module Type
32+
:param module_config: Module Type
3633
:return: t.List[t.Type[ModuleBase]]
3734
"""
38-
module_dependency = [module] + list(cls.read_all_module(module).values())
35+
module_dependency = [module_config] + list(
36+
cls.read_all_module(module_config).values()
37+
)
3938
return module_dependency
4039

4140
@classmethod
42-
def read_all_module(
43-
cls, module: t.Type[t.Union[ModuleBase, t.Any]]
44-
) -> t.Dict[t.Type, t.Type[ModuleBase]]:
41+
def read_all_module(cls, module_config: ModuleSetup) -> t.Dict[t.Type, ModuleSetup]:
4542
"""
4643
Retrieves all modules dependencies registered in another module
47-
:param module: Module Type
44+
:param module_config: Module Type
4845
:return: t.Dict[t.Type, t.Type[ModuleBase]]
4946
"""
50-
modules = reflect.get_metadata(MODULE_METADATA.MODULES, module) or []
47+
modules = (
48+
reflect.get_metadata(MODULE_METADATA.MODULES, module_config.module) or []
49+
)
5150
module_dependency = OrderedDict()
5251
for module in modules:
53-
module_dependency[module] = module
54-
module_dependency.update(cls.read_all_module(module))
52+
if isinstance(module, DynamicModule):
53+
module_config = ModuleSetup(module.module)
54+
elif isinstance(module, ModuleSetup):
55+
module_config = module
56+
else:
57+
module_config = ModuleSetup(module)
58+
59+
module_dependency[module_config.module] = module_config
60+
module_dependency.update(cls.read_all_module(module_config))
5561
return module_dependency
5662

5763
@classmethod
@@ -60,7 +66,7 @@ def _build_modules(
6066
app_module: t.Type[t.Union[ModuleBase, t.Any]],
6167
config: Config,
6268
injector: EllarInjector,
63-
) -> None:
69+
) -> t.List[BaseRoute]:
6470
"""
6571
builds application module and registers them to EllarInjector
6672
:param app_module: Root App Module
@@ -72,18 +78,37 @@ def _build_modules(
7278
MODULE_WATERMARK, app_module
7379
), "Only Module is allowed"
7480

75-
module_dependency = cls.get_all_modules(app_module)
76-
for module in reversed(module_dependency):
77-
if injector.get_module(module): # pragma: no cover
81+
app_module_config = ModuleSetup(app_module)
82+
module_dependency = cls.get_all_modules(app_module_config)
83+
routes = []
84+
85+
for module_config in reversed(module_dependency):
86+
if injector.get_module(module_config.module): # pragma: no cover
7887
continue
7988

80-
module_ref = create_module_ref_factor(
81-
module,
82-
container=injector.container,
83-
config=config,
89+
module_ref = module_config.get_module_ref(
90+
container=injector.container, config=config
8491
)
92+
93+
if not isinstance(module_ref, ModuleSetup):
94+
module_ref.run_module_register_services()
95+
routes.extend(module_ref.routes)
96+
8597
injector.add_module(module_ref)
8698

99+
for module_config in injector.get_dynamic_modules():
100+
if injector.get_module(module_config.module): # pragma: no cover
101+
continue
102+
103+
module_ref = module_config.configure_with_factory(
104+
config, injector.container
105+
)
106+
module_ref.run_module_register_services()
107+
108+
injector.add_module(module_ref)
109+
routes.extend(module_ref.routes)
110+
return routes
111+
87112
@classmethod
88113
def _create_app(
89114
cls,
@@ -100,20 +125,42 @@ def _create_app(
100125
injector.container.register_instance(config, concrete_type=Config)
101126
CoreServiceRegistration(injector, config).register_all()
102127

103-
cls._build_modules(app_module=module, injector=injector, config=config)
128+
routes = cls._build_modules(app_module=module, injector=injector, config=config)
104129

105130
shutdown_event = config.ON_REQUEST_STARTUP
106131
startup_event = config.ON_REQUEST_SHUTDOWN
107132

108133
app = App(
109134
config=config,
110135
injector=injector,
136+
routes=routes,
111137
on_shutdown_event_handlers=shutdown_event if shutdown_event else None,
112138
on_startup_event_handlers=startup_event if startup_event else None,
113139
lifespan=config.DEFAULT_LIFESPAN_HANDLER,
114140
global_guards=global_guards,
115141
)
116142

143+
routes = []
144+
module_changed = False
145+
146+
for module_config in injector.get_app_dependent_modules():
147+
if injector.get_module(module_config.module): # pragma: no cover
148+
continue
149+
150+
module_ref = module_config.configure_with_factory(
151+
config, injector.container
152+
)
153+
module_ref.run_module_register_services()
154+
155+
injector.add_module(module_ref)
156+
routes.extend(module_ref.routes)
157+
module_changed = True
158+
159+
if module_changed:
160+
app.router.extend(routes)
161+
app.reload_static_app()
162+
app.rebuild_middleware_stack()
163+
117164
return app
118165

119166
@classmethod

ellar/core/main.py

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@
2020
RequestVersioningMiddleware,
2121
TrustedHostMiddleware,
2222
)
23-
from ellar.core.modules import ModuleBase, ModuleTemplateRef
24-
from ellar.core.modules.ref import create_module_ref_factor
23+
from ellar.core.modules import DynamicModule, ModuleBase, ModuleSetup, ModuleTemplateRef
2524
from ellar.core.routing import ApplicationRouter
2625
from ellar.core.templating import AppTemplating, Environment
2726
from ellar.core.versioning import VERSIONING, BaseAPIVersioning
@@ -35,6 +34,7 @@ def __init__(
3534
self,
3635
config: Config,
3736
injector: EllarInjector,
37+
routes: t.List[BaseRoute],
3838
on_startup_event_handlers: t.Optional[t.Sequence[EventHandler]] = None,
3939
on_shutdown_event_handlers: t.Optional[t.Sequence[EventHandler]] = None,
4040
lifespan: t.Optional[t.Callable[["App"], t.AsyncContextManager]] = None,
@@ -77,7 +77,7 @@ def __init__(
7777
lifespan or self.config.DEFAULT_LIFESPAN_HANDLER
7878
)
7979
self.router = ApplicationRouter(
80-
routes=self._get_module_routes(),
80+
routes=self._get_module_routes(routes),
8181
redirect_slashes=self.config.REDIRECT_SLASHES,
8282
on_startup=[self.on_startup.async_run]
8383
if self.config.DEFAULT_LIFESPAN_HANDLER is None
@@ -110,8 +110,8 @@ async def _statics_func_wrapper(
110110

111111
return _statics_func_wrapper
112112

113-
def _get_module_routes(self) -> t.List[BaseRoute]:
114-
_routes: t.List[BaseRoute] = []
113+
def _get_module_routes(self, routes: t.List[BaseRoute]) -> t.List[BaseRoute]:
114+
_routes: t.List[BaseRoute] = list(routes)
115115
if self.has_static_files:
116116
self._static_app = self.create_static_app()
117117
_routes.append(
@@ -122,32 +122,41 @@ def _get_module_routes(self) -> t.List[BaseRoute]:
122122
)
123123
)
124124

125-
for _, module_ref in self._injector.get_modules().items():
126-
module_ref.run_module_register_services()
127-
_routes.extend(module_ref.routes)
125+
# for _, module_ref in self._injector.get_modules().items():
126+
# _routes.extend(module_ref.routes)
127+
# module_ref.run_module_register_services()
128128

129129
return _routes
130130

131131
def install_module(
132132
self,
133-
module: t.Union[t.Type[T], t.Type[ModuleBase]],
133+
module: t.Union[t.Type[T], t.Type[ModuleBase], DynamicModule],
134134
**init_kwargs: t.Any,
135135
) -> t.Union[T, ModuleBase]:
136-
module_ref = self.injector.get_module(module)
136+
if isinstance(module, DynamicModule):
137+
module_config = ModuleSetup(module.module, init_kwargs=init_kwargs)
138+
else:
139+
module_config = ModuleSetup(module, init_kwargs=init_kwargs)
140+
141+
module_ref = self.injector.get_module(module_config.module)
137142
if module_ref:
138143
return module_ref.get_module_instance()
139144

140-
module_ref = create_module_ref_factor(
141-
module, container=self.injector.container, config=self.config, **init_kwargs
145+
module_ref = module_config.get_module_ref( # type: ignore
146+
config=self.config,
147+
container=self.injector.container,
142148
)
149+
if not module_ref:
150+
raise Exception(f"Invalid Module Configuration for {module_config.module}")
151+
143152
self.injector.add_module(module_ref)
144-
self.rebuild_middleware_stack()
145153

154+
module_ref.run_module_register_services()
146155
if isinstance(module_ref, ModuleTemplateRef):
147-
module_ref.run_module_register_services()
148156
self.router.extend(module_ref.routes)
149157
self.reload_static_app()
150-
module_ref.run_application_ready(self)
158+
159+
self.rebuild_middleware_stack()
151160

152161
return t.cast(T, module_ref.get_module_instance())
153162

@@ -252,17 +261,10 @@ def enable_versioning(
252261
**init_kwargs,
253262
)
254263

255-
def _run_module_application_ready(
256-
self,
257-
) -> None:
258-
for _, module_ref in self.injector.get_templating_modules().items():
259-
module_ref.run_application_ready(self)
260-
261264
def _finalize_app_initialization(self) -> None:
262265
self.injector.container.register_instance(self)
263266
self.injector.container.register_instance(self.config, Config)
264267
self.injector.container.register_instance(self.jinja_environment, Environment)
265-
self._run_module_application_ready()
266268

267269
def add_exception_handler(
268270
self,

ellar/core/modules/__init__.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
11
from .base import ModuleBase
2+
from .config import DynamicModule, IModuleSetup, ModuleSetup
23
from .ref import ModulePlainRef, ModuleRefBase, ModuleTemplateRef
34

4-
__all__ = ["ModuleBase", "ModulePlainRef", "ModuleTemplateRef", "ModuleRefBase"]
5+
__all__ = [
6+
"ModuleBase",
7+
"ModulePlainRef",
8+
"ModuleTemplateRef",
9+
"ModuleRefBase",
10+
"IModuleSetup",
11+
"ModuleSetup",
12+
"DynamicModule",
13+
]

ellar/core/modules/base.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@
77
from ellar.core.modules.builder import ModuleBaseBuilder
88
from ellar.di.injector import Container
99

10-
if t.TYPE_CHECKING: # pragma: no cover
11-
from ellar.core import App
12-
1310

1411
class ModuleBaseMeta(type):
1512
__MODULE_FIELDS__: t.Dict = {}
@@ -26,11 +23,10 @@ def __init__(cls, name, bases, namespace) -> None:
2623

2724
class ModuleBase(_InjectorModule, metaclass=ModuleBaseMeta):
2825
@classmethod
29-
def before_init(cls, config: Config) -> None:
30-
"""Before Module initialisation"""
31-
32-
def application_ready(self, app: "App") -> None:
33-
"""Application Ready"""
26+
def before_init(cls, config: Config) -> t.Dict:
27+
"""Before Module initialisation. Whatever value that is returned here will be passed
28+
to the Module constructor during initialisation"""
29+
return {}
3430

3531
def register_services(self, container: Container) -> None:
3632
"""Register other services manually"""

0 commit comments

Comments
 (0)