Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion modern_di_aiogram/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,18 @@ def inject(func: typing.Callable[..., T]) -> typing.Callable[..., T]:
visible_params = [p for name, p in original_signature.parameters.items() if name not in di_params]
container_param_injected = _CHILD_CONTAINER_KEY not in original_signature.parameters
if container_param_injected:
visible_params.append(
has_var_keyword = bool(visible_params) and visible_params[-1].kind is inspect.Parameter.VAR_KEYWORD
visible_params.insert(
len(visible_params) - has_var_keyword,
inspect.Parameter(_CHILD_CONTAINER_KEY, kind=inspect.Parameter.KEYWORD_ONLY, annotation=Container),
)

def resolve(kwargs: dict[str, typing.Any]) -> dict[str, typing.Any]:
container = _fetch_child_container(kwargs)
if container_param_injected:
del kwargs[_CHILD_CONTAINER_KEY]
for name in di_params:
kwargs.pop(name, None)
return integrations.resolve_markers(container, di_params)

wrapper: typing.Callable[..., typing.Any]
Expand Down
73 changes: 73 additions & 0 deletions tests/test_inject.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,76 @@ async def async_handler(app: typing.Annotated[SimpleCreator, FromDI(SimpleCreato

assert not inspect.iscoroutinefunction(inject(sync_handler))
assert inspect.iscoroutinefunction(inject(async_handler))


async def test_inject_handler_with_var_keyword_gets_dependency_and_aiogram_data(
dispatcher: Dispatcher,
bot: Bot,
) -> None:
seen: dict[str, typing.Any] = {}

@dispatcher.message()
@inject
async def handler(
message: Message, # noqa: ARG001
app_instance: typing.Annotated[SimpleCreator, FromDI(SimpleCreator)],
**kwargs: typing.Any, # noqa: ANN401
) -> None:
seen["app"] = app_instance.dep1
seen["bot"] = kwargs.get("bot") is bot
seen["container_leaked"] = "modern_di_container" in kwargs

await dispatcher.emit_startup()
await dispatcher.feed_update(bot, make_message_update())
await dispatcher.emit_shutdown()

assert seen == {"app": "original", "bot": True, "container_leaked": False}


def test_inject_keeps_var_keyword_last_in_wrapper_signature() -> None:
async def with_kwargs(
message: Message,
app: typing.Annotated[SimpleCreator, FromDI(SimpleCreator)],
**kwargs: typing.Any, # noqa: ANN401
) -> None: ...

async def without_kwargs(
message: Message,
app: typing.Annotated[SimpleCreator, FromDI(SimpleCreator)],
) -> None: ...

def kinds(func: typing.Callable[..., typing.Any]) -> list[tuple[str, typing.Any]]:
return [(p.name, p.kind) for p in inspect.signature(inject(func)).parameters.values()]

assert kinds(with_kwargs) == [
("message", inspect.Parameter.POSITIONAL_OR_KEYWORD),
("modern_di_container", inspect.Parameter.KEYWORD_ONLY),
("kwargs", inspect.Parameter.VAR_KEYWORD),
]
assert kinds(without_kwargs) == [
("message", inspect.Parameter.POSITIONAL_OR_KEYWORD),
("modern_di_container", inspect.Parameter.KEYWORD_ONLY),
]


async def test_inject_fromdi_param_named_like_aiogram_data_wins_over_var_keyword(
dispatcher: Dispatcher,
bot: Bot,
) -> None:
seen: dict[str, typing.Any] = {}

@dispatcher.message()
@inject
async def handler(
message: Message, # noqa: ARG001
bot: typing.Annotated[SimpleCreator, FromDI(SimpleCreator)],
**kwargs: typing.Any, # noqa: ANN401
) -> None:
seen["bot"] = bot.dep1
seen["has_event_chat"] = "event_chat" in kwargs

await dispatcher.emit_startup()
await dispatcher.feed_update(bot, make_message_update())
await dispatcher.emit_shutdown()

assert seen == {"bot": "original", "has_event_chat": True}
Loading