Skip to content

Commit 3da649c

Browse files
committed
Document streamlined release flow and add registry script
1 parent dc751a2 commit 3da649c

File tree

3 files changed

+269
-31
lines changed

3 files changed

+269
-31
lines changed

CONTRIBUTING.md

Lines changed: 36 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,59 @@
11
# Contributing
22

3-
## HOWTO Release
3+
## Release Checklist
44

5-
This is a Howto guide on the commands to run and files to update in order to publish a new release of the Python SDK to Pypi
5+
Use this checklist whenever you cut a new version of the Python SDK.
66

7-
### Prerequisite
7+
### Prerequisites
88

9-
Poetry will handle things for us. You need to configure poetry with your pypi token for the publishing process to work.
9+
- Docker installed (our helper scripts build and run inside the project Docker image).
10+
- Writable Transloadit GitHub repository access.
11+
- A PyPI API token with upload rights to `pytransloadit` (`PYPI_TOKEN`). Store it in your shell or `.env`.
12+
- Optionally, a TestPyPI token (`PYPI_TEST_TOKEN`) if you want to dry‑run the release before pushing to the real registry.
1013

11-
Enable testing publishing on pypi test index.
14+
### 1. Prepare the Release Commit
1215

13-
```bash
14-
poetry config repositories.test-pypi https://test.pypi.org/legacy/
15-
poetry config pypi-token.test-pypi pypi-XXXXX
16-
```
16+
1. Update the version in all synced files:
17+
- `pyproject.toml`
18+
- `transloadit/__init__.py`
19+
- `tests/test_request.py` (the `Transloadit-Client` header)
20+
2. Add a matching entry to `CHANGELOG.md`.
21+
3. Run the test matrix (add `PYTHON_SDK_E2E=1` if you want to exercise the live upload):
22+
```bash
23+
./scripts/test-in-docker.sh --python 3.12
24+
```
25+
4. Commit the changes with a message such as `Prepare 1.0.3 release`.
1726

18-
To setup your token to publish to pypi.
27+
### 2. Tag the Release
28+
29+
After landing the release commit on `main` (or the branch you will tag), create and push an annotated tag:
1930

2031
```bash
21-
poetry config pypi-token.pypi pypi-XXXXX`````
32+
git tag -a v1.0.3 -m "v1.0.3"
33+
git push origin main --tags
2234
```
2335

24-
### Release Steps
36+
### 3. Publish to PyPI
2537

26-
1. Update the changelog, the version file, and the test file as done in [this commit](https://github.com/transloadit/python-sdk/commit/35789c535bd02086ff8f3a07eda9583d6e676d4d) and push it to main.
27-
2. Update the version
28-
```bash
29-
# e.g: 0.2.2 -> 0.2.3a0
30-
poetry version prerelease
31-
# or the following for, e.g.: 0.2.3
32-
poetry version patch
33-
```
34-
3. Publish to Pypi
38+
The `scripts/notify-registry.sh` helper publishes from inside our Docker image and performs the usual safety checks (clean git tree, version consistency, changelog entry). It looks for tokens in the environment or `.env`.
3539

36-
Pypi test index
40+
Publish to the real registry:
3741

3842
```bash
39-
poetry build
40-
poetry publish -r test-pypi
43+
PYPI_TOKEN=... scripts/notify-registry.sh
4144
```
4245

43-
To publish to pypi
46+
Run a dry‑run against TestPyPI first (optional):
47+
4448
```bash
45-
poetry publish
49+
PYPI_TEST_TOKEN=... scripts/notify-registry.sh --repository test-pypi --dry-run
50+
# When satisfied:
51+
PYPI_TEST_TOKEN=... scripts/notify-registry.sh --repository test-pypi
4652
```
4753

48-
4. Now that release has been published on Pypi, please head to GitHub to [draft a new tag release](https://github.com/transloadit/python-sdk/releases). Point this tag release to the latest commit pushed on step 1 above. Once you're done drafting the release, go ahead to publish it.
49-
50-
If all the steps above have been followed without errors, then you've successfully published a release. 🎉
54+
### 4. Announce the Release
5155

52-
---
56+
1. Draft a GitHub release for the new tag and paste the changelog entry.
57+
2. Confirm that the [Read the Docs build](https://transloadit.readthedocs.io/en/latest/) completes (it is triggered when you publish the GitHub release).
5358

54-
Further reading for Transloadians: https://github.com/transloadit/team-internals/blob/HEAD/_howtos/2020-12-14-maintain-python-sdk.md
59+
That’s it—PyPI and the documentation are now up to date. For additional background see the internal guide: <https://github.com/transloadit/team-internals/blob/HEAD/_howtos/2020-12-14-maintain-python-sdk.md>.

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,7 @@ poetry run pytest --cov=transloadit --cov-report=html tests
9797
```
9898

