wexample-wex-addon-process 1.0.4__tar.gz → 2.1.0__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 → wexample_wex_addon_process-2.1.0}/PKG-INFO +8 -8
  2. {wexample_wex_addon_process-1.0.4 → wexample_wex_addon_process-2.1.0}/README.md +4 -4
  3. {wexample_wex_addon_process-1.0.4 → wexample_wex_addon_process-2.1.0}/pyproject.toml +7 -4
  4. {wexample_wex_addon_process-1.0.4 → wexample_wex_addon_process-2.1.0}/src/wexample_wex_addon_process/commands/run/execute.py +13 -5
  5. {wexample_wex_addon_process-1.0.4 → wexample_wex_addon_process-2.1.0}/src/wexample_wex_addon_process/process_addon_manager.py +7 -0
  6. {wexample_wex_addon_process-1.0.4 → wexample_wex_addon_process-2.1.0}/src/wexample_wex_addon_process/process_type/process_context.py +6 -9
  7. {wexample_wex_addon_process-1.0.4 → wexample_wex_addon_process-2.1.0}/src/wexample_wex_addon_process/process_type/process_type_registry.py +15 -6
  8. {wexample_wex_addon_process-1.0.4 → wexample_wex_addon_process-2.1.0}/src/wexample_wex_addon_process/record/record_file.py +10 -0
  9. {wexample_wex_addon_process-1.0.4 → wexample_wex_addon_process-2.1.0}/src/wexample_wex_addon_process/runner/process_runner.py +14 -1
  10. {wexample_wex_addon_process-1.0.4 → wexample_wex_addon_process-2.1.0}/src/wexample_wex_addon_process/service/process_run_service.py +1 -1
  11. {wexample_wex_addon_process-1.0.4 → wexample_wex_addon_process-2.1.0}/src/wexample_wex_addon_process/__init__.py +0 -0
  12. {wexample_wex_addon_process-1.0.4 → wexample_wex_addon_process-2.1.0}/src/wexample_wex_addon_process/commands/__init__.py +0 -0
  13. {wexample_wex_addon_process-1.0.4 → wexample_wex_addon_process-2.1.0}/src/wexample_wex_addon_process/commands/run/__init__.py +0 -0
  14. {wexample_wex_addon_process-1.0.4 → wexample_wex_addon_process-2.1.0}/src/wexample_wex_addon_process/commands/worker/__init__.py +0 -0
  15. {wexample_wex_addon_process-1.0.4 → wexample_wex_addon_process-2.1.0}/src/wexample_wex_addon_process/commands/worker/start.py +0 -0
  16. {wexample_wex_addon_process-1.0.4 → wexample_wex_addon_process-2.1.0}/src/wexample_wex_addon_process/const/__init__.py +0 -0
  17. {wexample_wex_addon_process-1.0.4 → wexample_wex_addon_process-2.1.0}/src/wexample_wex_addon_process/const/process.py +0 -0
  18. {wexample_wex_addon_process-1.0.4 → wexample_wex_addon_process-2.1.0}/src/wexample_wex_addon_process/process_type/__init__.py +0 -0
  19. {wexample_wex_addon_process-1.0.4 → wexample_wex_addon_process-2.1.0}/src/wexample_wex_addon_process/process_type/abstract_process_type.py +0 -0
  20. {wexample_wex_addon_process-1.0.4 → wexample_wex_addon_process-2.1.0}/src/wexample_wex_addon_process/process_type/family/__init__.py +0 -0
  21. {wexample_wex_addon_process-1.0.4 → wexample_wex_addon_process-2.1.0}/src/wexample_wex_addon_process/process_type/family/abstract_process_type_family.py +0 -0
  22. {wexample_wex_addon_process-1.0.4 → wexample_wex_addon_process-2.1.0}/src/wexample_wex_addon_process/process_type/family/file_state_process_type_family.py +0 -0
  23. {wexample_wex_addon_process-1.0.4 → wexample_wex_addon_process-2.1.0}/src/wexample_wex_addon_process/process_type/filestate/__init__.py +0 -0
  24. {wexample_wex_addon_process-1.0.4 → wexample_wex_addon_process-2.1.0}/src/wexample_wex_addon_process/process_type/filestate/file_state_option_process_type.py +0 -0
  25. {wexample_wex_addon_process-1.0.4 → wexample_wex_addon_process-2.1.0}/src/wexample_wex_addon_process/process_type/filestate/file_state_report_process_type.py +0 -0
  26. {wexample_wex_addon_process-1.0.4 → wexample_wex_addon_process-2.1.0}/src/wexample_wex_addon_process/process_type/selection.py +0 -0
  27. {wexample_wex_addon_process-1.0.4 → wexample_wex_addon_process-2.1.0}/src/wexample_wex_addon_process/py.typed +0 -0
  28. {wexample_wex_addon_process-1.0.4 → wexample_wex_addon_process-2.1.0}/src/wexample_wex_addon_process/record/__init__.py +0 -0
  29. {wexample_wex_addon_process-1.0.4 → wexample_wex_addon_process-2.1.0}/src/wexample_wex_addon_process/runner/__init__.py +0 -0
  30. {wexample_wex_addon_process-1.0.4 → wexample_wex_addon_process-2.1.0}/src/wexample_wex_addon_process/service/__init__.py +0 -0
  31. {wexample_wex_addon_process-1.0.4 → wexample_wex_addon_process-2.1.0}/tests/.gitkeep +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: wexample-wex-addon-process
