wexample-wex-addon-process 1.0.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. wexample_wex_addon_process-1.0.4/PKG-INFO +332 -0
  2. wexample_wex_addon_process-1.0.4/README.md +314 -0
  3. wexample_wex_addon_process-1.0.4/pyproject.toml +83 -0
  4. wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/__init__.py +0 -0
  5. wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/commands/__init__.py +0 -0
  6. wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/commands/run/__init__.py +0 -0
  7. wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/commands/run/execute.py +53 -0
  8. wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/commands/worker/__init__.py +0 -0
  9. wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/commands/worker/start.py +47 -0
  10. wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/const/__init__.py +0 -0
  11. wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/const/process.py +34 -0
  12. wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/process_addon_manager.py +15 -0
  13. wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/process_type/__init__.py +0 -0
  14. wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/process_type/abstract_process_type.py +34 -0
  15. wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/process_type/family/__init__.py +0 -0
  16. wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/process_type/family/abstract_process_type_family.py +26 -0
  17. wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/process_type/family/file_state_process_type_family.py +61 -0
  18. wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/process_type/filestate/__init__.py +0 -0
  19. wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/process_type/filestate/file_state_option_process_type.py +70 -0
  20. wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/process_type/filestate/file_state_report_process_type.py +43 -0
  21. wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/process_type/process_context.py +46 -0
  22. wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/process_type/process_type_registry.py +43 -0
  23. wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/process_type/selection.py +86 -0
  24. wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/py.typed +0 -0
  25. wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/record/__init__.py +0 -0
  26. wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/record/record_file.py +45 -0
  27. wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/runner/__init__.py +0 -0
  28. wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/runner/process_runner.py +244 -0
  29. wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/service/__init__.py +0 -0
  30. wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/service/process_run_service.py +77 -0
  31. wexample_wex_addon_process-1.0.4/tests/.gitkeep +0 -0
