hyze-cloud 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,263 @@
1
+ Metadata-Version: 2.5
2
+ Name: hyze-cloud
3
+ Version: 0.1.0
4
+ Summary: Official Python SDK for the Hyze Cloud API
5
+ Project-URL: Homepage, https://hyzecloud.com
6
+ Project-URL: Documentation, https://docs.hyzecloud.app
7
+ Project-URL: Repository, https://github.com/Hyze-Cloud/hyzecloud-sdk-python
8
+ Project-URL: Issues, https://github.com/Hyze-Cloud/hyzecloud-sdk-python/issues
9
+ Project-URL: Changelog, https://github.com/Hyze-Cloud/hyzecloud-sdk-python/releases
10
+ Author: Hyze Cloud
11
+ License-File: LICENSE
12
+ Keywords: api,deploy,hyze,hyzecloud,paas,python,sdk
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: httpx<1,>=0.27
25
+ Provides-Extra: dev
26
+ Requires-Dist: mypy>=1.11; extra == 'dev'
27
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
28
+ Requires-Dist: pytest>=8.0; extra == 'dev'
29
+ Requires-Dist: ruff>=0.6; extra == 'dev'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # hyze-cloud
33
+
34
+ [![CI](https://github.com/Hyze-Cloud/hyzecloud-sdk-python/actions/workflows/ci.yml/badge.svg)](https://github.com/Hyze-Cloud/hyzecloud-sdk-python/actions/workflows/ci.yml)
35
+ [![PyPI](https://img.shields.io/pypi/v/hyze-cloud)](https://pypi.org/project/hyze-cloud/)
36
+ [![Python versions](https://img.shields.io/pypi/pyversions/hyze-cloud)](https://pypi.org/project/hyze-cloud/)
37
+ [![license](https://img.shields.io/badge/license-MIT-blue)](./LICENSE)
38
+
39
+ Official **Python** SDK for the [Hyze Cloud API](https://docs.hyzecloud.app).
40
+
41
+ - **Sync and async** clients with the same surface — `client.apps.list()` or `await`
42
+ - Typed helpers for apps, databases, API keys, invoices, GitHub and plans
43
+ - One dependency (`httpx`), and it is the only one
44
+ - Consistent `HyzeError` carrying status, code and `Retry-After`
45
+ - Ships `py.typed`, so editors and type checkers see the shapes
46
+
47
+ This is a port of the TypeScript SDK ([`@hyze-cloud/sdk`](https://github.com/Hyze-Cloud/hyzecloud-sdk-ts)):
48
+ same resources, same routes, same error shape. The one naming difference is deliberate — Python
49
+ callers use `snake_case` (`memory_mb`) and the SDK translates it to the API's `camelCase`
50
+ (`memoryMB`).
51
+
52
+ ## Install
53
+
54
+ ```bash
55
+ pip install hyze-cloud
56
+ # or
57
+ uv add hyze-cloud
58
+ ```
59
+
60
+ Requires **Python 3.10+**.
61
+
62
+ ## Quickstart
63
+
64
+ ```python
65
+ from hyzecloud import HyzeCloud, HyzeError
66
+
67
+ client = HyzeCloud(
68
+ # api_key="hyze_...", # defaults to $HYZE_API_KEY
69
+ # base_url="https://api.hyzecloud.com/api", # the default
70
+ # workspace_id="org_...", # optional scope
71
+ )
72
+
73
+ for app in client.apps.list()["apps"]:
74
+ print(app["name"], app["status"])
75
+
76
+ try:
77
+ client.apps.restart("app_001")
78
+ except HyzeError as err:
79
+ print(err.status, err.code, err.message)
80
+ if err.is_rate_limited:
81
+ print("retry after", err.retry_after_seconds)
82
+ raise
83
+ ```
84
+
85
+ Close the client when you are done, or use it as a context manager:
86
+
87
+ ```python
88
+ with HyzeCloud() as client:
89
+ client.plans.current()
90
+ ```
91
+
92
+ ## Async
93
+
94
+ The same calls, awaited. `AsyncHyzeCloud` is the client to reach for inside FastAPI, aiohttp or
95
+ any asyncio program:
96
+
97
+ ```python
98
+ import asyncio
99
+ from hyzecloud import AsyncHyzeCloud
100
+
101
+
102
+ async def main() -> None:
103
+ async with AsyncHyzeCloud() as client:
104
+ apps = await client.apps.list()
105
+ print(len(apps["apps"]))
106
+
107
+
108
+ asyncio.run(main())
109
+ ```
110
+
111
+ ## Apps
112
+
113
+ ```python
114
+ # List / get
115
+ apps = client.apps.list()["apps"]
116
+ detail = client.apps.get("app_001")["container"]
117
+
118
+ # Lifecycle
119
+ client.apps.start("app_001")
120
+ client.apps.stop("app_001")
121
+ client.apps.restart("app_001")
122
+
123
+ # Logs, env, deploy history
124
+ client.apps.logs("app_001", tail=200, timestamps=True)
125
+ client.apps.get_env("app_001")
126
+ client.apps.set_env("app_001", {"NODE_ENV": "production"})
127
+ client.apps.deployments("app_001", limit=30)
128
+
129
+ # Deploy from a ZIP — a path, raw bytes, or an open file all work.
130
+ # A path is read into memory; pass an open file for a large archive (it gets streamed).
131
+ client.apps.deploy_from_zip(
132
+ file="./app.zip",
133
+ name="my-api",
134
+ runtime="python",
135
+ memory_mb=512,
136
+ expose_port=8000,
137
+ subdomain="my-api",
138
+ # startup_command omitted (or "auto") -> Hyze detects the start command
139
+ )
140
+
141
+ # Detect the env vars of a ZIP without deploying it
142
+ client.apps.inspect_env("./app.zip")
143
+
144
+ # Deploy from a connected GitHub repository
145
+ client.apps.deploy_from_repo(
146
+ name="my-api",
147
+ runtime="python",
148
+ memory_mb=512,
149
+ repository={"id": 123, "owner": "acme", "name": "api", "branch": "main"},
150
+ )
151
+
152
+ # Backups
153
+ client.apps.create_backup("app_001")
154
+ client.apps.list_backups("app_001")
155
+ client.apps.restore_backup("app_001", "backup_001")
156
+ ```
157
+
158
+ ## Databases
159
+
160
+ ```python
161
+ client.databases.create(name="prod-postgres", engine="postgresql", memory_mb=1024, storage_gb=20)
162
+ client.databases.list()
163
+ client.databases.stats("db_001")
164
+ client.databases.rotate_password("db_001")
165
+ client.databases.create_backup("db_001")
166
+ client.databases.restore("db_001", "backup_001")
167
+ ```
168
+
169
+ ## API keys, invoices, GitHub, plans
170
+
171
+ ```python
172
+ keys = client.api_keys.list()["keys"]
173
+ created = client.api_keys.create(name="ci")
174
+ # created["key"]["key"] is the one-time secret — it is never returned again
175
+
176
+ invoices = client.invoices.list()["invoices"]
177
+ pix = client.invoices.create_pix(plan_id="pro", interval="month")
178
+ # pix["invoice"]["brCode"] / ["brCodeBase64"] for the checkout screen
179
+ client.invoices.status(pix["invoice"]["id"], include_pix=True)
180
+
181
+ client.github.status()
182
+ client.github.repos()
183
+ client.github.branches("acme", "api")
184
+ client.github.detect_runtime("acme", "api", branch="main")
185
+
186
+ current = client.plans.current() # plan + usage, no nested apps/databases lists
187
+ client.plans.list()
188
+ ```
189
+
190
+ ## Error handling
191
+
192
+ Every failure raises `HyzeError`, including a 2xx response whose body is shaped like an error.
193
+
194
+ | Attribute | Meaning |
195
+ | --- | --- |
196
+ | `status` | HTTP status (401 when a 2xx body looked like an error) |
197
+ | `code` | Structured code from the payload, when the API sent one |
198
+ | `message` | Human-readable message |
199
+ | `body` | The decoded payload, untouched |
200
+ | `retry_after_seconds` | From the `Retry-After` header, when present |
201
+
202
+ `is_rate_limited` (`429`), `is_unauthorized` (`401`/`403`) and `is_not_found` (`404`) are there so
203
+ callers do not have to compare numbers by hand.
204
+
205
+ ## Low-level access
206
+
207
+ ```python
208
+ client.get("/apps/")
209
+ client.post("/apps/app_001/restart")
210
+ client.request("GET", "/apps/", query={"workspaceId": "org_1"})
211
+ ```
212
+
213
+ ## Environment
214
+
215
+ | Variable | Description |
216
+ | --- | --- |
217
+ | `HYZE_API_KEY` | Default API key when `api_key` is omitted |
218
+ | `HYZE_API_URL` | Override the base URL (default `https://api.hyzecloud.com/api`) |
219
+
220
+ ## Development
221
+
222
+ ```bash
223
+ uv venv && uv pip install -e ".[dev]"
224
+ .venv/bin/python -m pytest
225
+ .venv/bin/python -m mypy
226
+ .venv/bin/python -m ruff check .
227
+ .venv/bin/python scripts/check_api_routes.py # are the routes this SDK calls still there?
228
+ ```
229
+
230
+ `check_api_routes.py` hits the production API **unauthenticated**: a 404 means the route is gone,
231
+ any other status (401/400/…) means it exists. It runs on every PR and before publishing — a
232
+ published client calling a dead route is a 404 for the user.
233
+
234
+ ### Live smoke test
235
+
236
+ Exercises the read paths against the real API with your key:
237
+
238
+ ```bash
239
+ HYZE_API_KEY=hyze_xxx .venv/bin/python scripts/live_smoke.py
240
+ ```
241
+
242
+ ## Release
243
+
244
+ The tag drives the version: `vX.Y.Z` must match `pyproject.toml`, otherwise the workflow aborts
245
+ before publishing.
246
+
247
+ ```bash
248
+ # bump the version, then
249
+ git tag v0.1.1 && git push origin v0.1.1
250
+ ```
251
+
252
+ `release.yml` runs ruff + mypy + pytest + `check_api_routes.py`, builds the sdist and the wheel,
253
+ and publishes to PyPI through **Trusted Publishing** (OIDC) — there is no API token stored anywhere.
254
+
255
+ ## Docs
256
+
257
+ - [Hyze Cloud Docs](https://docs.hyzecloud.app)
258
+ - [API introduction](https://docs.hyzecloud.app/api-reference/introduction)
259
+ - [Rate limits](https://docs.hyzecloud.app/en/concepts/rate-limits)
260
+
261
+ ## License
262
+
263
+ MIT
@@ -0,0 +1,17 @@
1
+ hyzecloud/__init__.py,sha256=IYhNXJc_qk9Eul_E5AM7U7je_p-hlpTIR5thKfEnuW0,3465
2
+ hyzecloud/_url.py,sha256=kRXIvKbHDHAiHk8aWXHe0Bw-U_oPBxn6cbrxjdR-5Fs,828
3
+ hyzecloud/client.py,sha256=-DAFoJe-UzHaVbDPr9_smx-b2W8FbikIc4a0TBJgspc,13936
4
+ hyzecloud/errors.py,sha256=QKZIqDMeGZfuWi5oGO-ll343Bvd5Fb2v1LchM53llCM,3466
5
+ hyzecloud/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ hyzecloud/types.py,sha256=kNKV7QJkrtQJnTPpThk-6Tinq8j86g7wKVACQi-hdCk,10437
7
+ hyzecloud/resources/__init__.py,sha256=-2gJMZE_oxaoLSu3u9vBWlkhJgv_aqjg_k2QAHPC0xw,665
8
+ hyzecloud/resources/api_keys.py,sha256=ef_vOIT4SoAkjUaB1mXl88NaK8QiCqp25Hiaz0EiN38,2095
9
+ hyzecloud/resources/apps.py,sha256=TBj0bgXYiTscoEVrAQ5BepOFP8JuGwhVwZxzXjl2Ih0,9096
10
+ hyzecloud/resources/databases.py,sha256=51cA6ImkIjFX8s7pVXs_X1EQJc1HGAVtskPJmYNZyBI,5203
11
+ hyzecloud/resources/github.py,sha256=5TvHRvNQFYN_if5AJVxRMUg9OCn-bOtABbgJnWyp598,1832
12
+ hyzecloud/resources/invoices.py,sha256=uYnAyIHNyXVFBrykcVlD9qfJ1-OK6iVjnG8ht2CRsho,1349
13
+ hyzecloud/resources/plans.py,sha256=3DI0_9RGC6QycIdPadWoD9Im2bksYW4SiXyaYOLNcD8,713
14
+ hyze_cloud-0.1.0.dist-info/METADATA,sha256=AQ_ZHnWJ3rRa4Myx9pHC2tXO4MSIofTXIZDPqwyZH3k,8229
15
+ hyze_cloud-0.1.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
16
+ hyze_cloud-0.1.0.dist-info/licenses/LICENSE,sha256=A0cg9rg53IWNTe-qsGjEexNWxA-SY3z0kjUXyRISdS0,1067
17
+ hyze_cloud-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hyze Cloud
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
hyzecloud/__init__.py ADDED
@@ -0,0 +1,143 @@
1
+ """Official Python SDK for the Hyze Cloud API.
2
+
3
+ from hyzecloud import HyzeCloud
4
+
5
+ client = HyzeCloud(api_key="hyze_...")
6
+ for app in client.apps.list()["apps"]:
7
+ print(app["name"], app["status"])
8
+
9
+ An async client with the same surface is available as :class:`AsyncHyzeCloud`:
10
+
11
+ from hyzecloud import AsyncHyzeCloud
12
+
13
+ async with AsyncHyzeCloud() as client: # reads HYZE_API_KEY
14
+ apps = await client.apps.list()
15
+
16
+ This mirrors the TypeScript SDK (``@hyze-cloud/sdk``): same resources, same routes, same
17
+ error shape.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ from importlib.metadata import PackageNotFoundError
23
+ from importlib.metadata import version as _version
24
+
25
+ from .client import DEFAULT_BASE_URL, DEFAULT_TIMEOUT, AsyncHyzeCloud, HyzeCloud, create_hyze_client
26
+ from .errors import HyzeError, looks_like_error_payload, parse_error_body
27
+ from .resources import (
28
+ ApiKeysResource,
29
+ AppsResource,
30
+ DatabasesResource,
31
+ GithubResource,
32
+ InvoicesResource,
33
+ PlansResource,
34
+ )
35
+ from .types import (
36
+ ApiKeyCreated,
37
+ ApiKeyCreateResponse,
38
+ ApiKeyItem,
39
+ ApiKeysListResponse,
40
+ ApiKeyUpdateResponse,
41
+ AppDeployment,
42
+ AppDeploymentsResponse,
43
+ AppDeploymentStatus,
44
+ AppDetail,
45
+ AppDetailResponse,
46
+ AppEnvResponse,
47
+ AppListItem,
48
+ AppLogsResponse,
49
+ AppsListResponse,
50
+ BillingInterval,
51
+ ContainerStats,
52
+ CreateApiKeyInput,
53
+ CreateDatabaseInput,
54
+ CreatePixInvoiceInput,
55
+ CurrentPlanResponse,
56
+ DatabaseEngine,
57
+ DatabaseItem,
58
+ DatabaseResponse,
59
+ DatabasesListResponse,
60
+ DeployFromRepoInput,
61
+ DeployFromZipInput,
62
+ DeployRepository,
63
+ GithubStatusResponse,
64
+ InvoiceBilling,
65
+ InvoiceDetail,
66
+ InvoiceDetailResponse,
67
+ InvoiceListItem,
68
+ InvoicePlanSummary,
69
+ InvoicesListResponse,
70
+ PlanInfo,
71
+ PlanPriceInfo,
72
+ PlansListResponse,
73
+ PlanUsage,
74
+ RequestOptions,
75
+ Runtime,
76
+ UpdateApiKeyInput,
77
+ UpdateAppSettingsInput,
78
+ )
79
+
80
+ try:
81
+ __version__ = _version("hyze-cloud")
82
+ except PackageNotFoundError: # running from a source tree without an install
83
+ __version__ = "0.0.0"
84
+
85
+ __all__ = [
86
+ "DEFAULT_BASE_URL",
87
+ "DEFAULT_TIMEOUT",
88
+ "ApiKeyCreateResponse",
89
+ "ApiKeyCreated",
90
+ "ApiKeyItem",
91
+ "ApiKeyUpdateResponse",
92
+ "ApiKeysListResponse",
93
+ "ApiKeysResource",
94
+ "AppDeployment",
95
+ "AppDeploymentStatus",
96
+ "AppDeploymentsResponse",
97
+ "AppDetail",
98
+ "AppDetailResponse",
99
+ "AppEnvResponse",
100
+ "AppListItem",
101
+ "AppLogsResponse",
102
+ "AppsListResponse",
103
+ "AppsResource",
104
+ "AsyncHyzeCloud",
105
+ "BillingInterval",
106
+ "ContainerStats",
107
+ "CreateApiKeyInput",
108
+ "CreateDatabaseInput",
109
+ "CreatePixInvoiceInput",
110
+ "CurrentPlanResponse",
111
+ "DatabaseEngine",
112
+ "DatabaseItem",
113
+ "DatabaseResponse",
114
+ "DatabasesListResponse",
115
+ "DatabasesResource",
116
+ "DeployFromRepoInput",
117
+ "DeployFromZipInput",
118
+ "DeployRepository",
119
+ "GithubResource",
120
+ "GithubStatusResponse",
121
+ "HyzeCloud",
122
+ "HyzeError",
123
+ "InvoiceBilling",
124
+ "InvoiceDetail",
125
+ "InvoiceDetailResponse",
126
+ "InvoiceListItem",
127
+ "InvoicePlanSummary",
128
+ "InvoicesListResponse",
129
+ "InvoicesResource",
130
+ "PlanInfo",
131
+ "PlanPriceInfo",
132
+ "PlanUsage",
133
+ "PlansListResponse",
134
+ "PlansResource",
135
+ "RequestOptions",
136
+ "Runtime",
137
+ "UpdateApiKeyInput",
138
+ "UpdateAppSettingsInput",
139
+ "__version__",
140
+ "create_hyze_client",
141
+ "looks_like_error_payload",
142
+ "parse_error_body",
143
+ ]
hyzecloud/_url.py ADDED
@@ -0,0 +1,26 @@
1
+ """URL helpers shared by the client and the resources.
2
+
3
+ Kept in its own module so resources can import it without importing the client, which
4
+ would be a cycle (the client imports every resource).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from urllib.parse import quote
10
+
11
+ __all__ = ["join_url", "segment"]
12
+
13
+
14
+ def join_url(base: str, path: str) -> str:
15
+ """Append a path to a base URL without doubling or dropping the slash."""
16
+ normalized_path = path if path.startswith("/") else f"/{path}"
17
+ return f"{base.rstrip('/')}{normalized_path}"
18
+
19
+
20
+ def segment(value: object) -> str:
21
+ """Percent-encode one path segment.
22
+
23
+ The TypeScript SDK wraps every id in ``encodeURIComponent``. Without this, an id
24
+ containing a space, a slash or a ``?`` would silently change the route.
25
+ """
26
+ return quote(str(value), safe="")