clientcraft 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- clientcraft-0.1.0/.github/workflows/ci.yml +84 -0
- clientcraft-0.1.0/.github/workflows/publish.yml +56 -0
- clientcraft-0.1.0/.gitignore +56 -0
- clientcraft-0.1.0/LICENSE +21 -0
- clientcraft-0.1.0/PKG-INFO +181 -0
- clientcraft-0.1.0/README.md +137 -0
- clientcraft-0.1.0/example.py +273 -0
- clientcraft-0.1.0/pyproject.toml +121 -0
- clientcraft-0.1.0/src/clientcraft/__init__.py +76 -0
- clientcraft-0.1.0/src/clientcraft/__init__.pyi +47 -0
- clientcraft-0.1.0/src/clientcraft/_base.py +300 -0
- clientcraft-0.1.0/src/clientcraft/_endpoints.py +194 -0
- clientcraft-0.1.0/src/clientcraft/_responses.py +32 -0
- clientcraft-0.1.0/src/clientcraft/_types.py +54 -0
- clientcraft-0.1.0/src/clientcraft/async_client.py +91 -0
- clientcraft-0.1.0/src/clientcraft/backends/__init__.py +55 -0
- clientcraft-0.1.0/src/clientcraft/backends/_protocols.py +119 -0
- clientcraft-0.1.0/src/clientcraft/backends/aiohttp.py +151 -0
- clientcraft-0.1.0/src/clientcraft/backends/httpx.py +251 -0
- clientcraft-0.1.0/src/clientcraft/backends/requests.py +144 -0
- clientcraft-0.1.0/src/clientcraft/backends/urllib.py +166 -0
- clientcraft-0.1.0/src/clientcraft/client.py +90 -0
- clientcraft-0.1.0/src/clientcraft/py.typed +0 -0
- clientcraft-0.1.0/tests/__init__.py +1 -0
- clientcraft-0.1.0/tests/backends/__init__.py +1 -0
- clientcraft-0.1.0/tests/backends/conftest.py +279 -0
- clientcraft-0.1.0/tests/backends/test_aiohttp.py +198 -0
- clientcraft-0.1.0/tests/backends/test_httpx.py +308 -0
- clientcraft-0.1.0/tests/backends/test_integration.py +385 -0
- clientcraft-0.1.0/tests/backends/test_requests.py +171 -0
- clientcraft-0.1.0/tests/backends/test_urllib.py +215 -0
- clientcraft-0.1.0/tests/conftest.py +172 -0
- clientcraft-0.1.0/tests/test_async.py +100 -0
- clientcraft-0.1.0/tests/test_client.py +177 -0
- clientcraft-0.1.0/tests/test_endpoints.py +135 -0
- clientcraft-0.1.0/tests/test_parse_response.py +108 -0
- clientcraft-0.1.0/tests/test_prepare_request.py +216 -0
- clientcraft-0.1.0/tests/test_responses.py +143 -0
- clientcraft-0.1.0/uv.lock +1153 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
ci:
|
|
15
|
+
name: CI (Python ${{ matrix.python-version }})
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
strategy:
|
|
18
|
+
fail-fast: false
|
|
19
|
+
matrix:
|
|
20
|
+
python-version: ["3.12", "3.13", "3.14"]
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v6
|
|
23
|
+
|
|
24
|
+
- name: Setup uv and Python
|
|
25
|
+
uses: astral-sh/setup-uv@v7
|
|
26
|
+
with:
|
|
27
|
+
python-version: ${{ matrix.python-version }}
|
|
28
|
+
|
|
29
|
+
- name: Install dependencies
|
|
30
|
+
run: uv sync
|
|
31
|
+
|
|
32
|
+
- name: Run ruff check
|
|
33
|
+
run: uv run ruff check src tests
|
|
34
|
+
|
|
35
|
+
- name: Run ruff format check
|
|
36
|
+
run: uv run ruff format --check src tests
|
|
37
|
+
|
|
38
|
+
- name: Run mypy
|
|
39
|
+
run: uv run mypy src tests
|
|
40
|
+
|
|
41
|
+
- name: Run tests
|
|
42
|
+
run: uv run pytest --tb=short
|
|
43
|
+
|
|
44
|
+
integration:
|
|
45
|
+
name: Integration Tests
|
|
46
|
+
runs-on: ubuntu-latest
|
|
47
|
+
# Only run on main branch or when explicitly requested
|
|
48
|
+
if: github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'run-integration')
|
|
49
|
+
# Self-hosted httpbin avoids the flaky/503-prone public httpbin.org.
|
|
50
|
+
# Uses the canonical kennethreitz/httpbin image (same one httpbin.org runs),
|
|
51
|
+
# so response shapes match exactly (e.g. string-valued headers).
|
|
52
|
+
services:
|
|
53
|
+
httpbin:
|
|
54
|
+
image: kennethreitz/httpbin
|
|
55
|
+
ports:
|
|
56
|
+
- 8080:80
|
|
57
|
+
env:
|
|
58
|
+
HTTPBIN_BASE_URL: http://localhost:8080
|
|
59
|
+
steps:
|
|
60
|
+
- uses: actions/checkout@v6
|
|
61
|
+
|
|
62
|
+
- name: Setup uv and Python
|
|
63
|
+
uses: astral-sh/setup-uv@v7
|
|
64
|
+
with:
|
|
65
|
+
python-version: "3.12"
|
|
66
|
+
|
|
67
|
+
- name: Install dependencies
|
|
68
|
+
run: uv sync --dev
|
|
69
|
+
|
|
70
|
+
- name: Wait for httpbin to be ready
|
|
71
|
+
run: |
|
|
72
|
+
for i in $(seq 1 30); do
|
|
73
|
+
if curl -sf "$HTTPBIN_BASE_URL/status/200" >/dev/null; then
|
|
74
|
+
echo "httpbin is ready"
|
|
75
|
+
exit 0
|
|
76
|
+
fi
|
|
77
|
+
echo "waiting for httpbin... ($i)"
|
|
78
|
+
sleep 1
|
|
79
|
+
done
|
|
80
|
+
echo "httpbin did not become ready in time"
|
|
81
|
+
exit 1
|
|
82
|
+
|
|
83
|
+
- name: Run integration tests
|
|
84
|
+
run: uv run pytest -m integration -n auto --tb=short
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
# Publishes to PyPI when a GitHub Release is published. The release tag (e.g.
|
|
4
|
+
# v0.1.0) is the source of truth for the version: hatch-vcs derives the package
|
|
5
|
+
# version from it, so no manual version bump is needed.
|
|
6
|
+
on:
|
|
7
|
+
release:
|
|
8
|
+
types: [published]
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
build:
|
|
12
|
+
name: Build distribution
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v6
|
|
16
|
+
with:
|
|
17
|
+
# Full history + tags are required for hatch-vcs to compute the version.
|
|
18
|
+
fetch-depth: 0
|
|
19
|
+
|
|
20
|
+
- name: Setup uv and Python
|
|
21
|
+
uses: astral-sh/setup-uv@v7
|
|
22
|
+
with:
|
|
23
|
+
python-version: "3.12"
|
|
24
|
+
|
|
25
|
+
- name: Build sdist and wheel
|
|
26
|
+
run: uv build
|
|
27
|
+
|
|
28
|
+
- name: Check distribution metadata
|
|
29
|
+
run: uvx twine check dist/*
|
|
30
|
+
|
|
31
|
+
- name: Upload distribution artifacts
|
|
32
|
+
uses: actions/upload-artifact@v4
|
|
33
|
+
with:
|
|
34
|
+
name: dist
|
|
35
|
+
path: dist/
|
|
36
|
+
|
|
37
|
+
publish:
|
|
38
|
+
name: Publish to PyPI
|
|
39
|
+
needs: build
|
|
40
|
+
runs-on: ubuntu-latest
|
|
41
|
+
# Trusted Publishing (OIDC) — no API token needed. Requires a matching
|
|
42
|
+
# publisher configured on PyPI (see notes below) and the environment below.
|
|
43
|
+
environment:
|
|
44
|
+
name: pypi
|
|
45
|
+
url: https://pypi.org/p/clientcraft
|
|
46
|
+
permissions:
|
|
47
|
+
id-token: write
|
|
48
|
+
steps:
|
|
49
|
+
- name: Download distribution artifacts
|
|
50
|
+
uses: actions/download-artifact@v4
|
|
51
|
+
with:
|
|
52
|
+
name: dist
|
|
53
|
+
path: dist/
|
|
54
|
+
|
|
55
|
+
- name: Publish to PyPI
|
|
56
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
develop-eggs/
|
|
9
|
+
dist/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
.eggs/
|
|
13
|
+
lib/
|
|
14
|
+
lib64/
|
|
15
|
+
parts/
|
|
16
|
+
sdist/
|
|
17
|
+
var/
|
|
18
|
+
wheels/
|
|
19
|
+
*.egg-info/
|
|
20
|
+
.installed.cfg
|
|
21
|
+
*.egg
|
|
22
|
+
|
|
23
|
+
# Virtual environments
|
|
24
|
+
.venv/
|
|
25
|
+
venv/
|
|
26
|
+
ENV/
|
|
27
|
+
|
|
28
|
+
# uv
|
|
29
|
+
.uv/
|
|
30
|
+
# uv.lock
|
|
31
|
+
|
|
32
|
+
# IDE
|
|
33
|
+
.idea/
|
|
34
|
+
.vscode/
|
|
35
|
+
*.swp
|
|
36
|
+
*.swo
|
|
37
|
+
*~
|
|
38
|
+
|
|
39
|
+
# Testing
|
|
40
|
+
.pytest_cache/
|
|
41
|
+
.coverage
|
|
42
|
+
htmlcov/
|
|
43
|
+
.tox/
|
|
44
|
+
.nox/
|
|
45
|
+
|
|
46
|
+
# mypy
|
|
47
|
+
.mypy_cache/
|
|
48
|
+
.dmypy.json
|
|
49
|
+
dmypy.json
|
|
50
|
+
|
|
51
|
+
# ruff
|
|
52
|
+
.ruff_cache/
|
|
53
|
+
|
|
54
|
+
# OS
|
|
55
|
+
.DS_Store
|
|
56
|
+
Thumbs.db
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sunbright Technologies
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: clientcraft
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A declarative, type-safe API client framework for Python
|
|
5
|
+
Project-URL: Homepage, https://github.com/Sunbright-Technologies/clientcraft
|
|
6
|
+
Project-URL: Repository, https://github.com/Sunbright-Technologies/clientcraft
|
|
7
|
+
Author-email: SunBright Technologies <engineering@sunbright.tech>
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: api,async,client,declarative,http,pydantic,type-safe
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Framework :: Pydantic :: 2
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: >=3.12
|
|
21
|
+
Requires-Dist: pydantic>=2.0
|
|
22
|
+
Provides-Extra: aiohttp
|
|
23
|
+
Requires-Dist: aiohttp>=3.9; extra == 'aiohttp'
|
|
24
|
+
Provides-Extra: all
|
|
25
|
+
Requires-Dist: aiohttp>=3.9; extra == 'all'
|
|
26
|
+
Requires-Dist: httpx>=0.27; extra == 'all'
|
|
27
|
+
Requires-Dist: requests>=2.32.5; extra == 'all'
|
|
28
|
+
Requires-Dist: types-requests>=2.32.5; extra == 'all'
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: aiohttp>=3.9; extra == 'dev'
|
|
31
|
+
Requires-Dist: httpx>=0.28.1; extra == 'dev'
|
|
32
|
+
Requires-Dist: mypy>=1.8; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: requests>=2.32.5; extra == 'dev'
|
|
36
|
+
Requires-Dist: ruff>=0.3; extra == 'dev'
|
|
37
|
+
Requires-Dist: types-requests>=2.32.5; extra == 'dev'
|
|
38
|
+
Provides-Extra: httpx
|
|
39
|
+
Requires-Dist: httpx>=0.27; extra == 'httpx'
|
|
40
|
+
Provides-Extra: requests
|
|
41
|
+
Requires-Dist: requests>=2.32.5; extra == 'requests'
|
|
42
|
+
Requires-Dist: types-requests>=2.32.5; extra == 'requests'
|
|
43
|
+
Description-Content-Type: text/markdown
|
|
44
|
+
|
|
45
|
+
# Declarative API Client
|
|
46
|
+
|
|
47
|
+
A declarative, type-safe API client framework for Python 3.12+.
|
|
48
|
+
|
|
49
|
+
## Features
|
|
50
|
+
|
|
51
|
+
- **Declarative endpoint definitions** using type annotations
|
|
52
|
+
- **Type-safe** request and response handling with Pydantic
|
|
53
|
+
- **Sync and async** client support
|
|
54
|
+
- **Pluggable backends** (aiohttp, httpx, or custom)
|
|
55
|
+
- **Multiple response types**: JSON, text, bytes, or no content
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# Using uv
|
|
61
|
+
uv add clientcraft
|
|
62
|
+
|
|
63
|
+
# With aiohttp backend
|
|
64
|
+
uv add "clientcraft[aiohttp]"
|
|
65
|
+
|
|
66
|
+
# With all optional backends
|
|
67
|
+
uv add "clientcraft[all]"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Quick Start
|
|
71
|
+
|
|
72
|
+
### Define your API
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from typing import Literal
|
|
76
|
+
from pydantic import BaseModel
|
|
77
|
+
from clientcraft import Get, Post, Delete
|
|
78
|
+
from clientcraft.client import APIClient
|
|
79
|
+
|
|
80
|
+
# Define request/response models
|
|
81
|
+
class GetUserRequest(BaseModel):
|
|
82
|
+
user_id: str
|
|
83
|
+
|
|
84
|
+
class User(BaseModel):
|
|
85
|
+
id: str
|
|
86
|
+
name: str
|
|
87
|
+
email: str
|
|
88
|
+
|
|
89
|
+
class CreateUserRequest(BaseModel):
|
|
90
|
+
name: str
|
|
91
|
+
email: str
|
|
92
|
+
|
|
93
|
+
# Define your API client declaratively
|
|
94
|
+
class UserAPI(APIClient):
|
|
95
|
+
get_user: Get[GetUserRequest, User, Literal["/users/{user_id}"]]
|
|
96
|
+
create_user: Post[CreateUserRequest, User, Literal["/users"]]
|
|
97
|
+
delete_user: Delete[GetUserRequest, None, Literal["/users/{user_id}"]]
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Use your API
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from clientcraft.backends import RequestsBackend
|
|
104
|
+
|
|
105
|
+
# Create client with a backend
|
|
106
|
+
backend = RequestsBackend()
|
|
107
|
+
client = UserAPI(base_url="https://api.example.com", backend=backend)
|
|
108
|
+
|
|
109
|
+
# Make requests - fully typed!
|
|
110
|
+
user = client.get_user(GetUserRequest(user_id="123"))
|
|
111
|
+
print(user.name) # Type checker knows this is a User
|
|
112
|
+
|
|
113
|
+
# Create a user
|
|
114
|
+
new_user = client.create_user(CreateUserRequest(name="Alice", email="alice@example.com"))
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Async Usage
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from clientcraft import AsyncGet, AsyncPost
|
|
121
|
+
from clientcraft.async_client import AsyncAPIClient
|
|
122
|
+
from clientcraft.backends import AiohttpBackend
|
|
123
|
+
|
|
124
|
+
class AsyncUserAPI(AsyncAPIClient):
|
|
125
|
+
get_user: AsyncGet[GetUserRequest, User, Literal["/users/{user_id}"]]
|
|
126
|
+
create_user: AsyncPost[CreateUserRequest, User, Literal["/users"]]
|
|
127
|
+
|
|
128
|
+
async with AiohttpBackend() as backend:
|
|
129
|
+
client = AsyncUserAPI(base_url="https://api.example.com", backend=backend)
|
|
130
|
+
user = await client.get_user(GetUserRequest(user_id="123"))
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Endpoint Types
|
|
134
|
+
|
|
135
|
+
| Type | HTTP Method | Request Style | Description |
|
|
136
|
+
|------|-------------|---------------|-------------|
|
|
137
|
+
| `Get` | GET | Query params | Read operations |
|
|
138
|
+
| `Post` | POST | JSON body | Create operations |
|
|
139
|
+
| `Put` | PUT | JSON body | Full update |
|
|
140
|
+
| `Patch` | PATCH | JSON body | Partial update |
|
|
141
|
+
| `Delete` | DELETE | Query params | Delete operations |
|
|
142
|
+
|
|
143
|
+
## Response Types
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
from clientcraft import TextResponse, BytesResponse
|
|
147
|
+
|
|
148
|
+
class HealthAPI(APIClient):
|
|
149
|
+
# JSON response (default)
|
|
150
|
+
get_user: Get[Request, User, Literal["/users/{id}"]]
|
|
151
|
+
|
|
152
|
+
# Text response
|
|
153
|
+
health_check: Get[Request, TextResponse, Literal["/health"]]
|
|
154
|
+
|
|
155
|
+
# Binary response
|
|
156
|
+
download: Get[Request, BytesResponse, Literal["/files/{id}"]]
|
|
157
|
+
|
|
158
|
+
# No response body (204 No Content)
|
|
159
|
+
delete_user: Delete[Request, None, Literal["/users/{id}"]]
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Development
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
# Clone and setup
|
|
166
|
+
cd clientcraft
|
|
167
|
+
uv sync --all-extras
|
|
168
|
+
|
|
169
|
+
# Run tests
|
|
170
|
+
uv run pytest
|
|
171
|
+
|
|
172
|
+
# Type checking
|
|
173
|
+
uv run mypy
|
|
174
|
+
|
|
175
|
+
# Linting
|
|
176
|
+
uv run ruff check .
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## License
|
|
180
|
+
|
|
181
|
+
MIT
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Declarative API Client
|
|
2
|
+
|
|
3
|
+
A declarative, type-safe API client framework for Python 3.12+.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Declarative endpoint definitions** using type annotations
|
|
8
|
+
- **Type-safe** request and response handling with Pydantic
|
|
9
|
+
- **Sync and async** client support
|
|
10
|
+
- **Pluggable backends** (aiohttp, httpx, or custom)
|
|
11
|
+
- **Multiple response types**: JSON, text, bytes, or no content
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Using uv
|
|
17
|
+
uv add clientcraft
|
|
18
|
+
|
|
19
|
+
# With aiohttp backend
|
|
20
|
+
uv add "clientcraft[aiohttp]"
|
|
21
|
+
|
|
22
|
+
# With all optional backends
|
|
23
|
+
uv add "clientcraft[all]"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
### Define your API
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from typing import Literal
|
|
32
|
+
from pydantic import BaseModel
|
|
33
|
+
from clientcraft import Get, Post, Delete
|
|
34
|
+
from clientcraft.client import APIClient
|
|
35
|
+
|
|
36
|
+
# Define request/response models
|
|
37
|
+
class GetUserRequest(BaseModel):
|
|
38
|
+
user_id: str
|
|
39
|
+
|
|
40
|
+
class User(BaseModel):
|
|
41
|
+
id: str
|
|
42
|
+
name: str
|
|
43
|
+
email: str
|
|
44
|
+
|
|
45
|
+
class CreateUserRequest(BaseModel):
|
|
46
|
+
name: str
|
|
47
|
+
email: str
|
|
48
|
+
|
|
49
|
+
# Define your API client declaratively
|
|
50
|
+
class UserAPI(APIClient):
|
|
51
|
+
get_user: Get[GetUserRequest, User, Literal["/users/{user_id}"]]
|
|
52
|
+
create_user: Post[CreateUserRequest, User, Literal["/users"]]
|
|
53
|
+
delete_user: Delete[GetUserRequest, None, Literal["/users/{user_id}"]]
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Use your API
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from clientcraft.backends import RequestsBackend
|
|
60
|
+
|
|
61
|
+
# Create client with a backend
|
|
62
|
+
backend = RequestsBackend()
|
|
63
|
+
client = UserAPI(base_url="https://api.example.com", backend=backend)
|
|
64
|
+
|
|
65
|
+
# Make requests - fully typed!
|
|
66
|
+
user = client.get_user(GetUserRequest(user_id="123"))
|
|
67
|
+
print(user.name) # Type checker knows this is a User
|
|
68
|
+
|
|
69
|
+
# Create a user
|
|
70
|
+
new_user = client.create_user(CreateUserRequest(name="Alice", email="alice@example.com"))
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Async Usage
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from clientcraft import AsyncGet, AsyncPost
|
|
77
|
+
from clientcraft.async_client import AsyncAPIClient
|
|
78
|
+
from clientcraft.backends import AiohttpBackend
|
|
79
|
+
|
|
80
|
+
class AsyncUserAPI(AsyncAPIClient):
|
|
81
|
+
get_user: AsyncGet[GetUserRequest, User, Literal["/users/{user_id}"]]
|
|
82
|
+
create_user: AsyncPost[CreateUserRequest, User, Literal["/users"]]
|
|
83
|
+
|
|
84
|
+
async with AiohttpBackend() as backend:
|
|
85
|
+
client = AsyncUserAPI(base_url="https://api.example.com", backend=backend)
|
|
86
|
+
user = await client.get_user(GetUserRequest(user_id="123"))
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Endpoint Types
|
|
90
|
+
|
|
91
|
+
| Type | HTTP Method | Request Style | Description |
|
|
92
|
+
|------|-------------|---------------|-------------|
|
|
93
|
+
| `Get` | GET | Query params | Read operations |
|
|
94
|
+
| `Post` | POST | JSON body | Create operations |
|
|
95
|
+
| `Put` | PUT | JSON body | Full update |
|
|
96
|
+
| `Patch` | PATCH | JSON body | Partial update |
|
|
97
|
+
| `Delete` | DELETE | Query params | Delete operations |
|
|
98
|
+
|
|
99
|
+
## Response Types
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from clientcraft import TextResponse, BytesResponse
|
|
103
|
+
|
|
104
|
+
class HealthAPI(APIClient):
|
|
105
|
+
# JSON response (default)
|
|
106
|
+
get_user: Get[Request, User, Literal["/users/{id}"]]
|
|
107
|
+
|
|
108
|
+
# Text response
|
|
109
|
+
health_check: Get[Request, TextResponse, Literal["/health"]]
|
|
110
|
+
|
|
111
|
+
# Binary response
|
|
112
|
+
download: Get[Request, BytesResponse, Literal["/files/{id}"]]
|
|
113
|
+
|
|
114
|
+
# No response body (204 No Content)
|
|
115
|
+
delete_user: Delete[Request, None, Literal["/users/{id}"]]
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Development
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
# Clone and setup
|
|
122
|
+
cd clientcraft
|
|
123
|
+
uv sync --all-extras
|
|
124
|
+
|
|
125
|
+
# Run tests
|
|
126
|
+
uv run pytest
|
|
127
|
+
|
|
128
|
+
# Type checking
|
|
129
|
+
uv run mypy
|
|
130
|
+
|
|
131
|
+
# Linting
|
|
132
|
+
uv run ruff check .
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## License
|
|
136
|
+
|
|
137
|
+
MIT
|