Skip to content

Commit 10081d2

Browse files
committed
Refactored InjectByTag
1 parent f370f9e commit 10081d2

File tree

5 files changed

+60
-12
lines changed

5 files changed

+60
-12
lines changed

docs/basics/testing.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@ When controllers use `InjectByTag` for dependency injection, the resolver automa
305305

306306
```python
307307
from ellar.di import InjectByTag, ProviderConfig
308+
from ellar.common.types import T
308309

309310
# Module with tagged provider
310311
@Module(
@@ -316,12 +317,14 @@ from ellar.di import InjectByTag, ProviderConfig
316317
class UserModule:
317318
pass
318319

319-
# Controller using tagged dependency
320+
# Controller using tagged dependency (supports both syntaxes)
320321
@Controller()
321322
class UserController:
322-
def __init__(self, user_repo: InjectByTag('user_repo')):
323+
def __init__(self, user_repo: InjectByTag[T("user_repo")]): # Generic syntax
323324
self.user_repo = user_repo
324325

326+
# Or use callable syntax: InjectByTag('user_repo')
327+
325328
# Automatic resolution works with tags!
326329
test_module = Test.create_test_module(
327330
controllers=[UserController],

docs/overview/providers.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,5 +221,11 @@ class AModule(ModuleBase):
221221

222222
In the above example, we are tagging `Foo` as `first_foo` and `FooB` as `second_foo`. By doing this, we can resolve both services using their tag names, thus providing the possibility of resolving services by tag name or type.
223223

224-
Also, services can be injected as a dependency by using tags. To achieve this, the `InjectByTag` decorator is used as a `**constructor**` argument.
224+
Also, services can be injected as a dependency by using tags. To achieve this, `InjectByTag` is used as a `**constructor**` argument.
225225
This allows for more flexibility in managing dependencies and resolving services based on tags.
226+
227+
`InjectByTag` supports two syntaxes:
228+
- **Callable syntax**: `InjectByTag('tag_name')`
229+
- **Generic syntax**: `InjectByTag[T("tag_name")]` where T is from `ellar.common.types.T`
230+
231+
Both syntaxes work identically and can be used interchangeably based on your preference.

ellar/common/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,13 @@
9999
from .serializer import Serializer, serialize_object
100100
from .templating import TemplateResponse, render_template, render_template_string
101101

102+
103+
def T(tag: str) -> str:
104+
return tag
105+
106+
102107
__all__ = [
108+
"T",
103109
"AnonymousIdentity",
104110
"ControllerBase",
105111
"serialize_object",

ellar/di/service_config.py

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,12 @@ class ProviderConfig(t.Generic[T]):
7272
>>> provider_config = ProviderConfig(SomeClass, scope=request_scope)
7373
>>> provider_config.register(container)
7474
75-
Example with tag:
75+
Example with tag (supports both callable and generic syntax):
7676
7777
>>> provider_config = ProviderConfig(SomeClass, tag='some_tag')
7878
>>> provider_config.register(container)
79-
>>> instance = container.get(InjectByTag('some_tag'))
79+
>>> instance = container.get(InjectByTag('some_tag')) # Callable syntax
80+
>>> # or: instance = container.get(InjectByTag[T('some_tag')]) # Generic syntax
8081
>>> assert isinstance(instance, SomeClass)
8182
8283
@@ -315,10 +316,14 @@ def get_scope(
315316
)
316317

317318

318-
def InjectByTag(tag: str) -> t.Any:
319+
class _InjectByTagClass:
319320
"""
320321
Inject a provider/service by tag.
321322
323+
Supports both callable and generic syntax:
324+
- InjectByTag('A') # callable syntax
325+
- InjectByTag['A'] # generic syntax
326+
322327
For example:
323328
324329
class A:
@@ -332,6 +337,12 @@ def __init__(self, a: InjectByTag('A'), b: AnotherType):
332337
self.a = a
333338
self.b = b
334339
340+
# Or using generic syntax:
341+
class SomeClass:
342+
def __init__(self, a: InjectByTag['A'], b: AnotherType):
343+
self.a = a
344+
self.b = b
345+
335346
injector = EllarInjector()
336347
injector.container.register_exact_scoped(A, tag='A')
337348
injector.container.register_exact_scoped(AnotherType)
@@ -340,8 +351,30 @@ def __init__(self, a: InjectByTag('A'), b: AnotherType):
340351
instance = injector.get(SomeClass)
341352
assert instance.a.name == 'A'
342353
assert instance.b.name == 'AnotherType'
343-
344-
:param tag: Registered Provider/Service tag name
345-
:return: typing.Any
346354
"""
347-
return t.NewType(Tag(tag), Tag)
355+
356+
def __call__(self, tag: str) -> t.Any:
357+
"""
358+
Callable syntax: InjectByTag('tag_name')
359+
360+
:param tag: Registered Provider/Service tag name
361+
:return: A NewType for tagged dependency injection
362+
"""
363+
return t.NewType(Tag(tag), Tag)
364+
365+
def __getitem__(self, tag: str) -> t.Any:
366+
"""
367+
Generic syntax: InjectByTag['tag_name']
368+
369+
:param tag: Registered Provider/Service tag name
370+
:return: A NewType for tagged dependency injection
371+
"""
372+
return t.NewType(Tag(tag), Tag)
373+
374+
def __repr__(self) -> str:
375+
"""Return a string representation for debugging"""
376+
return "InjectByTag"
377+
378+
379+
# Create singleton instance to support both syntaxes
380+
InjectByTag = _InjectByTagClass()

tests/test_testing_dependency_resolution.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"""
44

55
import pytest
6-
from ellar.common import Controller, Module, get
6+
from ellar.common import Controller, Module, T, get
77
from ellar.core import ForwardRefModule, ModuleBase
88
from ellar.di import InjectByTag, ProviderConfig, injectable
99
from ellar.di.exceptions import UnsatisfiedRequirement
@@ -519,7 +519,7 @@ class ComplexAppModule(ModuleBase):
519519

520520
@Controller()
521521
class ComplexController:
522-
def __init__(self, user_repo: InjectByTag("user_repo"), auth: IAuthService):
522+
def __init__(self, user_repo: InjectByTag[T("user_repo")], auth: IAuthService):
523523
self.user_repo = user_repo
524524
self.auth = auth
525525

0 commit comments

Comments
 (0)