Skip to content

Commit 7ba4f99

Browse files
committed
Ellar DI module refactor for easy code review and readability
1 parent 5a12375 commit 7ba4f99

File tree

5 files changed

+197
-132
lines changed

5 files changed

+197
-132
lines changed

ellar/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ def __new__(mcls, name, bases, namespace):
6868
CONTROLLER_WATERMARK = "CONTROLLER_WATERMARK"
6969

7070
MULTI_RESOLVER_KEY = "MULTI_RESOLVER_KEY"
71+
MULTI_RESOLVER_FORM_GROUPED_KEY = "MULTI_RESOLVER_FORM_GROUPED_KEY"
7172
ROUTE_OPENAPI_PARAMETERS = "ROUTE_OPENAPI_PARAMETERS"
7273

7374
OPERATION_ENDPOINT_KEY = "OPERATION_ENDPOINT"

ellar/di/injector/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from .container import Container
2+
from .ellar_injector import EllarInjector
3+
from .request_provider import RequestServiceProvider
4+
5+
__all__ = ["Container", "EllarInjector", "RequestServiceProvider"]
Lines changed: 47 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,36 @@
11
import typing as t
2-
from collections import OrderedDict, defaultdict
32
from inspect import isabstract
43

5-
from injector import (
6-
Binder as InjectorBinder,
7-
Binding,
8-
Injector,
9-
Module as InjectorModule,
10-
)
4+
from injector import Binder as InjectorBinder, Binding, Module as InjectorModule
115

12-
from ellar.compatible import asynccontextmanager
13-
from ellar.constants import MODULE_REF_TYPES
146
from ellar.helper import get_name
15-
from ellar.logger import logger as log
167
from ellar.types import T
178

