ppkit 0.1.0__tar.gz → 0.2.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 (62) hide show
  1. ppkit-0.2.0/CHANGELOG.md +234 -0
  2. {ppkit-0.1.0 → ppkit-0.2.0}/CONTRIBUTING.md +92 -24
  3. {ppkit-0.1.0 → ppkit-0.2.0}/PKG-INFO +40 -16
  4. {ppkit-0.1.0 → ppkit-0.2.0}/README.md +38 -14
  5. {ppkit-0.1.0 → ppkit-0.2.0}/bin/pp +4 -2
  6. {ppkit-0.1.0 → ppkit-0.2.0}/ci/check +2 -2
  7. {ppkit-0.1.0 → ppkit-0.2.0}/pp/__init__.py +1 -1
  8. {ppkit-0.1.0 → ppkit-0.2.0}/pp/cli.py +41 -15
  9. {ppkit-0.1.0 → ppkit-0.2.0}/pp/commands/batch.py +299 -31
  10. {ppkit-0.1.0 → ppkit-0.2.0}/pp/commands/cmd.py +64 -42
  11. ppkit-0.2.0/pp/commands/completion.py +66 -0
  12. {ppkit-0.1.0 → ppkit-0.2.0}/pp/commands/http.py +36 -12
  13. {ppkit-0.1.0 → ppkit-0.2.0}/pp/commands/kill.py +23 -3
  14. {ppkit-0.1.0 → ppkit-0.2.0}/pp/commands/promote.py +402 -77
  15. ppkit-0.2.0/pp/commands/queue.py +389 -0
  16. {ppkit-0.1.0 → ppkit-0.2.0}/pp/lib/config.py +0 -13
  17. ppkit-0.2.0/pp/lib/jobcore.py +908 -0
  18. ppkit-0.2.0/pp/lib/jobpolicy.py +1164 -0
  19. ppkit-0.2.0/pp/lib/jobq.py +2108 -0
  20. ppkit-0.2.0/pp/lib/jobstore.py +486 -0
  21. {ppkit-0.1.0 → ppkit-0.2.0}/pp/lib/paths.py +14 -8
  22. {ppkit-0.1.0 → ppkit-0.2.0}/pp/lib/ui.py +101 -8
  23. {ppkit-0.1.0 → ppkit-0.2.0}/pyproject.toml +2 -2
  24. {ppkit-0.1.0 → ppkit-0.2.0}/tests/journey.py +10 -1
  25. {ppkit-0.1.0 → ppkit-0.2.0}/tests/test_batch.py +200 -0
  26. {ppkit-0.1.0 → ppkit-0.2.0}/tests/test_cli.py +99 -0
  27. {ppkit-0.1.0 → ppkit-0.2.0}/tests/test_constructs.py +463 -172
  28. {ppkit-0.1.0 → ppkit-0.2.0}/tests/test_http.py +98 -0
  29. {ppkit-0.1.0 → ppkit-0.2.0}/tests/test_journey_batch.py +25 -0
  30. {ppkit-0.1.0 → ppkit-0.2.0}/tests/test_journey_cli.py +103 -2
  31. {ppkit-0.1.0 → ppkit-0.2.0}/tests/test_journey_cmd.py +77 -1
  32. {ppkit-0.1.0 → ppkit-0.2.0}/tests/test_journey_http.py +2 -0
  33. {ppkit-0.1.0 → ppkit-0.2.0}/tests/test_journey_promote.py +18 -0
  34. ppkit-0.2.0/tests/test_journey_queue.py +1402 -0
  35. {ppkit-0.1.0 → ppkit-0.2.0}/tests/test_kill.py +31 -0
  36. ppkit-0.2.0/tests/test_promote.py +1336 -0
  37. ppkit-0.2.0/tests/test_queue.py +5923 -0
  38. ppkit-0.2.0/tests/test_ui.py +319 -0
  39. ppkit-0.1.0/CHANGELOG.md +0 -20
  40. ppkit-0.1.0/pp/commands/completion.py +0 -55
  41. ppkit-0.1.0/pp/commands/queue.py +0 -243
  42. ppkit-0.1.0/pp/lib/jobq.py +0 -3106
  43. ppkit-0.1.0/tests/test_journey_queue.py +0 -637
  44. ppkit-0.1.0/tests/test_promote.py +0 -679
  45. ppkit-0.1.0/tests/test_queue.py +0 -3280
  46. ppkit-0.1.0/tests/test_ui.py +0 -107
  47. {ppkit-0.1.0 → ppkit-0.2.0}/.pp.toml +0 -0
  48. {ppkit-0.1.0 → ppkit-0.2.0}/LICENSE +0 -0
  49. {ppkit-0.1.0 → ppkit-0.2.0}/ci/package +0 -0
  50. {ppkit-0.1.0 → ppkit-0.2.0}/pp/__main__.py +0 -0
  51. {ppkit-0.1.0 → ppkit-0.2.0}/pp/commands/__init__.py +0 -0
  52. {ppkit-0.1.0 → ppkit-0.2.0}/pp/lib/__init__.py +0 -0
  53. {ppkit-0.1.0 → ppkit-0.2.0}/pp/lib/lock.py +0 -0
  54. {ppkit-0.1.0 → ppkit-0.2.0}/pp/lib/proc.py +0 -0
  55. {ppkit-0.1.0 → ppkit-0.2.0}/pp/lib/session.py +0 -0
  56. {ppkit-0.1.0 → ppkit-0.2.0}/tests/sandbox.py +0 -0
  57. {ppkit-0.1.0 → ppkit-0.2.0}/tests/test_cmd.py +0 -0
  58. {ppkit-0.1.0 → ppkit-0.2.0}/tests/test_journey_kill.py +0 -0
  59. {ppkit-0.1.0 → ppkit-0.2.0}/tests/test_lock.py +0 -0
  60. {ppkit-0.1.0 → ppkit-0.2.0}/tests/test_paths.py +0 -0
  61. {ppkit-0.1.0 → ppkit-0.2.0}/tests/test_proc.py +0 -0
  62. {ppkit-0.1.0 → ppkit-0.2.0}/tests/test_session.py +0 -0