3
- Version: 1.0.4
3
+ Version: 2.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
5
  Author-Email: weeger <contact@wexample.com>
6
6
  License: MIT
@@ -8,9 +8,9 @@ Classifier: Programming Language :: Python :: 3
8
8
  Classifier: License :: OSI Approved :: MIT License
9
9
  Classifier: Operating System :: OS Independent
10
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
11
+ Requires-Dist: wexample-file>=9.2.0
12
+ Requires-Dist: wexample-queue>=1.1.0
13
+ Requires-Dist: wexample-wex-addon-app>=31.1.0
14
14
  Provides-Extra: dev
15
15
  Requires-Dist: pytest; extra == "dev"
16
16
  Requires-Dist: pytest-cov; extra == "dev"
@@ -18,7 +18,7 @@ Description-Content-Type: text/markdown
18
18
 
19
19
  # wex-addon-process
20
20
 
21
- Version: 1.0.4
21
+ Version: 2.1.0
22
22
 
23
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
24
 
@@ -273,9 +273,9 @@ Visit the [Wexample Suite documentation](https://docs.wexample.com) for the comp
273
273
 
274
274
  ## Dependencies
275
275
 
276
- - wexample-file: >=9.1.6
277
- - wexample-queue: >=1.0.1
278
- - wexample-wex-addon-app: >=31.0.0
276
+ - wexample-file: >=9.2.0
277
+ - wexample-queue: >=1.1.0
278
+ - wexample-wex-addon-app: >=31.1.0
279
279
 
280
280
  ## Versioning & Compatibility Policy
281
281
 
@@ -1,6 +1,6 @@
1
1
  # wex-addon-process
2
2
 
3
- Version: 1.0.4
3
+ Version: 2.1.0
4
4
 
5
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
6
 
@@ -255,9 +255,9 @@ Visit the [Wexample Suite documentation](https://docs.wexample.com) for the comp
255
255
 
256
256
  ## Dependencies
257
257
 
258
- - wexample-file: >=9.1.6
259
- - wexample-queue: >=1.0.1
260
- - wexample-wex-addon-app: >=31.0.0
258
+ - wexample-file: >=9.2.0
259
+ - wexample-queue: >=1.1.0
260
+ - wexample-wex-addon-app: >=31.1.0
261
261
 
262
262
  ## Versioning & Compatibility Policy
263
263
 
@@ -6,7 +6,7 @@ build-backend = "pdm.backend"
6
6
 
7
7
  [project]
8
8
  name = "wexample-wex-addon-process"
9
- version = "1.0.4"
9
+ version = "2.1.0"
10
10
  description = "Executes process runs defined on disk—either directly or by consuming them from a queue as a worker—against filestate selections"
11
11
  authors = [
12
12
  { name = "weeger", email = "contact@wexample.com" },
@@ -18,9 +18,9 @@ classifiers = [
18
18
  "Operating System :: OS Independent",
19
19
  ]
20
20
  dependencies = [
21
- "wexample-file>=9.1.6",
22
- "wexample-queue>=1.0.1",
23
- "wexample-wex-addon-app>=31.0.0",
21
+ "wexample-file>=9.2.0",
22
+ "wexample-queue>=1.1.0",
23
+ "wexample-wex-addon-app>=31.1.0",
24
24
  ]
25
25
 
26
26
  [project.readme]
@@ -36,6 +36,9 @@ dev = [
36
36
  "pytest-cov",
37
37
  ]
38
38
 
39
+ [project.entry-points."wex.addons"]
40
+ process = "wexample_wex_addon_process.process_addon_manager:ProcessAddonManager"
41
+
39
42
  [tool.setuptools.packages.find]
40
43
  include = [
41
44
  "*",
@@ -37,17 +37,25 @@ def process__run__execute(
37
37
  ) -> AbstractResponse | None:
38
38
  from wexample_app.response.success_response import SuccessResponse
39
39
 
40
+ from wexample_wex_addon_process.const.process import RUN_KEY_STATE
40
41
  from wexample_wex_addon_process.process_type.process_type_registry import (
41
42
  ProcessTypeRegistry,
42
43
  )
43
44
  from wexample_wex_addon_process.runner.process_runner import ProcessRunner
44
45
 
45
- runner = ProcessRunner(io=context.io, registry=ProcessTypeRegistry.default())
46
+ runner = ProcessRunner(
47
+ io=context.io,
48
+ registry=ProcessTypeRegistry.from_kernel(context.kernel),
49
+ )
50
+
51
+ record = runner.execute(
52
+ workdir=app_workdir.get_path(),
53
+ run_id=run,
54
+ )
46
55
 
56
+ # What became of the run is in its record either way; the response only has
57
+ # to say where it landed.
47
58
  return SuccessResponse(
48
59
  kernel=context.kernel,
49
- data=runner.execute(
50
- workdir=app_workdir.get_path(),
51
- run_id=run,
52
- ),
60
+ message=f"Run {run}: {record.get(RUN_KEY_STATE)}.",
53
61
  )
@@ -13,3 +13,10 @@ class ProcessAddonManager(AbstractAddonManager):
13
13
  import wexample_wex_addon_process
14
14
 
15
15
  return wexample_wex_addon_process
16
+
17
+ def get_process_type_family_classes(self) -> list[type]:
18
+ from wexample_wex_addon_process.process_type.family.file_state_process_type_family import (
19
+ FileStateProcessTypeFamily,
20
+ )
21
+
22
+ return [FileStateProcessTypeFamily]
@@ -16,26 +16,23 @@ class ProcessContext:
16
16
  It holds no run and no record. A type that could reach the record could write
17
17
  anywhere in it, and what a run is in is decided by the runner alone — a type
18
18
  says what it did, not that it is finished.
19
+
20
+ The files are handed over rather than read here: a selection is a rule and
21
+ answers what the disk holds when asked, so a second walk would count files a
22
+ run created itself and leave it done on more than its own total.
19
23
  """
20
24
 
21
25
  def __init__(
22
26
  self,
23
27
  selection: Selection,
28
+ files: list[Path],
24
29
  options: dict[str, Any],
25
30
  advance: Callable[[int], None],
26
31
  ) -> None:
27
32
  self.selection = selection
33
+ self.files = files
28
34
  self.options = options
29
35
  self._advance = advance
30
- self._files: list[Path] | None = None
31
-
32
- @property
33
- def files(self) -> list[Path]:
34
- """The files the selection takes, read once for the length of the run."""
35
- if self._files is None:
36
- self._files = self.selection.files()
37
-
38
- return self._files
39
36
 
40
37
  @property
41
38
  def workdir(self) -> Path:
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING
3
+ from typing import TYPE_CHECKING, Any
4
4
 
5
5
  if TYPE_CHECKING:
6
6
  from wexample_wex_addon_process.process_type.abstract_process_type import (
@@ -29,12 +29,21 @@ class ProcessTypeRegistry:
29
29
  self._types[process_type.name] = process_type
30
30
 
31
31
  @classmethod
32
- def default(cls) -> ProcessTypeRegistry:
33
- from wexample_wex_addon_process.process_type.family.file_state_process_type_family import (
34
- FileStateProcessTypeFamily,
35
- )
32
+ def from_kernel(cls, kernel: Any) -> ProcessTypeRegistry:
33
+ """Every family the loaded addons bring, this one's included.
36
34
 
37
- return cls([FileStateProcessTypeFamily()])
35
+ The only way in: a hard-coded list would answer for the addons that
36
+ happened to be known here, and a type brought by an addon installed
37
+ beside it would be a name the runner refuses.
38
+ """
39
+ families: list[AbstractProcessTypeFamily] = []
40
+
41
+ for addon in kernel.get_addons().values():
42
+ families.extend(
43
+ family_class() for family_class in addon.get_process_type_family_classes()
44
+ )
45
+
46
+ return cls(families)
38
47
 
39
48
  def get(self, name: str) -> AbstractProcessType | None:
40
49
  return self._types.get(name)
@@ -43,3 +43,13 @@ def write_record(path: Path, values: dict[str, Any]) -> None:
43
43
  allow_unicode=True,
44
44
  )
45
45
  )
46
+
47
+ # Written from both ends over a shared bind mount — the board web as root,
48
+ # a worker as its own user — so each writer leaves the record open for the
49
+ # other rather than at a mode only its own uid can rewrite. Best-effort:
50
+ # chmod is the owner's to make, and a writer that is not the owner is
51
+ # writing a file the owner already opened wide enough to reach.
52
+ import contextlib
53
+
54
+ with contextlib.suppress(PermissionError):
55
+ path.chmod(0o666)
@@ -97,7 +97,19 @@ class ProcessRunner:
97
97
  )
98
98
 
99
99
  selection = self._selection(workdir, process.get(PROCESS_KEY_SELECTION_ID))
100
- files = selection.files()
100
+
101
+ # The walk reads the disk, and a run whose walk raises must end up
102
+ # `failed` like any other: left out of the guard, the exception would
103
+ # sink the message and freeze the run on `pending` with nothing to show.
104
+ try:
105
+ files = selection.files()
106
+ except Exception as exception:
107
+ self.io.debug(traceback.format_exc())
108
+
109
+ return self._fail(
110
+ run_path, run_id, f"{type(exception).__name__}: {exception}"
111
+ )
112
+
101
113
  done = {"count": 0}
102
114
 
103
115
  def advance(count: int) -> None:
@@ -122,6 +134,7 @@ class ProcessRunner:
122
134
  data = process_type.run(
123
135
  ProcessContext(
124
136
  selection=selection,
137
+ files=files,
125
138
  options=self._options(process.get(PROCESS_KEY_OPTIONS)),
126
139
  advance=advance,
127
140
  )
@@ -60,7 +60,7 @@ class ProcessRunService(AbstractQueueService):
60
60
  # must not answer for each other.
61
61
  return ProcessRunner(
62
62
  io=self.io,
63
- registry=ProcessTypeRegistry.default(),
63
+ registry=ProcessTypeRegistry.from_kernel(self.kernel),
64
64
  ring=lambda run_id: self._ring(workdir, run_id),
65
65
  )
66
66