Skip to content

Commit 0607238

Browse files
committed
fixup! feat: support injexting typer context
1 parent 6543fd1 commit 0607238

File tree

1 file changed

+27
-81
lines changed

1 file changed

+27
-81
lines changed

src/dishka/integrations/typer.py

Lines changed: 27 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -21,95 +21,41 @@
2121
T = TypeVar("T")
2222
P = ParamSpec("P")
2323
CONTAINER_NAME: Final = "dishka_container"
24+
CONTAINER_NAME_REQ: Final = "dishka_container_req"
2425

2526

2627
def inject(func: Callable[P, T]) -> Callable[P, T]:
27-
# Try to isolate a parameter in the function signature requesting a
28-
# typer.Context
2928
hints = get_type_hints(func)
30-
param_name = next(
29+
context_hint = next(
3130
(name for name, hint in hints.items() if hint is typer.Context),
3231
None,
3332
)
34-
if param_name is None:
35-
# When the handler does not request a typer.Context, we need to add it
36-
# in our wrapper to be able to inject it in into the container
37-
def wrapper(context: typer.Context, *args: P.args, **kwargs: P.kwargs) -> T:
38-
# Inject the typer context into the container
39-
container: Container = context.meta[CONTAINER_NAME]
40-
with container({typer.Context: context}, scope=Scope.REQUEST) as new_container:
41-
context.meta[CONTAINER_NAME] = new_container
42-
43-
# Then proceed with the regular injection logic
44-
injected_func = wrap_injection(
45-
func=func,
46-
container_getter=lambda _, __: click.get_current_context().meta[CONTAINER_NAME],
47-
remove_depends=True,
48-
is_async=False,
49-
)
50-
return injected_func(*args, **kwargs)
51-
52-
# We reuse the logic of `wrap_injection`, but only to build the expected
53-
# signature (removing dishka dependencies, adding the typer.Context
54-
# parameter)
55-
expected_signature = wrap_injection(
56-
func=func,
57-
container_getter=lambda _, __: click.get_current_context().meta[CONTAINER_NAME],
58-
additional_params=[Parameter(name="context", kind=Parameter.POSITIONAL_ONLY, annotation=typer.Context)],
59-
remove_depends=True,
60-
is_async=False,
61-
)
62-
33+
if context_hint is None:
34+
additional_params = [
35+
Parameter(
36+
name="___dishka_context",
37+
annotation=typer.Context,
38+
kind=Parameter.KEYWORD_ONLY,
39+
),
40+
]
6341
else:
64-
# When the handler requests a typer.Context, we just need to find it and
65-
# inject
66-
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
67-
# Get the context from the existing argument
68-
if param_name in kwargs:
69-
context: typer.Context = kwargs[param_name] # type: ignore[assignment]
70-
else:
71-
maybe_context = next(
72-
# Even though we type `typer.Context`, we get a
73-
# `click.Context` instance
74-
(arg for arg in args if isinstance(arg, click.Context)), None,
75-
)
76-
if maybe_context is None:
77-
raise RuntimeError(f"Context argument {param_name} not provided at runtime.")
78-
context = maybe_context
79-
80-
# Inject the typer context into the container
81-
container: Container = context.meta[CONTAINER_NAME]
82-
with container({typer.Context: context}, scope=Scope.REQUEST) as new_container:
83-
context.meta[CONTAINER_NAME] = new_container
84-
85-
# Then proceed with the regular injection logic
86-
injected_func = wrap_injection(
87-
func=func,
88-
container_getter=lambda _, __: click.get_current_context().meta[CONTAINER_NAME],
89-
remove_depends=True,
90-
is_async=False,
91-
)
92-
return injected_func(*args, **kwargs)
93-
94-
# This time, no need to add a parameter to the signature
95-
expected_signature = wrap_injection(
96-
func=func,
97-
container_getter=lambda _, __: get_current_context().meta[CONTAINER_NAME],
98-
remove_depends=True,
99-
is_async=False,
100-
)
101-
102-
# Copy over all metadata from the expected injected function's signature to
103-
# our wrapper
104-
wrapper.__dishka_injected__ = True # type: ignore[attr-defined]
105-
wrapper.__name__ = expected_signature.__name__
106-
wrapper.__qualname__ = expected_signature.__qualname__
107-
wrapper.__doc__ = expected_signature.__doc__
108-
wrapper.__module__ = expected_signature.__module__
109-
wrapper.__annotations__ = expected_signature.__annotations__
110-
wrapper.__signature__ = expected_signature.__signature__ # type: ignore[attr-defined]
111-
112-
return cast(Callable[P, T], wrapper)
42+
additional_params = []
43+
44+
param_name = context_hint or "___dishka_context"
45+
46+
def get_container(_, p):
47+
context: typer.Context = p[param_name]
48+
container = context.meta[CONTAINER_NAME]
49+
with container({typer.Context: context}, scope=Scope.REQUEST) as req_container:
50+
context.meta[CONTAINER_NAME_REQ] = req_container
51+
return req_container
52+
53+
return wrap_injection(
54+
func=func,
55+
is_async=False,
56+
additional_params=additional_params,
57+
container_getter=get_container,
58+
)
11359

11460

11561
def _inject_commands(app: typer.Typer) -> None:

0 commit comments

Comments
 (0)