@@ -0,0 +1,234 @@
1
+ # Changelog
2
+
3
+ ## v0.2.0
4
+
5
+ The interface this minor version keeps. `pp help queue` says what is promised
6
+ and what may change in a patch. `pp queue` stops what a container runner
7
+ started, takes a campaign from a file with one job's flags per line, and runs
8
+ a stage after the one it names.
9
+
10
+ `pp queue`: a job is named and selected the same way by every verb.
11
+
12
+ - A job added without `--tag` has no name. Views show its command, and a
13
+ selection by tag never matches it.
14
+ - A `--tag` is at most 64 characters of letters, digits and `.`, `_`, `-`
15
+ and `:`. It may not hold `..`, be `.` on its own, or read as an id or a
16
+ range, because a tag can be part of a log file's name.
17
+ - `counter` is not a queue name, beside `probes` and `watchers`: a queue of
18
+ that name took the place of the job id counter, and every later `add`
19
+ failed.
20
+ - Every verb that takes jobs takes the same five forms: an id, a range
21
+ (`12-15`), a tag matched whole, `--prefix P` (repeatable), and `--all`
22
+ with `-q NAME`. `--all` without `-q` is a usage error, and a tag or a
23
+ prefix that names jobs in two queues is an error that lists the ids.
24
+ `pp queue note` is the exception, and takes one job by its id or by a
25
+ tag that names one.
26
+ - `pp queue clean` takes a selection as well as `--older AGE`.
27
+
28
+ `pp queue`: a project says how its work is launched and stopped, so a kill
29
+ and a timeout reach work that is not in the job's process group.
30
+
31
+ - `[queue] stop` in a project's `.pp.toml`, and `--stop CMD` on `add`:
32
+ `"group"` where the runner's work stays in the job's own process group,
33
+ else a shell command that ends it. `pp queue add` is refused in a project
34
+ that sets `[queue] runner` and no stop, naming both spellings.
35
+ `--runner CMD` on `add` sets the runner for one job.
36
+ - In a project with a command stop, `pp queue kill` and a `--timeout` run
37
+ that command in place of the SIGTERM to the job's process group. The
38
+ grace, the SIGKILL and the sweep of the group are unchanged, so a project
39
+ with `stop = "group"` behaves as it did. The kill line says which ran, and
40
+ a stop command that failed is reported with its exit code.
41
+ - A job added with `--no-runner` carries `stop = "group"` whatever its
42
+ project says.
43
+ - The sweep that runs when a job's run is over also runs the project's stop
44
+ command, where there is one and the attempt wrote no exit code of its own,
45
+ and it takes the job's process group with it. So nothing the runner
46
+ started outlives the attempt that started it, and a leaked process no
47
+ longer holds a reservation the next job is told it may use.
48
+ - A job and its runner are given `PP_QUEUE_CONTAINER`, a name no other run
49
+ on the machine has, one per attempt, beginning `pp-<queue>-<id>-`; and
50
+ `PP_QUEUE_ENVFILE`, the path of a file of `KEY=VALUE` lines readable by
51
+ its owner alone. That file holds the project's `[queue] env`, the job's
52
+ `--env`, `CUDA_VISIBLE_DEVICES` where a card was reserved, `PP_QUEUE`,
53
+ `PP_QUEUE_JOB` and `PP_QUEUE_CONTAINER`, and nothing else of the
54
+ machine's: no `PATH`, `VIRTUAL_ENV`, `CONDA_PREFIX`, `CONDA_DEFAULT_ENV`
55
+ or `PP_QUEUE_RCFILE`. The record names it, and it goes with the record.
56
+ - An `--env` value, or a `[queue] env` value, holding a newline is refused,
57
+ and so is a name holding `=`, NUL or a newline.
58
+ - An exit code an attempt wrote while a stop of it was going out is that
59
+ stop's outcome, killed or timeout, with the code kept as `rc`.
60
+ - `pp queue kill --grace DUR` takes a duration and overrides the job's own
61
+ grace for the jobs that kill names.
62
+
63
+ `pp queue`: a job's output goes where the project says.
64
+
65
+ - `log` in a project's `[queue]`, and `--log PATH` on `add`: a path with
66
+ `{tag}` or `{id}` in it, relative to the project root or absolute. Any
67
+ other placeholder, a path holding `..`, and a relative path that leaves
68
+ the project root are refused. `{tag}` on a job with no tag is filled in
69
+ with the job's id.
70
+ - Every attempt appends to that one file, after a `[pp queue]` line naming
71
+ the job and its command, and several jobs may name one file. The record
72
+ names the file; `log`, `show`, a failure's tail and `PP_QUEUE_LOG` all
73
+ read it from there. `drop` and `clean` remove the files pp put in its own
74
+ store, and never a log at a path you named.
75
+ - `--retry-if` matches from the byte offset where the failed attempt's own
76
+ output begins, so it no longer matches an earlier attempt's lines or pp's
77
+ own line.
78
+
79
+ `pp queue`: a campaign file carries one job's flags per line, and a job can
80
+ wait for the jobs it names.
81
+
82
+ - A line of a `--from` file is the rest of an `add` command line: its own
83
+ flags, then `--`, then the command. A flag on the `add` is the default for
84
+ every line and a line's own flag wins over it; `--env` and `--need` merge
85
+ key by key. A line that is only a command behaves as before.
86
+ - A line is split into words the way a shell splits them, with quotes and
87
+ backslashes honoured. A line holding `|`, `&`, `;`, `<`, `>`, `(`, `)`,
88
+ `` ` `` or `$(` outside quotes is refused with its line number, and so is a
89
+ line with no `--` whose first word begins with `-`. A file one line of
90
+ which is refused queues nothing. A command that wants a shell operator
91
+ passes it quoted: `-- sh -c 'python x.py | grep -v noise'`.
92
+ - `--from FILE` together with a command on the same command line is a usage
93
+ error, and so is a line that names the queue, the file, `--hold` or
94
+ `--no-start`, which are the batch's and no line's.
95
+ - `--after SELECTION` on `add`: the job waits until every job that selection
96
+ named has ended, whatever the outcome. The value is one or more selections
97
+ separated by commas (`41,42`, `41-66`, `s1_eval,s1_report`), read as the
98
+ job is added and kept as ids on the record. An id no job has is a usage
99
+ error and nothing is queued. A job that is waiting is skipped rather than
100
+ waited for, so it never becomes the head of its queue, and a job whose
101
+ record is gone counts as ended.
102
+ - `pp queue retry` over a range, a tag, a prefix or `--all` shows a plan and
103
+ asks, as `kill` and `drop` do; `-n` shows the plan and `-y` does not ask.
104
+ A job that succeeded has ended, so it suits `retry`. The new job is owned
105
+ by the session that asked for it, and a priority is set with
106
+ `pp queue reprio` on the job the retry queued.
107
+ - A bare `pp queue wait` waits for every job that is queued or running
108
+ instead of returning at the first outcome. It names no held job; a
109
+ selection that names one waits for it and says so. `--timeout` is honoured
110
+ whatever `--poll` is, and ids together with `--idle` or `--mine` is a
111
+ usage error.
112
+ - A worker that adopts a running job takes the offset its output begins at
113
+ from the job's own run claim, so `--retry-if` reads that attempt's lines
114
+ and never pp's own line.
115
+
116
+ `pp queue`: every setting has one declaration, one default and one order of
117
+ its sources.
118
+
119
+ - Each setting of a job is a key of a project's `.pp.toml [queue]` as well
120
+ as of `[queue.NAME]`: `timeout`, `retries`, `retry_delay`, `retry_if`,
121
+ `on_fail`, `on_done`, `prio`, `gpu_gb`, `cpu`, `mem_gb`, `need`, `env`,
122
+ `log` and `grace`. The first source that gives a setting wins, in this
123
+ order: `add`'s flag, the project's `[queue]`, the queue's `[queue.NAME]`,
124
+ the built-in default. `env` and `need` merge key by key.
125
+ - `grace` is one setting of the job: `--grace DUR` on `add`, the `grace`
126
+ key, the resolved value on the record, and 10s by default. A timeout's
127
+ stop and a kill of that job both take it.
128
+ - Durations are read one way everywhere: `90s`, `30m`, `2h`, `1d`, a bare
129
+ number of seconds, `.5` and `1.5h`, and `none` where a setting offers it.
130
+ An empty value, a value that is not finite and one over 1e9 seconds are
131
+ refused, in a config key as on a command line.
132
+ - Numbers are bounded: `retries` at most 1000, `retry_delay` at most the
133
+ one-hour cap, a queue's `poll` longer than 0. A need that is 0, negative
134
+ or nan is refused.
135
+ - `pp queue work` takes no `--backfill` and no `--poll`: both are keys of
136
+ `[queue.NAME]`, which the queue's worker re-reads every loop, so they
137
+ change under running jobs with no restart.
138
+
139
+ `pp queue`: the store and the machine surface.
140
+
141
+ - A queue's `FORMAT` is 2. A pp that reads another format refuses the queue
142
+ where it opens it, naming the format the queue says and the one that pp
143
+ reads, rather than reading it.
144
+ - The `--json` field `gpu`, an integer, becomes `cards`, a list of card
145
+ indices; a job that reserved no card has `cards: []`.
146
+ - New `--json` fields: `stop_cmd`, `grace`, `after`, `container`, `envfile`,
147
+ `log_at`, and `cards`, `container`, `envfile` and `log_at` in each
148
+ `history` entry. `show --json` carries every promised field on a job in
149
+ each of the five states: a field the record does not hold yet is null, or
150
+ an empty list. The two exceptions are `after`, which is there only on a
151
+ job that names the jobs it waits for, and `stop`, only on a job asked to
152
+ stop.
153
+ - `pp help queue` is one page: a first job, a project whose work runs in a
154
+ container in four steps, names and selection, campaign files, the settings
155
+ table, how a job ends, workers, and what is left to you.
156
+
157
+ `pp kill`: `--grace DUR` takes a duration in the spelling every duration pp
158
+ takes has. A bare number is still seconds.
159
+
160
+ `pp promote`, `pp cmd` and `pp http`: plans under `-n`, help by flag, and
161
+ links one target holds.
162
+
163
+ - `pp -h NAME` and `pp --help NAME` print that command's help, as
164
+ `pp help NAME` does. `pp -h NAME VERB` prints the verb's.
165
+ - `pp promote rollback`, `pp promote link`, `pp promote init` and
166
+ `pp cmd new` take `-n`: each one shows its plan and changes nothing.
167
+ - `pp promote` does not take over a link path another target holds: the
168
+ refusal names that target, and `--force-links` moves the link.
169
+ - `pp promote status --json` has `failed`: the current release's
170
+ activation failed, or nothing gated it.
171
+ - `pp promote diff --json` has `source_gone`. A target whose project
172
+ directory is gone exits 1 there, in place of git's error.
173
+ - `pp cmd ls --json` has `aliases`, each command's short names.
174
+ - An unknown key in `[[http.roots]]` or `[[http.discover]]` is a config
175
+ error naming the file and the key, so a misspelt `marker` stops the
176
+ server instead of serving every folder under `under`.
177
+ - `pp help http` documents every `[http]` key with its default.
178
+
179
+ ## v0.1.1
180
+
181
+ Fixes. A few command lines and config values that did harm are refused.
182
+
183
+ - `pp batch run` refuses a file-name field where a shell would read the
184
+ name again as code: inside `$( )`, backticks, `${ }`, arithmetic, a
185
+ `[[ ]]` test, a case pattern, a comment, `eval`, `trap`, a shell's `-c`
186
+ script, or a template with a heredoc or a function. The message names the
187
+ spelling that works: `sh -c 'cmd "$1"' sh {}`. Its plan lists every
188
+ command.
189
+ - A `pp batch rename` that is interrupted leaves a journal of what landed,
190
+ and `pp batch undo` takes that journal and says so.
191
+ - `pp promote WORD` releases nothing when the word is neither a verb nor a
192
+ path. It used to release the project in the current directory.
193
+ - A wrong value in `[promote]` is refused, naming the file and the key.
194
+ `name` must be one path component: `name = ".."` let `gc` remove every
195
+ release and plugin.
196
+ - `pp promote` keeps the release it flips away from, so the one you rolled
197
+ back to is still there after the next promote.
198
+ - A socket or fifo in a tree project no longer hangs `pp promote`, and a
199
+ file pp cannot read is a blocker that offers `exclude`.
200
+ - A hook that leaves a process running no longer holds `pp promote`. Each
201
+ hook's output is a file beside the run's log.
202
+ - A release that `pp promote gc` cannot remove stays whole, and gc exits 1.
203
+ - `pp queue`: a job that cannot start fails on its own and the rest start.
204
+ A job whose wrapper is still ending reads running, not lost.
205
+ - `pp queue --json` and `pp queue show --json` give a job's variables by
206
+ name, with each value `***`.
207
+ - An empty duration such as `--timeout ""` is a usage error, and so is an
208
+ empty `pp kill` target, which selected every process you own.
209
+ - `pp http` never serves `.git`, serves a file whose name is not UTF-8, and
210
+ sorts by name when `?sort=` names anything else.
211
+ - pp's messages and the `pp cmd add` and `update` screens escape the text
212
+ they name. TAB completion runs nothing in a command's name: run
213
+ `pp completion bash` or `zsh` again to replace a saved script.
214
+ - A command file whose name is not UTF-8 no longer breaks `pp -h`.
215
+ - pp needs Python 3.11.4 or later, which `pp promote` always needed.
216
+
217
+ ## v0.1.0
218
+
219
+ The first release.
220
+
221
+ - `pp queue` runs commands later, in named queues, when the GPU memory,
222
+ CPUs, RAM or counters they ask for are free. Jobs have timeouts, retries
223
+ for the failures you name, and hooks. Tags and ranges select groups of
224
+ jobs. A project's `.pp.toml` can run its jobs through a runner, such as a
225
+ container.
226
+ - `pp queue wait`, `watch` and `wait --mine` tell a person or an agent how
227
+ jobs ended, with the end of the log for a failure.
228
+ - `pp promote` releases a project from a git commit when its check passes
229
+ on that copy, and rolls back.
230
+ - `pp kill`, `pp batch` and `pp http` stop processes, rename or convert
231
+ files, and show project folders in a browser.
232
+ - `pp cmd` writes commands, and adds other people's from git, pinned to the
233
+ commit you read.
234
+ - Completion for zsh and bash.
@@ -28,19 +28,19 @@ If you installed pp as a tool, `uv tool uninstall ppkit` (or
28
28
 
29
29
  ## Building and testing
30
30
 
31
- You need Python 3.11 or later, git, sh and bash. The tests that need zsh
31
+ You need Python 3.11.4 or later, git, sh and bash. The tests that need zsh
32
32
  skip without it. `ci/check` uses [uv](https://docs.astral.sh/uv/)
33
33
  for the linter and for other Python versions.
34
34
 
35
35
  ```
36
36
  ci/check the linter, then every test, on python3
37
- ci/check 3.11 the same on Python 3.11, which uv installs the first time
38
- ci/check --cpus 2 3.11 the same on 2 CPUs, like a CI runner
37
+ ci/check 3.11.4 the same on Python 3.11.4, which uv installs the first time
38
+ ci/check --cpus 2 3.11.4 the same on 2 CPUs, like a CI runner
39
39
  python3 -m unittest discover -s tests the tests alone
40
40
  ci/package build ppkit into dist/, install it fresh, and try it
41
41
  ```
42
42
 
43
- CI runs `ci/check 3.11`, `ci/check 3.14` and `ci/package` on every push to
43
+ CI runs `ci/check 3.11.4`, `ci/check 3.14` and `ci/package` on every push to
44
44
  main and every pull request. The whole suite takes a few minutes. Most of
45
45
  that is the journeys (`tests/test_journey_*.py`), which run the real
46
46
  `bin/pp` once per step in a throwaway home directory.
@@ -55,7 +55,7 @@ on a developer's machine takes tens of milliseconds, and a runner's few
55
55
  hundred take a few. The test that needs another user's process is skipped
56
56
  there.
57
57
 
58
- A uv-managed CPython has no `os.pidfd_open`, so `ci/check 3.11`, `ci/check
58
+ A uv-managed CPython has no `os.pidfd_open`, so `ci/check 3.11.4`, `ci/check
59
59
  3.14` and CI run `pp/lib/proc.py`'s ctypes syscall path, while the distro
60
60
  `python3` of a plain `ci/check` runs the wrapped calls. Both are tested only
61
61
  by running both.
@@ -149,6 +149,13 @@ These rules keep pp working as commands come and go:
149
149
  command, two workers, a signal and a write) go through a construct that
