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.
Files changed (77) hide show
  1. bigfix_remote_client_relevance-0.0.1/.editorconfig +21 -0
  2. bigfix_remote_client_relevance-0.0.1/.github/dependabot.yml +46 -0
  3. bigfix_remote_client_relevance-0.0.1/.github/workflows/pre-commit.yaml +122 -0
  4. bigfix_remote_client_relevance-0.0.1/.github/workflows/tag_and_release.yaml +222 -0
  5. bigfix_remote_client_relevance-0.0.1/.github/workflows/test.yaml +57 -0
  6. bigfix_remote_client_relevance-0.0.1/.gitignore +219 -0
  7. bigfix_remote_client_relevance-0.0.1/.pre-commit-config.yaml +221 -0
  8. bigfix_remote_client_relevance-0.0.1/.python-version +1 -0
  9. bigfix_remote_client_relevance-0.0.1/.yamllint.yaml +13 -0
  10. bigfix_remote_client_relevance-0.0.1/DESIGN.md +996 -0
  11. bigfix_remote_client_relevance-0.0.1/LICENSE +21 -0
  12. bigfix_remote_client_relevance-0.0.1/PKG-INFO +296 -0
  13. bigfix_remote_client_relevance-0.0.1/README.md +242 -0
  14. bigfix_remote_client_relevance-0.0.1/_typos.toml +7 -0
  15. bigfix_remote_client_relevance-0.0.1/pyproject.toml +119 -0
  16. bigfix_remote_client_relevance-0.0.1/pyrightconfig.json +15 -0
  17. bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/__init__.py +52 -0
  18. bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/bootstrap/__init__.py +17 -0
  19. bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/bootstrap/cache.py +141 -0
  20. bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/bootstrap/extract_local.py +229 -0
  21. bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/bootstrap/provision.py +270 -0
  22. bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/bootstrap/release_site.py +386 -0
  23. bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/bootstrap/targets.py +282 -0
  24. bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/cli.py +462 -0
  25. bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/inventory.py +145 -0
  26. bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/orchestrate.py +391 -0
  27. bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/qna_paths.py +67 -0
  28. bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/results.py +169 -0
  29. bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/transports/__init__.py +49 -0
  30. bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/transports/container.py +937 -0
  31. bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/transports/container_libs.py +173 -0
  32. bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/transports/container_setup.py +211 -0
  33. bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/transports/coordination.py +44 -0
  34. bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/transports/fastquery.py +87 -0
  35. bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/transports/local.py +385 -0
  36. bigfix_remote_client_relevance-0.0.1/src/bigfix_remote_client_relevance/transports/ssh.py +493 -0
  37. bigfix_remote_client_relevance-0.0.1/tests/conftest.py +370 -0
  38. bigfix_remote_client_relevance-0.0.1/tests/fixtures/artifacts/README.md +18 -0
  39. bigfix_remote_client_relevance-0.0.1/tests/fixtures/artifacts/tiny-qna-gzip.rpm +0 -0
  40. bigfix_remote_client_relevance-0.0.1/tests/fixtures/artifacts/tiny-qna-lzma.rpm +0 -0
  41. bigfix_remote_client_relevance-0.0.1/tests/fixtures/artifacts/tiny-qna-zstd.rpm +0 -0
  42. bigfix_remote_client_relevance-0.0.1/tests/fixtures/qna_output/crlf_windows.txt +4 -0
  43. bigfix_remote_client_relevance-0.0.1/tests/fixtures/qna_output/echo_q_variant.txt +3 -0
  44. bigfix_remote_client_relevance-0.0.1/tests/fixtures/qna_output/empty_result.txt +3 -0
  45. bigfix_remote_client_relevance-0.0.1/tests/fixtures/qna_output/midline_prefix_trap.txt +5 -0
  46. bigfix_remote_client_relevance-0.0.1/tests/fixtures/qna_output/mixed_answer_error.txt +4 -0
  47. bigfix_remote_client_relevance-0.0.1/tests/fixtures/qna_output/multi_answer.txt +6 -0
  48. bigfix_remote_client_relevance-0.0.1/tests/fixtures/qna_output/no_showtypes.txt +4 -0
  49. bigfix_remote_client_relevance-0.0.1/tests/fixtures/qna_output/relevance_error.txt +3 -0
  50. bigfix_remote_client_relevance-0.0.1/tests/fixtures/qna_output/single_answer.txt +4 -0
  51. bigfix_remote_client_relevance-0.0.1/tests/fixtures/qna_output/utf8_answer.txt +4 -0
  52. bigfix_remote_client_relevance-0.0.1/tests/fixtures/release_site/SHA256SUMS.txt +41 -0
  53. bigfix_remote_client_relevance-0.0.1/tests/fixtures/release_site/patch_page_11.0.6.html +500 -0
  54. bigfix_remote_client_relevance-0.0.1/tests/fixtures/release_site/release_index.html +809 -0
  55. bigfix_remote_client_relevance-0.0.1/tests/integration/test_docker_container.py +244 -0
  56. bigfix_remote_client_relevance-0.0.1/tests/integration/test_live_qna_local.py +50 -0
  57. bigfix_remote_client_relevance-0.0.1/tests/integration/test_live_release_site.py +75 -0
  58. bigfix_remote_client_relevance-0.0.1/tests/integration/test_ssh_localhost.py +123 -0
  59. bigfix_remote_client_relevance-0.0.1/tests/integration/test_ssh_windows.py +90 -0
  60. bigfix_remote_client_relevance-0.0.1/tests/unit/test_bootstrap_targets.py +240 -0
  61. bigfix_remote_client_relevance-0.0.1/tests/unit/test_cache.py +168 -0
  62. bigfix_remote_client_relevance-0.0.1/tests/unit/test_cli.py +656 -0
  63. bigfix_remote_client_relevance-0.0.1/tests/unit/test_container_libs.py +182 -0
  64. bigfix_remote_client_relevance-0.0.1/tests/unit/test_container_setup.py +160 -0
  65. bigfix_remote_client_relevance-0.0.1/tests/unit/test_extract_local.py +270 -0
  66. bigfix_remote_client_relevance-0.0.1/tests/unit/test_image_coordinator.py +101 -0
  67. bigfix_remote_client_relevance-0.0.1/tests/unit/test_inventory.py +229 -0
  68. bigfix_remote_client_relevance-0.0.1/tests/unit/test_no_print.py +102 -0
  69. bigfix_remote_client_relevance-0.0.1/tests/unit/test_orchestrate.py +908 -0
  70. bigfix_remote_client_relevance-0.0.1/tests/unit/test_qna_paths.py +85 -0
  71. bigfix_remote_client_relevance-0.0.1/tests/unit/test_release_site.py +188 -0
  72. bigfix_remote_client_relevance-0.0.1/tests/unit/test_results.py +172 -0
  73. bigfix_remote_client_relevance-0.0.1/tests/unit/test_transport_container.py +1407 -0
  74. bigfix_remote_client_relevance-0.0.1/tests/unit/test_transport_fastquery.py +54 -0
  75. bigfix_remote_client_relevance-0.0.1/tests/unit/test_transport_local.py +606 -0
  76. bigfix_remote_client_relevance-0.0.1/tests/unit/test_transport_ssh.py +775 -0
  77. 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