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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def my_command(ctx: typer.Context) -> None:
- `setup_di(app, container)` — register the container with a Typer app
- `inject` — decorator that resolves `FromDI`-annotated parameters before the command runs; also exposes `typer.Context` with `ctx.obj["di_container"]` for manual use. Raises `RuntimeError` naming `setup_di` when a command reaches it without `setup_di` called
- `FromDI(provider)` — marker used in `Annotated[T, FromDI(...)]`; accepts a provider instance or a type
- `action_scope(ctx)` — context manager yielding a fresh `Scope.ACTION` container (a child of the command container); open one per action
- `action_scope(ctx)` — context manager yielding a fresh `Scope.ACTION` container (a child of the command container); open one per action. Raises `RuntimeError` naming `@inject` when called from a command without it
- `fetch_di_container(ctx)` — returns the app-scoped container from `ctx.obj`

## Used by
Expand Down
9 changes: 8 additions & 1 deletion modern_di_typer/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,14 @@ def _build_command_container(ctx: typer.Context) -> typing.Iterator[Container]:

@contextlib.contextmanager
def action_scope(ctx: typer.Context) -> typing.Iterator[Container]:
request_container: Container = ctx.meta[_COMMAND_CONTAINER_KEY]
try:
request_container: Container = ctx.meta[_COMMAND_CONTAINER_KEY]
except KeyError:
msg = (
"No modern-di command container found for this command. "
"Decorate the command with @inject so action_scope has a per-command container to nest under."
)
raise RuntimeError(msg) from None
with request_container.build_child_container() as action_container:
yield action_container

Expand Down
12 changes: 12 additions & 0 deletions tests/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,3 +284,15 @@ def cmd(ctx: typer.Context) -> None:

with pytest.raises(RuntimeError, match=r"setup_di\(app, container\)"):
runner.invoke(app, catch_exceptions=False)


def test_action_scope_without_inject_raises_clear_error(app: typer.Typer) -> None:
runner = CliRunner()

@app.command()
def cmd(ctx: typer.Context) -> None:
with action_scope(ctx):
...

with pytest.raises(RuntimeError, match="@inject"):
runner.invoke(app, catch_exceptions=False)
Loading