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.
- wexample_wex_addon_process-1.0.4/PKG-INFO +332 -0
- wexample_wex_addon_process-1.0.4/README.md +314 -0
- wexample_wex_addon_process-1.0.4/pyproject.toml +83 -0
- wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/__init__.py +0 -0
- wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/commands/__init__.py +0 -0
- wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/commands/run/__init__.py +0 -0
- wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/commands/run/execute.py +53 -0
- wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/commands/worker/__init__.py +0 -0
- wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/commands/worker/start.py +47 -0
- wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/const/__init__.py +0 -0
- wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/const/process.py +34 -0
- wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/process_addon_manager.py +15 -0
- wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/process_type/__init__.py +0 -0
- wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/process_type/abstract_process_type.py +34 -0
- wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/process_type/family/__init__.py +0 -0
- wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/process_type/family/abstract_process_type_family.py +26 -0
- wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/process_type/family/file_state_process_type_family.py +61 -0
- wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/process_type/filestate/__init__.py +0 -0
- wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/process_type/filestate/file_state_option_process_type.py +70 -0
- wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/process_type/filestate/file_state_report_process_type.py +43 -0
- wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/process_type/process_context.py +46 -0
- wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/process_type/process_type_registry.py +43 -0
- wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/process_type/selection.py +86 -0
- wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/py.typed +0 -0
- wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/record/__init__.py +0 -0
- wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/record/record_file.py +45 -0
- wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/runner/__init__.py +0 -0
- wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/runner/process_runner.py +244 -0
- wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/service/__init__.py +0 -0
- wexample_wex_addon_process-1.0.4/src/wexample_wex_addon_process/service/process_run_service.py +77 -0
- 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.
|