hyze-cloud 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,83 @@
1
+ # CI do SDK Python. Roda em PR, em push na main e no agendamento diário — o job de rotas
2
+ # precisa do agendamento porque a API vive em outro repo e pode sumir com uma rota sem que
3
+ # nada aqui mude.
4
+ name: CI
5
+
6
+ on:
7
+ push:
8
+ branches: [main]
9
+ pull_request:
10
+ branches: [main]
11
+ schedule:
12
+ - cron: "17 9 * * *"
13
+ workflow_dispatch:
14
+
15
+ permissions:
16
+ contents: read
17
+
18
+ jobs:
19
+ checks:
20
+ name: Ruff · mypy · pytest (py${{ matrix.python }})
21
+ runs-on: ubuntu-latest
22
+ timeout-minutes: 15
23
+ strategy:
24
+ fail-fast: false
25
+ matrix:
26
+ python: ["3.10", "3.11", "3.12", "3.13"]
27
+
28
+ steps:
29
+ - name: Checkout
30
+ uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
31
+
32
+ - name: Setup uv (and Python ${{ matrix.python }})
33
+ uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
34
+ with:
35
+ python-version: ${{ matrix.python }}
36
+ enable-cache: true
37
+
38
+ # Biblioteca não versiona lock: o `requires-python` e as faixas do pyproject é que mandam.
39
+ - name: Install
40
+ run: uv sync --extra dev --no-progress
41
+
42
+ - name: Format check
43
+ run: uv run ruff format --check .
44
+
45
+ - name: Lint
46
+ run: uv run ruff check .
47
+
48
+ - name: Typecheck (strict)
49
+ run: uv run mypy
50
+
51
+ - name: Test
52
+ run: uv run pytest -q
53
+
54
+ api-routes:
55
+ name: API routes (live)
56
+ runs-on: ubuntu-latest
57
+ timeout-minutes: 10
58
+
59
+ steps:
60
+ - name: Checkout
61
+ uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
62
+
63
+ - name: Setup uv
64
+ uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
65
+ with:
66
+ enable-cache: true
67
+
68
+ - name: Install
69
+ run: uv sync --no-progress
70
+
71
+ # Sem isso o check não distingue rota viva de rota morta: se uma rota inventada não
72
+ # devolver 404, um 404 legítimo deixaria de significar "a rota sumiu".
73
+ - name: Negative control (an invented route must 404)
74
+ run: |
75
+ code="$(curl -s -o /dev/null -w "%{http_code}" "https://api.hyzecloud.com/api/rota-inventada-do-ci")"
76
+ echo "invented route -> $code"
77
+ if [ "$code" != "404" ]; then
78
+ echo "expected a 404 from an invented route" >&2
79
+ exit 1
80
+ fi
81
+
82
+ - name: Every route the SDK calls still exists
83
+ run: uv run python scripts/check_api_routes.py
@@ -0,0 +1,133 @@
1
+ # Publica o pacote no PyPI a partir de uma tag `v<versão>` — a tag TEM que bater com a versão
2
+ # do pyproject.toml (o check abaixo aborta antes de qualquer publish).
3
+ # git tag v0.1.1 && git push origin v0.1.1
4
+ #
5
+ # Autenticação: Trusted Publishing (OIDC) — não existe token do PyPI em lugar nenhum.
6
+ # O PyPI amarra o pacote a ESTE repositório e a ESTE arquivo de workflow, num cadastro que é
7
+ # feito uma vez em https://pypi.org/manage/account/publishing/ (Pending publisher):
8
+ # PyPI Project Name: hyze-cloud
9
+ # Owner: Hyze-Cloud
10
+ # Repository name: hyzecloud-sdk-python
11
+ # Workflow name: release.yml
12
+ # Environment: (deixe vazio — este workflow não usa environment)
13
+ # Sem esse cadastro o publish falha com "trusted publisher" e nenhuma tag resolve.
14
+ name: Release
15
+
16
+ on:
17
+ push:
18
+ tags: ["v*"]
19
+ workflow_dispatch:
20
+
21
+ permissions:
22
+ contents: read
23
+ id-token: write # o OIDC que o PyPI troca pelo token de publish
24
+
25
+ jobs:
26
+ checks:
27
+ name: Ruff · mypy · pytest
28
+ runs-on: ubuntu-latest
29
+ timeout-minutes: 15
30
+
31
+ steps:
32
+ - name: Checkout
33
+ uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
34
+
35
+ - name: Setup uv
36
+ uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
37
+ with:
38
+ enable-cache: true
39
+
40
+ - name: Install
41
+ run: uv sync --extra dev --no-progress
42
+
43
+ - name: Format check
44
+ run: uv run ruff format --check .
45
+
46
+ - name: Lint
47
+ run: uv run ruff check .
48
+
49
+ - name: Typecheck (strict)
50
+ run: uv run mypy
51
+
52
+ - name: Test
53
+ run: uv run pytest -q
54
+
55
+ api-routes:
56
+ name: API routes (live)
57
+ runs-on: ubuntu-latest
58
+ timeout-minutes: 10
59
+
60
+ steps:
61
+ - name: Checkout
62
+ uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
63
+
64
+ - name: Setup uv
65
+ uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
66
+ with:
67
+ enable-cache: true
68
+
69
+ - name: Install
70
+ run: uv sync --no-progress
71
+
72
+ - name: Negative control (an invented route must 404)
73
+ run: |
74
+ code="$(curl -s -o /dev/null -w "%{http_code}" "https://api.hyzecloud.com/api/rota-inventada-do-ci")"
75
+ echo "invented route -> $code"
76
+ if [ "$code" != "404" ]; then
77
+ echo "expected a 404 from an invented route" >&2
78
+ exit 1
79
+ fi
80
+
81
+ # Cliente publicado chamando rota morta é 404 pro usuário.
82
+ - name: Every route the SDK calls still exists
83
+ run: uv run python scripts/check_api_routes.py
84
+
85
+ publish:
86
+ name: Build and publish to PyPI
87
+ needs: [checks, api-routes]
88
+ runs-on: ubuntu-latest
89
+ timeout-minutes: 15
90
+
91
+ steps:
92
+ - name: Checkout
93
+ uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
94
+
95
+ - name: Setup uv
96
+ uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
97
+
98
+ - name: Verify tag matches pyproject version
99
+ run: |
100
+ PKG="$(uv run --no-project python -c "import tomllib,pathlib;print(tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']['version'])")"
101
+ case "${GITHUB_REF}" in
102
+ refs/tags/*)
103
+ TAG="${GITHUB_REF_NAME#v}"
104
+ if [ "$TAG" != "$PKG" ]; then
105
+ echo "Tag v$TAG does not match pyproject version $PKG" >&2
106
+ exit 1
107
+ fi
108
+ echo "tag v$TAG == version $PKG" ;;
109
+ *) echo "workflow_dispatch: publishing version $PKG (no tag to check)" ;;
110
+ esac
111
+
112
+ - name: Build sdist and wheel
113
+ run: uv build
114
+
115
+ - name: Check the artifacts before shipping them
116
+ run: |
117
+ ls -la dist/
118
+ uv run --no-project python - <<'PY'
119
+ import glob, tomllib, zipfile
120
+ wheel = glob.glob("dist/*.whl")[0]
121
+ names = zipfile.ZipFile(wheel).namelist()
122
+ meta = next(n for n in names if n.endswith("METADATA"))
123
+ text = zipfile.ZipFile(wheel).read(meta).decode()
124
+ version = tomllib.load(open("pyproject.toml", "rb"))["project"]["version"]
125
+ assert f"Version: {version}" in text, text
126
+ assert any(n.endswith("hyzecloud/py.typed") for n in names), names
127
+ assert any(n.endswith("hyzecloud/client.py") for n in names), names
128
+ print(f"wheel ok: {len(names)} files, version {version}, py.typed present")
129
+ PY
130
+
131
+ # Sem token: o `id-token: write` no topo é o que o PyPI troca pelo publish token.
132
+ - name: Publish to PyPI (Trusted Publishing)
133
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
@@ -0,0 +1,22 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .venv/
6
+ venv/
7
+
8
+ # Build
9
+ dist/
10
+ build/
11
+
12
+ # Tooling caches
13
+ .pytest_cache/
14
+ .mypy_cache/
15
+ .ruff_cache/
16
+ .coverage
17
+ htmlcov/
18
+
19
+ # Editors / OS
20
+ .DS_Store
21
+ .idea/
22
+ .vscode/
@@ -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.
@@ -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