Metadata-Version: 2.5
Name: termix-sdk
Version: 0.1.1
Summary: Unofficial Python client SDK for the Termix REST API
Project-URL: Homepage, https://github.com/MatheusAlves96/termix-sdk
Project-URL: Issues, https://github.com/MatheusAlves96/termix-sdk/issues
Project-URL: Changelog, https://github.com/MatheusAlves96/termix-sdk/blob/main/CHANGELOG.md
Author-email: Matheus Alves <matheusalves965@gmail.com>
License-Expression: MIT
License-File: LICENSE
License-File: NOTICE
Keywords: api-client,sdk,ssh,termix
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: typing-extensions>=4.10
Provides-Extra: dev
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
Requires-Dist: pytest-cov>=6.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Description-Content-Type: text/markdown

# termix-sdk

Unofficial Python client SDK for the [Termix](https://github.com/Termix-SSH/Termix) REST API.

Generated from a spec this repo derives directly from the Termix backend
source, not from Termix's own hand-written `openapi.json` — see
[Generating our own Termix API spec](tools/spec-gen/docs/spec-generation-strategy.md)
for why. As of `release-2.7.1-tag`, that covers **477 of 491** real
endpoints; the rest are either genuinely out of scope (browser-redirect
OIDC flows, internal-only routes) or need a documented request/response
shape the spec doesn't have (3 `file_manager` streaming-upload endpoints).

## Install

```bash
pip install termix-sdk
```

Requires Python 3.10+. The only runtime dependencies are `httpx` and
`typing-extensions`.

## Quickstart

An API key (`tmx_...`, created in Termix under Settings → API Keys) is the
recommended way to authenticate — it skips the login/TOTP dance entirely:

```python
from termix_sdk import TermixClient

client = TermixClient(base_url="https://termix.example.com", api_key="tmx_...")

for host in client.hosts.list():
    print(host.name, host.ip)

client.close()
```

`TermixClient` is also a context manager:

```python
with TermixClient(base_url="https://termix.example.com", api_key="tmx_...") as client:
    host = client.hosts.retrieve("42")
```

### Username/password login

`TermixClient.login()` handles Termix's login quirk for you: the backend
only puts the JWT in the response body (rather than only in a cookie a
Python client can't use) when the request looks like it came from a
native app, so this always sends that header.

```python
from termix_sdk import TermixClient, PendingTOTP

result = TermixClient.login(
    base_url="https://termix.example.com",
    username="alice",
    password="...",
)

if isinstance(result, PendingTOTP):
    # The account has TOTP enabled and this device isn't trusted yet.
    client = result.verify(input("6-digit code: "))
else:
    client = result
```

### Async

Every resource has an async twin, via a separate client rather than
`_async` suffixed methods:

```python
import asyncio
from termix_sdk import AsyncTermixClient


async def main():
    async with AsyncTermixClient(
        base_url="https://termix.example.com", api_key="tmx_..."
    ) as client:
        hosts = await client.hosts.list()
        print([h.name for h in hosts])


asyncio.run(main())
```

### SSH-backed sessions (file manager, Docker)

`file_manager` and `docker` open an SSH-backed session server-side
before you can call anything else on them. `ssh_session()` (and its
async twin `async_ssh_session()`) manage that lifecycle — connect,
optional keepalive, disconnect on exit, even on error:

```python
from termix_sdk import ssh_session

with ssh_session(client.file_manager, host_id=42) as fm:
    for entry in fm.list_files(path="/"):
        print(entry)
# disconnected automatically here
```

Every method is still callable directly with an explicit `session_id` if
you'd rather manage the lifecycle yourself — `ssh_session()` is a
convenience layer on top, not a requirement.

### Errors

Every non-2xx response raises a typed subclass of `TermixError`:

```python
from termix_sdk import AuthenticationError, NotFoundError, RateLimitError, TermixError

try:
    client.hosts.retrieve("does-not-exist")
except NotFoundError as e:
    print(e.code, e.details)
except RateLimitError as e:
    print("retry after", e.remaining_time, "seconds")
except TermixError as e:
    print(e.http_status, e)
```

## What's generated vs. hand-written

`src/termix_sdk/resources/`, `models/`, and `types/` are generated by
`tools/sdk-gen/generate.py` from `spec/termix-openapi.json` and
`tools/sdk-gen/config/resource-map.json` — don't hand-edit them, changes
will be overwritten on the next run. Everything else (`_client.py`,
`_error.py`, `_object.py`, `_session.py`, `_sse.py`, ...) is hand-written
core, adapted from [stripe/stripe-python](https://github.com/stripe/stripe-python)
where noted — see [NOTICE](NOTICE) for the MIT attribution this carries.

## Development

```bash
pip install -e ".[dev]"
pytest
ruff check .
mypy
```

See [CONTRIBUTING.md](CONTRIBUTING.md) for how the spec/generator/tests
fit together and how to regenerate the SDK after a Termix release.

## Documentation

- [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).
- [CHANGELOG.md](CHANGELOG.md)
- [CONTRIBUTING.md](CONTRIBUTING.md)

## License

MIT — see [LICENSE](LICENSE). Portions adapted from stripe-python are
also MIT; see [NOTICE](NOTICE).
