dioxide.click¶
Click integration for dioxide dependency injection.
This module provides seamless integration between dioxide’s dependency injection container and Click CLI applications. It enables:
Single function setup:
configure_dioxide(profile=...)Command scoping:
@with_scope(container)decorator for per-command scopesClean injection: Scope passed as first argument to commands
Lifecycle management: Components disposed after command completes
- Quick Start:
Set up dioxide in your Click CLI:
import click from dioxide import Profile from dioxide.click import configure_dioxide, with_scope container = configure_dioxide(profile=Profile.PRODUCTION) @click.command() @with_scope(container) def greet(scope, name): service = scope.resolve(GreetingService) click.echo(service.greet(name)) @click.argument('name') def main(): greet()
- Command Scoping:
The
with_scopedecorator creates aScopedContainerfor each command invocation. This enables REQUEST-scoped components to be fresh for each command while SINGLETON components remain shared:from dioxide import service, Scope @service(scope=Scope.REQUEST) class CommandContext: def __init__(self): import uuid self.command_id = str(uuid.uuid4()) @click.command() @with_scope(container) def my_command(scope): ctx = scope.resolve(CommandContext) # ctx.command_id is unique per command invocation click.echo(f'Command ID: {ctx.command_id}')
- Lifecycle Management:
The integration handles lifecycle disposal automatically:
from dioxide import adapter, lifecycle, Profile @adapter.for_(DatabasePort, profile=Profile.PRODUCTION) @lifecycle class PostgresAdapter: async def initialize(self) -> None: self.engine = create_engine(...) print('Database connected') async def dispose(self) -> None: await self.engine.dispose() print('Database disconnected') # When command completes: scope disposes REQUEST-scoped @lifecycle components
- Click Groups:
The integration works with Click groups and nested commands:
@click.group() def cli(): pass @cli.command() @with_scope(container) @click.argument('user_id') def get_user(scope, user_id): service = scope.resolve(UserService) click.echo(service.get_user(user_id)) @cli.group() def config(): pass @config.command() @with_scope(container) @click.argument('key') def get(scope, key): service = scope.resolve(ConfigService) click.echo(service.get_value(key))
- Typer Compatibility:
Since Typer is built on Click, this integration works with Typer applications:
import typer import click from dioxide.click import configure_dioxide, with_scope container = configure_dioxide(profile=Profile.PRODUCTION) app = typer.Typer() @click.command() @with_scope(container) @click.argument('name') def greet(scope, name): service = scope.resolve(GreetingService) typer.echo(service.greet(name)) app.command()(greet)
See also
configure_dioxide()- The main setup functionwith_scope()- Decorator for per-command scopingdioxide.container.Container- The DI containerdioxide.container.ScopedContainer- Command-scoped container
Functions¶
|
Configure dioxide dependency injection for a Click CLI application. |
|
Decorator that creates a dioxide scope for each command invocation. |
Module Contents¶
- dioxide.click.configure_dioxide(profile=None, container=None, packages=None)[source]¶
Configure dioxide dependency injection for a Click CLI application.
This function sets up the integration between dioxide and Click:
Creates or uses provided container
Scans for components in specified packages (or all registered)
Returns the configured container for use with
with_scope
- Parameters:
profile (dioxide.profile_enum.Profile | str | None) – Profile to scan with (e.g.,
Profile.PRODUCTION). Accepts either a Profile enum value or a string profile name.container (dioxide.container.Container | None) – Optional Container instance. If not provided, creates a new Container instance.
packages (list[str] | None) – Optional list of packages to scan for components. If not provided, scans all registered components.
- Returns:
The configured Container instance ready for use with
with_scope.- Raises:
ImportError – If Click is not installed.
- Return type:
Example
Basic setup:
from dioxide import Profile from dioxide.click import configure_dioxide container = configure_dioxide(profile=Profile.PRODUCTION)
With custom container:
from dioxide import Container, Profile from dioxide.click import configure_dioxide my_container = Container() container = configure_dioxide(profile=Profile.TEST, container=my_container)
With package scanning:
container = configure_dioxide( profile=Profile.PRODUCTION, packages=['myapp.services', 'myapp.adapters'], )
See also
with_scope()- How to inject dependencies in commandsdioxide.container.ScopedContainer- How scoping works
- dioxide.click.with_scope(container)[source]¶
Decorator that creates a dioxide scope for each command invocation.
This decorator wraps a Click command to: 1. Create a new ScopedContainer before the command runs 2. Pass the scope as the first argument to the command 3. Dispose the scope after the command completes (even on error)
The scope enables REQUEST-scoped components to be cached within a single command invocation while remaining fresh across different invocations.
- Parameters:
container (dioxide.container.Container) – The Container instance (from
configure_dioxide).- Returns:
A decorator that wraps Click commands with scope management.
- Return type:
collections.abc.Callable[[F], F]
Example
Basic usage:
from dioxide.click import configure_dioxide, with_scope container = configure_dioxide(profile=Profile.PRODUCTION) @click.command() @with_scope(container) def my_command(scope): service = scope.resolve(MyService) click.echo(service.do_something())
With Click arguments and options:
@click.command() @with_scope(container) @click.option('--verbose', is_flag=True) @click.argument('name') def greet(scope, verbose, name): service = scope.resolve(GreetingService) result = service.greet(name) if verbose: click.echo(f'Greeting: {result}') else: click.echo(result)
With Click groups:
@click.group() def cli(): pass @cli.command() @with_scope(container) @click.argument('user_id') def get_user(scope, user_id): service = scope.resolve(UserService) click.echo(service.get_user(user_id))
Note
The scope is always passed as the FIRST argument to the decorated function, before any Click arguments or options. This is because decorators are applied bottom-up, and
with_scopeneeds to inject the scope before Click processes its arguments.See also
configure_dioxide()- Must be called first to get containerdioxide.container.ScopedContainer- How scoping works