|
21 | 21 | T = TypeVar("T") |
22 | 22 | P = ParamSpec("P") |
23 | 23 | CONTAINER_NAME: Final = "dishka_container" |
| 24 | +CONTAINER_NAME_REQ: Final = "dishka_container_req" |
24 | 25 |
|
25 | 26 |
|
26 | 27 | def inject(func: Callable[P, T]) -> Callable[P, T]: |
27 | | - # Try to isolate a parameter in the function signature requesting a |
28 | | - # typer.Context |
29 | 28 | hints = get_type_hints(func) |
30 | | - param_name = next( |
| 29 | + context_hint = next( |
31 | 30 | (name for name, hint in hints.items() if hint is typer.Context), |
32 | 31 | None, |
33 | 32 | ) |
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 | + ] |
63 | 41 | 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 | + ) |
113 | 59 |
|
114 | 60 |
|
115 | 61 | def _inject_commands(app: typer.Typer) -> None: |
|
0 commit comments