Metadata-Version: 2.1
Name: wexample-app
Version: 19.2.1
Summary: Provides an AbstractKernel base class, command resolvers, typed response objects, and a service-container mixin for building structured Python CLI applications
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-app
Requires-Python: >=3.10
Requires-Dist: attrs>=23.1.0
Requires-Dist: cattrs>=23.1.0
Requires-Dist: dotenv
Requires-Dist: python-dotenv
Requires-Dist: requests>=2.31.0
Requires-Dist: wexample-filestate>=17.2.0
Requires-Dist: wexample-helpers-yaml>=7.0.0
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Description-Content-Type: text/markdown

# app

Version: 19.2.1

`wexample-app` provides `AbstractKernel`, `CommandRunnerKernel`, and `CommandLineKernel` — base classes and mixins for building structured Python CLI applications. A kernel wires together a service container, a resolver-and-runner pipeline that dispatches name patterns such as `group/command` to Python module functions or YAML definitions, and a suite of typed response objects covering every output category. It targets Python developers who want a consistent, extensible command-dispatch architecture without implementing the plumbing themselves.

## 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-app
```

Requires Python >=3.10.

## Quickstart

Install the package:

```bash
pip install wexample-app
```

Define a kernel class by composing the three base classes with `@base_class`, and wire in `DefaultCommandResolver` so the kernel can locate command files:

```python
# my_app.py
from wexample_helpers.decorator.base_class import base_class
from wexample_app.common.abstract_kernel import AbstractKernel
from wexample_app.common.mixin.command_line_kernel import CommandLineKernel
from wexample_app.common.mixin.command_runner_kernel import CommandRunnerKernel
from wexample_app.resolver.default_command_resolver import DefaultCommandResolver

@base_class
class MyApp(CommandRunnerKernel, CommandLineKernel, AbstractKernel):
    def _get_command_resolvers(self):
        return [DefaultCommandResolver]

if __name__ == "__main__":
    MyApp().setup().exec_argv()
```

`DefaultCommandResolver` maps the CLI argument `greet/hello` to the file `src/commands/greet/hello.py` and calls the function `greet__hello` inside it:

```python
# src/commands/greet/hello.py
def greet__hello(kernel, arguments):
    kernel.io.log("Hello, world!")
    return "Hello, world!"
```

Run the command:

```bash
python my_app.py greet/hello
# Hello, 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

The package is built around five cooperating layers: **kernel**, **request**, **resolver / runner**, **command**, and **response / output**. Every object that needs the kernel holds it through `AbstractKernelChild` (src/wexample_app/common/abstract_kernel_child.py), which declares a single `kernel` field and nothing else.

### Kernel

`AbstractKernel` (src/wexample_app/common/abstract_kernel.py) is the root object. It owns the working directory, the IO manager, the service container, and the output-handler factory. It exposes two execution entry points:

- `execute_kernel_command(request)` — pushes an IO recorder buffer, calls `request.execute()`, attaches the captured `prompt_trace` to the response, and returns it.
- `execute_kernel_command_and_print(request)` — calls the above, then passes the response to every output handler produced by `create_output_handlers()`.

Two mixins are composed on top of `AbstractKernel` in the typical concrete class:

**`CommandRunnerKernel`** (src/wexample_app/common/mixin/command_runner_kernel.py) manages the resolver and runner registries. It provides `get_resolver(type)`, `get_resolvers()`, `get_runner(type)`, `get_runners()`, and the `_init_resolvers()` / `_init_runners()` initializers that register class lists into the service container. The default `_get_command_runners()` returns `[PythonCommandRunner]`; `_get_command_resolvers()` returns an empty list and must be overridden.

**`CommandLineKernel`** (src/wexample_app/common/mixin/command_line_kernel.py) handles `sys.argv`. Its `exec_argv()` method parses the argument list, builds one or more `CommandRequest` objects, and drives `execute_kernel_command_and_print()` for each. It also owns the crash-report logic and maps known exception types to their exit codes.

A minimal concrete kernel looks like:

```python
@base_class
class MyApp(CommandRunnerKernel, CommandLineKernel, AbstractKernel):
    def _get_command_resolvers(self):
        return [DefaultCommandResolver]
```

### Service container

`AbstractKernel` inherits `ServiceContainerMixin` (src/wexample_app/service/mixin/service_container_mixin.py), which stores named `ServiceRegistry` instances (src/wexample_app/service/service_registry.py). The two registries the kernel always uses are declared in src/wexample_app/const/registries.py:

- `REGISTRY_KERNEL_COMMAND_RESOLVER = "command_resolvers"` — holds resolver instances.
- `REGISTRY_KERNEL_COMMAND_RUNNERS = "command_runners"` — holds runner instances.

`ServiceRegistry.instantiate_all(kernel=self)` is called at init time; it constructs each registered class on demand and caches the instance under the class's snake-cased short name.

### Request

`CommandRequest` (src/wexample_app/common/command_request.py) is built from a `name` string plus an `arguments` list. Its `__attrs_post_init__` runs the entire dispatch chain synchronously:

