Skip to content

Publish PyPI on develop as PEP 440 dev releases (prerelease channel) - #74

Merged
ptr727 merged 6 commits into
developfrom
pypi-develop-prerelease
May 11, 2026
Merged

Publish PyPI on develop as PEP 440 dev releases (prerelease channel)#74
ptr727 merged 6 commits into
developfrom
pypi-develop-prerelease

Conversation

@ptr727

@ptr727ptr727 commented May 11, 2026

Copy link
Copy Markdown
Owner

Summary

Make PyPI behave like NuGet / Docker / GitHub releases — main pushes ship release versions, develop pushes ship prerelease versions. PyPI was the odd one out, only publishing on main.

Workflow changes

build-pypilibrary-task.yml

New "Compute PyPI version step" before "Write version into _version.py":

github.refComputed versionpip install behavior
refs/heads/mainMajor.Minor.Patch.BuildNumber (e.g. 1.0.47.53309)Picked by default.
refs/heads/developMajor.Minor.Patch.BuildNumber.dev0 (e.g. 1.0.47.53400.dev0)Filtered out unless --pre is passed.
Anything else (PR validation, feature branches)AssemblyFileVersion as-isNever published — version just needs to be PEP 440 valid for uv build.

Why .dev0 not .devB (Copilot caught this on the first review): PEP 440 compares release segments first. M.N.P.devN has release [M,N,P] while main's M.N.P.B has [M,N,P,B] — main wins on segments even with --pre. Keeping the BuildNumber in the release segment (M.N.P.B.dev0) makes develop's segment grow past main's per commit, which is what lets --pre actually prefer develop. NBGV BuildNumber is unique per commit, so a constant .dev0 is enough to disambiguate uploads.

Edge case (documented inline): in the window between a release merge to main and the next commit on develop, develop's BuildNumber equals main's (or is one lower), so --pre will still resolve to the main release until a new develop commit lands. Self-healing.

The sed step now consumes steps.pypiver.outputs.version instead of needs.get-version.outputs.AssemblyFileVersion directly.

publish-release.yml

  • Drop if: github.ref == 'refs/heads/main' on publish-pypi. The job now runs on every push to main AND develop, like its NuGet / Docker / executable siblings.
  • Comment rewritten to describe the dual-channel model and point at the env Deployment branch rule as the security boundary.

Operator action (already done out-of-band)

  • pypi GitHub environment's Deployment branch rule now allows bothmain and develop. Without this, develop pushes would be blocked at the env gate. Confirmed done by repo owner.

Documentation

  • PyPiLibrary/README.md:
    • Stack-table "Version" entry rewritten to describe the branch-aware format.
    • "Publishing" section gains a new "Two-channel publishing" paragraph explaining pip install <pkg> (release channel) vs pip install --pre <pkg> (dev channel), the BuildNumber-in-release-segment rationale, and the edge case.
    • First-time-setup step 2 lists both main and develop as required entries in the env Deployment branch rule.
  • _version.py docstring updated to describe both branches' PEP 440 forms and the post-release edge case.

Test plan

  • CI passes on this PR (PR validation runs build-pypilibrary-task; the new Compute step lands in the "other refs" branch and uses AssemblyFileVersion as-is — same observable behavior as before for PR builds).
  • After merge to develop: the develop push fires publish-release.yml, publish-pypi enters the pypi environment (no longer blocked by if: or env rule), and uploads M.N.P.B.dev0 to PyPI.
  • pip install ptr727-projecttemplate-library still resolves to the latest main release (unchanged); pip install --pre ptr727-projecttemplate-library resolves to the latest develop dev release.
  • Next release on main ships PyPI at M.N.P.B as before.

