Metadata-Version: 2.1
Name: wexample-cli
Version: 2.1.1
Summary: Supplies command decorators, typed options, and a composable middleware pipeline for kernels built on wexample-app
Author-Email: weeger <contact@wexample.com>
License: MIT
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Project-URL: homepage, https://github.com/wexample/python-cli
Requires-Python: >=3.10
Requires-Dist: attrs>=23.1.0
Requires-Dist: wexample-app>=19.1.0
Requires-Dist: wexample-helpers>=19.1.0
Requires-Dist: wexample-prompt>=15.0.0
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Description-Content-Type: text/markdown

# cli

Version: 2.1.1

`wexample-cli` supplies the decorator layer — `@command`, `@option`, and `@middleware` — that turns plain Python methods into typed, discoverable commands for kernels built on `wexample-app`. Options are declared with Python types, short names, defaults, required flags, and per-value validators; a composable middleware pipeline can fan a single invocation across multiple execution contexts, run them in parallel, and abort on the first failure. It is aimed at Python developers building or extending a `wexample-app` kernel with new CLI commands.

## Table of Contents

- [Installation](#installation)
- [Quickstart](#quickstart)
- [Tests](#tests)
- [Architecture](#architecture)
- [Integration in the Suite](#integration-in-the-suite)
- [Dependencies](#dependencies)
- [Versioning & Compatibility Policy](#versioning--compatibility-policy)
- [License](#license)
- [About us](#about-us)
- [Known Limitations & Roadmap](#known-limitations--roadmap)
- [Status & Compatibility](#status--compatibility)
- [Useful Links](#useful-links)
- [Migration Notes](#migration-notes)

## Installation

```bash
pip install wexample-cli
```

Requires Python >=3.10.

## Quickstart

Install the package:

```bash
pip install wexample-cli
```

Declare a command inside your kernel class. `@command` wraps the method into a `CommandMethodWrapper`; each `@option` stacks on top in outer-to-inner order to append a typed option to it.

```python
from wexample_cli.decorator.command import command
from wexample_cli.decorator.option import option

@option(name="name", type=str, description="Who to greet", required=True)
@command(type="demo", description="Print a greeting")
def demo__greet(context, name: str) -> None:
    context.io.log(f"Hello, {name}!")
```

The decorated object is a `CommandMethodWrapper`. You can inspect it immediately:

```python
demo__greet.description      # "Print a greeting"
demo__greet.options[0].name  # "name"
```

The kernel maps the function name `demo__greet` to the command path `demo/greet`. To exercise the full decorator and middleware pipeline in a test, use the helpers in src/wexample_cli/testing/kernel.py:

```python
from wexample_cli.testing.kernel import boot_kernel, dispatch_command

kernel = boot_kernel(MyKernel, entrypoint_path="/path/to/__main__.py")
response = dispatch_command(kernel, "demo/greet", {"name": "world"})
```

`dispatch_command` accepts pre-parsed kwargs (`{"name": "world"}`) or CLI-style strings (`["--name", "world"]`).

## Tests

This project uses `pytest` for testing and `pytest-cov` for code coverage analysis.

### Installation

First, install the required testing dependencies:
```bash
.venv/bin/python -m pip install pytest pytest-cov
```

### Basic Usage

Run all tests with coverage:
```bash
.venv/bin/python -m pytest --cov --cov-report=html
```

### Common Commands
```bash
# Run tests with coverage for a specific module
.venv/bin/python -m pytest --cov=your_module

# Show which lines are not covered
.venv/bin/python -m pytest --cov=your_module --cov-report=term-missing

# Generate an HTML coverage report
.venv/bin/python -m pytest --cov=your_module --cov-report=html

# Combine terminal and HTML reports
.venv/bin/python -m pytest --cov=your_module --cov-report=term-missing --cov-report=html

# Run specific test file with coverage
.venv/bin/python -m pytest tests/test_file.py --cov=your_module --cov-report=term-missing
```

### Viewing HTML Reports

After generating an HTML report, open `htmlcov/index.html` in your browser to view detailed line-by-line coverage information.

### Coverage Threshold

To enforce a minimum coverage percentage:
```bash
.venv/bin/python -m pytest --cov=your_module --cov-fail-under=80
```

This will cause the test suite to fail if coverage drops below 80%.

## Architecture

`wexample-cli` is a decorator and dispatch layer that sits between a `wexample-app` kernel and the Python functions that implement its commands. It has no entry-point of its own; downstream kernels import its decorators, wrap their methods, and pass the resulting objects to the kernel's registration machinery.

### Parts

**Decorators** — `src/wexample_cli/decorator/`

The decorators are the only thing a command author touches directly. Applied bottom-to-top on a plain function, they produce a `CommandMethodWrapper`:

- `@command(type, description, tags)` — creates the wrapper; must be the innermost decorator.
- `@option(name, type, ...)` — calls `wrapper.set_option(Option(...))` to append a typed option.
- `@middleware(name, **kwargs)` — calls `wrapper.register_middleware(name, kwargs)` to schedule a middleware class at runtime.
- `@alias(*names)` — extends `wrapper.aliases`.
- `@screenable(interval, height)` — adds `--screen` and `--screen-interval` options and pushes a pipeline wrapper that drives a refresh loop.
- `@as_sudo()` — sets `wrapper.sudo = True`.
- `@webhook()` — sets `wrapper.webhook = True`.
- `@option_stop_on_failure()` — adds a `--stop-on-failure` flag option.

**`CommandMethodWrapper`** — `src/wexample_cli/common/command_method_wrapper.py`

The data structure produced by decoration. It carries everything the runtime needs: `function`, `options`, `middlewares_attributes` (names → init-kwargs, resolved at call time), `pipeline_wrappers` (registered in outer-to-inner order), `aliases`, `tags`, and the boolean flags `sudo` and `webhook`. It owns no execution logic.

**`ExtendedCommand`** — `src/wexample_cli/command/extended_command.py`

The runtime command object registered with the kernel; extends `wexample_app.common.command.Command`. It owns the full execution path for one `CommandRequest`.

**`AbstractMiddleware`** — `src/wexample_cli/middleware/abstract_middleware.py`

Base class for middleware. A middleware can add options dynamically (`append_options`), produce multiple `ExecutionContext` objects from a single request (`build_execution_contexts`), and declare whether execution should be parallel, show a progress bar, or abort on the first failure. The values `"allways"` and `"optional"` (defined in `src/wexample_cli/const/middleware.py`) allow a middleware to make a behaviour mandatory or user-selectable.

**`ExecutionContext`** — `src/wexample_cli/context/execution_context.py`

A single unit of work inside one dispatch. Holds `function_kwargs`, `request`, `kernel`, `middleware`, and an optional `function` override. Its `__attrs_post_init__` injects `context=self` into `function_kwargs` so every command function receives it. Also provides progress-tracking helpers (`create_progress_range`, `finish_progress`, `get_or_create_progress`).

**Exceptions** — `src/wexample_cli/exception/`

`CommandOptionMissingException` and `CommandOptionValidationException` both extend `AbstractCommandOptionException`. The first is raised when a required option is absent; the second when an `AbstractValidator` rejects a value.

**Constants** — `src/wexample_cli/const/`

- `types.py` — `ParsedArgs = dict[str, Any]`.
- `middleware.py` — the `"allways"` / `"optional"` sentinel strings.
- `tags.py` — `EffectTag`, `AudienceTag`, and `ScopeTag` class-level string constants for `@command(tags=[...])`.

**Testing helpers** — `src/wexample_cli/testing/kernel.py`

`boot_kernel(KernelClass, entrypoint_path)` and `dispatch_command(kernel, name, arguments)` exercise the full decorator and dispatch pipeline from a test, identical to a real invocation.

**Helper** — `src/wexample_cli/helper/extra_args.py`

`resolve_shell_command(context, command, extra_args)` — resolves a shell command string from either a `--command "..."` option or positional `-- args`, preferring the positional form.

### Call path

**At import time** (decorators run):

```
@alias / @as_sudo / @webhook
@screenable          →  set_option(screen, screen_interval) + register_pipeline_wrapper(screen_wrapper)
@middleware(name)    →  register_middleware(name, kwargs)
@option(...)         →  set_option(Option(...))
@command(type, ...)  →  CommandMethodWrapper(function=fn, ...)
```

The resulting `CommandMethodWrapper` is what the kernel stores; `ExtendedCommand` wraps it.

**At invocation** (`ExtendedCommand.execute_request`):

1. Instantiate each middleware from the kernel's `"middlewares"` registry using `middlewares_attributes`; attach it with `set_middleware` (which also extends `wrapper.options` with the middleware's own options).
2. If `--help` or `-h` is in the raw arguments, render help via `_render_help` and return.
3. `_build_function_kwargs`:
   - Each middleware calls `append_options` to inject any dynamic options (e.g. `--parallel`, `--progress`).
   - Raw arguments (list or dict) are parsed by `_parse_arguments` into `ParsedArgs`.
   - Each declared option is resolved from parsed args, then its default, then `CommandOptionMissingException` if required.
   - Each non-`None` value is run through its validators; failure raises `CommandOptionValidationException`.
   - If `--` passthrough tokens are present, they land in `parsed_args["__extra_args__"]` and are forwarded as `extra_args` if the function declares that parameter, otherwise `CommandUnexpectedArgumentException`.
4. Build the pipeline: `_make_dispatch` produces the innermost callable; each entry in `pipeline_wrappers` is composed around it with `_wrap_dispatch`.
5. Call `dispatch(function_kwargs)`.

**Inside `_execute_dispatch`:**

- With middlewares: each middleware calls `build_execution_contexts` (base returns one context; subclasses may fan out). Contexts are executed sequentially or in parallel (`asyncio` + `ThreadPoolExecutor`). Parallel runs each context with a `PromptBufferOutputHandler` so output is buffered per-context. Results accumulate in a `MultipleResponse`; a single result is unwrapped. `stop_on_failure` halts the loop early on `FailureResponse`.
- Without middlewares: a single `ExecutionContext` is built by `request.resolver.build_execution_context`, and the function is called directly.

In both paths, `ExecutionContext.__attrs_post_init__` injects `context=self` so the function can access `context.io`, `context.kernel`, progress handles, and the resolved `function_kwargs`.

## Integration in the Suite

This package is part of the Wexample Suite — a collection of high-quality, modular tools designed to work seamlessly together across multiple languages and environments.

### Related Packages

The suite includes packages for configuration management, file handling, prompts, and more. Each package can be used independently or as part of the integrated suite.

Visit the [Wexample Suite documentation](https://docs.wexample.com) for the complete package ecosystem.

## Dependencies

- attrs: >=23.1.0
- wexample-app: >=19.1.0
- wexample-helpers: >=19.1.0
- wexample-prompt: >=15.0.0

## Versioning & Compatibility Policy

Wexample packages follow **Semantic Versioning** (SemVer):

- **MAJOR**: Breaking changes
- **MINOR**: New features, backward compatible
- **PATCH**: Bug fixes, backward compatible

We maintain backward compatibility within major versions and provide clear migration guides for breaking changes.

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

Free to use in both personal and commercial projects.

## About us

[Wexample](https://wexample.com) stands as a cornerstone of the digital ecosystem — a collective of seasoned engineers, researchers, and creators driven by a relentless pursuit of technological excellence. More than a media platform, it has grown into a vibrant community where innovation meets craftsmanship, and where every line of code reflects a commitment to clarity, durability, and shared intelligence.

This packages suite embodies this spirit. Trusted by professionals and enthusiasts alike, it delivers a consistent, high-quality foundation for modern development — open, elegant, and battle-tested. Its reputation is built on years of collaboration, refinement, and rigorous attention to detail, making it a natural choice for those who demand both robustness and beauty in their tools.

Wexample cultivates a culture of mastery. Each package, each contribution carries the mark of a community that values precision, ethics, and innovation — a community proud to shape the future of digital craftsmanship.

## Known Limitations & Roadmap

Current limitations and planned features are tracked in the GitHub issues.

See the [project roadmap](https://github.com/wexample/python-cli/issues) for upcoming features and improvements.

## Status & Compatibility

**Maturity**: Production-ready

**Python Support**: >=3.10

**OS Support**: Linux, macOS, Windows

**Status**: Actively maintained

## Useful Links

- **Homepage**: https://github.com/wexample/python-cli
- **Documentation**: [docs.wexample.com](https://docs.wexample.com)
- **Issue Tracker**: https://github.com/wexample/python-cli/issues
- **Discussions**: https://github.com/wexample/python-cli/discussions
- **PyPI**: [pypi.org/project/wexample-cli](https://pypi.org/project/wexample-cli/)

## Migration Notes

When upgrading between major versions, refer to the migration guides in the documentation.

Breaking changes are clearly documented with upgrade paths and examples.
