iceberg-subzero 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,2 @@
1
+ # Auto detect text files and perform LF normalization
2
+ * text=auto
@@ -0,0 +1,153 @@
1
+ Metadata-Version: 2.4
2
+ Name: iceberg-subzero
3
+ Version: 0.1.0
4
+ Summary: Python client for the Subzero tokenization vault and LLM proxy
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: httpx>=0.28.0
7
+ Provides-Extra: auth
8
+ Requires-Dist: pyotp>=2.9.0; extra == 'auth'
9
+ Provides-Extra: dev
10
+ Requires-Dist: openai>=1.0.0; extra == 'dev'
11
+ Requires-Dist: pyotp>=2.9.0; extra == 'dev'
12
+ Requires-Dist: pytest>=8.3.0; extra == 'dev'
13
+ Requires-Dist: respx>=0.22.0; extra == 'dev'
14
+ Provides-Extra: openai
15
+ Requires-Dist: openai>=1.0.0; extra == 'openai'
16
+ Description-Content-Type: text/markdown
17
+
18
+ # Subzero Python SDK
19
+
20
+ Thin Python client for the [Subzero](../api/) tokenization vault and LLM proxy.
21
+
22
+ ## Install
23
+
24
+ ```bash
25
+ cd python-sdk
26
+ pip install -e ".[dev,openai,auth]"
27
+ ```
28
+
29
+ The optional `auth` extra adds `pyotp` for TOTP code generation during dashboard login.
30
+
31
+ ## Two auth planes
32
+
33
+ | Plane | Credential | Use for |
34
+ |-------|------------|---------|
35
+ | **Server integration** | API key (`sz_live_...`) | `tokenize`, `search`, `reveal`, `proxy`, tenant admin after bootstrap |
36
+ | **Dashboard / human** | JWT access token | `auth.*`, `members.*`, `admin.create_tenant`, `admin.get_tenant` |
37
+
38
+ Pass an API key, an access token, or both when constructing the client:
39
+
40
+ ```python
41
+ from subzero import SubzeroClient
42
+
43
+ # Vault + proxy (server-side)
44
+ vault = SubzeroClient(api_key="sz_live_...", base_url="http://127.0.0.1:8000")
45
+
46
+ # Dashboard session (platform or tenant admin JWT)
47
+ dashboard = SubzeroClient(access_token="eyJ...", base_url="http://127.0.0.1:8000")
48
+ ```
49
+
50
+ ## Quick start (vault)
51
+
52
+ ```python
53
+ from subzero import SubzeroClient
54
+
55
+ client = SubzeroClient(
56
+ api_key="sz_live_...",
57
+ base_url="http://127.0.0.1:8000",
58
+ )
59
+ client.ready()
60
+
61
+ token = client.tokenize("SSN", "123-45-6789").token
62
+ value = client.reveal(token).value
63
+ ```
64
+
65
+ ## Platform admin login (tenant provisioning)
66
+
67
+ `POST /v1/tenants` requires a platform-admin JWT. Log in with MFA, then create tenants:
68
+
69
+ ```python
70
+ from subzero import SubzeroClient
71
+
72
+ client = SubzeroClient(base_url="http://127.0.0.1:8000")
73
+ client.ready()
74
+
75
+ client.auth.login_platform(
76
+ email="admin@iceberg.local",
77
+ password="...",
78
+ totp_code="123456", # or totp_secret="BASE32..." with pip install 'subzero[auth]'
79
+ )
80
+
81
+ tenant = client.admin.create_tenant(name="Acme", slug="acme")
82
+ admin_key = tenant.bootstrap_api_key
83
+
84
+ # Switch to tenant admin API key for day-to-day vault/proxy setup
85
+ admin = SubzeroClient(api_key=admin_key, base_url="http://127.0.0.1:8000")
86
+ admin.admin.create_entity_type(tenant.id, name="SSN", deterministic=True)
87
+ ```
88
+
89
+ Tenant member invites and management use `client.members` with a tenant-admin or platform-admin JWT.
90
+
91
+ ## API key scopes
92
+
93
+ | Scope | Vault methods | Notes |
94
+ |-------|---------------|-------|
95
+ | `tokenize` | `tokenize`, `search` | No plaintext return |
96
+ | `reveal` | `reveal` | Server-side plaintext reveal; requires matching policy rule |
97
+ | `reveal_grant` | `create_reveal_grant` | Mint browser reveal grants only; requires matching policy rule |
98
+ | `proxy` | `proxy.chat.completions` | In-flight tokenization |
99
+ | `admin` | All of the above + `admin.*` + `delete_token` | Bypasses reveal policy |
100
+
101
+ **Browser reveal:** the iframe calls `POST /v1/browser/reveal` with a server-minted grant. Your BFF uses a **`reveal_grant`** key to call `create_reveal_grant(token, client_public_key_jwk=..., allowed_origin=...)`. Keep **`reveal`** keys for server pipelines that need `client.reveal(token).value`.
102
+
103
+ ```python
104
+ grant = client.create_reveal_grant(
105
+ token,
106
+ client_public_key_jwk=jwk_from_iframe,
107
+ allowed_origin="https://app.yourcompany.com",
108
+ )
109
+ ```
110
+
111
+ **Delete requires admin.** There is no delegatable delete-scoped API key at the HTTP layer. `delete_token()` needs an admin key even though policy rules support a `delete` action at the service layer.
112
+
113
+ ## Proxy vs reveal vs detokenize
114
+
115
+ - **Vault reveal:** `client.reveal(token)` — `POST /v1/reveal`, reveal-scoped key + policy
116
+ - **Proxy chat:** `client.proxy.chat.completions(...)` — tokenizes declared patterns in-flight
117
+ - **Proxy detokenize:** pass `detokenize=True` or use OpenAI helper below — governed by reveal policy for the proxy key, not the reveal endpoint
118
+
119
+ ```python
120
+ from subzero import create_openai_client
121
+
122
+ client = create_openai_client(
123
+ api_key="sz_live_...", # Subzero proxy key
124
+ base_url="http://127.0.0.1:8000/v1",
125
+ detokenize=True, # X-Subzero-Detokenize: true via default_headers
126
+ )
127
+ client.chat.completions.create(model="gpt-4o", stream=False, messages=[...])
128
+ ```
129
+
130
+ ## Examples
131
+
132
+ With the API running (`docker compose up` or `uvicorn`):
133
+
134
+ ```bash
135
+ export SUBZERO_PLATFORM_EMAIL=admin@iceberg.local
136
+ export SUBZERO_PLATFORM_PASSWORD=...
137
+ export SUBZERO_TOTP_CODE=123456 # or SUBZERO_TOTP_SECRET / SUBZERO_ACCESS_TOKEN
138
+
139
+ python examples/hero_demo.py
140
+ python examples/vault_loop.py
141
+ ```
142
+
143
+ Set `SUBZERO_BASE_URL` and `OPENAI_API_KEY` as needed.
144
+
145
+ ## Tests
146
+
147
+ ```bash
148
+ pytest
149
+ ```
150
+
151
+ ## PyPI
152
+
153
+ Local editable install only for now. `# TODO: twine upload` when ready to publish.
@@ -0,0 +1,136 @@
1
+ # Subzero Python SDK
2
+
3
+ Thin Python client for the [Subzero](../api/) tokenization vault and LLM proxy.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ cd python-sdk
9
+ pip install -e ".[dev,openai,auth]"
10
+ ```
11
+
12
+ The optional `auth` extra adds `pyotp` for TOTP code generation during dashboard login.
13
+
14
+ ## Two auth planes
15
+
16
+ | Plane | Credential | Use for |
17
+ |-------|------------|---------|
18
+ | **Server integration** | API key (`sz_live_...`) | `tokenize`, `search`, `reveal`, `proxy`, tenant admin after bootstrap |
19
+ | **Dashboard / human** | JWT access token | `auth.*`, `members.*`, `admin.create_tenant`, `admin.get_tenant` |
20
+
21
+ Pass an API key, an access token, or both when constructing the client:
22
+
23
+ ```python
24
+ from subzero import SubzeroClient
25
+
26
+ # Vault + proxy (server-side)
27
+ vault = SubzeroClient(api_key="sz_live_...", base_url="http://127.0.0.1:8000")
28
+
29
+ # Dashboard session (platform or tenant admin JWT)
30
+ dashboard = SubzeroClient(access_token="eyJ...", base_url="http://127.0.0.1:8000")
31
+ ```
32
+
33
+ ## Quick start (vault)
34
+
35
+ ```python
36
+ from subzero import SubzeroClient
37
+
38
+ client = SubzeroClient(
39
+ api_key="sz_live_...",
40
+ base_url="http://127.0.0.1:8000",
41
+ )
42
+ client.ready()
43
+
44
+ token = client.tokenize("SSN", "123-45-6789").token
45
+ value = client.reveal(token).value
46
+ ```
47
+
48
+ ## Platform admin login (tenant provisioning)
49
+
50
+ `POST /v1/tenants` requires a platform-admin JWT. Log in with MFA, then create tenants:
51
+
52
+ ```python
53
+ from subzero import SubzeroClient
54
+
55
+ client = SubzeroClient(base_url="http://127.0.0.1:8000")
56
+ client.ready()
57
+
58
+ client.auth.login_platform(
59
+ email="admin@iceberg.local",
60
+ password="...",
61
+ totp_code="123456", # or totp_secret="BASE32..." with pip install 'subzero[auth]'
62
+ )
63
+
64
+ tenant = client.admin.create_tenant(name="Acme", slug="acme")
65
+ admin_key = tenant.bootstrap_api_key
66
+
67
+ # Switch to tenant admin API key for day-to-day vault/proxy setup
68
+ admin = SubzeroClient(api_key=admin_key, base_url="http://127.0.0.1:8000")
69
+ admin.admin.create_entity_type(tenant.id, name="SSN", deterministic=True)
70
+ ```
71
+
72
+ Tenant member invites and management use `client.members` with a tenant-admin or platform-admin JWT.
73
+
74
+ ## API key scopes
75
+
76
+ | Scope | Vault methods | Notes |
77
+ |-------|---------------|-------|
78
+ | `tokenize` | `tokenize`, `search` | No plaintext return |
79
+ | `reveal` | `reveal` | Server-side plaintext reveal; requires matching policy rule |
80
+ | `reveal_grant` | `create_reveal_grant` | Mint browser reveal grants only; requires matching policy rule |
81
+ | `proxy` | `proxy.chat.completions` | In-flight tokenization |
82
+ | `admin` | All of the above + `admin.*` + `delete_token` | Bypasses reveal policy |
83
+
84
+ **Browser reveal:** the iframe calls `POST /v1/browser/reveal` with a server-minted grant. Your BFF uses a **`reveal_grant`** key to call `create_reveal_grant(token, client_public_key_jwk=..., allowed_origin=...)`. Keep **`reveal`** keys for server pipelines that need `client.reveal(token).value`.
85
+
86
+ ```python
87
+ grant = client.create_reveal_grant(
88
+ token,
89
+ client_public_key_jwk=jwk_from_iframe,
90
+ allowed_origin="https://app.yourcompany.com",
91
+ )
92
+ ```
93
+
94
+ **Delete requires admin.** There is no delegatable delete-scoped API key at the HTTP layer. `delete_token()` needs an admin key even though policy rules support a `delete` action at the service layer.
95
+
96
+ ## Proxy vs reveal vs detokenize
97
+
98
+ - **Vault reveal:** `client.reveal(token)` — `POST /v1/reveal`, reveal-scoped key + policy
99
+ - **Proxy chat:** `client.proxy.chat.completions(...)` — tokenizes declared patterns in-flight
100
+ - **Proxy detokenize:** pass `detokenize=True` or use OpenAI helper below — governed by reveal policy for the proxy key, not the reveal endpoint
101
+
102
+ ```python
103
+ from subzero import create_openai_client
104
+
105
+ client = create_openai_client(
106
+ api_key="sz_live_...", # Subzero proxy key
107
+ base_url="http://127.0.0.1:8000/v1",
108
+ detokenize=True, # X-Subzero-Detokenize: true via default_headers
109
+ )
110
+ client.chat.completions.create(model="gpt-4o", stream=False, messages=[...])
111
+ ```
112
+
113
+ ## Examples
114
+
115
+ With the API running (`docker compose up` or `uvicorn`):
116
+
117
+ ```bash
118
+ export SUBZERO_PLATFORM_EMAIL=admin@iceberg.local
119
+ export SUBZERO_PLATFORM_PASSWORD=...
120
+ export SUBZERO_TOTP_CODE=123456 # or SUBZERO_TOTP_SECRET / SUBZERO_ACCESS_TOKEN
121
+
122
+ python examples/hero_demo.py
123
+ python examples/vault_loop.py
124
+ ```
125
+
126
+ Set `SUBZERO_BASE_URL` and `OPENAI_API_KEY` as needed.
127
+
128
+ ## Tests
129
+
130
+ ```bash
131
+ pytest
132
+ ```
133
+
134
+ ## PyPI
135
+
136
+ Local editable install only for now. `# TODO: twine upload` when ready to publish.
@@ -0,0 +1,42 @@
1
+ """Shared bootstrap helpers for SDK examples."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import sys
7
+
8
+ from subzero import SubzeroClient
9
+ from subzero.exceptions import SubzeroAPIError
10
+
11
+
12
+ def platform_bootstrap_client(base_url: str) -> SubzeroClient:
13
+ """Return a client authenticated as platform admin (JWT on client.access_token)."""
14
+ access_token = os.environ.get("SUBZERO_ACCESS_TOKEN")
15
+ if access_token:
16
+ client = SubzeroClient(access_token=access_token, base_url=base_url)
17
+ client.ready()
18
+ return client
19
+
20
+ email = os.environ.get("SUBZERO_PLATFORM_EMAIL")
21
+ password = os.environ.get("SUBZERO_PLATFORM_PASSWORD")
22
+ if not email or not password:
23
+ print(
24
+ "Set SUBZERO_PLATFORM_EMAIL and SUBZERO_PLATFORM_PASSWORD, "
25
+ "or SUBZERO_ACCESS_TOKEN for an existing session.",
26
+ file=sys.stderr,
27
+ )
28
+ sys.exit(1)
29
+
30
+ client = SubzeroClient(base_url=base_url)
31
+ client.ready()
32
+ try:
33
+ client.auth.login_platform(
34
+ email=email,
35
+ password=password,
36
+ totp_code=os.environ.get("SUBZERO_TOTP_CODE"),
37
+ totp_secret=os.environ.get("SUBZERO_TOTP_SECRET"),
38
+ )
39
+ except SubzeroAPIError as exc:
40
+ print(f"Platform login failed: {exc}", file=sys.stderr)
41
+ sys.exit(1)
42
+ return client
@@ -0,0 +1,67 @@
1
+ """Elements SDK setup: publishable key + tokenize + reveal grant mint."""
2
+
3
+ import base64
4
+
5
+ from cryptography.hazmat.primitives.asymmetric import rsa
6
+
7
+ from subzero import SubzeroClient
8
+
9
+ from _bootstrap import admin_client, tenant_id
10
+
11
+
12
+ def _rsa_public_jwk() -> dict:
13
+ public_key = rsa.generate_private_key(public_exponent=65537, key_size=2048).public_key()
14
+ numbers = public_key.public_numbers()
15
+
16
+ def int_to_b64url(val: int) -> str:
17
+ byte_length = (val.bit_length() + 7) // 8
18
+ return base64.urlsafe_b64encode(val.to_bytes(byte_length, "big")).rstrip(b"=").decode()
19
+
20
+ return {
21
+ "kty": "RSA",
22
+ "n": int_to_b64url(numbers.n),
23
+ "e": int_to_b64url(numbers.e),
24
+ "alg": "RSA-OAEP-256",
25
+ "ext": True,
26
+ }
27
+
28
+
29
+ def main() -> None:
30
+ admin = admin_client()
31
+ admin.ready()
32
+ tid = tenant_id(admin)
33
+
34
+ pub = admin.admin.create_publishable_key(
35
+ tid,
36
+ name="demo-browser",
37
+ allowed_origins=["http://localhost:3000", "http://localhost"],
38
+ )
39
+ print(f"Publishable key: {pub.key}")
40
+
41
+ token = admin.tokenize("SSN", "123-45-6789").token
42
+ print(f"Token (server-side): {token}")
43
+
44
+ grant_key = admin.admin.create_api_key(tid, name="reveal-grant-demo", scope="reveal_grant")
45
+ policies = admin.admin.list_policies(tid)
46
+ if not any(p.principal == f"api_key:{grant_key.id}" for p in policies):
47
+ admin.admin.create_policy(
48
+ tid,
49
+ api_key_id=grant_key.id,
50
+ entity_type="SSN",
51
+ action="reveal",
52
+ )
53
+
54
+ grant_client = SubzeroClient(api_key=grant_key.secret, base_url=admin._base_url)
55
+ grant = grant_client.create_reveal_grant(
56
+ token,
57
+ client_public_key_jwk=_rsa_public_jwk(),
58
+ allowed_origin="http://localhost:3000",
59
+ )
60
+ print(f"Reveal grant (one-time): {grant.grant_id} expires {grant.expires_at}")
61
+
62
+ grant_client.close()
63
+ admin.close()
64
+
65
+
66
+ if __name__ == "__main__":
67
+ main()
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env python3
2
+ """Subzero LLM proxy hero demo using the Python SDK."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import json
7
+ import os
8
+ import sys
9
+
10
+ from subzero import SubzeroClient
11
+
12
+ from _bootstrap import platform_bootstrap_client
13
+
14
+ BASE_URL = os.environ.get("SUBZERO_BASE_URL", "http://127.0.0.1:8000")
15
+ UPSTREAM_OPENAI_KEY = os.environ.get("OPENAI_API_KEY", "sk-demo-replace-me")
16
+ SSN_PATTERN = r"\b\d{3}-\d{2}-\d{4}\b"
17
+
18
+
19
+ def main() -> int:
20
+ bootstrap = platform_bootstrap_client(BASE_URL)
21
+
22
+ tenant = bootstrap.admin.create_tenant(name="Proxy Demo", slug="proxy-demo-sdk")
23
+ tenant_id = tenant.id
24
+ admin_key = tenant.bootstrap_api_key
25
+ if not admin_key:
26
+ print("Expected bootstrap_api_key from tenant signup", file=sys.stderr)
27
+ return 1
28
+
29
+ admin = SubzeroClient(api_key=admin_key, base_url=BASE_URL)
30
+ admin.admin.create_entity_type(
31
+ tenant_id,
32
+ name="SSN",
33
+ deterministic=True,
34
+ match_pattern=SSN_PATTERN,
35
+ )
36
+ admin.admin.create_llm_config(
37
+ tenant_id,
38
+ base_url="https://api.openai.com/v1",
39
+ api_key=UPSTREAM_OPENAI_KEY,
40
+ )
41
+ proxy_key_result = admin.admin.create_api_key(tenant_id, name="proxy", scope="proxy")
42
+ proxy_key = proxy_key_result.secret
43
+ if not proxy_key:
44
+ print("Expected proxy key secret", file=sys.stderr)
45
+ return 1
46
+
47
+ print("Subzero proxy key (use as OpenAI client api_key):")
48
+ print(f" {proxy_key}")
49
+ print()
50
+ print("OpenAI Python client one-liner change:")
51
+ print(f' base_url="{BASE_URL}/v1"')
52
+ print(f' api_key="{proxy_key[:16]}..."')
53
+ print(" stream=False # required for v1")
54
+ print()
55
+
56
+ proxy_client = SubzeroClient(api_key=proxy_key, base_url=BASE_URL)
57
+ try:
58
+ response = proxy_client.proxy.chat.completions(
59
+ model="gpt-4o",
60
+ messages=[
61
+ {
62
+ "role": "user",
63
+ "content": "Summarize account for SSN 123-45-6789 in one sentence.",
64
+ }
65
+ ],
66
+ )
67
+ except Exception as exc:
68
+ print(f"POST /v1/chat/completions failed: {exc}", file=sys.stderr)
69
+ if UPSTREAM_OPENAI_KEY == "sk-demo-replace-me":
70
+ print(
71
+ "Note: set OPENAI_API_KEY for a live upstream call; "
72
+ "tokenization still runs before forward.",
73
+ file=sys.stderr,
74
+ )
75
+ return 0
76
+ return 1
77
+
78
+ print("POST /v1/chat/completions -> 200")
79
+ print(json.dumps(response, indent=2)[:500])
80
+ return 0
81
+
82
+
83
+ if __name__ == "__main__":
84
+ raise SystemExit(main())
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env python3
2
+ """Vault loop demo: tokenize → search → reveal → delete.
3
+
4
+ Delete is the one vault step that requires an admin-scoped API key — there is no
5
+ delegatable delete-scoped key at the HTTP layer (per Phase 1B).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ import sys
12
+
13
+ from subzero import SubzeroClient
14
+ from subzero.exceptions import AuthorizationError, NotFoundError
15
+
16
+ from _bootstrap import platform_bootstrap_client
17
+
18
+ BASE_URL = os.environ.get("SUBZERO_BASE_URL", "http://127.0.0.1:8000")
19
+ SSN_VALUE = "123-45-6789"
20
+
21
+
22
+ def main() -> int:
23
+ bootstrap = platform_bootstrap_client(BASE_URL)
24
+
25
+ tenant = bootstrap.admin.create_tenant(name="Vault Loop Demo", slug="vault-loop-sdk")
26
+ tenant_id = tenant.id
27
+ admin_key = tenant.bootstrap_api_key
28
+ if not admin_key:
29
+ print("Expected bootstrap_api_key", file=sys.stderr)
30
+ return 1
31
+
32
+ admin = SubzeroClient(api_key=admin_key, base_url=BASE_URL)
33
+ admin.admin.create_entity_type(tenant_id, name="SSN", deterministic=True)
34
+
35
+ tokenize_key = admin.admin.create_api_key(tenant_id, name="tokenize", scope="tokenize").secret
36
+ reveal_key_record = admin.admin.create_api_key(tenant_id, name="reveal", scope="reveal")
37
+ reveal_key = reveal_key_record.secret
38
+ if not tokenize_key or not reveal_key:
39
+ print("Expected scoped key secrets", file=sys.stderr)
40
+ return 1
41
+
42
+ admin.admin.create_policy(
43
+ tenant_id,
44
+ api_key_id=reveal_key_record.id,
45
+ entity_type="SSN",
46
+ action="reveal",
47
+ )
48
+
49
+ tokenize_client = SubzeroClient(api_key=tokenize_key, base_url=BASE_URL)
50
+ reveal_client = SubzeroClient(api_key=reveal_key, base_url=BASE_URL)
51
+ admin_client = SubzeroClient(api_key=admin_key, base_url=BASE_URL)
52
+
53
+ token = tokenize_client.tokenize("SSN", SSN_VALUE).token
54
+ print(f"tokenize -> {token}")
55
+
56
+ found = tokenize_client.search("SSN", SSN_VALUE).token
57
+ assert found == token
58
+ print("search -> hit")
59
+
60
+ try:
61
+ tokenize_client.reveal(token)
62
+ except AuthorizationError:
63
+ print("reveal with tokenize key -> denied (expected)")
64
+ else:
65
+ print("expected reveal denial with tokenize key", file=sys.stderr)
66
+ return 1
67
+
68
+ plaintext = reveal_client.reveal(token).value
69
+ assert plaintext == SSN_VALUE
70
+ print(f"reveal with reveal key -> {plaintext}")
71
+
72
+ admin_client.delete_token(token)
73
+ print("delete with admin key -> ok")
74
+
75
+ try:
76
+ tokenize_client.search("SSN", SSN_VALUE)
77
+ except NotFoundError:
78
+ print("search after delete -> 404 (expected)")
79
+ else:
80
+ print("expected search miss after delete", file=sys.stderr)
81
+ return 1
82
+
83
+ return 0
84
+
85
+
86
+ if __name__ == "__main__":
87
+ raise SystemExit(main())
@@ -0,0 +1,33 @@
1
+ [project]
2
+ name = "iceberg-subzero"
3
+ version = "0.1.0"
4
+ description = "Python client for the Subzero tokenization vault and LLM proxy"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ dependencies = [
8
+ "httpx>=0.28.0",
9
+ ]
10
+
11
+ [project.optional-dependencies]
12
+ auth = [
13
+ "pyotp>=2.9.0",
14
+ ]
15
+ openai = [
16
+ "openai>=1.0.0",
17
+ ]
18
+ dev = [
19
+ "pytest>=8.3.0",
20
+ "respx>=0.22.0",
21
+ "openai>=1.0.0",
22
+ "pyotp>=2.9.0",
23
+ ]
24
+
25
+ [build-system]
26
+ requires = ["hatchling"]
27
+ build-backend = "hatchling.build"
28
+
29
+ [tool.hatch.build.targets.wheel]
30
+ packages = ["src/subzero"]
31
+
32
+ [tool.pytest.ini_options]
33
+ testpaths = ["tests"]
@@ -0,0 +1,43 @@
1
+ from subzero.auth import AuthResource
2
+ from subzero.client import SubzeroClient
3
+ from subzero.exceptions import (
4
+ AuthenticationError,
5
+ AuthorizationError,
6
+ ConflictError,
7
+ NotFoundError,
8
+ PolicyDeniedError,
9
+ SubzeroAPIError,
10
+ SubzeroNotReadyError,
11
+ )
12
+ from subzero.members import MembersResource
13
+ from subzero.openai import create_openai_client
14
+ from subzero.types import (
15
+ AcceptInviteResult,
16
+ LoginResult,
17
+ MeResult,
18
+ MemberResult,
19
+ MfaEnrollConfirmResult,
20
+ MfaEnrollStartResult,
21
+ TokenResult,
22
+ )
23
+
24
+ __all__ = [
25
+ "SubzeroClient",
26
+ "AuthResource",
27
+ "MembersResource",
28
+ "create_openai_client",
29
+ "LoginResult",
30
+ "TokenResult",
31
+ "MfaEnrollStartResult",
32
+ "MfaEnrollConfirmResult",
33
+ "AcceptInviteResult",
34
+ "MeResult",
35
+ "MemberResult",
36
+ "AuthenticationError",
37
+ "AuthorizationError",
38
+ "ConflictError",
39
+ "NotFoundError",
40
+ "PolicyDeniedError",
41
+ "SubzeroAPIError",
42
+ "SubzeroNotReadyError",
43
+ ]