This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
python -m venv venv
venv\Scripts\activate # Windows
source venv/bin/activate # Unix/Mac
pip install -e .
pip install -r etsy_python/requirements.txtpython setup.py build
python setup.py sdist bdist_wheelpython scripts/check_version_consistency.py # Validate versions match
python scripts/bump_version.py --dry-run --type patch # Preview bump
python scripts/bump_version.py --type [patch|minor|major] # Execute bump
python scripts/release.py patch --no-push # Local release testpip install -r requirements-dev.txt # Install pytest + pytest-cov
pytest # Run all tests
pytest -v # Verbose output
pytest --cov=etsy_python --cov-report=term-missing # Coverage report
pytest tests/test_session.py # Run specific test file
python scripts/check_version_consistency.py # Validate versions match# Check for Etsy API changes
python scripts/fetch_spec.py # Fetch latest OAS spec
python scripts/diff_spec.py # Diff against baseline
# Audit SDK coverage
python scripts/audit_sdk.py # Compare spec vs SDK code
# After applying changes, update baseline
cp specs/latest.json specs/baseline.json
# Claude Code skills (interactive)
# /maintain-check - Fetch + diff spec
# /maintain-release-check - Check Etsy GitHub release notes
# /maintain-audit - Full pipeline: fetch spec + releases, audit, review, implement
# GitHub Actions (see .github/workflows/)
# pr-tests.yml - Tests + coverage on every PR push (posts/updates comment)
# maintenance-check.yml - Weekly API change detection + SDK audit
# pr-coverage.yml - SDK coverage report on PRs (add 'sdk-check' label)Single source of truth: etsy_python/_version.py
setup.py reads version dynamically from _version.py, which remains the single source of truth -- always trust _version.py. The .bumpversion.cfg file tracks version for the bump2version tool (which CI does not run); scripts/bump_version.py now keeps its current_version synced automatically on every bump, and the publish workflow commits both files together, so the two should no longer drift.
A pre-commit hook (.pre-commit-config.yaml) runs scripts/check_version_consistency.py to validate _version.py and .bumpversion.cfg agree.
Commit messages trigger automatic version bumps on push to master:
breaking:orBREAKING CHANGE-> Majorfeat:or containsfeature-> Minor- All other commits -> Patch
Skip CI with [skip ci], [ci skip], or [no ci] in commit message. Version bump commits automatically include [skip ci] to prevent infinite loops.
Resources (API endpoints) -> Models (request validation) -> EtsyClient (HTTP + auth) -> Etsy API
All code lives under etsy_python/v3/:
| Directory | Purpose |
|---|---|
auth/ |
OAuth 2.0 PKCE flow (EtsyOAuth) |
common/ |
Utilities (Utils.py), environment config (Env.py), HTTP constants (Request.py) |
enums/ |
Type-safe API parameter constants |
exceptions/ |
BaseAPIException -> RequestException (with rate limits) |
models/ |
Request data models with validation (Request base, FileRequest for uploads) |
resources/ |
~25 API endpoint classes + Session.py (EtsyClient) + Response.py |
EtsyClient (resources/Session.py): Central HTTP client. Manages OAuth tokens with auto-refresh, session headers, rate limit parsing. All resources receive it via constructor injection.
Request base class (models/Request.py): Provides nullable/mandatory field lists. check_mandatory() validates required fields, get_dict() serializes to API format excluding empty nullable fields. Note: mandatory list keys must match the Python attribute name (e.g. "_type", not "type").
FileRequest (models/FileRequest.py): Extends Request with file (multipart) and data attributes for upload endpoints.
_type field mapping: Python's type is a builtin, so model attributes that map to the API's type field are stored as _type. todict() in Utils.py maps _type → "type" during serialization. The mandatory list in Request models must use "_type" (the attribute name), not "type".
Boolean False vs nullable: get_nulled() in Request.py treats empty strings, empty lists, and zero as null for nullable fields — but explicitly excludes bool values. False is a valid distinct value from null and serializes as False, not None. The todict() function in Utils.py has the same guard: (value != 0 or isinstance(value, bool)).
- Resource method called with parameters
- Request model validates mandatory fields via
super().__init__() EtsyClient.make_request()checks token expiry (UTC-aware), refreshes if needed- HTTP request sent; response parsed into
Responseor raisesRequestException - Rate limits extracted from response headers when present
Every resource is a @dataclass with session: EtsyClient. Methods return Union[Response, RequestException].
- GET/DELETE: Parameters passed as
**kwargs, built into query string viagenerate_get_uri() - POST/PUT/PATCH: Payload is a
Requestmodel, serialized via.get_dict()to JSON - File uploads: Payload is a
FileRequest, sent as multipart form data - Enums: Always use
.valueto extract the API string (e.g.,State.ACTIVE.value->"active")
generate_get_uri(uri, **kwargs)-- builds query string from kwargs dicttodict(obj)-- recursive serializer handling Enums, nested objects, nullable fieldsgenerate_bytes_from_file(file)-- reads file bytes for uploads
Controlled by ETSY_ENV env var (default: "PROD"). Sets base URLs for OAuth (etsy.com) and API (openapi.etsy.com).
- Classes: PascalCase. Methods: snake_case. Constants/Enum values: UPPER_SNAKE_CASE
- Always use type hints for parameters and return values
- Resource methods return
Union[Response, RequestException]-- no try/catch in resource layer - Imports: stdlib -> third-party -> internal (absolute imports:
from etsy_python.v3...) - All datetime comparisons use UTC timezone via
datetime.now(tz=timezone.utc)
- Model in
models/: Create a class extendingRequestwithnullableandmandatorylists - Enums in
enums/: Add any new type-safe constants - Resource method in
resources/: Add method to existing or new@dataclassresource class - Exports: Update
resources/__init__.pyif adding a new resource class
requests==2.32.4-- HTTP clientrequests-oauthlib>=1.3.1-- OAuth 2.0
On push to master:
- Check for skip-ci markers
- Bump version based on commit message semantics
- Build sdist + wheel
- Publish to PyPI (trusted publishing)
- Create GitHub Release
Uses VERSION_BUMP_TOKEN (fine-grained PAT with Contents:Read/Write) to push version bump commits to protected master branch. Also supports manual workflow dispatch with version type selection.
pr-tests.yml— Runs pytest with coverage on every PR push. Posts/updates a coverage comment on the PR using PATCH-first upsert to avoid race conditions. Has concurrency groups to cancel in-flight runs.pr-coverage.yml— SDK coverage audit against Etsy OAS spec. Triggered bysdk-checklabel or manual dispatch. Fetches latest spec with fallback tospecs/baseline.json.maintenance-check.yml— Weekly scheduled check for Etsy API changes. Fetches spec, diffs against baseline, runs SDK audit.
.github/CODEOWNERS requires @amitray007 review for changes to .github/workflows/ and .github/CODEOWNERS to protect CI secrets from unauthorized workflow modifications.
| Script | Purpose |
|---|---|
scripts/bump_version.py |
Semantic version bumping (supports --dry-run, --type auto) |
scripts/check_version_consistency.py |
Validates _version.py matches .bumpversion.cfg |
scripts/release.py |
Manual release orchestration (--no-push, --no-build, --no-tag, --force) |
scripts/generate_release_notes.py |
Generates changelog from git commits (used by CI) |
scripts/fetch_spec.py |
Downloads latest Etsy OAS spec to specs/latest.json |
scripts/diff_spec.py |
Diffs specs/baseline.json vs specs/latest.json, outputs specs/diff-report.md |
scripts/audit_sdk.py |
Audits SDK coverage against OAS spec, outputs specs/audit-report.md. Loads specs/audit-ignore.json to suppress reviewed findings (override with --ignore-file) |
scripts/check_releases.py |
Checks Etsy GitHub releases for new changes, outputs specs/release-notes.md |
scripts/format_pr_comment.py |
Formats audit report as a PR comment (used by CI) |
Reviewed, accepted audit findings (deliberate deprecated aliases, intentionally
partial enums, back-compat kwargs, etc.) live in specs/audit-ignore.json — never
hard-coded in audit_sdk.py. Each run re-derives findings and only suppresses an
entry while its finding still occurs; for the value-bearing types
(enum_staleness, param_drift), only the listed values are hidden, so a newly
added enum value or newly drifted parameter still surfaces. Entries matching nothing
are reported under a Stale Ignores section so the list stays honest, and
suppressed findings are listed (with reasons) under Suppressed (Verified). To
accept a finding, add an entry (type + key, plus direction/values for the
value-bearing types); to stop accepting it, delete the entry. A missing file means
"no suppressions".
Supported type values: extra_method, enum_staleness, param_drift,
code_issue. Prefer an explicit values list over "*" — a wildcard hides
everything on that key, including drift nobody has reviewed.