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
120 changes: 120 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
name: Publish

# Builds the distributions for a GitHub release and uploads them to PyPI using
# Trusted Publishing. Merging to main never publishes anything; only publishing a
# release on GitHub (which creates or points at a v* tag) does, and the upload
# step waits for approval in the protected "pypi" environment. See CONTRIBUTING.md.

on:
release:
types: [published]
workflow_dispatch: # build and verify only, without publishing

permissions:
contents: read

# Never run two publishes at once, and never cancel one that is in progress
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false

jobs:
build:
name: Build and verify distributions
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
with:
python-version: "3.14"

- name: Build distributions
run: |
python -m pip install build twine
python -m build
python -m twine check --strict dist/*
ls -l dist

- name: Install the wheel and run the tests against it
run: |
python -m pip install "$(ls dist/*.whl)[test]"
python -m pip check
# Work from outside the checkout so that "osmnet" resolves to the
# installed wheel rather than the source tree
cd "$RUNNER_TEMP"
python -c "from importlib.metadata import version; print('installed version', version('osmnet'))"
# The tests live inside the package but are left out of the wheel, so
# copy the test folder (with its saved Overpass responses) into the
# installed package and run it from there, exercising the wheel
# rather than the source tree. The live API tests are skipped
installed="$(python -c 'import os, osmnet; print(os.path.dirname(osmnet.__file__))')"
echo "installed package at $installed"
(cd "$GITHUB_WORKSPACE/osmnet" && find . -type d -name tests -print0 | tar -cf - --null -T -) \
| tar -C "$installed" -xf -
python -m pytest --pyargs osmnet -q -p no:cacheprovider

- name: Check that the package version matches the release
if: github.event_name == 'release'
env:
PRERELEASE: ${{ github.event.release.prerelease }}
run: |
# Read the version from the installed wheel's metadata, from outside the
# checkout, so the check binds the tag to the artifact being uploaded rather
# than to the source tree (or the egg-info that the build leaves behind)
cd "$RUNNER_TEMP"
expected="${GITHUB_REF_NAME#v}"
actual="$(python -c 'from importlib.metadata import version; print(version("osmnet"))')"
echo "tag $GITHUB_REF_NAME expects version $expected; installed wheel reports $actual"
if [ "$expected" != "$actual" ]; then
echo "::error::the package version does not match the release tag"
exit 1
fi
if [[ "$actual" =~ (a|b|rc|dev)[0-9]*$ ]]; then
if [ "$PRERELEASE" != "true" ]; then
echo "::error::$actual is a pre-release version, but the GitHub release is not marked as a pre-release"
exit 1
fi
elif [ "$PRERELEASE" = "true" ]; then
echo "::error::$actual is a final version, but the GitHub release is marked as a pre-release"
exit 1
fi

- name: Save distributions
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: dist
path: dist/
if-no-files-found: error

publish:
name: Publish to PyPI
needs: build
if: github.event_name == 'release'
runs-on: ubuntu-latest
timeout-minutes: 10
environment:
name: pypi
url: https://pypi.org/project/osmnet/
permissions:
id-token: write # for Trusted Publishing; no other access is needed

steps:
- name: Download distributions
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
with:
name: dist
path: dist/

- name: Upload to PyPI
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
with:
# Files upload one at a time and PyPI refuses re-uploads of a filename, so
# let a re-run after a partial failure finish the release instead of aborting
skip-existing: true
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Unreleased
* no longer uses GeoPandas' deprecated ``GeoSeries.unary_union``
* the tests replay saved Overpass API responses, so they no longer depend on the live API or on later edits to OpenStreetMap; ``OSMNET_RECORD_OVERPASS=1`` refreshes the saved responses, and the live API tests in ``test_live.py`` run with ``OSMNET_LIVE_TESTS=1``
* CI on GitHub Actions: minimum dependencies on Python 3.10, latest dependencies on Python 3.14 (Linux, macOS, Windows), code style, package build and install, and a strict documentation build; Travis and AppVeyor configurations removed
* releases are published to PyPI by a GitHub Actions workflow using Trusted Publishing, after the distributions are built and the tests pass against the installed wheel; ``CONTRIBUTING.md`` describes the release process

v0.1.7
======
Expand Down
12 changes: 8 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ OSMnet's status is Deprecated / Migration: it supports existing Pandana and Urba

- Open a pull request to the main branch, and merge it once the checks pass

- Publish the release on GitHub: create a tag on the merge commit named with a `v` prefix (e.g. `v0.2` for version `0.2`), and use the changelog text as the release notes
- Publish the release on GitHub: create a tag on the merge commit named with a `v` prefix (e.g. `v0.2` for version `0.2`), and use the changelog text as the release notes. Publishing the release starts the `Publish` workflow described below

- For anything more than a trivial release, do a dry run first with a release candidate: set the version to e.g. `0.2rc1`, tag it `v0.2rc1`, and mark the GitHub release as a pre-release. Pip ignores pre-releases unless asked for them (`pip install --pre osmnet==0.2rc1`), and the Conda Forge bots ignore them too, so this is a safe way to test the whole process. There's no need to delete the release candidate from PyPI afterward. Then repeat with the final version number

Expand All @@ -76,14 +76,18 @@ OSMnet's status is Deprecated / Migration: it supports existing Pandana and Urba

## Distributing a release on PyPI (for pip installation):

- Register an account at https://pypi.org with two-factor authentication enabled, ask one of the current maintainers to add you to the project, and create an API token scoped to the OSMnet project
- Publishing is automated by the `Publish` GitHub Actions workflow (`.github/workflows/publish.yml`), which runs when a release is published on GitHub. It builds the source distribution and wheel, checks them with `twine check --strict`, installs the wheel and runs the test suite against it, confirms that the package version matches the release tag (and that pre-release versions go with pre-release GitHub releases), and then uploads the files to PyPI using [Trusted Publishing](https://docs.pypi.org/trusted-publishers/), so no PyPI credentials are stored on GitHub

- Check out the tagged release, `pip install build twine`, delete any old files in `dist/`, and run `python -m build` and `twine check --strict dist/*`
- The upload step runs in the repository's `pypi` deployment environment, which requires approval from a maintainer: once the build job succeeds, the workflow pauses until a reviewer approves the deployment from the workflow run page. Merging to `main` never publishes anything

- Run `twine upload dist/*`, entering `__token__` as the username and the token as the password
- The workflow can also be run by hand from the Actions tab (`workflow_dispatch`) to build and test the distributions without publishing them

- Check https://pypi.org/project/osmnet/ for the new version, and try `pip install osmnet` in a fresh environment

- One-time setup, in case it needs to be repeated: a PyPI owner of the project registers the trusted publisher at https://pypi.org/manage/project/osmnet/settings/publishing/ with owner `UDST`, repository `osmnet`, workflow `publish.yml`, and environment `pypi`; and a repository admin creates the `pypi` environment at https://github.com/UDST/osmnet/settings/environments with required reviewers

- Manual fallback, if the workflow can't be used: register an account at https://pypi.org with two-factor authentication enabled, ask one of the current maintainers to add you to the project, and create an API token scoped to the OSMnet project. Then check out the tagged release, `pip install build twine`, delete any old files in `dist/`, and run `python -m build`, `twine check --strict dist/*`, and `twine upload dist/*`, entering `__token__` as the username and the token as the password


## Distributing a release on Conda Forge (for conda installation):

Expand Down
2 changes: 1 addition & 1 deletion osmnet/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "0.2.dev0"
__version__ = "0.2.dev1"

version = __version__

Expand Down
Loading