@@ -0,0 +1,332 @@
1
+ Metadata-Version: 2.1
2
+ Name: wexample-wex-addon-process
3
+ Version: 1.0.4
4
+ Summary: Executes process runs defined on disk—either directly or by consuming them from a queue as a worker—against filestate selections
5
+ Author-Email: weeger <contact@wexample.com>
6
+ License: MIT
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.10
11
+ Requires-Dist: wexample-file>=9.1.6
12
+ Requires-Dist: wexample-queue>=1.0.1
13
+ Requires-Dist: wexample-wex-addon-app>=31.0.0
14
+ Provides-Extra: dev
15
+ Requires-Dist: pytest; extra == "dev"
16
+ Requires-Dist: pytest-cov; extra == "dev"
17
+ Description-Content-Type: text/markdown
18
+
19
+ # wex-addon-process
20
+
21
+ Version: 1.0.4
22
+
23
+ `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.
24
+
25
+ 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.
26
+
27
+ ## Running one by hand
28
+
29
+ ```bash
30
+ wex process::run/execute --run 8020e9fb-05b8-4da7-857d-d3947449b6f7
31
+ ```
32
+
33
+ The run record goes from `pending` to `running` to `complete`, gaining its dates,
34
+ its counters and, at the end, whatever the type wrote in `data`. A run in any
35
+ state but `pending` is left alone: a queue redelivers, and a run is done once.
36
+
37
+ ## Running as a worker
38
+
39
+ ```bash
40
+ wex process::worker/start
41
+ ```
42
+
43
+ Blocks, consuming the `process_run` queue, and rings back on `process_run_event`
44
+ every time a run moves — on each state change, and at most once a second while
45
+ it advances. This is the process a worker container runs, and there may be as
46
+ many as the parallelism asked for.
47
+
48
+ ## The types on offer
49
+
50
+ A type is named by the family that answers for it. The whole of the `filestate`
51
+ family is read off filestate's own options — every option that can produce an
52
+ operation is a treatment a process may name:
53
+
54
+ ```
55
+ filestate:report says what the selection takes, changes nothing
56
+ filestate:mode permissions, and ownership with them
57
+ filestate:should_exist create when missing, delete when forbidden
58
+ filestate:name rename to a required form
59
+ filestate:content filestate:text write, trim, sort, keep unique lines
60
+ filestate:yaml filestate:structured_keys
61
+ filestate:should_contain_lines and its `should_not_` twin
62
+ filestate:managed_blocks filestate:class filestate:on_bad_format
63
+ ```
64
+
65
+ Nothing is written per treatment: an option added to filestate is a type
66
+ available here the same day, under the very name filestate gives it.
67
+
68
+ The process's `options` block is the configuration handed to each file, and has
69
+ to carry the option the type is named after:
70
+
71
+ ```yaml
72
+ # a process of type filestate:mode
73
+ mode: '600'
74
+ dry_run: true
75
+ ```
76
+
77
+ `dry_run` is read by the family and handed to nobody: it runs the pass without
78
+ applying it, so what a treatment *would* do is readable before it does it.
79
+
80
+ ## Writing a type
81
+
82
+ A type of your own is a name and a `run()`:
83
+
84
+ ```python
85
+ class ReviewProcessType(AbstractProcessType):
86
+ name = "agent:review"
87
+ label = "Review"
88
+
89
+ def run(self, context: ProcessContext) -> dict[str, Any]:
90
+ for path in context.files:
91
+ context.advance()
92
+
93
+ return {"files": len(context.files)}
94
+ ```
95
+
96
+ What `run()` returns goes into the run's `data`. Raising is how a type says it
97
+ could not go on — the failure is written down for it, so nothing in a type has
98
+ to know what a failed run looks like.
99
+
100
+ A type that touches files should not be written this way: it belongs to the
101
+ filestate family, where the operation, its description and its undo already
102
+ exist.
103
+
104
+ ## Table of Contents
105
+
106
+ - [Running one by hand](#running-one-by-hand)
107
+ - [Running as a worker](#running-as-a-worker)
108
+ - [The types on offer](#the-types-on-offer)
109
+ - [Writing a type](#writing-a-type)
110
+ - [Installation](#installation)
111
+ - [Tests](#tests)
112
+ - [Architecture](#architecture)
113
+ - [Integration in the Suite](#integration-in-the-suite)
114
+ - [Dependencies](#dependencies)
115
+ - [Versioning & Compatibility Policy](#versioning--compatibility-policy)
116
+ - [License](#license)
117
+ - [About us](#about-us)
118
+ - [Known Limitations & Roadmap](#known-limitations--roadmap)
119
+ - [Status & Compatibility](#status--compatibility)
120
+ - [Useful Links](#useful-links)
121
+ - [Migration Notes](#migration-notes)
122
+
123
+ ## Installation
124
+
125
+ ```bash
126
+ pip install wexample-wex-addon-process
127
+ ```
128
+
129
+ Requires Python >=3.10.
130
+
131
+ A process and a selection are records, so a treatment can be declared without a board:
132
+
133
+ ```yaml
134
+ # .wex/data/selection/<uuid>.yml
135
+ title: Markdowns
136
+ patterns: "*.md\n!.wex/**"
137
+ ```
138
+
139
+ ```yaml
140
+ # .wex/data/process/<uuid>.yml
141
+ title: What the Markdowns take
142
+ type: 'filestate:report'
143
+ selection_id: <the selection's uuid>
144
+ options: ''
145
+ ```
146
+
147
+ Asking for a run is writing a third record:
148
+
149
+ ```yaml
150
+ # .wex/data/process_run/<uuid>.yml
151
+ process_id: <the process's uuid>
152
+ state: pending
153
+ ```
154
+
155
+ Then:
156
+
157
+ ```bash
158
+ wex process::run/execute --run <the run's uuid>
159
+ ```
160
+
161
+ The same file is read back afterwards, holding what happened:
162
+
163
+ ```yaml
164
+ process_id: ...
165
+ state: complete
166
+ items_total: 3
167
+ items_done: 3
168
+ data: |
169
+ files: 3
170
+ bytes: 16
171
+ by_extension:
172
+ .md: 3
173
+ ```
174
+
175
+ ## Tests
176
+
177
+ This project uses `pytest` for testing and `pytest-cov` for code coverage analysis.
178
+
179
+ ### Installation
180
+
181
+ First, install the required testing dependencies:
182
+ ```bash
183
+ .venv/bin/python -m pip install pytest pytest-cov
184
+ ```
185
+
186
+ ### Basic Usage
187
+
188
+ Run all tests with coverage:
189
+ ```bash
190
+ .venv/bin/python -m pytest --cov --cov-report=html
191
+ ```
192
+
193
+ ### Common Commands
194
+ ```bash
195
+ # Run tests with coverage for a specific module
196
+ .venv/bin/python -m pytest --cov=your_module
197
+
198
+ # Show which lines are not covered
199
+ .venv/bin/python -m pytest --cov=your_module --cov-report=term-missing
200
+
201
+ # Generate an HTML coverage report
202
+ .venv/bin/python -m pytest --cov=your_module --cov-report=html
203
+
204
+ # Combine terminal and HTML reports
205
+ .venv/bin/python -m pytest --cov=your_module --cov-report=term-missing --cov-report=html
206
+
207
+ # Run specific test file with coverage
208
+ .venv/bin/python -m pytest tests/test_file.py --cov=your_module --cov-report=term-missing
209
+ ```
210
+
211
+ ### Viewing HTML Reports
212
+
213
+ After generating an HTML report, open `htmlcov/index.html` in your browser to view detailed line-by-line coverage information.
214
+
215
+ ### Coverage Threshold
216
+
217
+ To enforce a minimum coverage percentage:
218
+ ```bash
219
+ .venv/bin/python -m pytest --cov=your_module --cov-fail-under=80
220
+ ```
221
+
222
+ This will cause the test suite to fail if coverage drops below 80%.
223
+
224
+ ## Architecture
225
+
226
+ The addon is one runner, a few types, and two ways to reach it.
227
+
228
+ ### The runner
229
+
230
+ 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.
231
+
232
+ 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.
233
+
234
+ ### Reading a selection
235
+
236
+ 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`.
237
+
238
+ 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.
239
+
240
+ 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.
241
+
242
+ ### Types, and the families that declare them
243
+
244
+ 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.
245
+
246
+ 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.
247
+
248
+ 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.
249
+
250
+ 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.
251
+
252
+ 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.
253
+
254
+ ### The two entry points
255
+
256
+ 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.
257
+
258
+ 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.
259
+
260
+ ### What a record is called
261
+
262
+ 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.
263
+
264
+ ## Integration in the Suite
265
+
266
+ 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.
267
+
268
+ ### Related Packages
269
+
270
+ 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.
271
+
272
+ Visit the [Wexample Suite documentation](https://docs.wexample.com) for the complete package ecosystem.
273
+
274
+ ## Dependencies
275
+
276
+ - wexample-file: >=9.1.6
277
+ - wexample-queue: >=1.0.1
278
+ - wexample-wex-addon-app: >=31.0.0
279
+
280
+ ## Versioning & Compatibility Policy
281
+
282
+ Wexample packages follow **Semantic Versioning** (SemVer):
283
+
284
+ - **MAJOR**: Breaking changes
285
+ - **MINOR**: New features, backward compatible
286
+ - **PATCH**: Bug fixes, backward compatible
287
+
288
+ We maintain backward compatibility within major versions and provide clear migration guides for breaking changes.
289
+
290
+ ## License
291
+
292
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
293
+
294
+ Free to use in both personal and commercial projects.
295
+
296
+ ## About us
297
+
298
+ [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.
299
+
300
+ 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.
301
+
302
+ 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.
303
+
304
+ ## Known Limitations & Roadmap
305
+
306
+ Current limitations and planned features are tracked in the GitHub issues.
307
+
308
+ See the [project roadmap](https://github.com/wexample/python-wex-addon-process/issues) for upcoming features and improvements.
309
+
310
+ ## Status & Compatibility
311
+
312
+ **Maturity**: Production-ready
313
+
314
+ **Python Support**: >=3.10
315
+
316
+ **OS Support**: Linux, macOS, Windows
317
+
318
+ **Status**: Actively maintained
319
+
320
+ ## Useful Links
321
+
322
+ - **Homepage**: https://github.com/wexample/python-wex-addon-process
323
+ - **Documentation**: [docs.wexample.com](https://docs.wexample.com)
324
+ - **Issue Tracker**: https://github.com/wexample/python-wex-addon-process/issues
325
+ - **Discussions**: https://github.com/wexample/python-wex-addon-process/discussions
326
+ - **PyPI**: [pypi.org/project/wexample-wex-addon-process](https://pypi.org/project/wexample-wex-addon-process/)
327
+
328
+ ## Migration Notes
329
+
330
+ When upgrading between major versions, refer to the migration guides in the documentation.
331
+
332
+ Breaking changes are clearly documented with upgrade paths and examples.
@@ -0,0 +1,314 @@
1
+ # wex-addon-process
2
+
3
+ Version: 1.0.4
4
+
5
+ `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.
6
+
7
+ 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.
8
+
9
+ ## Running one by hand
10
+
11
+ ```bash
12
+ wex process::run/execute --run 8020e9fb-05b8-4da7-857d-d3947449b6f7
13
+ ```
14
+
15
+ The run record goes from `pending` to `running` to `complete`, gaining its dates,
16
+ its counters and, at the end, whatever the type wrote in `data`. A run in any
17
+ state but `pending` is left alone: a queue redelivers, and a run is done once.
18
+
19
+ ## Running as a worker
20
+
21
+ ```bash
22
+ wex process::worker/start
23
+ ```
24
+
25
+ Blocks, consuming the `process_run` queue, and rings back on `process_run_event`
26
+ every time a run moves — on each state change, and at most once a second while
27
+ it advances. This is the process a worker container runs, and there may be as
28
+ many as the parallelism asked for.
29
+
30
+ ## The types on offer
31
+
32
+ A type is named by the family that answers for it. The whole of the `filestate`
33
+ family is read off filestate's own options — every option that can produce an
34
+ operation is a treatment a process may name:
35
+
36
+ ```
37
+ filestate:report says what the selection takes, changes nothing
38
+ filestate:mode permissions, and ownership with them
39
+ filestate:should_exist create when missing, delete when forbidden
40
+ filestate:name rename to a required form
41
+ filestate:content filestate:text write, trim, sort, keep unique lines
42
+ filestate:yaml filestate:structured_keys
43
+ filestate:should_contain_lines and its `should_not_` twin
44
+ filestate:managed_blocks filestate:class filestate:on_bad_format
45
+ ```
46
+
47
+ Nothing is written per treatment: an option added to filestate is a type
48
+ available here the same day, under the very name filestate gives it.
49
+
50
+ The process's `options` block is the configuration handed to each file, and has
51
+ to carry the option the type is named after:
52
+
53
+ ```yaml
54
+ # a process of type filestate:mode
55
+ mode: '600'
56
+ dry_run: true
57
+ ```
58
+
59
+ `dry_run` is read by the family and handed to nobody: it runs the pass without
60
+ applying it, so what a treatment *would* do is readable before it does it.
61
+
62
+ ## Writing a type
63
+
64
+ A type of your own is a name and a `run()`:
65
+
66
+ ```python
67
+ class ReviewProcessType(AbstractProcessType):
68
+ name = "agent:review"
69
+ label = "Review"
70
+
71
+ def run(self, context: ProcessContext) -> dict[str, Any]:
72
+ for path in context.files:
73
+ context.advance()
74
+
75
+ return {"files": len(context.files)}
76
+ ```
77
+
78
+ What `run()` returns goes into the run's `data`. Raising is how a type says it
79
+ could not go on — the failure is written down for it, so nothing in a type has
80
+ to know what a failed run looks like.
81
+
82
+ A type that touches files should not be written this way: it belongs to the
83
+ filestate family, where the operation, its description and its undo already
84
+ exist.
85
+
86
+ ## Table of Contents
87
+
88
+ - [Running one by hand](#running-one-by-hand)
89
+ - [Running as a worker](#running-as-a-worker)
90
+ - [The types on offer](#the-types-on-offer)
91
+ - [Writing a type](#writing-a-type)
92
+ - [Installation](#installation)
93
+ - [Tests](#tests)
94
+ - [Architecture](#architecture)
95
+ - [Integration in the Suite](#integration-in-the-suite)
96
+ - [Dependencies](#dependencies)
97
+ - [Versioning & Compatibility Policy](#versioning--compatibility-policy)
98
+ - [License](#license)
99
+ - [About us](#about-us)
100
+ - [Known Limitations & Roadmap](#known-limitations--roadmap)
101
+ - [Status & Compatibility](#status--compatibility)
102
+ - [Useful Links](#useful-links)
103
+ - [Migration Notes](#migration-notes)
104
+
105
+ ## Installation
106
+
107
+ ```bash
108
+ pip install wexample-wex-addon-process
109
+ ```
110
+
111
+ Requires Python >=3.10.
112
+
113
+ A process and a selection are records, so a treatment can be declared without a board:
114
+
115
+ ```yaml
116
+ # .wex/data/selection/<uuid>.yml
117
+ title: Markdowns
118
+ patterns: "*.md\n!.wex/**"
119
+ ```
120
+
121
+ ```yaml
122
+ # .wex/data/process/<uuid>.yml
123
+ title: What the Markdowns take
124
+ type: 'filestate:report'
125
+ selection_id: <the selection's uuid>
126
+ options: ''
127
+ ```
128
+
129
+ Asking for a run is writing a third record:
130
+
131
+ ```yaml
132
+ # .wex/data/process_run/<uuid>.yml
133
+ process_id: <the process's uuid>
134
+ state: pending
135
+ ```
136
+
137
+ Then:
138
+
139
+ ```bash
140
+ wex process::run/execute --run <the run's uuid>
141
+ ```
142
+
143
+ The same file is read back afterwards, holding what happened:
144
+
145
+ ```yaml
146
+ process_id: ...
147
+ state: complete
148
+ items_total: 3
149
+ items_done: 3
150
+ data: |
151
+ files: 3
152
+ bytes: 16
153
+ by_extension:
154
+ .md: 3
155
+ ```
156
+
157
+ ## Tests
158
+
159
+ This project uses `pytest` for testing and `pytest-cov` for code coverage analysis.
160
+
161
+ ### Installation
162
+
163
+ First, install the required testing dependencies:
164
+ ```bash
165
+ .venv/bin/python -m pip install pytest pytest-cov
166
+ ```
167
+
168
+ ### Basic Usage
169
+
170
+ Run all tests with coverage:
171
+ ```bash
172
+ .venv/bin/python -m pytest --cov --cov-report=html
173
+ ```
174
+
175
+ ### Common Commands
176
+ ```bash
177
+ # Run tests with coverage for a specific module
178
+ .venv/bin/python -m pytest --cov=your_module
179
+
180
+ # Show which lines are not covered
181
+ .venv/bin/python -m pytest --cov=your_module --cov-report=term-missing
182
+
183
+ # Generate an HTML coverage report
184
+ .venv/bin/python -m pytest --cov=your_module --cov-report=html
185
+
186
+ # Combine terminal and HTML reports
187
+ .venv/bin/python -m pytest --cov=your_module --cov-report=term-missing --cov-report=html
188
+
189
+ # Run specific test file with coverage
190
+ .venv/bin/python -m pytest tests/test_file.py --cov=your_module --cov-report=term-missing
191
+ ```
192
+
193
+ ### Viewing HTML Reports
194
+
195
+ After generating an HTML report, open `htmlcov/index.html` in your browser to view detailed line-by-line coverage information.
196
+
197
+ ### Coverage Threshold
198
+
199
+ To enforce a minimum coverage percentage:
200
+ ```bash
201
+ .venv/bin/python -m pytest --cov=your_module --cov-fail-under=80
202
+ ```
203
+
204
+ This will cause the test suite to fail if coverage drops below 80%.
205
+
206
+ ## Architecture
207
+
208
+ The addon is one runner, a few types, and two ways to reach it.
209
+
210
+ ### The runner
211
+
212
+ 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.
213
+
214
+ 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.
215
+
216
+ ### Reading a selection
217
+
218
+ 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`.
219
+
220
+ 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.
221
+
222
+ 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.
223
+
224
+ ### Types, and the families that declare them
225
+
226
+ 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.
227
+
228
+ 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.
229
+
230
+ 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.
231
+
232
+ 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.
233
+
234
+ 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.
235
+
236
+ ### The two entry points
237
+
238
+ 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.
239
+
240
+ 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.
241
+
242
+ ### What a record is called
243
+
244
+ 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.
245
+
246
+ ## Integration in the Suite
247
+
248
+ 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.
249
+
250
+ ### Related Packages
251
+
252
+ 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.
253
+
254
+ Visit the [Wexample Suite documentation](https://docs.wexample.com) for the complete package ecosystem.
255
+
256
+ ## Dependencies
257
+
258
+ - wexample-file: >=9.1.6
259
+ - wexample-queue: >=1.0.1
260
+ - wexample-wex-addon-app: >=31.0.0
261
+
262
+ ## Versioning & Compatibility Policy
263
+
264
+ Wexample packages follow **Semantic Versioning** (SemVer):
265
+
266
+ - **MAJOR**: Breaking changes
267
+ - **MINOR**: New features, backward compatible
268
+ - **PATCH**: Bug fixes, backward compatible
269
+
270
+ We maintain backward compatibility within major versions and provide clear migration guides for breaking changes.
271
+
272
+ ## License
273
+
274
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
275
+
276
+ Free to use in both personal and commercial projects.
277
+
278
+ ## About us
279
+
280
+ [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.
281
+
282
+ 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.
283
+
284
+ 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.
285
+
286
+ ## Known Limitations & Roadmap
287
+
288
+ Current limitations and planned features are tracked in the GitHub issues.
289
+
290
+ See the [project roadmap](https://github.com/wexample/python-wex-addon-process/issues) for upcoming features and improvements.
291
+
292
+ ## Status & Compatibility
293
+
294
+ **Maturity**: Production-ready
295
+
296
+ **Python Support**: >=3.10
297
+
298
+ **OS Support**: Linux, macOS, Windows
299
+
300
+ **Status**: Actively maintained
301
+
302
+ ## Useful Links
303
+
304
+ - **Homepage**: https://github.com/wexample/python-wex-addon-process
305
+ - **Documentation**: [docs.wexample.com](https://docs.wexample.com)
306
+ - **Issue Tracker**: https://github.com/wexample/python-wex-addon-process/issues
307
+ - **Discussions**: https://github.com/wexample/python-wex-addon-process/discussions
308
+ - **PyPI**: [pypi.org/project/wexample-wex-addon-process](https://pypi.org/project/wexample-wex-addon-process/)
309
+
310
+ ## Migration Notes
311
+
312
+ When upgrading between major versions, refer to the migration guides in the documentation.
313
+
314
+ Breaking changes are clearly documented with upgrade paths and examples.