Skip to content

Commit ddb2043

Browse files
committed
fixed forwardref module circular dependency
1 parent 5ff0ef0 commit ddb2043

File tree

12 files changed

+272
-37
lines changed

12 files changed

+272
-37
lines changed

ellar/app/factory.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,9 @@ def _get_config_kwargs() -> t.Dict:
159159

160160
# app.setup_jinja_environment
161161
app.setup_jinja_environment()
162+
core_module_ref.run_module_register_services()
162163

163-
for module, data in context.tree_manager.modules.items():
164-
data.value.run_module_register_services()
165-
164+
for module in context.tree_manager.modules.keys():
166165
if issubclass(module, IApplicationReady):
167166
context.get(module).on_ready(app)
168167

ellar/app/main.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,20 +91,25 @@ def _config_logging(self) -> None:
9191
if self.config.LOG_LEVEL
9292
else constants.LOG_LEVELS.info.value
9393
)
94-
logger_ = logging.getLogger("ellar")
95-
if not logger_.handlers:
94+
logger_ellar = logging.getLogger("ellar")
95+
logger_ellar_request = logging.getLogger("ellar.request")
96+
logger_ellar_di = logging.getLogger("ellar.di")
97+
98+
if not logger_ellar.handlers:
9699
formatter = logging.Formatter(constants.ELLAR_LOG_FMT_STRING)
97100
stream_handler = logging.StreamHandler()
98101
# file_handler = logging.FileHandler("my_app.log")
99102
# file_handler.setFormatter(formatter)
100103
# logger_.addHandler(file_handler)
101104
stream_handler.setFormatter(formatter)
102-
logger_.addHandler(stream_handler)
103105

104-
logger_.setLevel(log_level)
105-
else:
106-
logging.getLogger("ellar").setLevel(log_level)
107-
logging.getLogger("ellar.request").setLevel(log_level)
106+
logger_ellar.addHandler(stream_handler)
107+
logger_ellar_request.addHandler(stream_handler)
108+
logger_ellar_di.addHandler(stream_handler)
109+
110+
logger_ellar.setLevel(log_level)
111+
logger_ellar_request.setLevel(log_level)
112+
logger_ellar_di.setLevel(log_level)
108113

109114
def get_guards(self) -> t.List[t.Union[t.Type[GuardCanActivate], GuardCanActivate]]:
110115
return self.__global_guard + self._global_guards

ellar/common/constants.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,11 @@ def __deepcopy__(
169169
"level": "INFO",
170170
"propagate": False,
171171
},
172+
"ellar.di": {
173+
"handlers": ["ellar-default"],
174+
"level": "INFO",
175+
"propagate": False,
176+
},
172177
},
173178
}
174179

ellar/core/modules/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from .base import ModuleBase, ModuleBaseMeta
22
from .config import DynamicModule, ForwardRefModule, LazyModuleImport, ModuleSetup
3-
from .ref import ModulePlainRef, ModuleRefBase, ModuleTemplateRef
3+
from .ref import ModuleForwardRef, ModulePlainRef, ModuleRefBase, ModuleTemplateRef
44

55
__all__ = [
66
"ModuleBase",
@@ -11,5 +11,6 @@
1111
"DynamicModule",
1212
"ModuleBaseMeta",
1313
"LazyModuleImport",
14+
"ModuleForwardRef",
1415
"ForwardRefModule",
1516
]

