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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
60 changes: 60 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: CI

on:
push:
branches: [main]
pull_request:

permissions:
contents: read

jobs:
checks:
name: Checks (Python ${{ matrix.python }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python: ['3.11', '3.12', '3.13', '3.14']
steps:
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v10.2.0
with:
python-version: ${{ matrix.python }}
- run: uv sync --locked
- run: uv run --no-sync ruff format --check
- run: uv run --no-sync ruff check
- run: uv run --no-sync mypy
- run: uv run --no-sync pytest
env:
# The integration tests are skipped unless these secrets are set.
SHELLRENT_CLIENT_ID: ${{ secrets.SHELLRENT_CLIENT_ID }}
SHELLRENT_CLIENT_SECRET: ${{ secrets.SHELLRENT_CLIENT_SECRET }}
SHELLRENT_API_URL: ${{ vars.SHELLRENT_API_URL }}

lowest-dependencies:
name: Checks (Python 3.11, lowest dependencies)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v10.2.0
with:
python-version: '3.11'
# The lowest versions allowed by pyproject.toml for the direct dependencies (httpx, attrs, dev tools).
- run: uv sync --resolution lowest-direct
- run: uv run --no-sync ruff format --check
- run: uv run --no-sync ruff check
- run: uv run --no-sync mypy
- run: uv run --no-sync pytest

generated-code:
name: Generated code is up to date
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v10.2.0
- run: bin/generate
- name: Compare with the committed code
run: |
git add --all --intent-to-add
git diff --stat --exit-code
51 changes: 51 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Publishes to PyPI with Trusted Publishing: PyPI trusts this file by its name (release.yml)
# and the "pypi" environment, whose approval is required before the upload.
name: Release

on:
push:
tags: ['v*']

permissions:
contents: read

jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
# hatch-vcs takes the version from the git tags.
fetch-depth: 0
- uses: astral-sh/setup-uv@v10.2.0
- run: uv build
- name: Check that the version is the one of the tag
run: |
version="${GITHUB_REF_NAME#v}"
ls dist
if [[ ! -f "dist/shellrent_sdk-${version}.tar.gz" || ! -f "dist/shellrent_sdk-${version}-py3-none-any.whl" ]]; then
echo "::error::The built version is not ${version}, the one of the tag ${GITHUB_REF_NAME}"
exit 1
fi
- uses: actions/upload-artifact@v7
with:
name: dist
path: dist/
if-no-files-found: error

publish:
name: Publish to PyPI
needs: build
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/shellrent-sdk
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v8
with:
name: dist
path: dist/
- uses: pypa/gh-action-pypi-publish@release/v1
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
__pycache__/
*.py[cod]
.venv/
dist/
build/
*.egg-info/
.mypy_cache/
.pytest_cache/
.ruff_cache/
.coverage
htmlcov/

# Metadata of files copied from Windows to WSL
*:Zone.Identifier
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Changelog

All notable changes to this project are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- Functions and models for the Shellrent API 3.0.0 (OpenAPI 3.1 specification), generated with
openapi-python-client 0.29.1: nullable references typed as `Model | None`, required fields without
`Unset`, `OrderPayRequest` as the body of `pay_orders` and `Payment | None` as its data, and the 429
responses documented.
- `create_client()`, returning the generated client configured for the API.
- OAuth2 client credentials authentication (`shellrent_sdk.auth.OAuth2Auth`), with the token cached in
memory, in files (`FileTokenStore`) or in any store implementing `TokenStore`.
- Retries of the requests rejected with 429 Too Many Requests, following `Retry-After`
(`shellrent_sdk.http.RetryTransport`). Like the default transports of httpx, it follows `HTTP_PROXY`,
`HTTPS_PROXY`, `ALL_PROXY`, `NO_PROXY`, `SSL_CERT_FILE` and `SSL_CERT_DIR` (`trust_env=False` ignores
them).
- `unwrap()` and `ApiException`, to turn the documented error responses into exceptions, including the
429 left after the retries.
- `TokenError`, with the OAuth2 `error` and `error_description` of the token endpoint, such as
`invalid_client` or, for its rate limit, `rate_limited`.
- The `shellrent` command, with `shellrent api` and `shellrent token`.

[Unreleased]: https://github.com/shellrent/shellrent-sdk-python/commits/main
60 changes: 60 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# CLAUDE.md

SDK Python ufficiale delle API pubbliche Shellrent (`shellrent-sdk`, package `shellrent_sdk`): client generato
da `spec/openapi.yaml`; a mano solo autenticazione, creazione del client, CLI, test, documentazione e CI.

## Generato e scritto a mano

- Generato: tutto `src/shellrent_sdk/client/`. Non si modifica mai a mano: `bin/generate` lo cancella e lo ricrea.
- Scritto a mano: il resto di `src/shellrent_sdk/`, `tests/`, `bin/generate`, `openapi-python-client.yaml`,
`pyproject.toml`, README, CHANGELOG, CI.
- Per cambiare il codice generato, in quest'ordine: opzioni in `openapi-python-client.yaml`,
`content_type_overrides`, template custom (ultima risorsa: dalla 0.29 si rompono a ogni aggiornamento).

## Rigenerazione

- `bin/generate` (serve uv) esegue con `uvx` openapi-python-client e ruff, con le versioni fissate nello
script. `--fail-on-warning`: un'operazione non gestita blocca la generazione invece di sparire dal client.
- I post-hook del generatore (ruff) usano `[tool.ruff]` di `pyproject.toml`: se lo cambi, rigenera.
- Per aggiornare il generatore: cambia `GENERATOR_VERSION` (ed eventualmente `RUFF_VERSION`), lancia
`bin/generate`, rivedi il diff, confronta `requires-python` e dipendenze con quelle che il generatore
dichiarerebbe (prova con `--meta uv` in una cartella temporanea), lancia i test, annota in `CHANGELOG.md`.
- `spec/openapi.yaml` è una copia della spec dell'applicazione Shellrent: qui non si modifica. I problemi si
correggono nell'applicazione e poi si ricopia il file.

## Versionamento e rilascio

- SemVer dai tag git (hatch-vcs: niente versione in `pyproject.toml`). Nuove operazioni o modelli: minor.
operationId o modelli rinominati o rimossi: major, perché cambiano moduli e classi. Nel diff controlla anche
le firme generate: parametri di path posizionali, gli altri keyword-only; un parametro rinominato o un
parametro di path aggiunto o spostato è una major.
- Ogni modifica va in `CHANGELOG.md` (Keep a Changelog, sezione `[Unreleased]`).
- Rilascio: tag `vX.Y.Z` (versione PEP 440 normalizzata, come `v1.2.0` o `v1.3.0rc1`) → `release.yml` builda
e verifica che la versione coincida col tag → il job `publish` attende l'approvazione dell'environment
`pypi` → Trusted Publishing, senza token. Non rinominare `release.yml`: PyPI lo riconosce per nome.

## Test e verifica

- `uv sync`, poi `uv run ruff format`, `uv run ruff check`, `uv run mypy` (strict, senza il codice generato)
e `uv run pytest`.
- `tests/support.py`: API finta per `httpx.MockTransport`, orologio finto, `Sleeps` (retry senza dormire).
`tests/servers.py`: proxy e server HTTPS locali; i certificati di `tests/fixtures/tls` scadono nel 2126 e la
chiave della CA non è conservata: per rifarli crea una nuova CA e aggiorna `CA_HASH_NAME`.
- `tests/integration` chiama l'API reale solo con `SHELLRENT_CLIENT_ID` e `SHELLRENT_CLIENT_SECRET` (e
opzionalmente `SHELLRENT_API_URL`); `test_errors.py` richiede lo scope `purchases:read`.
- La CI esegue ruff, mypy e pytest su Python 3.11-3.14, gli stessi controlli su 3.11 con le dipendenze minime
(`uv sync --resolution lowest-direct`) e `bin/generate`, fallendo se il codice rigenerato è diverso.

## Vincoli

- Python minimo 3.11, come il codice generato. Dipendenze di runtime: solo quelle del codice generato (httpx,
attrs) con gli stessi limiti. Niente nuove dipendenze senza un motivo forte.
- Il client secret non deve mai finire in cache, nei log, nei messaggi d'errore o nell'output della CLI; la CLI
lo legge solo dall'ambiente, mai da un'opzione.
- `auth.py`, `http.py` e `cli.py` non importano nulla da `shellrent_sdk.client` (lo verifica `test_layers.py`).
- I transport di default di `RetryTransport` seguono l'ambiente come quelli di httpx: proxy con
`urllib.request.getproxies`/`proxy_bypass` (httpx non ha un'API pubblica), certificati con `trust_env`.
- L'interfaccia pubblica di `auth`, `http` e della CLI (`main(argv)`, comandi, variabili d'ambiente, exit
code) è usata da `shellrent-internal-sdk`: cambiarla è una modifica incompatibile (major).
- `create_client()` restituisce `AuthenticatedClient` perché le funzioni generate lo richiedono nel tipo; il
token lo mette `OAuth2Auth` a ogni richiesta, non il campo `token` del client.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2026 Shellrent
Copyright (c) 2026 Shellrent S.r.l.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
Loading
Loading