Skip to content

ci: add PyPI release workflow - #10

Merged
eddietejeda merged 1 commit into
mainfrom
ci/pypi-release
Sep 8, 2026
Merged

ci: add PyPI release workflow#10
eddietejeda merged 1 commit into
mainfrom
ci/pypi-release

Conversation

@eddietejeda

Copy link
Copy Markdown
Contributor

Summary

Adds .github/workflows/release.yml, triggered by pushing a v* tag:

  1. build — asserts the tag matches pyproject.toml's version (guards against tagging without bumping), runs uv build, and re-runs the same wheel-contents assertion as CI before anything ships
  2. publish — publishes dist/ to PyPI via trusted publishing (OIDC through pypa/gh-action-pypi-publish), gated on the pypi GitHub environment — no API token secret needed

One-time setup required before the first release

  1. On PyPI, add a pending publisher for the dbt-hotdata project name (pypi.org → Publishing → "Add a new pending publisher"): owner hotdata-dev, repository dbt-hotdata, workflow release.yml, environment pypi. This claims the name on first publish.
  2. In this repo's settings, create the pypi environment (optionally restricted to protected tags and with required reviewers for an approval gate).

Then releasing is: bump version in pyproject.toml, merge, tag vX.Y.Z, push the tag.

Once the first release lands, hotdata-dev/data-engineering can swap its git pin for a plain dbt-hotdata>=X.Y.Z PyPI dependency.

Tag-triggered (v*): asserts the tag matches the pyproject version,
builds with uv, re-runs the wheel-contents guard, then publishes to
PyPI via trusted publishing (OIDC, pypi environment).
Comment on lines +15 to +22
- name: Assert tag matches pyproject version
run: |
TAG="${GITHUB_REF_NAME#v}"
VERSION="$(python3 -c 'import tomllib; print(tomllib.load(open("pyproject.toml","rb"))["project"]["version"])')"
if [ "$TAG" != "$VERSION" ]; then
echo "Tag v$TAG does not match pyproject version $VERSION" >&2
exit 1
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nit: extend the assertion to cover dbt/adapters/hotdata/__version__.py (not blocking).

The repo declares the version in two places. dbt/adapters/hotdata/__version__.py:1 also holds 0.2.0. This step compares the tag against pyproject.toml only.

A maintainer can bump pyproject.toml, tag, and pass this guard while __version__.py stays at the old value. The wheel then ships metadata version 0.3.0 next to a module reporting 0.2.0. dbt-core imports dbt.adapters.<name>.__version__ to print installed adapter versions, so dbt --version shows the stale number to users.

That module is not optional. Both workflows list it as a required wheel file, so the file reaches every install.

Suggested addition after the existing comparison:

MODVER="$(python3 -c 'ns={}; exec(open("dbt/adapters/hotdata/__version__.py").read(), ns); print(ns["version"])')"
if [ "$MODVER" != "$VERSION" ]; then
  echo "__version__.py $MODVER does not match pyproject version $VERSION" >&2
  exit 1
fi

Comment on lines +36 to +55
required = [
"dbt/adapters/hotdata/__init__.py",
"dbt/adapters/hotdata/__version__.py",
"dbt/adapters/hotdata/client.py",
"dbt/adapters/hotdata/column.py",
"dbt/adapters/hotdata/connections.py",
"dbt/adapters/hotdata/credentials.py",
"dbt/adapters/hotdata/impl.py",
"dbt/adapters/hotdata/relation.py",
"dbt/adapters/hotdata/seeds.py",
"dbt/include/hotdata/__init__.py",
"dbt/include/hotdata/dbt_project.yml",
"dbt/include/hotdata/profile_template.yml",
"dbt/include/hotdata/macros/adapters.sql",
"dbt/include/hotdata/macros/materializations/table.sql",
"dbt/include/hotdata/macros/materializations/incremental.sql",
"dbt/include/hotdata/macros/materializations/seed.sql",
"dbt/include/hotdata/macros/materializations/view.sql",
"dbt/include/hotdata/macros/materializations/snapshot.sql",
]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nit: extract this list into a script shared with ci.yml (not blocking).

.github/workflows/ci.yml:48-67 holds an identical 18-entry list. Two copies drift apart over time.

Consider a future macro file added to the CI list but missed here. The release guard silently stops covering that file. The release guard is the more important of the two, because it inspects the exact artifact that ships to PyPI.

Move the script to scripts/assert_wheel_contents.py. Then both workflows run python3 scripts/assert_wheel_contents.py and share one list.

@eddietejeda
eddietejeda merged commit fa023e5 into main Sep 8, 2026
4 checks passed
@eddietejeda
eddietejeda deleted the ci/pypi-release branch September 8, 2026 20:55
@eddietejeda eddietejeda mentioned this pull request Sep 9, 2026
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