1. Iterates registered resolvers, calls `resolver.supports(request)` (a regex match against the name). The first match sets `request.type` and `request.match`.
2. Retrieves the resolver for that type from the registry.
3. Iterates registered runners, calls `runner.will_run(request)`. The first that returns `True` becomes `request.runner`.
4. Calls `runner.build_command_path(request)` to set `request.path`.
5. Defaults `output_target` to `["stdout"]` and `output_format` to `"str"`.

`CommandRequest.execute()` delegates to `resolver.build_command(request)`, which in turn delegates to `runner.build_runnable_command(request)`. The resulting `Command` object is then asked to `execute_request_and_normalize(request)`.

### Resolvers

`AbstractCommandResolver` (src/wexample_app/resolver/abstract_command_resolver.py) defines the contract: a `get_pattern()` classmethod returns a regex, `supports()` tests it against the command name, `get_type()` returns the type string, `build_command_path()` returns the file path for a given extension, and `build_command_function_name()` returns the Python function name to load.

`DefaultCommandResolver` (src/wexample_app/resolver/default_command_resolver.py) matches the pattern `^([\w_-]+)/([\w_-]+)$` (e.g., `greet/hello`). It maps the command to a file under `src/commands/<name>.<ext>` relative to the working directory (or `kernel.command_base_path` if set), and derives the function name by replacing `/` with `__` and stripping non-alphanumeric characters (e.g., `greet/hello` → `greet__hello`).

### Runners

`AbstractCommandRunner` (src/wexample_app/runner/abstract_command_runner.py) declares `will_run(request) -> bool` (returns `False` by default), `_build_command_function(request)` (abstract), and `build_runnable_command(request)`, which wraps the resolved callable in a `Command` instance.

`AbstractFileCommandRunner` (src/wexample_app/runner/abstract_file_command_runner.py) overrides `will_run()` to check whether the file path built by the resolver actually exists on disk. Subclasses supply `get_file_extension()`.

`PythonCommandRunner` (src/wexample_app/runner/python_command_runner.py) matches `.py` files. It loads the module via `importlib.util.spec_from_file_location` and retrieves the function by name with `getattr`.

`YamlCommandRunner` (src/wexample_app/runner/yaml_command_runner.py) matches `.yaml` files. It wraps the YAML execution in a closure and returns that closure as the command function.

### Command

`Command` (src/wexample_app/common/command.py) is a thin wrapper around a callable. `execute_request(request)` calls `self.function(kernel=self.kernel, arguments=request.arguments)`. `execute_request_and_normalize(request)` additionally passes the raw return value through `response_normalize()` (src/wexample_app/helper/response.py), which wraps plain Python values (`None` → `NullResponse`, `bool` → `BooleanResponse`, everything else → `DefaultResponse`) while passing through objects that are already an `AbstractResponse`.

### Responses

`AbstractResponse` (src/wexample_app/response/abstract_response.py) carries `content`, a `kernel` reference, and `prompt_trace` — the list of `PromptResponse` objects recorded by the IO manager while the command ran. Its `get_formatted(output_format)` method serializes to `str`, `json`, or `yaml` without writing to any output destination.

Concrete subclasses cover every output category: `StrResponse`, `IntResponse`, `ErrorResponse`, `SuccessResponse`, `WarningResponse`, `TitleResponse`, `LogResponse`, `TableResponse`, `ListResponse`, `DictResponse`, `MultipleResponse`, `QueuedCollectionResponse`, and others under `src/wexample_app/response/`.

### Output handlers

`AbstractAppOutputHandler` (src/wexample_app/output/abstract_app_output_handler.py) receives the `CommandRequest` and `AbstractResponse` from the kernel, calls `response.get_formatted(request.output_format)`, and delegates the resulting string to `_write_output()`.

`AppStdoutOutputHandler` (src/wexample_app/output/app_stdout_output_handler.py) writes to `sys.stdout`. The kernel's `create_output_handlers()` returns it by default; subclasses may return additional handlers keyed by `request.output_target`.

### Call path

```
exec_argv()
  └─ _build_command_requests_from_arguments()   # parses sys.argv
       └─ CommandRequest.__init__()             # resolver + runner selection
  └─ execute_kernel_command_and_print(request)
       └─ execute_kernel_command(request)       # pushes IO recorder
            └─ request.execute()
                 └─ resolver.build_command(request)
                      └─ runner.build_runnable_command(request)
                           └─ Command.execute_request_and_normalize(request)
                                └─ function(kernel, arguments)  # user code
                                └─ response_normalize()
       └─ output_handler.print(request, response)
            └─ response.get_formatted(output_format)
            └─ _write_output(content)           # e.g. sys.stdout.write
```

## 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
- cattrs: >=23.1.0
- dotenv: 
- python-dotenv: 
- requests: >=2.31.0
- wexample-filestate: >=17.2.0
- wexample-helpers-yaml: >=7.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-app/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-app
- **Documentation**: [docs.wexample.com](https://docs.wexample.com)
- **Issue Tracker**: https://github.com/wexample/python-app/issues
- **Discussions**: https://github.com/wexample/python-app/discussions
- **PyPI**: [pypi.org/project/wexample-app](https://pypi.org/project/wexample-app/)

## 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.
