Skip to content

Commit b48a33a

Browse files
bokelleyclaude
andauthored
fix: use correct PYPY_API_TOKEN secret for PyPI publishing (#8)
* fix: actually use PYPY_API_TOKEN secret for PyPI publishing Previous revert commit didn't actually change the secret name. The correct secret name in the repository is PYPY_API_TOKEN. This should fix the 403 Forbidden error when publishing to PyPI. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: add CLAUDE.md with Python SDK development learnings Document key learnings from building the Python AdCP SDK: **Type Safety & Code Generation:** - Auto-generate Pydantic models from upstream schemas - Handle missing schema types with documented type aliases - Use TYPE_CHECKING for optional dependencies - Use cast() for JSON deserialization type safety **Testing Strategy:** - Mock at the right level (_get_client() not httpx class) - httpx response.json() is SYNCHRONOUS not async - Test the API as it exists, not as we wish it existed **CI/CD & Release:** - Verify secret names before changing them (PYPY_API_TOKEN not PYPI_API_TOKEN) - Release Please automates version bumps and PyPI publishing - Entry points in pyproject.toml enable uvx usage **Python Patterns:** - String escaping order matters (backslash first, then quotes) - Atomic file operations for config files - Connection pooling for HTTP clients - Python 3.10+ required for | union syntax 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent f9cfb95 commit b48a33a

File tree

2 files changed

+127
-1
lines changed

2 files changed

+127
-1
lines changed

.github/workflows/release-please.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,5 @@ jobs:
4343
if: ${{ steps.release.outputs.release_created }}
4444
env:
4545
TWINE_USERNAME: __token__
46-
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
46+
TWINE_PASSWORD: ${{ secrets.PYPY_API_TOKEN }}
4747
run: twine upload dist/*

CLAUDE.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# Python SDK Development Learnings
2+
3+
## Type Safety & Code Generation
4+
5+
**Auto-generate from specs when possible**
6+
- Download schemas from canonical source (e.g., adcontextprotocol.org/schemas)
7+
- Generate Pydantic models automatically - keeps types in sync with spec
8+
- Validate generated code in CI (syntax check + import test)
9+
- For missing upstream types, add type aliases with clear comments explaining why
10+
11+
**Handling Missing Schema Types**
12+
When schemas reference types that don't exist upstream:
13+
```python
14+
# MISSING SCHEMA TYPES (referenced but not provided by upstream)
15+
# These types are referenced in schemas but don't have schema files
16+
FormatId = str
17+
PackageRequest = dict[str, Any]
18+
```
19+
20+
**Type Checking Best Practices**
21+
- Use `TYPE_CHECKING` for optional dependencies to avoid runtime import errors
22+
- Use `cast()` for JSON deserialization to satisfy mypy's `no-any-return` checks
23+
- Add specific `type: ignore` comments (e.g., `# type: ignore[no-any-return]`) rather than blanket ignores
24+
- Test type checking in CI across multiple Python versions (3.10+)
25+
26+
## Testing Strategy
27+
28+
**Mock at the Right Level**
29+
- For HTTP clients: Mock `_get_client()` method, not the httpx class directly
30+
- For async operations: Use `AsyncMock` for async functions, `MagicMock` for sync methods
31+
- Remember: httpx's `response.json()` is SYNCHRONOUS, not async
32+
33+
**Test API Changes Properly**
34+
- When API changes from kwargs to typed objects, update tests to match
35+
- Remove tests for non-existent methods rather than keep failing tests
36+
- Test the API as it exists, not as we wish it existed
37+
38+
## CI/CD & Release Automation
39+
40+
**GitHub Actions Secrets**
41+
- Secret names matter! Check actual secret name in repository settings
42+
- Common pattern: `PYPY_API_TOKEN` (not `PYPI_API_TOKEN`) for PyPI publishing
43+
- Test locally with `python -m build` before relying on CI
44+
45+
**Release Please Workflow**
46+
- Runs automatically on push to main
47+
- Creates release PR with version bump and changelog
48+
- When release PR is merged, automatically publishes to PyPI
49+
- Requires proper `[project.scripts]` entry point in pyproject.toml for CLI tools
50+
51+
**Entry Points for CLI Tools**
52+
```toml
53+
[project.scripts]
54+
toolname = "package.__main__:main"
55+
```
56+
This enables `uvx toolname` and `pip install toolname` to work correctly.
57+
58+
## Python-Specific Patterns
59+
60+
**Optional Dependencies with TYPE_CHECKING**
61+
```python
62+
from typing import TYPE_CHECKING
63+
64+
if TYPE_CHECKING:
65+
from optional_lib import SomeType
66+
67+
try:
68+
from optional_lib import SomeType as _SomeType
69+
AVAILABLE = True
70+
except ImportError:
71+
AVAILABLE = False
72+
```
73+
74+
**Atomic File Operations**
75+
For config files with sensitive data:
76+
```python
77+
temp_file = CONFIG_FILE.with_suffix(".tmp")
78+
with open(temp_file, "w") as f:
79+
json.dump(config, f, indent=2)
80+
temp_file.replace(CONFIG_FILE) # Atomic rename
81+
```
82+
83+
**Connection Pooling**
84+
```python
85+
# Reuse HTTP client across requests
86+
self._client: httpx.AsyncClient | None = None
87+
88+
async def _get_client(self) -> httpx.AsyncClient:
89+
if self._client is None:
90+
limits = httpx.Limits(
91+
max_keepalive_connections=10,
92+
max_connections=20,
93+
)
94+
self._client = httpx.AsyncClient(limits=limits)
95+
return self._client
96+
```
97+
98+
## Common Pitfalls to Avoid
99+
100+
**String Escaping in Code Generation**
101+
Always escape in this order:
102+
1. Backslashes first: `\\``\\\\`
103+
2. Then quotes: `"``\"`
104+
3. Then control chars (newlines, tabs)
105+
106+
Wrong order creates invalid escape sequences!
107+
108+
**Python Version Requirements**
109+
- Union syntax `str | None` requires Python 3.10+
110+
- Always include `from __future__ import annotations` at top of files
111+
- Use `target-version = "py310"` in ruff/black config
112+
- Test in CI across all supported Python versions
113+
114+
**Test Fixtures vs. Mocks**
115+
- Don't over-mock - it hides serialization bugs
116+
- Test actual API calls when possible
117+
- Use real Pydantic validation in tests
118+
- Mock external services, not internal logic
119+
120+
## Additional Important Reminders
121+
122+
**NEVER**:
123+
- Assume a "typo" without checking the actual secret name in GitHub settings
124+
125+
**ALWAYS**:
126+
- Verify secret names match repository settings before "fixing" them

0 commit comments

Comments
 (0)