Skip to content

Commit 1dc266f

Browse files
shixishclaude
andcommitted
Add release tooling for PyPI / TestPyPI publishing
- Makefile with build/bump/release/verify targets using uv and macOS Keychain for token storage - pyproject.toml: keywords, classifiers, Documentation URL, sdist include filter - .gitignore: uv.lock, dist/, build/, *.egg-info/ Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent c64b7cf commit 1dc266f

3 files changed

Lines changed: 133 additions & 0 deletions

File tree

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
__pycache__
44
.pytest_cache
55
.env
6+
uv.lock
7+
dist/
8+
build/
9+
*.egg-info/
610

711
# Claude
812
.claude/settings.local.json

Makefile

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
.PHONY: test test-live build clean bump-patch bump-minor bump-major set-token-pypi set-token-testpypi release-test release verify-release-test verify-release
2+
3+
test:
4+
uv run --extra dev pytest
5+
6+
test-live:
7+
uv run --extra dev pytest -m live
8+
9+
clean:
10+
rm -rf dist build *.egg-info
11+
12+
build: clean
13+
uv build
14+
15+
# Version bumps: edits pyproject.toml in place and prints old => new.
16+
bump-patch:
17+
uv version --bump patch
18+
19+
bump-minor:
20+
uv version --bump minor
21+
22+
bump-major:
23+
uv version --bump major
24+
25+
# Store a PyPI / TestPyPI token in macOS Keychain. Prompts with hidden input
26+
# (bash `read -rsp`), so the token never appears on screen, in shell history,
27+
# or in `make` output. Re-running overwrites the existing entry.
28+
set-token-pypi:
29+
@bash -c 'read -rsp "Paste PyPI token: " TOKEN && echo && \
30+
security delete-generic-password -s pypi-token-pypi >/dev/null 2>&1; \
31+
security add-generic-password -a "$$USER" -s pypi-token-pypi -w "$$TOKEN" && \
32+
echo "stored in Keychain under service: pypi-token-pypi"'
33+
34+
set-token-testpypi:
35+
@bash -c 'read -rsp "Paste TestPyPI token: " TOKEN && echo && \
36+
security delete-generic-password -s pypi-token-testpypi >/dev/null 2>&1; \
37+
security add-generic-password -a "$$USER" -s pypi-token-testpypi -w "$$TOKEN" && \
38+
echo "stored in Keychain under service: pypi-token-testpypi"'
39+
40+
# Publish to TestPyPI. Token comes from macOS Keychain (service: pypi-token-testpypi).
41+
# The `@` on the recipe lines hides the actual command so the token never appears in output.
42+
release-test: build
43+
@VERSION=$$(grep '^version' pyproject.toml | head -1 | cut -d'"' -f2) && \
44+
STATUS=$$(curl -s -o /dev/null -w "%{http_code}" "https://test.pypi.org/pypi/diffbot-python/$$VERSION/json") && \
45+
if [ "$$STATUS" = "200" ]; then \
46+
echo "ERROR: diffbot-python $$VERSION is already on TestPyPI. Bump the version in pyproject.toml."; \
47+
exit 1; \
48+
fi
49+
@TOKEN=$$(security find-generic-password -s pypi-token-testpypi -w 2>/dev/null) && \
50+
if [ -z "$$TOKEN" ]; then \
51+
echo "ERROR: no Keychain entry for pypi-token-testpypi. Run 'make set-token-testpypi' first."; \
52+
exit 1; \
53+
fi && \
54+
UV_PUBLISH_TOKEN="$$TOKEN" uv publish --publish-url https://test.pypi.org/legacy/
55+
56+
# Publish to real PyPI. Confirmation gate before upload (PyPI does not allow re-uploads).
57+
release: build
58+
@VERSION=$$(grep '^version' pyproject.toml | head -1 | cut -d'"' -f2) && \
59+
STATUS=$$(curl -s -o /dev/null -w "%{http_code}" "https://pypi.org/pypi/diffbot-python/$$VERSION/json") && \
60+
if [ "$$STATUS" = "200" ]; then \
61+
echo "ERROR: diffbot-python $$VERSION is already on PyPI. Bump the version in pyproject.toml."; \
62+
exit 1; \
63+
fi && \
64+
echo "About to publish diffbot-python $$VERSION to PyPI. This cannot be undone." && \
65+
read -p "Type the version to confirm: " CONFIRM && \
66+
[ "$$CONFIRM" = "$$VERSION" ] || { echo "Aborted."; exit 1; }
67+
@TOKEN=$$(security find-generic-password -s pypi-token-pypi -w 2>/dev/null) && \
68+
if [ -z "$$TOKEN" ]; then \
69+
echo "ERROR: no Keychain entry for pypi-token-pypi. Run 'make set-token-pypi' first."; \
70+
exit 1; \
71+
fi && \
72+
UV_PUBLISH_TOKEN="$$TOKEN" uv publish
73+
74+
# Smoke-test installs from each index in a throwaway venv.
75+
# `cd $$TMP` before running python so CWD doesn't shadow the venv install with this repo's source.
76+
# Deps live on prod PyPI, so TestPyPI install needs --extra-index-url.
77+
verify-release-test:
78+
@VERSION=$$(grep '^version' pyproject.toml | head -1 | cut -d'"' -f2) && \
79+
TMP=$$(mktemp -d) && \
80+
uv venv --python 3.12 $$TMP/.venv >/dev/null 2>&1 && \
81+
uv pip install --quiet --python $$TMP/.venv/bin/python \
82+
--index-url https://test.pypi.org/simple/ \
83+
--extra-index-url https://pypi.org/simple/ \
84+
"diffbot-python==$$VERSION" && \
85+
(cd $$TMP && $$TMP/.venv/bin/python -c "import diffbot; print('TestPyPI install OK:', diffbot.__version__)") && \
86+
rm -rf $$TMP
87+
88+
verify-release:
89+
@VERSION=$$(grep '^version' pyproject.toml | head -1 | cut -d'"' -f2) && \
90+
TMP=$$(mktemp -d) && \
91+
uv venv --python 3.12 $$TMP/.venv >/dev/null 2>&1 && \
92+
uv pip install --quiet --python $$TMP/.venv/bin/python "diffbot-python==$$VERSION" && \
93+
(cd $$TMP && $$TMP/.venv/bin/python -c "import diffbot; print('PyPI install OK:', diffbot.__version__)") && \
94+
rm -rf $$TMP

