From d0e78f53fc4e494d3337bd84519711db0de9f75f Mon Sep 17 00:00:00 2001 From: golem Date: Mon, 5 Oct 2026 21:35:52 -0600 Subject: [PATCH 1/2] release: verify_index_release.sh and 0.2.3 changelog Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 17 ++++++++++++++++- scripts/verify_index_release.sh | 29 +++++++++++++++++++++++++++++ tests/test_ci_workflow.py | 2 -- 3 files changed, 45 insertions(+), 3 deletions(-) create mode 100755 scripts/verify_index_release.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 073d6f3..9a46b57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,21 @@ # Changelog -## [0.2.2] (2020-07-19) +## [0.2.3] + +Hotfix. In 0.2.2, `import app_skellington` raised +`ImportError: cannot import name 'NoCommandSpecified' from partially initialized module` +(a circular import) for every install. + + * Fix the circular import between `cli` and `app_container`. The framework + exceptions now live in `_util`, and the public names are unchanged. + * Tested on Linux, CPython 3.8–3.14 (Gitea CI). Each version builds the wheel + and sdist, installs them and smoke-tests the import. + * Tooling: ruff replaces black/isort/flake8; uv with a committed lockfile. + * Metadata: license classifier corrected to MIT-0; Python version classifiers + added; "OS Independent" replaced by Linux (Windows and macOS untested). + * README: the debug variable is `APPSKELLINGTON_DEBUG`. + +## [0.2.2] (2024-11-17) Second release is focused on cleanup, documentation. diff --git a/scripts/verify_index_release.sh b/scripts/verify_index_release.sh new file mode 100755 index 0000000..a5b1479 --- /dev/null +++ b/scripts/verify_index_release.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Install a published app_skellington from a package index into a clean venv +# for every supported Python, then run scripts/smoke_import.py against it. +# +# Usage: scripts/verify_index_release.sh VERSION [INDEX_URL] +# INDEX_URL defaults to TestPyPI. Dependencies always come from PyPI, +# because TestPyPI hosts junk uploads under real dependency names. +set -euo pipefail + +version="${1:?usage: $0 VERSION [INDEX_URL]}" +index="${2:-https://test.pypi.org/simple/}" +here="$(cd "$(dirname "$0")" && pwd)" +pythons=(3.8 3.9 3.10 3.11 3.12 3.13 3.14) + +work="$(mktemp -d)" +trap 'rm -rf "$work"' EXIT + +for py in "${pythons[@]}"; do + venv="$work/$py" + uv venv -q --python "$py" "$venv" + uv pip install -q --python "$venv" --no-cache --no-deps \ + --index-url "$index" "app_skellington==$version" + "$venv/bin/python" -c 'import importlib.metadata as m; print("\n".join(m.requires("app_skellington") or []))' \ + > "$work/requirements.txt" + uv pip install -q --python "$venv" -r "$work/requirements.txt" + (cd / && "$venv/bin/python" "$here/smoke_import.py" "$version") +done + +echo "verified app_skellington==$version from $index on: ${pythons[*]}" diff --git a/tests/test_ci_workflow.py b/tests/test_ci_workflow.py index b086eec..de7da1d 100644 --- a/tests/test_ci_workflow.py +++ b/tests/test_ci_workflow.py @@ -4,7 +4,6 @@ import pathlib import re -import pytest import yaml ROOT = pathlib.Path(__file__).resolve().parent.parent @@ -95,7 +94,6 @@ def test_classifiers_match_matrix(): assert claimed == expected_versions() -@pytest.mark.skipif(not VERIFY_SCRIPT.exists(), reason="added with the release task") def test_verify_script_covers_matrix(): match = re.search(r"^pythons=\(([^)]*)\)", VERIFY_SCRIPT.read_text(), re.M) assert match.group(1).split() == expected_versions() From 63af95a769860e28e89b8b98cea596371f12c0ed Mon Sep 17 00:00:00 2001 From: golem Date: Mon, 5 Oct 2026 21:40:29 -0600 Subject: [PATCH 2/2] fix(release): verify script installs only from the given index, wheel-only Review findings: - UV_FIND_LINKS / extra-index env vars or a uv.toml could redirect the install to a local build and false-pass the gate; now --no-config on every uv call and refuse to run when an index-redirecting var is set - an sdist fallback would build with backends fetched from TestPyPI; install the package with --only-binary :all: - show the package install (no -q) so the log records what was installed - changelog: note the removed [dev] extra Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 2 ++ scripts/verify_index_release.sh | 22 ++++++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a46b57..e417404 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ Hotfix. In 0.2.2, `import app_skellington` raised * Tested on Linux, CPython 3.8–3.14 (Gitea CI). Each version builds the wheel and sdist, installs them and smoke-tests the import. * Tooling: ruff replaces black/isort/flake8; uv with a committed lockfile. + * Removed the `[dev]` extra: `pip install app_skellington[dev]` no longer + installs dev tools. Contributors use `uv sync` (a PEP 735 dependency group). * Metadata: license classifier corrected to MIT-0; Python version classifiers added; "OS Independent" replaced by Linux (Windows and macOS untested). * README: the debug variable is `APPSKELLINGTON_DEBUG`. diff --git a/scripts/verify_index_release.sh b/scripts/verify_index_release.sh index a5b1479..5cc3ee1 100755 --- a/scripts/verify_index_release.sh +++ b/scripts/verify_index_release.sh @@ -12,17 +12,31 @@ index="${2:-https://test.pypi.org/simple/}" here="$(cd "$(dirname "$0")" && pwd)" pythons=(3.8 3.9 3.10 3.11 3.12 3.13 3.14) +# The package must come from $index and nowhere else. Otherwise a local +# build could pass for the published release. --no-config ignores +# uv.toml/pyproject settings; these variables would still redirect, so refuse. +for var in UV_INDEX UV_INDEX_URL UV_DEFAULT_INDEX UV_EXTRA_INDEX_URL \ + UV_FIND_LINKS UV_INDEX_STRATEGY UV_NO_INDEX PIP_INDEX_URL \ + PIP_EXTRA_INDEX_URL PIP_FIND_LINKS; do + if [ -n "${!var:-}" ]; then + echo "refusing: $var is set and could redirect the install away from $index" >&2 + exit 2 + fi +done + work="$(mktemp -d)" trap 'rm -rf "$work"' EXIT for py in "${pythons[@]}"; do venv="$work/$py" - uv venv -q --python "$py" "$venv" - uv pip install -q --python "$venv" --no-cache --no-deps \ - --index-url "$index" "app_skellington==$version" + uv venv -q --no-config --python "$py" "$venv" + # Wheel only: an sdist fallback would fetch its build backend from $index + # (TestPyPI). Not quiet, so the log shows what was installed. + uv pip install --no-config --python "$venv" --no-cache --no-deps \ + --only-binary :all: --index-url "$index" "app_skellington==$version" "$venv/bin/python" -c 'import importlib.metadata as m; print("\n".join(m.requires("app_skellington") or []))' \ > "$work/requirements.txt" - uv pip install -q --python "$venv" -r "$work/requirements.txt" + uv pip install -q --no-config --python "$venv" -r "$work/requirements.txt" (cd / && "$venv/bin/python" "$here/smoke_import.py" "$version") done