haveapi-client 0.30.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 (24) hide show
  1. haveapi_client-0.30.0/LICENSE.txt +21 -0
  2. haveapi_client-0.30.0/PKG-INFO +198 -0
  3. haveapi_client-0.30.0/README.md +176 -0
  4. haveapi_client-0.30.0/pyproject.toml +65 -0
  5. haveapi_client-0.30.0/pyproject.toml.orig +56 -0
  6. haveapi_client-0.30.0/src/haveapi_client/__init__.py +62 -0
  7. haveapi_client-0.30.0/src/haveapi_client/_auth/__init__.py +139 -0
  8. haveapi_client-0.30.0/src/haveapi_client/_protocol/__init__.py +52 -0
  9. haveapi_client-0.30.0/src/haveapi_client/_protocol/description.py +176 -0
  10. haveapi_client-0.30.0/src/haveapi_client/_protocol/execution.py +61 -0
  11. haveapi_client-0.30.0/src/haveapi_client/_protocol/input.py +161 -0
  12. haveapi_client-0.30.0/src/haveapi_client/_protocol/models.py +166 -0
  13. haveapi_client-0.30.0/src/haveapi_client/_protocol/request.py +345 -0
  14. haveapi_client-0.30.0/src/haveapi_client/_protocol/response.py +97 -0
  15. haveapi_client-0.30.0/src/haveapi_client/_resources/__init__.py +196 -0
  16. haveapi_client-0.30.0/src/haveapi_client/_resources/state.py +266 -0
  17. haveapi_client-0.30.0/src/haveapi_client/_transport/__init__.py +155 -0
  18. haveapi_client-0.30.0/src/haveapi_client/async_client.py +209 -0
  19. haveapi_client-0.30.0/src/haveapi_client/client.py +203 -0
  20. haveapi_client-0.30.0/src/haveapi_client/exceptions.py +88 -0
  21. haveapi_client-0.30.0/src/haveapi_client/operations.py +190 -0
  22. haveapi_client-0.30.0/src/haveapi_client/pagination.py +202 -0
  23. haveapi_client-0.30.0/src/haveapi_client/py.typed +0 -0
  24. haveapi_client-0.30.0/src/haveapi_client/response.py +303 -0
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Jakub Skokan
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
13
+ all 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
21
+ THE SOFTWARE.
@@ -0,0 +1,198 @@
1
+ Metadata-Version: 2.4
2
+ Name: haveapi-client
3
+ Version: 0.30.0
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.
@@ -0,0 +1,176 @@
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.
@@ -0,0 +1,65 @@
1
+ [project]
2
+ name = "haveapi-client"
3
+ version = "0.30.0"
4
+ description = "Dynamic synchronous and asynchronous clients for HaveAPI APIs"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ license = "MIT"
8
+ license-files = ["LICENSE.txt"]
9
+ dependencies = [
10
+ "httpx>=0.28.1,<1",
11
+ "anyio>=4.15,<5",
12
+ ]
13
+ classifiers = [
14
+ "Development Status :: 4 - Beta",
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.11",
17
+ "Programming Language :: Python :: 3.12",
18
+ "Programming Language :: Python :: 3.13",
19
+ "Programming Language :: Python :: 3.14",
20
+ "Programming Language :: Python :: 3.15",
21
+ "Typing :: Typed",
22
+ ]
23
+
24
+ [project.urls]
25
+ Homepage = "https://github.com/vpsfreecz/haveapi"
26
+ Source = "https://github.com/vpsfreecz/haveapi/tree/master/clients/python"
27
+ Issues = "https://github.com/vpsfreecz/haveapi/issues"
28
+
29
+ [build-system]
30
+ requires = ["uv_build>=0.13.0,<0.14"]
31
+ build-backend = "uv_build"
32
+
33
+ [dependency-groups]
34
+ dev = [
35
+ "mypy>=2.4,<3",
36
+ "pytest>=9.1,<10",
37
+ "ruff==0.17.1",
38
+ "trio>=0.34,<1",
39
+ ]
40
+
41
+ [tool.ruff]
42
+ target-version = "py311"
43
+ line-length = 88
44
+
45
+ [tool.ruff.lint]
46
+ select = [
47
+ "E",
48
+ "F",
49
+ "I",
50
+ "UP",
51
+ "B",
52
+ ]
53
+
54
+ [tool.ruff.lint.isort]
55
+ known-first-party = ["haveapi_client"]
56
+
57
+ [tool.mypy]
58
+ python_version = "3.11"
59
+ strict = true
60
+ files = ["src"]
61
+
62
+ [tool.pytest.ini_options]
63
+ testpaths = ["tests/unit"]
64
+ addopts = "--strict-markers"
65
+ markers = ["integration: starts a local real Ruby HaveAPI server"]
@@ -0,0 +1,56 @@
1
+ [project]
2
+ name = "haveapi-client"
3
+ version = "0.30.0"
4
+ description = "Dynamic synchronous and asynchronous clients for HaveAPI APIs"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ license = "MIT"
8
+ license-files = ["LICENSE.txt"]
9
+ dependencies = ["httpx>=0.28.1,<1", "anyio>=4.15,<5"]
10
+ classifiers = [
11
+ "Development Status :: 4 - Beta",
12
+ "Programming Language :: Python :: 3",
13
+ "Programming Language :: Python :: 3.11",
14
+ "Programming Language :: Python :: 3.12",
15
+ "Programming Language :: Python :: 3.13",
16
+ "Programming Language :: Python :: 3.14",
17
+ "Programming Language :: Python :: 3.15",
18
+ "Typing :: Typed",
19
+ ]
20
+
21
+ [project.urls]
22
+ Homepage = "https://github.com/vpsfreecz/haveapi"
23
+ Source = "https://github.com/vpsfreecz/haveapi/tree/master/clients/python"
24
+ Issues = "https://github.com/vpsfreecz/haveapi/issues"
25
+
26
+ [build-system]
27
+ requires = ["uv_build>=0.13.0,<0.14"]
28
+ build-backend = "uv_build"
29
+
30
+ [dependency-groups]
31
+ dev = [
32
+ "mypy>=2.4,<3",
33
+ "pytest>=9.1,<10",
34
+ "ruff==0.17.1",
35
+ "trio>=0.34,<1",
36
+ ]
37
+
38
+ [tool.ruff]
39
+ target-version = "py311"
40
+ line-length = 88
41
+
42
+ [tool.ruff.lint]
43
+ select = ["E", "F", "I", "UP", "B"]
44
+
45
+ [tool.ruff.lint.isort]
46
+ known-first-party = ["haveapi_client"]
47
+
48
+ [tool.mypy]
49
+ python_version = "3.11"
50
+ strict = true
51
+ files = ["src"]
52
+
53
+ [tool.pytest.ini_options]
54
+ testpaths = ["tests/unit"]
55
+ addopts = "--strict-markers"
56
+ markers = ["integration: starts a local real Ruby HaveAPI server"]
@@ -0,0 +1,62 @@
1
+ """Dynamic clients for self-describing HaveAPI APIs."""
2
+
3
+ from importlib.metadata import version
4
+
5
+ from ._auth import (
6
+ AsyncAuthenticationChallenge,
7
+ Authenticated,
8
+ AuthenticationChallenge,
9
+ BasicAuth,
10
+ OAuth2Auth,
11
+ TokenAuth,
12
+ )
13
+ from ._protocol import RequestOptions
14
+ from .async_client import AsyncClient
15
+ from .client import Client
16
+ from .exceptions import (
17
+ ActionError,
18
+ AuthenticationError,
19
+ HaveAPIError,
20
+ NotConnectedError,
21
+ OperationFailedError,
22
+ OperationTimeoutError,
23
+ PaginationError,
24
+ ProtocolError,
25
+ StaleDescriptionError,
26
+ TransportError,
27
+ ValidationError,
28
+ )
29
+ from .operations import AsyncOperation, Operation
30
+ from .pagination import IdCursor
31
+ from .response import ResourceObject, Response
32
+
33
+ __version__ = version("haveapi-client")
34
+
35
+ __all__ = [
36
+ "ActionError",
37
+ "AsyncAuthenticationChallenge",
38
+ "AsyncClient",
39
+ "AsyncOperation",
40
+ "Authenticated",
41
+ "AuthenticationChallenge",
42
+ "AuthenticationError",
43
+ "BasicAuth",
44
+ "Client",
45
+ "HaveAPIError",
46
+ "IdCursor",
47
+ "NotConnectedError",
48
+ "OAuth2Auth",
49
+ "Operation",
50
+ "OperationFailedError",
51
+ "OperationTimeoutError",
52
+ "PaginationError",
53
+ "ProtocolError",
54
+ "RequestOptions",
55
+ "ResourceObject",
56
+ "Response",
57
+ "StaleDescriptionError",
58
+ "TokenAuth",
59
+ "TransportError",
60
+ "ValidationError",
61
+ "__version__",
62
+ ]