Mirror the NuGet / Docker / GitHub-release pattern where main pushes
ship release versions and develop pushes ship prerelease versions —
PyPI was the odd one out, only publishing on main.
Workflow changes
- build-pypilibrary-task.yml: new "Compute PyPI version step" formats
the version from NBGV `AssemblyFileVersion` per branch ref:
refs/heads/main -> `Major.Minor.Patch.BuildNumber`
(PEP 440 release; what `pip install` picks
by default).
refs/heads/develop -> `Major.Minor.Patch.devBuildNumber`
(PEP 440 dev release; filtered out by `pip`
unless `--pre` is passed, matching NuGet
beta tags / GitHub prerelease flag).
other refs (PR validation via test-release-task, feature
branches) -> `AssemblyFileVersion` as-is. These refs
never publish; this is just so `uv build` accepts the
version string during build validation.
The "Write version into _version.py step" now consumes
`steps.pypiver.outputs.version` instead of NBGV's
`AssemblyFileVersion` directly.
- publish-release.yml: remove the `if: github.ref == 'refs/heads/main'`
guard on `publish-pypi`. The job now runs on every push to main and
develop, just like the NuGet / Docker / executable publish jobs do.
Comment rewritten to describe the dual-channel model and point at
the env Deployment branch rule as the security boundary.
Operator action (done out-of-band; documented in PyPiLibrary/README.md)
- `pypi` GitHub environment's Deployment branch rule now allows both
`main` and `develop`. Without this, develop pushes would be blocked
at the env gate.
Documentation
- PyPiLibrary/README.md: stack-table "Version" entry expanded to
describe the branch-aware version format. "Publishing" section
gains a new "Two-channel publishing" paragraph explaining the
`pip install <pkg>` vs `pip install --pre <pkg>` flow. First-time
setup step 2 updated to list both `main` and `develop` in the env
Deployment branch rule.
- PyPiLibrary/src/.../_version.py docstring updated to describe both
branches' PEP 440 forms.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CopilotAI review requested due to automatic review settings May 11, 2026 21:09

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the PyPI publishing pipeline to publish from both main (release channel) and develop (prerelease/dev channel), aligning PyPI behavior with the repo’s existing NuGet/Docker/GitHub release model. It also updates documentation to describe the new two-channel publishing scheme and the branch-aware version formatting.

Changes:

  • Add a “Compute PyPI version” step in the reusable PyPI build workflow and use its output when rewriting _version.py before uv build.
  • Enable the publish-pypi job to run on both main and develop pushes (relying on the pypi environment branch gate for protection).
  • Update PyPiLibrary docs (README.md and _version.py docstring) to describe the dual-channel publishing/versioning behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

FileDescription
PyPiLibrary/src/ptr727_projecttemplate_library/_version.pyDocstring updated to describe branch-aware PEP 440 versioning.
PyPiLibrary/README.mdDocumentation updated for two-channel publishing and branch-aware versions.
.github/workflows/publish-release.ymlRemoves main-only job gating; comments updated to describe dual-channel publishing.
.github/workflows/build-pypilibrary-task.ymlAdds computed PyPI version output and uses it when rewriting _version.py.

Comment thread.github/workflows/build-pypilibrary-task.yml
Comment thread.github/workflows/publish-release.yml Outdated
Comment threadPyPiLibrary/README.md
Comment threadPyPiLibrary/src/ptr727_projecttemplate_library/_version.py Outdated
Copilot review on PR #74 caught a real ordering bug across four
locations: the develop version `Major.Minor.Patch.devBuildNumber`
won't behave as the docs claim because PEP 440 compares release
segments first. `1.0.47.devX` has release `[1,0,47]`, while main's
`1.0.47.53309` has release `[1,0,47,53309]` — main wins on the
release-segment comparison, so `pip install --pre <pkg>` still
resolves to main, never to the develop dev build.
Fix: keep the BuildNumber in the release segment and put the dev
marker after it as `.dev0`:
main: Major.Minor.Patch.BuildNumber (e.g., 1.0.47.53309)
develop: Major.Minor.Patch.BuildNumber.dev0 (e.g., 1.0.47.53400.dev0)
Develop's BuildNumber grows past main's after every new commit on
develop, so develop's release segment `[1,0,47,53400]` compares
higher than main's `[1,0,47,53309]`, and the `.dev0` suffix marks
develop as a prerelease. Result:
- `pip install <pkg>` → main (dev filtered out by default)
- `pip install --pre <pkg>` → develop (highest release segment, dev)
Each develop push has a unique BuildNumber (NBGV increments it per
commit), so a constant `.dev0` is enough to disambiguate uploads —
no second counter needed in the dev segment.
Edge case documented: in the window between a release merge to main
and the next commit on develop, develop's BuildNumber equals main's
(or is one lower), so `--pre` will still resolve to the main release
until a new develop commit lands. Self-healing.
Updates in four places:
- .github/workflows/build-pypilibrary-task.yml: version computation
now produces `${AFV}.dev0` on develop instead of splitting AFV and
rebuilding with `.dev${build}`. Comment block rewritten to spell
out the PEP 440 ordering argument and the edge case.
- .github/workflows/publish-release.yml: publish-pypi comment
rewritten to match.
- PyPiLibrary/README.md: stack-table "Version" entry, Publishing
section's "Two-channel publishing" paragraph, and Template
Adoption section all rewritten.
- PyPiLibrary/src/.../_version.py docstring rewritten to match.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment thread.github/workflows/build-pypilibrary-task.yml Outdated
The "Write version into _version.py step" comment block still
referenced the discarded `M.N.P.devB` scheme that f475ed8 replaced
with `M.N.P.B.dev0`. Update the prose to match the actual workflow
behaviour. No code change; comment only.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment thread.github/workflows/build-pypilibrary-task.yml
Comment threadPyPiLibrary/src/ptr727_projecttemplate_library/_version.py
Copilot caught that _version.py's docstring claims `pip install --pre`
always prefers develop over main, but doesn't note the post-release
window where develop's BuildNumber can be equal to or lower than
main's. Add the edge-case note here for consistency with the workflow
comment and PyPiLibrary/README.md, which both already document it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