150
150
  is written once. "The constructs" below is the list of them, and a check
151
151
  added at one call site is not a fix.
152
+ - A test checks a contract, not a wording. Every sentence pp prints may be
153
+ reworded in a patch, so a test asserts an exit status, a `--json` field, a
154
+ record field, a file's existence or its mode, an id, or a state, and nothing
155
+ else. Where a test has to know that pp said something, it asserts the field
156
+ the sentence was made from. Two exceptions are written down where they are
157
+ made: a journey types the examples of `pp help NAME`, and one journey step
158
+ holds the headings and the line width of `pp help queue`.
152
159
  - A test of a construct checks what the construct promises. A test that
153
160
  runs a second actor at an exact step keeps one fix from coming back
154
161
  (`TestTwoAtOnce` in `tests/test_queue.py`). That step goes through
@@ -185,8 +192,11 @@ list of the parts:
185
192
 
186
193
  - `pp/cli.py`: finding commands, the order of their origins, help and
187
194
  completion.
188
- - `pp/lib/jobq.py`: queues, workers, resources, and the files a job lives
189
- in; `pp help queue` is the user's view of the same.
195
+ - `pp/lib/jobstore.py`: a job's record, the files it lives in, and the
196
+ transaction that changes one. `pp/lib/jobcore.py`: the worker, the claims
197
+ and the processes of a queue. `pp/lib/jobpolicy.py`: every rule of a queue
198
+ as a value, so each one is a table. `pp/lib/jobq.py`: the verbs, the views,
199
+ the resources and the CLI; `pp help queue` is the user's view of the same.
190
200
  - `pp/commands/promote.py`: releases. `pp/commands/cmd.py`: plugins.
