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
23 changes: 23 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,29 @@ jobs:
run: |
mypy serializable

sdist:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Build sdist and wheel
run: |
python -m pip install --upgrade pip build
python -m build

- name: Run the tests shipped in the sdist
run: |
pip install dist/*.whl pytest
mkdir sdist
tar -xzf dist/*.tar.gz -C sdist --strip-components=1
cd sdist && python -m pytest tests -q -p no:cacheprovider

test:
runs-on: ubuntu-latest
strategy:
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Do not tell the user you are "done" or that changes are "complete" until all thr
- `./format.sh` — Formats code with ruff (run this first)
- `./lint.sh` — Checks linting and formatting (must pass). **Always use this for linting if it exists.**
- `./test.sh` — Runs pytest with coverage (must pass)
- `./deploy.sh` — Builds the distribution and uploads to PyPI via `twine` (gates on `lint.sh` and `test.sh`). **Always use this for deploying if it exists.**
- `./deploy.sh` — From a clean `master` that matches `origin`: gates on `lint.sh` and `test.sh`, builds, uploads to PyPI via `twine`, then tags `v<version>` and pushes the tag. `./deploy.sh --dry-run` stops before the upload. **Always use this for deploying if it exists.**
- `./develop.sh` — Installs package in development mode into `.venv`

## Code Style
Expand Down
4 changes: 4 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# setuptools only adds tests/test*.py to the sdist by default; ship the whole
# suite (tests/__init__.py, tests/common.py) so tests run from the tarball.
graft tests
global-exclude *.py[cod]
76 changes: 68 additions & 8 deletions deploy.sh
Original file line number Diff line number Diff line change
@@ -1,15 +1,75 @@
#!/usr/bin/env bash
# Publish the merged version to PyPI and tag it. Adapted from varcode's deploy.sh.
# Override the interpreter with DEPLOY_PYTHON=/path/to/python ./deploy.sh
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$ROOT"
usage() {
echo "Usage: ./deploy.sh [--dry-run] [version]"
echo "Publish the merged version; an optional version must match serializable/version.py."
}

fail() { echo "deploy.sh: $*" >&2; exit 1; }

dry_run=0
expected_version=""
while [[ $# -gt 0 ]]; do
case "$1" in
--dry-run) dry_run=1 ;;
-h|--help) usage; exit 0 ;;
-*) fail "Unknown option: $1" ;;
*)
[[ -z "$expected_version" ]] || fail "Expected at most one version."
expected_version="${1#v}"
[[ -n "$expected_version" ]] || fail "Expected a release version."
;;
esac
shift
done

cd "$(dirname "$0")"
PYTHON="${DEPLOY_PYTHON:-python3}"
branch="$(git symbolic-ref --quiet --short HEAD)" || fail "Deploy from master, not a detached HEAD."
[[ "$branch" == master ]] || fail "Deploys require master (current: $branch)."
commit="$(git rev-parse HEAD)"
version="$("$PYTHON" -c 'import runpy; print(runpy.run_path("serializable/version.py")["__version__"])')"
[[ "$version" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]] || fail "Invalid release version: $version"
[[ -z "$expected_version" || "$expected_version" == "$version" ]] || fail "Requested $expected_version, but the merged version is $version. Bump versions in a PR."
tag="v$version"

# Check live origin refs, not a possibly stale remote-tracking branch.
check_release() {
local status remote_branch remote_tag
status="$(git status --porcelain --untracked-files=all)"
[[ -z "$status" ]] || fail "Working tree is not clean."
[[ "$(git symbolic-ref --quiet --short HEAD)" == "$branch" && "$(git rev-parse HEAD)" == "$commit" ]] || fail "Checkout changed during release."
remote_branch="$(git ls-remote --exit-code origin "refs/heads/$branch")" || fail "Cannot read origin/$branch."
[[ "${remote_branch%%$'\t'*}" == "$commit" ]] || fail "HEAD must match live origin/$branch; pull the merged release first."
if git show-ref --verify --quiet "refs/tags/$tag"; then
fail "Tag $tag already exists locally. Verify the publication state before retrying."
fi
remote_tag="$(git ls-remote origin "refs/tags/$tag")" || fail "Cannot check release tags on origin."
[[ -z "$remote_tag" ]] || fail "Tag $tag already exists on origin."
}

check_release
./lint.sh
./test.sh

PYTHON_BIN="${DEPLOY_PYTHON:-python3}"
"$PYTHON_BIN" -m pip install --upgrade build twine
rm -rf dist build ./*.egg-info
"$PYTHON_BIN" -m build
"$PYTHON_BIN" -m twine check dist/*
"$PYTHON_BIN" -m twine upload dist/*
# Keep each attempt's original bytes, including after a failed/partial upload.
mkdir -p dist
artifact_dir="$(mktemp -d "dist/$tag.XXXXXX")"
echo "Building $tag at $commit; artifacts: $artifact_dir"
"$PYTHON" -m build --outdir "$artifact_dir"
distributions=("$artifact_dir/serializable-$version-py3-none-any.whl" "$artifact_dir/serializable-$version.tar.gz")
"$PYTHON" -m twine check --strict "${distributions[@]}"
check_release

if [[ "$dry_run" -eq 1 ]]; then
echo "Dry run passed for $tag; no upload, tag, or push. Artifacts: $artifact_dir"
exit 0
fi

"$PYTHON" -m twine upload --non-interactive --disable-progress-bar "${distributions[@]}"
git tag "$tag" "$commit"
git push origin "refs/tags/$tag"
echo "Deployed serializable $version: https://pypi.org/project/serializable/$version/"
3 changes: 1 addition & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[build-system]
requires = ["setuptools>=61.0", "wheel"]
requires = ["setuptools>=64", "wheel"]
build-backend = "setuptools.build_meta"

[project]
Expand Down Expand Up @@ -37,7 +37,6 @@ dev = [
"pytest-cov",
"ruff",
"mypy",
"coveralls",
"build",
"twine",
]
Expand Down
2 changes: 1 addition & 1 deletion serializable/version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "1.3.0"
__version__ = "1.3.1"


def print_version():
Expand Down
6 changes: 0 additions & 6 deletions setup.py

This file was deleted.

Loading