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.
Files changed (39) hide show
  1. clientcraft-0.1.0/.github/workflows/ci.yml +84 -0
  2. clientcraft-0.1.0/.github/workflows/publish.yml +56 -0
  3. clientcraft-0.1.0/.gitignore +56 -0
  4. clientcraft-0.1.0/LICENSE +21 -0
  5. clientcraft-0.1.0/PKG-INFO +181 -0
  6. clientcraft-0.1.0/README.md +137 -0
  7. clientcraft-0.1.0/example.py +273 -0
  8. clientcraft-0.1.0/pyproject.toml +121 -0
  9. clientcraft-0.1.0/src/clientcraft/__init__.py +76 -0
  10. clientcraft-0.1.0/src/clientcraft/__init__.pyi +47 -0
  11. clientcraft-0.1.0/src/clientcraft/_base.py +300 -0
  12. clientcraft-0.1.0/src/clientcraft/_endpoints.py +194 -0
  13. clientcraft-0.1.0/src/clientcraft/_responses.py +32 -0
  14. clientcraft-0.1.0/src/clientcraft/_types.py +54 -0
  15. clientcraft-0.1.0/src/clientcraft/async_client.py +91 -0
  16. clientcraft-0.1.0/src/clientcraft/backends/__init__.py +55 -0
  17. clientcraft-0.1.0/src/clientcraft/backends/_protocols.py +119 -0
  18. clientcraft-0.1.0/src/clientcraft/backends/aiohttp.py +151 -0
  19. clientcraft-0.1.0/src/clientcraft/backends/httpx.py +251 -0
  20. clientcraft-0.1.0/src/clientcraft/backends/requests.py +144 -0
  21. clientcraft-0.1.0/src/clientcraft/backends/urllib.py +166 -0
  22. clientcraft-0.1.0/src/clientcraft/client.py +90 -0
  23. clientcraft-0.1.0/src/clientcraft/py.typed +0 -0
  24. clientcraft-0.1.0/tests/__init__.py +1 -0
  25. clientcraft-0.1.0/tests/backends/__init__.py +1 -0
  26. clientcraft-0.1.0/tests/backends/conftest.py +279 -0
  27. clientcraft-0.1.0/tests/backends/test_aiohttp.py +198 -0
  28. clientcraft-0.1.0/tests/backends/test_httpx.py +308 -0
  29. clientcraft-0.1.0/tests/backends/test_integration.py +385 -0
  30. clientcraft-0.1.0/tests/backends/test_requests.py +171 -0
  31. clientcraft-0.1.0/tests/backends/test_urllib.py +215 -0
  32. clientcraft-0.1.0/tests/conftest.py +172 -0
  33. clientcraft-0.1.0/tests/test_async.py +100 -0
  34. clientcraft-0.1.0/tests/test_client.py +177 -0
  35. clientcraft-0.1.0/tests/test_endpoints.py +135 -0
  36. clientcraft-0.1.0/tests/test_parse_response.py +108 -0
  37. clientcraft-0.1.0/tests/test_prepare_request.py +216 -0
  38. clientcraft-0.1.0/tests/test_responses.py +143 -0
  39. 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