zppy 0.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.
- zppy-0.1.0/LICENSE +121 -0
- zppy-0.1.0/MANIFEST.in +2 -0
- zppy-0.1.0/PKG-INFO +359 -0
- zppy-0.1.0/README.md +339 -0
- zppy-0.1.0/examples/mock.py +123 -0
- zppy-0.1.0/pyproject.toml +37 -0
- zppy-0.1.0/setup.cfg +4 -0
- zppy-0.1.0/src/zppy/__init__.py +5 -0
- zppy-0.1.0/src/zppy/progress.py +587 -0
- zppy-0.1.0/src/zppy.egg-info/PKG-INFO +359 -0
- zppy-0.1.0/src/zppy.egg-info/SOURCES.txt +18 -0
- zppy-0.1.0/src/zppy.egg-info/dependency_links.txt +1 -0
- zppy-0.1.0/src/zppy.egg-info/top_level.txt +1 -0
- zppy-0.1.0/tests/__init__.py +1 -0
- zppy-0.1.0/tests/process_helpers.py +27 -0
- zppy-0.1.0/tests/test_demo.py +131 -0
- zppy-0.1.0/tests/test_fork.py +487 -0
- zppy-0.1.0/tests/test_progress.py +388 -0
- zppy-0.1.0/tests/test_subprocess.py +591 -0
- zppy-0.1.0/tests/test_zpg.py +270 -0
zppy-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
Creative Commons Legal Code
|
|
2
|
+
|
|
3
|
+
CC0 1.0 Universal
|
|
4
|
+
|
|
5
|
+
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
|
6
|
+
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
|
7
|
+
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
|
8
|
+
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
|
9
|
+
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
|
10
|
+
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
|
11
|
+
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
|
12
|
+
HEREUNDER.
|
|
13
|
+
|
|
14
|
+
Statement of Purpose
|
|
15
|
+
|
|
16
|
+
The laws of most jurisdictions throughout the world automatically confer
|
|
17
|
+
exclusive Copyright and Related Rights (defined below) upon the creator
|
|
18
|
+
and subsequent owner(s) (each and all, an "owner") of an original work of
|
|
19
|
+
authorship and/or a database (each, a "Work").
|
|
20
|
+
|
|
21
|
+
Certain owners wish to permanently relinquish those rights to a Work for
|
|
22
|
+
the purpose of contributing to a commons of creative, cultural and
|
|
23
|
+
scientific works ("Commons") that the public can reliably and without fear
|
|
24
|
+
of later claims of infringement build upon, modify, incorporate in other
|
|
25
|
+
works, reuse and redistribute as freely as possible in any form whatsoever
|
|
26
|
+
and for any purposes, including without limitation commercial purposes.
|
|
27
|
+
These owners may contribute to the Commons to promote the ideal of a free
|
|
28
|
+
culture and the further production of creative, cultural and scientific
|
|
29
|
+
works, or to gain reputation or greater distribution for their Work in
|
|
30
|
+
part through the use and efforts of others.
|
|
31
|
+
|
|
32
|
+
For these and/or other purposes and motivations, and without any
|
|
33
|
+
expectation of additional consideration or compensation, the person
|
|
34
|
+
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
|
35
|
+
is an owner of Copyright and Related Rights in the Work, voluntarily
|
|
36
|
+
elects to apply CC0 to the Work and publicly distribute the Work under its
|
|
37
|
+
terms, with knowledge of his or her Copyright and Related Rights in the
|
|
38
|
+
Work and the meaning and intended legal effect of CC0 on those rights.
|
|
39
|
+
|
|
40
|
+
1. Copyright and Related Rights. A Work made available under CC0 may be
|
|
41
|
+
protected by copyright and related or neighboring rights ("Copyright and
|
|
42
|
+
Related Rights"). Copyright and Related Rights include, but are not
|
|
43
|
+
limited to, the following:
|
|
44
|
+
|
|
45
|
+
i. the right to reproduce, adapt, distribute, perform, display,
|
|
46
|
+
communicate, and translate a Work;
|
|
47
|
+
ii. moral rights retained by the original author(s) and/or performer(s);
|
|
48
|
+
iii. publicity and privacy rights pertaining to a person's image or
|
|
49
|
+
likeness depicted in a Work;
|
|
50
|
+
iv. rights protecting against unfair competition in regards to a Work,
|
|
51
|
+
subject to the limitations in paragraph 4(a), below;
|
|
52
|
+
v. rights protecting the extraction, dissemination, use and reuse of data
|
|
53
|
+
in a Work;
|
|
54
|
+
vi. database rights (such as those arising under Directive 96/9/EC of the
|
|
55
|
+
European Parliament and of the Council of 11 March 1996 on the legal
|
|
56
|
+
protection of databases, and under any national implementation
|
|
57
|
+
thereof, including any amended or successor version of such
|
|
58
|
+
directive); and
|
|
59
|
+
vii. other similar, equivalent or corresponding rights throughout the
|
|
60
|
+
world based on applicable law or treaty, and any national
|
|
61
|
+
implementations thereof.
|
|
62
|
+
|
|
63
|
+
2. Waiver. To the greatest extent permitted by, but not in contravention
|
|
64
|
+
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
|
65
|
+
irrevocably and unconditionally waives, abandons, and surrenders all of
|
|
66
|
+
Affirmer's Copyright and Related Rights and associated claims and causes
|
|
67
|
+
of action, whether now known or unknown (including existing as well as
|
|
68
|
+
future claims and causes of action), in the Work (i) in all territories
|
|
69
|
+
worldwide, (ii) for the maximum duration provided by applicable law or
|
|
70
|
+
treaty (including future time extensions), (iii) in any current or future
|
|
71
|
+
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
|
72
|
+
including without limitation commercial, advertising or promotional
|
|
73
|
+
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
|
74
|
+
member of the public at large and to the detriment of Affirmer's heirs and
|
|
75
|
+
successors, fully intending that such Waiver shall not be subject to
|
|
76
|
+
revocation, rescission, cancellation, termination, or any other legal or
|
|
77
|
+
equitable action to disrupt the quiet enjoyment of the Work by the public
|
|
78
|
+
as contemplated by Affirmer's express Statement of Purpose.
|
|
79
|
+
|
|
80
|
+
3. Public License Fallback. Should any part of the Waiver for any reason
|
|
81
|
+
be judged legally invalid or ineffective under applicable law, then the
|
|
82
|
+
Waiver shall be preserved to the maximum extent permitted taking into
|
|
83
|
+
account Affirmer's express Statement of Purpose. In addition, to the
|
|
84
|
+
extent the Waiver is so judged Affirmer hereby grants to each affected
|
|
85
|
+
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
|
86
|
+
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
|
87
|
+
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
|
88
|
+
maximum duration provided by applicable law or treaty (including future
|
|
89
|
+
time extensions), (iii) in any current or future medium and for any number
|
|
90
|
+
of copies, and (iv) for any purpose whatsoever, including without
|
|
91
|
+
limitation commercial, advertising or promotional purposes (the
|
|
92
|
+
"License"). The License shall be deemed effective as of the date CC0 was
|
|
93
|
+
applied by Affirmer to the Work. Should any part of the License for any
|
|
94
|
+
reason be judged legally invalid or ineffective under applicable law, such
|
|
95
|
+
partial invalidity or ineffectiveness shall not invalidate the remainder
|
|
96
|
+
of the License, and in such case Affirmer hereby affirms that he or she
|
|
97
|
+
will not (i) exercise any of his or her remaining Copyright and Related
|
|
98
|
+
Rights in the Work or (ii) assert any associated claims and causes of
|
|
99
|
+
action with respect to the Work, in either case contrary to Affirmer's
|
|
100
|
+
express Statement of Purpose.
|
|
101
|
+
|
|
102
|
+
4. Limitations and Disclaimers.
|
|
103
|
+
|
|
104
|
+
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
|
105
|
+
surrendered, licensed or otherwise affected by this document.
|
|
106
|
+
b. Affirmer offers the Work as-is and makes no representations or
|
|
107
|
+
warranties of any kind concerning the Work, express, implied,
|
|
108
|
+
statutory or otherwise, including without limitation warranties of
|
|
109
|
+
title, merchantability, fitness for a particular purpose, non
|
|
110
|
+
infringement, or the absence of latent or other defects, accuracy, or
|
|
111
|
+
the present or absence of errors, whether or not discoverable, all to
|
|
112
|
+
the greatest extent permissible under applicable law.
|
|
113
|
+
c. Affirmer disclaims responsibility for clearing rights of other persons
|
|
114
|
+
that may apply to the Work or any use thereof, including without
|
|
115
|
+
limitation any person's Copyright and Related Rights in the Work.
|
|
116
|
+
Further, Affirmer disclaims responsibility for obtaining any necessary
|
|
117
|
+
consents, permissions or other rights required for any use of the
|
|
118
|
+
Work.
|
|
119
|
+
d. Affirmer understands and acknowledges that Creative Commons is not a
|
|
120
|
+
party to this document and has no duty or obligation with respect to
|
|
121
|
+
this CC0 or use of the Work.
|
zppy-0.1.0/MANIFEST.in
ADDED
zppy-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zppy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Zig progress protocol client for Python
|
|
5
|
+
License-Expression: CC0-1.0
|
|
6
|
+
Project-URL: Repository, https://github.com/asciimoth/zppy
|
|
7
|
+
Project-URL: Issues, https://github.com/asciimoth/zppy/issues
|
|
8
|
+
Project-URL: Documentation, https://github.com/asciimoth/zppy#readme
|
|
9
|
+
Keywords: zig,progress,zig-progress-protocol
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Operating System :: POSIX
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# zppy
|
|
22
|
+
|
|
23
|
+
`zppy` is a Python library that reports task progress through the Zig progress
|
|
24
|
+
protocol. Use it to report tasks, subtasks, and progress from child processes.
|
|
25
|
+
|
|
26
|
+
`zppy` requires Python 3.11 or later. The library has no runtime dependencies.
|
|
27
|
+
The pipe transport supports POSIX systems, including Linux and macOS.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
python -m pip install zppy
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The package uses the [CC0 1.0 Universal license](LICENSE).
|
|
36
|
+
|
|
37
|
+
## Zig progress protocol
|
|
38
|
+
|
|
39
|
+
The Zig progress protocol lets a process send a tree of tasks to its parent
|
|
40
|
+
through a pipe. Each task has a name, a completed item count, and an estimated
|
|
41
|
+
total. The `ZIG_PROGRESS` environment variable identifies the pipe. The parent
|
|
42
|
+
can combine progress from child processes and display it in one place.
|
|
43
|
+
|
|
44
|
+
You can use the protocol outside the Zig toolchain. For example,
|
|
45
|
+
[`zpg`](https://github.com/asciimoth/zpg) runs commands and displays their
|
|
46
|
+
progress in a terminal. With `zppy`, Python programs can send progress to
|
|
47
|
+
`zpg` or another compatible receiver.
|
|
48
|
+
|
|
49
|
+
See Andrew Kelley's [Zig progress protocol specification][protocol] for the
|
|
50
|
+
protocol design.
|
|
51
|
+
|
|
52
|
+

|
|
53
|
+
|
|
54
|
+
## Library usage
|
|
55
|
+
|
|
56
|
+
If you use this library, [open an issue][issues] or [send a pull
|
|
57
|
+
request][pulls] with a link to your project. This helps us track use of the
|
|
58
|
+
Zig progress protocol outside the Zig toolchain.
|
|
59
|
+
|
|
60
|
+
Add the [`zig-progress-protocol` topic][topic] to your GitHub repository. Use
|
|
61
|
+
the same tag on other platforms so people can find related projects.
|
|
62
|
+
|
|
63
|
+
[issues]: https://github.com/asciimoth/zppy/issues
|
|
64
|
+
[pulls]: https://github.com/asciimoth/zppy/pulls
|
|
65
|
+
[topic]: https://github.com/topics/zig-progress-protocol
|
|
66
|
+
|
|
67
|
+
### Report tasks
|
|
68
|
+
|
|
69
|
+
Save this example as `build.py`:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
import time
|
|
73
|
+
|
|
74
|
+
from zppy import Progress
|
|
75
|
+
|
|
76
|
+
with Progress("build", estimated_total_items=2) as progress:
|
|
77
|
+
for name in ("assets", "code"):
|
|
78
|
+
with progress.root.start(name, estimated_total_items=10) as task:
|
|
79
|
+
for _ in range(10):
|
|
80
|
+
time.sleep(0.1) # Replace this with one item of work.
|
|
81
|
+
task.complete_one()
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Run `build.py` with a progress display:
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
zpg python build.py
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Each child task can start more child tasks. When a child task ends, it leaves
|
|
91
|
+
the tree and adds one completed item to its parent task. Do not also call
|
|
92
|
+
`parent.complete_one()` for the same child task.
|
|
93
|
+
|
|
94
|
+
A context manager ends its task on normal exit and on exceptions. The context
|
|
95
|
+
manager does not suppress exceptions or report task success or failure.
|
|
96
|
+
|
|
97
|
+
Without a valid `ZIG_PROGRESS` channel, reporting is disabled. Task operations
|
|
98
|
+
have no effect. The library does not print to stdout or stderr. It does not
|
|
99
|
+
draw its own terminal display.
|
|
100
|
+
|
|
101
|
+
### Run the combined example
|
|
102
|
+
|
|
103
|
+
From the repository root, run:
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
PYTHONPATH=src zpg python -u examples/mock.py
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
This example requires `zpg` on `PATH` and a system with `os.fork()`. It runs
|
|
110
|
+
`zpg --mock` as a subprocess and a Python worker as a forked process at the
|
|
111
|
+
same time. Both task trees appear under `demo`. Python task labels start with
|
|
112
|
+
`PY`. Both workers also write messages to stdout and stderr.
|
|
113
|
+
|
|
114
|
+
### Child processes
|
|
115
|
+
|
|
116
|
+
Use `Node.spawn()` to start a process with its own progress pipe.
|
|
117
|
+
`Node.spawn()` accepts the arguments to `subprocess.Popen` and returns a
|
|
118
|
+
`Popen` object:
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
import sys
|
|
122
|
+
|
|
123
|
+
from zppy import Progress
|
|
124
|
+
|
|
125
|
+
with Progress("build", estimated_total_items=1) as progress:
|
|
126
|
+
with progress.root.start("compile") as task:
|
|
127
|
+
with task.spawn([sys.executable, "worker.py"]) as process:
|
|
128
|
+
returncode = process.wait()
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
In `worker.py`, use `Progress`. The parent process combines the child's task
|
|
132
|
+
tree with its own tasks and sends the combined tree upstream. A child process
|
|
133
|
+
can use `Node.spawn()` to start more processes. Use a separate task node for
|
|
134
|
+
each process. Processes on different nodes can run at the same time.
|
|
135
|
+
|
|
136
|
+
`Node.spawn()` follows [Zig's child progress behavior][zig-child]. The child's
|
|
137
|
+
root replaces the task node in each report. The child root's descendants
|
|
138
|
+
appear below that node. If the child root has no name, the task keeps its
|
|
139
|
+
local name. Until the first complete report arrives, the task keeps its local
|
|
140
|
+
state. The last complete tree stays visible after the child closes its pipe or
|
|
141
|
+
exits.
|
|
142
|
+
|
|
143
|
+
Wait for the process, then end its task, as in the example above. Process exit
|
|
144
|
+
does not end the task or mark success or failure. Ending the task completes
|
|
145
|
+
one item in its parent.
|
|
146
|
+
|
|
147
|
+
Ending a task or closing the reporter closes its child progress pipes. Neither
|
|
148
|
+
operation waits for or terminates processes. Use one process per active node.
|
|
149
|
+
A second `spawn()` or `fork()` on the same node raises `ValueError`.
|
|
150
|
+
|
|
151
|
+
The update thread collects child reports. With `refresh_rate=None`, call
|
|
152
|
+
`flush()` regularly while children run.
|
|
153
|
+
|
|
154
|
+
`spawn()` copies the child environment and sets `ZIG_PROGRESS` to the new
|
|
155
|
+
pipe. It preserves other environment entries and adds the pipe to `pass_fds`.
|
|
156
|
+
`close_fds` must be true. `spawn()` does not change the parent environment.
|
|
157
|
+
Disabled, ended, or closed nodes still start processes, but remove
|
|
158
|
+
`ZIG_PROGRESS` from the child environment. Do not pass the upstream progress
|
|
159
|
+
descriptor to children.
|
|
160
|
+
|
|
161
|
+
All processes in the tree must use the same `name_size`. There is no format
|
|
162
|
+
negotiation.
|
|
163
|
+
|
|
164
|
+
[zig-child]: https://github.com/ziglang/zig/blob/0.15.2/lib/std/process/Child.zig#L96-L104
|
|
165
|
+
|
|
166
|
+
### Forked processes
|
|
167
|
+
|
|
168
|
+
On systems with `os.fork()`, use `Node.fork()` to attach a forked process to a
|
|
169
|
+
task. It returns the child's PID in the parent and `0` in the child:
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
import os
|
|
173
|
+
|
|
174
|
+
from zppy import Progress
|
|
175
|
+
|
|
176
|
+
with Progress("parent", estimated_total_items=1) as progress:
|
|
177
|
+
with progress.root.start("worker") as task:
|
|
178
|
+
pid = task.fork()
|
|
179
|
+
if pid == 0:
|
|
180
|
+
with Progress("worker", estimated_total_items=10) as child:
|
|
181
|
+
for _ in range(10):
|
|
182
|
+
child.root.complete_one()
|
|
183
|
+
os._exit(0)
|
|
184
|
+
_, status = os.waitpid(pid, 0)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
The child gets a private `ZIG_PROGRESS` pipe. Create a new `Progress` in the
|
|
188
|
+
child, with the same `name_size` as its parent. All inherited reporters and
|
|
189
|
+
their nodes are disabled in the child. Caller-owned descriptors remain the
|
|
190
|
+
caller's responsibility. The parent's reporters and environment stay
|
|
191
|
+
unchanged.
|
|
192
|
+
|
|
193
|
+
The forked child's tree replaces its task node, as for `spawn()`. Forked
|
|
194
|
+
children can use either `fork()` or `spawn()` to create more children. Wait
|
|
195
|
+
for the child with `os.waitpid()`, then end its task in the parent. Close the
|
|
196
|
+
child's reporter before `os._exit()`, which skips normal cleanup. Handle child
|
|
197
|
+
errors and exit status as required by your application.
|
|
198
|
+
|
|
199
|
+
Forks on disabled, ended, or closed nodes still run, but the child has no
|
|
200
|
+
`ZIG_PROGRESS` channel. A failed fork leaves the parent reporter usable.
|
|
201
|
+
|
|
202
|
+
Use `Node.fork()` to connect a forked process to a task. Direct `os.fork()`
|
|
203
|
+
calls and `multiprocessing` do not create this connection. `Node.fork()`
|
|
204
|
+
protects `zppy`'s own state. Application code and other libraries must also
|
|
205
|
+
meet the platform's [fork
|
|
206
|
+
requirements](https://docs.python.org/3/library/os.html#os.fork).
|
|
207
|
+
|
|
208
|
+
### API reference
|
|
209
|
+
|
|
210
|
+
`Progress(root_name="", estimated_total_items=0, *, refresh_rate=0.08,
|
|
211
|
+
initial_delay=0.2, fd=None, disable=False, name_size=120)` creates a reporter.
|
|
212
|
+
Use `progress.root` to access its root task.
|
|
213
|
+
|
|
214
|
+
| Option | Meaning |
|
|
215
|
+
| --- | --- |
|
|
216
|
+
| `root_name` | Root label, as `str` or `bytes`. |
|
|
217
|
+
| `estimated_total_items` | Estimated root item count. Zero means unknown. |
|
|
218
|
+
| `refresh_rate` | Seconds between reports. Use `None` for manual reports. |
|
|
219
|
+
| `initial_delay` | Seconds before the first automatic report. |
|
|
220
|
+
| `fd` | Explicit pipe descriptor. `None` reads `ZIG_PROGRESS`. |
|
|
221
|
+
| `disable` | Disable all reporting when true. |
|
|
222
|
+
| `name_size` | Name field size: `120` by default, or `40` for the old format. |
|
|
223
|
+
|
|
224
|
+
Timer values must be finite numbers no greater than `threading.TIMEOUT_MAX`.
|
|
225
|
+
`refresh_rate` must be positive, and `initial_delay` must be nonnegative.
|
|
226
|
+
|
|
227
|
+
`progress.enabled` shows whether the channel is open. `progress.flush()` tries
|
|
228
|
+
to send a snapshot immediately. It returns `True` if the current snapshot was
|
|
229
|
+
sent in full, or `False` if reporting is disabled or the write could not
|
|
230
|
+
finish. It does not wait for pipe space.
|
|
231
|
+
|
|
232
|
+
`progress.close()` stops the update thread, tries to send the final state, and
|
|
233
|
+
releases its descriptor. Use a context manager or call `close()` directly.
|
|
234
|
+
Closing the reporter does not set counts to their totals or remove the final
|
|
235
|
+
tree from the receiver. Ending the root also closes the reporter.
|
|
236
|
+
|
|
237
|
+
Nodes provide these methods:
|
|
238
|
+
|
|
239
|
+
| Method | Action |
|
|
240
|
+
| --- | --- |
|
|
241
|
+
| `start(name, estimated_total_items=0)` | Create a child task. |
|
|
242
|
+
| `spawn(args, **kwargs)` | Start a process and collect its progress. |
|
|
243
|
+
| `fork()` | Fork and collect the child's progress at this node. |
|
|
244
|
+
| `complete_one()` | Add one completed item. |
|
|
245
|
+
| `set_completed_items(count)` | Set the absolute completed count. |
|
|
246
|
+
| `set_estimated_total_items(count)` | Set the estimated total. |
|
|
247
|
+
| `increase_estimated_total_items(count)` | Add to the estimated total. |
|
|
248
|
+
| `set_name(name)` | Change the task label. |
|
|
249
|
+
| `end()` | Remove this branch and complete one item in its parent. |
|
|
250
|
+
|
|
251
|
+
Calls are thread-safe. Repeated `end()` calls have no effect. Task updates on
|
|
252
|
+
ended nodes also have no effect. Calls to `spawn()` and `fork()` still start a
|
|
253
|
+
process on ended nodes, as described in the child process sections. Invalid
|
|
254
|
+
arguments raise `TypeError` or `ValueError`, including when reporting is
|
|
255
|
+
disabled.
|
|
256
|
+
|
|
257
|
+
Counts must be nonnegative integers. Completed counts are limited to
|
|
258
|
+
`4294967295`. Totals are limited to `4294967294`, because Zig reserves the
|
|
259
|
+
next value. The library reduces larger values to these limits. Completed
|
|
260
|
+
counts can exceed totals.
|
|
261
|
+
|
|
262
|
+
Names are zero-padded to the field size. Text is encoded as UTF-8 and
|
|
263
|
+
shortened at a character boundary. Byte names are shortened without decoding.
|
|
264
|
+
Bytes after the first NUL are ignored.
|
|
265
|
+
|
|
266
|
+
The reporter tracks up to 253 active nodes, including the root. Additional
|
|
267
|
+
nodes have no effect. Ending nodes makes space for new nodes. Combined reports
|
|
268
|
+
also have a 253-node limit. Local nodes take priority, followed by child trees
|
|
269
|
+
in process creation order. Child roots replace local nodes without using more
|
|
270
|
+
slots. The reporter omits excess descendants and their branches. These
|
|
271
|
+
descendants can appear in later reports when space is available.
|
|
272
|
+
|
|
273
|
+
## Technical details
|
|
274
|
+
|
|
275
|
+
### Protocol compatibility
|
|
276
|
+
|
|
277
|
+
The default uses a 120-byte name field. Each node has two little-endian 32-bit
|
|
278
|
+
counters and 120 name bytes. A packet contains the node count, all node
|
|
279
|
+
records, then all parent indices. The root's parent is `255`.
|
|
280
|
+
|
|
281
|
+
The [published specification][protocol] uses 40-byte names. Select that format
|
|
282
|
+
explicitly with `Progress(name_size=40)`. The two formats are not wire
|
|
283
|
+
compatible. Both use `ZIG_PROGRESS`, and there is no version handshake. Select
|
|
284
|
+
the format that matches your receiver.
|
|
285
|
+
|
|
286
|
+
[protocol]: https://andrewkelley.me/post/zig-new-cli-progress-bar-explained.html#the-zig-progress-protocol-specification
|
|
287
|
+
|
|
288
|
+
Use one reporter per pipe. Each packet replaces the previous tree. A packet
|
|
289
|
+
does not add tasks to the previous tree. Multiple writers can replace each
|
|
290
|
+
other's progress or interleave packet bytes. Use `Node.spawn()` or
|
|
291
|
+
`Node.fork()` to combine progress from separate processes through private
|
|
292
|
+
pipes. Create a new reporter in each child, and use the same `name_size`
|
|
293
|
+
throughout the process tree.
|
|
294
|
+
|
|
295
|
+
### Platforms and pipe handling
|
|
296
|
+
|
|
297
|
+
The reporter accepts only writable POSIX pipes, such as those on Linux and
|
|
298
|
+
macOS. Other descriptors, including regular files and read-only pipes, disable
|
|
299
|
+
reporting without changing their data or blocking mode. Reporting is disabled
|
|
300
|
+
on other platforms.
|
|
301
|
+
|
|
302
|
+
The reporter duplicates the supplied descriptor and never closes the original.
|
|
303
|
+
The duplicate is not inherited across exec. The reporter sets the channel to
|
|
304
|
+
nonblocking mode. This flag also applies to the original descriptor. When you
|
|
305
|
+
create a reporter, it does not change `ZIG_PROGRESS` in the environment.
|
|
306
|
+
`Node.fork()` sets `ZIG_PROGRESS` in the forked child only.
|
|
307
|
+
|
|
308
|
+
A full pipe can delay or drop updates. The reporter resumes partial packets
|
|
309
|
+
before it sends a new snapshot. A permanent write error disables reporting.
|
|
310
|
+
|
|
311
|
+
Closing the reporter never waits for pipe space. The final report can be lost.
|
|
312
|
+
If the receiver stops reading, a large partial packet can remain incomplete.
|
|
313
|
+
If final delivery is required, retry `flush()` while the receiver is active
|
|
314
|
+
before you close the reporter.
|
|
315
|
+
|
|
316
|
+
## Release notes
|
|
317
|
+
|
|
318
|
+
### 0.1.0
|
|
319
|
+
|
|
320
|
+
Initial release:
|
|
321
|
+
|
|
322
|
+
- Report tasks and nested tasks through the Zig progress protocol.
|
|
323
|
+
- Combine progress from subprocesses and forked Python workers.
|
|
324
|
+
- Support both 120-byte and 40-byte task names.
|
|
325
|
+
- Send updates through POSIX pipes without runtime dependencies.
|
|
326
|
+
|
|
327
|
+
## Manual releases
|
|
328
|
+
|
|
329
|
+
Use the Nix development shell on this machine:
|
|
330
|
+
|
|
331
|
+
```sh
|
|
332
|
+
nix develop
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
The shell includes the build tools and Twine. Before each release:
|
|
336
|
+
|
|
337
|
+
1. Set the version in `pyproject.toml`. The Nix package reads this version too.
|
|
338
|
+
2. Update the release notes above.
|
|
339
|
+
3. Run `just check`, then `just build` and `just check-dist`.
|
|
340
|
+
4. Install the wheel in a fresh virtual environment outside the source tree
|
|
341
|
+
and check that it works.
|
|
342
|
+
5. Commit the release changes and create an annotated tag, for example
|
|
343
|
+
`git tag -a v0.1.0 -m "Release 0.1.0"`.
|
|
344
|
+
6. Run `just publish` to upload the checked source archive and wheel to PyPI.
|
|
345
|
+
7. Push the release commit and tag to the repository.
|
|
346
|
+
|
|
347
|
+
`just publish` checks the files again but does not rebuild them. It selects
|
|
348
|
+
only the two files for the version in `pyproject.toml`, so older releases in
|
|
349
|
+
`dist/` are not uploaded. If you change the source, rebuild and check it before
|
|
350
|
+
you publish.
|
|
351
|
+
|
|
352
|
+
For the first upload, create a PyPI account, verify its email address, enable
|
|
353
|
+
two-factor authentication, and create an API token with account-wide scope.
|
|
354
|
+
Enter the token at Twine's password prompt. After the first upload, replace
|
|
355
|
+
that token with a token scoped to `zppy`. Do not commit tokens to the
|
|
356
|
+
repository. See the [PyPI publishing guide][pypi-publishing] for account and
|
|
357
|
+
authentication details.
|
|
358
|
+
|
|
359
|
+
[pypi-publishing]: https://docs.pypi.org/publishing/publishing-to-pypi/
|