numerapi ships to PyPI only. There are no container images, no ECR, and no
branch-triggered deploys in this repo — if you are thinking of the
master → prod / staging → staging image flow from our other repos, that does
not apply here.
Two rules explain everything else:
numerapi_versioninsetup.pyis the release. The git tag is only the trigger. Whatever that string says is what lands on PyPI.- The version string picks the channel, not the branch. A
PEP 440 pre-release (
3.2.0.dev0) is invisible topip install numerapi; a final version (3.2.0) is what everyone gets by default.
| Ref | Role |
|---|---|
<user>/<topic> |
all work; branch off preview |
preview |
integration branch; beta releases are cut here |
master |
released state; final releases are cut here |
X.Y.Z.devN tag |
publishes a pre-release |
X.Y.Z tag |
publishes a final release |
Nothing publishes on a branch push. Only pushing a tag publishes.
| What a user runs | What they get |
|---|---|
pip install numerapi / pip install -U numerapi |
latest final version |
pip install 'numerapi==3.2.0.dev0' |
that exact pre-release |
pip install --pre numerapi |
latest including pre-releases |
- Tags are bare version numbers and must match
setup.pyexactly:3.2.0,3.2.0.dev0. Avprefix is not allowed — CI rejectsv3.2.0with an explicit error. - Use the canonical PEP 440 spelling with the dot:
3.2.0.dev0, not3.2.0dev0. Both normalize to the same release, but the canonical form avoids confusion. - Pre-releases use
.devN. IncrementNfor each beta on the same version line. - A version number can never be reused. PyPI permanently rejects re-uploading a version, even one that was deleted. If you burn a number, move to the next.
git checkout preview && git pull
git checkout -b josh/some-feature
# ... work ...
git push -u origin josh/some-feature
gh pr create --base previewTests and lint run on every push. No tag means nothing is published. preview
can sit ahead of master indefinitely — that is what it is for. Leave
setup.py alone until you are actually cutting something.
For beta users who need the code before it is stable.
git checkout preview && git pull
# 1. setup.py: numerapi_version = "3.2.0.dev0"
# 2. CHANGELOG.md: open an entry
# ## [3.2.0] - Unreleased
# - what changed
git commit -am "numerapi 3.2.0.dev0"
git push origin preview # publishes nothing
# 3. tag and push — this is the release event
git tag 3.2.0.dev0
git push origin 3.2.0.dev0Verify:
gh run list --workflow=pypi.yml --limit 1 # expect success
pip install 'numerapi==3.2.0.dev0' # what beta users run
pip install -U numerapi # must NOT be the dev versionTell beta users to install the exact version. Note that pip index versions and
the simple index can lag a few minutes behind a successful publish on CDN cache;
an exact-version install works immediately.
For the next beta, repeat with .dev1, .dev2, …
Flip the version to final on preview, as the last commit before merging, so
master never holds a pre-release string and picks up the release version
atomically at merge.
git checkout preview && git pull
# 1. setup.py: numerapi_version = "3.2.0" (drop the .devN suffix)
# 2. CHANGELOG.md: date the entry, e.g. ## [3.2.0] - 2026-08-17
git commit -am "numerapi 3.2.0"
git push origin preview
# 3. merge preview into master
gh pr create --base master --head preview --title "numerapi 3.2.0"
gh pr merge <n> --merge
# 4. tag master
git checkout master && git pull
grep numerapi_version setup.py # must read exactly 3.2.0
git tag 3.2.0
git push origin 3.2.0
# 5. keep preview caught up so it does not drift
git checkout preview && git merge master && git push origin previewVerify with pip install -U numerapi.
Then bump the numerapi pin in tournament-monorepo (shared, init-round,
integration-test, compute-pickle-scheduler). That PR moving through the
monorepo's own staging → master is what carries the new numerapi into staging
and prod images. Never pin a .devN version in anything that reaches prod.
Use this when master is released and preview holds unreleased work you do not
want to ship yet.
git checkout -b hotfix/3.2.1 master # branch off master, NOT preview
# fix + setup.py 3.2.1 + CHANGELOG entry
gh pr create --base master
# after merge:
git checkout master && git pull
git tag 3.2.1 && git push origin 3.2.1
git checkout preview && git merge master && git push origin previewRead the Docs is fully automatic — there is nothing to tag or move.
/en/latest/tracksmaster./en/stable/tracks the greatest non-pre-release semver tag, so cutting3.2.0promotes it;3.2.0.dev0is correctly ignored.
Do not create a tag or branch named stable. That overrides the automatic
behavior above, has to be force-moved by hand on every release, and silently goes
stale when someone forgets. One used to exist here and was removed for exactly
those reasons. The trade-off is that a docs-only fix reaches /en/latest/
immediately but does not appear on /en/stable/ until the next release; if that
matters, cut a patch release.
.github/workflows/pypi.yml runs on tag pushes that start with a digit (and on
v-prefixed tags, solely to reject them). It refuses to publish unless:
- The tag has no
vprefix.v3.2.0fails with an error telling you to re-tag as3.2.0. - The tag matches
setup.py. Compared as normalized PEP 440 versions, so3.2.0dev0and3.2.0.dev0are equivalent, but3.2.0against asetup.pyof3.2.0.dev0fails. - Final releases point at a commit on
master. Pre-releases skip this check, so betas can be cut frompreviewbut a final one cannot.
pytest.yml (Python 3.10–3.14) and ruff.yml run on every push and PR.
File already exists on publish. That version is already on PyPI. Bump to
the next number — you cannot re-upload, and you cannot fix it by deleting the
release on PyPI either.
Tag mismatch error. You tagged without bumping setup.py, or vice versa. Fix
setup.py, commit, delete the tag locally and on origin
(git push origin :refs/tags/X.Y.Z), then re-tag. Deleting a tag never publishes
anything.
"Final release tags must point at a commit on master." You tagged a
suffix-free version on preview. Either merge to master first, or cut it as a
.devN pre-release instead.
Do not retro-tag old releases. Any new tag starting with a digit triggers a
publish attempt that will fail on a duplicate version. Historical tags are
inconsistent (some v-prefixed, some not, several .devN tags that published
final versions before the guards existed) — leave them as they are.
Do not delete the 3.0.0.dev2 tag. The commit it points at is on no branch,
and that tag is the only thing keeping the source of the published 3.0.0
reachable.