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.
- hyze_cloud-0.1.0/.github/workflows/ci.yml +83 -0
- hyze_cloud-0.1.0/.github/workflows/release.yml +133 -0
- hyze_cloud-0.1.0/.gitignore +22 -0
- hyze_cloud-0.1.0/LICENSE +21 -0
- hyze_cloud-0.1.0/PKG-INFO +263 -0
- hyze_cloud-0.1.0/README.md +232 -0
- hyze_cloud-0.1.0/pyproject.toml +61 -0
- hyze_cloud-0.1.0/scripts/check_api_routes.py +125 -0
- hyze_cloud-0.1.0/scripts/live_smoke.py +94 -0
- hyze_cloud-0.1.0/src/hyzecloud/__init__.py +143 -0
- hyze_cloud-0.1.0/src/hyzecloud/_url.py +26 -0
- hyze_cloud-0.1.0/src/hyzecloud/client.py +409 -0
- hyze_cloud-0.1.0/src/hyzecloud/errors.py +118 -0
- hyze_cloud-0.1.0/src/hyzecloud/py.typed +0 -0
- hyze_cloud-0.1.0/src/hyzecloud/resources/__init__.py +24 -0
- hyze_cloud-0.1.0/src/hyzecloud/resources/api_keys.py +69 -0
- hyze_cloud-0.1.0/src/hyzecloud/resources/apps.py +252 -0
- hyze_cloud-0.1.0/src/hyzecloud/resources/databases.py +142 -0
- hyze_cloud-0.1.0/src/hyzecloud/resources/github.py +55 -0
- hyze_cloud-0.1.0/src/hyzecloud/resources/invoices.py +41 -0
- hyze_cloud-0.1.0/src/hyzecloud/resources/plans.py +28 -0
- hyze_cloud-0.1.0/src/hyzecloud/types.py +448 -0
- hyze_cloud-0.1.0/tests/test_client.py +364 -0
- hyze_cloud-0.1.0/tests/test_resources.py +360 -0
|
@@ -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/
|
hyze_cloud-0.1.0/LICENSE
ADDED
|
@@ -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
|
+
[](https://github.com/Hyze-Cloud/hyzecloud-sdk-python/actions/workflows/ci.yml)
|
|
35
|
+
[](https://pypi.org/project/hyze-cloud/)
|
|
36
|
+
[](https://pypi.org/project/hyze-cloud/)
|
|
37
|
+
[](./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
|