Metadata-Version: 2.1
Name: wexample-wex-addon-process
Version: 2.0.0
Summary: Executes process runs defined on disk—either directly or by consuming them from a queue as a worker—against filestate selections
Author-Email: weeger <contact@wexample.com>
License: MIT
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.10
Requires-Dist: wexample-file>=9.2.0
Requires-Dist: wexample-queue>=1.1.0
Requires-Dist: wexample-wex-addon-app>=31.0.0
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Description-Content-Type: text/markdown

# wex-addon-process

Version: 2.0.0

`wexample-wex-addon-process` runs the treatments a board declares. A *process* says what to run and on which selection of files; a *run* is one execution of it, asked for by writing a record and picked up by a worker.

Nothing here talks to a database. A run names a process, the process names a selection and a type, the selection names files, and all four are records under the app's own `.wex/data/` — which is what lets a process run on a repository that has no board at all, and what makes a demand survive a broker that falls over.

## Running one by hand

```bash
wex process::run/execute --run 8020e9fb-05b8-4da7-857d-d3947449b6f7
```

The run record goes from `pending` to `running` to `complete`, gaining its dates,
its counters and, at the end, whatever the type wrote in `data`. A run in any
state but `pending` is left alone: a queue redelivers, and a run is done once.

## Running as a worker

```bash
wex process::worker/start
```

Blocks, consuming the `process_run` queue, and rings back on `process_run_event`
every time a run moves — on each state change, and at most once a second while
it advances. This is the process a worker container runs, and there may be as
many as the parallelism asked for.

## The types on offer

A type is named by the family that answers for it. The whole of the `filestate`
family is read off filestate's own options — every option that can produce an
operation is a treatment a process may name:

```
filestate:report                    says what the selection takes, changes nothing
filestate:mode                      permissions, and ownership with them
filestate:should_exist              create when missing, delete when forbidden
filestate:name                      rename to a required form
filestate:content  filestate:text   write, trim, sort, keep unique lines
filestate:yaml     filestate:structured_keys
filestate:should_contain_lines      and its `should_not_` twin
filestate:managed_blocks            filestate:class      filestate:on_bad_format
```

Nothing is written per treatment: an option added to filestate is a type
available here the same day, under the very name filestate gives it.

The process's `options` block is the configuration handed to each file, and has
to carry the option the type is named after:

```yaml
# a process of type filestate:mode
mode: '600'
dry_run: true
```

`dry_run` is read by the family and handed to nobody: it runs the pass without
applying it, so what a treatment *would* do is readable before it does it.

## Writing a type

A type of your own is a name and a `run()`:

```python
class ReviewProcessType(AbstractProcessType):
    name = "agent:review"
    label = "Review"

    def run(self, context: ProcessContext) -> dict[str, Any]:
        for path in context.files:
            context.advance()

        return {"files": len(context.files)}
```

What `run()` returns goes into the run's `data`. Raising is how a type says it
could not go on — the failure is written down for it, so nothing in a type has
to know what a failed run looks like.

A type that touches files should not be written this way: it belongs to the
filestate family, where the operation, its description and its undo already
exist.

## Table of Contents

