Skip to content
50 changes: 36 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ lifecycle:
```python
from __future__ import annotations

from typing import Any

import base_cli


Expand All @@ -54,7 +56,7 @@ app = base_cli.App(name="hello", version="0.1.0")

@app.command()
@base_cli.option("--name", default="world", show_default=True)
def hello(ctx: base_cli.Context, name: str) -> int:
def hello(ctx: base_cli.Context[Any, Any, Any], name: str) -> int:
ctx.log.info("greeting %s", name)
print(f"Hello, {name}!")
return base_cli.ExitCode.SUCCESS
Expand Down Expand Up @@ -179,8 +181,10 @@ from `base_cli`. `RuntimeBinding.layout` uses the public immutable
service payloads owned by a consumer:

```python
Config = dict[str, object]
context: base_cli.Context[Config, ApplicationState, Services]
from typing import Any

Config = dict[str, Any]
context: base_cli.Context[Config, ApplicationContext, Services]
```

`App.command()`, `App.subcommand()`, `@base_cli.command()`, `@base_cli.option()`,
Expand Down Expand Up @@ -266,6 +270,8 @@ guide](https://basefoundry.github.io/base-cli/adopter-readiness/) and run the th
```python
from __future__ import annotations

from typing import Any

import base_cli


Expand All @@ -278,7 +284,7 @@ app = base_cli.App(

@app.command()
@base_cli.option("--name", required=True)
def main(ctx: base_cli.Context, name: str) -> None:
def main(ctx: base_cli.Context[Any, Any, Any], name: str) -> None:
ctx.log.info("starting hello")
print(f"hello {name}")

Expand Down Expand Up @@ -321,8 +327,10 @@ name to `@app.command(...)`; change `App(name=...)` instead.
Register the command function explicitly:

```python
from typing import Any

@app.command()
def main(ctx: base_cli.Context) -> None:
def main(ctx: base_cli.Context[Any, Any, Any]) -> None:
...
```

Expand All @@ -333,8 +341,10 @@ removed from Click's keyword arguments.
For small scripts, the module-level decorators are available:

```python
from typing import Any

@base_cli.command()
def main(ctx: base_cli.Context) -> None:
def main(ctx: base_cli.Context[Any, Any, Any]) -> None:
...


Expand All @@ -354,6 +364,8 @@ Use `@app.subcommand()` when one CLI needs multiple verbs while keeping the
standard context, logging, redaction, and cleanup lifecycle for each invocation:

```python
from typing import Any

app = base_cli.App(
name="workspace-tools",
version="0.1.0",
Expand All @@ -363,13 +375,13 @@ app = base_cli.App(

@app.subcommand()
@base_cli.argument("project")
def status(ctx: base_cli.Context, project: str) -> None:
def status(ctx: base_cli.Context[Any, Any, Any], project: str) -> None:
ctx.log.info("checking %s", project)


@app.subcommand("sync")
@base_cli.option("--dry-run", is_flag=True)
def sync_project(ctx: base_cli.Context, dry_run: bool) -> None:
def sync_project(ctx: base_cli.Context[Any, Any, Any], dry_run: bool) -> None:
if ctx.dry_run:
ctx.log.info("previewing sync")
```
Expand Down Expand Up @@ -465,11 +477,13 @@ root parameters and before any existing group, command, or result callback
runs:

```python
def make_application_context(ctx: base_cli.Context) -> ApplicationContext:
def make_application_context(
ctx: base_cli.Context[Config, ApplicationContext, Services],
) -> ApplicationContext:
return ApplicationContext(environment=ctx.environment)


def make_services(ctx: base_cli.Context) -> Services:
def make_services(ctx: base_cli.Context[Config, ApplicationContext, Services]) -> Services:
services = Services(ctx.config)
ctx.on_cleanup(services.close)
return services
Expand Down Expand Up @@ -506,19 +520,23 @@ boundaries.
`base_cli.option` and `base_cli.argument` mirror Click's decorators:

```python
from typing import Any

@app.command()
@base_cli.argument("project")
@base_cli.option("--workspace", type=str)
def main(ctx: base_cli.Context, project: str, workspace: str | None) -> None:
def main(ctx: base_cli.Context[Any, Any, Any], project: str, workspace: str | None) -> None:
...
```

Use `sensitive=True` for options or arguments whose values must not reach
invocation logs or history writers:

```python
from typing import Any

@base_cli.option("--token", sensitive=True, required=True)
def main(ctx: base_cli.Context, token: str) -> None:
def main(ctx: base_cli.Context[Any, Any, Any], token: str) -> None:
...
```

Expand All @@ -528,9 +546,11 @@ values are redacted. Sensitive positional arguments are redacted according to
the Click command schema:

```python
from typing import Any

@app.command()
@base_cli.argument("credential", sensitive=True)
def login(ctx: base_cli.Context, credential: str) -> None:
def login(ctx: base_cli.Context[Any, Any, Any], credential: str) -> None:
...
```

Expand All @@ -544,8 +564,10 @@ For native `App` commands, use `dry_run=True` when a nonstandard option should
drive `ctx.dry_run` and the lifecycle's default durable-write suppression:

```python
from typing import Any

@base_cli.option("--preview", is_flag=True, dry_run=True)
def main(ctx: base_cli.Context, preview: bool) -> None:
def main(ctx: base_cli.Context[Any, Any, Any], preview: bool) -> None:
if ctx.dry_run:
ctx.log.info("previewing changes")
```
Expand Down
4 changes: 3 additions & 1 deletion docs/integrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ Pass `rich=True` when constructing an app and pass the active context's flag to
the shared record renderer:

```python
from typing import Any

import base_cli

app = base_cli.App(name="catalog", rich=True)

@app.command()
def list_items(ctx: base_cli.Context) -> None:
def list_items(ctx: base_cli.Context[Any, Any, Any]) -> None:
base_cli.render_records(
({"name": "base", "path": "/work/base"},),
requested_format="text",
Expand Down
Loading