stagegate 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.
- stagegate-0.1.0/.gitignore +19 -0
- stagegate-0.1.0/API.md +685 -0
- stagegate-0.1.0/LICENSE +21 -0
- stagegate-0.1.0/PKG-INFO +226 -0
- stagegate-0.1.0/README.md +200 -0
- stagegate-0.1.0/USE_CASES.md +286 -0
- stagegate-0.1.0/pyproject.toml +51 -0
- stagegate-0.1.0/src/stagegate/__init__.py +20 -0
- stagegate-0.1.0/src/stagegate/_records.py +150 -0
- stagegate-0.1.0/src/stagegate/_states.py +34 -0
- stagegate-0.1.0/src/stagegate/_wait_utils.py +97 -0
- stagegate-0.1.0/src/stagegate/exceptions.py +13 -0
- stagegate-0.1.0/src/stagegate/handles.py +216 -0
- stagegate-0.1.0/src/stagegate/pipeline.py +122 -0
- stagegate-0.1.0/src/stagegate/scheduler.py +482 -0
- stagegate-0.1.0/src/stagegate/wait.py +13 -0
- stagegate-0.1.0/tests/test_handles.py +307 -0
- stagegate-0.1.0/tests/test_namespace.py +16 -0
- stagegate-0.1.0/tests/test_pipeline_runtime.py +148 -0
- stagegate-0.1.0/tests/test_scheduler_basics.py +197 -0
- stagegate-0.1.0/tests/test_shutdown_runtime.py +230 -0
- stagegate-0.1.0/tests/test_task_runtime.py +377 -0
- stagegate-0.1.0/tests/test_task_submission.py +171 -0
- stagegate-0.1.0/tests/test_waits.py +571 -0
stagegate-0.1.0/API.md
ADDED
|
@@ -0,0 +1,685 @@
|
|
|
1
|
+
# stagegate API Reference
|
|
2
|
+
|
|
3
|
+
## Top-Level Namespace
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
import stagegate
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Public names:
|
|
10
|
+
|
|
11
|
+
- `Scheduler`
|
|
12
|
+
- `Pipeline`
|
|
13
|
+
- `TaskHandle`
|
|
14
|
+
- `PipelineHandle`
|
|
15
|
+
- `FIRST_COMPLETED`
|
|
16
|
+
- `FIRST_EXCEPTION`
|
|
17
|
+
- `ALL_COMPLETED`
|
|
18
|
+
- `CancelledError`
|
|
19
|
+
- `UnknownResourceError`
|
|
20
|
+
- `UnschedulableTaskError`
|
|
21
|
+
|
|
22
|
+
## `Scheduler`
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
stagegate.Scheduler(
|
|
26
|
+
*,
|
|
27
|
+
resources: dict[str, int | float],
|
|
28
|
+
pipeline_parallelism: int = 1,
|
|
29
|
+
task_parallelism: int | None = None,
|
|
30
|
+
)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Create a single-process scheduler.
|
|
34
|
+
|
|
35
|
+
### Constructor arguments
|
|
36
|
+
|
|
37
|
+
- `resources: dict[str, int | float]`
|
|
38
|
+
Abstract resource capacities such as `{"cpu": 16, "mem": 64}`.
|
|
39
|
+
These are scheduler admission labels, not OS-enforced quotas.
|
|
40
|
+
- `pipeline_parallelism: int = 1`
|
|
41
|
+
Maximum number of pipeline coordinator threads that may run `Pipeline.run()` concurrently.
|
|
42
|
+
- `task_parallelism: int | None = None`
|
|
43
|
+
Maximum number of tasks that may be admitted concurrently.
|
|
44
|
+
If `None`, the effective worker count is `1`.
|
|
45
|
+
|
|
46
|
+
### Context manager behavior
|
|
47
|
+
|
|
48
|
+
`Scheduler` can be used in a `with` block:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
with stagegate.Scheduler(resources={"cpu": 8}) as scheduler:
|
|
52
|
+
...
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Leaving the block calls `close()`.
|
|
56
|
+
|
|
57
|
+
### `Scheduler.run_pipeline(pipeline)`
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
run_pipeline(pipeline: Pipeline) -> PipelineHandle
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Submit a pipeline instance for FIFO execution.
|
|
64
|
+
|
|
65
|
+
Arguments:
|
|
66
|
+
|
|
67
|
+
- `pipeline`
|
|
68
|
+
A `Pipeline` instance.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
|
|
72
|
+
- `PipelineHandle`
|
|
73
|
+
A handle for observing, waiting for, or cancelling that pipeline submission.
|
|
74
|
+
|
|
75
|
+
Raises:
|
|
76
|
+
|
|
77
|
+
- `TypeError`
|
|
78
|
+
If `pipeline` is not a `Pipeline` instance.
|
|
79
|
+
- `RuntimeError`
|
|
80
|
+
If shutdown has already started.
|
|
81
|
+
- `RuntimeError`
|
|
82
|
+
If the same pipeline instance has already been submitted once before.
|
|
83
|
+
|
|
84
|
+
Notes:
|
|
85
|
+
|
|
86
|
+
- the same pipeline instance is single-use
|
|
87
|
+
- queue order is FIFO
|
|
88
|
+
- pipeline execution begins later on a scheduler-owned coordinator thread
|
|
89
|
+
|
|
90
|
+
### `Scheduler.wait_pipelines(handles, timeout=None, return_when=...)`
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
wait_pipelines(
|
|
94
|
+
handles,
|
|
95
|
+
timeout: float | None = None,
|
|
96
|
+
return_when: str = stagegate.ALL_COMPLETED,
|
|
97
|
+
) -> tuple[set[PipelineHandle], set[PipelineHandle]]
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Wait on pipeline handles owned by the same scheduler.
|
|
101
|
+
|
|
102
|
+
Arguments:
|
|
103
|
+
|
|
104
|
+
- `handles`
|
|
105
|
+
An iterable of `PipelineHandle` objects created by this scheduler.
|
|
106
|
+
- `timeout`
|
|
107
|
+
Maximum wait time in seconds.
|
|
108
|
+
`None` means wait indefinitely.
|
|
109
|
+
`0` means immediate poll.
|
|
110
|
+
- `return_when`
|
|
111
|
+
One of `stagegate.FIRST_COMPLETED`, `stagegate.FIRST_EXCEPTION`, or `stagegate.ALL_COMPLETED`.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
|
|
115
|
+
- `tuple[set[PipelineHandle], set[PipelineHandle]]`
|
|
116
|
+
A pair `(done, pending)`.
|
|
117
|
+
- `done`
|
|
118
|
+
The subset of input handles that are terminal at the moment the call returns.
|
|
119
|
+
A pipeline handle is terminal when it is `SUCCEEDED`, `FAILED`, or `CANCELLED`.
|
|
120
|
+
- `pending`
|
|
121
|
+
The subset of input handles that are still non-terminal at the moment the call returns.
|
|
122
|
+
|
|
123
|
+
Important details:
|
|
124
|
+
|
|
125
|
+
- timeout does not raise; it returns the current `(done, pending)`
|
|
126
|
+
- duplicate handles are deduplicated before waiting
|
|
127
|
+
- already terminal handles are placed in `done` immediately
|
|
128
|
+
- this method does not raise the pipeline's stored execution exception
|
|
129
|
+
|
|
130
|
+
Raises:
|
|
131
|
+
|
|
132
|
+
- `ValueError`
|
|
133
|
+
If `handles` is empty.
|
|
134
|
+
- `TypeError`
|
|
135
|
+
If any element is not a `PipelineHandle`.
|
|
136
|
+
- `ValueError`
|
|
137
|
+
If any handle belongs to a different scheduler.
|
|
138
|
+
- `ValueError`
|
|
139
|
+
If `timeout < 0`.
|
|
140
|
+
- `ValueError`
|
|
141
|
+
If `return_when` is invalid.
|
|
142
|
+
|
|
143
|
+
### `Scheduler.shutdown(cancel_pending_pipelines=False)`
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
shutdown(
|
|
147
|
+
cancel_pending_pipelines: bool = False,
|
|
148
|
+
) -> None
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Start shutdown.
|
|
152
|
+
|
|
153
|
+
Arguments:
|
|
154
|
+
|
|
155
|
+
- `cancel_pending_pipelines`
|
|
156
|
+
If `True`, cancel pipelines that are still queued and not yet started.
|
|
157
|
+
Running pipelines are not cancelled.
|
|
158
|
+
|
|
159
|
+
Behavior:
|
|
160
|
+
|
|
161
|
+
- returns after shutdown initiation
|
|
162
|
+
- does not wait for in-flight work to finish
|
|
163
|
+
- does not stop or join internal coordinator, dispatcher, or worker threads
|
|
164
|
+
- after shutdown starts, new pipeline submission is rejected
|
|
165
|
+
- already running pipelines continue
|
|
166
|
+
- already running pipelines may still submit tasks on their coordinator thread
|
|
167
|
+
- existing handles remain usable
|
|
168
|
+
- repeated shutdown calls are allowed
|
|
169
|
+
|
|
170
|
+
Related predicates:
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
shutdown_started() -> bool
|
|
174
|
+
closed() -> bool
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Return values:
|
|
178
|
+
|
|
179
|
+
- `shutdown_started()`
|
|
180
|
+
`True` once shutdown has begun.
|
|
181
|
+
- `closed()`
|
|
182
|
+
`True` only after `close()` completes full shutdown.
|
|
183
|
+
|
|
184
|
+
### `Scheduler.close(cancel_pending_pipelines=False)`
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
close(
|
|
188
|
+
cancel_pending_pipelines: bool = False,
|
|
189
|
+
) -> None
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Fully close the scheduler.
|
|
193
|
+
|
|
194
|
+
Arguments:
|
|
195
|
+
|
|
196
|
+
- `cancel_pending_pipelines`
|
|
197
|
+
If `True`, cancel pipelines that are still queued and not yet started.
|
|
198
|
+
Running pipelines are not cancelled.
|
|
199
|
+
|
|
200
|
+
Behavior:
|
|
201
|
+
|
|
202
|
+
- starts shutdown if it has not started already
|
|
203
|
+
- waits until no live work remains
|
|
204
|
+
- joins the scheduler-owned runtime threads that were started
|
|
205
|
+
- returns only after the scheduler is fully closed
|
|
206
|
+
- already running pipelines continue
|
|
207
|
+
- already running pipelines may still submit tasks on their coordinator thread
|
|
208
|
+
- existing handles remain usable
|
|
209
|
+
- repeated close calls are allowed
|
|
210
|
+
- later calls made before the scheduler reaches `CLOSED` may strengthen the request by adding `cancel_pending_pipelines=True`
|
|
211
|
+
|
|
212
|
+
Raises:
|
|
213
|
+
|
|
214
|
+
- `RuntimeError`
|
|
215
|
+
If called from a scheduler-owned runtime thread such as a pipeline coordinator thread, worker thread, or dispatcher thread.
|
|
216
|
+
|
|
217
|
+
Related predicates:
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
shutdown_started() -> bool
|
|
221
|
+
closed() -> bool
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Return values:
|
|
225
|
+
|
|
226
|
+
- `shutdown_started()`
|
|
227
|
+
`True` once shutdown has begun.
|
|
228
|
+
- `closed()`
|
|
229
|
+
`True` once no live work remains and the scheduler-owned runtime threads have terminated.
|
|
230
|
+
|
|
231
|
+
## `Pipeline`
|
|
232
|
+
|
|
233
|
+
Subclass `stagegate.Pipeline` and implement `run()`.
|
|
234
|
+
|
|
235
|
+
```python
|
|
236
|
+
class MyPipeline(stagegate.Pipeline):
|
|
237
|
+
def run(self):
|
|
238
|
+
...
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Pipeline control APIs are valid only while `run()` is actively executing on the scheduler-owned coordinator thread.
|
|
242
|
+
|
|
243
|
+
### `Pipeline.run()`
|
|
244
|
+
|
|
245
|
+
```python
|
|
246
|
+
run() -> Any
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
User-defined pipeline body.
|
|
250
|
+
|
|
251
|
+
Return value:
|
|
252
|
+
|
|
253
|
+
- any Python object
|
|
254
|
+
If `run()` returns normally, that value becomes the success result of the `PipelineHandle`.
|
|
255
|
+
|
|
256
|
+
Failure behavior:
|
|
257
|
+
|
|
258
|
+
- if `run()` raises, the pipeline becomes `FAILED`
|
|
259
|
+
- the original exception is stored on the handle and re-raised by `PipelineHandle.result()`
|
|
260
|
+
|
|
261
|
+
### `Pipeline.task(...)`
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
task(
|
|
265
|
+
fn,
|
|
266
|
+
*,
|
|
267
|
+
resources: dict[str, int | float],
|
|
268
|
+
args: tuple = (),
|
|
269
|
+
kwargs: dict | None = None,
|
|
270
|
+
name: str | None = None,
|
|
271
|
+
) -> TaskBuilder
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Create a task builder.
|
|
275
|
+
The task is not submitted until `.run()` is called on the returned builder.
|
|
276
|
+
|
|
277
|
+
Arguments:
|
|
278
|
+
|
|
279
|
+
- `fn`
|
|
280
|
+
The callable to execute later on a worker thread.
|
|
281
|
+
- `resources`
|
|
282
|
+
Abstract resource requirements for admission control.
|
|
283
|
+
- `args`
|
|
284
|
+
Positional arguments passed to `fn`.
|
|
285
|
+
- `kwargs`
|
|
286
|
+
Keyword arguments passed to `fn`.
|
|
287
|
+
- `name`
|
|
288
|
+
Optional user-facing label for debugging or future diagnostics.
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
|
|
292
|
+
- `TaskBuilder`
|
|
293
|
+
A builder/factory object.
|
|
294
|
+
Calling `.run()` on it submits the task and returns a `TaskHandle`.
|
|
295
|
+
|
|
296
|
+
Raises on builder `.run()`:
|
|
297
|
+
|
|
298
|
+
- `RuntimeError`
|
|
299
|
+
If the pipeline is not currently running.
|
|
300
|
+
- `RuntimeError`
|
|
301
|
+
If the call is not made from the pipeline's coordinator thread.
|
|
302
|
+
- `UnknownResourceError`
|
|
303
|
+
If any resource label is unknown.
|
|
304
|
+
- `UnschedulableTaskError`
|
|
305
|
+
If a single-task requirement exceeds configured capacity.
|
|
306
|
+
- `ValueError`
|
|
307
|
+
If a resource amount is non-numeric, non-finite, or negative.
|
|
308
|
+
|
|
309
|
+
### `Pipeline.stage_forward()`
|
|
310
|
+
|
|
311
|
+
```python
|
|
312
|
+
stage_forward() -> None
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Advance the internal stage by one.
|
|
316
|
+
|
|
317
|
+
Effects:
|
|
318
|
+
|
|
319
|
+
- later task submissions get the new stage snapshot
|
|
320
|
+
- already queued tasks keep the priority captured when they were submitted
|
|
321
|
+
|
|
322
|
+
Raises:
|
|
323
|
+
|
|
324
|
+
- `RuntimeError`
|
|
325
|
+
If called before pipeline execution starts.
|
|
326
|
+
- `RuntimeError`
|
|
327
|
+
If called after pipeline execution ends.
|
|
328
|
+
- `RuntimeError`
|
|
329
|
+
If called from any thread other than the pipeline's coordinator thread.
|
|
330
|
+
|
|
331
|
+
### `Pipeline.wait(handles, timeout=None, return_when=...)`
|
|
332
|
+
|
|
333
|
+
```python
|
|
334
|
+
wait(
|
|
335
|
+
handles,
|
|
336
|
+
timeout: float | None = None,
|
|
337
|
+
return_when: str = stagegate.ALL_COMPLETED,
|
|
338
|
+
) -> tuple[set[TaskHandle], set[TaskHandle]]
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Wait on task handles created by that pipeline.
|
|
342
|
+
|
|
343
|
+
Arguments:
|
|
344
|
+
|
|
345
|
+
- `handles`
|
|
346
|
+
An iterable of `TaskHandle` objects created by this pipeline.
|
|
347
|
+
- `timeout`
|
|
348
|
+
Maximum wait time in seconds.
|
|
349
|
+
`None` means wait indefinitely.
|
|
350
|
+
`0` means immediate poll.
|
|
351
|
+
- `return_when`
|
|
352
|
+
One of `stagegate.FIRST_COMPLETED`, `stagegate.FIRST_EXCEPTION`, or `stagegate.ALL_COMPLETED`.
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
|
|
356
|
+
- `tuple[set[TaskHandle], set[TaskHandle]]`
|
|
357
|
+
A pair `(done, pending)`.
|
|
358
|
+
- `done`
|
|
359
|
+
The subset of input handles that are terminal when the call returns.
|
|
360
|
+
- `pending`
|
|
361
|
+
The subset of input handles that are still non-terminal when the call returns.
|
|
362
|
+
|
|
363
|
+
Important details:
|
|
364
|
+
|
|
365
|
+
- timeout does not raise; it returns the current `(done, pending)`
|
|
366
|
+
- duplicate handles are deduplicated before waiting
|
|
367
|
+
- already terminal handles are placed in `done` immediately
|
|
368
|
+
- this method does not raise the task's stored execution exception
|
|
369
|
+
|
|
370
|
+
Raises:
|
|
371
|
+
|
|
372
|
+
- `RuntimeError`
|
|
373
|
+
If called outside the active pipeline coordinator-thread context.
|
|
374
|
+
- `ValueError`
|
|
375
|
+
If `handles` is empty.
|
|
376
|
+
- `TypeError`
|
|
377
|
+
If any element is not a `TaskHandle`.
|
|
378
|
+
- `ValueError`
|
|
379
|
+
If any handle belongs to a different pipeline.
|
|
380
|
+
- `ValueError`
|
|
381
|
+
If `timeout < 0`.
|
|
382
|
+
- `ValueError`
|
|
383
|
+
If `return_when` is invalid.
|
|
384
|
+
|
|
385
|
+
## `TaskHandle`
|
|
386
|
+
|
|
387
|
+
Handle for one submitted task.
|
|
388
|
+
|
|
389
|
+
### `TaskHandle.cancel()`
|
|
390
|
+
|
|
391
|
+
```python
|
|
392
|
+
cancel() -> bool
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
Return value:
|
|
396
|
+
|
|
397
|
+
- `True`
|
|
398
|
+
The task had not started yet and was transitioned to `CANCELLED`.
|
|
399
|
+
- `False`
|
|
400
|
+
The task was already running or already terminal.
|
|
401
|
+
|
|
402
|
+
Important detail:
|
|
403
|
+
|
|
404
|
+
- cancellation is non-preemptive
|
|
405
|
+
- running tasks are never force-killed
|
|
406
|
+
|
|
407
|
+
### `TaskHandle.done()`
|
|
408
|
+
|
|
409
|
+
```python
|
|
410
|
+
done() -> bool
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
Return value:
|
|
414
|
+
|
|
415
|
+
- `True`
|
|
416
|
+
The task is terminal: `SUCCEEDED`, `FAILED`, or `CANCELLED`.
|
|
417
|
+
- `False`
|
|
418
|
+
The task is still queued, ready, or running.
|
|
419
|
+
|
|
420
|
+
### `TaskHandle.running()`
|
|
421
|
+
|
|
422
|
+
```python
|
|
423
|
+
running() -> bool
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
Return value:
|
|
427
|
+
|
|
428
|
+
- `True`
|
|
429
|
+
The task callable is actively executing on a worker thread.
|
|
430
|
+
- `False`
|
|
431
|
+
Otherwise.
|
|
432
|
+
|
|
433
|
+
### `TaskHandle.cancelled()`
|
|
434
|
+
|
|
435
|
+
```python
|
|
436
|
+
cancelled() -> bool
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
Return value:
|
|
440
|
+
|
|
441
|
+
- `True`
|
|
442
|
+
The task ended in the cancelled state.
|
|
443
|
+
- `False`
|
|
444
|
+
Otherwise.
|
|
445
|
+
|
|
446
|
+
### `TaskHandle.result(timeout=None)`
|
|
447
|
+
|
|
448
|
+
```python
|
|
449
|
+
result(timeout: float | None = None) -> Any
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
Arguments:
|
|
453
|
+
|
|
454
|
+
- `timeout`
|
|
455
|
+
Maximum wait time in seconds.
|
|
456
|
+
`None` means wait indefinitely.
|
|
457
|
+
`0` means immediate check.
|
|
458
|
+
|
|
459
|
+
Return value:
|
|
460
|
+
|
|
461
|
+
- the task callable's normal return value
|
|
462
|
+
|
|
463
|
+
Raises:
|
|
464
|
+
|
|
465
|
+
- `TimeoutError`
|
|
466
|
+
If the task is not terminal before the timeout expires.
|
|
467
|
+
- `CancelledError`
|
|
468
|
+
If the task was cancelled.
|
|
469
|
+
- original task exception
|
|
470
|
+
If the task failed by raising.
|
|
471
|
+
- `ValueError`
|
|
472
|
+
If `timeout < 0`.
|
|
473
|
+
|
|
474
|
+
### `TaskHandle.exception(timeout=None)`
|
|
475
|
+
|
|
476
|
+
```python
|
|
477
|
+
exception(timeout: float | None = None) -> BaseException | None
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
Arguments:
|
|
481
|
+
|
|
482
|
+
- `timeout`
|
|
483
|
+
Maximum wait time in seconds.
|
|
484
|
+
`None` means wait indefinitely.
|
|
485
|
+
`0` means immediate check.
|
|
486
|
+
|
|
487
|
+
Return value:
|
|
488
|
+
|
|
489
|
+
- `None`
|
|
490
|
+
If the task succeeded.
|
|
491
|
+
- the stored exception object
|
|
492
|
+
If the task failed.
|
|
493
|
+
|
|
494
|
+
Raises:
|
|
495
|
+
|
|
496
|
+
- `TimeoutError`
|
|
497
|
+
If the task is not terminal before the timeout expires.
|
|
498
|
+
- `CancelledError`
|
|
499
|
+
If the task was cancelled.
|
|
500
|
+
- `ValueError`
|
|
501
|
+
If `timeout < 0`.
|
|
502
|
+
|
|
503
|
+
## `PipelineHandle`
|
|
504
|
+
|
|
505
|
+
Handle for one submitted pipeline.
|
|
506
|
+
|
|
507
|
+
### `PipelineHandle.cancel()`
|
|
508
|
+
|
|
509
|
+
```python
|
|
510
|
+
cancel() -> bool
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
Return value:
|
|
514
|
+
|
|
515
|
+
- `True`
|
|
516
|
+
The pipeline had not started yet and was transitioned to `CANCELLED`.
|
|
517
|
+
- `False`
|
|
518
|
+
The pipeline was already running or already terminal.
|
|
519
|
+
|
|
520
|
+
Important detail:
|
|
521
|
+
|
|
522
|
+
- started pipelines are not force-stopped
|
|
523
|
+
|
|
524
|
+
### `PipelineHandle.done()`
|
|
525
|
+
|
|
526
|
+
```python
|
|
527
|
+
done() -> bool
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
Return value:
|
|
531
|
+
|
|
532
|
+
- `True`
|
|
533
|
+
The pipeline is terminal: `SUCCEEDED`, `FAILED`, or `CANCELLED`.
|
|
534
|
+
- `False`
|
|
535
|
+
The pipeline is still queued or running.
|
|
536
|
+
|
|
537
|
+
### `PipelineHandle.running()`
|
|
538
|
+
|
|
539
|
+
```python
|
|
540
|
+
running() -> bool
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
Return value:
|
|
544
|
+
|
|
545
|
+
- `True`
|
|
546
|
+
The pipeline `run()` method is actively executing.
|
|
547
|
+
- `False`
|
|
548
|
+
Otherwise.
|
|
549
|
+
|
|
550
|
+
### `PipelineHandle.cancelled()`
|
|
551
|
+
|
|
552
|
+
```python
|
|
553
|
+
cancelled() -> bool
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
Return value:
|
|
557
|
+
|
|
558
|
+
- `True`
|
|
559
|
+
The pipeline ended in the cancelled state.
|
|
560
|
+
- `False`
|
|
561
|
+
Otherwise.
|
|
562
|
+
|
|
563
|
+
### `PipelineHandle.result(timeout=None)`
|
|
564
|
+
|
|
565
|
+
```python
|
|
566
|
+
result(timeout: float | None = None) -> Any
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
Arguments:
|
|
570
|
+
|
|
571
|
+
- `timeout`
|
|
572
|
+
Maximum wait time in seconds.
|
|
573
|
+
`None` means wait indefinitely.
|
|
574
|
+
`0` means immediate check.
|
|
575
|
+
|
|
576
|
+
Return value:
|
|
577
|
+
|
|
578
|
+
- the normal return value of `Pipeline.run()`
|
|
579
|
+
|
|
580
|
+
Raises:
|
|
581
|
+
|
|
582
|
+
- `TimeoutError`
|
|
583
|
+
If the pipeline is not terminal before the timeout expires.
|
|
584
|
+
- `CancelledError`
|
|
585
|
+
If the pipeline was cancelled.
|
|
586
|
+
- original pipeline exception
|
|
587
|
+
If the pipeline failed by raising.
|
|
588
|
+
- `ValueError`
|
|
589
|
+
If `timeout < 0`.
|
|
590
|
+
|
|
591
|
+
### `PipelineHandle.exception(timeout=None)`
|
|
592
|
+
|
|
593
|
+
```python
|
|
594
|
+
exception(timeout: float | None = None) -> BaseException | None
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
Arguments:
|
|
598
|
+
|
|
599
|
+
- `timeout`
|
|
600
|
+
Maximum wait time in seconds.
|
|
601
|
+
`None` means wait indefinitely.
|
|
602
|
+
`0` means immediate check.
|
|
603
|
+
|
|
604
|
+
Return value:
|
|
605
|
+
|
|
606
|
+
- `None`
|
|
607
|
+
If the pipeline succeeded.
|
|
608
|
+
- the stored exception object
|
|
609
|
+
If the pipeline failed.
|
|
610
|
+
|
|
611
|
+
Raises:
|
|
612
|
+
|
|
613
|
+
- `TimeoutError`
|
|
614
|
+
If the pipeline is not terminal before the timeout expires.
|
|
615
|
+
- `CancelledError`
|
|
616
|
+
If the pipeline was cancelled.
|
|
617
|
+
- `ValueError`
|
|
618
|
+
If `timeout < 0`.
|
|
619
|
+
|
|
620
|
+
## Wait Constants
|
|
621
|
+
|
|
622
|
+
```python
|
|
623
|
+
stagegate.FIRST_COMPLETED
|
|
624
|
+
stagegate.FIRST_EXCEPTION
|
|
625
|
+
stagegate.ALL_COMPLETED
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
These constants are used by both:
|
|
629
|
+
|
|
630
|
+
- `Pipeline.wait(...)`
|
|
631
|
+
- `Scheduler.wait_pipelines(...)`
|
|
632
|
+
|
|
633
|
+
### `FIRST_COMPLETED`
|
|
634
|
+
|
|
635
|
+
Return once at least one handle is terminal.
|
|
636
|
+
|
|
637
|
+
Terminal means:
|
|
638
|
+
|
|
639
|
+
- `SUCCEEDED`
|
|
640
|
+
- `FAILED`
|
|
641
|
+
- `CANCELLED`
|
|
642
|
+
|
|
643
|
+
### `FIRST_EXCEPTION`
|
|
644
|
+
|
|
645
|
+
Return once at least one handle is `FAILED`.
|
|
646
|
+
|
|
647
|
+
Important detail:
|
|
648
|
+
|
|
649
|
+
- `CANCELLED` does not count as an exception trigger
|
|
650
|
+
- if no handle ever fails, this behaves like `ALL_COMPLETED`
|
|
651
|
+
|
|
652
|
+
### `ALL_COMPLETED`
|
|
653
|
+
|
|
654
|
+
Return once every handle is terminal.
|
|
655
|
+
|
|
656
|
+
## Exceptions
|
|
657
|
+
|
|
658
|
+
### `CancelledError`
|
|
659
|
+
|
|
660
|
+
Raised by `result()` and `exception()` on cancelled task or pipeline handles.
|
|
661
|
+
|
|
662
|
+
### `UnknownResourceError`
|
|
663
|
+
|
|
664
|
+
Raised when task submission requests a resource label unknown to the scheduler.
|
|
665
|
+
|
|
666
|
+
### `UnschedulableTaskError`
|
|
667
|
+
|
|
668
|
+
Raised when a single task requires more of some resource than the scheduler can ever provide.
|
|
669
|
+
|
|
670
|
+
## Minimal Example
|
|
671
|
+
|
|
672
|
+
```python
|
|
673
|
+
import stagegate
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
class Demo(stagegate.Pipeline):
|
|
677
|
+
def run(self) -> str:
|
|
678
|
+
handle = self.task(lambda: "ok", resources={"cpu": 1}).run()
|
|
679
|
+
return handle.result()
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
with stagegate.Scheduler(resources={"cpu": 2}) as scheduler:
|
|
683
|
+
pipeline_handle = scheduler.run_pipeline(Demo())
|
|
684
|
+
print(pipeline_handle.result())
|
|
685
|
+
```
|
stagegate-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ttkkmg
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|