Skip to content

Build/pypi readiness - #2

Merged
gagreene merged 8 commits into
masterfrom
build/pypi_readiness
Aug 24, 2026
Merged

Build/pypi readiness#2
gagreene merged 8 commits into
masterfrom
build/pypi_readiness

Conversation

@gagreene

Copy link
Copy Markdown
Owner

Summary

Executes Phases 1–6 of docs/pypi-publication-readiness-plan.md (local reference doc,
not part of this diff) in preparation for the first public PyPI release of
flame-components as v0.1.1. Covers release policy, documentation, package metadata,
CI compatibility, build-artifact verification, and a secure publishing workflow.

Full test suite: 114/114 passing throughout.

Breaking changes (pre-1.0, no PyPI release exists yet)

  • Removed the 7 deprecated camelCase aliases (getMidFlameWS, getFlameLength,
    getFlameHeight, getFlameTilt, getFlameResidenceTime, getFlameDepth,
    flameComponent_ArrayMultiprocessing) entirely, rather than carrying them through a
    deprecation cycle. The package has never been published and has no known external
    consumers of the camelCase names, so there's nothing to protect.
  • Raised the supported Python floor to >=3.11 (from >=3.9) — a deliberate choice
    to stop carrying 3.9/3.10.
  • Raised the numpy floor to >=1.23.2 (from >=1.20) to match — the oldest numpy
    release with Python 3.11 wheels. Validated directly (not inferred) on the actual floor
    and multiple newer versions; still unbounded above.

Phase 1 — Release policy and package identity

  • Decided and validated the Python/numpy floors above.
  • Decided to remove (not deprecate) the camelCase aliases; done.
  • Recorded the release tag decision: v0.1.1 (not re-pointing v0.1.0, since packaging
    changes already merged after that tag).

Phase 2 — Project documentation

  • Rewrote docs/CODEBASE.md against the current src/ layout, snake_case-only API, and
    removed stale references to fixed bugs and the unshipped ArcGIS variant.
  • Expanded README.md: distribution-vs-import-name note, a verified self-contained/
    executable multiprocessing example, an API Reference section for all 7 public
    functions, a Scientific References section, and a Support/Issue-Reporting section.
  • Added CITATION.cff.

Phase 3 — Package metadata

  • Added [project.urls] (Homepage, Source, Issue Tracker, Release Notes, Documentation).
  • Added Python version classifiers only for versions actually verified this effort
    (3.11–3.14), plus Development Status / Intended Audience classifiers.
  • Modernized license metadata to SPDX (license = "MIT" + license-files), dropping the
    now-redundant classifier. Verified via a real build: License-Expression: MIT, twine check passes.
  • Confirmed exactly one runtime dependency (numpy); pytest remains test-only.

Phase 4 — CI compatibility and quality gates

  • Replaced the single ubuntu/py3.12 test job with a matrix: Python 3.11/3.12/3.13/3.14 on
    ubuntu-latest, plus a Windows + Python 3.12 leg (multiprocessing coverage).
  • Added a minimum-dependency job pinning Python 3.11 + numpy==1.23.2 exactly, with a
    version-assert step.
  • All jobs run with -W error (unexpected warnings fail the build).
  • Added scripts/smoke_test.py: a standalone script that only imports the installed
    package (no repo import) and exercises __version__, __all__, and one scalar/array/
    multiprocessing call each. Runs in every CI job and is reused by the release workflow.
  • Bumped astral-sh/setup-uv and actions/checkout to current major versions.

Phase 5 — Build and artifact verification

  • Built from a genuinely clean clone (not the working directory); twine check passes on
    both the wheel and sdist.
  • Full content-listing inspection of both artifacts against the expected
    include/exclude list.
  • Built a wheel from an independently re-extracted copy of the sdist and confirmed it's
    byte-for-byte identical (SHA-256 per file) to the directly-built wheel.
  • Installed the wheel and the sdist into two separate clean environments and ran the
    smoke test in each, outside the repo.
  • Found and fixed a real issue: the README's License link was relative
    ([LICENSE](LICENSE)), which PyPI isn't guaranteed to resolve correctly under modern
    [project.urls] metadata. Changed to an absolute GitHub URL.

