Skip to content

Commit 08f14b9

Browse files
authored
Merge pull request #224 from python-ellar/di_exports
feat: Module Container Services Encapsulation
2 parents 7cc2b6d + 49d306c commit 08f14b9

File tree

74 files changed

+2276
-1126
lines changed

Some content is hidden

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

74 files changed

+2276
-1126
lines changed

ellar/app/core_module.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import typing as t
2+
3+
from ellar.auth import AppIdentitySchemes, IdentityAuthenticationService
4+
from ellar.auth.session import SessionServiceNullStrategy, SessionStrategy
5+
from ellar.common import (
6+
GlobalGuard,
7+
GuardCanActivate,
8+
IApplicationReady,
9+
IApplicationShutdown,
10+
IApplicationStartup,
11+
IExceptionMiddlewareService,
12+
IExecutionContext,
13+
IExecutionContextFactory,
14+
IGuardsConsumer,
15+
IHostContext,
16+
IHostContextFactory,
17+
IHTTPConnectionContextFactory,
18+
IIdentitySchemes,
19+
IInterceptorsConsumer,
20+
IWebSocketContextFactory,
21+
Module,
22+
)
23+
from ellar.core import ModuleSetup
24+
from ellar.core.conf import Config
25+
from ellar.core.exceptions.service import ExceptionMiddlewareService
26+
from ellar.core.execution_context import ExecutionContextFactory, HostContextFactory
27+
from ellar.core.execution_context.factory import (
28+
HTTPConnectionContextFactory,
29+
WebSocketContextFactory,
30+
)
31+
from ellar.core.guards import GuardConsumer
32+
from ellar.core.interceptors import EllarInterceptorConsumer
33+
from ellar.core.services import Reflector, reflector
34+
from ellar.di import EllarInjector, ProviderConfig, injectable, request_scope
35+
from ellar.di.injector.tree_manager import ModuleTreeManager
36+
37+
if t.TYPE_CHECKING: # pragma: no cover
38+
from ellar.app import App
39+
40+
41+
def _raise_unavailable_exception() -> None:
42+
raise Exception("Service is only available during request")
43+
44+
45+
@injectable
46+
class GlobalCanActivatePlaceHolder(GuardCanActivate):
47+
placeholder: bool = True
48+
49+
async def can_activate(self, context: IExecutionContext) -> bool:
50+
return True
51+
52+
53+
def get_core_module(app_module: t.Union[t.Type, t.Any], config: Config) -> t.Type:
54+
@Module(
55+
modules=[app_module],
56+
providers=[
57+
ProviderConfig(
58+
Config,
59+
use_value=config,
60+
export=True,
61+
),
62+
ProviderConfig(
63+
GlobalGuard,
64+
use_class=GlobalCanActivatePlaceHolder,
65+
export=True,
66+
),
67+
ProviderConfig(
68+
ModuleTreeManager,
69+
use_value=lambda: config.MODULE_TREE_MANAGER_CLASS(
70+
ModuleSetup(EllarCoreModule)
71+
),
72+
export=True,
73+
),
74+
ProviderConfig(
75+
IExceptionMiddlewareService,
76+
use_class=ExceptionMiddlewareService,
77+
export=True,
78+
),
79+
ProviderConfig(
80+
IExecutionContextFactory, use_class=ExecutionContextFactory, export=True
81+
),
82+
ProviderConfig(
83+
IHostContextFactory, use_class=HostContextFactory, export=True
84+
),
85+
ProviderConfig(
86+
IHostContext,
87+
use_value=_raise_unavailable_exception,
88+
scope=request_scope,
89+
export=True,
90+
),
91+
ProviderConfig(
92+
IExecutionContext,
93+
use_value=_raise_unavailable_exception,
94+
scope=request_scope,
95+
export=True,
96+
),
97+
ProviderConfig(
98+
IHTTPConnectionContextFactory,
99+
use_class=HTTPConnectionContextFactory,
100+
export=True,
101+
),
102+
ProviderConfig(
103+
IWebSocketContextFactory, use_class=WebSocketContextFactory, export=True
104+
),
105+
ProviderConfig(Reflector, use_value=reflector, export=True),
106+
ProviderConfig(
107+
IInterceptorsConsumer, use_value=EllarInterceptorConsumer, export=True
108+
),
109+
ProviderConfig(IGuardsConsumer, use_class=GuardConsumer, export=True),
110+
ProviderConfig(IIdentitySchemes, use_class=AppIdentitySchemes, export=True),
111+
ProviderConfig(
112+
IdentityAuthenticationService,
113+
use_class=IdentityAuthenticationService,
114+
export=True,
115+
),
116+
ProviderConfig(
117+
SessionStrategy, use_class=SessionServiceNullStrategy, export=True
118+
),
119+
],
120+
)
121+
class EllarCoreModule(IApplicationReady, IApplicationStartup, IApplicationShutdown):
122+
def __init__(self, _config: Config, injector: EllarInjector) -> None:
123+
self.config = _config
124+
self.injector = injector
125+
126+
def on_ready(self, app: "App") -> None:
127+
pass
128+
129+
async def on_startup(self, app: "App") -> None:
130+
pass
131+
132+
async def on_shutdown(self) -> None:
133+
pass
134+
135+
return EllarCoreModule

ellar/app/factory.py

Lines changed: 70 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,29 @@
11
import typing as t
2-
from collections import OrderedDict
32
from pathlib import Path
43

54
import click
5+
from ellar.app.core_module import get_core_module
66
from ellar.common import IApplicationReady, Module
7-
from ellar.common.constants import MODULE_METADATA, MODULE_WATERMARK
7+
from ellar.common.constants import MODULE_METADATA
8+
from ellar.common.exceptions import ImproperConfiguration
89
from ellar.common.models import GuardCanActivate
9-
from ellar.core import Config, DynamicModule, LazyModuleImport, ModuleBase, ModuleSetup
10-
from ellar.core.context import ApplicationContext
11-
from ellar.core.modules import ModuleRefBase
10+
from ellar.core import (
11+
Config,
12+
DynamicModule,
13+
ForwardRefModule,
14+
LazyModuleImport,
15+
ModuleBase,
16+
ModuleSetup,
17+
)
18+
from ellar.core.modules import ModuleRefBase, ModuleTemplateRef
1219
from ellar.di import EllarInjector, ProviderConfig
20+
from ellar.di.injector.tree_manager import ModuleTreeManager
1321
from ellar.reflect import reflect
1422
from ellar.threading.sync_worker import execute_async_context_manager
1523
from ellar.utils import get_name, get_unique_type
16-
from starlette.routing import BaseRoute, Host, Mount
24+
from starlette.routing import Host, Mount
1725

1826
from .main import App
19-
from .services import EllarAppService
2027

