Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions .github/workflows/pypi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
name: PyPI Release

on:
push:
tags:
# Release tags are bare version numbers: 1.2.0, 1.2.0.dev0
- "[0-9]*"
# A `v` prefix is NOT a valid release tag. It is matched here only so the
# job can fail with an explanatory error instead of silently doing nothing.
- "v[0-9]*"

concurrency: pypi-release-${{ github.ref }}

jobs:
publish:
name: Build and publish numerai-cli to PyPI
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# full history so the master-ancestry check below can run
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: python -m pip install --upgrade build packaging setuptools twine wheel

- name: Verify tag format and version match
env:
TAG_NAME: ${{ github.ref_name }}
run: |
python - <<'PY'
import os
import pathlib
import re
import sys
from packaging.version import Version

tag = os.environ["TAG_NAME"]
if tag.startswith("v"):
sys.exit(
f"::error::Release tags must be bare version numbers. "
f"`{tag}` has a `v` prefix; delete it and tag `{tag[1:]}` instead."
)
pkg = re.search(
r'numerai_cli_version\s*=\s*"([^"]+)"',
pathlib.Path("setup.py").read_text(),
).group(1)
print(f"tag={tag} setup.py={pkg}")
if Version(tag) != Version(pkg):
sys.exit(
f"::error::Tag {tag} does not match setup.py numerai_cli_version={pkg}"
)
print(f"::notice::publishing numerai-cli {Version(pkg)}")
PY

- name: Final releases must come from master
env:
TAG_NAME: ${{ github.ref_name }}
run: |
python - <<'PY' > prerelease.txt
import os
from packaging.version import Version

print("yes" if Version(os.environ["TAG_NAME"]).is_prerelease else "no")
PY
if [ "$(cat prerelease.txt)" = "no" ]; then
git fetch --no-tags --quiet origin master
if git merge-base --is-ancestor "$GITHUB_SHA" FETCH_HEAD; then
echo "final release from a commit on master: ok"
else
echo "::error::Final release tags must point at a commit on master; $GITHUB_SHA is not one. Cut a pre-release (e.g. 1.1.7.dev0) from your branch instead."
exit 1
fi
else
echo "pre-release: master-ancestry check skipped"
fi
rm -f prerelease.txt

- name: Build a source tarball and wheel
run: python -m build

# No --skip-existing: under tag-triggered releases a duplicate version is
# a mistake worth failing on, not something to silently swallow.
- name: Publish to PyPI
run: |
python -m twine upload \
-u __token__ \
-p ${{ secrets.PYPI_API_KEY }} \
dist/*
33 changes: 9 additions & 24 deletions .github/workflows/test-and-deploy.yml
Original file line number Diff line number Diff line change
@@ -1,52 +1,37 @@
name: Test & Deploy Numerai CLI
name: Test Numerai CLI

# Publishing lives in pypi.yml and is triggered by version tags, not by pushes
# to this or any other branch. See RELEASING.md.
on:
workflow_dispatch:
push:

concurrency: deploy-numerai-cli-${{ github.ref }}
concurrency: test-numerai-cli-${{ github.ref }}

jobs:
build-python:
name: Build Python ${{ matrix.python-version }}
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v4
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: pip install .
- run: python -m unittest discover -s tests
- run: numerai copy-example
- run: test -e tournament-python3/predict.py

build-docker:
name: Build Docker
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- run: |
cd numerai/examples/tournament-python3 \
&& docker build -t example-python \
--build-arg NUMERAI_PUBLIC_ID=test \
--build-arg NUMERAI_SECRET_KEY=test .

pypi-release:
name: PyPI Release
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/master'
needs: [build-python, build-docker]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v4
with:
python-version: "3.12"
- run: python -m pip install --upgrade setuptools twine wheel
- run: python setup.py sdist bdist_wheel
- run: |
python -m twine upload \
-u __token__ \
-p ${{ secrets.PYPI_API_KEY }} \
--skip-existing dist/*
53 changes: 53 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Changelog

Notable changes to numerai-cli. Update this file as part of the version-bump
commit for each release (see [RELEASING.md](RELEASING.md)).

Releases before 1.2.0 were not tracked here — their notes live in the pull
requests that cut them.

## 1.2.0 - 2026-08-12

### Added

- Python 3.14 support. The CI test matrix now covers Python 3.10 through 3.14,
and the example prediction nodes build on `python:3.14` Docker images.
- Tag-triggered PyPI release workflow (`.github/workflows/pypi.yml`). Pushing a
bare version tag (e.g. `1.2.0`, `1.2.0.dev0`) is now the only thing that
publishes. The workflow refuses to publish if the tag has a `v` prefix, if it
doesn't match `numerai_cli_version` in `setup.py`, or if a final (non-`.devN`)
release is tagged off a commit that isn't on `master`.
- [RELEASING.md](RELEASING.md), documenting the release flow: beta `.devN`
pre-releases from topic branches, final releases from `master`, and hotfixes.
- Tests for the platform setup scripts (`tests/test_setup_scripts.py`), run by
CI on every push.
- This changelog.

### Changed

- Upgraded the pinned dependencies in all three example nodes
(`tournament-python3`, `signals-python3`, `crypto-python3`):
- `pandas` 2.3.3 → 3.0.5 (pandas 3 support)
- `pyarrow` 18.1.0 → 23.0.1
- `lightgbm` 4.5.0 → 4.7.0 — required for the new scikit-learn pin: the
scikit-learn estimator API in lightgbm ≤ 4.5.0 is incompatible with
scikit-learn ≥ 1.6 (`__sklearn_tags__` change). 4.7.0 also adds official
Python 3.14 and pandas 3 support.
- `scikit-learn` 1.6.1 → 1.8.0
- The platform setup scripts (`setup-mac.sh`, `setup-win10.ps1`) now install
Python 3.12.10 instead of 3.9.1. The macOS script uses the single universal2
installer, which supports both Intel and Apple Silicon on macOS 10.13+.
- Branch pushes no longer publish anything. The old `pypi-release` job was
removed from `test-and-deploy.yml`, which now only runs the test matrix and
the example Docker build.
- `DEPLOY.md`'s manual `~/.pypirc` + `twine` instructions were replaced with a
pointer to [RELEASING.md](RELEASING.md).

### Fixed

- The macOS setup script's Python and OS-version detection checks, which could
never match (a broken `which` comparison and a malformed version regex), were
rewritten using `command -v` and `sw_vers`.
- Publishing a version that already exists on PyPI now fails loudly instead of
silently succeeding: the publish step no longer passes `--skip-existing` to
twine, so forgetting to bump `setup.py` can't ship nothing on a green build.
30 changes: 4 additions & 26 deletions DEPLOY.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,7 @@
# Deployment Instructions
Create a .pypirc file in your home directory like so:
```
▶ cat ~/.pypirc
[distutils]
index-servers =
pypi

[pypi]
repository: https://upload.pypi.org/legacy/
username: REPLACE_ME
password: ***
```
Releasing has moved to a tag-triggered flow. See [RELEASING.md](RELEASING.md).

Install twine:
```
pip3 install twine
```

Build the dist packages:
```
rm -rf dist
python3 setup.py sdist bdist_wheel
```

Deploy:
```
twine upload dist/*
```
The manual `~/.pypirc` + `twine upload dist/*` procedure that used to live here
is no longer the release path — publishing is done by
`.github/workflows/pypi.yml` when a bare `X.Y.Z` version tag is pushed.
Loading
Loading