Phase 6 — Secure publishing

  • Added .github/workflows/release.yml: triggers only on a human-published GitHub
    Release (never a bare tag push). An isolated build job runs the full suite, builds
    and validates distributions, installs the built wheel into a fresh environment and
    smoke-tests it, then attaches the exact files to the GitHub Release. Separate
    publish-testpypi/publish-pypi jobs (gated by the Release's prerelease flag) download
    that same artifact and publish via pypa/gh-action-pypi-publish using OIDC Trusted
    Publishing — no long-lived API token. id-token: write is scoped to only those two jobs.
  • Added docs/RELEASING.md: the ongoing release procedure, one-time setup steps, and
    recovery guidance (PyPI releases can't be replaced — yank or ship a new version).

Not yet done (needs repo/account access this session doesn't have):

  • Creating the pypi/testpypi GitHub environments (with a required reviewer on pypi).
  • Registering a pending Trusted Publisher on pypi.org and test.pypi.org for this repo,
    workflow release.yml, matching environment names.
  • Branch-protecting master to require the Tests workflow.

These are spelled out in docs/RELEASING.md's "One-time setup" section and need to happen
before release.yml can succeed — GitHub only recognizes release-triggered workflows
from the default branch, so this also couldn't be rehearsed before this merge.

Not in this PR

Phases 7–9 (TestPyPI rehearsal, production release, FuelAnalyst downstream adoption) are
next, after the account-side setup above and this merge.

Test plan

  • uv run pytest tests/ -v — 114/114 pass
  • uv build + twine check dist/* — both artifacts pass
  • Wheel and sdist each installed into a clean environment and smoke-tested
    independently of the source tree
  • Release workflow YAML validated for syntax and least-privilege permissions
    (not yet executed end-to-end — blocked on the account-side setup above)

…to Python 3.11+

Adds docs/pypi-publication-readiness-plan.md (9-phase plan for the first
public PyPI release, v0.1.1) and resolves three of its Phase 1 decisions:

1. Removed the 7 camelCase backward-compatibility wrappers (getMidFlameWS,
   getFlameLength, getFlameHeight, getFlameTilt, getFlameResidenceTime,
   getFlameDepth, flameComponent_ArrayMultiprocessing) and the
   _deprecated_alias factory from core.py, and their re-exports from
   __init__.py. The package has never been published to PyPI and has no
   known external consumers of the camelCase names, so there's nothing to
   protect by deprecating them further.

   Replaces tests/unit/test_deprecated_aliases.py (9 tests) with a new
   tests/unit/test_public_api.py (1 test) that keeps the one still-relevant
   assertion: __all__ contains exactly the 7 snake_case public names.

   Updates docs/TEST_SUITE_DESCRIPTION.md (122 -> 114 tests; unit 110 -> 102).

2. Narrowed supported Python to `>=3.11` (from `>=3.9`), a deliberate choice
   to stop carrying 3.9/3.10. Updates pyproject.toml `requires-python`,
   README.md's stated floor, and regenerates uv.lock (drops all 3.9/3.10
   resolution markers and wheels).

3. Raised the numpy floor to `>=1.23.2` (from `>=1.20`) to match -- it's the
   oldest numpy release with Python 3.11 wheels. Validated directly rather
   than inferred: `uv venv --python 3.11` + `uv pip install -e ".[test]"
   "numpy==1.23.2"` with an explicit version assertion -- 114/114 pass.
   Also confirmed at numpy 2.4.6 (py3.11) and 2.5.2 (py3.14, newest
   available locally). No upper bound added.

Full suite verified via `uv sync` + `uv run pytest`: 114/114 pass.
…dd CITATION.cff

Resolves the readiness plan's Phase 2 documentation items:

1. Rewrote docs/CODEBASE.md against the current repository. It previously
   described the pre-refactor layout (a monolithic flame_components.py),
   the camelCase-first API, an ArcGIS raster variant presented as a shipped
   implementation, and several defects that were fixed in earlier sessions.
   Now reflects src/flame_components/core.py + __init__.py, the snake_case
   only API (no aliases), archive/flame_components_arcgisRaster.py marked
   explicitly as unshipped/unmaintained (excluded from the sdist), and only
   gotchas that are still true of the current code.

2. Expanded README.md:
   - documented the flame-components (PyPI) vs flame_components (import)
     name difference;
   - made the multiprocessing example self-contained and executable --
     defines real numpy arrays inline and shows np.concatenate(blocks) to
     get a single result array back;
   - verified both the basic usage example and the multiprocessing example
     actually run correctly via `uv run python`;
   - added an API Reference section covering all 7 public functions: units,
     model-specific required arguments, return shapes, and the
     TypeError/ValueError conventions;
   - added a Scientific References section (author/year per function,
     sourced from each function's docstring, with a note to verify full
     bibliographic detail before citing formally);
   - added a Support and Issue Reporting section listing what's needed to
     reproduce a numerical discrepancy.

3. Added CITATION.cff at the repo root (title, author, repository-code,
   license -- no fabricated DOI or unverified bibliographic details).

Full suite verified: 114/114 pass.
Resolves the readiness plan's Phase 3 metadata items in pyproject.toml:

1. Added [project.urls]: Homepage, Source, Issue Tracker, Release Notes
   (GitHub Releases -- no maintained changelog claimed), and Documentation
   (points at the README on GitHub, since there's no separate docs site).

2. Added classifiers for every Python version actually verified this
   session (3.11, 3.12, 3.13, 3.14 -- each installed fresh and run through
   the full suite, not just declared), plus Development Status :: 4 - Beta
   and Intended Audience :: Science/Research.

3. Modernized license metadata to SPDX: `license = "MIT"` +
   `license-files = ["LICENSE"]`, replacing the old `license = {file =
   "LICENSE"}` form and dropping the now-redundant "License :: OSI
   Approved :: MIT License" classifier. Verified via a real build: the
   wheel's METADATA shows `License-Expression: MIT` and `twine check`
   passes on both the wheel and sdist.

4. Author/maintainer contact info: left as name-only, no email -- explicit
   user decision.

5. Confirmed via an actual build+install (not just reading pyproject.toml):
   the wheel's only non-extra Requires-Dist is numpy>=1.23.2 (pytest is
   extra == 'test' only), and __version__ + __all__ resolve correctly in a
   freshly `uv pip install`-ed wheel in an isolated venv.

Keywords and short description reviewed and confirmed accurate; no change
needed.

Full suite verified: 114/114 pass.
Planning docs (docs/*-plan.md) are working references for the readiness
effort, not part of the public-facing repo -- they shouldn't ship to
origin. Untracks docs/pypi-publication-readiness-plan.md (kept on disk,
just no longer committed) and ignores the docs/*-plan.md pattern going
forward, which also covers docs/test-suite-robustness-improvement-plan.md
(already untracked).
…ob, smoke test

Resolves the readiness plan's Phase 4 compatibility/quality-gate items in
.github/workflows/tests.yml:

1. Bumped astral-sh/setup-uv@v3 -> @v10 (latest) and actions/checkout@v4 ->
   @v7 (stale, zero-risk to update alongside).

2. Replaced the single ubuntu/py3.12 job with a matrix covering the full
   claimed Python range: 3.11, 3.12, 3.13, 3.14 on ubuntu-latest, plus a
   Windows + Python 3.12 leg (multiprocessing coverage -- FuelAnalyst runs
   on Windows). fail-fast: false so one failing leg doesn't hide others.

3. Added a minimum-dependency job: Python 3.11 + numpy==1.23.2 pinned
   exactly (not >=1.23.2), with an explicit version-assert step so a
   resolver can't silently substitute a newer NumPy and defeat the test.

4. No separate "latest NumPy" job needed -- the regular matrix jobs already
   use `uv sync` with no NumPy pin, so they resolve the newest-compatible
   NumPy per Python version at CI run time.

5. All jobs now run `pytest tests/ -v --tb=long -W error` (unexpected
   warnings become failures; tests using pytest.warns(...) are unaffected
   since that context manager isolates its own warning capture).

6. Added scripts/smoke_test.py: a standalone script that only imports the
   installed flame_components package (no repo import) -- checks
   __version__, verifies __all__ is exactly the 7 expected snake_case
   names and each is callable, and runs one scalar call, one array call,
   and one multiprocessing call. Runs in every CI job after install;
   reusable as-is for Phase 5/7/8's build-artifact verification steps.

Verified before trusting the YAML: simulated both the regular matrix job
(Python 3.11) and the minimum-dependency job end-to-end locally -- real
venv, real install, real pytest -W error run, real smoke test run. All
pass. Branch-protection enforcement of these checks is Phase 6, not this
commit.

Full suite verified: 114/114 pass.
…ld artifacts

Resolves the readiness plan's Phase 5 build/artifact-inspection items. Only
one code change resulted (the rest was verification against a clean
clone, not committed):

Fixed a real relative-link issue in README.md: the License section linked
`[LICENSE](LICENSE)`, a relative path Warehouse (PyPI) isn't guaranteed to
resolve for `[project.urls]`-based metadata (automatic relative-link
rewriting was reliably tied to the legacy `Home-page` field, not the
modern Project-URL fields). Changed to an absolute GitHub URL, which
renders identically on GitHub and is guaranteed to work on PyPI.

Verification performed (no other files changed):
- Built from a genuinely clean git clone of this branch (not the working
  directory) with full history/tags -- confirmed docs/*-plan.md, .idea/,
  and other ignored files are correctly absent from what a real build
  would see.
- `uv sync --locked --all-extras --dev` + `uv run pytest tests/ -v`
  (114/114) + `uv build --no-sources`.
- `twine check` PASSED on both the wheel and sdist.
- Inspected full file listings of both artifacts -- wheel has only package
  code + metadata + license; sdist correctly excludes archive/, plan
  docs, caches, and IDE files.
- Built a wheel from an independently re-extracted copy of the sdist and
  diffed it byte-for-byte (SHA-256 per file) against the original wheel:
  identical, confirming the sdist->wheel path is truly reproducible.
- Installed the wheel into one fresh venv and the sdist into a second,
  separate fresh venv, and ran scripts/smoke_test.py from a directory
  with no repo on PYTHONPATH in each -- both passed.
- Re-verified twine check and README.md's raw-byte UTF-8 validity after
  the link fix.

Recorded (not the real release artifact -- hashes will differ once built
from the tagged v0.1.1 commit in Phase 8):
  flame_components-0.1.1.dev7+ge5aea5fbc-py3-none-any.whl
    sha256:d4f225ea6610df38fbc04eed6204f18f74881f65c39a1e0dae24d22bc046bfac
  flame_components-0.1.1.dev7+ge5aea5fbc.tar.gz
    sha256:593cc642fdc2ecb89c5bfaadc6fb4c759b513fe11aeecfd01ba4867f02225e29

Full suite verified: 114/114 pass.
…e procedure

Resolves the readiness plan's Phase 6 items that don't require PyPI/GitHub
account access (three items do and are called out below for the user):

1. Added .github/workflows/release.yml: triggers only on a deliberate,
   human-published GitHub Release (release: types: [published]), never a
   bare tag push.
   - `build` job (isolated): full test suite -> `uv build` -> `twine
     check` -> installs the built wheel into a fresh venv and runs
     scripts/smoke_test.py -> uploads dist/ as a workflow artifact ->
     attaches those exact files to the GitHub Release via `gh release
     upload`.
   - `publish-testpypi`/`publish-pypi` jobs each download that same
     artifact (never rebuild) and run pypa/gh-action-pypi-publish@release/v1
     -- no long-lived API token, OIDC Trusted Publishing only. Which one
     runs is decided by the Release's `prerelease` flag: checked (an rcN
     tag) -> TestPyPI, unchecked (a real version tag) -> PyPI.
   - `id-token: write` is granted only on the two publish jobs (verified
     via a parsed-YAML permissions dump); `build` gets `contents: write`
     only, for the release-upload step.
   - Bumped actions/upload-artifact and download-artifact to @v7 (latest)
     while adding them.

2. Added docs/RELEASING.md (tracked reference doc describing the ongoing
   process, not a plan): one-time Trusted Publishing/GitHub-environment/
   branch-protection setup, the cut-a-release steps, what belongs in
   release notes vs. the private vault, and recovery options (yank vs. new
   version vs. safe-to-repoint-if-nothing-published-yet).

Needs the user directly, not automatable from a local session:
- Create `pypi`/`testpypi` GitHub environments, with a required reviewer
  on `pypi`.
- Register a pending Trusted Publisher on pypi.org and test.pypi.org
  (workflow filename `release.yml`, environment names `pypi`/`testpypi`
  matching the workflow exactly).
- Protect `master`, requiring the `Tests` workflow before merge.

Full suite verified: 114/114 pass (no production code touched this phase).
CI failed on the PR: "Unable to resolve action astral-sh/setup-uv@v10,
unable to find version v10" on every Python 3.11-3.14 matrix job.

Unlike actions/checkout or actions/upload-artifact, astral-sh/setup-uv
does not publish a floating major-version tag (no "v10" ref exists in
that repo -- only exact tags like v10.0.1, v10.0.0, v9.0.0). Pinning it
the same way as GitHub's own actions was the mistake.

Fixed all three references (tests.yml's two jobs, release.yml's build
job) to the exact tag astral-sh/setup-uv@v10.0.1.

While fixing this, re-verified every other action reference in both
workflow files against the actual upstream repos (not just assumed):
actions/checkout@v7, actions/upload-artifact@v7,
actions/download-artifact@v7, and pypa/gh-action-pypi-publish@release/v1
all confirmed to exist as real refs.

Full suite verified: 114/114 pass (no test code affected -- this is a
CI-config-only fix).
@gagreene
gagreene merged commit a4618c5 into master Aug 24, 2026
6 checks passed
@gagreene
gagreene deleted the build/pypi_readiness branch August 24, 2026 22:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant