haveapi-client 0.30.2__tar.gz → 0.31.1__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 (26) hide show
  1. haveapi_client-0.31.1/PKG-INFO +215 -0
  2. haveapi_client-0.31.1/README.md +193 -0
  3. {haveapi_client-0.30.2 → haveapi_client-0.31.1}/pyproject.toml +2 -2
  4. {haveapi_client-0.30.2 → haveapi_client-0.31.1}/pyproject.toml.orig +2 -2
  5. haveapi_client-0.30.2/PKG-INFO +0 -198
  6. haveapi_client-0.30.2/README.md +0 -176
  7. {haveapi_client-0.30.2 → haveapi_client-0.31.1}/LICENSE.txt +0 -0
  8. {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/__init__.py +0 -0
  9. {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/_auth/__init__.py +0 -0
  10. {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/_protocol/__init__.py +0 -0
  11. {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/_protocol/description.py +0 -0
  12. {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/_protocol/execution.py +0 -0
  13. {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/_protocol/input.py +0 -0
  14. {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/_protocol/models.py +0 -0
  15. {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/_protocol/request.py +0 -0
  16. {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/_protocol/response.py +0 -0
  17. {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/_resources/__init__.py +0 -0
  18. {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/_resources/state.py +0 -0
  19. {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/_transport/__init__.py +0 -0
  20. {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/async_client.py +0 -0
  21. {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/client.py +0 -0
  22. {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/exceptions.py +0 -0
  23. {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/operations.py +0 -0
  24. {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/pagination.py +0 -0
  25. {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/py.typed +0 -0
  26. {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/response.py +0 -0
@@ -0,0 +1,215 @@
1
+ Metadata-Version: 2.4
2
+ Name: haveapi-client
3
+ Version: 0.31.1
4
+ Summary: Dynamic synchronous and asynchronous clients for HaveAPI APIs
5
+ License-Expression: MIT
6
+ License-File: LICENSE.txt
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: Programming Language :: Python :: 3.14
13
+ Classifier: Programming Language :: Python :: 3.15
14
+ Classifier: Typing :: Typed
15
+ Requires-Dist: httpx>=0.28.1,<1
16
+ Requires-Dist: anyio>=4.15,<5
17
+ Requires-Python: >=3.11
18
+ Project-URL: Homepage, https://github.com/vpsfreecz/haveapi
19
+ Project-URL: Source, https://github.com/vpsfreecz/haveapi/tree/master/clients/python
20
+ Project-URL: Issues, https://github.com/vpsfreecz/haveapi/issues
21
+ Description-Content-Type: text/markdown
22
+
23
+ # HaveAPI Python client
24
+
25
+ `haveapi-client` discovers resources, actions and parameter types from a
26
+ HaveAPI server using protocol 2.x. It provides synchronous and asynchronous clients
27
+ with the same dynamic interface. Python 3.11 or newer is required.
28
+
29
+ [vpsAdmin](https://github.com/vpsfreecz/vpsadmin) is the control panel used by
30
+ vpsFree.cz to manage virtual private servers (VPSes). It is HaveAPI's main user,
31
+ so the examples in this README and the `examples/` directory use its API.
32
+
33
+ ## Installation
34
+
35
+ Add the package from PyPI to your project with uv:
36
+
37
+ ```sh
38
+ uv add haveapi-client
39
+ ```
40
+
41
+ Or install it with pip in your application's Python environment:
42
+
43
+ ```sh
44
+ python3 -m pip install haveapi-client
45
+ ```
46
+
47
+ ## Synchronous client
48
+
49
+ Set `VPSADMIN_TOKEN` to an existing vpsAdmin API token. Keep credentials out of
50
+ source files. This example reads one page of VPSes:
51
+
52
+ ```python
53
+ import os
54
+
55
+ from haveapi_client import Client, TokenAuth
56
+
57
+ with Client(
58
+ "https://api.vpsfree.cz", auth=TokenAuth(os.environ["VPSADMIN_TOKEN"])
59
+ ) as api:
60
+ result = api.vps.list(limit=10)
61
+ for vps in result.data:
62
+ print(vps["id"], vps["hostname"])
63
+ ```
64
+
65
+ Entering `with` discovers the API and the actions available to your token.
66
+ These examples use the server's default API version. Pass the API root to the
67
+ constructor, including any mount such as `https://api.example/mount/`, without
68
+ appending a version. To select a particular version, use the optional `version`
69
+ constructor argument.
70
+
71
+ ## Asynchronous client
72
+
73
+ `AsyncClient` has the same resource/action interface. Use `async with` and await
74
+ action calls. This asyncio example reads the same page:
75
+
76
+ ```python
77
+ import asyncio
78
+ import os
79
+
80
+ from haveapi_client import AsyncClient, TokenAuth
81
+
82
+
83
+ async def main():
84
+ async with AsyncClient(
85
+ "https://api.vpsfree.cz", auth=TokenAuth(os.environ["VPSADMIN_TOKEN"])
86
+ ) as api:
87
+ result = await api.vps.list(limit=10)
88
+ for vps in result.data:
89
+ print(vps["id"], vps["hostname"])
90
+
91
+
92
+ asyncio.run(main())
93
+ ```
94
+
95
+ The async client also works with Trio. The runnable scripts in
96
+ [`examples/list_vps.py`](examples/list_vps.py) and
97
+ [`examples/list_vps_async.py`](examples/list_vps_async.py) accept the API URL and
98
+ token from environment variables.
99
+
100
+ ## Authentication
101
+
102
+ Pass `TokenAuth(token)`, `BasicAuth(username, password)` or
103
+ `OAuth2Auth(access_token)` as the constructor's `auth` argument. Omit `auth` for
104
+ public resources. OAuth2Auth sends an existing bearer token; browser
105
+ authorization and token refresh are outside this package.
106
+
107
+ Password-based token login, including multi-step challenges such as TOTP, uses
108
+ `begin_token_login()`. See [token login](docs/reference.md#token-login) for the
109
+ continuation flow, token renewal and revocation. Closing a client does not revoke
110
+ its server token. The package keeps credentials in memory and writes no
111
+ credential cache.
112
+
113
+ ## Resources, actions and results
114
+
115
+ Resources and actions come from the API description. Index a resource to bind
116
+ its ID. Inside a connected synchronous client context, with `vps_id` set to an
117
+ accessible VPS ID:
118
+
119
+ ```python
120
+ detail = api.vps[vps_id].show()
121
+ print(detail.data["hostname"])
122
+
123
+ interfaces = api.network_interface.list(vps=vps_id)
124
+ for interface in interfaces.data:
125
+ print(interface["id"])
126
+ ```
127
+
128
+ vpsAdmin exposes network interfaces at the top level. Follow the discovered
129
+ hierarchy for other APIs. Use exact-name maps such as
130
+ `api.resources["vps"].actions["index"]` for names that collide with Python methods
131
+ or keywords. Resource and action lookup sends no requests.
132
+
133
+ `api.vps.list(limit=10)` is shorthand for
134
+ `api.vps.list.call(params={"limit": 10})`. Use `.call()` for action metadata,
135
+ explicit path parameters or per-request options. For example, vpsAdmin's count
136
+ metadata requests the total number of matching VPSes:
137
+
138
+ ```python
139
+ page = api.vps.list.call(params={"limit": 10}, meta={"count": True})
140
+ print(page.meta["total_count"])
141
+ for vps in page.data:
142
+ print(vps["id"], vps["hostname"])
143
+ ```
144
+
145
+ With `AsyncClient`, await the same calls, for example
146
+ `detail = await api.vps[vps_id].show()` and
147
+ `page = await api.vps.list.call(params={"limit": 10}, meta={"count": True})`.
148
+
149
+ Each action returns a `Response`: `.data` holds the result and `.meta` holds
150
+ action metadata. Returned objects are read-only mappings; access fields with
151
+ expressions such as `vps["hostname"]`. Associations are not fetched automatically: call an
152
+ association's `.fetch()` explicitly, awaiting it with the async client.
153
+
154
+ Input is validated against the description. Omitting a parameter differs from
155
+ passing `None`; `None` is accepted only where advertised. GET has stricter shape
156
+ limits than JSON requests. See the [input encoding reference](docs/reference.md#input-encoding)
157
+ for those limits.
158
+
159
+ ## Pagination and operations
160
+
161
+ Normal list calls fetch one page. Use `action.iter_pages(...)` or
162
+ `action.iter_all(...)` for explicit iteration. Async clients return async
163
+ iterators, consumed with `async for`. Inside a connected synchronous client
164
+ context, supply a cursor strategy that matches the endpoint's ordering:
165
+
166
+ ```python
167
+ from haveapi_client import IdCursor
168
+
169
+ for vps in api.vps.list.iter_all(strategy=IdCursor(direction="asc"), page_size=100):
170
+ print(vps["id"])
171
+ ```
172
+
173
+ `IdCursor` requires integer IDs in the requested monotonic order and rejects a
174
+ repeated or reversed cursor. The endpoint must advertise `limit` and the cursor
175
+ parameter. HaveAPI pagination metadata alone does not promise this ordering;
176
+ select the strategy from the API's documented behavior.
177
+
178
+ When a response includes an operation, use `response.operation.show()`,
179
+ `.wait(timeout=300, interval=1)` or `.cancel()`. Await these methods on an async
180
+ operation. For an existing synchronous action result:
181
+
182
+ ```python
183
+ if result.operation is not None:
184
+ final_state = result.operation.wait(timeout=300, interval=1)
185
+ print(final_state.data)
186
+ ```
187
+
188
+ Waiting checks the described action-state resource and raises
189
+ `OperationFailedError` for failure or `OperationTimeoutError` at the deadline.
190
+ Timeout and coroutine cancellation do not cancel the server operation. Explicit
191
+ cancel requires advertised cancellation support.
192
+
193
+ See [operation deadlines](docs/reference.md#operation-deadlines) for the
194
+ difference between synchronous and asynchronous wait timeouts.
195
+
196
+ ## Errors and timeouts
197
+
198
+ Catch `HaveAPIError` for client errors, or its more specific `TransportError`,
199
+ `ProtocolError`, `AuthenticationError`, `ValidationError` and `ActionError`
200
+ subclasses. Errors provide status and structured details when available. Treat
201
+ response payloads and diagnostics as potentially private application data.
202
+
203
+ The default HTTP timeout is 30 seconds and can be overridden per request with
204
+ `RequestOptions(timeout=...)` through `.call(options=...)`. Requests are not
205
+ retried or redirected.
206
+
207
+ ## Reference and development
208
+
209
+ See the [runtime reference](docs/reference.md) for discovery, URL resolution,
210
+ encoding, response fields and connection ownership.
211
+
212
+ For development against a local HaveAPI checkout, run
213
+ `uv add /path/to/haveapi/clients/python` in your application. See
214
+ [CONTRIBUTING.md](CONTRIBUTING.md) for checks and packaging, and the shared
215
+ [release procedure](../../doc/agent-instructions/releases.md) for publication.
@@ -0,0 +1,193 @@
1
+ # HaveAPI Python client
2
+
3
+ `haveapi-client` discovers resources, actions and parameter types from a
4
+ HaveAPI server using protocol 2.x. It provides synchronous and asynchronous clients
5
+ with the same dynamic interface. Python 3.11 or newer is required.
6
+
7
+ [vpsAdmin](https://github.com/vpsfreecz/vpsadmin) is the control panel used by
8
+ vpsFree.cz to manage virtual private servers (VPSes). It is HaveAPI's main user,
9
+ so the examples in this README and the `examples/` directory use its API.
10
+
11
+ ## Installation
12
+
13
+ Add the package from PyPI to your project with uv:
14
+
15
+ ```sh
16
+ uv add haveapi-client
17
+ ```
18
+
19
+ Or install it with pip in your application's Python environment:
20
+
21
+ ```sh
22
+ python3 -m pip install haveapi-client
23
+ ```
24
+
25
+ ## Synchronous client
26
+
27
+ Set `VPSADMIN_TOKEN` to an existing vpsAdmin API token. Keep credentials out of
28
+ source files. This example reads one page of VPSes:
29
+
30
+ ```python
31
+ import os
32
+
33
+ from haveapi_client import Client, TokenAuth
34
+
35
+ with Client(
36
+ "https://api.vpsfree.cz", auth=TokenAuth(os.environ["VPSADMIN_TOKEN"])
37
+ ) as api:
38
+ result = api.vps.list(limit=10)
39
+ for vps in result.data:
40
+ print(vps["id"], vps["hostname"])
41
+ ```
42
+
43
+ Entering `with` discovers the API and the actions available to your token.
44
+ These examples use the server's default API version. Pass the API root to the
45
+ constructor, including any mount such as `https://api.example/mount/`, without
46
+ appending a version. To select a particular version, use the optional `version`
47
+ constructor argument.
48
+
49
+ ## Asynchronous client
50
+
51
+ `AsyncClient` has the same resource/action interface. Use `async with` and await
52
+ action calls. This asyncio example reads the same page:
53
+
54
+ ```python
55
+ import asyncio
56
+ import os
57
+
58
+ from haveapi_client import AsyncClient, TokenAuth
59
+
60
+
61
+ async def main():
62
+ async with AsyncClient(
63
+ "https://api.vpsfree.cz", auth=TokenAuth(os.environ["VPSADMIN_TOKEN"])
64
+ ) as api:
65
+ result = await api.vps.list(limit=10)
66
+ for vps in result.data:
67
+ print(vps["id"], vps["hostname"])
68
+
69
+
70
+ asyncio.run(main())
71
+ ```
72
+
73
+ The async client also works with Trio. The runnable scripts in
74
+ [`examples/list_vps.py`](examples/list_vps.py) and
75
+ [`examples/list_vps_async.py`](examples/list_vps_async.py) accept the API URL and
76
+ token from environment variables.
77
+
78
+ ## Authentication
79
+
80
+ Pass `TokenAuth(token)`, `BasicAuth(username, password)` or
81
+ `OAuth2Auth(access_token)` as the constructor's `auth` argument. Omit `auth` for
82
+ public resources. OAuth2Auth sends an existing bearer token; browser
83
+ authorization and token refresh are outside this package.
84
+
85
+ Password-based token login, including multi-step challenges such as TOTP, uses
86
+ `begin_token_login()`. See [token login](docs/reference.md#token-login) for the
87
+ continuation flow, token renewal and revocation. Closing a client does not revoke
88
+ its server token. The package keeps credentials in memory and writes no
89
+ credential cache.
90
+
91
+ ## Resources, actions and results
92
+
93
+ Resources and actions come from the API description. Index a resource to bind
94
+ its ID. Inside a connected synchronous client context, with `vps_id` set to an
95
+ accessible VPS ID:
96
+
97
+ ```python
98
+ detail = api.vps[vps_id].show()
99
+ print(detail.data["hostname"])
100
+
101
+ interfaces = api.network_interface.list(vps=vps_id)
102
+ for interface in interfaces.data:
103
+ print(interface["id"])
104
+ ```
105
+
106
+ vpsAdmin exposes network interfaces at the top level. Follow the discovered
107
+ hierarchy for other APIs. Use exact-name maps such as
108
+ `api.resources["vps"].actions["index"]` for names that collide with Python methods
109
+ or keywords. Resource and action lookup sends no requests.
110
+
111
+ `api.vps.list(limit=10)` is shorthand for
112
+ `api.vps.list.call(params={"limit": 10})`. Use `.call()` for action metadata,
113
+ explicit path parameters or per-request options. For example, vpsAdmin's count
114
+ metadata requests the total number of matching VPSes:
115
+
116
+ ```python
117
+ page = api.vps.list.call(params={"limit": 10}, meta={"count": True})
118
+ print(page.meta["total_count"])
119
+ for vps in page.data:
120
+ print(vps["id"], vps["hostname"])
121
+ ```
122
+
123
+ With `AsyncClient`, await the same calls, for example
124
+ `detail = await api.vps[vps_id].show()` and
125
+ `page = await api.vps.list.call(params={"limit": 10}, meta={"count": True})`.
126
+
127
+ Each action returns a `Response`: `.data` holds the result and `.meta` holds
128
+ action metadata. Returned objects are read-only mappings; access fields with
129
+ expressions such as `vps["hostname"]`. Associations are not fetched automatically: call an
130
+ association's `.fetch()` explicitly, awaiting it with the async client.
131
+
132
+ Input is validated against the description. Omitting a parameter differs from
133
+ passing `None`; `None` is accepted only where advertised. GET has stricter shape
134
+ limits than JSON requests. See the [input encoding reference](docs/reference.md#input-encoding)
135
+ for those limits.
136
+
137
+ ## Pagination and operations
138
+
139
+ Normal list calls fetch one page. Use `action.iter_pages(...)` or
140
+ `action.iter_all(...)` for explicit iteration. Async clients return async
141
+ iterators, consumed with `async for`. Inside a connected synchronous client
142
+ context, supply a cursor strategy that matches the endpoint's ordering:
143
+
144
+ ```python
145
+ from haveapi_client import IdCursor
146
+
147
+ for vps in api.vps.list.iter_all(strategy=IdCursor(direction="asc"), page_size=100):
148
+ print(vps["id"])
149
+ ```
150
+
151
+ `IdCursor` requires integer IDs in the requested monotonic order and rejects a
152
+ repeated or reversed cursor. The endpoint must advertise `limit` and the cursor
153
+ parameter. HaveAPI pagination metadata alone does not promise this ordering;
154
+ select the strategy from the API's documented behavior.
155
+
156
+ When a response includes an operation, use `response.operation.show()`,
157
+ `.wait(timeout=300, interval=1)` or `.cancel()`. Await these methods on an async
158
+ operation. For an existing synchronous action result:
159
+
160
+ ```python
161
+ if result.operation is not None:
162
+ final_state = result.operation.wait(timeout=300, interval=1)
163
+ print(final_state.data)
164
+ ```
165
+
166
+ Waiting checks the described action-state resource and raises
167
+ `OperationFailedError` for failure or `OperationTimeoutError` at the deadline.
168
+ Timeout and coroutine cancellation do not cancel the server operation. Explicit
169
+ cancel requires advertised cancellation support.
170
+
171
+ See [operation deadlines](docs/reference.md#operation-deadlines) for the
172
+ difference between synchronous and asynchronous wait timeouts.
173
+
174
+ ## Errors and timeouts
175
+
176
+ Catch `HaveAPIError` for client errors, or its more specific `TransportError`,
177
+ `ProtocolError`, `AuthenticationError`, `ValidationError` and `ActionError`
178
+ subclasses. Errors provide status and structured details when available. Treat
179
+ response payloads and diagnostics as potentially private application data.
180
+
181
+ The default HTTP timeout is 30 seconds and can be overridden per request with
182
+ `RequestOptions(timeout=...)` through `.call(options=...)`. Requests are not
183
+ retried or redirected.
184
+
185
+ ## Reference and development
186
+
187
+ See the [runtime reference](docs/reference.md) for discovery, URL resolution,
188
+ encoding, response fields and connection ownership.
189
+
190
+ For development against a local HaveAPI checkout, run
191
+ `uv add /path/to/haveapi/clients/python` in your application. See
192
+ [CONTRIBUTING.md](CONTRIBUTING.md) for checks and packaging, and the shared
193
+ [release procedure](../../doc/agent-instructions/releases.md) for publication.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "haveapi-client"
3
- version = "0.30.2"
3
+ version = "0.31.1"
4
4
  description = "Dynamic synchronous and asynchronous clients for HaveAPI APIs"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -34,7 +34,7 @@ build-backend = "uv_build"
34
34
  dev = [
35
35
  "mypy>=2.4,<3",
36
36
  "pytest>=9.1,<10",
37
- "ruff==0.17.1",
37
+ "ruff==0.17.2",
38
38
  "trio>=0.34,<1",
39
39
  ]
40
40
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "haveapi-client"
3
- version = "0.30.2"
3
+ version = "0.31.1"
4
4
  description = "Dynamic synchronous and asynchronous clients for HaveAPI APIs"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -31,7 +31,7 @@ build-backend = "uv_build"
31
31
  dev = [
32
32
  "mypy>=2.4,<3",
33
33
  "pytest>=9.1,<10",
34
- "ruff==0.17.1",
34
+ "ruff==0.17.2",
35
35
  "trio>=0.34,<1",
36
36
  ]
37
37
 
@@ -1,198 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: haveapi-client
3
- Version: 0.30.2
4
- Summary: Dynamic synchronous and asynchronous clients for HaveAPI APIs
5
- License-Expression: MIT
6
- License-File: LICENSE.txt
7
- Classifier: Development Status :: 4 - Beta
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: Programming Language :: Python :: 3.11
10
- Classifier: Programming Language :: Python :: 3.12
11
- Classifier: Programming Language :: Python :: 3.13
12
- Classifier: Programming Language :: Python :: 3.14
13
- Classifier: Programming Language :: Python :: 3.15
14
- Classifier: Typing :: Typed
15
- Requires-Dist: httpx>=0.28.1,<1
16
- Requires-Dist: anyio>=4.15,<5
17
- Requires-Python: >=3.11
18
- Project-URL: Homepage, https://github.com/vpsfreecz/haveapi
19
- Project-URL: Source, https://github.com/vpsfreecz/haveapi/tree/master/clients/python
20
- Project-URL: Issues, https://github.com/vpsfreecz/haveapi/issues
21
- Description-Content-Type: text/markdown
22
-
23
- # HaveAPI Python client
24
-
25
- `haveapi-client` discovers resources, actions and parameter types from any
26
- HaveAPI API using protocol 2.x. It provides synchronous and asynchronous clients
27
- with the same dynamic interface. Python 3.11 or newer is required.
28
-
29
- [vpsAdmin](https://github.com/vpsfreecz/vpsadmin) is the control panel used by
30
- vpsFree.cz to manage virtual private servers (VPSes). It is HaveAPI's main user,
31
- so the examples in this README and the `examples/` directory use its API.
32
-
33
- Install the package with `uv add haveapi-client` or `pip install haveapi-client`
34
- once a Python release is available. To try a checkout before publication, run
35
- `uv add /path/to/haveapi/clients/python` in your application.
36
-
37
- ## Read a page of VPSes
38
-
39
- The examples use an existing vpsAdmin API token. Set `VPSADMIN_URL` to the API
40
- base URL and `VPSADMIN_TOKEN` to your token. Keep credentials out of source files.
41
-
42
- ```python
43
- import os
44
-
45
- from haveapi_client import Client, TokenAuth
46
-
47
- with Client(
48
- os.environ["VPSADMIN_URL"], auth=TokenAuth(os.environ["VPSADMIN_TOKEN"])
49
- ) as api:
50
- result = api.vps.list.call(params={"limit": 100}, meta={"count": True})
51
- for vps in result.data:
52
- print(vps["id"], vps["hostname"])
53
- print(result.meta.get("total_count"))
54
- ```
55
-
56
- Use `AsyncClient` with `async with` and await action calls for async applications.
57
- It works with asyncio and Trio. Complete runnable examples are in
58
- [`examples/list_vps.py`](examples/list_vps.py) and
59
- [`examples/list_vps_async.py`](examples/list_vps_async.py).
60
-
61
- ## Discovery and calls
62
-
63
- Constructing a client and inspecting resource or action properties sends no
64
- requests. Call `connect()` explicitly or enter the client context to fetch its
65
- description. An authenticated connection refreshes discovery with the selected
66
- identity, so available actions and parameters reflect server authorization.
67
- The server still authorizes every request.
68
-
69
- The base URL includes the API mount, for example `https://api.example/mount/`.
70
- Select the API version with the client's `version` argument. Discovery uses that
71
- mount and version; described action paths already contain their routing prefix
72
- and resolve from the origin. The client preserves the server's ordinary,
73
- authentication and action-state routes without repeating the mount or adding
74
- a version to a global authentication route.
75
-
76
- Action destinations must remain within the configured origin and mount before
77
- credentials are attached. Applications that fabricate or cache API-relative
78
- action descriptions for a mounted API must refresh them from the server's
79
- OPTIONS description. Root-mounted APIs keep the same URLs.
80
-
81
- `api.vps.list(limit=10)` is shorthand for
82
- `api.vps.list.call(params={"limit": 10})`. Use `.call()` to pass action metadata,
83
- explicit path parameters or per-request `RequestOptions`. Input values are
84
- validated and encoded from the description. Omitted parameters and explicit
85
- `None` have different meanings; `None` is accepted only where advertised.
86
-
87
- POST, PUT and DELETE send JSON input and metadata. The Ruby server requires
88
- the JSON root to be an object: list input needs a described namespace around
89
- it. An unnamespaced list body is rejected locally. JSON retains empty
90
- containers, sparse object rows, and finite numbers, booleans and nulls in
91
- Custom values.
92
-
93
- GET uses Rack bracket-query encoding. It accepts only shapes that can be
94
- represented without changing their meaning:
95
-
96
- - Described scalar values use their normal type conversion, including the
97
- empty query value for a permitted nullable scalar.
98
- - Nonempty mappings can nest. Every key component, including described
99
- namespaces and parameter names, must be nonempty and contain no `[` or `]`.
100
- - Nonempty arrays of strings retain their order. Arrays of flat objects must
101
- have the same nonempty key set in every row, with no container-valued fields.
102
- The client emits each row's keys in the same order. Described row fields use
103
- their declared types; Custom row fields must contain strings.
104
- - Nested Custom values may contain strings, mappings and these permitted
105
- arrays. Nested numbers, booleans and nulls are rejected because a query
106
- cannot preserve their JSON types. A nullable top-level Custom value follows
107
- the normal nullable-scalar rule.
108
- - Sparse rows, empty rows, nested arrays, explicit empty lists or mappings,
109
- and object arrays with container-valued fields are rejected. Omitted input
110
- remains omitted; structural input or metadata wrappers with no supplied
111
- fields do not require a query pair.
112
-
113
- The same GET restrictions apply to metadata. Invalid shapes raise
114
- `ValidationError` before an action request is sent. The client follows the
115
- action's described method; it does not replace GET with JSON. Only the literal
116
- `Custom` parameter type accepts arbitrary finite JSON values. Unknown parameter
117
- type names are rejected rather than treated as Custom.
118
-
119
- For names that collide with Python methods or keywords, use exact-name maps:
120
- `api.resources["vps"].actions["index"]`. Indexing a resource binds one path
121
- argument, such as `api.vps[vps_id].show()`. Follow the discovered hierarchy for
122
- nested resources: `api.parent[parent_id].child.list()` applies when that API
123
- describes a child resource under its parent. In vpsAdmin, network interfaces
124
- are top-level: use `api.network_interface.list(vps=vps_id)`.
125
-
126
- An action returns `Response`: `.data` is a finite result, `.meta` contains action
127
- metadata, and `.status_code`, `.headers` and `.envelope` expose response context.
128
- Returned objects are read-only mappings with optional attribute access to
129
- fields. Mapping access remains reliable when a field name collides with a method.
130
- Association handles can be inspected without fetching them; call `.fetch()`
131
- explicitly to retrieve an association. Refreshing discovery or changing
132
- authentication invalidates previously obtained resource and object handles.
133
-
134
- ## Authentication
135
-
136
- Use `BasicAuth(username, password)`, `TokenAuth(token)` or
137
- `OAuth2Auth(access_token)` for an existing credential. OAuth2Auth sends a bearer
138
- token; browser authorization and token refresh are outside this package.
139
-
140
- For password-based token login, call
141
- `api.begin_token_login({"user": username, "password": password, ...})` with
142
- fields accepted by that API. The result is `Authenticated` or an
143
- `AuthenticationChallenge`. Read a challenge's `.next_action` and `.input`, then
144
- call `.submit({...})` with the required fields. Continue until authenticated;
145
- this supports API-specific chains such as TOTP followed by a password reset.
146
- `AsyncClient` returns `AsyncAuthenticationChallenge`, whose submit is awaited.
147
- The continuation token is kept by the challenge and cannot be overridden.
148
-
149
- `renew_token()` and `revoke_token()` use the provider's advertised actions.
150
- `logout()` clears local authentication and rediscovers the public API. Closing a
151
- client does not revoke its server token. Credentials are kept in memory; the
152
- package does not write a credential cache.
153
-
154
- ## Pagination and operations
155
-
156
- Normal action calls fetch one page. Use `action.iter_pages(...)` or
157
- `action.iter_all(...)` for explicit iteration. Async clients return async
158
- iterators. Supply a cursor strategy that matches the endpoint's ordering:
159
-
160
- ```python
161
- from haveapi_client import IdCursor
162
-
163
- for vps in api.vps.list.iter_all(strategy=IdCursor(direction="asc"), page_size=100):
164
- print(vps["id"])
165
- ```
166
-
167
- `IdCursor` requires integer IDs in the requested monotonic order and rejects a
168
- repeated or reversed cursor. The endpoint must advertise `limit` and the cursor
169
- parameter. HaveAPI pagination metadata alone does not promise this ordering;
170
- select the strategy from the API's documented behavior.
171
-
172
- When a response includes an operation, use `response.operation.show()`,
173
- `.wait(timeout=300, interval=1)` or `.cancel()`. Await these methods on an async
174
- operation. Waiting checks the described action-state resource and raises
175
- `OperationFailedError` for failure or `OperationTimeoutError` at the deadline.
176
- Timeout and coroutine cancellation do not cancel the server operation. Explicit
177
- cancel requires advertised cancellation support.
178
-
179
- Async waits enforce the overall deadline. A synchronous wait checks its budget
180
- between requests and bounds each HTTP phase by the remaining time; an in-flight
181
- synchronous request can finish after the overall deadline.
182
-
183
- ## Errors and connection ownership
184
-
185
- Catch `HaveAPIError` for client errors, or its more specific `TransportError`,
186
- `ProtocolError`, `AuthenticationError`, `ValidationError` and `ActionError`
187
- subclasses. Errors provide status and structured details when available. Treat
188
- response payloads and diagnostics as potentially private application data.
189
-
190
- The default HTTP timeout is 30 seconds and can be overridden per request with
191
- `RequestOptions(timeout=...)`. Requests are not retried or redirected. Clients
192
- use independent request headers and bodies; authentication changes are rejected
193
- while requests are active. Injected HTTPX clients remain caller-owned and must
194
- be closed by the caller. Their default authentication, headers and cookies are
195
- not inherited by HaveAPI requests.
196
-
197
- See [CONTRIBUTING.md](CONTRIBUTING.md) for checks and packaging, and the shared
198
- [release procedure](../../doc/agent-instructions/releases.md) for publication.
@@ -1,176 +0,0 @@
1
- # HaveAPI Python client
2
-
3
- `haveapi-client` discovers resources, actions and parameter types from any
4
- HaveAPI API using protocol 2.x. It provides synchronous and asynchronous clients
5
- with the same dynamic interface. Python 3.11 or newer is required.
6
-
7
- [vpsAdmin](https://github.com/vpsfreecz/vpsadmin) is the control panel used by
8
- vpsFree.cz to manage virtual private servers (VPSes). It is HaveAPI's main user,
9
- so the examples in this README and the `examples/` directory use its API.
10
-
11
- Install the package with `uv add haveapi-client` or `pip install haveapi-client`
12
- once a Python release is available. To try a checkout before publication, run
13
- `uv add /path/to/haveapi/clients/python` in your application.
14
-
15
- ## Read a page of VPSes
16
-
17
- The examples use an existing vpsAdmin API token. Set `VPSADMIN_URL` to the API
18
- base URL and `VPSADMIN_TOKEN` to your token. Keep credentials out of source files.
19
-
20
- ```python
21
- import os
22
-
23
- from haveapi_client import Client, TokenAuth
24
-
25
- with Client(
26
- os.environ["VPSADMIN_URL"], auth=TokenAuth(os.environ["VPSADMIN_TOKEN"])
27
- ) as api:
28
- result = api.vps.list.call(params={"limit": 100}, meta={"count": True})
29
- for vps in result.data:
30
- print(vps["id"], vps["hostname"])
31
- print(result.meta.get("total_count"))
32
- ```
33
-
34
- Use `AsyncClient` with `async with` and await action calls for async applications.
35
- It works with asyncio and Trio. Complete runnable examples are in
36
- [`examples/list_vps.py`](examples/list_vps.py) and
37
- [`examples/list_vps_async.py`](examples/list_vps_async.py).
38
-
39
- ## Discovery and calls
40
-
41
- Constructing a client and inspecting resource or action properties sends no
42
- requests. Call `connect()` explicitly or enter the client context to fetch its
43
- description. An authenticated connection refreshes discovery with the selected
44
- identity, so available actions and parameters reflect server authorization.
45
- The server still authorizes every request.
46
-
47
- The base URL includes the API mount, for example `https://api.example/mount/`.
48
- Select the API version with the client's `version` argument. Discovery uses that
49
- mount and version; described action paths already contain their routing prefix
50
- and resolve from the origin. The client preserves the server's ordinary,
51
- authentication and action-state routes without repeating the mount or adding
52
- a version to a global authentication route.
53
-
54
- Action destinations must remain within the configured origin and mount before
55
- credentials are attached. Applications that fabricate or cache API-relative
56
- action descriptions for a mounted API must refresh them from the server's
57
- OPTIONS description. Root-mounted APIs keep the same URLs.
58
-
59
- `api.vps.list(limit=10)` is shorthand for
60
- `api.vps.list.call(params={"limit": 10})`. Use `.call()` to pass action metadata,
61
- explicit path parameters or per-request `RequestOptions`. Input values are
62
- validated and encoded from the description. Omitted parameters and explicit
63
- `None` have different meanings; `None` is accepted only where advertised.
64
-
65
- POST, PUT and DELETE send JSON input and metadata. The Ruby server requires
66
- the JSON root to be an object: list input needs a described namespace around
67
- it. An unnamespaced list body is rejected locally. JSON retains empty
68
- containers, sparse object rows, and finite numbers, booleans and nulls in
69
- Custom values.
70
-
71
- GET uses Rack bracket-query encoding. It accepts only shapes that can be
72
- represented without changing their meaning:
73
-
74
- - Described scalar values use their normal type conversion, including the
75
- empty query value for a permitted nullable scalar.
76
- - Nonempty mappings can nest. Every key component, including described
77
- namespaces and parameter names, must be nonempty and contain no `[` or `]`.
78
- - Nonempty arrays of strings retain their order. Arrays of flat objects must
79
- have the same nonempty key set in every row, with no container-valued fields.
80
- The client emits each row's keys in the same order. Described row fields use
81
- their declared types; Custom row fields must contain strings.
82
- - Nested Custom values may contain strings, mappings and these permitted
83
- arrays. Nested numbers, booleans and nulls are rejected because a query
84
- cannot preserve their JSON types. A nullable top-level Custom value follows
85
- the normal nullable-scalar rule.
86
- - Sparse rows, empty rows, nested arrays, explicit empty lists or mappings,
87
- and object arrays with container-valued fields are rejected. Omitted input
88
- remains omitted; structural input or metadata wrappers with no supplied
89
- fields do not require a query pair.
90
-
91
- The same GET restrictions apply to metadata. Invalid shapes raise
92
- `ValidationError` before an action request is sent. The client follows the
93
- action's described method; it does not replace GET with JSON. Only the literal
94
- `Custom` parameter type accepts arbitrary finite JSON values. Unknown parameter
95
- type names are rejected rather than treated as Custom.
96
-
97
- For names that collide with Python methods or keywords, use exact-name maps:
98
- `api.resources["vps"].actions["index"]`. Indexing a resource binds one path
99
- argument, such as `api.vps[vps_id].show()`. Follow the discovered hierarchy for
100
- nested resources: `api.parent[parent_id].child.list()` applies when that API
101
- describes a child resource under its parent. In vpsAdmin, network interfaces
102
- are top-level: use `api.network_interface.list(vps=vps_id)`.
103
-
104
- An action returns `Response`: `.data` is a finite result, `.meta` contains action
105
- metadata, and `.status_code`, `.headers` and `.envelope` expose response context.
106
- Returned objects are read-only mappings with optional attribute access to
107
- fields. Mapping access remains reliable when a field name collides with a method.
108
- Association handles can be inspected without fetching them; call `.fetch()`
109
- explicitly to retrieve an association. Refreshing discovery or changing
110
- authentication invalidates previously obtained resource and object handles.
111
-
112
- ## Authentication
113
-
114
- Use `BasicAuth(username, password)`, `TokenAuth(token)` or
115
- `OAuth2Auth(access_token)` for an existing credential. OAuth2Auth sends a bearer
116
- token; browser authorization and token refresh are outside this package.
117
-
118
- For password-based token login, call
119
- `api.begin_token_login({"user": username, "password": password, ...})` with
120
- fields accepted by that API. The result is `Authenticated` or an
121
- `AuthenticationChallenge`. Read a challenge's `.next_action` and `.input`, then
122
- call `.submit({...})` with the required fields. Continue until authenticated;
123
- this supports API-specific chains such as TOTP followed by a password reset.
124
- `AsyncClient` returns `AsyncAuthenticationChallenge`, whose submit is awaited.
125
- The continuation token is kept by the challenge and cannot be overridden.
126
-
127
- `renew_token()` and `revoke_token()` use the provider's advertised actions.
128
- `logout()` clears local authentication and rediscovers the public API. Closing a
129
- client does not revoke its server token. Credentials are kept in memory; the
130
- package does not write a credential cache.
131
-
132
- ## Pagination and operations
133
-
134
- Normal action calls fetch one page. Use `action.iter_pages(...)` or
135
- `action.iter_all(...)` for explicit iteration. Async clients return async
136
- iterators. Supply a cursor strategy that matches the endpoint's ordering:
137
-
138
- ```python
139
- from haveapi_client import IdCursor
140
-
141
- for vps in api.vps.list.iter_all(strategy=IdCursor(direction="asc"), page_size=100):
142
- print(vps["id"])
143
- ```
144
-
145
- `IdCursor` requires integer IDs in the requested monotonic order and rejects a
146
- repeated or reversed cursor. The endpoint must advertise `limit` and the cursor
147
- parameter. HaveAPI pagination metadata alone does not promise this ordering;
148
- select the strategy from the API's documented behavior.
149
-
150
- When a response includes an operation, use `response.operation.show()`,
151
- `.wait(timeout=300, interval=1)` or `.cancel()`. Await these methods on an async
152
- operation. Waiting checks the described action-state resource and raises
153
- `OperationFailedError` for failure or `OperationTimeoutError` at the deadline.
154
- Timeout and coroutine cancellation do not cancel the server operation. Explicit
155
- cancel requires advertised cancellation support.
156
-
157
- Async waits enforce the overall deadline. A synchronous wait checks its budget
158
- between requests and bounds each HTTP phase by the remaining time; an in-flight
159
- synchronous request can finish after the overall deadline.
160
-
161
- ## Errors and connection ownership
162
-
163
- Catch `HaveAPIError` for client errors, or its more specific `TransportError`,
164
- `ProtocolError`, `AuthenticationError`, `ValidationError` and `ActionError`
165
- subclasses. Errors provide status and structured details when available. Treat
166
- response payloads and diagnostics as potentially private application data.
167
-
168
- The default HTTP timeout is 30 seconds and can be overridden per request with
169
- `RequestOptions(timeout=...)`. Requests are not retried or redirected. Clients
170
- use independent request headers and bodies; authentication changes are rejected
171
- while requests are active. Injected HTTPX clients remain caller-owned and must
172
- be closed by the caller. Their default authentication, headers and cookies are
173
- not inherited by HaveAPI requests.
174
-
175
- See [CONTRIBUTING.md](CONTRIBUTING.md) for checks and packaging, and the shared
176
- [release procedure](../../doc/agent-instructions/releases.md) for publication.