191
201
  `pp/commands/http.py`: serving a folder to a browser, with its cache and
192
202
  its server. `pp/commands/batch.py`: bulk file changes, and the journal
@@ -214,9 +224,9 @@ this table, and each rule's own docstring is its contract.
214
224
  | `alive` | judging a process alive by its pid | a claim's holder, or `Handle` | `pp/lib/proc.py` |
215
225
  | `lock` | taking a lock | `locked`, and `deferring` for a scope with no lock | `pp/lib/lock.py` |
216
226
  | `claim` | opening a claim's file | `Claim` | `pp/lib/lock.py` |
217
- | `job` | reading or writing a job's file | `txn`, `listing`, `find` | `pp/lib/jobq.py` |
218
- | `worker-claim` | taking a queue's worker claim, or reading its holder | `worker`, `worker_now`, `work` | `pp/lib/jobq.py` |
219
- | `job-process` | signalling or reaping a job's processes | the `Worker` class, `cmd_worker`, `work` | `pp/lib/jobq.py`, `pp/lib/proc.py`, `pp/commands/kill.py` |
227
+ | `job` | reading or writing a job's file | `txn`, `listing`, `find` | `pp/lib/jobstore.py` |
228
+ | `worker-claim` | taking a queue's worker claim, or reading its holder | `worker`, `worker_now`, `work` | `pp/lib/jobcore.py` |
229
+ | `job-process` | signalling or reaping a job's processes | the `Worker` class, `worker_stop`, `work` | `pp/lib/jobcore.py`, `pp/lib/proc.py`, `pp/commands/kill.py` |
220
230
  | `by-name` | deciding on a served file by its name | act on the open file | `pp/commands/http.py` |
