jacl4py 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.
@@ -0,0 +1,159 @@
1
+ # CI Support and Reference Alignment
2
+
3
+ These workflows assume this package is its own repository root. GitHub does not
4
+ discover them while nested in the parent workspace. Reference workflow/action
5
+ files in `reference-pookiepy`, not its potentially stale README, define the
6
+ comparison. This note records the original differences, implemented changes
7
+ and intentional deviations.
8
+
9
+ ## Original Differences
10
+
11
+ - Local `test.yml` covered Python 3.10-3.14 on three desktop runners, with raw
12
+ unittest discovery, no coverage report, ARM jobs or floating-Python probes.
13
+ Reference `ci.yml` also has examples, integration and latest-Python jobs.
14
+ - Local `lint.yml` ran Python 3.14 Pylint directly, without a shared action or
15
+ local rcfile. Development requirements used `pylint>=4,<5` alongside runtime
16
+ requirements and coverage; reference mandatory lint pins `pylint==3.2.6`.
17
+ - Local jobs repeated setup/install commands and disabled uv caching; reference
18
+ shares setup, dependency probing and check dispatch with CI caches enabled.
19
+ - Weekly latest-Python checks and actionlint/zizmor audits were absent. Reference
20
+ additionally has weekly gRPC compatibility and installed CLI skeleton smoke.
21
+ - Local `build.yml` already ran tests, built and Twine-checked wheel/sdist,
22
+ smoke-tested both installations and uploaded artifacts. Reference has no
23
+ equivalent build workflow, but does have its framework CLI smoke workflow.
24
+ - Local publishing already separated read-only build validation from OIDC
25
+ publication, retaining the package environment URL. Originally the workflow
26
+ used a GitHub Release trigger, without skip-existing, hash controls or
27
+ post-publish tagging, as its README described. Reference
28
+ `pypi_publish.yml` uses one privileged job, master pushes and post-publish tags.
29
+ - Checkout, setup-python and setup-uv SHA pins already matched the reference.
30
+ Standalone artifact upload/download actions were already SHA-pinned; the
31
+ reference has no artifact handoff. PyPI already used exact release `v1.14.2`.
32
+ - Local publication used fixed Python 3.14; reference publishing uses `3.x`.
33
+ Release build and tag setup now use `3.x`, without optional-probe failure policy.
34
+ - Dynamic, file-based PDM version metadata already matched the reference model;
35
+ the standalone package has no framework protobuf build hook to preserve.
36
+
37
+ ## Implemented Configuration and CI Contract
38
+
39
+ - `test.yml`: push/PR mandatory Python 3.10, 3.11, 3.12, 3.13 and 3.14 on
40
+ `ubuntu-latest`, `windows-latest`, `macos-latest`, `ubuntu-22.04-arm` and
41
+ `ubuntu-24.04-arm`. This core OS/Python matrix exactly matches the reference.
42
+ Nonblocking `3.x` probes use the same five runners. Dependency-resolution
43
+ failures warn and skip probe checks; actual check failures are reported.
44
+ - Core checks use `coverage run -m unittest discover -s tests -p "test_*.py"`
45
+ followed by `coverage report -m`, exactly the reference gate, with no minimum.
46
+ `coverage>=7.10,<8` is development tooling, not runtime metadata.
47
+ - `lint.yml`: Ubuntu / Python 3.14 runs `pylint --jobs=1 jacl4py tests`, using
48
+ `pylint==4.0.10` from `requirements-dev.txt`, which includes runtime requirements.
49
+ No diagnostics are disabled; default fail-under is 10, with no custom threshold.
50
+ - The compact `.pylintrc` preserves every effective reference setting, without
51
+ comments. Only domain-specific entries are removed: the custom integration
52
+ path in `init-hook`, generated protobuf ignore patterns/modules, grpc ignored
53
+ classes, and `grpc`/`google` third-party classifications. The root import hook,
54
+ `source-roots`, `ignore=third_party,interface`, optparse/thread ignored classes,
55
+ empty settings and all other reference values remain. Configured `jobs=4`
56
+ remains; the CI command overrides it to 1, just like the reference.
57
+ - `weekly-latest-python.yml`: Monday 12:00 UTC (`0 12 * * 1`) and manual runs,
58
+ nonblocking `3.x` unit jobs on the same five runners, plus Ubuntu lint with
59
+ latest Pylint, not the mandatory development pin, matching reference weekly.
60
+ - `verify-workflows.yml`: reference syntax/expression checks with actionlint and
61
+ security audit with zizmor, on workflow/action/script changes and manual runs.
62
+ - Shared `setup-python`, `install-dependencies` and `python-check` actions use
63
+ adapted `install_dependencies.py`, `run_checks.py` and `report_probe.py` helpers.
64
+ Callers own matrices/policy; helpers omit grpc/proto/examples handling. The
65
+ selected interpreter is passed through `UV_PYTHON`; the local project
66
+ is installed for distribution-metadata tests. CI uv caches are enabled and
67
+ keyed by suite, Python, OS and architecture; release caches stay disabled.
68
+ - Action pins follow the reference. PyPI publishing intentionally uses exact
69
+ `pypa/gh-action-pypi-publish@v1.14.2`, with the same narrow zizmor unpinned-use
70
+ suppression because its published container is tag-based. No API token is stored.
71
+
72
+ ## Publication and Justified Deviations
73
+
74
+ `pypi_publish.yml` matches the configured PyPI trusted publisher identity. Pushes
75
+ to `master`, not GitHub Releases, start a read-only build job: preflight unit
76
+ tests, wheel/sdist build, Twine validation, isolated installed-artifact smoke
77
+ checks and validated artifact upload. Only its dependent publish job receives
78
+ OIDC and tag-write permissions, under environment `pypi-release` with URL
79
+ `https://pypi.org/project/jacl4py/`. It downloads the validated artifacts and uses
80
+ `verify-metadata: true`, `skip-existing: true`, `print-hash: false` and
81
+ `attestations: true`. Publication success precedes an annotated `v<version>` tag;
82
+ same-commit tags are no-ops, different-commit tags cause failure and are never moved.
83
+
84
+ - Two publication jobs deliberately retain least privilege and validated
85
+ artifacts, instead of copying the reference's single privileged job.
86
+ - Pylint is pinned to 4.0.10, not reference 3.2.6, because Python 3.14 support
87
+ was added in Pylint 4.0. See the [upstream release notes](https://pylint.readthedocs.io/en/latest/whatsnew/4/4.0/index.html).
88
+ Applicable reference configuration and failure policy remain unchanged;
89
+ diagnostic results can differ between Pylint major versions.
90
+ - `build.yml` remains an additional Ubuntu / Python 3.14 gate. Reference CLI
91
+ skeleton smoke covers 3.10/3.14/3.x on three desktop OSes, but tests installed
92
+ pookiepy skeleton generation, which this package does not provide. Package
93
+ wheel/sdist smoke checks are retained, not claimed as literal CLI matrix parity.
94
+ - Weekly gRPC, protobuf generation, examples, framework integration and CLI
95
+ suites/dependencies are excluded because they are absent here. No fake suites
96
+ or dependencies are added. Core supported-version matrix parity is unchanged.
97
+ - Existing `test.yml`, `lint.yml` and `build.yml` identities remain
98
+ instead of merging into reference `ci.yml`, reducing branch-protection
99
+ migration. Check job display names may change; review required status contexts.
100
+ - Publication has no full-matrix CI `needs` barrier, matching the reference;
101
+ only its own validated build is a dependency. Configure external controls below.
102
+ - `jacl4py/__init__.py` remains the authoritative manually edited version source.
103
+ PDM dynamic metadata needs no backend change or version bump. Tags are outputs;
104
+ there is no automatic bump or semantic-version inference from commit messages.
105
+ - Source distributions retain their existing CI-source inclusion policy, now
106
+ including all of `.github/` and `.pylintrc`, so workflows do not ship without
107
+ their new helpers. Unlike the reference, CI sources were already included here.
108
+ - Tag extraction uses uv and the selected interpreter, rather than bare Python,
109
+ consistent with the standalone tooling; version parsing and tag policy match.
110
+
111
+ ## External Settings and Validation
112
+
113
+ Configure the PyPI publisher with the actual GitHub owner/repository,
114
+ `pypi_publish.yml` and `pypi-release`; verify project availability and use unpublished
115
+ versions. Skipping existing files supports retries, not artifact replacement.
116
+ Protect `master` with mandatory supported-version test, lint and build checks.
117
+ The path-filtered workflow audit should not be universally required unless your
118
+ ruleset handles skipped workflows, or unrelated PRs can wait indefinitely.
119
+ Configure environment reviewers, allowed release branches,
120
+ and tag rules that allow the release bot to create, but not move, release tags.
121
+ Grant repository Actions the tag-write permission required by publication.
122
+ Floating-Python and weekly probes should not be required checks.
123
+
124
+ Some editor/actionlint versions cannot resolve the reference's `$/...` local
125
+ action syntax, especially in this nested workspace. Paths are relative to each
126
+ standalone repository root. The audit workflow retains the reference's narrow
127
+ actionlint compatibility filter; no security findings are filtered out.
128
+
129
+ No tests or linters were run for these configuration/documentation changes.
130
+ Reference-setting comparison is not evidence that package code passes Pylint.
131
+ Local verification parsed all nine workflow/action YAML files, Python helpers,
132
+ embedded shell commands, TOML and INI configuration. Reference matrices, release
133
+ options and permissions were compared structurally. `uv build` successfully
134
+ built the sdist and a wheel from it; the sdist includes CI helpers and `.pylintrc`.
135
+ Actual runner behavior, installed-artifact smoke checks, actionlint, zizmor and
136
+ PyPI/OIDC publication still require GitHub Actions validation.
137
+
138
+ ## Patch Summary
139
+
140
+ | File | Change |
141
+ | --- | --- |
142
+ | `.github/workflows/test.yml` | Add ARM, coverage dispatch and nonblocking latest-Python probes. |
143
+ | `.github/workflows/lint.yml` | Shared pinned-Python lint action and development requirements. |
144
+ | `.github/workflows/build.yml` | Shared setup; preserve preflight, build, metadata and artifact smoke gates. |
145
+ | `.github/workflows/pypi_publish.yml` | Master pushes, latest stable Python, reference publish options, tag after publish. |
146
+ | `.github/workflows/weekly-latest-python.yml` | Add reference weekly/manual latest-Python unit and lint probes. |
147
+ | `.github/workflows/verify-workflows.yml` | Add reference syntax and security audits. |
148
+ | `.github/actions/setup-python/action.yml` | Shared pinned tooling, interpreter output and cache policy. |
149
+ | `.github/actions/install-dependencies/action.yml` | Shared installation and nonblocking dependency probe. |
150
+ | `.github/actions/python-check/action.yml` | Shared setup, local package installation and check dispatch. |
151
+ | `.github/scripts/install_dependencies.py` | One dependency command for installation and dry-run probes. |
152
+ | `.github/scripts/run_checks.py` | Coverage/unit and serial Pylint execution. |
153
+ | `.github/scripts/report_probe.py` | Warnings and job summaries for skipped/failed probes. |
154
+ | `.github/scripts/tag_release.sh` | Annotated version tags; same-commit retry; refuse tag movement. |
155
+ | `.github/CI.md` | Differences, deviations, validation, external setup and this inventory. |
156
+ | `.pylintrc` | Applicable reference lint configuration without framework exclusions. |
157
+ | `requirements-dev.txt` | Supported, fixed Pylint pin; retain coverage and runtime requirements. |
158
+ | `pyproject.toml` | Include complete CI sources and lint configuration in sdists. |
159
+ | `README.md` | Correct release lifecycle, quality checks and version-management instructions. |
@@ -0,0 +1,46 @@
1
+ name: Install CI dependencies
2
+ description: Probe optional latest-Python jobs before installing dependencies.
3
+ inputs:
4
+ python-path:
5
+ description: Interpreter selected by setup-python.
6
+ required: true
7
+ requirements:
8
+ description: Requirement files, one per line.
9
+ default: requirements.txt
10
+ packages:
11
+ description: Additional packages or local project paths, one per line.
12
+ default: ''
13
+ probe:
14
+ description: Skip checks when latest-Python dependencies cannot resolve.
15
+ default: 'false'
16
+ outputs:
17
+ ready:
18
+ description: String true when checks can run; false when the probe was skipped.
19
+ value: ${{ inputs.probe != 'true' || steps.wheel-check.outcome == 'success' }}
20
+ runs:
21
+ using: composite
22
+ steps:
23
+ - name: Check latest Python dependencies
24
+ id: wheel-check
25
+ if: inputs.probe == 'true'
26
+ continue-on-error: true
27
+ shell: bash
28
+ env:
29
+ UV_PYTHON: ${{ inputs.python-path }}
30
+ CI_REQUIREMENTS: ${{ inputs.requirements }}
31
+ CI_PACKAGES: ${{ inputs.packages }}
32
+ run: uv run --no-project python .github/scripts/install_dependencies.py --dry-run
33
+ - name: Explain skipped latest Python probe
34
+ if: inputs.probe == 'true' && steps.wheel-check.outcome != 'success'
35
+ shell: bash
36
+ env:
37
+ UV_PYTHON: ${{ inputs.python-path }}
38
+ run: uv run --no-project python .github/scripts/report_probe.py skipped
39
+ - name: Install dependencies
40
+ if: inputs.probe != 'true' || steps.wheel-check.outcome == 'success'
41
+ shell: bash
42
+ env:
43
+ UV_PYTHON: ${{ inputs.python-path }}
44
+ CI_REQUIREMENTS: ${{ inputs.requirements }}
45
+ CI_PACKAGES: ${{ inputs.packages }}
46
+ run: uv run --no-project python .github/scripts/install_dependencies.py
@@ -0,0 +1,49 @@
1
+ name: Python check
2
+ description: Set up Python, install dependencies, and run one suite.
3
+ inputs:
4
+ python-version:
5
+ description: Python version; floating 3.x enables the optional dependency probe.
6
+ required: true
7
+ suite:
8
+ description: Check suite (unit or lint).
9
+ required: true
10
+ requirements:
11
+ description: Core requirement file.
12
+ default: requirements.txt
13
+ extra-requirements:
14
+ description: Optional additional requirement file.
15
+ default: ''
16
+ runs:
17
+ using: composite
18
+ steps:
19
+ - name: Set up Python and uv
20
+ id: setup
21
+ uses: $/.github/actions/setup-python
22
+ with:
23
+ python-version: ${{ inputs.python-version }}
24
+ cache-suffix: ${{ inputs.suite }}-py${{ inputs.python-version }}-${{ runner.os }}-${{ runner.arch }}
25
+ - name: Prepare dependencies
26
+ id: dependencies
27
+ uses: $/.github/actions/install-dependencies
28
+ with:
29
+ python-path: ${{ steps.setup.outputs.python-path }}
30
+ probe: ${{ inputs.python-version == '3.x' }}
31
+ requirements: |
32
+ ${{ inputs.requirements }}
33
+ ${{ inputs.extra-requirements }}
34
+ packages: |
35
+ .
36
+ ${{ inputs.suite == 'unit' && 'coverage' || 'pylint' }}
37
+ - name: Run checks
38
+ if: steps.dependencies.outputs.ready == 'true'
39
+ shell: bash
40
+ env:
41
+ UV_PYTHON: ${{ steps.setup.outputs.python-path }}
42
+ CHECK_SUITE: ${{ inputs.suite }}
43
+ run: uv run --no-project python .github/scripts/run_checks.py "$CHECK_SUITE"
44
+ - name: Report latest Python probe failure
45
+ if: failure() && inputs.python-version == '3.x'
46
+ shell: bash
47
+ env:
48
+ UV_PYTHON: ${{ steps.setup.outputs.python-path }}
49
+ run: uv run --no-project python .github/scripts/report_probe.py failed
@@ -0,0 +1,29 @@
1
+ name: Set up Python and uv
2
+ description: Install pinned CI tooling and expose the selected Python interpreter.
3
+ inputs:
4
+ python-version:
5
+ description: Python version requested from actions/setup-python.
6
+ required: true
7
+ cache-suffix:
8
+ description: Cache namespace for this job.
9
+ default: ${{ github.job }}
10
+ enable-cache:
11
+ description: Whether uv should cache dependencies.
12
+ default: 'true'
13
+ outputs:
14
+ python-path:
15
+ description: Absolute path to the selected Python interpreter.
16
+ value: ${{ steps.python.outputs.python-path }}
17
+ runs:
18
+ using: composite
19
+ steps:
20
+ - name: Set up Python
21
+ id: python
22
+ uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
23
+ with:
24
+ python-version: ${{ inputs.python-version }}
25
+ - name: Set up uv
26
+ uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
27
+ with:
28
+ enable-cache: ${{ inputs.enable-cache }}
29
+ cache-suffix: ${{ inputs.cache-suffix }}
@@ -0,0 +1,31 @@
1
+ """Install CI dependencies, or probe resolution without changing the environment."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import os
7
+ import subprocess
8
+ import sys
9
+
10
+
11
+ def main() -> None:
12
+ """Resolve the same requirements for probes and actual installations."""
13
+ parser = argparse.ArgumentParser(description=__doc__)
14
+ parser.add_argument("--dry-run", action="store_true")
15
+ args = parser.parse_args()
16
+ command = ["uv", "pip", "install", "--python", sys.executable]
17
+ if args.dry_run:
18
+ command.append("--dry-run")
19
+ for requirement in os.environ.get("CI_REQUIREMENTS", "").splitlines():
20
+ if requirement.strip():
21
+ command.extend(["-r", requirement.strip()])
22
+ command.extend(
23
+ package.strip()
24
+ for package in os.environ.get("CI_PACKAGES", "").splitlines()
25
+ if package.strip()
26
+ )
27
+ subprocess.run(command, check=True)
28
+
29
+
30
+ if __name__ == "__main__":
31
+ main()
@@ -0,0 +1,34 @@
1
+ """Report non-blocking latest-Python probe outcomes consistently on all runners."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import os
7
+ from pathlib import Path
8
+
9
+
10
+ def main() -> None:
11
+ """Emit a workflow warning and append an actionable job summary."""
12
+ parser = argparse.ArgumentParser(description=__doc__)
13
+ parser.add_argument("status", choices=("skipped", "failed"))
14
+ args = parser.parse_args()
15
+ title = f"Latest Python probe {args.status}"
16
+ details = (
17
+ "Required dependencies unavailable; checks skipped."
18
+ if args.status == "skipped"
19
+ else "Check job logs for Python compatibility issues or missing dependencies."
20
+ )
21
+ message = (
22
+ f"{os.environ.get('GITHUB_JOB', 'probe')} on "
23
+ f"{os.environ.get('RUNNER_OS', 'local')}: {details} "
24
+ "This probe is non-blocking; pinned Python checks remain mandatory."
25
+ )
26
+ print(f"::warning title={title}::{message}")
27
+ summary = os.environ.get("GITHUB_STEP_SUMMARY")
28
+ if summary:
29
+ with Path(summary).open("a", encoding="utf-8") as output:
30
+ output.write(f"### {title}\n{message}\n")
31
+
32
+
33
+ if __name__ == "__main__":
34
+ main()
@@ -0,0 +1,31 @@
1
+ """Run one CI check suite using the selected interpreter."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import subprocess
7
+ import sys
8
+
9
+
10
+ def run_module(*arguments: str) -> None:
11
+ """Run a Python module and propagate failures to the CI job."""
12
+ subprocess.run([sys.executable, "-m", *arguments], check=True)
13
+
14
+
15
+ def main() -> None:
16
+ """Dispatch a named suite without duplicating commands in workflow YAML."""
17
+ parser = argparse.ArgumentParser(description=__doc__)
18
+ parser.add_argument("suite", choices=("unit", "lint"))
19
+ args = parser.parse_args()
20
+ if args.suite == "unit":
21
+ run_module(
22
+ "coverage", "run", "-m", "unittest", "discover",
23
+ "-s", "tests", "-p", "test_*.py",
24
+ )
25
+ run_module("coverage", "report", "-m")
26
+ else:
27
+ run_module("pylint", "--jobs=1", "jacl4py", "tests")
28
+
29
+
30
+ if __name__ == "__main__":
31
+ main()
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
5
+ PACKAGE_INIT="${SCRIPT_DIR}/../../jacl4py/__init__.py"
6
+
7
+ VERSION="$(uv run --no-project python - "$PACKAGE_INIT" <<'PY'
8
+ import pathlib
9
+ import re
10
+ import sys
11
+
12
+ version_file = pathlib.Path(sys.argv[1])
13
+ text = version_file.read_text(encoding='utf-8')
14
+ match = re.search(r'^__version__\s*=\s*["\']([^"\']+)["\']\s*$', text, re.MULTILINE)
15
+ if not match:
16
+ raise SystemExit(f'Could not find __version__ in {version_file}')
17
+ print(match.group(1))
18
+ PY
19
+ )"
20
+
21
+ TAG="v${VERSION}"
22
+
23
+ git fetch --tags
24
+
25
+ if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
26
+ TAG_SHA="$(git rev-list -n 1 "refs/tags/${TAG}")"
27
+ HEAD_SHA="$(git rev-parse HEAD)"
28
+
29
+ if [ "$TAG_SHA" = "$HEAD_SHA" ]; then
30
+ echo "Tag ${TAG} already points to this commit; nothing to do."
31
+ exit 0
32
+ fi
33
+
34
+ echo "Tag ${TAG} already exists on a different commit (${TAG_SHA}); refusing to move it."
35
+ exit 1
36
+ fi
37
+
38
+ git config user.name "github-actions[bot]"
39
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
40
+ git tag -a "$TAG" -m "Release $TAG"
41
+ git push origin "$TAG"
@@ -0,0 +1,70 @@
1
+ name: Build
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+ workflow_call:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ defaults:
12
+ run:
13
+ shell: bash
14
+
15
+ jobs:
16
+ build:
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
20
+ with:
21
+ persist-credentials: false
22
+ - id: python
23
+ uses: $/.github/actions/setup-python
24
+ with:
25
+ python-version: "3.14"
26
+ enable-cache: 'false'
27
+ - name: Run release preflight tests
28
+ env:
29
+ UV_PYTHON: ${{ steps.python.outputs.python-path }}
30
+ run: uv run --with-requirements requirements.txt python -m unittest discover -s tests -p "test_*.py"
31
+ - name: Build source distribution and wheel
32
+ env:
33
+ UV_PYTHON: ${{ steps.python.outputs.python-path }}
34
+ run: uv build
35
+ - name: Validate distribution metadata
36
+ env:
37
+ UV_PYTHON: ${{ steps.python.outputs.python-path }}
38
+ run: uvx twine check dist/*
39
+ - name: Install and smoke-test each distribution
40
+ env:
41
+ UV_PYTHON: ${{ steps.python.outputs.python-path }}
42
+ run: |
43
+ for artifact in dist/*.whl dist/*.tar.gz; do
44
+ uv run --isolated --no-project --with "$artifact" python -I -c '
45
+ import io
46
+ import logging
47
+ import sys
48
+ from importlib.metadata import version
49
+ from jacl4py import __version__
50
+ from jacl4py.logger import JacLogger, get_logger
51
+ assert version("jacl4py") == __version__
52
+ assert "pookiepy" not in sys.modules
53
+ logger = get_logger("artifact_smoke", console_log_level=None, file_log_level=None)
54
+ assert isinstance(logger, JacLogger)
55
+ output = io.StringIO()
56
+ handler = logging.StreamHandler(output)
57
+ logger.addHandler(handler)
58
+ logger.idebug("debug")
59
+ logger.iinfo("info")
60
+ assert output.getvalue() == "debug\ninfo\n"
61
+ handler.close()
62
+ logger.removeHandler(handler)
63
+ '
64
+ done
65
+ - name: Upload validated distributions
66
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
67
+ with:
68
+ name: python-distributions
69
+ path: dist/*
70
+ if-no-files-found: error
@@ -0,0 +1,25 @@
1
+ name: Lint
2
+
3
+ on: [push, pull_request]
4
+
5
+ permissions:
6
+ contents: read
7
+
8
+ defaults:
9
+ run:
10
+ shell: bash
11
+
12
+ jobs:
13
+ pylint:
14
+ name: Pylint
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
18
+ with:
19
+ persist-credentials: false
20
+ - name: Run Pylint
21
+ uses: $/.github/actions/python-check
22
+ with:
23
+ python-version: "3.14"
24
+ suite: lint
25
+ extra-requirements: requirements-dev.txt
@@ -0,0 +1,115 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ defaults:
11
+ run:
12
+ shell: bash
13
+
14
+ jobs:
15
+ build:
16
+ runs-on: ubuntu-latest
17
+ permissions:
18
+ contents: read
19
+ steps:
20
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
21
+ with:
22
+ persist-credentials: false
23
+ - id: python
24
+ uses: $/.github/actions/setup-python
25
+ with:
26
+ python-version: "3.x"
27
+ enable-cache: 'false'
28
+ - name: Run release preflight tests
29
+ env:
30
+ UV_PYTHON: ${{ steps.python.outputs.python-path }}
31
+ run: uv run --with-requirements requirements.txt python -m unittest discover -s tests -p "test_*.py"
32
+ - name: Build source distribution and wheel
33
+ env:
34
+ UV_PYTHON: ${{ steps.python.outputs.python-path }}
35
+ run: uv build
36
+ - name: Validate distribution metadata
37
+ env:
38
+ UV_PYTHON: ${{ steps.python.outputs.python-path }}
39
+ run: uvx twine check dist/*
40
+ - name: Install and smoke-test each distribution
41
+ shell: bash
42
+ env:
43
+ UV_PYTHON: ${{ steps.python.outputs.python-path }}
44
+ run: |
45
+ for artifact in dist/*.whl dist/*.tar.gz; do
46
+ uv run --isolated --no-project --with "$artifact" python -I -c '
47
+ import io
48
+ import logging
49
+ import sys
50
+ from importlib.metadata import version
51
+ from jacl4py import __version__
52
+ from jacl4py.logger import JacLogger, get_logger
53
+ assert version("jacl4py") == __version__
54
+ assert "pookiepy" not in sys.modules
55
+ logger = get_logger("artifact_smoke", console_log_level=None, file_log_level=None)
56
+ assert isinstance(logger, JacLogger)
57
+ output = io.StringIO()
58
+ handler = logging.StreamHandler(output)
59
+ logger.addHandler(handler)
60
+ logger.idebug("debug")
61
+ logger.iinfo("info")
62
+ assert output.getvalue() == "debug\ninfo\n"
63
+ handler.close()
64
+ logger.removeHandler(handler)
65
+ '
66
+ done
67
+ - name: Upload validated distributions
68
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
69
+ with:
70
+ name: python-distributions
71
+ path: dist/*
72
+ if-no-files-found: error
73
+
74
+ publish:
75
+ needs: build
76
+ runs-on: ubuntu-latest
77
+ environment:
78
+ name: pypi-release
79
+ url: https://pypi.org/project/jacl4py/
80
+ permissions:
81
+ contents: write
82
+ id-token: write
83
+ steps:
84
+ - name: Checkout release commit for tagging
85
+ uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
86
+ with:
87
+ persist-credentials: true # zizmor: ignore[artipacked] required for release tag push
88
+ - name: Set up Python
89
+ id: python
90
+ uses: $/.github/actions/setup-python
91
+ with:
92
+ python-version: "3.x"
93
+ enable-cache: 'false'
94
+ - name: Download validated distributions
95
+ uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
96
+ with:
97
+ name: python-distributions
98
+ path: dist
99
+ - name: Publish using PyPI Trusted Publishing
100
+ uses: pypa/gh-action-pypi-publish@v1.14.2 # zizmor: ignore[unpinned-uses] exact release tag intentionally used
101
+ with:
102
+ repository-url: https://upload.pypi.org/legacy/
103
+ # repository-url: https://test.pypi.org/legacy/
104
+ packages-dir: dist
105
+ verify-metadata: true
106
+ skip-existing: true
107
+ print-hash: false
108
+ attestations: true
109
+
110
+ - name: Create and push release tag
111
+ if: success() && github.ref_name == 'master'
112
+ shell: bash
113
+ env:
114
+ UV_PYTHON: ${{ steps.python.outputs.python-path }}
115
+ run: bash .github/scripts/tag_release.sh