Metadata-Version: 2.5
Name: argledger
Version: 0.1.1
Summary: Reusable class-based command registry that generates argparse parsers, resolves defaults reproducibly, and records execution history.
Project-URL: Homepage, https://github.com/GrayJou/argledger
Project-URL: Repository, https://github.com/GrayJou/argledger
Project-URL: Issues, https://github.com/GrayJou/argledger/issues
Project-URL: Changelog, https://github.com/GrayJou/argledger/blob/main/PYPI_README.md
Project-URL: Documentation, https://github.com/GrayJou/argledger#readme
Author: GrayJou
Maintainer: GrayJou
License: MIT
License-File: LICENSE
Keywords: argparse,cli,history,provenance,registry,reproducibility
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Logging
Classifier: Typing :: Typed
Requires-Python: >=3.11
Provides-Extra: dev
Requires-Dist: build; extra == 'dev'
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Requires-Dist: twine; extra == 'dev'
Description-Content-Type: text/markdown

# argledger

> Reusable, class-based command registry that turns validated JSON specs into real `argparse` parsers — with reproducible default resolution and automatic execution history.

`argledger` gives you **one source of truth** for every command:

- parser construction (`argparse.ArgumentParser`)
- default resolution `CLI > env > config file > spec default` with source tracking
- deterministic default `argv` / shell rendering
- automatic `ExecutionContext` recording (timestamps, duration, resolved args, provenance, git, outputs, metrics, success/failure)
- JSONL + SQLite history, query, and replay
- guide generation & shell completion hooks

No domain coupling. Your specs live with *your* project via `Registry(user_root=...)`.

## Installation

```bash
pip install argledger
```

Requires Python ≥3.11. No mandatory dependencies.

```bash
pip install argledger  # from PyPI
# or for development
pip install -e ".[dev]"
```

## Quick start

**1. Define a spec** (`my_project/specs/demo/greet.json`):

```json
{
  "name": "demo.greet",
  "description": "Greet someone a number of times.",
  "version": "1.0.0",
  "groups": ["common"],
  "arguments": {
    "name": { "type": "str", "default": "world", "help": "Name to greet." },
    "count": { "type": "int", "default": 1, "minimum": 1, "maximum": 10 },
    "excited": { "action": "store_true", "default": false }
  }
}
```

**2. Use it:**

```python
from pathlib import Path
from argledger import Registry, History, ExecutionContext

registry = Registry(user_root=Path("my_project"))  # loads specs/my_project via importlib.resources + user_root
command = registry.load("demo.greet")

# resolves CLI > env > config > default, tracks source for each arg
resolved = command.parse_args(["--name", "Alice", "--count", "2"])
print(resolved.values)   # {'name': 'Alice', 'count': 2, 'excited': False, ...}
print(resolved.sources)  # {'name': 'cli', 'count': 'cli', ...}
print(resolved.namespace)  # argparse.Namespace

history = History(jsonl=Path(".run-registry/history.jsonl"), sqlite=Path(".run-registry/history.db"))

with ExecutionContext(command, resolved, history=history) as ctx:
    for _ in range(resolved.values["count"]):
        print(f"Hello, {resolved.values['name']}!")

    ctx.add_output(Path("out.txt"))
    ctx.set_metric("greetings", resolved.values["count"])
    ctx.set_data_version("my-data-v1")
```

Normal code stays simple:

```python
args = command.parser().parse_args()
```

also works (legacy, source=`unknown`).

## CLI

```bash
# list/show/defaults (pure package – no builtins – so pass where your specs live)
python -m argledger list --user-root my_project
python -m argledger show demo.greet --user-root my_project
python -m argledger defaults demo.greet --user-root my_project

# history query / replay
python -m argledger query --command demo.greet --success --limit 5
python -m argledger replay <execution-id>               # exact argv
python -m argledger replay <execution-id> --expanded     # fully expanded, reproducible argv

# guide generation (marker-based, never overwrites hand-written docs)
python -m argledger guide --output docs/COMMAND_GUIDE.md --user-root my_project

# shell completion
python -m argledger completion --shell bash
```

## Features

- **Validated models** `Argument` / `Command` – reject malformed names, unknown types/actions, `minimum>maximum`, invalid `pattern`, `default` not in `choices`, etc. (see `Argument.from_spec`)
- **Field-level group merge** – groups listed in order, then spec overrides field-by-field (`{**group, **spec}`), e.g. a spec can override only `"default"` while keeping group's `type`/`help`/`env`.
- **Precedence** `CLI > env > config (TOML) > spec default`. TOML layout:
  ```toml
  [groups.common]
  workers = 1
  [commands."demo.greet"]
  count = 5
  ```
  Configure via `Registry(config_path=Path("argledger.toml"))` or `RUN_REGISTRY_CONFIG` env.
- **Default argv** `Command.default_argv()` / `default_argument_string()` – deterministic, `shlex.join`'d, respects `include_in_default_argv`/`include_empty`/`include_false_flags`. All docs delegate to this.
- **History** – JSONL is canonical, SQLite is mirror. Queries by `command/since/until/success/tags/limit`; fallback to JSONL scan if SQLite disabled. Sensitive args stored as `"<redacted>"`, expanded replay raises `ReplayUnavailableError`.
- **Provenance** – UTC timestamps, monotonic duration, hostname/pid/cwd/python/platform/cpu_count, `git commit/branch/dirty` (never crashes outside a repo), optional peak RSS.
- **Compatibility** `argledger.compat.parser_from_spec(__file__, "demo/greet.json", user_root=...)` wraps `Registry().load(...).parser()` with deprecation warning.

See `examples/` for runnable demos (`demo.greet`, `demo.counter`, `demo.file_processor`) and `tests/fixtures/` for the minimal spec used in 74 core tests.

## Why

Research and product CLIs usually duplicate: arg definitions vs. parser vs. docs vs. history append vs. replay. `argledger` collapses them to one validated spec, so your script stays

```python
command = registry.load(NAME)
resolved = command.parse_args()
with ExecutionContext(command, resolved, history=history) as run:
    result = do_the_actual_work(resolved.namespace)
    run.add_output(result.path)
```

— everything else (parser, defaults, env/config, docs, timing, history, replay, provenance) belongs to the framework.

## Links

- Repository: https://github.com/GrayJou/argledger
- Issues: https://github.com/GrayJou/argledger/issues
- License: MIT – see `LICENSE`

## Publishing / Development

```bash
pip install -e ".[dev]"
pytest -q
python -m build
twine check dist/*
```
