Skip to content
Merged
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
69 changes: 69 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: Release

# Tag-triggered publish to PyPI via Trusted Publishing (OIDC, no stored tokens).
# Same shape as the crucible workflow, deliberately: one publish mechanism across the
# fleet means one thing to get right and one thing to audit.
#
# Push a tag that matches the pyproject version, e.g.:
# git tag v0.3.0 && git push origin v0.3.0
# A manual run (workflow_dispatch) builds and twine-checks WITHOUT publishing, which is
# the only dry run available: there is no TestPyPI path here.
#
# ONE-TIME SETUP, on PyPI rather than in this repo. The publish step authenticates by
# OIDC, so it fails until pypi.org/manage/project/cotdata/settings/publishing/ carries a
# GitHub publisher for owner `mspinola`, repo `cotdata`, workflow `release.yml`, and
# environment `pypi`. All four must match what is written below.

on:
push:
tags: ["v*"]
workflow_dispatch:

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Guard — tag must match pyproject version
if: startsWith(github.ref, 'refs/tags/')
run: |
TAG="${GITHUB_REF_NAME#v}"
VER="$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")"
echo "tag=v$TAG pyproject=$VER"
test "$TAG" = "$VER" || { echo "::error::tag v$TAG does not match pyproject version $VER"; exit 1; }
- name: Guard — __version__ must match pyproject version
run: |
PY="$(python -c "import re; print(re.search(r'__version__ = \"([^\"]+)\"', open('src/cotdata/__init__.py').read()).group(1))")"
VER="$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")"
echo "__init__=$PY pyproject=$VER"
test "$PY" = "$VER" || { echo "::error::__version__ $PY does not match pyproject version $VER"; exit 1; }
- name: Build sdist + wheel
run: |
python -m pip install --upgrade build twine
python -m build
twine check dist/*
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/

publish-pypi:
needs: build
# tag push -> PyPI. A manual run stops after build, as a dry run.
if: github.event_name == 'push'
runs-on: ubuntu-latest
environment: pypi
permissions:
id-token: write # required for Trusted Publishing
steps:
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- uses: pypa/gh-action-pypi-publish@release/v1
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.3.0] - 2026-08-02

**The first release actually published to PyPI since 0.1.0.** 0.2.0 was tagged earlier
today and never uploaded: the repo had no release workflow at the time, and the only
alternative was a hand-rolled `twine` upload with a stored token. That tag stands as a git
tag and nothing more. Everything in it ships here, described in the 0.2.0 section below.

A PyPI consumer moving from 0.1.0 therefore gets the whole vintage subsystem plus this
release's removal in one step, and `docs/WINDOWS_SETUP.md`'s "PyPI is well behind this
repo, install editable from a clone" note stops being true once this lands.

### Removed
- **`vintage_flow.decompose` and the `cotdata-vintage flow` subcommand.** Flow
decomposition was duplicated in `crowdmon.futures.flow`, and measurement showed the two
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "cotdata"
version = "0.2.0"
version = "0.3.0"
description = "Canonical data layer for COT/futures strategies — a producer/consumer split over a file-based store."
readme = "README.md"
authors = [
Expand Down
2 changes: 1 addition & 1 deletion src/cotdata/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from .registry import REGISTRY, Symbol, all_symbols, symbol
from .store import load_manifest, require_schema, schema_version

__version__ = "0.2.0"
__version__ = "0.3.0"
__all__ = [
"get_prices", "roll_dates", "get_cot",
"symbol", "all_symbols", "REGISTRY", "Symbol",
Expand Down
Loading