Comment threadPyPiLibrary/README.md Outdated
Comment threadPyPiLibrary/README.md Outdated
Comment threadPyPiLibrary/src/ptr727_projecttemplate_library/_version.py Outdated
Copilot review on afc3949 caught that the docs in three places still
say all four artifact families correspond to the same NBGV "release
commit", but the two-channel publishing model means develop publishes
artifacts for ordinary (non-release) commits too. Rewording:
- PyPiLibrary/README.md: both occurrences ("Version" stack-table entry
and "Template Adoption" section) now say "per commit".
- PyPiLibrary/src/.../_version.py docstring: rewrites the closing
sentence to say "the same commit (main pushes publish release
versions; develop pushes publish PEP 440 dev releases / NBGV
prereleases)" so the distinction is explicit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment thread.github/workflows/build-pypilibrary-task.yml Outdated
Final occurrence of "per release commit" that I missed in 92d20b0.
Same fix: reword to "per commit (main pushes publish release versions;
develop pushes publish PEP 440 dev releases / NBGV prereleases)" to
match the README and _version.py docstring.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ptr727
ptr727 requested a review from CopilotMay 11, 2026 21:42
@ptr727
ptr727 enabled auto-merge (squash) May 11, 2026 21:42

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

@ptr727
ptr727 merged commit 2a27067 into developMay 11, 2026
25 checks passed
@ptr727
ptr727 deleted the pypi-develop-prerelease branch May 11, 2026 21:55
ptr727 added a commit that referenced this pull request May 11, 2026
…75)
Release merge: brings one squashed PR from develop into main, plus a
routine main → develop back-merge that catches develop up to main's last
release commit.
## Squashed PR included
- **#74 — Publish PyPI on develop as PEP 440 dev releases (prerelease
channel).** Aligns PyPI publish behavior with the existing NuGet /
Docker / GitHub-release pattern — develop pushes now publish a PEP 440
dev release alongside the main stable release on a single PyPI project.
### What changed
- `publish-pypi` job in `publish-release.yml` no longer has the `if:
github.ref == 'refs/heads/main'` guard — runs on both `main` and
`develop` pushes.
- New "Compute PyPI version step" in `build-pypilibrary-task.yml`
formats the version per branch:
- `main` → `Major.Minor.Patch.BuildNumber` (PEP 440 release)
- `develop` → `Major.Minor.Patch.BuildNumber.dev0` (PEP 440 dev release)
- The BuildNumber stays in the release segment on develop so develop's
release segments climb past main's per commit, giving `pip install --pre
<pkg>` the correct ordering to resolve to develop.
- Docs updated (PyPiLibrary/README.md "Two-channel publishing" +
"Version" stack entry; `_version.py` docstring; inline workflow
comments).
- Through five rounds of Copilot review on the source PR.
## Back-merge included
- **`ffb9e64` — Merge remote-tracking branch 'origin/main' into
develop.** Per AGENTS.md release flow, brings main's last release merge
commit (`d03d810`) into develop's history so develop is no longer
"BEHIND" main. No content change; the second parent reference is what
matters. Also bumps develop's NBGV gitHeight by 1, which activates the
PyPI `--pre` channel from PR #74 (the back-merge commit publishes
`1.0.48.<B>.dev0`, which is unambiguously higher than main's
`1.0.47.53309`).
## Operator action items (already completed out-of-band)
- `CODEGEN_APP_ID` / `CODEGEN_APP_PRIVATE_KEY` already in both Actions
and Dependabot secret stores.
- `WORKFLOW_PAT` secret + PAT revoked.
- `pypi` GitHub environment Deployment branch rule already allows both
`main` and `develop`.
## Notes
- Merge method: **merge-commit** (per [AGENTS.md branching
model](https://github.com/ptr727/ProjectTemplate/blob/develop/AGENTS.md#branching-model)).
## Test plan
- [ ] CI passes on the merge commit.
- [ ] After merge, the publish-release run on main publishes
`1.0.48.<B>` as the next stable release (Patch bumped from 47 to 48
because of the back-merge contributing one to gitHeight).
- [ ] `pip install ptr727-projecttemplate-library` (no `--pre`) still
resolves to a release version (`1.0.48.<B>` after this merge, was
`1.0.47.53309` before).
- [ ] `pip install --pre ptr727-projecttemplate-library` resolves to the
latest develop dev release (`1.0.48.<B'>.dev0` from develop's tip, where
`B' >= B`).
ptr727 added a commit that referenced this pull request May 11, 2026
…erit (#76)
Two small workflow follow-ups that have been outstanding through PR #70
and PR #74 reviews.
## 1. `get-version-task.yml` — carve out the `dotnet/nbgv@master`
exception in comments
`dotnet/nbgv` is the only third-party action in this repo that's not
SHA-pinned. Copilot has flagged this twice now ([PR
#70](#70) and again in
passing). Adding an inline comment that explicitly documents it as the
AGENTS.md exception so future reviewers / agents don't keep flagging it.
Reasoning recorded in the comment:
- Upstream tag stream is dormant — latest tag `v0.5.1` (`3cf2d96c…`)
lags `master` (`b944774b…` as of today). Real fixes accumulate on
`master` between tag bumps.
- Dependabot's GitHub Actions ecosystem tracks tagged releases. If we
pin to a post-`v0.5.1` `master` SHA, Dependabot will either silently
stall (no new tags to bump to) or attempt a **downgrade** PR to
`v0.5.1`. Neither beats just floating on `master`.
- Upstream owner is Microsoft (`dotnet/`), so the
tag-/branch-retargeting risk the rule guards against is materially lower
than for a random-author action.
## 2. `build-nugetlibrary-task.yml` — drop redundant `secrets: inherit`
`get-version-task.yml` declares no required secrets in its `on:
workflow_call` block, so `secrets: inherit` on the `get-version` job
call was passing the caller's secret context to a job that consumes
none. Same fix [PR
#74](#74) applied to
`build-pypilibrary-task.yml`; flagged by Copilot there but declined here
for opportunistic-pin scope. This PR closes the gap.
## Test plan
- [ ] CI passes on this PR.
- [ ] No behavior change on either workflow (NBGV invocation unchanged,
version outputs identical, no secrets actually consumed by
`get-version`).
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ptr727 added a commit that referenced this pull request May 12, 2026
…nups (#77)
Release merge: brings two squashed PRs and one unnecessary back-merge
commit from `develop` into `main`.
## Squashed PRs included
- **#76 — Document `dotnet/nbgv@master` exception and drop redundant
`secrets: inherit`.**
- `.github/workflows/get-version-task.yml`: inline comment carves out
`dotnet/nbgv@master` as the deliberate deviation from the AGENTS.md
SHA-pinning rule (upstream tag stream is dormant, Dependabot would stall
or attempt a downgrade if we pinned, upstream owner is Microsoft so
retargeting risk is low).
- `.github/workflows/build-nugetlibrary-task.yml`: dropped `secrets:
inherit` from the `get-version` job call (same fix PR #74 applied to
`build-pypilibrary-task.yml`).
- **#78 — Forward-only develop with dual-target Dependabot + codegen.**
The substantive change in this release:
- **AGENTS.md "Branching Model"** explicitly codifies forward-only
develop (no `main → develop` back-merges; develop squash-only ruleset
blocks them) and the dual-target bot model with rationale.
- **`.github/dependabot.yml`** duplicates every ecosystem entry per
branch (six entries total) so Dependabot opens parallel PRs against both
`main` and `develop` independently. Both branches stay current on dep
versions without back-merges.
- **`.github/workflows/run-codegen-pull-request-task.yml`** runs as a
matrix over `main` and `develop`. Branch names `codegen-main` and
`codegen-develop`; each opens a PR against its own base.
- **`.github/workflows/merge-bot-pull-request.yml`** `merge-codegen` job
uses strict head/base pairing and dispatches `--squash` (develop) vs
`--merge` (main) per base, same `case` statement pattern as
`merge-dependabot`.
- **README.md "Template - Release Distribution Model: Push vs. Pull"**
(new section) — documents the default push-on-merge model and the
manual-release alternative for HACS / distro-vendored projects,
referencing
[homeassistant-purpleair](https://github.com/ptr727/homeassistant-purpleair)
as the working example.
- **README.md "Template - GitHub Setup"** rulesets section split into
separate Develop (squash-only + `Require linear history`) and Main
(merge-commit-only) rulesets with shared settings extracted. Repo-level
Pull Requests block now correctly shows **both** `Allow merge commits`
and `Allow squash merging` enabled (the prior wording suggested merge
would be disabled, which contradicted the actual main ruleset).
## Unnecessary back-merge `5ce95cf` (acknowledged misstep)
I opened this release with a `Merge remote-tracking branch 'origin/main'
into develop` commit assuming the back-merge pattern was the standard
cycle close. **It wasn't.** That model conflicts with AGENTS.md's
squash-only develop rule (the push only succeeded via admin bypass). PR
#78's AGENTS.md update now explicitly forbids future back-merges and
routes both bots to update both branches independently — closing the gap
that made this pattern feel necessary.
The back-merge commit is left in place: reverting requires a destructive
force-push to develop. Behavioral effect is a one-time gitHeight bump,
which fixes the `--pre` channel ordering (develop's next dev publish is
now unambiguously higher than main's last release).
## Operator action items (already completed)
- `CODEGEN_APP_ID` / `CODEGEN_APP_PRIVATE_KEY` in both Actions and
Dependabot secret stores. ✓
- `WORKFLOW_PAT` secret + PAT revoked. ✓
- `pypi` GitHub environment Deployment branch rule allows `main` and
`develop`. ✓
## Open follow-up (tracked separately, not in this release)
PR #78 documented a limitation in the actor-check guardrail on
`merge-codegen` / `merge-dependabot`: it stops the merge-bot from
re-invoking `gh pr merge --auto` on a maintainer-triggered
`synchronize`, but **does not** disable auto-merge that's already
enabled. Once auto-merge is on a bot PR, maintainer commits will land.
The honest workaround is documented (`gh pr merge --disable-auto <PR>`
before pushing). The real safeguard — a `synchronize`-triggered job that
disables auto-merge automatically when the actor isn't the bot — is a
follow-up PR I'll open after this release lands.
## Notes
- Merge method: **merge-commit** (per [AGENTS.md branching
model](https://github.com/ptr727/ProjectTemplate/blob/develop/AGENTS.md#branching-model)).
## Test plan
- [ ] CI passes on the merge commit.
- [ ] `publish-release.yml` on main publishes `1.0.<N>.<B>` as the next
stable release.
- [ ] After release, `pip install ptr727-projecttemplate-library`
resolves to the new stable; `pip install --pre
ptr727-projecttemplate-library` resolves to the latest develop dev
(assuming develop's gitHeight exceeds main's, which the back-merge
ensures).
- [ ] Next Dependabot scheduled run opens **two** PRs per ecosystem (one
against main, one against develop).
- [ ] Next codegen weekly run opens **two** PRs (`codegen-main` against
main, `codegen-develop` against develop).
- [ ] Each bot PR auto-merges via the merge-bot with the correct method
(`--squash` for develop, `--merge` for main).
Sign up for freeto 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.

2 participants

@ptr727