bigfix-remote-client-relevance 0.0.1__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.
- bigfix_remote_client_relevance-0.0.1/.editorconfig +21 -0
- bigfix_remote_client_relevance-0.0.1/.github/dependabot.yml +46 -0
- bigfix_remote_client_relevance-0.0.1/.github/workflows/pre-commit.yaml +122 -0
- bigfix_remote_client_relevance-0.0.1/.github/workflows/tag_and_release.yaml +222 -0
- bigfix_remote_client_relevance-0.0.1/.github/workflows/test.yaml +57 -0
- bigfix_remote_client_relevance-0.0.1/.gitignore +219 -0
- bigfix_remote_client_relevance-0.0.1/.pre-commit-config.yaml +221 -0
- bigfix_remote_client_relevance-0.0.1/.python-version +1 -0
- bigfix_remote_client_relevance-0.0.1/.yamllint.yaml +13 -0
- bigfix_remote_client_relevance-0.0.1/DESIGN.md +996 -0
- bigfix_remote_client_relevance-0.0.1/LICENSE +21 -0
- bigfix_remote_client_relevance-0.0.1/PKG-INFO +296 -0
- bigfix_remote_client_relevance-0.0.1/README.md +242 -0
- bigfix_remote_client_relevance-0.0.1/_typos.toml +7 -0
- bigfix_remote_client_relevance-0.0.1/pyproject.toml +119 -0
- bigfix_remote_client_relevance-0.0.1/pyrightconfig.json +15 -0
- bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/__init__.py +52 -0
- bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/bootstrap/__init__.py +17 -0
- bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/bootstrap/cache.py +141 -0
- bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/bootstrap/extract_local.py +229 -0
- bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/bootstrap/provision.py +270 -0
- bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/bootstrap/release_site.py +386 -0
- bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/bootstrap/targets.py +282 -0
- bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/cli.py +462 -0
- bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/inventory.py +145 -0
- bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/orchestrate.py +391 -0
- bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/qna_paths.py +67 -0
- bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/results.py +169 -0
- bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/transports/__init__.py +49 -0
- bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/transports/container.py +937 -0
- bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/transports/container_libs.py +173 -0
- bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/transports/container_setup.py +211 -0
- bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/transports/coordination.py +44 -0
- bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/transports/fastquery.py +87 -0
- bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/transports/local.py +385 -0
- bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/transports/ssh.py +493 -0
- bigfix_remote_client_relevance-0.0.1/tests/conftest.py +370 -0
- bigfix_remote_client_relevance-0.0.1/tests/fixtures/artifacts/README.md +18 -0
- bigfix_remote_client_relevance-0.0.1/tests/fixtures/artifacts/tiny-qna-gzip.rpm +0 -0
- bigfix_remote_client_relevance-0.0.1/tests/fixtures/artifacts/tiny-qna-lzma.rpm +0 -0
- bigfix_remote_client_relevance-0.0.1/tests/fixtures/artifacts/tiny-qna-zstd.rpm +0 -0
- bigfix_remote_client_relevance-0.0.1/tests/fixtures/qna_output/crlf_windows.txt +4 -0
- bigfix_remote_client_relevance-0.0.1/tests/fixtures/qna_output/echo_q_variant.txt +3 -0
- bigfix_remote_client_relevance-0.0.1/tests/fixtures/qna_output/empty_result.txt +3 -0
- bigfix_remote_client_relevance-0.0.1/tests/fixtures/qna_output/midline_prefix_trap.txt +5 -0
- bigfix_remote_client_relevance-0.0.1/tests/fixtures/qna_output/mixed_answer_error.txt +4 -0
- bigfix_remote_client_relevance-0.0.1/tests/fixtures/qna_output/multi_answer.txt +6 -0
- bigfix_remote_client_relevance-0.0.1/tests/fixtures/qna_output/no_showtypes.txt +4 -0
- bigfix_remote_client_relevance-0.0.1/tests/fixtures/qna_output/relevance_error.txt +3 -0
- bigfix_remote_client_relevance-0.0.1/tests/fixtures/qna_output/single_answer.txt +4 -0
- bigfix_remote_client_relevance-0.0.1/tests/fixtures/qna_output/utf8_answer.txt +4 -0
- bigfix_remote_client_relevance-0.0.1/tests/fixtures/release_site/SHA256SUMS.txt +41 -0
- bigfix_remote_client_relevance-0.0.1/tests/fixtures/release_site/patch_page_11.0.6.html +500 -0
- bigfix_remote_client_relevance-0.0.1/tests/fixtures/release_site/release_index.html +809 -0
- bigfix_remote_client_relevance-0.0.1/tests/integration/test_docker_container.py +244 -0
- bigfix_remote_client_relevance-0.0.1/tests/integration/test_live_qna_local.py +50 -0
- bigfix_remote_client_relevance-0.0.1/tests/integration/test_live_release_site.py +75 -0
- bigfix_remote_client_relevance-0.0.1/tests/integration/test_ssh_localhost.py +123 -0
- bigfix_remote_client_relevance-0.0.1/tests/integration/test_ssh_windows.py +90 -0
- bigfix_remote_client_relevance-0.0.1/tests/unit/test_bootstrap_targets.py +240 -0
- bigfix_remote_client_relevance-0.0.1/tests/unit/test_cache.py +168 -0
- bigfix_remote_client_relevance-0.0.1/tests/unit/test_cli.py +656 -0
- bigfix_remote_client_relevance-0.0.1/tests/unit/test_container_libs.py +182 -0
- bigfix_remote_client_relevance-0.0.1/tests/unit/test_container_setup.py +160 -0
- bigfix_remote_client_relevance-0.0.1/tests/unit/test_extract_local.py +270 -0
- bigfix_remote_client_relevance-0.0.1/tests/unit/test_image_coordinator.py +101 -0
- bigfix_remote_client_relevance-0.0.1/tests/unit/test_inventory.py +229 -0
- bigfix_remote_client_relevance-0.0.1/tests/unit/test_no_print.py +102 -0
- bigfix_remote_client_relevance-0.0.1/tests/unit/test_orchestrate.py +908 -0
- bigfix_remote_client_relevance-0.0.1/tests/unit/test_qna_paths.py +85 -0
- bigfix_remote_client_relevance-0.0.1/tests/unit/test_release_site.py +188 -0
- bigfix_remote_client_relevance-0.0.1/tests/unit/test_results.py +172 -0
- bigfix_remote_client_relevance-0.0.1/tests/unit/test_transport_container.py +1407 -0
- bigfix_remote_client_relevance-0.0.1/tests/unit/test_transport_fastquery.py +54 -0
- bigfix_remote_client_relevance-0.0.1/tests/unit/test_transport_local.py +606 -0
- bigfix_remote_client_relevance-0.0.1/tests/unit/test_transport_ssh.py +775 -0
- bigfix_remote_client_relevance-0.0.1/uv.lock +1037 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Check http://editorconfig.org for more information
|
|
2
|
+
# This is the main config file for this project:
|
|
3
|
+
root = true
|
|
4
|
+
|
|
5
|
+
[*]
|
|
6
|
+
charset = utf-8
|
|
7
|
+
trim_trailing_whitespace = true
|
|
8
|
+
indent_style = space
|
|
9
|
+
insert_final_newline = true
|
|
10
|
+
|
|
11
|
+
[*.py]
|
|
12
|
+
indent_size = 4
|
|
13
|
+
|
|
14
|
+
[*.{bes,bes.mustache}]
|
|
15
|
+
# bes files are XML, but the `actionscript` tag text must use crlf
|
|
16
|
+
end_of_line = crlf
|
|
17
|
+
indent_style = tab
|
|
18
|
+
indent_size = 3
|
|
19
|
+
|
|
20
|
+
[*.{bat,cmd}]
|
|
21
|
+
end_of_line = crlf
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Dependency-update alerts, closing the "no automated vulnerability alerting
|
|
2
|
+
# path" gap in docs/security-review.md.
|
|
3
|
+
#
|
|
4
|
+
# Requires Dependabot to be enabled on the GHES instance - this file is inert,
|
|
5
|
+
# not an error, if it is not. Check Security > Dependabot settings.
|
|
6
|
+
#
|
|
7
|
+
# The 7-day cooldown is the point, not a courtesy: a release published minutes
|
|
8
|
+
# ago is the likeliest to be a compromised one. It matches `exclude-newer` in
|
|
9
|
+
# pyproject.toml and is zizmor's minimum.
|
|
10
|
+
version: 2
|
|
11
|
+
updates:
|
|
12
|
+
# "uv", not "pip": uv.lock is what the release wheel is built from
|
|
13
|
+
# (uv sync --locked), and "pip" would read pyproject.toml without updating
|
|
14
|
+
# the lock. Fall back to "pip" if this instance rejects the newer ecosystem.
|
|
15
|
+
- package-ecosystem: "uv"
|
|
16
|
+
directory: "/"
|
|
17
|
+
schedule:
|
|
18
|
+
interval: "weekly"
|
|
19
|
+
cooldown:
|
|
20
|
+
default-days: 7
|
|
21
|
+
open-pull-requests-limit: 5
|
|
22
|
+
labels:
|
|
23
|
+
- "dependencies"
|
|
24
|
+
|
|
25
|
+
- package-ecosystem: "github-actions"
|
|
26
|
+
directory: "/"
|
|
27
|
+
schedule:
|
|
28
|
+
interval: "weekly"
|
|
29
|
+
cooldown:
|
|
30
|
+
default-days: 7
|
|
31
|
+
open-pull-requests-limit: 5
|
|
32
|
+
labels:
|
|
33
|
+
- "dependencies"
|
|
34
|
+
|
|
35
|
+
# Scans .pre-commit-config.yaml and updates each hook's `rev:` (SHA-pinned
|
|
36
|
+
# revs get their version comment updated too, same as the github-actions
|
|
37
|
+
# entries above). No `cooldown` here: as of 2026-03, GitHub does not support
|
|
38
|
+
# the cooldown option for this ecosystem, so these PRs land without the
|
|
39
|
+
# 7-day delay the other two ecosystems get - review before merging.
|
|
40
|
+
- package-ecosystem: "pre-commit"
|
|
41
|
+
directory: "/"
|
|
42
|
+
schedule:
|
|
43
|
+
interval: "weekly"
|
|
44
|
+
open-pull-requests-limit: 5
|
|
45
|
+
labels:
|
|
46
|
+
- "dependencies"
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pre-commit
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
pull_request:
|
|
6
|
+
push:
|
|
7
|
+
branches:
|
|
8
|
+
- main
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
# read-only by default; the cross-repo checkout uses secrets.CROSSREPO_PAT
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
|
|
15
|
+
concurrency:
|
|
16
|
+
group: pre-commit-${{ github.ref }}
|
|
17
|
+
cancel-in-progress: true
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
pre-commit:
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
# no-commit-to-branch guards a developer's local `git commit`; it has nothing
|
|
23
|
+
# to check on a runner. On a push to main, actions/checkout leaves HEAD on
|
|
24
|
+
# the main branch, so the hook fails the whole run on a merged PR. (On a
|
|
25
|
+
# pull_request event HEAD is detached at the merge ref, where it passes -
|
|
26
|
+
# which is why this only shows up after the merge.) SKIP is read by
|
|
27
|
+
# pre-commit itself, so it applies to both steps below.
|
|
28
|
+
#
|
|
29
|
+
# GH_TOKEN puts zizmor in online mode (offline is the default without it):
|
|
30
|
+
# it can then fetch the referenced actions themselves for the audits that
|
|
31
|
+
# need that - see the comment above the zizmor hook in
|
|
32
|
+
# .pre-commit-config.yaml. The runner's own GITHUB_TOKEN is enough for a
|
|
33
|
+
# public repo; this is read-only per the `permissions:` block above and
|
|
34
|
+
# never touches git credentials (actions/checkout above already sets
|
|
35
|
+
# persist-credentials: false).
|
|
36
|
+
env:
|
|
37
|
+
SKIP: no-commit-to-branch
|
|
38
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
39
|
+
steps:
|
|
40
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
41
|
+
with:
|
|
42
|
+
# requited to grab the history of the PR for pre-commit
|
|
43
|
+
fetch-depth: 0
|
|
44
|
+
persist-credentials: false
|
|
45
|
+
|
|
46
|
+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
47
|
+
with:
|
|
48
|
+
python-version: "3.12"
|
|
49
|
+
|
|
50
|
+
# single source of truth for the uv version: the `rev` of the
|
|
51
|
+
# astral-sh/uv-pre-commit repo in .pre-commit-config.yaml, which
|
|
52
|
+
# `pre-commit autoupdate` already keeps current. Read with awk rather
|
|
53
|
+
# than a yaml parser so it needs nothing installed first. `rev` is
|
|
54
|
+
# SHA-pinned (Dependabot's pre-commit ecosystem keeps the trailing
|
|
55
|
+
# `# X.Y.Z` comment in sync when it bumps the SHA - same convention as
|
|
56
|
+
# the actions/* pins above), so the version comes from that comment;
|
|
57
|
+
# fall back to the bare `rev` value for a tag-pinned rev with no
|
|
58
|
+
# comment.
|
|
59
|
+
- name: read uv version from .pre-commit-config.yaml
|
|
60
|
+
run: |
|
|
61
|
+
UV_VERSION="$(awk '
|
|
62
|
+
/^[[:space:]]*-[[:space:]]*repo:.*astral-sh\/uv-pre-commit[[:space:]]*$/ { found = 1; next }
|
|
63
|
+
found && $1 == "rev:" {
|
|
64
|
+
if (match($0, /#[ \t]*(frozen:[ \t]*)?[vV]?[0-9]+(\.[0-9]+)*/)) {
|
|
65
|
+
ver = substr($0, RSTART, RLENGTH)
|
|
66
|
+
sub(/^#[ \t]*(frozen:[ \t]*)?[vV]?/, "", ver)
|
|
67
|
+
} else {
|
|
68
|
+
ver = $2
|
|
69
|
+
gsub(/["'\'']/, "", ver)
|
|
70
|
+
sub(/^[vV]/, "", ver)
|
|
71
|
+
}
|
|
72
|
+
print ver
|
|
73
|
+
exit
|
|
74
|
+
}
|
|
75
|
+
' .pre-commit-config.yaml)"
|
|
76
|
+
if [ -z "$UV_VERSION" ]; then
|
|
77
|
+
echo "::error::could not read the astral-sh/uv-pre-commit version from .pre-commit-config.yaml"
|
|
78
|
+
exit 1
|
|
79
|
+
fi
|
|
80
|
+
echo "UV_VERSION=$UV_VERSION" >> "$GITHUB_ENV"
|
|
81
|
+
echo "uv version: $UV_VERSION"
|
|
82
|
+
|
|
83
|
+
# the local `uv-lock-check` / `uv-build-wheel` hooks are language: system
|
|
84
|
+
# and run at the manual stage, which this workflow always requests. They
|
|
85
|
+
# look for `uv` on PATH first and only fall back to this repo's .venv,
|
|
86
|
+
# which does not exist on a runner - so uv has to be installed here or
|
|
87
|
+
# both hooks fail.
|
|
88
|
+
- name: install uv (needed by the local pre-commit hooks)
|
|
89
|
+
run: python -m pip install "uv==${UV_VERSION}"
|
|
90
|
+
|
|
91
|
+
- name: pre-commit local action test
|
|
92
|
+
if: github.event_name != 'workflow_dispatch'
|
|
93
|
+
uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
|
|
94
|
+
with:
|
|
95
|
+
extra_args: >-
|
|
96
|
+
--color=always
|
|
97
|
+
--from-ref ${{ github.event.pull_request.base.sha || github.event.before }}
|
|
98
|
+
--to-ref ${{ github.event.pull_request.head.sha || github.sha }}
|
|
99
|
+
--hook-stage manual
|
|
100
|
+
|
|
101
|
+
# manual dispatch: run every hook (including the manual-stage ones -
|
|
102
|
+
# validate-pyproject, the pytest wrapper, the check-useless-excludes/
|
|
103
|
+
# check-hooks-apply meta hooks) against the full tree, not just the
|
|
104
|
+
# diff range a PR/push would use
|
|
105
|
+
- name: pre-commit local action test (manual dispatch)
|
|
106
|
+
if: github.event_name == 'workflow_dispatch'
|
|
107
|
+
uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
|
|
108
|
+
with:
|
|
109
|
+
extra_args: --color=always --all-files --hook-stage manual
|
|
110
|
+
|
|
111
|
+
# `restore-keys` above means a config change restores the previous cache
|
|
112
|
+
# and then saves it back under the new key - so every hook rev bump would
|
|
113
|
+
# otherwise leave its old environment in the cache forever (the pytest
|
|
114
|
+
# hook's fastmcp install is the fat one). gc deletes exactly the
|
|
115
|
+
# environments no live config references. Last step in the job on purpose:
|
|
116
|
+
# actions/cache saves in its post step, which runs after this, so what
|
|
117
|
+
# gets uploaded is the pruned store. No `if:` - the default success()
|
|
118
|
+
# matches the cache action's own `post-if: success()`, so this runs in
|
|
119
|
+
# precisely the cases where a save follows. `pre-commit` is on PATH from
|
|
120
|
+
# the shared action's `python -m pip install pre-commit`.
|
|
121
|
+
- name: prune unused hook environments before the cache is saved
|
|
122
|
+
run: pre-commit gc
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
---
|
|
2
|
+
# Tags and releases on a version bump. Nothing here bumps the version: the flow
|
|
3
|
+
# is to change `[project] version` in pyproject.toml in a PR, merge it to main,
|
|
4
|
+
# and this workflow does the rest (tag, build, checksums, GitHub Release, and -
|
|
5
|
+
# once deliberately enabled, see the publish step - PyPI).
|
|
6
|
+
name: Tag and Release
|
|
7
|
+
|
|
8
|
+
on:
|
|
9
|
+
push:
|
|
10
|
+
branches:
|
|
11
|
+
- main
|
|
12
|
+
paths:
|
|
13
|
+
- "pyproject.toml"
|
|
14
|
+
- "uv.lock"
|
|
15
|
+
- "src/**"
|
|
16
|
+
- ".github/workflows/tag_and_release.yaml"
|
|
17
|
+
- ".github/workflows/test.yaml"
|
|
18
|
+
# a re-run path for when a release run failed after the version was already
|
|
19
|
+
# merged; the tag check below still makes it a no-op if the tag exists
|
|
20
|
+
workflow_dispatch:
|
|
21
|
+
|
|
22
|
+
permissions:
|
|
23
|
+
contents: read
|
|
24
|
+
|
|
25
|
+
concurrency:
|
|
26
|
+
# deliberately no cancel-in-progress: never kill a release mid-flight
|
|
27
|
+
group: release-${{ github.ref }}
|
|
28
|
+
|
|
29
|
+
jobs:
|
|
30
|
+
# Decides whether there is anything to do, so the expensive jobs below can be
|
|
31
|
+
# skipped outright. A push that touches src/** without bumping the version
|
|
32
|
+
# gets a green, near-instant run and no release.
|
|
33
|
+
version:
|
|
34
|
+
name: Check for an untagged version
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
# fork guard: a fork that pushes to its own main should not try to release
|
|
37
|
+
if: github.repository == 'jgstew/bigfix-remote-client-relevance'
|
|
38
|
+
outputs:
|
|
39
|
+
version: ${{ steps.getversion.outputs.version }}
|
|
40
|
+
release_needed: ${{ steps.tagged.outputs.release_needed }}
|
|
41
|
+
steps:
|
|
42
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
43
|
+
with:
|
|
44
|
+
# full history + tags: the tag check below needs every existing tag
|
|
45
|
+
fetch-depth: 0
|
|
46
|
+
fetch-tags: true
|
|
47
|
+
persist-credentials: false
|
|
48
|
+
|
|
49
|
+
# `[project] version` in pyproject.toml is the single source of truth.
|
|
50
|
+
# tomllib is stdlib on the 3.11+ this package requires, and the runner's
|
|
51
|
+
# preinstalled python is new enough, so no setup-python step is needed.
|
|
52
|
+
- name: Read version from pyproject.toml
|
|
53
|
+
id: getversion
|
|
54
|
+
run: |
|
|
55
|
+
version="$(python3 -c 'import pathlib, tomllib; print(tomllib.loads(pathlib.Path("pyproject.toml").read_text())["project"]["version"])')"
|
|
56
|
+
if ! printf '%s' "$version" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+([.-]?[0-9A-Za-z.]+)?$'; then
|
|
57
|
+
echo "::error::unexpected version string in pyproject.toml: '$version'"
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
60
|
+
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
61
|
+
echo "version: $version"
|
|
62
|
+
|
|
63
|
+
# Release only if this version has no tag yet. This is what makes the
|
|
64
|
+
# workflow idempotent and safe to re-run.
|
|
65
|
+
- name: Check whether v<version> is already tagged
|
|
66
|
+
id: tagged
|
|
67
|
+
env:
|
|
68
|
+
VERSION: ${{ steps.getversion.outputs.version }}
|
|
69
|
+
run: |
|
|
70
|
+
if git show-ref --tags --verify --quiet -- "refs/tags/v${VERSION}"; then
|
|
71
|
+
echo "release_needed=false" >> "$GITHUB_OUTPUT"
|
|
72
|
+
echo "v${VERSION} is already tagged - nothing to release"
|
|
73
|
+
else
|
|
74
|
+
echo "release_needed=true" >> "$GITHUB_OUTPUT"
|
|
75
|
+
echo "v${VERSION} has no tag yet - will release"
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
# The gate. Reusing the test workflow means the release is blocked by the
|
|
79
|
+
# exact same job that guards PRs, with no third-party wait-for-checks action
|
|
80
|
+
# and no polling. Lint/type-check are owned by pre-commit.yaml and are not
|
|
81
|
+
# waited on here - branch protection on main is what keeps those green.
|
|
82
|
+
test:
|
|
83
|
+
name: Test
|
|
84
|
+
needs: version
|
|
85
|
+
if: needs.version.outputs.release_needed == 'true'
|
|
86
|
+
uses: ./.github/workflows/test.yaml
|
|
87
|
+
|
|
88
|
+
release:
|
|
89
|
+
name: Tag and Release
|
|
90
|
+
needs: [version, test]
|
|
91
|
+
runs-on: ubuntu-latest
|
|
92
|
+
# Deliberately gates the whole release - tag, GitHub release, and PyPI
|
|
93
|
+
# upload - not just the publish step, so an environment protection rule
|
|
94
|
+
# (required reviewer, wait timer, branch restriction) stops a release
|
|
95
|
+
# before the tag is cut rather than after. The name must match the
|
|
96
|
+
# `Environment name` field of the PyPI trusted publisher exactly: PyPI
|
|
97
|
+
# checks the `environment` claim in the OIDC token, and a mismatch fails
|
|
98
|
+
# the publish. Create it under Settings > Environments; an environment
|
|
99
|
+
# with no protection rules is valid and simply records the deployment.
|
|
100
|
+
environment: pypi
|
|
101
|
+
permissions:
|
|
102
|
+
# contents: write - create the tag and the release, and upload assets.
|
|
103
|
+
# id-token: write - OIDC for the PyPI trusted-publishing step at the end
|
|
104
|
+
# of this job. Both live here rather than in a separate publish job so
|
|
105
|
+
# the built dist/ does not have to be handed between jobs through
|
|
106
|
+
# upload-artifact/download-artifact.
|
|
107
|
+
contents: write
|
|
108
|
+
id-token: write
|
|
109
|
+
steps:
|
|
110
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
111
|
+
with:
|
|
112
|
+
persist-credentials: false
|
|
113
|
+
|
|
114
|
+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
115
|
+
with:
|
|
116
|
+
python-version: "3.13"
|
|
117
|
+
|
|
118
|
+
# same awk-from-.pre-commit-config.yaml source of truth as
|
|
119
|
+
# .github/workflows/test.yaml and pre-commit.yaml
|
|
120
|
+
- name: read uv version from .pre-commit-config.yaml
|
|
121
|
+
run: |
|
|
122
|
+
UV_VERSION="$(awk '
|
|
123
|
+
/^[[:space:]]*-[[:space:]]*repo:.*astral-sh\/uv-pre-commit[[:space:]]*$/ { found = 1; next }
|
|
124
|
+
found && $1 == "rev:" {
|
|
125
|
+
if (match($0, /#[ \t]*(frozen:[ \t]*)?[vV]?[0-9]+(\.[0-9]+)*/)) {
|
|
126
|
+
ver = substr($0, RSTART, RLENGTH)
|
|
127
|
+
sub(/^#[ \t]*(frozen:[ \t]*)?[vV]?/, "", ver)
|
|
128
|
+
} else {
|
|
129
|
+
ver = $2
|
|
130
|
+
gsub(/["'\'']/, "", ver)
|
|
131
|
+
sub(/^[vV]/, "", ver)
|
|
132
|
+
}
|
|
133
|
+
print ver
|
|
134
|
+
exit
|
|
135
|
+
}
|
|
136
|
+
' .pre-commit-config.yaml)"
|
|
137
|
+
if [ -z "$UV_VERSION" ]; then
|
|
138
|
+
echo "::error::could not read the astral-sh/uv-pre-commit version from .pre-commit-config.yaml"
|
|
139
|
+
exit 1
|
|
140
|
+
fi
|
|
141
|
+
echo "UV_VERSION=$UV_VERSION" >> "$GITHUB_ENV"
|
|
142
|
+
echo "uv version: $UV_VERSION"
|
|
143
|
+
|
|
144
|
+
- name: install uv
|
|
145
|
+
run: python -m pip install "uv==${UV_VERSION}"
|
|
146
|
+
|
|
147
|
+
- name: uv build (wheel + sdist)
|
|
148
|
+
run: uv build
|
|
149
|
+
|
|
150
|
+
# `sha256sum -c SHA256SUMS.txt`-verifiable, and with no dist/ prefix in
|
|
151
|
+
# it, so it checks out wherever the assets are downloaded to. Globs are
|
|
152
|
+
# listed explicitly rather than `./*` so the output file can never end up
|
|
153
|
+
# listing itself.
|
|
154
|
+
- name: Compute SHA-256 checksums
|
|
155
|
+
working-directory: dist
|
|
156
|
+
run: |
|
|
157
|
+
sha256sum ./*.whl ./*.tar.gz > SHA256SUMS.txt
|
|
158
|
+
cat SHA256SUMS.txt
|
|
159
|
+
|
|
160
|
+
# Release notes: GitHub's own generated notes (same content the Releases
|
|
161
|
+
# UI would produce) with the checksums appended, so the hashes are
|
|
162
|
+
# readable in the release body as well as downloadable as an asset. The
|
|
163
|
+
# notes are built here instead of passing --generate-notes to `gh release
|
|
164
|
+
# create` so the appended block is not at the mercy of how that flag
|
|
165
|
+
# combines with --notes.
|
|
166
|
+
- name: Build release notes
|
|
167
|
+
env:
|
|
168
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
169
|
+
VERSION: ${{ needs.version.outputs.version }}
|
|
170
|
+
run: |
|
|
171
|
+
notes_file="${RUNNER_TEMP}/release-notes.md"
|
|
172
|
+
if ! gh api "repos/${GITHUB_REPOSITORY}/releases/generate-notes" \
|
|
173
|
+
--method POST \
|
|
174
|
+
-f "tag_name=v${VERSION}" \
|
|
175
|
+
-f "target_commitish=${GITHUB_SHA}" \
|
|
176
|
+
--jq '.body' > "$notes_file"; then
|
|
177
|
+
echo "::warning::could not generate release notes; falling back to a plain body"
|
|
178
|
+
printf 'Release v%s\n' "${VERSION}" > "$notes_file"
|
|
179
|
+
fi
|
|
180
|
+
{
|
|
181
|
+
printf '\n## SHA-256\n\n'
|
|
182
|
+
printf 'Verify with `sha256sum -c SHA256SUMS.txt`:\n\n'
|
|
183
|
+
printf '```\n'
|
|
184
|
+
cat dist/SHA256SUMS.txt
|
|
185
|
+
printf '```\n'
|
|
186
|
+
} >> "$notes_file"
|
|
187
|
+
echo "NOTES_FILE=$notes_file" >> "$GITHUB_ENV"
|
|
188
|
+
cat "$notes_file"
|
|
189
|
+
|
|
190
|
+
# `gh release create --target` creates the tag itself, so this needs no
|
|
191
|
+
# git push and the checkout above can stay credential-less. Using the gh
|
|
192
|
+
# CLI (preinstalled on the runner) also means one less third-party action
|
|
193
|
+
# to pin and keep updated.
|
|
194
|
+
- name: Create the GitHub release
|
|
195
|
+
env:
|
|
196
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
197
|
+
VERSION: ${{ needs.version.outputs.version }}
|
|
198
|
+
run: |
|
|
199
|
+
gh release create "v${VERSION}" \
|
|
200
|
+
--target "${GITHUB_SHA}" \
|
|
201
|
+
--title "v${VERSION}" \
|
|
202
|
+
--notes-file "${NOTES_FILE}" \
|
|
203
|
+
dist/*.whl dist/*.tar.gz dist/SHA256SUMS.txt
|
|
204
|
+
|
|
205
|
+
# Inert until all of these are true, so nothing can publish by accident:
|
|
206
|
+
# 1. a PyPI trusted publisher exists for this repo with workflow name
|
|
207
|
+
# `tag_and_release.yaml` and environment name `pypi`, matching the
|
|
208
|
+
# `environment:` above (https://docs.pypi.org/trusted-publishers/)
|
|
209
|
+
# 2. the repository variable PYPI_PUBLISH is set to `true`
|
|
210
|
+
# (Settings > Secrets and variables > Actions > Variables)
|
|
211
|
+
# 3. whatever protection rules the `pypi` environment carries have been
|
|
212
|
+
# satisfied - those gate this entire job, tagging included
|
|
213
|
+
# `uv publish` speaks OIDC natively given the id-token: write permission
|
|
214
|
+
# above, so there is no long-lived API token to store and no
|
|
215
|
+
# pypa/gh-action-pypi-publish pin to maintain. The release is already
|
|
216
|
+
# created at this point: a failure here leaves the GitHub release intact
|
|
217
|
+
# and is re-run by hand, not by re-running the whole workflow.
|
|
218
|
+
- name: Publish to PyPI (trusted publishing)
|
|
219
|
+
if: vars.PYPI_PUBLISH == 'true'
|
|
220
|
+
# the explicit globs keep SHA256SUMS.txt (which lives in dist/ too) out
|
|
221
|
+
# of the upload; uv publish would otherwise take everything in dist/
|
|
222
|
+
run: uv publish --trusted-publishing always dist/*.whl dist/*.tar.gz
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
name: test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
# lets tag_and_release.yaml gate the release on this same job via `uses:`
|
|
9
|
+
workflow_call:
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
13
|
+
cancel-in-progress: true
|
|
14
|
+
|
|
15
|
+
permissions:
|
|
16
|
+
contents: read
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
test:
|
|
20
|
+
name: py${{ matrix.python-version }} on ${{ matrix.os }}
|
|
21
|
+
runs-on: ${{ matrix.os }}
|
|
22
|
+
strategy:
|
|
23
|
+
fail-fast: false
|
|
24
|
+
matrix:
|
|
25
|
+
os: [ubuntu-latest]
|
|
26
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
27
|
+
include:
|
|
28
|
+
- os: macos-latest
|
|
29
|
+
python-version: "3.12"
|
|
30
|
+
- os: windows-latest
|
|
31
|
+
python-version: "3.12"
|
|
32
|
+
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
35
|
+
with:
|
|
36
|
+
persist-credentials: false
|
|
37
|
+
|
|
38
|
+
- uses: astral-sh/setup-uv@94527f2e458b27549849d47d273a16bec83a01e9 # v7
|
|
39
|
+
with:
|
|
40
|
+
enable-cache: true
|
|
41
|
+
|
|
42
|
+
# --frozen resolves from the committed lockfile. That matters because
|
|
43
|
+
# [tool.uv] exclude-newer is a rolling 7-day window: re-resolving here
|
|
44
|
+
# would drift from what was locked.
|
|
45
|
+
- name: Install
|
|
46
|
+
run: uv sync --frozen --python ${{ matrix.python-version }}
|
|
47
|
+
|
|
48
|
+
- name: Lint
|
|
49
|
+
run: uv run ruff check .
|
|
50
|
+
|
|
51
|
+
- name: Type-check
|
|
52
|
+
run: uv run mypy src
|
|
53
|
+
|
|
54
|
+
# Tests needing a real qna, Docker, sshd, or the network are marked and
|
|
55
|
+
# auto-skip here (see tests/conftest.py).
|
|
56
|
+
- name: Test
|
|
57
|
+
run: uv run pytest -q -rs
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
# Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
# uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
# poetry.lock
|
|
109
|
+
# poetry.toml
|
|
110
|
+
|
|
111
|
+
# pdm
|
|
112
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
113
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
114
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
115
|
+
# pdm.lock
|
|
116
|
+
# pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# pixi
|
|
121
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
122
|
+
# pixi.lock
|
|
123
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
124
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
125
|
+
.pixi
|
|
126
|
+
|
|
127
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
128
|
+
__pypackages__/
|
|
129
|
+
|
|
130
|
+
# Celery stuff
|
|
131
|
+
celerybeat-schedule
|
|
132
|
+
celerybeat.pid
|
|
133
|
+
|
|
134
|
+
# Redis
|
|
135
|
+
*.rdb
|
|
136
|
+
*.aof
|
|
137
|
+
*.pid
|
|
138
|
+
|
|
139
|
+
# RabbitMQ
|
|
140
|
+
mnesia/
|
|
141
|
+
rabbitmq/
|
|
142
|
+
rabbitmq-data/
|
|
143
|
+
|
|
144
|
+
# ActiveMQ
|
|
145
|
+
activemq-data/
|
|
146
|
+
|
|
147
|
+
# SageMath parsed files
|
|
148
|
+
*.sage.py
|
|
149
|
+
|
|
150
|
+
# Environments
|
|
151
|
+
.env
|
|
152
|
+
.envrc
|
|
153
|
+
.venv
|
|
154
|
+
env/
|
|
155
|
+
venv/
|
|
156
|
+
ENV/
|
|
157
|
+
env.bak/
|
|
158
|
+
venv.bak/
|
|
159
|
+
|
|
160
|
+
# Spyder project settings
|
|
161
|
+
.spyderproject
|
|
162
|
+
.spyproject
|
|
163
|
+
|
|
164
|
+
# Rope project settings
|
|
165
|
+
.ropeproject
|
|
166
|
+
|
|
167
|
+
# mkdocs documentation
|
|
168
|
+
/site
|
|
169
|
+
|
|
170
|
+
# mypy
|
|
171
|
+
.mypy_cache/
|
|
172
|
+
.dmypy.json
|
|
173
|
+
dmypy.json
|
|
174
|
+
|
|
175
|
+
# Pyre type checker
|
|
176
|
+
.pyre/
|
|
177
|
+
|
|
178
|
+
# pytype static type analyzer
|
|
179
|
+
.pytype/
|
|
180
|
+
|
|
181
|
+
# Cython debug symbols
|
|
182
|
+
cython_debug/
|
|
183
|
+
|
|
184
|
+
# PyCharm
|
|
185
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
186
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
187
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
188
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
189
|
+
# .idea/
|
|
190
|
+
|
|
191
|
+
# Abstra
|
|
192
|
+
# Abstra is an AI-powered process automation framework.
|
|
193
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
194
|
+
# Learn more at https://abstra.io/docs
|
|
195
|
+
.abstra/
|
|
196
|
+
|
|
197
|
+
# Visual Studio Code
|
|
198
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
199
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
200
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
201
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
202
|
+
# .vscode/
|
|
203
|
+
# Temporary file for partial code execution
|
|
204
|
+
tempCodeRunnerFile.py
|
|
205
|
+
|
|
206
|
+
# Ruff stuff:
|
|
207
|
+
.ruff_cache/
|
|
208
|
+
|
|
209
|
+
# PyPI configuration file
|
|
210
|
+
.pypirc
|
|
211
|
+
|
|
212
|
+
# Marimo
|
|
213
|
+
marimo/_static/
|
|
214
|
+
marimo/_lsp/
|
|
215
|
+
__marimo__/
|
|
216
|
+
|
|
217
|
+
# Streamlit
|
|
218
|
+
.streamlit/secrets.toml
|
|
219
|
+
hosts.toml
|