add CLI skeleton with typer: stack run/load/generate/bib/lake/db/validate/docs refs #2

Eight subcommand groups wired as stub commands:
  stack run <pipeline> [--target]
  stack load cclf|bcda|seed
  stack generate models
  stack bib sync|tag|query
  stack lake deploy|load
  stack db comment|inspect
  stack validate [pipeline]
  stack docs build|serve [--port]

All commands print [stub] — implementations come in later milestones.
Entry point: [project.scripts] stack = "cli:main" in pyproject.toml.
21 tests covering every subcommand.
This commit is contained in:
kert
2026-03-12 14:44:57 -04:00
parent 7e923abc7c
commit 78faf0dd25
13 changed files with 431 additions and 0 deletions

View File

@@ -7,13 +7,18 @@ requires-python = ">=3.12"
dependencies = [
"databricks-cli>=0.18.0",
"databricks-sdk>=0.85.0",
"fastexcel>=0.19.0",
"fsspec>=2024.1.0",
"httpx>=0.28.1",
"narwhals>=2.17.0",
"pyarrow>=23.0.0",
"sqlglot>=26.0.0",
"typer>=0.24.1",
]
[project.scripts]
stack = "cli:main"
[dependency-groups]
dev = [
"coverage>=7.13.4",

42
src/cli/__init__.py Normal file
View File

@@ -0,0 +1,42 @@
"""CLI entry point for the stack platform.
Usage::
uv run stack --help
uv run stack run readmissions
uv run stack load cclf --path data/cclf/
uv run stack bib sync
"""
from __future__ import annotations
import typer
from cli.bib import app as bib_app
from cli.db import app as db_app
from cli.docs import app as docs_app
from cli.generate import app as generate_app
from cli.lake import app as lake_app
from cli.load import app as load_app
from cli.run import run
from cli.validate import validate
app = typer.Typer(
name="stack",
help="Healthcare data platform CLI.",
no_args_is_help=True,
)
app.command()(run)
app.command()(validate)
app.add_typer(load_app, name="load", help="Ingest data (CCLF, BCDA, seeds).")
app.add_typer(generate_app, name="generate", help="Regenerate code artefacts.")
app.add_typer(bib_app, name="bib", help="Bibliography operations.")
app.add_typer(lake_app, name="lake", help="Lakehouse schema and data.")
app.add_typer(db_app, name="db", help="DuckDB utilities.")
app.add_typer(docs_app, name="docs", help="Documentation generation.")
def main() -> None:
"""Entry point for ``uv run stack``."""
app()

29
src/cli/bib.py Normal file
View File

@@ -0,0 +1,29 @@
"""stack bib — bibliography operations."""
from __future__ import annotations
import typer
app = typer.Typer(no_args_is_help=True)
@app.command()
def sync() -> None:
"""Apply col: tags as DuckDB column comments."""
typer.echo("bib sync [stub]")
@app.command()
def tag(
pattern: str = typer.Argument("", help="Tag pattern to search."),
) -> None:
"""List or search bibliography tags."""
typer.echo(f"bib tag: pattern={pattern!r} [stub]")
@app.command()
def query(
term: str = typer.Argument(help="Search term."),
) -> None:
"""Search the bibliography store."""
typer.echo(f"bib query: term={term!r} [stub]")

21
src/cli/db.py Normal file
View File

@@ -0,0 +1,21 @@
"""stack db — DuckDB utilities."""
from __future__ import annotations
import typer
app = typer.Typer(no_args_is_help=True)
@app.command()
def comment() -> None:
"""Apply column comments from bib metadata to DuckDB."""
typer.echo("db comment [stub]")
@app.command()
def inspect(
table: str = typer.Argument("", help="Table name (schema.table)."),
) -> None:
"""Inspect DuckDB table schema and stats."""
typer.echo(f"db inspect: table={table!r} [stub]")

21
src/cli/docs.py Normal file
View File

@@ -0,0 +1,21 @@
"""stack docs — documentation generation and serving."""
from __future__ import annotations
import typer
app = typer.Typer(no_args_is_help=True)
@app.command()
def build() -> None:
"""Generate documentation site."""
typer.echo("docs build [stub]")
@app.command()
def serve(
port: int = typer.Option(8000, help="Port to serve on."),
) -> None:
"""Serve documentation locally."""
typer.echo(f"docs serve: port={port} [stub]")

13
src/cli/generate.py Normal file
View File

@@ -0,0 +1,13 @@
"""stack generate — regenerate code artefacts from DuckDB."""
from __future__ import annotations
import typer
app = typer.Typer(no_args_is_help=True)
@app.command()
def models() -> None:
"""Regenerate Pydantic table models from DuckDB schemas."""
typer.echo("generate models [stub]")

19
src/cli/lake.py Normal file
View File

@@ -0,0 +1,19 @@
"""stack lake — lakehouse schema and data operations."""
from __future__ import annotations
import typer
app = typer.Typer(no_args_is_help=True)
@app.command()
def deploy() -> None:
"""Create Iceberg schemas and tables from SQLTable models."""
typer.echo("lake deploy [stub]")
@app.command()
def load() -> None:
"""Populate Iceberg tables with data from DuckDB."""
typer.echo("lake load [stub]")

29
src/cli/load.py Normal file
View File

@@ -0,0 +1,29 @@
"""stack load — ingest data sources into DuckDB."""
from __future__ import annotations
from pathlib import Path
import typer
app = typer.Typer(no_args_is_help=True)
@app.command()
def cclf(
path: Path = typer.Option(None, help="Directory containing CCLF files."),
) -> None:
"""Ingest CCLF claim files into DuckDB."""
typer.echo(f"load cclf: path={path} [stub]")
@app.command()
def bcda() -> None:
"""Run a BCDA bulk FHIR export and ingest into DuckDB."""
typer.echo("load bcda [stub]")
@app.command()
def seed() -> None:
"""Load seed/reference data into DuckDB."""
typer.echo("load seed [stub]")

15
src/cli/run.py Normal file
View File

@@ -0,0 +1,15 @@
"""stack run — execute a pipeline by name."""
from __future__ import annotations
import typer
def run(
name: str = typer.Argument(help="Pipeline name (e.g. readmissions)."),
target: str = typer.Option(
"local", help="Target context: local, lake, trino, databricks."
),
) -> None:
"""Execute a named pipeline."""
typer.echo(f"run: pipeline={name} target={target} [stub]")

15
src/cli/validate.py Normal file
View File

@@ -0,0 +1,15 @@
"""stack validate — schema validation across all pipelines."""
from __future__ import annotations
import typer
def validate(
pipeline: str = typer.Argument(
"", help="Pipeline name to validate (all if omitted)."
),
) -> None:
"""Verify pipeline output contracts against SQLTable models."""
target = pipeline or "all"
typer.echo(f"validate: {target} [stub]")

0
tests/cli/__init__.py Normal file
View File

133
tests/cli/test_cli.py Normal file
View File

@@ -0,0 +1,133 @@
"""Tests for CLI skeleton — all subcommands are reachable and print stubs."""
from __future__ import annotations
from typer.testing import CliRunner
from cli import app
runner = CliRunner()
class TestTopLevel:
def test_help(self) -> None:
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
assert "Healthcare data platform CLI" in result.output
def test_no_args_shows_help(self) -> None:
result = runner.invoke(app, [])
assert "Commands" in result.output
class TestRun:
def test_run_pipeline(self) -> None:
result = runner.invoke(app, ["run", "readmissions"])
assert result.exit_code == 0
assert "pipeline=readmissions" in result.output
assert "target=local" in result.output
def test_run_with_target(self) -> None:
result = runner.invoke(app, ["run", "readmissions", "--target", "lake"])
assert result.exit_code == 0
assert "target=lake" in result.output
class TestLoad:
def test_load_help(self) -> None:
result = runner.invoke(app, ["load", "--help"])
assert result.exit_code == 0
for cmd in ("cclf", "bcda", "seed"):
assert cmd in result.output
def test_load_cclf(self) -> None:
result = runner.invoke(app, ["load", "cclf"])
assert result.exit_code == 0
assert "load cclf" in result.output
def test_load_bcda(self) -> None:
result = runner.invoke(app, ["load", "bcda"])
assert result.exit_code == 0
assert "load bcda" in result.output
def test_load_seed(self) -> None:
result = runner.invoke(app, ["load", "seed"])
assert result.exit_code == 0
assert "load seed" in result.output
class TestGenerate:
def test_generate_models(self) -> None:
result = runner.invoke(app, ["generate", "models"])
assert result.exit_code == 0
assert "generate models" in result.output
class TestBib:
def test_bib_sync(self) -> None:
result = runner.invoke(app, ["bib", "sync"])
assert result.exit_code == 0
assert "bib sync" in result.output
def test_bib_tag(self) -> None:
result = runner.invoke(app, ["bib", "tag"])
assert result.exit_code == 0
assert "bib tag" in result.output
def test_bib_query(self) -> None:
result = runner.invoke(app, ["bib", "query", "pfs"])
assert result.exit_code == 0
assert "bib query" in result.output
class TestLake:
def test_lake_deploy(self) -> None:
result = runner.invoke(app, ["lake", "deploy"])
assert result.exit_code == 0
assert "lake deploy" in result.output
def test_lake_load(self) -> None:
result = runner.invoke(app, ["lake", "load"])
assert result.exit_code == 0
assert "lake load" in result.output
class TestDb:
def test_db_comment(self) -> None:
result = runner.invoke(app, ["db", "comment"])
assert result.exit_code == 0
assert "db comment" in result.output
def test_db_inspect(self) -> None:
result = runner.invoke(app, ["db", "inspect"])
assert result.exit_code == 0
assert "db inspect" in result.output
class TestValidate:
def test_validate_all(self) -> None:
result = runner.invoke(app, ["validate"])
assert result.exit_code == 0
assert "validate: all" in result.output
def test_validate_pipeline(self) -> None:
result = runner.invoke(app, ["validate", "readmissions"])
assert result.exit_code == 0
assert "validate: readmissions" in result.output
class TestDocs:
def test_docs_build(self) -> None:
result = runner.invoke(app, ["docs", "build"])
assert result.exit_code == 0
assert "docs build" in result.output
def test_docs_serve(self) -> None:
result = runner.invoke(app, ["docs", "serve"])
assert result.exit_code == 0
assert "docs serve" in result.output
def test_docs_serve_custom_port(self) -> None:
result = runner.invoke(app, ["docs", "serve", "--port", "9000"])
assert result.exit_code == 0
assert "port=9000" in result.output

89
uv.lock generated
View File

@@ -20,6 +20,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/53/89b197cb472a3175d73384761a3413fd58e6b65a794c1102d148b8de87bd/agate-1.9.1-py2.py3-none-any.whl", hash = "sha256:1cf329510b3dde07c4ad1740b7587c9c679abc3dcd92bb1107eabc10c2e03c50", size = 95085, upload-time = "2023-12-21T20:05:21.954Z" },
]
[[package]]
name = "annotated-doc"
version = "0.0.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" },
]
[[package]]
name = "annotated-types"
version = "0.7.0"
@@ -577,6 +586,24 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059, upload-time = "2024-10-25T17:25:39.051Z" },
]
[[package]]
name = "fastexcel"
version = "0.19.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/0d/c8/3b09911348e9c64dbf41096d3e8f0e93c141a23990ec9f32514111bd5f55/fastexcel-0.19.0.tar.gz", hash = "sha256:216c3719ee90963bd93a0bf8c10b177233046ac975b67651152fdaedd3c99aa1", size = 60323, upload-time = "2026-01-20T11:17:37.253Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/e0/3820e93ea606549cfddb8c437141dd69f2b245e74785efc8bd7511ba909d/fastexcel-0.19.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:68601072a0b4b4277c165b68f1055f88ef7ffe7ed6f08c1eeda0f0271e3f7da0", size = 3082362, upload-time = "2026-01-20T11:17:27.157Z" },
{ url = "https://files.pythonhosted.org/packages/66/0f/b42dc09515879192919942157292912393584045fd8bad98bd92961d4c30/fastexcel-0.19.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:c8a87d94445678e7e3f46a6aa39d2afaee5b88a983ec3661143a6488d8955f44", size = 2864365, upload-time = "2026-01-20T11:17:28.786Z" },
{ url = "https://files.pythonhosted.org/packages/8e/4a/bc358b20fcff64b4c14ff7d7a0e1f797792b8b77e30ae755873c02362538/fastexcel-0.19.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e94fc1be6642555f277af792c22a9f80ec9b4d640d9690f00abb822b6d865069", size = 3186426, upload-time = "2026-01-20T11:17:19.087Z" },
{ url = "https://files.pythonhosted.org/packages/58/ae/d2ffdc5ad14190153e2422fc90a1052a4b0c3086d24cb8ae8967575321d8/fastexcel-0.19.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:334f9f40cd68b5924a712b6c104949757a0b8ad8a7e3fa3f3fad1c1ebc00258b", size = 3365628, upload-time = "2026-01-20T11:17:21.116Z" },
{ url = "https://files.pythonhosted.org/packages/6e/67/5f6d4e7760dc3dd8244cd124dabdd5bb7622bf1197edcc2513648847690e/fastexcel-0.19.0-cp310-abi3-win_amd64.whl", hash = "sha256:fbbdf9de79c3ef3572809bb187927c0dc5840968ffe513ea015a383024b7c6b0", size = 2905173, upload-time = "2026-01-20T11:17:33.687Z" },
{ url = "https://files.pythonhosted.org/packages/aa/a4/4290e356cfe028b11db8d96f8d5bca43bde8ed1fd9a491661df4d57551de/fastexcel-0.19.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:26eb85d98087b3c13e083a1fb51a3dfcd57607865fb44d8d6db451948ef65c63", size = 3069723, upload-time = "2026-01-20T11:17:30.521Z" },
{ url = "https://files.pythonhosted.org/packages/fb/1e/1364b08c1d5449236af23366ac3beaabbc63b283f354fc1aa6ad0b95cc37/fastexcel-0.19.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:42d48b077b7ec070de6ea34c99f9a0c97e45cd767fbadd135fc30fa70de24b42", size = 2852643, upload-time = "2026-01-20T11:17:32.079Z" },
{ url = "https://files.pythonhosted.org/packages/85/1b/57a5e2441ab29ecb774f642f66d5e9f9246cdc14ca4ee85ada5b081f4656/fastexcel-0.19.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3c49fac330cc306bb0bd73d96138f438441d8254eed19ca6c1800aaa9d69054", size = 3182805, upload-time = "2026-01-20T11:17:23.068Z" },
{ url = "https://files.pythonhosted.org/packages/76/50/0e5c416b990d153bad1e63b8268ea751fc8a319d134de14e3bbba38000c7/fastexcel-0.19.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75aad96c34836eca90fc6d0e061240c145795f8754424698e2aadfd634abb4cf", size = 3360260, upload-time = "2026-01-20T11:17:24.711Z" },
{ url = "https://files.pythonhosted.org/packages/48/38/b3faa12a74f387e037ff33adae761c22fc3aa44eed15a2c09d653b4eb194/fastexcel-0.19.0-cp314-cp314t-win_amd64.whl", hash = "sha256:7ef8e41cb0118f90d5f9a636fcdc0e9d635938cdaa54a3182328f3d34ce9ee1a", size = 2897686, upload-time = "2026-01-20T11:17:35.453Z" },
]
[[package]]
name = "fsspec"
version = "2026.2.0"
@@ -724,6 +751,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/1a/d4/c4dcb02ed11f8884e169b3350fc40aa4c08edf8bed77a8f0f267542e6452/leather-0.4.1-py3-none-any.whl", hash = "sha256:ec61cba1ca3ccb96ed90e38b116fc58757d97d352171006b3288c47ce3fbd183", size = 30340, upload-time = "2025-12-15T19:01:40.823Z" },
]
[[package]]
name = "markdown-it-py"
version = "4.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mdurl" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" },
]
[[package]]
name = "markupsafe"
version = "3.0.3"
@@ -804,6 +843,15 @@ msgpack = [
{ name = "msgpack" },
]
[[package]]
name = "mdurl"
version = "0.1.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
]
[[package]]
name = "more-itertools"
version = "10.8.0"
@@ -1433,6 +1481,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
]
[[package]]
name = "rich"
version = "14.3.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markdown-it-py" },
{ name = "pygments" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b3/c6/f3b320c27991c46f43ee9d856302c70dc2d0fb2dba4842ff739d5f46b393/rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b", size = 230582, upload-time = "2026-02-19T17:23:12.474Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458, upload-time = "2026-02-19T17:23:13.732Z" },
]
[[package]]
name = "rpds-py"
version = "0.30.0"
@@ -1551,6 +1612,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/3e/0a/9e1be9035b37448ce2e68c978f0591da94389ade5a5abafa4cf99985d1b2/ruff-0.15.4-py3-none-win_arm64.whl", hash = "sha256:60d5177e8cfc70e51b9c5fad936c634872a74209f934c1e79107d11787ad5453", size = 10966776, upload-time = "2026-02-26T20:03:56.908Z" },
]
[[package]]
name = "shellingham"
version = "1.5.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" },
]
[[package]]
name = "six"
version = "1.17.0"
@@ -1598,11 +1668,13 @@ source = { virtual = "." }
dependencies = [
{ name = "databricks-cli" },
{ name = "databricks-sdk" },
{ name = "fastexcel" },
{ name = "fsspec" },
{ name = "httpx" },
{ name = "narwhals" },
{ name = "pyarrow" },
{ name = "sqlglot" },
{ name = "typer" },
]
[package.dev-dependencies]
@@ -1622,11 +1694,13 @@ dev = [
requires-dist = [
{ name = "databricks-cli", specifier = ">=0.18.0" },
{ name = "databricks-sdk", specifier = ">=0.85.0" },
{ name = "fastexcel", specifier = ">=0.19.0" },
{ name = "fsspec", specifier = ">=2024.1.0" },
{ name = "httpx", specifier = ">=0.28.1" },
{ name = "narwhals", specifier = ">=2.17.0" },
{ name = "pyarrow", specifier = ">=23.0.0" },
{ name = "sqlglot", specifier = ">=26.0.0" },
{ name = "typer", specifier = ">=0.24.1" },
]
[package.metadata.requires-dev]
@@ -1660,6 +1734,21 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a6/a5/c0b6468d3824fe3fde30dbb5e1f687b291608f9473681bbf7dabbf5a87d7/text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", size = 78154, upload-time = "2019-08-30T21:37:03.543Z" },
]
[[package]]
name = "typer"
version = "0.24.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-doc" },
{ name = "click" },
{ name = "rich" },
{ name = "shellingham" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f5/24/cb09efec5cc954f7f9b930bf8279447d24618bb6758d4f6adf2574c41780/typer-0.24.1.tar.gz", hash = "sha256:e39b4732d65fbdcde189ae76cf7cd48aeae72919dea1fdfc16593be016256b45", size = 118613, upload-time = "2026-02-21T16:54:40.609Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-py3-none-any.whl", hash = "sha256:112c1f0ce578bfb4cab9ffdabc68f031416ebcc216536611ba21f04e9aa84c9e", size = 56085, upload-time = "2026-02-21T16:54:41.616Z" },
]
[[package]]
name = "typing-extensions"
version = "4.15.0"