2128
if t.TYPE_CHECKING: # pragma: no cover
2229
from ellar.common import ModuleRouter
@@ -29,30 +36,31 @@ class AppFactory:
2936
"""
3037

3138
@classmethod
32-
def get_all_modules(cls, module_config: ModuleSetup) -> t.List[ModuleSetup]:
33-
"""
34-
Gets all registered modules from a particular module in their order of dependencies
35-
:param module_config: Module Type
36-
:return: t.List[t.Type[ModuleBase]]
39+
def read_all_module(
40+
cls,
41+
module_config: t.Union[ModuleSetup, ModuleRefBase],
42+
tree_manager: t.Optional[ModuleTreeManager] = None,
43+
) -> ModuleTreeManager:
3744
"""
38-
module_dependency = [module_config] + list(
39-
cls.read_all_module(module_config).values()
40-
)
41-
return module_dependency
45+
Retrieves all module dependencies registered in another module
4246
43-
@classmethod
44-
def read_all_module(cls, module_config: ModuleSetup) -> t.Dict[t.Type, ModuleSetup]:
45-
"""
46-
Retrieves all modules dependencies registered in another module
47+
:param tree_manager: Module Tree Manager
4748
:param module_config: Module Type
4849
:return: t.Dict[t.Type, t.Type[ModuleBase]]
4950
"""
5051
global_module_config = module_config
52+
tree_manager = tree_manager or ModuleTreeManager().add_module(
53+
global_module_config.module, value=module_config
54+
)
55+
5156
modules = (
5257
reflect.get_metadata(MODULE_METADATA.MODULES, module_config.module) or []
5358
)
54-
module_dependency = OrderedDict()
5559
for module in modules:
60+
if isinstance(module, ForwardRefModule):
61+
# will be initialized using module_config moduleRef setup
62+
continue
63+
5664
if isinstance(module, LazyModuleImport):
5765
module = module.get_module(global_module_config.module.__name__)
5866

@@ -64,60 +72,14 @@ def read_all_module(cls, module_config: ModuleSetup) -> t.Dict[t.Type, ModuleSet
6472
else:
6573
module_config = ModuleSetup(module)
6674

67-
module_dependency[module_config.module] = module_config
68-
module_dependency.update(cls.read_all_module(module_config))
69-
return module_dependency
70-
71-
@classmethod
72-
def _build_modules(
73-
cls,
74-
app_module: t.Type[t.Union[ModuleBase, t.Any]],
75-
config: "Config",
76-
injector: EllarInjector,
77-
) -> t.List[BaseRoute]:
78-
"""
79-
builds application module and registers them to EllarInjector
80-
:param app_module: Root App Module
81-
:param config: App Configuration instance
82-
:param injector: App Injector instance
83-
:return: `None`
84-
"""
85-
if isinstance(app_module, LazyModuleImport):
86-
app_module = app_module.get_module("AppFactory")
87-
88-
assert reflect.get_metadata(
89-
MODULE_WATERMARK, app_module
90-
), "Only Module is allowed"
91-
92-
app_module_config = ModuleSetup(app_module)
93-
module_dependency = cls.get_all_modules(app_module_config)
94-
routes = []
95-
96-
for module_config in reversed(module_dependency):
97-
if injector.get_module(module_config.module): # pragma: no cover
98-
continue
99-
100-
module_ref = module_config.get_module_ref(
101-
container=injector.container, config=config
102-
)
103-
104-
if isinstance(module_ref, ModuleRefBase):
105-
routes.extend(module_ref.routes)
106-
107-
injector.add_module(module_ref)
108-
109-
for module_config in reversed(list(injector.get_dynamic_modules())):
110-
if injector.get_module(module_config.module): # pragma: no cover
111-
continue
112-
113-
module_ref = module_config.configure_with_factory(
114-
config, injector.container
75+
tree_manager.add_module(
76+
module_type=module_config.module,
77+
value=module_config,
78+
parent_module=global_module_config.module,
11579
)
116-
# module_ref.run_module_register_services()
117-
routes.extend(module_ref.routes)
118-
injector.add_module(module_ref)
11980

120-
return routes
81+
cls.read_all_module(module_config, tree_manager)
82+
return tree_manager
12183

12284
@classmethod
12385
@t.no_type_check
@@ -145,51 +107,56 @@ def _get_config_kwargs() -> t.Dict:
145107

146108
config = Config(app_configured=True, **_get_config_kwargs())
147109

148-
injector = EllarInjector(auto_bind=config.INJECTOR_AUTO_BIND, parent=injector)
149-
injector.container.register_instance(config, concrete_type=Config)
110+
# injector = EllarInjector(auto_bind=config.INJECTOR_AUTO_BIND, parent=injector)
111+
# injector.container.register_instance(config, concrete_type=Config)
150112

151-
service = EllarAppService(injector, config)
152-
service.register_core_services()
113+
core_module_ref = ModuleTemplateRef(
114+
module_type=get_core_module(module, config),
115+
parent_container=injector.container if injector else None,
116+
config=config,
117+
)
118+
core_module_ref.initiate_module_build()
153119

154-
with execute_async_context_manager(ApplicationContext(injector)) as context:
155-
routes = cls._build_modules(
156-
app_module=module, injector=injector, config=config
157-
)
120+
# service = EllarAppService(injector, config)
121+
# service.register_core_services()
122+
123+
with execute_async_context_manager(core_module_ref.context()) as context:
124+
tree_manager: ModuleTreeManager = core_module_ref.get(ModuleTreeManager)
125+
cls.read_all_module(core_module_ref, tree_manager)
126+
# Build application first level. This will trigger ApplicationModule to be built
127+
core_module_ref.build_dependencies(step=1)
128+
app_module_ref = tree_manager.get_root_module()
158129

159130
app = App(
160-
routes=routes,
131+
routes=[],
161132
config=config,
162-
injector=injector,
133+
injector=app_module_ref.container.injector,
163134
lifespan=config.DEFAULT_LIFESPAN_HANDLER,
164135
global_guards=global_guards,
165136
)
166137
# tag application instance by ApplicationModule name
167-
context.injector.container.register_instance(app, App, tag=get_name(module))
168-
169-
for module_config in reversed(
170-
list(context.injector.get_app_dependent_modules())
171-
):
172-
if context.injector.get_module(
173-
module_config.module
174-
): # pragma: no cover
175-
continue
176-
177-
module_ref = module_config.configure_with_factory(
178-
config, context.injector.container
179-
)
138+
core_module_ref.add_provider(
139+
ProviderConfig(App, use_value=app, tag=get_name(module), export=True)
140+
)
141+
app_module_ref.build_dependencies()
142+
143+
routes = core_module_ref.get_routes()
144+
app.router.extend(routes)
180145

181-
assert isinstance(
182-
module_ref, ModuleRefBase
183-
), f"{module_config.module} is not properly configured."
146+
for item in config.OVERRIDE_CORE_SERVICE:
147+
provider_type = item.get_type()
148+
if provider_type not in core_module_ref.providers:
149+
raise ImproperConfiguration(
150+
f"There is not core service identify as {provider_type}"
151+
)
184152

185-
context.injector.add_module(module_ref)
186-
app.router.extend(module_ref.routes)
153+
core_module_ref.add_provider(item, export=True)
187154

188155
# app.setup_jinja_environment
189156
app.setup_jinja_environment()
190157

191-
for module, module_ref in context.injector.get_modules().items():
192-
module_ref.run_module_register_services()
158+
for module, data in context.injector.tree_manager.modules.items():
159+
data.value.run_module_register_services()
193160

194161
if issubclass(module, IApplicationReady):
195162
context.injector.get(module).on_ready(app)

0 commit comments

Comments
 (0)