18-
from .providers import InstanceProvider, Provider
19-
from .scopes import (
9+
from ..providers import Provider
10+
from ..scopes import (
2011
DIScope,
2112
RequestScope,
2213
ScopeDecorator,
2314
SingletonScope,
2415
TransientScope,
2516
)
26-
from .service_config import get_scope
17+
from ..service_config import get_scope
2718

2819
if t.TYPE_CHECKING: # pragma: no cover
29-
from ellar.core.modules import ModuleBase, ModuleRefBase, ModuleTemplateRef
30-
31-
32-
class RequestServiceProvider(InjectorBinder):
33-
__slots__ = ("_bindings", "_log_prefix", "_context")
20+
from ellar.core.modules import ModuleBase
3421

35-
def __init__(self, container: "Container", auto_bind: bool = False) -> None:
36-
super(RequestServiceProvider, self).__init__(
37-
injector=container.injector, parent=container, auto_bind=auto_bind
38-
)
39-
self._context: t.Dict = {}
40-
self._log_prefix = container.injector._log_prefix
41-
42-
def get(self, interface: t.Type[T]) -> T:
43-
binding, binder = self.get_binding(interface)
44-
scope = binding.scope
45-
if isinstance(scope, ScopeDecorator):
46-
scope = scope.scope
47-
# Fetch the corresponding Scope instance from the Binder.
48-
scope_binding, _ = binder.get_binding(scope)
49-
scope_instance = t.cast(DIScope, scope_binding.provider.get(self))
50-
51-
log.debug(
52-
"%EllarInjector.get(%r, scope=%r) using %r",
53-
self._log_prefix,
54-
interface,
55-
scope,
56-
binding.provider,
57-
)
58-
result = scope_instance.get(
59-
interface, binding.provider, context=self._context
60-
).get(self.injector)
61-
log.debug("%s -> %r", self._log_prefix, result)
62-
return t.cast(T, result)
63-
64-
def update_context(self, interface: t.Type[T], value: T) -> None:
65-
assert not isinstance(value, type), f"value must be an object of {interface}"
66-
_context = {
67-
interface: Binding(interface, InstanceProvider(value), RequestScope)
68-
}
69-
if isinstance(value, Provider):
70-
_context = {interface: Binding(interface, value, RequestScope)}
71-
self._bindings.update(_context)
72-
73-
def dispose(self) -> None:
74-
del self._context
75-
del self.parent
76-
del self.injector
22+
from .ellar_injector import EllarInjector
7723

7824

7925
class Container(InjectorBinder):
80-
__slots__ = ("injector", "_auto_bind", "_bindings", "parent")
26+
__slots__ = (
27+
"injector",
28+
"_auto_bind",
29+
"_bindings",
30+
"parent",
31+
"_aliases",
32+
"_exact_aliases",
33+
)
8134

8235
injector: "EllarInjector"
8336

@@ -132,6 +85,12 @@ def register_singleton(
13285
base_type: t.Type[T],
13386
concrete_type: t.Union[t.Type[T], T, Provider] = None,
13487
) -> None:
88+
"""
89+
90+
:param base_type:
91+
:param concrete_type:
92+
:return:
93+
"""
13594
if not concrete_type:
13695
self.register_exact_singleton(base_type)
13796
self.register(base_type, concrete_type, scope=SingletonScope)
@@ -141,6 +100,12 @@ def register_transient(
141100
base_type: t.Type,
142101
concrete_type: t.Union[t.Type, Provider] = None,
143102
) -> None:
103+
"""
104+
105+
:param base_type:
106+
:param concrete_type:
107+
:return:
108+
"""
144109
if not concrete_type:
145110
self.register_exact_transient(base_type)
146111
self.register(base_type, concrete_type, scope=TransientScope)
@@ -150,19 +115,40 @@ def register_scoped(
150115
base_type: t.Type,
151116
concrete_type: t.Union[t.Type, Provider] = None,
152117
) -> None:
118+
"""
119+
120+
:param base_type:
121+
:param concrete_type:
122+
:return:
123+
"""
153124
if not concrete_type:
154125
self.register_exact_scoped(base_type)
155126
self.register(base_type, concrete_type, scope=RequestScope)
156127

157128
def register_exact_singleton(self, concrete_type: t.Type) -> None:
129+
"""
130+
131+
:param concrete_type:
132+
:return:
133+
"""
158134
assert not isabstract(concrete_type)
159135
self.register(base_type=concrete_type, scope=SingletonScope)
160136

161137
def register_exact_transient(self, concrete_type: t.Type) -> None:
138+
"""
139+
140+
:param concrete_type:
141+
:return:
142+
"""
162143
assert not isabstract(concrete_type)
163144
self.register(base_type=concrete_type, scope=TransientScope)
164145

165146
def register_exact_scoped(self, concrete_type: t.Type) -> None:
147+
"""
148+
149+
:param concrete_type:
150+
:return:
151+
"""
166152
assert not isabstract(concrete_type)
167153
self.register(base_type=concrete_type, scope=RequestScope)
168154

@@ -215,74 +201,3 @@ def register_services(self, container):
215201

216202
instance(self)
217203
return instance
218-
219-
220-
class EllarInjector(Injector):
221-
__slots__ = ("_stack", "parent", "app", "container", "_modules")
222-
223-
def __init__(
224-
self,
225-
auto_bind: bool = True,
226-
parent: "Injector" = None,
227-
) -> None:
228-
self._stack = ()
229-
230-
self.parent = parent
231-
# Binder
232-
self.container = Container(
233-
self,
234-
auto_bind=auto_bind,
235-
parent=parent.binder if parent is not None else None,
236-
)
237-
# Bind some useful types
238-
self.container.register_instance(self, EllarInjector)
239-
self.container.register_instance(self.binder)
240-
self._modules: t.DefaultDict = defaultdict(OrderedDict)
241-
self._modules[MODULE_REF_TYPES.TEMPLATE] = OrderedDict()
242-
self._modules[MODULE_REF_TYPES.PLAIN] = OrderedDict()
243-
244-
def get_modules(
245-
self,
246-
) -> t.Dict[t.Type["ModuleBase"], "ModuleRefBase"]:
247-
modules = dict(
248-
self._modules[MODULE_REF_TYPES.TEMPLATE],
249-
)
250-
modules.update(self._modules[MODULE_REF_TYPES.PLAIN])
251-
return modules
252-
253-
def get_module(self, module: t.Type) -> t.Optional["ModuleRefBase"]:
254-
result: t.Optional["ModuleRefBase"] = None
255-
if module in self._modules[MODULE_REF_TYPES.TEMPLATE]:
256-
result = self._modules[MODULE_REF_TYPES.TEMPLATE][module]
257-
return result
258-
259-
if module in self._modules[MODULE_REF_TYPES.PLAIN]:
260-
result = self._modules[MODULE_REF_TYPES.PLAIN][module]
261-
return result
262-
return result
263-
264-
def get_templating_modules(
265-
self,
266-
) -> t.Dict[t.Type["ModuleBase"], "ModuleTemplateRef"]:
267-
return self._modules.get(MODULE_REF_TYPES.TEMPLATE, {})
268-
269-
def add_module(self, module_ref: "ModuleRefBase") -> None:
270-
self._modules[module_ref.ref_type].update({module_ref.module: module_ref})
271-
272-
@property # type: ignore
273-
def binder(self) -> Container: # type: ignore
274-
return self.container
275-
276-
@binder.setter
277-
def binder(self, value: t.Any) -> None:
278-
"""Nothing happens"""
279-
280-
@asynccontextmanager
281-
async def create_request_service_provider(
282-
self,
283-
) -> t.AsyncGenerator[RequestServiceProvider, None]:
284-
request_provider = RequestServiceProvider(self.container)
285-
try:
286-
yield request_provider
287-
finally:
288-
request_provider.dispose()

ellar/di/injector/ellar_injector.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import typing as t
2+
from collections import OrderedDict, defaultdict
3+
4+
from injector import Injector
5+
6+
from ellar.compatible import asynccontextmanager
7+
from ellar.constants import MODULE_REF_TYPES
8+
9+
from .container import Container
10+
from .request_provider import RequestServiceProvider
11+
12+
if t.TYPE_CHECKING: # pragma: no cover
13+
from ellar.core.modules import ModuleBase, ModuleRefBase, ModuleTemplateRef
14+
15+
16+
class EllarInjector(Injector):
17+
__slots__ = ("_stack", "parent", "container", "_modules")
18+
19+
def __init__(
20+
self,
21+
auto_bind: bool = True,
22+
parent: "Injector" = None,
23+
) -> None:
24+
self._stack = ()
25+
26+
self.parent = parent
27+
# Binder
28+
self.container = Container(
29+
self,
30+
auto_bind=auto_bind,
31+
parent=parent.binder if parent is not None else None,
32+
)
33+
34+
# Bind some useful types
35+
self.container.register_instance(self, EllarInjector)
36+
self.container.register_instance(self.binder)
37+
self._modules: t.DefaultDict = defaultdict(OrderedDict)
38+
self._modules[MODULE_REF_TYPES.TEMPLATE] = OrderedDict()
39+
self._modules[MODULE_REF_TYPES.PLAIN] = OrderedDict()
40+
41+
@property # type: ignore
42+
def binder(self) -> Container: # type: ignore
43+
return self.container
44+
45+
@binder.setter
46+
def binder(self, value: t.Any) -> None:
47+
"""Nothing happens"""
48+
49+
def get_modules(
50+
self,
51+
) -> t.Dict[t.Type["ModuleBase"], "ModuleRefBase"]:
52+
modules = dict(
53+
self._modules[MODULE_REF_TYPES.TEMPLATE],
54+
)
55+
modules.update(self._modules[MODULE_REF_TYPES.PLAIN])
56+
return modules
57+
58+
def get_module(self, module: t.Type) -> t.Optional["ModuleRefBase"]:
59+
result: t.Optional["ModuleRefBase"] = None
60+
if module in self._modules[MODULE_REF_TYPES.TEMPLATE]:
61+
result = self._modules[MODULE_REF_TYPES.TEMPLATE][module]
62+
return result
63+
64+
if module in self._modules[MODULE_REF_TYPES.PLAIN]:
65+
result = self._modules[MODULE_REF_TYPES.PLAIN][module]
66+
return result
67+
return result
68+
69+
def get_templating_modules(
70+
self,
71+
) -> t.Dict[t.Type["ModuleBase"], "ModuleTemplateRef"]:
72+
return self._modules.get(MODULE_REF_TYPES.TEMPLATE, {})
73+
74+
def add_module(self, module_ref: "ModuleRefBase") -> None:
75+
self._modules[module_ref.ref_type].update({module_ref.module: module_ref})
76+
77+
@asynccontextmanager
78+
async def create_request_service_provider(
79+
self,
80+
) -> t.AsyncGenerator[RequestServiceProvider, None]:
81+
request_provider = RequestServiceProvider(self.container)
82+
try:
83+
yield request_provider
84+
finally:
85+
request_provider.dispose()

ellar/di/injector/request_provider.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import typing as t
2+
3+
from injector import Binder as InjectorBinder, Binding
4+
5+
from ellar.logger import logger as log
6+
from ellar.types import T
7+
8+
from ..providers import InstanceProvider, Provider
9+
from ..scopes import DIScope, RequestScope, ScopeDecorator
10+
11+
if t.TYPE_CHECKING: # pragma: no cover
12+
from .container import Container
13+
14+
15+
class RequestServiceProvider(InjectorBinder):
16+
__slots__ = ("_bindings", "_log_prefix", "_context")
17+
18+
def __init__(self, container: "Container", auto_bind: bool = False) -> None:
19+
super(RequestServiceProvider, self).__init__(
20+
injector=container.injector, parent=container, auto_bind=auto_bind
21+
)
22+
self._context: t.Dict = {}
23+
self._log_prefix = container.injector._log_prefix
24+
25+
def get(self, interface: t.Type[T]) -> T:
26+
binding, binder = self.get_binding(interface)
27+
scope = binding.scope
28+
if isinstance(scope, ScopeDecorator):
29+
scope = scope.scope
30+
# Fetch the corresponding Scope instance from the Binder.
31+
scope_binding, _ = binder.get_binding(scope)
32+
scope_instance = t.cast(DIScope, scope_binding.provider.get(self))
33+
34+
log.debug(
35+
"%EllarInjector.get(%r, scope=%r) using %r",
36+
self._log_prefix,
37+
interface,
38+
scope,
39+
binding.provider,
40+
)
41+
result = scope_instance.get(
42+
interface, binding.provider, context=self._context
43+
).get(self.injector)
44+
log.debug("%s -> %r", self._log_prefix, result)
45+
return t.cast(T, result)
46+
47+
def update_context(self, interface: t.Type[T], value: T) -> None:
48+
assert not isinstance(value, type), f"value must be an object of {interface}"
49+
_context = {
50+
interface: Binding(interface, InstanceProvider(value), RequestScope)
51+
}
52+
if isinstance(value, Provider):
53+
_context = {interface: Binding(interface, value, RequestScope)}
54+
self._bindings.update(_context)
55+
56+
def dispose(self) -> None:
57+
del self._context
58+
del self.parent
59+
del self.injector

0 commit comments

Comments
 (0)