221
231
  | `sleep` | sleeping in a test | `until` | `tests/journey.py` |
222
232
  | `once` | a once-flag of a test's own | `Gate` | `tests/journey.py` |
@@ -246,15 +256,66 @@ replacing a whole file in one step goes through `paths.write_json`.
246
256
  `tests/test_cmd.py` and `ci/package` keep the installed case working, and
247
257
  the journeys in `tests/test_journey_cmd.py` the other two.
248
258
 
259
+ ### The queue's four modules, and what may go where
260
+
261
+ The line between them is mechanism against policy, and a change goes to the
262
+ module whose job it is:
263
+
264
+ - `pp/lib/jobstore.py` is the record store and its transaction: one lock, no
265
+ timing, no process, no signal. A field a record gains and a write a verb
266
+ hands over go here.
267
+ - `pp/lib/jobcore.py` is the worker and the claims: starting, stopping,
268
+ reaping and adopting a job's processes, the worker's loop, and the watchers.
269
+ A bug here loses a job, which is why it is the module held still.
270
+ - `pp/lib/jobpolicy.py` is every rule that is values in and one value out: how
271
+ a job ends, what a selection names, what a setting comes to, what a job's
272
+ environment is. A rule goes here, and a table tests it.
273
+ - `pp/lib/jobq.py` is the verbs, the views, the readings, the one settings
274
+ declaration and the CLI, and `pp/commands/queue.py` is the command and its
275
+ help. Every sentence a user reads is worded here.
276
+
277
+ The store and the worker together are the core. These rules hold that line,
278
+ and `tests/test_constructs.py` reads the source for each:
279
+
280
+ - The boundary rule. Outside the core no function takes the store lock, and
281
+ none starts a job, a runner, a stop command, a hook or a worker, or signals
282
+ a job, a worker or a process group: a verb goes through the core's API, where
283
+ every write is one call and one whole transaction. Two sites outside it run a
284
+ process of their own, and both are named exemptions: `reading`, which runs a
285
+ user's probe under that probe's own claim, and `_cards`, which runs
286
+ nvidia-smi.
287
+ - The purity rule. A function of `jobpolicy.py` reaches no clock, no store, no
288
+ process, no signal and no file. It is handed records, readings, settings and
289
+ `now`, and gives back a value, so a rule that could be raced is a rule no
290
+ table tests.
291
+ - The import rule. `jobpolicy.py` imports nothing of pp's, the store imports
292
+ nothing of the worker, and neither imports the verbs above them. The
293
+ direction is a line, so no cycle can hide a rule that reads a mechanism.
294
+ - The core reads no config file and takes no reading of its own: `read_settings`
295
+ hands a worker the settings of one pass of its loop, and `admit_for` the
296
+ readings of one launch pass. It writes no sentence either: it tells a sink
297
+ what it did, and `jobq` words every one of them.
298
+ - The budgets, in code lines that are neither blank, comment nor docstring:
299
+ 250 for the store, 490 for the worker, 730 for the two together. A feature
300
+ that cannot be built without going over one of them waits until something
301
+ leaves the core, and moving a rule out into `jobpolicy.py` is the first thing
302
+ to try.
303
+ - Every function of the store and of the worker is named in one of the two
304
+ lists in that file, so a construct is placed when it lands, and a name that
305
+ has gone comes off the list in the same change.
306
+
249
307
  ### Where a queue's jobs live, and how one says it is alive
