pyattacker 0.3.2__tar.gz → 0.3.3__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.
- pyattacker-0.3.3/.github/workflows/docs.yml +55 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/.gitignore +2 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/CHANGELOG.md +40 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/PKG-INFO +15 -3
- {pyattacker-0.3.2 → pyattacker-0.3.3}/README.md +13 -1
- {pyattacker-0.3.2 → pyattacker-0.3.3}/README.zh-CN.md +12 -1
- {pyattacker-0.3.2 → pyattacker-0.3.3}/docs/cli.md +3 -2
- {pyattacker-0.3.2 → pyattacker-0.3.3}/docs/design.md +13 -6
- pyattacker-0.3.3/docs/documentation.md +49 -0
- pyattacker-0.3.3/docs/hooks.py +15 -0
- pyattacker-0.3.3/docs/index.md +48 -0
- pyattacker-0.3.3/docs/overrides/main.html +8 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/docs/reference.md +59 -6
- {pyattacker-0.3.2 → pyattacker-0.3.3}/docs/releasing.md +3 -2
- {pyattacker-0.3.2 → pyattacker-0.3.3}/docs/suites.md +38 -2
- {pyattacker-0.3.2 → pyattacker-0.3.3}/docs/tutorial.md +11 -11
- {pyattacker-0.3.2 → pyattacker-0.3.3}/docs/zh-CN/cli.md +3 -2
- {pyattacker-0.3.2 → pyattacker-0.3.3}/docs/zh-CN/design.md +9 -5
- pyattacker-0.3.3/docs/zh-CN/documentation.md +48 -0
- pyattacker-0.3.3/docs/zh-CN/index.md +48 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/docs/zh-CN/reference.md +49 -6
- {pyattacker-0.3.2 → pyattacker-0.3.3}/docs/zh-CN/releasing.md +3 -2
- {pyattacker-0.3.2 → pyattacker-0.3.3}/docs/zh-CN/suites.md +30 -1
- {pyattacker-0.3.2 → pyattacker-0.3.3}/docs/zh-CN/tutorial.md +11 -11
- pyattacker-0.3.3/mkdocs.yml +97 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/pyproject.toml +13 -2
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/__init__.py +1 -1
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/cli.py +5 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/declarative.py +4 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/errors.py +4 -1
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/export.py +6 -3
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/runner.py +50 -13
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/server.py +17 -6
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/store/__init__.py +2 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/store/base.py +23 -2
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/store/memory.py +3 -1
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/store/sqlite.py +31 -5
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/store/suite.py +268 -98
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/store/writebehind.py +50 -13
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/helpers.py +9 -0
- pyattacker-0.3.3/tests/test_admission.py +224 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_backends.py +1 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_cli.py +2 -2
- pyattacker-0.3.3/tests/test_dashboard_security.py +64 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_docs_i18n.py +2 -0
- pyattacker-0.3.3/tests/test_fact_batches.py +280 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_reported_metrics.py +2 -2
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_server.py +12 -2
- pyattacker-0.3.3/tests/test_suite_queries.py +219 -0
- pyattacker-0.3.3/uv.lock +685 -0
- pyattacker-0.3.2/uv.lock +0 -172
- {pyattacker-0.3.2 → pyattacker-0.3.3}/.github/workflows/ci.yml +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/.github/workflows/release.yml +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/LICENSE +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/docs/benchmark.md +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/docs/zh-CN/benchmark.md +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/examples/data.jsonl +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/examples/live_metrics.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/examples/llm_eval/README.md +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/examples/llm_eval/README.zh-CN.md +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/examples/llm_eval/__init__.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/examples/llm_eval/backend.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/examples/llm_eval/demo.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/examples/llm_eval/pipelines.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/examples/plugin_package/README.md +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/examples/plugin_package/README.zh-CN.md +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/examples/plugin_package/pa_demo_plugin/__init__.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/examples/plugin_package/pa_demo_plugin/algorithms.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/examples/plugin_package/pa_demo_plugin/codecs.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/examples/plugin_package/pa_demo_plugin/tasks.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/examples/plugin_package/pyproject.toml +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/examples/plugin_tasks.yaml +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/examples/qa_eval.yaml +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/examples/quickstart.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/examples/sharded.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/examples/suites/experiment.json +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/examples/suites/suite.json +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/__main__.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/algorithm.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/artifact.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/backends.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/benchmark/__init__.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/benchmark/clock.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/benchmark/harness.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/benchmark/metrics.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/benchmark/provider.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/benchmark/report.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/benchmark/scenario.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/handoff.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/history.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/merge.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/monitor.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/pipeline.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/plugins.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/reported_metrics.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/resource.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/scheduler.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/shard.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/store/visits.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/suite.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/task.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/src/pyattacker/tasks/__init__.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_algorithms.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_artifact.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_backward.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_benchmark_cli.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_benchmark_clock.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_benchmark_harness.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_benchmark_provider.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_benchmark_report.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_benchmark_scenario.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_declarative.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_docs_examples.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_docs_facts.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_errors.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_export.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_handoff.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_lease_safety.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_m2.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_monitor.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_optional_yaml.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_packaging.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_pipeline.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_plugins.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_resume_cursor.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_resume_identity.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_retry_policy.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_runner.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_scheduler.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_shard.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_store.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_subprocess_lifecycle.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_suite.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_task_overrides.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_tasks.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_tutorial.py +0 -0
- {pyattacker-0.3.2 → pyattacker-0.3.3}/tests/test_worker_liveness.py +0 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
name: Documentation
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
concurrency:
|
|
13
|
+
group: docs-${{ github.ref }}
|
|
14
|
+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
build:
|
|
18
|
+
name: Build documentation
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v7
|
|
22
|
+
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
23
|
+
with:
|
|
24
|
+
enable-cache: true
|
|
25
|
+
python-version: '3.11'
|
|
26
|
+
cache-suffix: docs
|
|
27
|
+
- name: Install dependencies
|
|
28
|
+
run: uv sync --locked --group docs
|
|
29
|
+
- name: Build site and check links
|
|
30
|
+
run: uv run --group docs mkdocs build --strict
|
|
31
|
+
- name: Check bilingual documentation and executable examples
|
|
32
|
+
run: uv run pytest tests/test_docs_i18n.py tests/test_docs_examples.py tests/test_tutorial.py
|
|
33
|
+
- name: Upload Pages artifact
|
|
34
|
+
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
|
|
35
|
+
uses: actions/upload-pages-artifact@v4
|
|
36
|
+
with:
|
|
37
|
+
path: site
|
|
38
|
+
|
|
39
|
+
deploy:
|
|
40
|
+
name: Deploy documentation
|
|
41
|
+
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
|
|
42
|
+
needs: build
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
permissions:
|
|
45
|
+
pages: write
|
|
46
|
+
id-token: write
|
|
47
|
+
environment:
|
|
48
|
+
name: github-pages
|
|
49
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
50
|
+
steps:
|
|
51
|
+
- name: Configure Pages
|
|
52
|
+
uses: actions/configure-pages@v5
|
|
53
|
+
- name: Deploy Pages
|
|
54
|
+
id: deployment
|
|
55
|
+
uses: actions/deploy-pages@v4
|
|
@@ -6,6 +6,45 @@ All notable changes to this project are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.3.3] — 2026-09-21
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
* **Dashboard HTML injection** (#78): render event and card values as text rather than
|
|
14
|
+
interpolating untrusted values into HTML.
|
|
15
|
+
* **Atomic write-behind batches** (#79): SQLite commits attempts and events in one transaction
|
|
16
|
+
and assigns IDs only after commit. Failed batches remain recoverable; legacy stores stop
|
|
17
|
+
accepting fact writes after an uncertain failure. Suite eviction and shutdown preserve
|
|
18
|
+
failure boundaries and close remaining resources.
|
|
19
|
+
* **Suite query correctness and cost** (#80): apply experiment/run filters before handoff
|
|
20
|
+
limits, support task limits consistently across built-in stores, and select recent events
|
|
21
|
+
using timestamp indexes. Counts and grouped statistics avoid decoding full history while
|
|
22
|
+
preserving historical run outcomes. Read-only older databases remain supported.
|
|
23
|
+
* Loopback HTTP tests bypass environment proxies (#76).
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
* **Bounded pipeline admission** (#81): `max_admitted` limits queued, executing and delayed
|
|
28
|
+
pipelines together, while due retries retain their admission. Configure it through Runner,
|
|
29
|
+
the run configuration or `--max-admitted`; Suite members share one Runner budget. Live
|
|
30
|
+
statistics and persisted run configuration expose the effective limit.
|
|
31
|
+
* **Bilingual documentation website** (#82): tutorials, Python API and CLI references at
|
|
32
|
+
<https://hazer-bjtu.github.io/pyattacker/>, with search, strict link checks and automated
|
|
33
|
+
GitHub Pages publishing. The site tracks main and identifies itself as development documentation.
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
* Admission is bounded by default to `4 * concurrency`; a positive `max_admitted` overrides
|
|
38
|
+
this. Long retry backoffs can now pause input consumption. This bounds scheduler state
|
|
39
|
+
counts, not payload bytes or total memory; MemoryStore still retains accumulated data.
|
|
40
|
+
* `tasks(limit=0)` returns an empty list; negative and non-integer task limits are rejected.
|
|
41
|
+
Suite timestamp indexes add disk/write and first-writer migration cost. Exact duration
|
|
42
|
+
percentiles still read and sort matching pipeline durations.
|
|
43
|
+
* English and Chinese export documentation now covers the `results` row type (#77), and
|
|
44
|
+
streaming-memory claims describe admission and store retention separately (#81).
|
|
45
|
+
* Development and documentation lockfile downloads use official PyPI URLs for portable CI
|
|
46
|
+
installation, without changing artifact hashes or adding library runtime dependencies (#82).
|
|
47
|
+
|
|
9
48
|
## [0.3.2] — 2026-09-21
|
|
10
49
|
|
|
11
50
|
### Changed
|
|
@@ -613,6 +652,7 @@ hard limit; asyncio tasks only (wrap blocking code with `asyncio.to_thread`); on
|
|
|
613
652
|
balance is statistical; a merged report is a union, not a sum; a pipeline is a linear chain; the HTTP endpoint
|
|
614
653
|
is unauthenticated.
|
|
615
654
|
|
|
655
|
+
[0.3.3]: https://github.com/Hazer-BJTU/pyattacker/releases/tag/v0.3.3
|
|
616
656
|
[0.3.0]: https://github.com/Hazer-BJTU/pyattacker/releases/tag/v0.3.0
|
|
617
657
|
[0.2.0]: https://github.com/Hazer-BJTU/pyattacker/releases/tag/v0.2.0
|
|
618
658
|
[0.1.1]: https://github.com/Hazer-BJTU/pyattacker/releases/tag/v0.1.1
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pyattacker
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: Artifact-centric, resumable async task orchestration: pipeline / task / resource pool.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Hazer-BJTU/pyattacker
|
|
6
6
|
Project-URL: Repository, https://github.com/Hazer-BJTU/pyattacker
|
|
7
7
|
Project-URL: Issues, https://github.com/Hazer-BJTU/pyattacker/issues
|
|
8
8
|
Project-URL: Changelog, https://github.com/Hazer-BJTU/pyattacker/blob/main/CHANGELOG.md
|
|
9
|
-
Project-URL: Documentation, https://github.
|
|
9
|
+
Project-URL: Documentation, https://hazer-bjtu.github.io/pyattacker/
|
|
10
10
|
Project-URL: Design, https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/design.md
|
|
11
11
|
Author: pyattacker contributors
|
|
12
12
|
License: MIT
|
|
@@ -34,6 +34,8 @@ Description-Content-Type: text/markdown
|
|
|
34
34
|
|
|
35
35
|
**English** | [简体中文](README.zh-CN.md)
|
|
36
36
|
|
|
37
|
+
[Documentation](https://hazer-bjtu.github.io/pyattacker/)
|
|
38
|
+
|
|
37
39
|
> Run tens of thousands of independent tasks to completion — resumably, observably, and without
|
|
38
40
|
> reimplementing endpoint pools, retries and "which rows already ran" for the fifth time.
|
|
39
41
|
|
|
@@ -119,7 +121,7 @@ async def fetch(row: dict, ctx) -> dict:
|
|
|
119
121
|
|
|
120
122
|
|
|
121
123
|
def dataset_rows():
|
|
122
|
-
"""Your dataset.
|
|
124
|
+
"""Your dataset. `map` streams generators; max_admitted bounds unfinished pipelines."""
|
|
123
125
|
return [{"q": f"question-{i}"} for i in range(4)]
|
|
124
126
|
|
|
125
127
|
|
|
@@ -135,6 +137,11 @@ with Runner(store="runs/qa.db", pools=[pool], concurrency=8) as runner:
|
|
|
135
137
|
report.export_jsonl("runs/qa.jsonl")
|
|
136
138
|
```
|
|
137
139
|
|
|
140
|
+
Streaming input uses `max_admitted` (default: `4 * concurrency`) to bound queued,
|
|
141
|
+
running and retry-delayed pipelines together. This is a count limit, not a byte
|
|
142
|
+
limit; task payloads, source buffers and Store retention also consume memory.
|
|
143
|
+
The default `MemoryStore` retains results as data grows. See [RunConfig](docs/reference.md#runconfig).
|
|
144
|
+
|
|
138
145
|
`ctx.acquire()` falls back to the task's declared `resource`, and `lease.report(ok=...)` feeds the pool's
|
|
139
146
|
health and quota accounting.
|
|
140
147
|
|
|
@@ -524,6 +531,11 @@ every known tradeoff with its reason.
|
|
|
524
531
|
|
|
525
532
|
## Status
|
|
526
533
|
|
|
534
|
+
**0.3.3 — reliability, bounded admission and online documentation.** Atomic write-behind batches,
|
|
535
|
+
correct and faster Suite queries, dashboard HTML injection fixes, and a configurable
|
|
536
|
+
`max_admitted` limit (default: `4 * concurrency`). The bilingual documentation site is now available.
|
|
537
|
+
See the [release notes](https://github.com/Hazer-BJTU/pyattacker/blob/main/CHANGELOG.md#033--2026-09-21).
|
|
538
|
+
|
|
527
539
|
**0.3.2 — experiment suites and supported control flow.** Independent experiments can share resource
|
|
528
540
|
pools and global concurrency, with unified run/resume/report commands and optional per-experiment output
|
|
529
541
|
directories. Durable run histories preserve earlier outcomes after resume. Handoffs, forward jumps,
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
**English** | [简体中文](README.zh-CN.md)
|
|
11
11
|
|
|
12
|
+
[Documentation](https://hazer-bjtu.github.io/pyattacker/)
|
|
13
|
+
|
|
12
14
|
> Run tens of thousands of independent tasks to completion — resumably, observably, and without
|
|
13
15
|
> reimplementing endpoint pools, retries and "which rows already ran" for the fifth time.
|
|
14
16
|
|
|
@@ -94,7 +96,7 @@ async def fetch(row: dict, ctx) -> dict:
|
|
|
94
96
|
|
|
95
97
|
|
|
96
98
|
def dataset_rows():
|
|
97
|
-
"""Your dataset.
|
|
99
|
+
"""Your dataset. `map` streams generators; max_admitted bounds unfinished pipelines."""
|
|
98
100
|
return [{"q": f"question-{i}"} for i in range(4)]
|
|
99
101
|
|
|
100
102
|
|
|
@@ -110,6 +112,11 @@ with Runner(store="runs/qa.db", pools=[pool], concurrency=8) as runner:
|
|
|
110
112
|
report.export_jsonl("runs/qa.jsonl")
|
|
111
113
|
```
|
|
112
114
|
|
|
115
|
+
Streaming input uses `max_admitted` (default: `4 * concurrency`) to bound queued,
|
|
116
|
+
running and retry-delayed pipelines together. This is a count limit, not a byte
|
|
117
|
+
limit; task payloads, source buffers and Store retention also consume memory.
|
|
118
|
+
The default `MemoryStore` retains results as data grows. See [RunConfig](docs/reference.md#runconfig).
|
|
119
|
+
|
|
113
120
|
`ctx.acquire()` falls back to the task's declared `resource`, and `lease.report(ok=...)` feeds the pool's
|
|
114
121
|
health and quota accounting.
|
|
115
122
|
|
|
@@ -499,6 +506,11 @@ every known tradeoff with its reason.
|
|
|
499
506
|
|
|
500
507
|
## Status
|
|
501
508
|
|
|
509
|
+
**0.3.3 — reliability, bounded admission and online documentation.** Atomic write-behind batches,
|
|
510
|
+
correct and faster Suite queries, dashboard HTML injection fixes, and a configurable
|
|
511
|
+
`max_admitted` limit (default: `4 * concurrency`). The bilingual documentation site is now available.
|
|
512
|
+
See the [release notes](https://github.com/Hazer-BJTU/pyattacker/blob/main/CHANGELOG.md#033--2026-09-21).
|
|
513
|
+
|
|
502
514
|
**0.3.2 — experiment suites and supported control flow.** Independent experiments can share resource
|
|
503
515
|
pools and global concurrency, with unified run/resume/report commands and optional per-experiment output
|
|
504
516
|
directories. Durable run histories preserve earlier outcomes after resume. Handoffs, forward jumps,
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
[English](README.md) | **简体中文**
|
|
11
11
|
|
|
12
|
+
[在线文档](https://hazer-bjtu.github.io/pyattacker/zh-CN/)
|
|
13
|
+
|
|
12
14
|
> 把几万条独立任务一口气跑完——崩了能续、跑着能看,而且不用第五次重写端点池、重试逻辑和"哪些行已经跑过"。
|
|
13
15
|
|
|
14
16
|
## 它解决什么问题
|
|
@@ -76,7 +78,7 @@ async def fetch(row: dict, ctx) -> dict:
|
|
|
76
78
|
|
|
77
79
|
|
|
78
80
|
def dataset_rows():
|
|
79
|
-
"""Your dataset.
|
|
81
|
+
"""Your dataset. `map` streams generators; max_admitted bounds unfinished pipelines."""
|
|
80
82
|
return [{"q": f"question-{i}"} for i in range(4)]
|
|
81
83
|
|
|
82
84
|
|
|
@@ -92,6 +94,11 @@ with Runner(store="runs/qa.db", pools=[pool], concurrency=8) as runner:
|
|
|
92
94
|
report.export_jsonl("runs/qa.jsonl")
|
|
93
95
|
```
|
|
94
96
|
|
|
97
|
+
流式输入通过 `max_admitted`(默认 `4 * concurrency`)限制排队、执行和等待重试的
|
|
98
|
+
流水线总数。这是数量上限,不是字节上限;任务载荷、源缓冲和 Store 保留数据也占用内存。
|
|
99
|
+
默认 `MemoryStore` 会随数据量增长保留结果。详见 [RunConfig](docs/zh-CN/reference.md#runconfig)。
|
|
100
|
+
|
|
101
|
+
|
|
95
102
|
`ctx.acquire()` 不指定参数时,自动用任务声明的 `resource`。`lease.report(ok=...)` 把健康和配额信息喂回资源池。
|
|
96
103
|
|
|
97
104
|
真正的网络调用写在哪?——你自己写,等框架跑通了再加:
|
|
@@ -393,6 +400,10 @@ uv run pyattacker run -c examples/qa_eval.yaml --limit 40
|
|
|
393
400
|
|
|
394
401
|
## 当前状态
|
|
395
402
|
|
|
403
|
+
**0.3.3——可靠性、准入上限与在线文档。** 提供原子 write-behind 批次、正确且更快的 Suite 查询、
|
|
404
|
+
仪表盘 HTML 注入修复,以及可配置的 `max_admitted` 上限(默认 `4 * concurrency`)。
|
|
405
|
+
双语文档站现已上线。详见[发布说明](https://github.com/Hazer-BJTU/pyattacker/blob/main/CHANGELOG.md#033--2026-09-21)。
|
|
406
|
+
|
|
396
407
|
**0.3.2——组合实验与正式控制流特性。** 独立子实验可共享资源池和总并发,统一运行、恢复和查看结果,
|
|
397
408
|
并可按实验分目录存储。持久化的运行历史在恢复后仍保留此前的结果。交接、正向跳转、回退和全量重试
|
|
398
409
|
作为正式公共特性维护,通过 `control` 显式声明后启用。
|
|
@@ -46,6 +46,7 @@ pyattacker run -c config.yaml [options]
|
|
|
46
46
|
| `--limit N` | run only the first N pipelines (a smoke test over a real dataset) |
|
|
47
47
|
| `--store PATH` | override `run.store`. Used verbatim with `--shard`; otherwise a shard suffix is appended |
|
|
48
48
|
| `--concurrency N` | override `run.concurrency` — attempts in flight, not pipelines alive |
|
|
49
|
+
| `--max-admitted N` | override `run.max_admitted`: queued + running + delayed pipelines per Runner (positive integer; default `4 * concurrency`) |
|
|
49
50
|
| `--journal {full,summary}` | `full` (default) stores artifact payloads, which is what makes resume work at task granularity; `summary` keeps digests only |
|
|
50
51
|
| `--label TEXT` | a label recorded on the run, for telling runs apart later |
|
|
51
52
|
| `--resume` | skip finished pipelines, restart failed ones at their checkpoint |
|
|
@@ -303,7 +304,7 @@ source: { kind: jsonl, path: data.jsonl, limit: 100, key_field: id, repeats: 1 }
|
|
|
303
304
|
```
|
|
304
305
|
|
|
305
306
|
The `run:` block accepts exactly the fields the CLI maps onto `RunConfig`: `store`, `journal`,
|
|
306
|
-
`concurrency`, `label`, `heartbeat_s`, `grace_s`, `stale_after_s`, `strict_leases`,
|
|
307
|
+
`concurrency`, `max_admitted`, `label`, `heartbeat_s`, `grace_s`, `stale_after_s`, `strict_leases`,
|
|
307
308
|
`stop_after_failures`, `stop_after_s`, `max_handoffs`, `retry_succeeded`, `fresh_restart`, `seed`, `notes`,
|
|
308
309
|
`write_behind`,
|
|
309
310
|
`write_batch`, `flush_interval`, `artifact_backend` and `meta`. Anything else is a config error, not a
|
|
@@ -397,7 +398,7 @@ Python examples, `HistoryArtifact` included.
|
|
|
397
398
|
* [`docs/tutorial.md`](tutorial.md) — the guided path, with runnable programs
|
|
398
399
|
* [`docs/reference.md`](reference.md) — every class and function the SDK exposes
|
|
399
400
|
* [`docs/design.md`](design.md) — why the CLI has these commands and no others
|
|
400
|
-
* [`README.md`](
|
|
401
|
+
* [`README.md`](https://github.com/Hazer-BJTU/pyattacker/blob/main/README.md) — the compact tour
|
|
401
402
|
|
|
402
403
|
## Suite commands
|
|
403
404
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**English** | [简体中文](zh-CN/design.md)
|
|
4
4
|
|
|
5
|
-
> Version: 0.3.
|
|
5
|
+
> Version: 0.3.3 — M0–M5 complete, including the supported control flow of §4.8 (forward handoffs and
|
|
6
6
|
> backward traversal), enabled through explicit declarations. See "Implemented / Left for later" in Section 9.
|
|
7
7
|
> In one sentence: **an async task orchestration framework centered on the artifact, using the pipeline as the unit of completion, and the resource pool as the only shared surface.**
|
|
8
8
|
> It does not touch the network, does not do reduction, and does not do DAG scheduling — it is only responsible for "running tens of thousands of mutually independent pipelines to completion, reliably, recoverably, and observably".
|
|
@@ -318,6 +318,11 @@ short, and with a fake clock the pump advances virtual time instantly (so tests
|
|
|
318
318
|
`pipelines`, `tasks`, `artifacts` — always go straight through, because a checkpoint that is not durable yet
|
|
319
319
|
is not a checkpoint. A `SIGKILL` can therefore lose the last batch of history while every checkpoint stays
|
|
320
320
|
intact; `--no-write-behind` trades throughput for an immediate commit per attempt.
|
|
321
|
+
SQLite commits a whole attempts/events batch in one transaction, publishing row IDs only after success.
|
|
322
|
+
A failed batch rolls back and remains buffered for retry. Suite stores batch per database, not across
|
|
323
|
+
all child databases. Legacy stores without atomic `write_facts` acknowledge one row at a time; after
|
|
324
|
+
an exception, the wrapper blocks automatic replay because that call may already have committed.
|
|
325
|
+
See [fact batch capability](reference.md#fact-batches-and-failure-recovery) for the recovery contract.
|
|
321
326
|
|
|
322
327
|
### 4.6 Monitoring: Operational State and Application Reports
|
|
323
328
|
|
|
@@ -655,8 +660,9 @@ digests and metadata (saves space, at the cost that intermediate artifacts canno
|
|
|
655
660
|
rerun the whole pipeline, and it leaves a `pipeline.checkpoint_missing` event behind).
|
|
656
661
|
|
|
657
662
|
**Write policy**: all store methods are synchronous — this keeps critical writes such as "record a running row
|
|
658
|
-
before the attempt starts" from being interrupted by cancellation.
|
|
659
|
-
|
|
663
|
+
before the attempt starts" from being interrupted by cancellation. Write-behind combines append-only
|
|
664
|
+
attempts/events through the optional atomic `write_facts` capability; state writes remain synchronous
|
|
665
|
+
and the required `Store` interface is unchanged.
|
|
660
666
|
|
|
661
667
|
**Read policy**: the list queries above may materialize their result — a report wants a list. A whole-kind
|
|
662
668
|
read that must stay bounded (an export of a large store) goes through the optional paged-iteration
|
|
@@ -710,7 +716,7 @@ pool = Pool("apis", [Resource.create("llm", capacity=4,
|
|
|
710
716
|
template = pipeline("qa_eval", fetch | ask | judge | metrics, tags={"bench": "mmlu"})
|
|
711
717
|
|
|
712
718
|
with Runner(store="runs/qa.db", pools=[pool], concurrency=64) as runner:
|
|
713
|
-
report = runner.run(template.map(dataset_rows, repeats=3)) #
|
|
719
|
+
report = runner.run(template.map(dataset_rows, repeats=3)) # streams seeds under the max_admitted budget
|
|
714
720
|
print(report.summary())
|
|
715
721
|
report.export_jsonl("runs/qa.jsonl")
|
|
716
722
|
```
|
|
@@ -791,7 +797,8 @@ children can never fight over one file. Each child also gets `PYATACKER_SHARD=I/
|
|
|
791
797
|
can record its own provenance.
|
|
792
798
|
|
|
793
799
|
**Row shapes** (`--rows`) for whatever consumes the results: `pipelines` (nested, the default), `tasks`,
|
|
794
|
-
`attempts` (with the retry `decision`), `events`, `artifacts
|
|
800
|
+
`attempts` (with the retry `decision`), `events`, `artifacts`, and `results` (one compact row per pipeline,
|
|
801
|
+
with its key, repeat, status, final payload and error; failed/interrupted rows are retained). **Formats** (`--format`): `jsonl`, `json`, `csv`.
|
|
795
802
|
CSV takes its header from the first `header_rows` rows and folds anything introduced later into an `extra`
|
|
796
803
|
column, which keeps memory flat without silently dropping fields. Every kind is exported in full — `events`
|
|
797
804
|
used to stop at the newest 100 000 rows — and read in bounded batches; see
|
|
@@ -873,7 +880,7 @@ src/pyattacker/
|
|
|
873
880
|
monitor.py snapshot rendering + watch
|
|
874
881
|
shard.py deterministic shard assignment + per-shard store paths (M3)
|
|
875
882
|
merge.py join N shard stores: de-duplicate by pipeline_id, recompute statistics (M3)
|
|
876
|
-
export.py row shapes (pipelines/tasks/attempts/events/artifacts) and formats (jsonl/json/csv) (M3)
|
|
883
|
+
export.py row shapes (pipelines/tasks/attempts/events/artifacts/results) and formats (jsonl/json/csv) (M3)
|
|
877
884
|
declarative.py YAML/TOML → pools + pipeline + source (including ${ENV} expansion)
|
|
878
885
|
plugins.py entry-point discovery for tasks/algorithms/codecs/stores (M4)
|
|
879
886
|
backends.py where artifact payloads live: inline / content-addressed files / null (M4)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Maintaining the documentation
|
|
2
|
+
|
|
3
|
+
**English** | [简体中文](zh-CN/documentation.md)
|
|
4
|
+
|
|
5
|
+
The website uses the Markdown files in `docs/` directly. The Python API reference is currently
|
|
6
|
+
curated Markdown; automatic API extraction is not part of this first site migration.
|
|
7
|
+
|
|
8
|
+
## Preview locally
|
|
9
|
+
|
|
10
|
+
From the repository root, install the locked documentation dependencies and run the server:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
uv sync --locked --group docs
|
|
14
|
+
uv run --group docs mkdocs serve
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Open the address printed by MkDocs (normally `http://127.0.0.1:8000/pyattacker/`).
|
|
18
|
+
The `/pyattacker/` prefix matches GitHub Pages, so relative links can be checked locally.
|
|
19
|
+
|
|
20
|
+
## Check a change
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
uv run --group docs mkdocs build --strict
|
|
24
|
+
uv run pytest tests/test_docs_i18n.py tests/test_docs_examples.py tests/test_tutorial.py
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The generated `site/` directory is ignored by Git. Keep dependencies in the `docs` dependency
|
|
28
|
+
group and commit `uv.lock` updates. They are not library runtime dependencies.
|
|
29
|
+
Add new pages to `mkdocs.yml`, with a Chinese counterpart and reciprocal language links.
|
|
30
|
+
Extend the document pairs in `tests/test_docs_i18n.py`; keep runnable code blocks identical
|
|
31
|
+
between languages. Existing tutorial/reference markers must remain so the tests execute examples.
|
|
32
|
+
Links to files outside `docs/` should use full GitHub URLs; Markdown pages inside `docs/` use
|
|
33
|
+
relative links. Strict builds check page links and anchors, but do not check external URL availability.
|
|
34
|
+
|
|
35
|
+
## Publish
|
|
36
|
+
|
|
37
|
+
The repository's Pages source is **GitHub Actions**. The Documentation workflow builds on PRs,
|
|
38
|
+
pushes to `main`, and manual runs. PRs only validate; pushes to `main` or manual runs on `main`
|
|
39
|
+
upload the site and deploy through the `github-pages` environment. No custom domain is required.
|
|
40
|
+
|
|
41
|
+
The site is published at <https://hazer-bjtu.github.io/pyattacker/>. Check the repository's Actions
|
|
42
|
+
tab if a build or deployment fails. Environment protection rules may require an approval before
|
|
43
|
+
deployment. The deployment job alone receives Pages and OIDC write permissions.
|
|
44
|
+
|
|
45
|
+
## Version policy
|
|
46
|
+
|
|
47
|
+
The first site tracks `main` and displays a development-version notice on every page.
|
|
48
|
+
It is not a frozen reference for the latest package release. A later versioned publishing setup
|
|
49
|
+
can add a stable release alongside development documentation.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Preserve the repository's GitHub-style heading links in the rendered website."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def github_slug(text: str, separator: str) -> str:
|
|
5
|
+
# Markdown has already stripped inline markup before calling the TOC slugger.
|
|
6
|
+
# Keep Unicode and repeated spaces: 'Step 1 — task' becomes 'step-1--task'.
|
|
7
|
+
return "".join(
|
|
8
|
+
char if char.isalnum() or char in "-_" else separator if char.isspace() else ""
|
|
9
|
+
for char in text.lower()
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def on_config(config):
|
|
14
|
+
config.mdx_configs["toc"]["slugify"] = github_slug
|
|
15
|
+
return config
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# pyattacker
|
|
2
|
+
|
|
3
|
+
**English** | [简体中文](zh-CN/index.md)
|
|
4
|
+
|
|
5
|
+
Build resumable Python workflows for model evaluations and other dataset-driven tasks.
|
|
6
|
+
Combine task chains, shared resource pools, retries and durable checkpoints under one Runner.
|
|
7
|
+
|
|
8
|
+
[Start the tutorial](tutorial.md){ .md-button .md-button--primary }
|
|
9
|
+
[Python API reference](reference.md){ .md-button }
|
|
10
|
+
|
|
11
|
+
## Start here
|
|
12
|
+
|
|
13
|
+
Install Python 3.11 or later, then install the library:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
python -m pip install pyattacker
|
|
17
|
+
pyattacker demo
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
For YAML configurations, install `pyattacker[yaml]`. The base library has no runtime dependencies.
|
|
21
|
+
This site follows the `main` branch; your installed release may not yet include every feature shown.
|
|
22
|
+
To try the development version, clone the repository and follow the [tutorial setup](tutorial.md#step-0--install-and-sanity-check).
|
|
23
|
+
|
|
24
|
+
## Choose a path
|
|
25
|
+
|
|
26
|
+
| Your goal | Read |
|
|
27
|
+
| --- | --- |
|
|
28
|
+
| Write a task and run a dataset | [Step-by-step tutorial](tutorial.md) |
|
|
29
|
+
| Share endpoints, retry failures and resume work | [Resources, retry and recovery tutorial](tutorial.md#find-what-you-need) |
|
|
30
|
+
| Compare several experiments under one Runner | [Experiment suites](suites.md) |
|
|
31
|
+
| Look up Python interfaces and behavior | [API reference](reference.md) |
|
|
32
|
+
| Run from JSON, TOML or YAML | [CLI and configuration](cli.md) |
|
|
33
|
+
| Understand checkpoints and scheduling guarantees | [Architecture](design.md) |
|
|
34
|
+
| Compare resource scheduling strategies | [Benchmarking](benchmark.md) |
|
|
35
|
+
|
|
36
|
+
## Know what is retained
|
|
37
|
+
|
|
38
|
+
`Runner` defaults to an in-memory store. Choose a file-backed store to retain checkpoints across
|
|
39
|
+
processes. `concurrency` limits executing attempts; `max_admitted` bounds queued, executing and
|
|
40
|
+
delayed pipelines together. Payloads, input buffers and accumulated store data also consume memory.
|
|
41
|
+
See [RunConfig](reference.md#runconfig) before scaling a workload.
|
|
42
|
+
|
|
43
|
+
## Explore and contribute
|
|
44
|
+
|
|
45
|
+
Browse the [runnable examples](https://github.com/Hazer-BJTU/pyattacker/tree/main/examples),
|
|
46
|
+
[release history](https://github.com/Hazer-BJTU/pyattacker/blob/main/CHANGELOG.md), or
|
|
47
|
+
[report an issue](https://github.com/Hazer-BJTU/pyattacker/issues).
|
|
48
|
+
For local previews and publishing, see [Maintaining the documentation](documentation.md).
|
|
@@ -19,7 +19,7 @@ shaped this way.
|
|
|
19
19
|
* **Defaults matter here.** Two in particular surprise people: `Retrying(max_attempts=1)` means *no retries*
|
|
20
20
|
unless you ask, and `Runner(store=":memory:")` means nothing is persisted unless you give it a path.
|
|
21
21
|
* **Some examples are complete programs.** A block whose first line is `# reference/<name>.py` is written out
|
|
22
|
-
and executed by [`tests/test_docs_examples.py`](
|
|
22
|
+
and executed by [`tests/test_docs_examples.py`](https://github.com/Hazer-BJTU/pyattacker/blob/main/tests/test_docs_examples.py) on every test run — the
|
|
23
23
|
README and the CLI document share that contract. Blocks without a marker are fragments on purpose.
|
|
24
24
|
|
|
25
25
|
## Contents
|
|
@@ -330,7 +330,7 @@ runner.run(template.map(rows)) # one pipeline per r
|
|
|
330
330
|
runner.run(template.map(rows, repeats=5)) # pass@5
|
|
331
331
|
runner.run(template.map(rows, key_of=lambda r: r["qid"])) # ids from your dataset's primary key
|
|
332
332
|
|
|
333
|
-
def stream(): #
|
|
333
|
+
def stream(): # streams seeds; max_admitted bounds unfinished pipelines
|
|
334
334
|
with open("dataset.jsonl") as fh:
|
|
335
335
|
for line in fh:
|
|
336
336
|
yield json.loads(line)
|
|
@@ -760,7 +760,27 @@ live = runner.stats() # while runnin
|
|
|
760
760
|
print(live["in_flight_pipelines"], live["delayed_pipelines"]) # in flight vs parked in backoff
|
|
761
761
|
```
|
|
762
762
|
|
|
763
|
-
`run()`
|
|
763
|
+
`run()` reads a `PipelineSpec` iterator only while admission capacity is available.
|
|
764
|
+
`max_admitted=None` (default) resolves to `4 * concurrency`; a positive integer sets
|
|
765
|
+
an explicit limit on queued, executing and delayed pipelines **together**. Retries
|
|
766
|
+
retain their admission and can return to workers while new input is paused. Lower
|
|
767
|
+
limits reduce memory held by unfinished work, but can leave workers idle during
|
|
768
|
+
backoff; a limit below concurrency also limits useful parallelism. Suite members
|
|
769
|
+
share one budget per Runner; shard processes each have their own budget.
|
|
770
|
+
|
|
771
|
+
This bounds the number of unfinished scheduler states, not total bytes or process
|
|
772
|
+
RSS. Memory also includes each retained seed/current value, source-side buffering,
|
|
773
|
+
worker references, write-behind buffers, caches and reporting queries. The default
|
|
774
|
+
`MemoryStore` retains accumulated records and artifact payloads as the run grows;
|
|
775
|
+
use a file-backed store and a streaming source for large datasets. A pre-built input
|
|
776
|
+
list or unbounded task payload remains outside the admission guarantee.
|
|
777
|
+
|
|
778
|
+
`runner.stats()` exposes `admitted_pipelines` (admitted minus terminal) and the
|
|
779
|
+
effective `max_admitted`; the run's persisted config records that effective limit.
|
|
780
|
+
Stop, cancellation and run budgets also interrupt admission waits. As before,
|
|
781
|
+
synchronous input iteration and blocking task code must return control before the
|
|
782
|
+
async scheduler can respond.
|
|
783
|
+
|
|
764
784
|
|
|
765
785
|
### `RunConfig`
|
|
766
786
|
|
|
@@ -771,6 +791,7 @@ Everything that shapes one run. Pass a `RunConfig`, or pass its fields as keywor
|
|
|
771
791
|
| `store` | `":memory:"` | `":memory:"`, a SQLite path, a plugin URI, or an open store instance |
|
|
772
792
|
| `journal` | `"full"` | `"full"` keeps artifact payloads (**required for task-level resume**); `"summary"` keeps only metadata |
|
|
773
793
|
| `concurrency` | `16` | max attempts in flight; a pipeline parked in a retry backoff does not hold a slot |
|
|
794
|
+
| `max_admitted` | `None` | queued + executing + delayed pipeline limit; auto = `4 * concurrency`, otherwise a positive integer |
|
|
774
795
|
| `label` | `""` | a label recorded on the run |
|
|
775
796
|
| `run_id` | `None` | explicit run id; default is timestamp + digest |
|
|
776
797
|
| `resume` | `False` | mark pipelines abandoned by dead runs as resumable before scheduling |
|
|
@@ -1404,6 +1425,37 @@ with closing(open_store("runs/qa.db")) as store: # reopen a finished run, a
|
|
|
1404
1425
|
`spec` can be `":memory:"`, a path, a plugin URI (`"s3://bucket/runs.db"`), or an already-open store.
|
|
1405
1426
|
Reading a live store while a run writes to it is supported — WAL allows one writer and many readers.
|
|
1406
1427
|
|
|
1428
|
+
### Fact batches and failure recovery
|
|
1429
|
+
|
|
1430
|
+
`pyattacker.store.FactBatchStore` is an optional capability, separate from the required `Store`
|
|
1431
|
+
protocol: `write_facts(attempts, events)` atomically commits both sequences and assigns their IDs
|
|
1432
|
+
only after success. On exception no batch rows may be committed and input IDs must be unchanged.
|
|
1433
|
+
The capability owns its transaction; SQLite rejects a call inside an existing transaction without
|
|
1434
|
+
committing or rolling back the caller's work. An empty batch performs no transaction.
|
|
1435
|
+
|
|
1436
|
+
`WriteBehindStore` uses this capability when available. SQLite implements it, including the SQLite
|
|
1437
|
+
stores wrapped by either Suite layout. A failed atomic batch stays buffered and can be flushed again.
|
|
1438
|
+
In a split Suite, a successful child batch stays committed if a later child's batch fails; retry only
|
|
1439
|
+
writes the remaining batches. Eviction flushes before removing a child from the connection cache,
|
|
1440
|
+
so a failed flush leaves that child and its pending batch available for retry. Checkpoint, artifact,
|
|
1441
|
+
and task-state writes still bypass the buffer; handoff/visit transactions keep their existing boundaries.
|
|
1442
|
+
|
|
1443
|
+
A legacy store without `write_facts` is supported through individual `record_attempt`/`emit_event`
|
|
1444
|
+
calls. Each normally returned call removes that confirmed record from the buffer. If a call raises,
|
|
1445
|
+
the original exception propagates and the failing record plus its unattempted suffix stay buffered.
|
|
1446
|
+
Because the failing call might have committed before raising, this wrapper then refuses further
|
|
1447
|
+
fact writes and flush-dependent reads/flushes with `StoreUnavailable`, chaining the original error.
|
|
1448
|
+
`buffer_stats()["flush_blocked"]` reports this condition; `pending` includes the uncertain record.
|
|
1449
|
+
Reconcile persisted records with the original submitted facts before constructing another wrapper;
|
|
1450
|
+
do not blindly replay the pending suffix. There is no automatic exactly-once guarantee for an
|
|
1451
|
+
ambiguous legacy write. Custom stores that can guarantee atomic rollback should implement the optional
|
|
1452
|
+
capability instead. SQLite subclasses that customize fact writes must also customize `write_facts`
|
|
1453
|
+
or disable it with `write_facts = None` to keep using their individual write overrides.
|
|
1454
|
+
|
|
1455
|
+
Closing always releases the underlying store even when flush raises; the error remains visible.
|
|
1456
|
+
Suite close attempts every child, the catalog and the ownership lock, and propagates the first error.
|
|
1457
|
+
After a failed close, reopen/reconcile as needed; do not retry a flush on the closed connection.
|
|
1458
|
+
|
|
1407
1459
|
### Tables and readers
|
|
1408
1460
|
|
|
1409
1461
|
| Table | One row per | Read with |
|
|
@@ -1888,6 +1940,7 @@ One row per kind, in the order `limit` truncates:
|
|
|
1888
1940
|
| `attempts` | attempt, including each retry `decision` | `attempt_id` (write order) |
|
|
1889
1941
|
| `events` | structured event | `event_id` (write order, oldest first) |
|
|
1890
1942
|
| `artifacts` | artifact, intermediate ones included | pipeline order, then `seq`, then `artifact_id` |
|
|
1943
|
+
| `results` | pipeline result: key, repeat, state, final payload and error; failed/interrupted rows included | `created_at`, then `pipeline_id` |
|
|
1891
1944
|
|
|
1892
1945
|
`limit` means the same thing for every kind — it counts rows of that kind, including `artifacts`,
|
|
1893
1946
|
where it used to count pipelines:
|
|
@@ -2030,7 +2083,7 @@ print(PLUGINS.errors()) # {name: reason} for everything that failed to lo
|
|
|
2030
2083
|
Built-ins resolve first, and a plugin that raises on import is recorded rather than propagated — so a broken
|
|
2031
2084
|
plugin cannot take a run down, and it cannot fail silently either. `pyattacker plugins` prints the same
|
|
2032
2085
|
information. `PluginRegistry` is the type; `PLUGINS` is the process-wide instance. A complete worked package
|
|
2033
|
-
is in [`examples/plugin_package/`](
|
|
2086
|
+
is in [`examples/plugin_package/`](https://github.com/Hazer-BJTU/pyattacker/blob/main/examples/plugin_package/README.md).
|
|
2034
2087
|
|
|
2035
2088
|
---
|
|
2036
2089
|
|
|
@@ -2177,7 +2230,7 @@ runner.run(template.map(jsonl_source("dataset.jsonl", limit=500)))
|
|
|
2177
2230
|
|
|
2178
2231
|
An application can publish its latest experiment values while a run is active. Pyattacker stores
|
|
2179
2232
|
and displays the values; the application computes them. For a runnable accuracy example, see
|
|
2180
|
-
[`examples/live_metrics.py`](
|
|
2233
|
+
[`examples/live_metrics.py`](https://github.com/Hazer-BJTU/pyattacker/blob/main/examples/live_metrics.py).
|
|
2181
2234
|
|
|
2182
2235
|
```python
|
|
2183
2236
|
runner.report_metric("evaluated", completed, label="Evaluated")
|
|
@@ -2273,7 +2326,7 @@ command line.
|
|
|
2273
2326
|
| [`docs/tutorial.md`](tutorial.md) | the guided path: seventeen runnable steps |
|
|
2274
2327
|
| [`docs/cli.md`](cli.md) | commands, flags, exit codes, config file format |
|
|
2275
2328
|
| [`docs/design.md`](design.md) | the model, the invariants, and the tradeoffs behind these APIs |
|
|
2276
|
-
| [`examples/`](
|
|
2329
|
+
| [`examples/`](https://github.com/Hazer-BJTU/pyattacker/tree/main/examples) | complete programs, including a measured comparison of pipeline shapes |
|
|
2277
2330
|
|
|
2278
2331
|
## Suite API
|
|
2279
2332
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
**English** | [简体中文](zh-CN/releasing.md)
|
|
4
4
|
|
|
5
5
|
Written for maintainers with push access. Publishing is automated: you push a tag, and
|
|
6
|
-
[`.github/workflows/release.yml`](
|
|
6
|
+
[`.github/workflows/release.yml`](https://github.com/Hazer-BJTU/pyattacker/blob/main/.github/workflows/release.yml) does the rest.
|
|
7
7
|
|
|
8
8
|
## One-time setup: trusted publishing
|
|
9
9
|
|
|
@@ -47,7 +47,8 @@ cannot be borrowed by a branch push.
|
|
|
47
47
|
release notes. No section means the release still publishes, with a placeholder and a warning.
|
|
48
48
|
|
|
49
49
|
**2. Bump the version in two places** — they must agree, and the workflow refuses the release if the tag
|
|
50
|
-
disagrees with either
|
|
50
|
+
disagrees with either. Also update the version headers in `docs/design.md` and
|
|
51
|
+
`docs/zh-CN/design.md`, and the current-status summaries in both READMEs:
|
|
51
52
|
|
|
52
53
|
```bash
|
|
53
54
|
# pyproject.toml: version = "X.Y.Z"
|