pyattacker 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyattacker-0.2.0/.github/workflows/ci.yml +115 -0
- pyattacker-0.2.0/.github/workflows/release.yml +288 -0
- pyattacker-0.2.0/CHANGELOG.md +359 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/PKG-INFO +143 -29
- {pyattacker-0.1.0 → pyattacker-0.2.0}/README.md +140 -27
- pyattacker-0.2.0/assets/logo/icon.png +0 -0
- pyattacker-0.2.0/assets/logo/title.png +0 -0
- pyattacker-0.2.0/docs/benchmark.md +418 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/docs/cli.md +108 -7
- {pyattacker-0.1.0 → pyattacker-0.2.0}/docs/design.md +90 -7
- {pyattacker-0.1.0 → pyattacker-0.2.0}/docs/reference.md +255 -10
- pyattacker-0.2.0/docs/releasing.md +144 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/docs/tutorial.md +13 -2
- {pyattacker-0.1.0 → pyattacker-0.2.0}/pyproject.toml +23 -5
- {pyattacker-0.1.0 → pyattacker-0.2.0}/src/pyattacker/__init__.py +5 -2
- pyattacker-0.2.0/src/pyattacker/benchmark/__init__.py +66 -0
- pyattacker-0.2.0/src/pyattacker/benchmark/clock.py +259 -0
- pyattacker-0.2.0/src/pyattacker/benchmark/harness.py +391 -0
- pyattacker-0.2.0/src/pyattacker/benchmark/metrics.py +245 -0
- pyattacker-0.2.0/src/pyattacker/benchmark/provider.py +296 -0
- pyattacker-0.2.0/src/pyattacker/benchmark/report.py +370 -0
- pyattacker-0.2.0/src/pyattacker/benchmark/scenario.py +296 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/src/pyattacker/cli.py +143 -18
- pyattacker-0.2.0/src/pyattacker/declarative.py +782 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/src/pyattacker/errors.py +5 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/src/pyattacker/export.py +71 -22
- {pyattacker-0.1.0 → pyattacker-0.2.0}/src/pyattacker/pipeline.py +2 -5
- {pyattacker-0.1.0 → pyattacker-0.2.0}/src/pyattacker/resource.py +75 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/src/pyattacker/runner.py +78 -43
- {pyattacker-0.1.0 → pyattacker-0.2.0}/src/pyattacker/store/__init__.py +14 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/src/pyattacker/store/base.py +157 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/src/pyattacker/store/memory.py +84 -1
- {pyattacker-0.1.0 → pyattacker-0.2.0}/src/pyattacker/store/sqlite.py +172 -2
- {pyattacker-0.1.0 → pyattacker-0.2.0}/src/pyattacker/store/writebehind.py +44 -1
- {pyattacker-0.1.0 → pyattacker-0.2.0}/src/pyattacker/task.py +302 -11
- {pyattacker-0.1.0 → pyattacker-0.2.0}/src/pyattacker/tasks/__init__.py +194 -12
- {pyattacker-0.1.0 → pyattacker-0.2.0}/tests/test_backends.py +5 -0
- pyattacker-0.2.0/tests/test_benchmark_cli.py +129 -0
- pyattacker-0.2.0/tests/test_benchmark_clock.py +263 -0
- pyattacker-0.2.0/tests/test_benchmark_harness.py +510 -0
- pyattacker-0.2.0/tests/test_benchmark_provider.py +346 -0
- pyattacker-0.2.0/tests/test_benchmark_report.py +426 -0
- pyattacker-0.2.0/tests/test_benchmark_scenario.py +128 -0
- pyattacker-0.2.0/tests/test_cli.py +785 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/tests/test_declarative.py +117 -1
- pyattacker-0.2.0/tests/test_docs_examples.py +109 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/tests/test_export.py +543 -1
- {pyattacker-0.1.0 → pyattacker-0.2.0}/tests/test_lease_safety.py +84 -0
- pyattacker-0.2.0/tests/test_optional_yaml.py +180 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/tests/test_packaging.py +21 -3
- pyattacker-0.2.0/tests/test_resume_identity.py +393 -0
- pyattacker-0.2.0/tests/test_retry_policy.py +144 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/tests/test_runner.py +1 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/tests/test_scheduler.py +19 -1
- {pyattacker-0.1.0 → pyattacker-0.2.0}/tests/test_shard.py +43 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/tests/test_store.py +114 -0
- pyattacker-0.2.0/tests/test_subprocess_lifecycle.py +599 -0
- pyattacker-0.2.0/tests/test_task_overrides.py +96 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/tests/test_tutorial.py +6 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/uv.lock +8 -3
- pyattacker-0.1.0/.github/workflows/ci.yml +0 -53
- pyattacker-0.1.0/CHANGELOG.md +0 -91
- pyattacker-0.1.0/src/pyattacker/declarative.py +0 -327
- pyattacker-0.1.0/tests/test_cli.py +0 -398
- {pyattacker-0.1.0 → pyattacker-0.2.0}/.gitignore +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/LICENSE +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/examples/data.jsonl +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/examples/llm_eval/README.md +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/examples/llm_eval/__init__.py +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/examples/llm_eval/backend.py +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/examples/llm_eval/demo.py +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/examples/llm_eval/pipelines.py +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/examples/plugin_package/README.md +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/examples/plugin_package/pa_demo_plugin/__init__.py +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/examples/plugin_package/pa_demo_plugin/algorithms.py +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/examples/plugin_package/pa_demo_plugin/codecs.py +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/examples/plugin_package/pa_demo_plugin/tasks.py +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/examples/plugin_package/pyproject.toml +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/examples/plugin_tasks.yaml +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/examples/qa_eval.yaml +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/examples/quickstart.py +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/examples/sharded.py +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/src/pyattacker/__main__.py +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/src/pyattacker/algorithm.py +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/src/pyattacker/artifact.py +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/src/pyattacker/backends.py +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/src/pyattacker/merge.py +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/src/pyattacker/monitor.py +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/src/pyattacker/plugins.py +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/src/pyattacker/scheduler.py +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/src/pyattacker/server.py +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/src/pyattacker/shard.py +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/tests/helpers.py +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/tests/test_algorithms.py +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/tests/test_artifact.py +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/tests/test_errors.py +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/tests/test_m2.py +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/tests/test_monitor.py +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/tests/test_pipeline.py +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/tests/test_plugins.py +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/tests/test_server.py +0 -0
- {pyattacker-0.1.0 → pyattacker-0.2.0}/tests/test_tasks.py +0 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
test:
|
|
15
|
+
name: python ${{ matrix.python-version }}
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
strategy:
|
|
18
|
+
fail-fast: false
|
|
19
|
+
matrix:
|
|
20
|
+
# The two versions declared in pyproject.toml classifiers.
|
|
21
|
+
python-version: ["3.11", "3.12"]
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v7
|
|
24
|
+
|
|
25
|
+
- name: Install uv
|
|
26
|
+
# The only third-party action in the pipeline, so it carries a commit pin: setup-uv stopped
|
|
27
|
+
# publishing floating major tags at v8, and pinning to the commit is what its README now
|
|
28
|
+
# recommends. The actions/* above and below are GitHub's own and track a major tag.
|
|
29
|
+
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
30
|
+
with:
|
|
31
|
+
enable-cache: true
|
|
32
|
+
python-version: ${{ matrix.python-version }}
|
|
33
|
+
|
|
34
|
+
- name: Install dependencies
|
|
35
|
+
run: uv sync --locked
|
|
36
|
+
|
|
37
|
+
- name: Lint
|
|
38
|
+
run: uv run ruff check
|
|
39
|
+
|
|
40
|
+
- name: Test
|
|
41
|
+
# The whole suite is offline; network access is never needed.
|
|
42
|
+
run: uv run pytest
|
|
43
|
+
|
|
44
|
+
- name: CLI smoke test
|
|
45
|
+
run: uv run pyattacker demo --pipelines 25 --store runs/demo.db
|
|
46
|
+
|
|
47
|
+
- name: Example smoke tests
|
|
48
|
+
# The examples are documentation, and documentation rots: the llm_eval demo also asserts
|
|
49
|
+
# its own claim about checkpoint granularity, so this catches recovery regressions.
|
|
50
|
+
run: |
|
|
51
|
+
uv run python examples/quickstart.py
|
|
52
|
+
uv run python examples/sharded.py
|
|
53
|
+
uv run python -m examples.llm_eval.demo
|
|
54
|
+
|
|
55
|
+
- name: Build sdist and wheel
|
|
56
|
+
run: uv build
|
|
57
|
+
|
|
58
|
+
no-extra:
|
|
59
|
+
name: base install, no yaml extra
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
steps:
|
|
62
|
+
- uses: actions/checkout@v7
|
|
63
|
+
|
|
64
|
+
- name: Install uv
|
|
65
|
+
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
66
|
+
with:
|
|
67
|
+
enable-cache: true
|
|
68
|
+
# Both 3.11 jobs read the same lockfile, so without a suffix they compute the same cache
|
|
69
|
+
# key and setup-uv warns that one of them could not upload the cache it just built. The
|
|
70
|
+
# suffix is also honest: this job installs a different set of packages (pytest and the
|
|
71
|
+
# build backend, rather than the dev group).
|
|
72
|
+
cache-suffix: no-extra
|
|
73
|
+
python-version: "3.11"
|
|
74
|
+
|
|
75
|
+
- name: Install the library and nothing else
|
|
76
|
+
# Deliberately not `uv sync`: the dev group carries the yaml extra, and doing without it is
|
|
77
|
+
# the entire point of this job. Installing into /tmp keeps the venv out of the checkout.
|
|
78
|
+
run: |
|
|
79
|
+
uv venv /tmp/no-extra --python 3.11
|
|
80
|
+
uv pip install --python /tmp/no-extra/bin/python . pytest
|
|
81
|
+
|
|
82
|
+
- name: PyYAML really is absent, and importing pyattacker does not want it
|
|
83
|
+
run: |
|
|
84
|
+
/tmp/no-extra/bin/python -c "
|
|
85
|
+
import importlib.util, sys
|
|
86
|
+
assert importlib.util.find_spec('yaml') is None, 'pyyaml leaked into the base install'
|
|
87
|
+
import pyattacker
|
|
88
|
+
assert 'yaml' not in sys.modules, 'importing pyattacker pulled in pyyaml'
|
|
89
|
+
print('pyattacker imports cleanly with no pyyaml installed')
|
|
90
|
+
"
|
|
91
|
+
|
|
92
|
+
- name: The suite passes without it
|
|
93
|
+
# The `requires_yaml` tests are deselected rather than skipped, so an accidental loss of the
|
|
94
|
+
# extra in the *development* environment fails the ordinary suite instead of quietly skipping
|
|
95
|
+
# a third of it. The optional-dependency tests above are not marked, and run here for real.
|
|
96
|
+
run: /tmp/no-extra/bin/python -m pytest -q -m "not requires_yaml"
|
|
97
|
+
|
|
98
|
+
- name: A YAML config asks for the extra, a JSON one does not
|
|
99
|
+
run: |
|
|
100
|
+
/tmp/no-extra/bin/pyattacker --version
|
|
101
|
+
mkdir -p /tmp/no-extra-check
|
|
102
|
+
/tmp/no-extra/bin/pyattacker demo --pipelines 10 --store /tmp/no-extra-check/demo.db
|
|
103
|
+
printf 'pipeline:\n name: x\n tasks:\n - {use: echo}\n' > /tmp/no-extra-check/x.yaml
|
|
104
|
+
printf '{"pipeline": {"name": "x", "tasks": [{"use": "echo"}]}}' > /tmp/no-extra-check/x.json
|
|
105
|
+
if /tmp/no-extra/bin/pyattacker validate -c /tmp/no-extra-check/x.yaml 2>/tmp/no-extra-check/err.txt; then
|
|
106
|
+
echo "::error::a YAML config was accepted without the extra installed"
|
|
107
|
+
exit 1
|
|
108
|
+
fi
|
|
109
|
+
grep -q 'pyattacker\[yaml\]' /tmp/no-extra-check/err.txt || {
|
|
110
|
+
echo "::error::the YAML error does not name the extra to install"
|
|
111
|
+
cat /tmp/no-extra-check/err.txt
|
|
112
|
+
exit 1
|
|
113
|
+
}
|
|
114
|
+
/tmp/no-extra/bin/pyattacker validate -c /tmp/no-extra-check/x.json > /dev/null
|
|
115
|
+
echo "yaml asked for the extra; json validated"
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Publishing is driven by a tag, so the artifact is always traceable to an exact commit.
|
|
4
|
+
# Pushing v0.2.0 builds, verifies and publishes to PyPI, then attaches the files to a GitHub
|
|
5
|
+
# Release. `workflow_dispatch` runs everything except the PyPI upload, which is how you rehearse
|
|
6
|
+
# a release without spending a version number: PyPI refuses a re-upload, and a version, once
|
|
7
|
+
# published, can be deleted but never reused.
|
|
8
|
+
on:
|
|
9
|
+
push:
|
|
10
|
+
tags: ["v*"]
|
|
11
|
+
workflow_dispatch:
|
|
12
|
+
inputs:
|
|
13
|
+
publish_to_testpypi:
|
|
14
|
+
description: "Publish to TestPyPI (a dry run needs no version number)"
|
|
15
|
+
type: boolean
|
|
16
|
+
default: false
|
|
17
|
+
|
|
18
|
+
concurrency:
|
|
19
|
+
group: release-${{ github.ref }}
|
|
20
|
+
cancel-in-progress: false # never interrupt an upload in progress
|
|
21
|
+
|
|
22
|
+
permissions: {}
|
|
23
|
+
|
|
24
|
+
jobs:
|
|
25
|
+
# The gate. A tag can point at any commit, including one that never passed CI, so the release
|
|
26
|
+
# re-runs the full suite instead of trusting that it did.
|
|
27
|
+
verify:
|
|
28
|
+
name: verify python ${{ matrix.python-version }}
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
strategy:
|
|
31
|
+
fail-fast: true # a release is all-or-nothing; stop at the first failure
|
|
32
|
+
matrix:
|
|
33
|
+
python-version: ["3.11", "3.12"]
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/checkout@v7
|
|
36
|
+
|
|
37
|
+
- name: Install uv
|
|
38
|
+
# The only third-party action in the pipeline, so it carries a commit pin: setup-uv stopped
|
|
39
|
+
# publishing floating major tags at v8, and pinning to the commit is what its README now
|
|
40
|
+
# recommends. The actions/* elsewhere in this file are GitHub's own and track a major tag.
|
|
41
|
+
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
42
|
+
with:
|
|
43
|
+
enable-cache: true
|
|
44
|
+
python-version: ${{ matrix.python-version }}
|
|
45
|
+
|
|
46
|
+
- name: Install dependencies
|
|
47
|
+
run: uv sync --locked
|
|
48
|
+
|
|
49
|
+
- name: Lint
|
|
50
|
+
run: uv run ruff check
|
|
51
|
+
|
|
52
|
+
- name: Test
|
|
53
|
+
run: uv run pytest
|
|
54
|
+
|
|
55
|
+
- name: CLI smoke test
|
|
56
|
+
run: uv run pyattacker demo --pipelines 25 --store runs/demo.db
|
|
57
|
+
|
|
58
|
+
- name: Example smoke tests
|
|
59
|
+
run: |
|
|
60
|
+
uv run python examples/quickstart.py
|
|
61
|
+
uv run python examples/sharded.py
|
|
62
|
+
uv run python -m examples.llm_eval.demo
|
|
63
|
+
|
|
64
|
+
build:
|
|
65
|
+
name: build and check artifacts
|
|
66
|
+
needs: verify
|
|
67
|
+
runs-on: ubuntu-latest
|
|
68
|
+
outputs:
|
|
69
|
+
version: ${{ steps.version.outputs.version }}
|
|
70
|
+
steps:
|
|
71
|
+
- uses: actions/checkout@v7
|
|
72
|
+
|
|
73
|
+
- name: Install uv
|
|
74
|
+
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
75
|
+
with:
|
|
76
|
+
enable-cache: true
|
|
77
|
+
python-version: "3.11"
|
|
78
|
+
|
|
79
|
+
- name: Build sdist and wheel
|
|
80
|
+
run: |
|
|
81
|
+
rm -rf dist
|
|
82
|
+
uv build
|
|
83
|
+
|
|
84
|
+
- name: Read the version being released
|
|
85
|
+
id: version
|
|
86
|
+
run: |
|
|
87
|
+
version="$(uv run --no-project --with ./dist/*.whl \
|
|
88
|
+
python -c 'import pyattacker; print(pyattacker.__version__)')"
|
|
89
|
+
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
90
|
+
echo "building version $version"
|
|
91
|
+
|
|
92
|
+
# A tag that disagrees with the package version produces a release nobody can find again.
|
|
93
|
+
- name: Check the tag matches the package version
|
|
94
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
95
|
+
env:
|
|
96
|
+
VERSION: ${{ steps.version.outputs.version }}
|
|
97
|
+
run: |
|
|
98
|
+
tag="${GITHUB_REF#refs/tags/v}"
|
|
99
|
+
if [ "$tag" != "$VERSION" ]; then
|
|
100
|
+
echo "::error::tag v$tag does not match __version__ $VERSION — fix one of them and re-tag"
|
|
101
|
+
exit 1
|
|
102
|
+
fi
|
|
103
|
+
echo "tag v$tag matches the package version"
|
|
104
|
+
|
|
105
|
+
# Metadata has to render on PyPI, and the sdist has to be self-sufficient: these two checks
|
|
106
|
+
# are what caught a leaked local config file and 16 README links that break off GitHub.
|
|
107
|
+
- name: Check metadata renders
|
|
108
|
+
run: uvx twine check --strict dist/*
|
|
109
|
+
|
|
110
|
+
- name: Check the sdist carries no local tooling state
|
|
111
|
+
run: |
|
|
112
|
+
if tar tzf dist/*.tar.gz | grep -E '(^|/)\.(claude|venv|env|idea|vscode)/|\.pyc$'; then
|
|
113
|
+
echo "::error::the sdist contains local tooling state; tighten the include list in pyproject.toml"
|
|
114
|
+
exit 1
|
|
115
|
+
fi
|
|
116
|
+
echo "sdist contains no local tooling state"
|
|
117
|
+
|
|
118
|
+
- name: Check the sdist rebuilds and passes its own tests
|
|
119
|
+
run: |
|
|
120
|
+
mkdir -p /tmp/sdist && tar xzf dist/*.tar.gz -C /tmp/sdist --strip-components=1
|
|
121
|
+
cd /tmp/sdist
|
|
122
|
+
uv venv .venv --python 3.11
|
|
123
|
+
# The extra is requested on purpose here: the whole suite is the point of this check, and
|
|
124
|
+
# the configuration without PyYAML is the `no-extra` job in ci.yml plus the wheel check below.
|
|
125
|
+
uv pip install --python .venv/bin/python -e ".[yaml]" pytest
|
|
126
|
+
.venv/bin/python -m pytest -q
|
|
127
|
+
|
|
128
|
+
- name: Check the wheel installs and runs
|
|
129
|
+
run: |
|
|
130
|
+
uv venv /tmp/wheel-check --python 3.11
|
|
131
|
+
uv pip install --python /tmp/wheel-check/bin/python dist/*.whl
|
|
132
|
+
/tmp/wheel-check/bin/pyattacker --version
|
|
133
|
+
/tmp/wheel-check/bin/pyattacker demo --pipelines 10 --store /tmp/wheel-check/demo.db
|
|
134
|
+
|
|
135
|
+
- name: Check the extra really is optional
|
|
136
|
+
# A plain install of the wheel must bring in nothing, so this is the one place where the
|
|
137
|
+
# published metadata is checked rather than the source tree: PyYAML must be absent, importing
|
|
138
|
+
# pyattacker must not change that, YAML must ask for the extra, and JSON must still work.
|
|
139
|
+
run: |
|
|
140
|
+
/tmp/wheel-check/bin/python -c "
|
|
141
|
+
import importlib.util, sys
|
|
142
|
+
assert importlib.util.find_spec('yaml') is None, 'pyyaml leaked into the base install'
|
|
143
|
+
import pyattacker
|
|
144
|
+
assert 'yaml' not in sys.modules, 'importing pyattacker pulled in pyyaml'
|
|
145
|
+
print('the wheel installs with no dependencies')
|
|
146
|
+
"
|
|
147
|
+
mkdir -p /tmp/wheel-check/cfg
|
|
148
|
+
printf 'pipeline:\n name: x\n tasks:\n - {use: echo}\n' > /tmp/wheel-check/cfg/x.yaml
|
|
149
|
+
printf '{"pipeline": {"name": "x", "tasks": [{"use": "echo"}]}}' > /tmp/wheel-check/cfg/x.json
|
|
150
|
+
if /tmp/wheel-check/bin/pyattacker validate -c /tmp/wheel-check/cfg/x.yaml 2>/tmp/wheel-check/err.txt; then
|
|
151
|
+
echo "::error::a YAML config was accepted without the extra installed"
|
|
152
|
+
exit 1
|
|
153
|
+
fi
|
|
154
|
+
grep -q 'pyattacker\[yaml\]' /tmp/wheel-check/err.txt || {
|
|
155
|
+
echo "::error::the YAML error does not name the extra to install"
|
|
156
|
+
cat /tmp/wheel-check/err.txt
|
|
157
|
+
exit 1
|
|
158
|
+
}
|
|
159
|
+
/tmp/wheel-check/bin/pyattacker validate -c /tmp/wheel-check/cfg/x.json > /dev/null
|
|
160
|
+
echo "yaml asked for the extra; json validated"
|
|
161
|
+
|
|
162
|
+
- name: Check the wheel's yaml extra installs and works
|
|
163
|
+
# The other direction of the same metadata, and the one no source-level test can cover: the
|
|
164
|
+
# extra has to make `pip install "pyattacker[yaml]"` resolve PyYAML *from the built wheel*, and
|
|
165
|
+
# that has to be enough to read a real config. A wrong Provides-Extra, a missing marker on
|
|
166
|
+
# Requires-Dist or a build-backend change would leave the suite green and break this.
|
|
167
|
+
run: |
|
|
168
|
+
uv venv /tmp/wheel-yaml-check --python 3.11
|
|
169
|
+
wheel="$(ls dist/*.whl)" # exactly one; `dist/` is rebuilt from scratch above
|
|
170
|
+
uv pip install --python /tmp/wheel-yaml-check/bin/python "${wheel}[yaml]"
|
|
171
|
+
/tmp/wheel-yaml-check/bin/python -c "
|
|
172
|
+
import importlib.metadata as md, yaml
|
|
173
|
+
print('PyYAML', yaml.__version__, 'from', md.requires('pyattacker'))
|
|
174
|
+
"
|
|
175
|
+
mkdir -p /tmp/wheel-yaml-check/cfg
|
|
176
|
+
printf 'pipeline:\n name: x\n tasks:\n - {use: echo}\n' > /tmp/wheel-yaml-check/cfg/x.yaml
|
|
177
|
+
/tmp/wheel-yaml-check/bin/pyattacker validate -c /tmp/wheel-yaml-check/cfg/x.yaml > /dev/null
|
|
178
|
+
echo "the extra resolved pyyaml and the config validated"
|
|
179
|
+
|
|
180
|
+
- uses: actions/upload-artifact@v7
|
|
181
|
+
with:
|
|
182
|
+
name: dist
|
|
183
|
+
path: dist/
|
|
184
|
+
if-no-files-found: error
|
|
185
|
+
|
|
186
|
+
# Publishing lives in its own job so the OIDC token is scoped to it alone: no test or build step
|
|
187
|
+
# ever holds a credential that can upload a release.
|
|
188
|
+
testpypi:
|
|
189
|
+
name: publish to TestPyPI
|
|
190
|
+
if: github.event_name == 'workflow_dispatch' && inputs.publish_to_testpypi
|
|
191
|
+
needs: build
|
|
192
|
+
runs-on: ubuntu-latest
|
|
193
|
+
environment: testpypi
|
|
194
|
+
permissions:
|
|
195
|
+
id-token: write # mint the OIDC token trusted publishing exchanges for an upload token
|
|
196
|
+
steps:
|
|
197
|
+
- uses: actions/download-artifact@v8
|
|
198
|
+
with:
|
|
199
|
+
name: dist
|
|
200
|
+
path: dist/
|
|
201
|
+
|
|
202
|
+
- name: Install uv
|
|
203
|
+
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
204
|
+
with:
|
|
205
|
+
# This job never checks out the repository, so there is no uv.lock to key a cache on:
|
|
206
|
+
# caching here can only ever warn that the cache will never get invalidated.
|
|
207
|
+
enable-cache: false
|
|
208
|
+
|
|
209
|
+
# --check-url lets a re-run skip files already uploaded instead of failing the job.
|
|
210
|
+
- name: Publish
|
|
211
|
+
run: |
|
|
212
|
+
uv publish \
|
|
213
|
+
--trusted-publishing always \
|
|
214
|
+
--publish-url https://test.pypi.org/legacy/ \
|
|
215
|
+
--check-url https://test.pypi.org/simple/ \
|
|
216
|
+
dist/*
|
|
217
|
+
|
|
218
|
+
pypi:
|
|
219
|
+
name: publish to PyPI
|
|
220
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
221
|
+
needs: build
|
|
222
|
+
runs-on: ubuntu-latest
|
|
223
|
+
environment: pypi
|
|
224
|
+
permissions:
|
|
225
|
+
id-token: write
|
|
226
|
+
steps:
|
|
227
|
+
- uses: actions/download-artifact@v8
|
|
228
|
+
with:
|
|
229
|
+
name: dist
|
|
230
|
+
path: dist/
|
|
231
|
+
|
|
232
|
+
- name: Install uv
|
|
233
|
+
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
234
|
+
with:
|
|
235
|
+
# No checkout in this job, so no lockfile to key a cache on — see the note in `testpypi`.
|
|
236
|
+
enable-cache: false
|
|
237
|
+
|
|
238
|
+
- name: Publish
|
|
239
|
+
run: |
|
|
240
|
+
uv publish \
|
|
241
|
+
--trusted-publishing always \
|
|
242
|
+
--check-url https://pypi.org/simple/ \
|
|
243
|
+
dist/*
|
|
244
|
+
|
|
245
|
+
github_release:
|
|
246
|
+
name: create the GitHub release
|
|
247
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
248
|
+
# `pypi` gates this on the upload having succeeded; `build` is also required because
|
|
249
|
+
# needs.build.outputs is only readable from a job that lists build as a dependency.
|
|
250
|
+
needs: [build, pypi]
|
|
251
|
+
runs-on: ubuntu-latest
|
|
252
|
+
permissions:
|
|
253
|
+
contents: write # create the release and upload the artifacts to it
|
|
254
|
+
steps:
|
|
255
|
+
- uses: actions/checkout@v7
|
|
256
|
+
|
|
257
|
+
- uses: actions/download-artifact@v8
|
|
258
|
+
with:
|
|
259
|
+
name: dist
|
|
260
|
+
path: dist/
|
|
261
|
+
|
|
262
|
+
# The section for this version in CHANGELOG.md, so the release notes are the notes we wrote
|
|
263
|
+
# rather than a list of commit subjects.
|
|
264
|
+
- name: Extract the changelog section
|
|
265
|
+
env:
|
|
266
|
+
VERSION: ${{ needs.build.outputs.version }}
|
|
267
|
+
run: |
|
|
268
|
+
if awk -v v="$VERSION" '
|
|
269
|
+
$0 ~ "^## \\[" v "\\]" {found=1; next}
|
|
270
|
+
found && /^## \[/ {exit}
|
|
271
|
+
found {print}
|
|
272
|
+
' CHANGELOG.md > notes.md && [ -s notes.md ]; then
|
|
273
|
+
echo "using the CHANGELOG.md section for $VERSION"
|
|
274
|
+
else
|
|
275
|
+
echo "No changelog section for $VERSION." > notes.md
|
|
276
|
+
echo "::warning::CHANGELOG.md has no section for $VERSION; publishing with a placeholder"
|
|
277
|
+
fi
|
|
278
|
+
|
|
279
|
+
- name: Create the release
|
|
280
|
+
env:
|
|
281
|
+
GH_TOKEN: ${{ github.token }}
|
|
282
|
+
VERSION: ${{ needs.build.outputs.version }}
|
|
283
|
+
run: |
|
|
284
|
+
gh release create "${GITHUB_REF#refs/tags/}" \
|
|
285
|
+
--title "pyattacker $VERSION" \
|
|
286
|
+
--notes-file notes.md \
|
|
287
|
+
--verify-tag \
|
|
288
|
+
dist/*
|