250
308
 
251
- The docstring of `pp/lib/jobq.py` is the reference: the files a queue is made
252
- of, how a worker and a job say they are alive, and the one actor that ends a
253
- job. Each mechanism inside it has one owner, and these are they:
309
+ The docstring of `pp/lib/jobstore.py` is the reference for the files a queue
310
+ is made of, and `pp/lib/jobcore.py`'s for how a worker and a job say they are
311
+ alive and which actor ends a job. Each mechanism has one owner, and these are
312
+ they:
254
313
 
255
- - the store: `Txn`, `txn`, `listing` and `find`. `open_queue` is where a
256
- queue's `FORMAT` is checked, and `Txn.add` is where an id and the record
257
- that holds it land together.
314
+ - the store: `Txn`, `txn`, `listing` and `find`, in `pp/lib/jobstore.py`.
315
+ `open_queue` is where a queue's `FORMAT` is checked, and `Txn.add` is where
316
+ an id and the record that holds it land together. The writes a verb hands
317
+ over are the four at the end of that file, each one a whole transaction of
318
+ its own that is told the states it may act on.
258
319
  - being alive: `lock.Claim`. A claim is held for as long as its holder lives,
259
320
  so nothing is cleaned up after a holder that was killed and no record is
260
321
  ever read as a live holder. A queue's worker holds one on `worker.lock`,
@@ -262,9 +323,13 @@ job. Each mechanism inside it has one owner, and these are they:
262
323
  - whether a queue's worker is up: the block comment above `worker_up`, for
263
324
  the two things that can be true of that claim and the first moment that is
264
325
  neither.
265
- - how a job ends: `decide` and `merge_stop`, which are pure, and
266
- `Worker.carry_out`, the one pass that gathers the facts and carries the
267
- action out.
326
+ - how a job ends: `decide` and `merge_stop` in `pp/lib/jobpolicy.py`, which
327
+ are pure, as every rule in that module is, and `Worker.carry_out`, the one
328
+ pass that gathers the facts and carries the action out.
329
+ - what a worker is handed: it reads no config file and takes no reading of
330
+ its own, so `read_settings` gives it the settings of one pass of its loop
331
+ and `admit_for` the readings of one launch pass. It writes no sentence
332
+ either: it tells a sink what it did, and `jobq` words every one of them.
268
333
  - what a stop carries: `public()`, where every field of a job record that
269
334
  others rely on is named.
270
335
 
@@ -281,9 +346,10 @@ the same place.
281
346
  A kill, pass by pass:
282
347
 
283
348
  ```
284
- kill: txn(merge_stop) -> worker_up(wake=True) -> SIGUSR1 to the worker
285
- worker: carry_out: the facts -> decide = TERM -> signal the group,
286
- then txn marks `sent` -> (the grace) -> decide = KILL9 -> signal
349
+ kill: ask_stop(merge_stop) -> worker_up(wake=True) -> SIGUSR1 to the worker
350
+ worker: carry_out: the facts -> decide = TERM -> the project's stop command,
351
+ or the SIGTERM to the group, then txn marks `sent`
352
+ -> (the grace) -> decide = KILL9 -> signal
287
353
  -> the wrapper ends, so the job's run claim is free
288
354
  -> decide = ENDED_AS -> _finish records the outcome and the hook it owes
289
355
  -> fire
@@ -297,9 +363,11 @@ own exit time and not the queue's poll period.
297
363
 
298
364
  The rules the worker follows are the ones `pp help queue` states under "How a
299
365
  job ends, whatever reached it", and that help text is their one statement.
300
- One more is for contributors: which of the two a stop is comes from the
366
+ Two more are for contributors: which of the two a stop is comes from the
301
367
  `timeout` field a worker writes on the stop it wrote itself, never from the
302
- name of whoever asked.
368
+ name of whoever asked; and an exit code the attempt wrote after its stop went
369
+ out is that stop's outcome, with the code kept, since a container runner writes
370
+ the code of a container the stop command ended.
303
371
 
304
372
  `TestTheDecision` in `tests/test_queue.py` is a table over every combination
305
373
  of those facts. Each row carries the rule from that help as its own label,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ppkit
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: A CLI kit for user & agents
5
5
  Keywords: cli,job queue,gpu,agents,toolkit
6
6
  Author: The pp Authors
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3
14
14
  Classifier: Programming Language :: Python :: 3 :: Only
15
15
  Classifier: Topic :: System :: Shells
16
16
  Classifier: Topic :: Utilities
17
- Requires-Python: >=3.11
17
+ Requires-Python: >=3.11.4
18
18
  Project-URL: Changelog, https://github.com/xflash96/pp/blob/main/CHANGELOG.md
19
19
  Project-URL: Homepage, https://github.com/xflash96/pp
20
20
  Project-URL: Issues, https://github.com/xflash96/pp/issues
@@ -85,7 +85,7 @@ shell that added it.
85
85
 
86
86
  ## Install
87
87
 
88
- pp runs on Linux, with Python 3.11 or later. Install it as a tool, in an
88
+ pp runs on Linux, with Python 3.11.4 or later. Install it as a tool, in an
89
89
  environment of its own:
90
90
 
91
91
  ```
@@ -132,8 +132,8 @@ pp queue add -q dl --need net=1 -- ./fetch.sh the dl queue, taking on
132
132
  pp queue add --timeout 2h -- python x.py stopped after two hours
133
133
  pp queue add --retries 2 --retry-if 'Connection reset' -- ./sync.sh