ellar/core/modules/config.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from ellar.common.constants import MODULE_METADATA, MODULE_WATERMARK
88
from ellar.common.exceptions import ImproperConfiguration
99
from ellar.core.conf import Config
10+
from ellar.core.modules.ref.forward import ModuleForwardRef
1011
from ellar.di import (
1112
MODULE_REF_TYPES,
1213
Container,
@@ -253,16 +254,18 @@ def __init__(
253254

254255
def resolve_module_dependency(self, parent_module_ref: "ModuleRefBase") -> None:
255256
tree_manager = parent_module_ref.container.injector.tree_manager
256-
module = (
257+
module_ref = (
257258
self.get_module_dependency_by_name(tree_manager, parent_module_ref)
258259
if self.module_name
259260
else self.get_module_dependency_by_type(tree_manager, parent_module_ref)
260261
)
261-
tree_manager.add_module_dependency(parent_module_ref.module, module)
262+
forward_ref = ModuleForwardRef(module_ref)
263+
264+
tree_manager.add_forward_ref(parent_module_ref.module, forward_ref)
262265

263266
def get_module_dependency_by_name(
264267
self, tree_manager: ModuleTreeManager, parent_module_ref: "ModuleRefBase"
265-
) -> t.Type:
268+
) -> "ModuleRefBase":
266269
assert self.module_name, "'module_name' can't be None"
267270

268271
node = next(
@@ -276,11 +279,11 @@ def get_module_dependency_by_name(
276279
f"Please kindly ensure a @Module(name={self.module_name}) is registered"
277280
)
278281

279-
return node.value.module
282+
return t.cast(ModuleRefBase, node.value)
280283

281284
def get_module_dependency_by_type(
282285
self, tree_manager: ModuleTreeManager, parent_module_ref: "ModuleRefBase"
283-
) -> t.Type:
286+
) -> "ModuleRefBase":
284287
if isinstance(self.module, str):
285288
try:
286289
module_cls: t.Type["ModuleBase"] = t.cast(
@@ -302,7 +305,7 @@ def get_module_dependency_by_type(
302305
f"Please kindly ensure a {self.module} is decorated with @Module() is registered"
303306
)
304307

305-
return node.value.module
308+
return t.cast(ModuleRefBase, node.value)
306309

307310
def __hash__(self) -> int: # pragma: no cover
308311
return hash((self.module, "ForwardRefModule"))

ellar/core/modules/ref/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
from .base import ModuleRefBase
22
from .factory import InvalidModuleTypeException, create_module_ref_factor
3+
from .forward import ModuleForwardRef
34
from .plain import ModulePlainRef
45
from .template import ModuleTemplateRef
56

67
__all__ = [
78
"ModulePlainRef",
89
"ModuleRefBase",
910
"ModuleTemplateRef",
11+
"ModuleForwardRef",
1012
"create_module_ref_factor",
1113
"InvalidModuleTypeException",
1214
]

ellar/core/modules/ref/base.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from ellar.core.conf import Config
1818
from ellar.core.modules.base import ModuleBase, ModuleBaseMeta
1919
from ellar.core.modules.ref.context import ModuleExecutionContext
20+
from ellar.core.modules.ref.forward import ModuleForwardRef
2021
from ellar.core.router_builders import get_controller_builder_factory
2122
from ellar.core.routing import EllarControllerMount, RouteOperationBase
2223
from ellar.di import (
@@ -69,6 +70,9 @@ def __init__(
6970
def __repr__(self) -> str:
7071
return f"<{self.__class__.__name__} name={self.name} module={self.module}>"
7172

73+
def __hash__(self) -> int:
74+
return hash(self.module)
75+
7276
@cached_property
7377
def module_context(self) -> ModuleExecutionContext:
7478
return ModuleExecutionContext(container=self.container, module=self.module)
@@ -154,15 +158,28 @@ def initiate_module_build(self) -> None:
154158

155159
def run_module_register_services(self) -> None:
156160
"""
157-
Defer module instantiation till lifespan call
161+
Defer module instantiation till lifespan/modules ready call
158162
"""
163+
from ellar.core.modules.config import ForwardRefModule
164+
159165
_module_type_instance = self.get_module_instance()
160166
self.container.install(_module_type_instance) # support for injector module
161167
# _module_type_instance.register_services(self.container)
162168
modules = list(self.tree_manager.get_module_dependencies(self.module))
169+
163170
for module_config in reversed(modules):
171+
if isinstance(module_config.value, ModuleForwardRef):
172+
continue
164173
module_config.value.run_module_register_services()
165174

175+
registered_modules = (
176+
reflect.get_metadata(MODULE_METADATA.MODULES, self.module) or []
177+
)
178+
179+
for module in registered_modules:
180+
if isinstance(module, ForwardRefModule):
181+
module.resolve_module_dependency(self)
182+
166183
@abstractmethod
167184
def _register_module(self) -> None:
168185
"""Register Module"""
@@ -247,8 +264,6 @@ def build_controllers_and_routers(self) -> None:
247264
self._search_providers_and_build_controller(_routers)
248265

249266
def _build_module_parameters_and_routes(self) -> None:
250-
from ellar.core.modules.config import ForwardRefModule
251-
252267
if reflect.get_metadata(MODULE_WATERMARK, self.module):
253268
_providers = list(
254269
reflect.get_metadata(MODULE_METADATA.PROVIDERS, self.module) or []
@@ -258,8 +273,6 @@ def _build_module_parameters_and_routes(self) -> None:
258273
reflect.get_metadata(MODULE_METADATA.EXPORTS, self.module) or []
259274
)
260275

261-
modules = reflect.get_metadata(MODULE_METADATA.MODULES, self.module) or []
262-
263276
self.build_controllers_and_routers()
264277

265278
for provider in _providers:
@@ -268,10 +281,6 @@ def _build_module_parameters_and_routes(self) -> None:
268281
for export in _exports:
269282
self.add_exports(export)
270283

271-
for module in modules:
272-
if isinstance(module, ForwardRefModule):
273-
module.resolve_module_dependency(self)
274-
275284
def _search_providers_and_build_controller(
276285
self, controllers: t.List[t.Union[t.Type, t.Any]]
277286
) -> None:

ellar/core/modules/ref/forward.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import typing as t
2+
3+
from ellar.di import MODULE_REF_TYPES
4+
5+
if t.TYPE_CHECKING:
6+
from .base import ModuleRefBase
7+
8+
9+
class ModuleForwardRef:
10+
__slots__ = ("_module_ref",)
11+
12+
ref_type: str = MODULE_REF_TYPES.FORWARD_REF
13+
14+
def __init__(self, module_ref: "ModuleRefBase") -> None:
15+
self._module_ref = module_ref
16+
17+
def __getattr__(self, item: t.Any) -> t.Any:
18+
return getattr(self._module_ref, item)
19+
20+
def __hash__(self) -> int:
21+
return hash((self._module_ref.module, "ForwardRef"))

ellar/di/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,5 @@ class MODULE_REF_TYPES(metaclass=AnnotationToValue):
4040
PLAIN: str
4141
TEMPLATE: str
4242
DYNAMIC: str
43+
FORWARD_REF: str
4344
APP_DEPENDENT: str

ellar/di/injector/container.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from .ellar_injector import EllarInjector
2626

2727
NOT_SET = object()
28+
logger = logging.getLogger("ellar.di")
2829

2930

3031
class Container(InjectorBinder):
@@ -182,5 +183,8 @@ def get_binding(self, interface: t.Type) -> t.Tuple[Binding, InjectorBinder]:
182183
return module_owner.value.container._get_binding(interface)
183184

184185
except (KeyError, UnsatisfiedRequirement, Exception) as ex:
185-
logging.exception(ex)
186+
logger.error(
187+
f"Ensure {interface} is exported by a module. eg @Module(exports=[{interface}])"
188+
)
189+
logger.exception(ex)
186190
raise UnsatisfiedRequirement(None, interface) from uex

0 commit comments

Comments
 (0)