9999
Then view the coverage report locally by opening `htmlcov/index.html` in your browser.
100+
101+
## Contributing
102+
103+
See [CONTRIBUTING.md](CONTRIBUTING.md) for local development, testing, and release instructions.

scripts/notify-registry.sh

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
IMAGE_NAME=${IMAGE_NAME:-transloadit-python-sdk-dev}
5+
CACHE_ROOT=${CACHE_ROOT:-.docker-cache}
6+
POETRY_CACHE_DIR="$CACHE_ROOT/pypoetry"
7+
PIP_CACHE_DIR="$CACHE_ROOT/pip"
8+
HOME_DIR="$CACHE_ROOT/home"
9+
10+
usage() {
11+
cat <<'EOF'
12+
Usage: scripts/notify-registry.sh [options]
13+
14+
Options:
15+
--repository <pypi|test-pypi> Publish to the chosen repository (default: pypi)
16+
--dry-run Build the package but skip publishing
17+
-h, --help Show this help text
18+
19+
Environment:
20+
PYPI_TOKEN API token with upload rights for pypi.org (required for --repository pypi)
21+
PYPI_TEST_TOKEN API token with upload rights for test.pypi.org (required for --repository test-pypi)
22+
These variables can optionally be defined in .env
23+
EOF
24+
}
25+
26+
err() {
27+
echo "notify-registry: $*" >&2
28+
}
29+
30+
ensure_docker() {
31+
if ! command -v docker >/dev/null 2>&1; then
32+
err "Docker is required to run this script."
33+
exit 1
34+
fi
35+
36+
if ! docker info >/dev/null 2>&1; then
37+
if [[ -z "${DOCKER_HOST:-}" && -S "$HOME/.colima/default/docker.sock" ]]; then
38+
export DOCKER_HOST="unix://$HOME/.colima/default/docker.sock"
39+
fi
40+
fi
41+
42+
if ! docker info >/dev/null 2>&1; then
43+
err "Docker daemon is not reachable. Start Docker (or Colima) and retry."
44+
exit 1
45+
fi
46+
}
47+
48+
configure_platform() {
49+
if [[ -z "${DOCKER_PLATFORM:-}" ]]; then
50+
local arch
51+
arch=$(uname -m)
52+
if [[ "$arch" == "arm64" || "$arch" == "aarch64" ]]; then
53+
DOCKER_PLATFORM=linux/amd64
54+
fi
55+
fi
56+
}
57+
58+
run_outside_container() {
59+
ensure_docker
60+
configure_platform
61+
62+
mkdir -p "$CACHE_ROOT" "$POETRY_CACHE_DIR" "$PIP_CACHE_DIR" "$HOME_DIR"
63+
64+
local build_args=()
65+
if [[ -n "${DOCKER_PLATFORM:-}" ]]; then
66+
build_args+=(--platform "$DOCKER_PLATFORM")
67+
fi
68+
build_args+=(-t "$IMAGE_NAME" -f Dockerfile .)
69+
70+
docker build "${build_args[@]}"
71+
72+
local docker_args=(
73+
--rm
74+
--user "$(id -u):$(id -g)"
75+
-e HOME=/workspace/$HOME_DIR
76+
-e POETRY_CACHE_DIR=/workspace/$POETRY_CACHE_DIR
77+
-e PIP_CACHE_DIR=/workspace/$PIP_CACHE_DIR
78+
-v "$PWD":/workspace
79+
-v "$PWD/$POETRY_CACHE_DIR":/workspace/"$POETRY_CACHE_DIR"
80+
-v "$PWD/$PIP_CACHE_DIR":/workspace/"$PIP_CACHE_DIR"
81+
-v "$PWD/$HOME_DIR":/workspace/"$HOME_DIR"
82+
-w /workspace
83+
)
84+
85+
if [[ -n "${DOCKER_PLATFORM:-}" ]]; then
86+
docker_args+=(--platform "$DOCKER_PLATFORM")
87+
fi
88+
89+
if [[ -f .env ]]; then
90+
docker_args+=(--env-file "$PWD/.env")
91+
fi
92+
93+
if [[ -n "${PYPI_TOKEN:-}" ]]; then
94+
docker_args+=(-e "PYPI_TOKEN=${PYPI_TOKEN}")
95+
fi
96+
if [[ -n "${PYPI_TEST_TOKEN:-}" ]]; then
97+
docker_args+=(-e "PYPI_TEST_TOKEN=${PYPI_TEST_TOKEN}")
98+
fi
99+
100+
exec docker run "${docker_args[@]}" "$IMAGE_NAME" bash -lc "set -euo pipefail; scripts/notify-registry.sh --inside-container \"$@\""
101+
}
102+
103+
load_env_var() {
104+
local var_name=$1
105+
if [[ -n "${!var_name:-}" ]]; then
106+
return 0
107+
fi
108+
109+
if [[ -f .env ]]; then
110+
# shellcheck disable=SC1091
111+
source .env || err "Failed to source .env"
112+
fi
113+
}
114+
115+
verify_repo_state() {
116+
if [[ -n "$(git status --porcelain)" ]]; then
117+
err "Git working tree is not clean. Commit or stash changes before publishing."
118+
exit 1
119+
fi
120+
}
121+
122+
verify_versions_consistent() {
123+
local version python_version header_version
124+
version=$(poetry version -s)
125+
python_version=$(python -c "import transloadit; print(transloadit.__version__)")
126+
header_version=$(grep -oE 'python-sdk:[0-9]+\.[0-9]+\.[0-9]+' tests/test_request.py | tail -n1 | cut -d: -f2)
127+
128+
if [[ "$version" != "$python_version" ]]; then
129+
err "Version mismatch: pyproject.toml=$version but transloadit/__init__.py=$python_version"
130+
exit 1
131+
fi
132+
if [[ "$version" != "$header_version" ]]; then
133+
err "Version mismatch: tests/test_request.py expects $header_version but pyproject.toml has $version"
134+
exit 1
135+
fi
136+
if ! grep -q "### ${version}/" CHANGELOG.md; then
137+
err "CHANGELOG.md does not contain an entry for ${version}"
138+
exit 1
139+
fi
140+
}
141+
142+
publish_inside_container() {
143+
local repository="pypi"
144+
local dry_run=0
145+
146+
while [[ $# -gt 0 ]]; do
147+
case "$1" in
148+
--repository)
149+
if [[ $# -lt 2 ]]; then
150+
err "Missing value for --repository"
151+
exit 1
152+
fi
153+
repository=$2
154+
shift 2
155+
;;
156+
--dry-run)
157+
dry_run=1
158+
shift
159+
;;
160+
--inside-container)
161+
shift
162+
;;
163+
-h|--help)
164+
usage
165+
exit 0
166+
;;
167+
*)
168+
err "Unknown option: $1"
169+
usage
170+
exit 1
171+
;;
172+
esac
173+
done
174+
175+
case "$repository" in
176+
pypi|test-pypi) ;;
177+
*)
178+
err "Invalid repository '${repository}'. Expected 'pypi' or 'test-pypi'."
179+
exit 1
180+
;;
181+
esac
182+
183+
if [[ "$repository" == "pypi" ]]; then
184+
load_env_var "PYPI_TOKEN"
185+
if [[ -z "${PYPI_TOKEN:-}" ]]; then
186+
err "PYPI_TOKEN is not set. Export it or add it to .env before publishing."
187+
exit 1
188+
fi
189+
export POETRY_PYPI_TOKEN_PYPI="$PYPI_TOKEN"
190+
else
191+
load_env_var "PYPI_TEST_TOKEN"
192+
if [[ -z "${PYPI_TEST_TOKEN:-}" ]]; then
193+
err "PYPI_TEST_TOKEN is not set. Export it or add it to .env before publishing to test-pypi."
194+
exit 1
195+
fi
196+
export POETRY_PYPI_TOKEN_TEST_PYPI="$PYPI_TEST_TOKEN"
197+
poetry config repositories.test-pypi https://test.pypi.org/legacy/ >/dev/null
198+
fi
199+
200+
verify_repo_state
201+
verify_versions_consistent
202+
203+
rm -rf dist
204+
poetry build
205+
206+
if [[ "$dry_run" == "1" ]]; then
207+
err "Dry run complete. Built artifacts in dist/ but skipped publishing."
208+
exit 0
209+
fi
210+
211+
if [[ "$repository" == "pypi" ]]; then
212+
poetry publish --no-interaction --no-ansi
213+
err "Published package to pypi.org."
214+
else
215+
poetry publish --no-interaction --no-ansi -r test-pypi
216+
err "Published package to test.pypi.org."
217+
fi
218+
}
219+
220+
main() {
221+
if [[ "${1:-}" != "--inside-container" ]]; then
222+
run_outside_container "$@"
223+
return
224+
fi
225+
226+
publish_inside_container "$@"
227+
}
228+
229+
main "$@"

0 commit comments

Comments
 (0)