134
134
  pp queue add --on-fail 'notify-send "job $PP_QUEUE_JOB failed"' -- ./nightly.sh
135
- pp queue add --tag sweep -- python x.py --lr 0.1 tagged, to act on the group later
136
- pp queue wait sweep every job tagged sweep
135
+ pp queue add --tag sweep.lr1 -- python x.py --lr 0.1 a name for the job, and for its family
136
+ pp queue wait --prefix sweep. every job whose name starts with sweep.
137
137
  pp queue watch 12 a line for each start, retry and outcome
138
138
  pp queue drop 20-29 a range: it shows the jobs and asks first
139
139
  pp queue kill 12 waits while the worker stops it, then:
@@ -141,6 +141,11 @@ pp queue kill 12 waits while the worker
141
141
  pp queue retry 11 pp queue hold 15 pp queue release 15
142
142
  ```
143
143
 
144
+ Every verb that takes jobs takes the same five forms: an id, a range of ids,
145
+ a `--tag` name matched whole, `--prefix P`, and `--all` with `-q NAME`. `pp
146
+ queue note` is the exception, and takes one job by its id or by a tag that
147
+ names one. A job added without `--tag` has no name, and its id selects it.
148
+
144
149
  Limits go in `~/.config/pp/config.toml`. Every queue draws on the shared
145
150
  GPU memory, CPUs and RAM; a queue's own pool caps what it may take of them.
146
151
  A counter is a number, or a command that prints how much is free:
@@ -158,21 +163,36 @@ retry_if = "Connection reset|503"
158
163
  net = 2
159
164
  ```
160
165
 
161
- A project's `.pp.toml` can wrap every job queued from it, for example in a
162
- container, and add to the jobs' environment:
166
+ A project's `.pp.toml` says how its jobs are launched and stopped, where
167
+ their output goes, and what is in their environment:
163
168
 
164
169
  ```toml
165
170
  [queue]
166
- runner = "docker/run.sh" # the job runs as docker/run.sh CMD...
171
+ runner = "docker/run.sh" # the job runs as docker/run.sh CMD...
172
+ stop = "docker rm -f $PP_QUEUE_CONTAINER" # how a stop reaches the work the runner started
173
+ log = "logs/{tag}.log" # under the project root; {tag} or {id}
167
174
  env = { HF_HUB_OFFLINE = "1" }
168
175
  ```
169
176
 
170
- `pp queue add --no-runner` skips the runner for one job. The runner gets the
171
- job's environment, including `CUDA_VISIBLE_DEVICES`, and a container runner
172
- has to pass it in. Give a job a token or a secret with `--env` or the
173
- project's `env`: a variable exported in your shell reaches it only through
174
- the worker. `pp help queue` has the examples, and `pp help queue add` every
175
- option of `add`.
177
+ A runner puts the work where no signal of pp's reaches it, so a project that
178
+ sets one says the stop as well, or the `add` is refused. pp hands the runner
179
+ `PP_QUEUE_CONTAINER`, a name for whatever it starts elsewhere, and
180
+ `PP_QUEUE_ENVFILE`, a file of `KEY=VALUE` lines for a container to read with
181
+ `--env-file`. `pp queue add --no-runner` skips the runner for one job, and
182
+ `--runner CMD` with `--stop CMD` gives one job both with no `.pp.toml`.
183
+
184
+ A campaign is a file of one job per line, each line with its own flags, and a
185
+ stage can wait for the one before it:
186
+
187
+ ```
188
+ IDS=$(pp queue add --from stage1.txt) # one id per line on stdout
189
+ pp queue add --from stage2.txt --after "$(echo "$IDS" | tr '\n' ',')"
190
+ ```
191
+
192
+ Give a job a token or a secret with `--env` or the project's `env`: a variable
193
+ exported in your shell reaches it only through the worker. `--json` gives a
194
+ job's variables by name, with each value `***`. `pp help queue` is the whole
195
+ page, with the runner to copy, and `pp help queue add` every option of `add`.
176
196
 
177
197
  ## Working with agents
178
198
 
@@ -231,6 +251,10 @@ pp promote rollback notes back to the release before
231
251
  database migration that cannot be undone does not belong in it. pp keeps the
232
252
  last five releases, and always keeps the current one and the one before.
233
253
 
254
+ Each hook's output goes to a file beside the run's log, and pp shows it as it
255
+ comes. pp waits for the hook itself, not for what the hook leaves running, so
256
+ redirect the output of a process an `after` starts.
257
+
234
258
  A systemd service runs the release by pointing at `current`:
235
259
 
236
260
  ```
@@ -262,7 +286,7 @@ pp http ~/notes . a tab for each folder, at http://127.0.0.1:8000
262
286
  ```
263
287
 
264
288
  `pp http` listens on 127.0.0.1 unless you pass `--bind`, and does not serve
265
- files such as `.env` or private keys.
289
+ `.git` or files such as `.env` or private keys.
266
290
 
267
291
  ## Your own commands
268
292
 