pyproject.toml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,31 @@ authors = [
1313
{ name = "Jerome Choo", email = "jerome@diffbot.com" },
1414
{ name = "Mike Tung", email = "miket@diffbot.com" }
1515
]
16+
keywords = [
17+
"diffbot",
18+
"knowledge-graph",
19+
"web-scraping",
20+
"extract",
21+
"crawler",
22+
"nlp",
23+
"llm",
24+
"api-client",
25+
]
26+
classifiers = [
27+
"Development Status :: 3 - Alpha",
28+
"Intended Audience :: Developers",
29+
"Operating System :: OS Independent",
30+
"Programming Language :: Python :: 3",
31+
"Programming Language :: Python :: 3 :: Only",
32+
"Programming Language :: Python :: 3.10",
33+
"Programming Language :: Python :: 3.11",
34+
"Programming Language :: Python :: 3.12",
35+
"Programming Language :: Python :: 3.13",
36+
"Topic :: Internet :: WWW/HTTP",
37+
"Topic :: Software Development :: Libraries :: Python Modules",
38+
"Topic :: Text Processing :: Markup :: HTML",
39+
"Typing :: Typed",
40+
]
1641
dependencies = [
1742
"httpx>=0.27.0",
1843
"click>=8.1.0",
@@ -26,6 +51,7 @@ dev = [
2651

2752
[project.urls]
2853
Homepage = "https://github.com/diffbot/diffbot-python"
54+
Documentation = "https://github.com/diffbot/diffbot-python#readme"
2955
Repository = "https://github.com/diffbot/diffbot-python"
3056
Issues = "https://github.com/diffbot/diffbot-python/issues"
3157

@@ -35,6 +61,15 @@ db = "diffbot.cli:main"
3561
[tool.hatch.build.targets.wheel]
3662
packages = ["src/diffbot"]
3763

64+
[tool.hatch.build.targets.sdist]
65+
include = [
66+
"/src",
67+
"/tests",
68+
"/README.md",
69+
"/LICENSE",
70+
"/pyproject.toml",
71+
]
72+
3873
[tool.pytest.ini_options]
3974
markers = ["live: marks tests as live integration tests requiring a real DIFFBOT_TOKEN"]
4075
addopts = "-m 'not live'"

0 commit comments

Comments
 (0)