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.
- haveapi_client-0.31.1/PKG-INFO +215 -0
- haveapi_client-0.31.1/README.md +193 -0
- {haveapi_client-0.30.2 → haveapi_client-0.31.1}/pyproject.toml +2 -2
- {haveapi_client-0.30.2 → haveapi_client-0.31.1}/pyproject.toml.orig +2 -2
- haveapi_client-0.30.2/PKG-INFO +0 -198
- haveapi_client-0.30.2/README.md +0 -176
- {haveapi_client-0.30.2 → haveapi_client-0.31.1}/LICENSE.txt +0 -0
- {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/__init__.py +0 -0
- {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/_auth/__init__.py +0 -0
- {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/_protocol/__init__.py +0 -0
- {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/_protocol/description.py +0 -0
- {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/_protocol/execution.py +0 -0
- {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/_protocol/input.py +0 -0
- {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/_protocol/models.py +0 -0
- {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/_protocol/request.py +0 -0
- {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/_protocol/response.py +0 -0
- {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/_resources/__init__.py +0 -0
- {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/_resources/state.py +0 -0
- {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/_transport/__init__.py +0 -0
- {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/async_client.py +0 -0
- {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/client.py +0 -0
- {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/exceptions.py +0 -0
- {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/operations.py +0 -0
- {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/pagination.py +0 -0
- {haveapi_client-0.30.2 → haveapi_client-0.31.1}/src/haveapi_client/py.typed +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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.
|
|
34
|
+
"ruff==0.17.2",
|
|
35
35
|
"trio>=0.34,<1",
|
|
36
36
|
]
|
|
37
37
|
|
haveapi_client-0.30.2/PKG-INFO
DELETED
|
@@ -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.
|
haveapi_client-0.30.2/README.md
DELETED
|
@@ -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.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|