@@ -314,7 +338,7 @@ too. To add a command to pp itself, see [CONTRIBUTING.md](https://github.com/xfl
314
338
  pp is new, and its version is 0.x. A command or a config key may still
315
339
  change, and so may a field of `--json` output; the
316
340
  [changelog](https://github.com/xflash96/pp/blob/main/CHANGELOG.md) says when one does. CI runs the tests on
317
- Python 3.11 and 3.14, on Ubuntu 24.04.
341
+ Python 3.11.4 and 3.14, on Ubuntu 24.04.
318
342
 
319
343
  ## Contributing
320
344
 
@@ -63,7 +63,7 @@ shell that added it.
63
63
 
64
64
  ## Install
65
65
 
66
- pp runs on Linux, with Python 3.11 or later. Install it as a tool, in an
66
+ pp runs on Linux, with Python 3.11.4 or later. Install it as a tool, in an
67
67
  environment of its own:
68
68
 
69
69
  ```
@@ -110,8 +110,8 @@ pp queue add -q dl --need net=1 -- ./fetch.sh the dl queue, taking on
110
110
  pp queue add --timeout 2h -- python x.py stopped after two hours
111
111
  pp queue add --retries 2 --retry-if 'Connection reset' -- ./sync.sh
112
112
  pp queue add --on-fail 'notify-send "job $PP_QUEUE_JOB failed"' -- ./nightly.sh
113
- pp queue add --tag sweep -- python x.py --lr 0.1 tagged, to act on the group later
114
- pp queue wait sweep every job tagged sweep
113
+ pp queue add --tag sweep.lr1 -- python x.py --lr 0.1 a name for the job, and for its family
114
+ pp queue wait --prefix sweep. every job whose name starts with sweep.
115
115
  pp queue watch 12 a line for each start, retry and outcome
116
116
  pp queue drop 20-29 a range: it shows the jobs and asks first
117
117
  pp queue kill 12 waits while the worker stops it, then:
@@ -119,6 +119,11 @@ pp queue kill 12 waits while the worker
119
119
  pp queue retry 11 pp queue hold 15 pp queue release 15
120
120
  ```
121
121
 
122
+ Every verb that takes jobs takes the same five forms: an id, a range of ids,
123
+ a `--tag` name matched whole, `--prefix P`, and `--all` with `-q NAME`. `pp
124
+ queue note` is the exception, and takes one job by its id or by a tag that
125
+ names one. A job added without `--tag` has no name, and its id selects it.
126
+
122
127
  Limits go in `~/.config/pp/config.toml`. Every queue draws on the shared
123
128
  GPU memory, CPUs and RAM; a queue's own pool caps what it may take of them.
124
129
  A counter is a number, or a command that prints how much is free:
@@ -136,21 +141,36 @@ retry_if = "Connection reset|503"
136
141
  net = 2
137
142
  ```
138
143
 
139
- A project's `.pp.toml` can wrap every job queued from it, for example in a
140
- container, and add to the jobs' environment:
144
+ A project's `.pp.toml` says how its jobs are launched and stopped, where
145
+ their output goes, and what is in their environment:
141
146
 
142
147
  ```toml
143
148
  [queue]
144
- runner = "docker/run.sh" # the job runs as docker/run.sh CMD...
149
+ runner = "docker/run.sh" # the job runs as docker/run.sh CMD...
150
+ stop = "docker rm -f $PP_QUEUE_CONTAINER" # how a stop reaches the work the runner started
151
+ log = "logs/{tag}.log" # under the project root; {tag} or {id}
145
152
  env = { HF_HUB_OFFLINE = "1" }
146
153
  ```
147
154
 
148
- `pp queue add --no-runner` skips the runner for one job. The runner gets the
149
- job's environment, including `CUDA_VISIBLE_DEVICES`, and a container runner
150
- has to pass it in. Give a job a token or a secret with `--env` or the
151
- project's `env`: a variable exported in your shell reaches it only through
152
- the worker. `pp help queue` has the examples, and `pp help queue add` every
153
- option of `add`.
155
+ A runner puts the work where no signal of pp's reaches it, so a project that
156
+ sets one says the stop as well, or the `add` is refused. pp hands the runner
157
+ `PP_QUEUE_CONTAINER`, a name for whatever it starts elsewhere, and
158
+ `PP_QUEUE_ENVFILE`, a file of `KEY=VALUE` lines for a container to read with
159
+ `--env-file`. `pp queue add --no-runner` skips the runner for one job, and
160
+ `--runner CMD` with `--stop CMD` gives one job both with no `.pp.toml`.
161
+
162
+ A campaign is a file of one job per line, each line with its own flags, and a
163
+ stage can wait for the one before it:
164
+
165
+ ```
166
+ IDS=$(pp queue add --from stage1.txt) # one id per line on stdout
167
+ pp queue add --from stage2.txt --after "$(echo "$IDS" | tr '\n' ',')"
168
+ ```
169
+
170
+ Give a job a token or a secret with `--env` or the project's `env`: a variable
171
+ exported in your shell reaches it only through the worker. `--json` gives a
172
+ job's variables by name, with each value `***`. `pp help queue` is the whole
173
+ page, with the runner to copy, and `pp help queue add` every option of `add`.
154
174
 
155
175
  ## Working with agents
156
176
 
@@ -209,6 +229,10 @@ pp promote rollback notes back to the release before
209
229
  database migration that cannot be undone does not belong in it. pp keeps the
210
230
  last five releases, and always keeps the current one and the one before.
211
231
 
232
+ Each hook's output goes to a file beside the run's log, and pp shows it as it
233
+ comes. pp waits for the hook itself, not for what the hook leaves running, so
234
+ redirect the output of a process an `after` starts.
235
+
212
236
  A systemd service runs the release by pointing at `current`:
213
237
 
214
238
  ```
@@ -240,7 +264,7 @@ pp http ~/notes . a tab for each folder, at http://127.0.0.1:8000
240
264
  ```
241
265
 
242
266
  `pp http` listens on 127.0.0.1 unless you pass `--bind`, and does not serve
243
- files such as `.env` or private keys.
267
+ `.git` or files such as `.env` or private keys.
244
268
 
245
269
  ## Your own commands
246
270
 
@@ -292,7 +316,7 @@ too. To add a command to pp itself, see [CONTRIBUTING.md](https://github.com/xfl
292
316
  pp is new, and its version is 0.x. A command or a config key may still
293
317
  change, and so may a field of `--json` output; the
294
318
  [changelog](https://github.com/xflash96/pp/blob/main/CHANGELOG.md) says when one does. CI runs the tests on
295
- Python 3.11 and 3.14, on Ubuntu 24.04.
319
+ Python 3.11.4 and 3.14, on Ubuntu 24.04.
296
320
 
297
321
  ## Contributing
298
322