termix-sdk 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.
- termix_sdk-0.1.0/.gitignore +15 -0
- termix_sdk-0.1.0/CHANGELOG.md +65 -0
- termix_sdk-0.1.0/CONTRIBUTING.md +155 -0
- termix_sdk-0.1.0/LICENSE +21 -0
- termix_sdk-0.1.0/NOTICE +43 -0
- termix_sdk-0.1.0/PKG-INFO +191 -0
- termix_sdk-0.1.0/README.md +157 -0
- termix_sdk-0.1.0/examples/README.md +21 -0
- termix_sdk-0.1.0/examples/async_quickstart.py +28 -0
- termix_sdk-0.1.0/examples/file_manager_session.py +39 -0
- termix_sdk-0.1.0/examples/login_with_totp.py +32 -0
- termix_sdk-0.1.0/examples/sync_quickstart.py +31 -0
- termix_sdk-0.1.0/pyproject.toml +85 -0
- termix_sdk-0.1.0/src/termix_sdk/__init__.py +57 -0
- termix_sdk-0.1.0/src/termix_sdk/_api_requestor.py +357 -0
- termix_sdk-0.1.0/src/termix_sdk/_async_client.py +217 -0
- termix_sdk-0.1.0/src/termix_sdk/_auth.py +225 -0
- termix_sdk-0.1.0/src/termix_sdk/_client.py +237 -0
- termix_sdk-0.1.0/src/termix_sdk/_client_options.py +114 -0
- termix_sdk-0.1.0/src/termix_sdk/_error.py +156 -0
- termix_sdk-0.1.0/src/termix_sdk/_http_client.py +387 -0
- termix_sdk-0.1.0/src/termix_sdk/_object.py +181 -0
- termix_sdk-0.1.0/src/termix_sdk/_request_options.py +44 -0
- termix_sdk-0.1.0/src/termix_sdk/_response.py +106 -0
- termix_sdk-0.1.0/src/termix_sdk/_service.py +153 -0
- termix_sdk-0.1.0/src/termix_sdk/_session.py +214 -0
- termix_sdk-0.1.0/src/termix_sdk/_sse.py +79 -0
- termix_sdk-0.1.0/src/termix_sdk/_util.py +93 -0
- termix_sdk-0.1.0/src/termix_sdk/_version.py +6 -0
- termix_sdk-0.1.0/src/termix_sdk/models/__init__.py +3 -0
- termix_sdk-0.1.0/src/termix_sdk/models/ai.py +59 -0
- termix_sdk-0.1.0/src/termix_sdk/models/alerts.py +115 -0
- termix_sdk-0.1.0/src/termix_sdk/models/api_keys.py +29 -0
- termix_sdk-0.1.0/src/termix_sdk/models/audit.py +21 -0
- termix_sdk-0.1.0/src/termix_sdk/models/automations.py +79 -0
- termix_sdk-0.1.0/src/termix_sdk/models/credentials.py +102 -0
- termix_sdk-0.1.0/src/termix_sdk/models/dashboard.py +36 -0
- termix_sdk-0.1.0/src/termix_sdk/models/database.py +38 -0
- termix_sdk-0.1.0/src/termix_sdk/models/docker.py +106 -0
- termix_sdk-0.1.0/src/termix_sdk/models/encryption.py +35 -0
- termix_sdk-0.1.0/src/termix_sdk/models/file_manager.py +234 -0
- termix_sdk-0.1.0/src/termix_sdk/models/fleets.py +75 -0
- termix_sdk-0.1.0/src/termix_sdk/models/guacamole.py +24 -0
- termix_sdk-0.1.0/src/termix_sdk/models/homepage.py +46 -0
- termix_sdk-0.1.0/src/termix_sdk/models/host_file_manager.py +34 -0
- termix_sdk-0.1.0/src/termix_sdk/models/hosts.py +608 -0
- termix_sdk-0.1.0/src/termix_sdk/models/instance_settings.py +215 -0
- termix_sdk-0.1.0/src/termix_sdk/models/metrics.py +253 -0
- termix_sdk-0.1.0/src/termix_sdk/models/network_topology.py +14 -0
- termix_sdk-0.1.0/src/termix_sdk/models/open_tabs.py +43 -0
- termix_sdk-0.1.0/src/termix_sdk/models/preferences.py +105 -0
- termix_sdk-0.1.0/src/termix_sdk/models/proxmox.py +11 -0
- termix_sdk-0.1.0/src/termix_sdk/models/proxmox_stats.py +37 -0
- termix_sdk-0.1.0/src/termix_sdk/models/rbac.py +105 -0
- termix_sdk-0.1.0/src/termix_sdk/models/session_logs.py +30 -0
- termix_sdk-0.1.0/src/termix_sdk/models/session_sharing.py +34 -0
- termix_sdk-0.1.0/src/termix_sdk/models/snippets.py +84 -0
- termix_sdk-0.1.0/src/termix_sdk/models/sso.py +31 -0
- termix_sdk-0.1.0/src/termix_sdk/models/sync.py +27 -0
- termix_sdk-0.1.0/src/termix_sdk/models/system.py +22 -0
- termix_sdk-0.1.0/src/termix_sdk/models/tailscale.py +15 -0
- termix_sdk-0.1.0/src/termix_sdk/models/terminal.py +27 -0
- termix_sdk-0.1.0/src/termix_sdk/models/termix_id.py +91 -0
- termix_sdk-0.1.0/src/termix_sdk/models/tmux.py +15 -0
- termix_sdk-0.1.0/src/termix_sdk/models/tunnel.py +36 -0
- termix_sdk-0.1.0/src/termix_sdk/models/tunnel_presets.py +22 -0
- termix_sdk-0.1.0/src/termix_sdk/models/user_admin.py +60 -0
- termix_sdk-0.1.0/src/termix_sdk/models/users.py +142 -0
- termix_sdk-0.1.0/src/termix_sdk/models/vault.py +26 -0
- termix_sdk-0.1.0/src/termix_sdk/models/webauthn.py +32 -0
- termix_sdk-0.1.0/src/termix_sdk/models/workspaces.py +95 -0
- termix_sdk-0.1.0/src/termix_sdk/py.typed +0 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/__init__.py +3 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/ai.py +457 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/alerts.py +562 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/api_keys.py +113 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/audit.py +120 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/automations.py +332 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/credentials.py +648 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/dashboard.py +287 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/database.py +214 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/docker.py +689 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/encryption.py +145 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/file_manager.py +1468 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/fleets.py +535 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/guacamole.py +133 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/homepage.py +364 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/host_file_manager.py +331 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/hosts.py +1047 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/instance_settings.py +1381 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/metrics.py +1917 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/network_topology.py +82 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/open_tabs.py +233 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/preferences.py +376 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/proxmox.py +117 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/proxmox_stats.py +279 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/rbac.py +762 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/session_logs.py +208 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/session_sharing.py +197 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/snippets.py +495 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/sso.py +223 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/sync.py +183 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/system.py +116 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/tailscale.py +50 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/terminal.py +278 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/termix_id.py +620 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/tmux.py +516 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/tunnel.py +217 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/tunnel_presets.py +152 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/user_admin.py +356 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/users.py +884 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/vault.py +155 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/webauthn.py +269 -0
- termix_sdk-0.1.0/src/termix_sdk/resources/workspaces.py +403 -0
- termix_sdk-0.1.0/src/termix_sdk/types/__init__.py +3 -0
- termix_sdk-0.1.0/src/termix_sdk/types/ai.py +41 -0
- termix_sdk-0.1.0/src/termix_sdk/types/alerts.py +56 -0
- termix_sdk-0.1.0/src/termix_sdk/types/api_keys.py +16 -0
- termix_sdk-0.1.0/src/termix_sdk/types/audit.py +19 -0
- termix_sdk-0.1.0/src/termix_sdk/types/automations.py +38 -0
- termix_sdk-0.1.0/src/termix_sdk/types/credentials.py +86 -0
- termix_sdk-0.1.0/src/termix_sdk/types/dashboard.py +39 -0
- termix_sdk-0.1.0/src/termix_sdk/types/database.py +20 -0
- termix_sdk-0.1.0/src/termix_sdk/types/docker.py +60 -0
- termix_sdk-0.1.0/src/termix_sdk/types/encryption.py +11 -0
- termix_sdk-0.1.0/src/termix_sdk/types/file_manager.py +181 -0
- termix_sdk-0.1.0/src/termix_sdk/types/fleets.py +53 -0
- termix_sdk-0.1.0/src/termix_sdk/types/guacamole.py +27 -0
- termix_sdk-0.1.0/src/termix_sdk/types/homepage.py +46 -0
- termix_sdk-0.1.0/src/termix_sdk/types/host_file_manager.py +40 -0
- termix_sdk-0.1.0/src/termix_sdk/types/hosts.py +355 -0
- termix_sdk-0.1.0/src/termix_sdk/types/instance_settings.py +114 -0
- termix_sdk-0.1.0/src/termix_sdk/types/metrics.py +130 -0
- termix_sdk-0.1.0/src/termix_sdk/types/network_topology.py +14 -0
- termix_sdk-0.1.0/src/termix_sdk/types/open_tabs.py +30 -0
- termix_sdk-0.1.0/src/termix_sdk/types/preferences.py +68 -0
- termix_sdk-0.1.0/src/termix_sdk/types/proxmox.py +18 -0
- termix_sdk-0.1.0/src/termix_sdk/types/proxmox_stats.py +27 -0
- termix_sdk-0.1.0/src/termix_sdk/types/rbac.py +53 -0
- termix_sdk-0.1.0/src/termix_sdk/types/session_logs.py +14 -0
- termix_sdk-0.1.0/src/termix_sdk/types/session_sharing.py +21 -0
- termix_sdk-0.1.0/src/termix_sdk/types/snippets.py +52 -0
- termix_sdk-0.1.0/src/termix_sdk/types/sso.py +33 -0
- termix_sdk-0.1.0/src/termix_sdk/types/sync.py +27 -0
- termix_sdk-0.1.0/src/termix_sdk/types/system.py +19 -0
- termix_sdk-0.1.0/src/termix_sdk/types/tailscale.py +11 -0
- termix_sdk-0.1.0/src/termix_sdk/types/terminal.py +31 -0
- termix_sdk-0.1.0/src/termix_sdk/types/termix_id.py +51 -0
- termix_sdk-0.1.0/src/termix_sdk/types/tmux.py +54 -0
- termix_sdk-0.1.0/src/termix_sdk/types/tunnel.py +38 -0
- termix_sdk-0.1.0/src/termix_sdk/types/tunnel_presets.py +24 -0
- termix_sdk-0.1.0/src/termix_sdk/types/user_admin.py +36 -0
- termix_sdk-0.1.0/src/termix_sdk/types/users.py +81 -0
- termix_sdk-0.1.0/src/termix_sdk/types/vault.py +42 -0
- termix_sdk-0.1.0/src/termix_sdk/types/webauthn.py +31 -0
- termix_sdk-0.1.0/src/termix_sdk/types/workspaces.py +35 -0
- termix_sdk-0.1.0/tests/__init__.py +0 -0
- termix_sdk-0.1.0/tests/conftest.py +85 -0
- termix_sdk-0.1.0/tests/contract/__init__.py +4 -0
- termix_sdk-0.1.0/tests/contract/test_ai.py +362 -0
- termix_sdk-0.1.0/tests/contract/test_alerts.py +789 -0
- termix_sdk-0.1.0/tests/contract/test_api_keys.py +114 -0
- termix_sdk-0.1.0/tests/contract/test_audit.py +80 -0
- termix_sdk-0.1.0/tests/contract/test_automations.py +396 -0
- termix_sdk-0.1.0/tests/contract/test_credentials.py +640 -0
- termix_sdk-0.1.0/tests/contract/test_dashboard.py +193 -0
- termix_sdk-0.1.0/tests/contract/test_database.py +381 -0
- termix_sdk-0.1.0/tests/contract/test_docker.py +640 -0
- termix_sdk-0.1.0/tests/contract/test_encryption.py +203 -0
- termix_sdk-0.1.0/tests/contract/test_file_manager.py +1943 -0
- termix_sdk-0.1.0/tests/contract/test_fleets.py +481 -0
- termix_sdk-0.1.0/tests/contract/test_guacamole.py +130 -0
- termix_sdk-0.1.0/tests/contract/test_homepage.py +259 -0
- termix_sdk-0.1.0/tests/contract/test_host_file_manager.py +208 -0
- termix_sdk-0.1.0/tests/contract/test_hosts.py +4333 -0
- termix_sdk-0.1.0/tests/contract/test_instance_settings.py +1328 -0
- termix_sdk-0.1.0/tests/contract/test_metrics.py +1709 -0
- termix_sdk-0.1.0/tests/contract/test_network_topology.py +51 -0
- termix_sdk-0.1.0/tests/contract/test_open_tabs.py +217 -0
- termix_sdk-0.1.0/tests/contract/test_preferences.py +921 -0
- termix_sdk-0.1.0/tests/contract/test_proxmox.py +180 -0
- termix_sdk-0.1.0/tests/contract/test_proxmox_stats.py +300 -0
- termix_sdk-0.1.0/tests/contract/test_rbac.py +925 -0
- termix_sdk-0.1.0/tests/contract/test_session_logs.py +135 -0
- termix_sdk-0.1.0/tests/contract/test_session_sharing.py +158 -0
- termix_sdk-0.1.0/tests/contract/test_snippets.py +493 -0
- termix_sdk-0.1.0/tests/contract/test_sso.py +183 -0
- termix_sdk-0.1.0/tests/contract/test_sync.py +99 -0
- termix_sdk-0.1.0/tests/contract/test_system.py +160 -0
- termix_sdk-0.1.0/tests/contract/test_tailscale.py +82 -0
- termix_sdk-0.1.0/tests/contract/test_terminal.py +188 -0
- termix_sdk-0.1.0/tests/contract/test_termix_id.py +457 -0
- termix_sdk-0.1.0/tests/contract/test_tmux.py +319 -0
- termix_sdk-0.1.0/tests/contract/test_tunnel.py +291 -0
- termix_sdk-0.1.0/tests/contract/test_tunnel_presets.py +501 -0
- termix_sdk-0.1.0/tests/contract/test_user_admin.py +377 -0
- termix_sdk-0.1.0/tests/contract/test_users.py +812 -0
- termix_sdk-0.1.0/tests/contract/test_vault.py +209 -0
- termix_sdk-0.1.0/tests/contract/test_webauthn.py +527 -0
- termix_sdk-0.1.0/tests/contract/test_workspaces.py +394 -0
- termix_sdk-0.1.0/tests/http_client_mock.py +124 -0
- termix_sdk-0.1.0/tests/test_api_requestor.py +196 -0
- termix_sdk-0.1.0/tests/test_auth.py +157 -0
- termix_sdk-0.1.0/tests/test_client.py +52 -0
- termix_sdk-0.1.0/tests/test_error.py +87 -0
- termix_sdk-0.1.0/tests/test_generated_pilot.py +163 -0
- termix_sdk-0.1.0/tests/test_http_client.py +57 -0
- termix_sdk-0.1.0/tests/test_object.py +102 -0
- termix_sdk-0.1.0/tests/test_options.py +72 -0
- termix_sdk-0.1.0/tests/test_response.py +59 -0
- termix_sdk-0.1.0/tests/test_session.py +147 -0
- termix_sdk-0.1.0/tests/test_sse.py +68 -0
- termix_sdk-0.1.0/tests/test_util.py +41 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.1.0] - 2026-09-19
|
|
10
|
+
|
|
11
|
+
First release. Generated against Termix `release-2.7.1-tag`.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- `TermixClient` / `AsyncTermixClient`: connection options (`base_url`,
|
|
16
|
+
per-service `service_urls` override, `api_key` or `jwt`, timeout,
|
|
17
|
+
retries), a low-level `request()` escape hatch, and context-manager
|
|
18
|
+
support.
|
|
19
|
+
- `TermixClient.login()` / `AsyncTermixClient.login()`: username/password
|
|
20
|
+
login handling Termix's native-app-header requirement for getting a
|
|
21
|
+
usable JWT back, including the TOTP second-factor flow
|
|
22
|
+
(`PendingTOTP`/`AsyncPendingTOTP`).
|
|
23
|
+
- A typed exception hierarchy (`TermixError` and subclasses —
|
|
24
|
+
`AuthenticationError`, `PermissionError`, `NotFoundError`,
|
|
25
|
+
`RateLimitError` with `remaining_time`, `DataLockedError`,
|
|
26
|
+
`TOTPRequiredError`, `SessionExpiredError`, `InvalidRequestError`,
|
|
27
|
+
`ServerError`) mapped from Termix's actual error response shapes.
|
|
28
|
+
- `TermixObject`: dict-backed base model with attribute access,
|
|
29
|
+
`to_dict()`, and secret redaction in `repr()`/logs.
|
|
30
|
+
- 477 of 491 real Termix endpoints, generated across 43 resource
|
|
31
|
+
modules (`client.hosts`, `client.credentials`, `client.file_manager`,
|
|
32
|
+
`client.metrics`, ... — see `tools/sdk-gen/config/resource-map.json`
|
|
33
|
+
for the full list), each with a sync and an async client.
|
|
34
|
+
- `ssh_session()` / `async_ssh_session()`: connect/keepalive/disconnect
|
|
35
|
+
lifecycle helper for the SSH-backed session pattern `file_manager` and
|
|
36
|
+
`docker` share.
|
|
37
|
+
- SSE support (`iter_sse_events`/`aiter_sse_events`, `SSEEvent`) for the
|
|
38
|
+
3 `text/event-stream` endpoints (tunnel status, Proxmox discovery, AI
|
|
39
|
+
chat streaming).
|
|
40
|
+
- Real multipart file upload (`fleets.transfer_push`) where a field has
|
|
41
|
+
no JSON equivalent.
|
|
42
|
+
- `tools/sdk-gen/generate.py`: the OpenAPI-spec-to-Python-client
|
|
43
|
+
generator, plus `tests/contract/`, one generated test per operation.
|
|
44
|
+
- `py.typed` marker, so mypy/pyright pick up the package's inline types.
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
|
|
48
|
+
- `ssh_session()`/`async_ssh_session()` now type-check when passed a
|
|
49
|
+
generated service (`client.file_manager`, `client.docker`): the
|
|
50
|
+
session protocol accepted only `**params: Any` methods, which the
|
|
51
|
+
generated `Unpack[TypedDict]` signatures don't satisfy.
|
|
52
|
+
|
|
53
|
+
### Known gaps
|
|
54
|
+
|
|
55
|
+
- 3 `file_manager` endpoints (`uploadFileStream`, `downloadFileStream`,
|
|
56
|
+
`uploadFileChunk`) send/receive a raw body the spec has no documented
|
|
57
|
+
schema for — not generated, to avoid inventing behavior the source
|
|
58
|
+
doesn't support.
|
|
59
|
+
- 11 endpoints are out of scope by design: internal-auth-only routes,
|
|
60
|
+
browser-redirect OIDC/opkssh flows, and `POST /users/login` /
|
|
61
|
+
`POST /users/totp/verify-login` (handled by `TermixClient.login()`
|
|
62
|
+
instead).
|
|
63
|
+
- No live-instance test suite yet (`tests/` and `tests/contract/` run
|
|
64
|
+
against a scripted fake transport only) — see CONTRIBUTING.md's
|
|
65
|
+
testing-philosophy section.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
## Setup
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
git clone https://github.com/MatheusAlves96/termix-sdk.git
|
|
7
|
+
cd termix-sdk
|
|
8
|
+
uv sync --extra dev # creates .venv from uv.lock (pinned tool versions)
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Without [uv](https://docs.astral.sh/uv/): `python -m venv .venv`, activate
|
|
12
|
+
it, `pip install -e ".[dev]"`. You then get whatever `ruff`/`mypy`/`pytest`
|
|
13
|
+
versions resolve today rather than the locked ones CI uses, so a check may
|
|
14
|
+
pass locally and fail in CI (or the reverse) — the lockfile exists to avoid
|
|
15
|
+
exactly that.
|
|
16
|
+
|
|
17
|
+
## Running the checks
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
uv run pytest # tests/ (unit) + tests/contract/ (generated)
|
|
21
|
+
uv run ruff check .
|
|
22
|
+
uv run ruff format --check .
|
|
23
|
+
uv run mypy # src/ and examples/
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## What CI checks
|
|
27
|
+
|
|
28
|
+
`.github/workflows/ci.yml` runs on every push to `main` and every PR:
|
|
29
|
+
|
|
30
|
+
| Job | What it does |
|
|
31
|
+
|-----|--------------|
|
|
32
|
+
| `lint` | `ruff check`, `ruff format --check`, `mypy` (Python 3.13) |
|
|
33
|
+
| `test (3.10..3.13)` | `pytest` with coverage; the 3.13 run uploads to Codecov (informational, never blocks) |
|
|
34
|
+
| `generated-sync` | runs `tools/sdk-gen/generate.py` and fails if any committed file changes — hand-edits to `resources/`, `models/`, `types/` or `tests/contract/` are caught here |
|
|
35
|
+
| `spec-gen-typecheck` | `npm ci && npm run typecheck` in `tools/spec-gen` |
|
|
36
|
+
| `build` | `uv build`, `twine check`, installs the wheel into a clean venv, imports it and checks `py.typed` is shipped; uploads `dist/` |
|
|
37
|
+
|
|
38
|
+
`uv.lock` is enforced (`UV_LOCKED=1`): if you change `pyproject.toml`
|
|
39
|
+
dependencies, run `uv lock` and commit the lockfile, or CI fails at
|
|
40
|
+
`uv sync`.
|
|
41
|
+
|
|
42
|
+
`.github/workflows/zizmor.yml` lints the workflows themselves for
|
|
43
|
+
security issues (unpinned actions, template injection, leaked
|
|
44
|
+
credentials). Actions are pinned by commit SHA; Dependabot
|
|
45
|
+
(`.github/dependabot.yml`) opens weekly PRs to bump them, plus Python
|
|
46
|
+
and `tools/spec-gen` npm dependencies.
|
|
47
|
+
|
|
48
|
+
## Releasing
|
|
49
|
+
|
|
50
|
+
Publishing is driven by a `vX.Y.Z` tag (`.github/workflows/release.yml`)
|
|
51
|
+
and goes to PyPI through Trusted Publishing — no API token lives in the
|
|
52
|
+
repo. The `pypi` GitHub environment requires a manual approval before
|
|
53
|
+
the publish step runs.
|
|
54
|
+
|
|
55
|
+
1. Bump `__version__` in `src/termix_sdk/_version.py`.
|
|
56
|
+
2. In `CHANGELOG.md`, rename `## [Unreleased]` to `## [X.Y.Z] - YYYY-MM-DD`
|
|
57
|
+
and add a fresh empty `## [Unreleased]` above it. The release notes on
|
|
58
|
+
GitHub are that section, verbatim.
|
|
59
|
+
3. Open a PR with those two changes and merge it.
|
|
60
|
+
4. `git tag vX.Y.Z main && git push origin vX.Y.Z`.
|
|
61
|
+
5. The `preflight` job checks the tag matches `__version__` and that the
|
|
62
|
+
CHANGELOG section exists, then the full CI runs again against the tag.
|
|
63
|
+
6. Approve the `pypi` environment deployment in the Actions UI. The
|
|
64
|
+
workflow publishes to PyPI and creates the GitHub Release with
|
|
65
|
+
`dist/` attached.
|
|
66
|
+
|
|
67
|
+
## How this repo is put together
|
|
68
|
+
|
|
69
|
+
Three layers, in dependency order:
|
|
70
|
+
|
|
71
|
+
1. **`tools/spec-gen/`** (TypeScript) — mines the Termix backend source
|
|
72
|
+
for every route, its auth, its request/response shape, and the
|
|
73
|
+
Drizzle schema, and emits `spec/termix-openapi.json`. See
|
|
74
|
+
[its own strategy doc](tools/spec-gen/docs/spec-generation-strategy.md)
|
|
75
|
+
for why this exists instead of trusting Termix's own `openapi.json`.
|
|
76
|
+
Not something you normally need to touch or re-run — only when
|
|
77
|
+
targeting a new Termix release (see below).
|
|
78
|
+
|
|
79
|
+
2. **`tools/sdk-gen/generate.py`** (Python) — reads `spec/termix-
|
|
80
|
+
openapi.json` plus the curated `tools/sdk-gen/config/resource-map.json`
|
|
81
|
+
(which operations exist, what Python method name and module each
|
|
82
|
+
gets — the spec alone doesn't have good naming) and writes
|
|
83
|
+
`src/termix_sdk/resources/*.py`, `models/*.py`, `types/*.py`, and
|
|
84
|
+
`tests/contract/test_*.py`. **Never hand-edit any of those** — the
|
|
85
|
+
next `python tools/sdk-gen/generate.py` run overwrites them.
|
|
86
|
+
|
|
87
|
+
3. **Hand-written core** (`src/termix_sdk/_*.py`) — the client,
|
|
88
|
+
requestor, error hierarchy, object model, session helper, SSE/stream
|
|
89
|
+
parsing. This is what the generated code in (2) is built on top of,
|
|
90
|
+
and most of it is adapted from
|
|
91
|
+
[stripe-python](https://github.com/stripe/stripe-python) (see
|
|
92
|
+
[NOTICE](NOTICE)) rather than written from scratch.
|
|
93
|
+
|
|
94
|
+
## Adding or fixing a generated operation
|
|
95
|
+
|
|
96
|
+
1. Confirm the operation exists in `spec/termix-openapi.json` (search by
|
|
97
|
+
path). If it's missing entirely, that's a `tools/spec-gen` gap, not
|
|
98
|
+
an `sdk-gen` one.
|
|
99
|
+
2. Add (or fix) its entry in `tools/sdk-gen/config/resource-map.json`,
|
|
100
|
+
under the right module. An empty `{}` takes the default name from
|
|
101
|
+
`generate.py`'s `default_method_name()`; most operations need an
|
|
102
|
+
explicit `"method"` override — the default rule only really fits
|
|
103
|
+
plain CRUD on a bare `{id}`.
|
|
104
|
+
3. Run `python tools/sdk-gen/generate.py`. It also runs `ruff format` +
|
|
105
|
+
`ruff check --fix` on everything it just wrote, and fails loudly (not
|
|
106
|
+
silently) on:
|
|
107
|
+
- a method name that collides with another in the same module
|
|
108
|
+
(`ruff`'s `F811`, at the `ruff check --fix` step)
|
|
109
|
+
- a method name that's a Python keyword (raised directly)
|
|
110
|
+
- a `resource-map.json` entry naming an `operationId` the spec
|
|
111
|
+
doesn't have (raised directly)
|
|
112
|
+
4. Run `uv run pytest`, `uv run mypy`, `uv run ruff check .` — same as CI.
|
|
113
|
+
Commit the regenerated files: the `generated-sync` CI job regenerates
|
|
114
|
+
and fails on any diff.
|
|
115
|
+
5. A field typed `Dict[str, Any]`/`List[...]` (or a list of a generated
|
|
116
|
+
model) is, at runtime, wrapped as a nested `TermixObject`/list of
|
|
117
|
+
`TermixObject` regardless of that static annotation — compare via
|
|
118
|
+
`.to_dict()` in any test you hand-write against generated code, not
|
|
119
|
+
field-by-field attribute equality against a plain dict/list literal.
|
|
120
|
+
|
|
121
|
+
`tools/sdk-gen/generate.py`'s own module docstring lists the known
|
|
122
|
+
simplifications this makes (every request-body field optional in the
|
|
123
|
+
generated `TypedDict` regardless of the spec's `required` list, no
|
|
124
|
+
per-operation `x-requires-admin`/`x-requires-data-access` docstring
|
|
125
|
+
callouts, etc.) — read it before making a structural change to the
|
|
126
|
+
generator itself.
|
|
127
|
+
|
|
128
|
+
## Regenerating against a new Termix release
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
cd tools/spec-gen
|
|
132
|
+
npm ci
|
|
133
|
+
npm run generate -- --tag <release-tag> # or --tag latest
|
|
134
|
+
cd ../..
|
|
135
|
+
python tools/sdk-gen/generate.py
|
|
136
|
+
pytest
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Diff the new `spec/termix-openapi.json` against the previous one for new
|
|
140
|
+
or removed operations before touching `resource-map.json` — `sdk-gen`
|
|
141
|
+
only processes what the map lists, so a brand-new endpoint won't appear
|
|
142
|
+
in the SDK until it's added there.
|
|
143
|
+
|
|
144
|
+
## Testing philosophy
|
|
145
|
+
|
|
146
|
+
`tests/` (hand-written) and `tests/contract/` (generated, one test per
|
|
147
|
+
operation) both run against a scripted fake transport
|
|
148
|
+
(`tests/http_client_mock.py`) — nothing here talks to a real Termix
|
|
149
|
+
instance. That's a deliberate scope decision, not an oversight: broad
|
|
150
|
+
live-instance coverage would need real SSH hosts, Docker containers, and
|
|
151
|
+
tmux sessions to test against, plus care around endpoints with real
|
|
152
|
+
side effects (`database/export`, `encryption/regenerate-jwt`,
|
|
153
|
+
`delete-account`, fleet actions across real hosts) that shouldn't run
|
|
154
|
+
unattended in CI. If you want to exercise the SDK against a real
|
|
155
|
+
instance, do so manually — there's no `tests/live/` yet.
|
termix_sdk-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Matheus Alves
|
|
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.
|
termix_sdk-0.1.0/NOTICE
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
termix-sdk includes code adapted from stripe-python
|
|
2
|
+
(https://github.com/stripe/stripe-python), used under the MIT License:
|
|
3
|
+
|
|
4
|
+
The MIT License
|
|
5
|
+
|
|
6
|
+
Copyright (c) 2010-2018 Stripe (http://stripe.com)
|
|
7
|
+
|
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
10
|
+
in the Software without restriction, including without limitation the rights
|
|
11
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
13
|
+
furnished to do so, subject to the following conditions:
|
|
14
|
+
|
|
15
|
+
The above copyright notice and this permission notice shall be included in
|
|
16
|
+
all copies or substantial portions of the Software.
|
|
17
|
+
|
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
24
|
+
THE SOFTWARE.
|
|
25
|
+
|
|
26
|
+
Files adapted from stripe-python (each carries its own header pointing back
|
|
27
|
+
here) as of 2026-09-19, per docs/sdk-plan.md section 5:
|
|
28
|
+
|
|
29
|
+
src/termix_sdk/_error.py <- stripe/_error.py
|
|
30
|
+
src/termix_sdk/_object.py <- stripe/_stripe_object.py
|
|
31
|
+
src/termix_sdk/_response.py <- stripe/_stripe_response.py
|
|
32
|
+
src/termix_sdk/_client_options.py <- stripe/_client_options.py, stripe/_requestor_options.py
|
|
33
|
+
src/termix_sdk/_request_options.py <- stripe/_request_options.py
|
|
34
|
+
src/termix_sdk/_http_client.py <- stripe/_http_client.py
|
|
35
|
+
src/termix_sdk/_api_requestor.py <- stripe/_api_requestor.py
|
|
36
|
+
src/termix_sdk/_service.py <- stripe/_stripe_service.py
|
|
37
|
+
src/termix_sdk/_client.py <- stripe/_stripe_client.py
|
|
38
|
+
src/termix_sdk/_async_client.py <- stripe/_stripe_client.py
|
|
39
|
+
src/termix_sdk/_util.py <- stripe/_util.py
|
|
40
|
+
|
|
41
|
+
None of this implies any endorsement by or affiliation with Stripe. Termix
|
|
42
|
+
is a separate, unrelated project; termix-sdk is an independent, unofficial
|
|
43
|
+
client for it.
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: termix-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Unofficial Python client SDK for the Termix REST API
|
|
5
|
+
Project-URL: Homepage, https://github.com/MatheusAlves96/termix-sdk
|
|
6
|
+
Project-URL: Issues, https://github.com/MatheusAlves96/termix-sdk/issues
|
|
7
|
+
Project-URL: Changelog, https://github.com/MatheusAlves96/termix-sdk/blob/main/CHANGELOG.md
|
|
8
|
+
Author-email: Matheus Alves <matheusalves965@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
License-File: NOTICE
|
|
12
|
+
Keywords: api-client,sdk,ssh,termix
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Requires-Dist: httpx>=0.27
|
|
26
|
+
Requires-Dist: typing-extensions>=4.10
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest-cov>=6.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# termix-sdk
|
|
36
|
+
|
|
37
|
+
Unofficial Python client SDK for the [Termix](https://github.com/Termix-SSH/Termix) REST API.
|
|
38
|
+
|
|
39
|
+
Generated from a spec this repo derives directly from the Termix backend
|
|
40
|
+
source, not from Termix's own hand-written `openapi.json` — see
|
|
41
|
+
[Generating our own Termix API spec](tools/spec-gen/docs/spec-generation-strategy.md)
|
|
42
|
+
for why. As of `release-2.7.1-tag`, that covers **477 of 491** real
|
|
43
|
+
endpoints; the rest are either genuinely out of scope (browser-redirect
|
|
44
|
+
OIDC flows, internal-only routes) or need a documented request/response
|
|
45
|
+
shape the spec doesn't have (3 `file_manager` streaming-upload endpoints).
|
|
46
|
+
|
|
47
|
+
## Install
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install termix-sdk
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Requires Python 3.10+. The only runtime dependencies are `httpx` and
|
|
54
|
+
`typing-extensions`.
|
|
55
|
+
|
|
56
|
+
## Quickstart
|
|
57
|
+
|
|
58
|
+
An API key (`tmx_...`, created in Termix under Settings → API Keys) is the
|
|
59
|
+
recommended way to authenticate — it skips the login/TOTP dance entirely:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from termix_sdk import TermixClient
|
|
63
|
+
|
|
64
|
+
client = TermixClient(base_url="https://termix.example.com", api_key="tmx_...")
|
|
65
|
+
|
|
66
|
+
for host in client.hosts.list():
|
|
67
|
+
print(host.name, host.ip)
|
|
68
|
+
|
|
69
|
+
client.close()
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`TermixClient` is also a context manager:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
with TermixClient(base_url="https://termix.example.com", api_key="tmx_...") as client:
|
|
76
|
+
host = client.hosts.retrieve("42")
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Username/password login
|
|
80
|
+
|
|
81
|
+
`TermixClient.login()` handles Termix's login quirk for you: the backend
|
|
82
|
+
only puts the JWT in the response body (rather than only in a cookie a
|
|
83
|
+
Python client can't use) when the request looks like it came from a
|
|
84
|
+
native app, so this always sends that header.
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from termix_sdk import TermixClient, PendingTOTP
|
|
88
|
+
|
|
89
|
+
result = TermixClient.login(
|
|
90
|
+
base_url="https://termix.example.com",
|
|
91
|
+
username="alice",
|
|
92
|
+
password="...",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
if isinstance(result, PendingTOTP):
|
|
96
|
+
# The account has TOTP enabled and this device isn't trusted yet.
|
|
97
|
+
client = result.verify(input("6-digit code: "))
|
|
98
|
+
else:
|
|
99
|
+
client = result
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Async
|
|
103
|
+
|
|
104
|
+
Every resource has an async twin, via a separate client rather than
|
|
105
|
+
`_async` suffixed methods:
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
import asyncio
|
|
109
|
+
from termix_sdk import AsyncTermixClient
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
async def main():
|
|
113
|
+
async with AsyncTermixClient(
|
|
114
|
+
base_url="https://termix.example.com", api_key="tmx_..."
|
|
115
|
+
) as client:
|
|
116
|
+
hosts = await client.hosts.list()
|
|
117
|
+
print([h.name for h in hosts])
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
asyncio.run(main())
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### SSH-backed sessions (file manager, Docker)
|
|
124
|
+
|
|
125
|
+
`file_manager` and `docker` open an SSH-backed session server-side
|
|
126
|
+
before you can call anything else on them. `ssh_session()` (and its
|
|
127
|
+
async twin `async_ssh_session()`) manage that lifecycle — connect,
|
|
128
|
+
optional keepalive, disconnect on exit, even on error:
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from termix_sdk import ssh_session
|
|
132
|
+
|
|
133
|
+
with ssh_session(client.file_manager, host_id=42) as fm:
|
|
134
|
+
for entry in fm.list_files(path="/"):
|
|
135
|
+
print(entry)
|
|
136
|
+
# disconnected automatically here
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Every method is still callable directly with an explicit `session_id` if
|
|
140
|
+
you'd rather manage the lifecycle yourself — `ssh_session()` is a
|
|
141
|
+
convenience layer on top, not a requirement.
|
|
142
|
+
|
|
143
|
+
### Errors
|
|
144
|
+
|
|
145
|
+
Every non-2xx response raises a typed subclass of `TermixError`:
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
from termix_sdk import AuthenticationError, NotFoundError, RateLimitError, TermixError
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
client.hosts.retrieve("does-not-exist")
|
|
152
|
+
except NotFoundError as e:
|
|
153
|
+
print(e.code, e.details)
|
|
154
|
+
except RateLimitError as e:
|
|
155
|
+
print("retry after", e.remaining_time, "seconds")
|
|
156
|
+
except TermixError as e:
|
|
157
|
+
print(e.http_status, e)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## What's generated vs. hand-written
|
|
161
|
+
|
|
162
|
+
`src/termix_sdk/resources/`, `models/`, and `types/` are generated by
|
|
163
|
+
`tools/sdk-gen/generate.py` from `spec/termix-openapi.json` and
|
|
164
|
+
`tools/sdk-gen/config/resource-map.json` — don't hand-edit them, changes
|
|
165
|
+
will be overwritten on the next run. Everything else (`_client.py`,
|
|
166
|
+
`_error.py`, `_object.py`, `_session.py`, `_sse.py`, ...) is hand-written
|
|
167
|
+
core, adapted from [stripe/stripe-python](https://github.com/stripe/stripe-python)
|
|
168
|
+
where noted — see [NOTICE](NOTICE) for the MIT attribution this carries.
|
|
169
|
+
|
|
170
|
+
## Development
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
pip install -e ".[dev]"
|
|
174
|
+
pytest
|
|
175
|
+
ruff check .
|
|
176
|
+
mypy
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for how the spec/generator/tests
|
|
180
|
+
fit together and how to regenerate the SDK after a Termix release.
|
|
181
|
+
|
|
182
|
+
## Documentation
|
|
183
|
+
|
|
184
|
+
- [Generating our own Termix API spec](tools/spec-gen/docs/spec-generation-strategy.md): why the SDK does not rely on the official `openapi.json`, and how the spec is derived from the Termix backend source (route discovery, auth, typed request bodies, every response per status code, Drizzle schema, test examples).
|
|
185
|
+
- [CHANGELOG.md](CHANGELOG.md)
|
|
186
|
+
- [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
187
|
+
|
|
188
|
+
## License
|
|
189
|
+
|
|
190
|
+
MIT — see [LICENSE](LICENSE). Portions adapted from stripe-python are
|
|
191
|
+
also MIT; see [NOTICE](NOTICE).
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# termix-sdk
|
|
2
|
+
|
|
3
|
+
Unofficial Python client SDK for the [Termix](https://github.com/Termix-SSH/Termix) REST API.
|
|
4
|
+
|
|
5
|
+
Generated from a spec this repo derives directly from the Termix backend
|
|
6
|
+
source, not from Termix's own hand-written `openapi.json` — see
|
|
7
|
+
[Generating our own Termix API spec](tools/spec-gen/docs/spec-generation-strategy.md)
|
|
8
|
+
for why. As of `release-2.7.1-tag`, that covers **477 of 491** real
|
|
9
|
+
endpoints; the rest are either genuinely out of scope (browser-redirect
|
|
10
|
+
OIDC flows, internal-only routes) or need a documented request/response
|
|
11
|
+
shape the spec doesn't have (3 `file_manager` streaming-upload endpoints).
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install termix-sdk
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Requires Python 3.10+. The only runtime dependencies are `httpx` and
|
|
20
|
+
`typing-extensions`.
|
|
21
|
+
|
|
22
|
+
## Quickstart
|
|
23
|
+
|
|
24
|
+
An API key (`tmx_...`, created in Termix under Settings → API Keys) is the
|
|
25
|
+
recommended way to authenticate — it skips the login/TOTP dance entirely:
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from termix_sdk import TermixClient
|
|
29
|
+
|
|
30
|
+
client = TermixClient(base_url="https://termix.example.com", api_key="tmx_...")
|
|
31
|
+
|
|
32
|
+
for host in client.hosts.list():
|
|
33
|
+
print(host.name, host.ip)
|
|
34
|
+
|
|
35
|
+
client.close()
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`TermixClient` is also a context manager:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
with TermixClient(base_url="https://termix.example.com", api_key="tmx_...") as client:
|
|
42
|
+
host = client.hosts.retrieve("42")
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Username/password login
|
|
46
|
+
|
|
47
|
+
`TermixClient.login()` handles Termix's login quirk for you: the backend
|
|
48
|
+
only puts the JWT in the response body (rather than only in a cookie a
|
|
49
|
+
Python client can't use) when the request looks like it came from a
|
|
50
|
+
native app, so this always sends that header.
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from termix_sdk import TermixClient, PendingTOTP
|
|
54
|
+
|
|
55
|
+
result = TermixClient.login(
|
|
56
|
+
base_url="https://termix.example.com",
|
|
57
|
+
username="alice",
|
|
58
|
+
password="...",
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
if isinstance(result, PendingTOTP):
|
|
62
|
+
# The account has TOTP enabled and this device isn't trusted yet.
|
|
63
|
+
client = result.verify(input("6-digit code: "))
|
|
64
|
+
else:
|
|
65
|
+
client = result
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Async
|
|
69
|
+
|
|
70
|
+
Every resource has an async twin, via a separate client rather than
|
|
71
|
+
`_async` suffixed methods:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
import asyncio
|
|
75
|
+
from termix_sdk import AsyncTermixClient
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
async def main():
|
|
79
|
+
async with AsyncTermixClient(
|
|
80
|
+
base_url="https://termix.example.com", api_key="tmx_..."
|
|
81
|
+
) as client:
|
|
82
|
+
hosts = await client.hosts.list()
|
|
83
|
+
print([h.name for h in hosts])
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
asyncio.run(main())
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### SSH-backed sessions (file manager, Docker)
|
|
90
|
+
|
|
91
|
+
`file_manager` and `docker` open an SSH-backed session server-side
|
|
92
|
+
before you can call anything else on them. `ssh_session()` (and its
|
|
93
|
+
async twin `async_ssh_session()`) manage that lifecycle — connect,
|
|
94
|
+
optional keepalive, disconnect on exit, even on error:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from termix_sdk import ssh_session
|
|
98
|
+
|
|
99
|
+
with ssh_session(client.file_manager, host_id=42) as fm:
|
|
100
|
+
for entry in fm.list_files(path="/"):
|
|
101
|
+
print(entry)
|
|
102
|
+
# disconnected automatically here
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Every method is still callable directly with an explicit `session_id` if
|
|
106
|
+
you'd rather manage the lifecycle yourself — `ssh_session()` is a
|
|
107
|
+
convenience layer on top, not a requirement.
|
|
108
|
+
|
|
109
|
+
### Errors
|
|
110
|
+
|
|
111
|
+
Every non-2xx response raises a typed subclass of `TermixError`:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from termix_sdk import AuthenticationError, NotFoundError, RateLimitError, TermixError
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
client.hosts.retrieve("does-not-exist")
|
|
118
|
+
except NotFoundError as e:
|
|
119
|
+
print(e.code, e.details)
|
|
120
|
+
except RateLimitError as e:
|
|
121
|
+
print("retry after", e.remaining_time, "seconds")
|
|
122
|
+
except TermixError as e:
|
|
123
|
+
print(e.http_status, e)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## What's generated vs. hand-written
|
|
127
|
+
|
|
128
|
+
`src/termix_sdk/resources/`, `models/`, and `types/` are generated by
|
|
129
|
+
`tools/sdk-gen/generate.py` from `spec/termix-openapi.json` and
|
|
130
|
+
`tools/sdk-gen/config/resource-map.json` — don't hand-edit them, changes
|
|
131
|
+
will be overwritten on the next run. Everything else (`_client.py`,
|
|
132
|
+
`_error.py`, `_object.py`, `_session.py`, `_sse.py`, ...) is hand-written
|
|
133
|
+
core, adapted from [stripe/stripe-python](https://github.com/stripe/stripe-python)
|
|
134
|
+
where noted — see [NOTICE](NOTICE) for the MIT attribution this carries.
|
|
135
|
+
|
|
136
|
+
## Development
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
pip install -e ".[dev]"
|
|
140
|
+
pytest
|
|
141
|
+
ruff check .
|
|
142
|
+
mypy
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for how the spec/generator/tests
|
|
146
|
+
fit together and how to regenerate the SDK after a Termix release.
|
|
147
|
+
|
|
148
|
+
## Documentation
|
|
149
|
+
|
|
150
|
+
- [Generating our own Termix API spec](tools/spec-gen/docs/spec-generation-strategy.md): why the SDK does not rely on the official `openapi.json`, and how the spec is derived from the Termix backend source (route discovery, auth, typed request bodies, every response per status code, Drizzle schema, test examples).
|
|
151
|
+
- [CHANGELOG.md](CHANGELOG.md)
|
|
152
|
+
- [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
153
|
+
|
|
154
|
+
## License
|
|
155
|
+
|
|
156
|
+
MIT — see [LICENSE](LICENSE). Portions adapted from stripe-python are
|
|
157
|
+
also MIT; see [NOTICE](NOTICE).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
Each script expects a real, reachable Termix instance and reads its
|
|
4
|
+
connection details from environment variables — nothing here talks to a
|
|
5
|
+
mock. Set at least:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
export TERMIX_BASE_URL="https://termix.example.com"
|
|
9
|
+
export TERMIX_API_KEY="tmx_..." # sync_quickstart.py, async_quickstart.py, file_manager_session.py
|
|
10
|
+
export TERMIX_USERNAME="alice" # login_with_totp.py
|
|
11
|
+
export TERMIX_PASSWORD="..." # login_with_totp.py
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
| Script | Shows |
|
|
15
|
+
|---|---|
|
|
16
|
+
| [`sync_quickstart.py`](sync_quickstart.py) | `TermixClient`, listing hosts, error handling |
|
|
17
|
+
| [`async_quickstart.py`](async_quickstart.py) | `AsyncTermixClient`, the same thing with `asyncio` |
|
|
18
|
+
| [`login_with_totp.py`](login_with_totp.py) | `TermixClient.login()`, handling `PendingTOTP` |
|
|
19
|
+
| [`file_manager_session.py`](file_manager_session.py) | `ssh_session()` against `client.file_manager` |
|
|
20
|
+
|
|
21
|
+
Run any of them with `python examples/<script>.py`.
|