- [Running one by hand](#running-one-by-hand)
- [Running as a worker](#running-as-a-worker)
- [The types on offer](#the-types-on-offer)
- [Writing a type](#writing-a-type)
- [Installation](#installation)
- [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-wex-addon-process
```

Requires Python >=3.10.

A process and a selection are records, so a treatment can be declared without a board:

```yaml
# .wex/data/selection/<uuid>.yml
title: Markdowns
patterns: "*.md\n!.wex/**"
```

```yaml
# .wex/data/process/<uuid>.yml
title: What the Markdowns take
type: 'filestate:report'
selection_id: <the selection's uuid>
options: ''
```

Asking for a run is writing a third record:

```yaml
# .wex/data/process_run/<uuid>.yml
process_id: <the process's uuid>
state: pending
```

Then:

```bash
wex process::run/execute --run <the run's uuid>
```

The same file is read back afterwards, holding what happened:

```yaml
process_id: ...
state: complete
items_total: 3
items_done: 3
data: |
  files: 3
  bytes: 16
  by_extension:
    .md: 3
```

## 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 addon is one runner, a few types, and two ways to reach it.

### The runner

src/wexample_wex_addon_process/runner/process_runner.py is where a run is taken from `pending` to `complete` or to `failed`. It reads the run, the process it names, the selection that process names, resolves the type, walks the files, and writes back at every state change.

Three decisions are worth reading. A run in any state but `pending` is left where it is, because a queue redelivers and a run is done once. The record is written on every file dealt with, being local and being the truth, while the ring is throttled to once a second — ten thousand files must not be ten thousand messages saying the same thing. And the type never sees the run: it is handed a src/wexample_wex_addon_process/process_type/process_context.py holding the files, the options and a way to say how far it has got, so what a run is *in* is decided in one place.

### Reading a selection

src/wexample_wex_addon_process/process_type/selection.py holds no list of files: a selection is a rule, and what it takes is read from disk each time it is asked for. The rule is `PathMatcher` from `wexample-file`, a line-for-line port of the PHP one the board uses; the walking is filestate's, the matcher being handed to a `ChildrenFilterOption` as its `filter`.

Going through filestate to *enumerate* and not only to *change* is deliberate: a type that reads the files and a type that rewrites them are then handed the very same tree, and there is one answer to what a selection takes rather than two.

The two implementations are kept honest against each other: three hundred random pattern sets, sixty paths and fifty directories were run through both and compared, with no difference.

### Types, and the families that declare them

src/wexample_wex_addon_process/process_type/abstract_process_type.py is deliberately an ordinary class — no kernel, no attrs, no state. A type contributed from a bundle should be readable by whoever wrote the bundle rather than by whoever wrote wex.

A type's name carries its family: `filestate:mode`, `agent:review`. The prefix is not a branch taken at run time — src/wexample_wex_addon_process/process_type/process_type_registry.py asks each family for its names once, and what a record names is found in that map or is an error. A family declares names; it decides nothing.

src/wexample_wex_addon_process/process_type/family/file_state_process_type_family.py declares none of its types by hand. It walks filestate's `DefaultOptionsProvider` and keeps the options that override `create_required_operation` — the precise line between a treatment and a piece of structure, asked of the option itself rather than listed here. `mode` is a treatment, `children` is not. Thirteen types come out of it, and an option added to filestate is a fourteenth the same day.

Each of those is a src/wexample_wex_addon_process/process_type/filestate/file_state_option_process_type.py, which holds no logic either: the process's `options` become the configuration asked of every selected file, and filestate turns the difference with the disk into operations that can be described, applied and undone. `dry_run` in the options runs the pass without applying it.

src/wexample_wex_addon_process/process_type/filestate/file_state_report_process_type.py is in that family and yet emits no operation, on filestate's own rule: a scope names something observable on disk, and a report changes nothing observable. An operation with no scope would be an operation nothing could ever run.

### The two entry points

src/wexample_wex_addon_process/commands/run/execute.py runs one run, here and now, with no queue in sight. src/wexample_wex_addon_process/service/process_run_service.py is the same runner behind a `wexample-queue` service: it consumes `process_run`, and rings back on `process_run_event` with the same shape of message it received — `kind`, `id`, `workdir`. The runner is rebuilt for each message so that the workdir a ring names is the one the message came with: one worker serves every app.

The message carries no work at all, only a pointer. A worker starting after the message was published, or reading a run somebody has since edited, sees what is true now rather than what was true when the button was pressed.

### What a record is called

src/wexample_wex_addon_process/const/process.py spells every key once. The board writes these very names into the same files, so one changed here has to be changed there — this file and `ProcessRunHydrator` on the PHP side are two halves of one contract.

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

- wexample-file: >=9.2.0
- wexample-queue: >=1.1.0
- wexample-wex-addon-app: >=31.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-wex-addon-process/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-wex-addon-process
- **Documentation**: [docs.wexample.com](https://docs.wexample.com)
- **Issue Tracker**: https://github.com/wexample/python-wex-addon-process/issues
- **Discussions**: https://github.com/wexample/python-wex-addon-process/discussions
- **PyPI**: [pypi.org/project/wexample-wex-addon-process](https://pypi.org/project/wexample-wex-addon-process/)

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