onehouse-python-sdk 0.1.0__tar.gz → 0.2.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.
Files changed (28) hide show
  1. onehouse_python_sdk-0.2.0/PKG-INFO +297 -0
  2. onehouse_python_sdk-0.2.0/README.md +265 -0
  3. {onehouse_python_sdk-0.1.0 → onehouse_python_sdk-0.2.0}/pyproject.toml +5 -1
  4. {onehouse_python_sdk-0.1.0 → onehouse_python_sdk-0.2.0}/src/onehouse_python_sdk/__init__.py +2 -1
  5. onehouse_python_sdk-0.2.0/src/onehouse_python_sdk/resources/__init__.py +9 -0
  6. onehouse_python_sdk-0.2.0/src/onehouse_python_sdk/resources/client.py +1048 -0
  7. onehouse_python_sdk-0.2.0/src/onehouse_python_sdk/resources/credentials.py +290 -0
  8. onehouse_python_sdk-0.2.0/src/onehouse_python_sdk/resources/exceptions.py +54 -0
  9. onehouse_python_sdk-0.2.0/src/onehouse_python_sdk/resources/sql/__init__.py +19 -0
  10. onehouse_python_sdk-0.2.0/src/onehouse_python_sdk/resources/sql/builder.py +226 -0
  11. onehouse_python_sdk-0.2.0/src/onehouse_python_sdk/resources/sql/commands.py +1149 -0
  12. onehouse_python_sdk-0.2.0/src/onehouse_python_sdk/resources/status.py +119 -0
  13. onehouse_python_sdk-0.2.0/src/onehouse_python_sdk/resources/transport.py +145 -0
  14. onehouse_python_sdk-0.1.0/PKG-INFO +0 -134
  15. onehouse_python_sdk-0.1.0/README.md +0 -104
  16. {onehouse_python_sdk-0.1.0 → onehouse_python_sdk-0.2.0}/.gitignore +0 -0
  17. {onehouse_python_sdk-0.1.0 → onehouse_python_sdk-0.2.0}/src/onehouse_python_sdk/base.py +0 -0
  18. {onehouse_python_sdk-0.1.0 → onehouse_python_sdk-0.2.0}/src/onehouse_python_sdk/exceptions.py +0 -0
  19. {onehouse_python_sdk-0.1.0 → onehouse_python_sdk-0.2.0}/src/onehouse_python_sdk/lakebase/__init__.py +0 -0
  20. {onehouse_python_sdk-0.1.0 → onehouse_python_sdk-0.2.0}/src/onehouse_python_sdk/lakebase/auth/__init__.py +0 -0
  21. {onehouse_python_sdk-0.1.0 → onehouse_python_sdk-0.2.0}/src/onehouse_python_sdk/lakebase/auth/_ssl.py +0 -0
  22. {onehouse_python_sdk-0.1.0 → onehouse_python_sdk-0.2.0}/src/onehouse_python_sdk/lakebase/auth/azure_oauth.py +0 -0
  23. {onehouse_python_sdk-0.1.0 → onehouse_python_sdk-0.2.0}/src/onehouse_python_sdk/lakebase/auth/browser_auth.py +0 -0
  24. {onehouse_python_sdk-0.1.0 → onehouse_python_sdk-0.2.0}/src/onehouse_python_sdk/lakebase/auth/local_auth_server.py +0 -0
  25. {onehouse_python_sdk-0.1.0 → onehouse_python_sdk-0.2.0}/src/onehouse_python_sdk/lakebase/auth/oidc_device_flow.py +0 -0
  26. {onehouse_python_sdk-0.1.0 → onehouse_python_sdk-0.2.0}/src/onehouse_python_sdk/lakebase/auth/saml.py +0 -0
  27. {onehouse_python_sdk-0.1.0 → onehouse_python_sdk-0.2.0}/src/onehouse_python_sdk/lakebase/client.py +0 -0
  28. {onehouse_python_sdk-0.1.0 → onehouse_python_sdk-0.2.0}/src/onehouse_python_sdk/lakebase/connection.py +0 -0
@@ -0,0 +1,297 @@
1
+ Metadata-Version: 2.4
2
+ Name: onehouse-python-sdk
3
+ Version: 0.2.0
4
+ Summary: Onehouse Python SDK — modular data-plane clients for LakeBase and beyond
5
+ Project-URL: Homepage, https://github.com/onehouseinc/onehouse-python-sdk
6
+ Project-URL: Repository, https://github.com/onehouseinc/onehouse-python-sdk
7
+ Project-URL: Issues, https://github.com/onehouseinc/onehouse-python-sdk/issues
8
+ Author-email: Onehouse Team <eng@onehouse.ai>
9
+ License: Proprietary
10
+ Keywords: auth,azure,lakebase,oauth2,oidc,onehouse,postgresql,saml
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: Other/Proprietary License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Database
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.9
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
24
+ Requires-Dist: pytest-mock>=3.0; extra == 'dev'
25
+ Requires-Dist: pytest>=7.0; extra == 'dev'
26
+ Provides-Extra: lakebase
27
+ Requires-Dist: certifi>=2024.1; extra == 'lakebase'
28
+ Requires-Dist: psycopg2-binary>=2.9; extra == 'lakebase'
29
+ Provides-Extra: resources
30
+ Requires-Dist: requests>=2.28; extra == 'resources'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # onehouse-python-sdk
34
+
35
+ Python SDK for connecting to [Onehouse](https://www.onehouse.ai) data-plane and control-plane services.
36
+
37
+ The base package has **zero required dependencies**. Drivers ship as optional extras so you only install what you need.
38
+
39
+ ```bash
40
+ pip install onehouse-python-sdk[lakebase] # PostgreSQL/LakeBase SQL client
41
+ pip install onehouse-python-sdk[resources] # Control-plane REST client
42
+ ```
43
+
44
+ ## LakeBase
45
+
46
+ LakeBase is Onehouse's PostgreSQL-compatible managed lakehouse engine. This SDK provides a `psycopg2`-backed client that handles the browser-based and federated authentication flows LakeBase clusters require, on top of standard username/password auth.
47
+
48
+ ### Installation
49
+
50
+ ```bash
51
+ pip install onehouse-python-sdk[lakebase]
52
+ ```
53
+
54
+ Requires Python 3.9+.
55
+
56
+ ### Quickstart
57
+
58
+ ```python
59
+ from onehouse_python_sdk import LakebaseClient
60
+
61
+ # Username / password
62
+ with LakebaseClient().connect(
63
+ host="<cluster-host>",
64
+ port=5432,
65
+ dbname="mydb",
66
+ user="admin",
67
+ password="secret",
68
+ ) as client:
69
+ rows = client.fetchall("SELECT * FROM mytable WHERE id = %s", (42,))
70
+ ```
71
+
72
+ ### Authentication flows
73
+
74
+ | Flow | Parameters |
75
+ |------|------------|
76
+ | Username / password | `user`, `password` |
77
+ | OIDC Device Flow (Okta / Auth0) | `browser_auth=true`, `oidc_client_id`, `oidc_issuer_url`, `oidc_iam_role` |
78
+ | Azure AD OAuth2 | `browser_auth=true`, `azure_oauth_tenant_id`, `azure_oauth_client_id`, `azure_oauth_client_secret` |
79
+ | Azure Entra ID SAML | `browser_auth=true`, `azure_tenant_id`, `azure_entity_id` |
80
+ | Built-in login form | `browser_auth=true` _(default when no IdP params are set)_ |
81
+ | External redirect | `browser_auth=true`, `auth_redirect_url` |
82
+
83
+ ```python
84
+ # OIDC Device Flow
85
+ client = LakebaseClient().connect(
86
+ host="<cluster-host>",
87
+ port=5432,
88
+ dbname="mydb",
89
+ browser_auth="true",
90
+ oidc_client_id="0oaXXXXX",
91
+ oidc_issuer_url="https://myorg.okta.com",
92
+ oidc_iam_role="arn:aws:iam::123456789012:role/LakeBaseRole",
93
+ )
94
+
95
+ # Azure AD OAuth2
96
+ client = LakebaseClient().connect(
97
+ host="<cluster-host>",
98
+ port=5432,
99
+ dbname="mydb",
100
+ browser_auth="true",
101
+ azure_oauth_tenant_id="your-tenant-id",
102
+ azure_oauth_client_id="your-client-id",
103
+ azure_oauth_client_secret="your-client-secret",
104
+ )
105
+ ```
106
+
107
+ DSN string form is also supported:
108
+
109
+ ```python
110
+ client = LakebaseClient().connect(
111
+ "postgresql://<cluster-host>:5432/mydb"
112
+ "?browser_auth=true"
113
+ "&oidc_client_id=0oaXXXX"
114
+ "&oidc_issuer_url=https://myorg.okta.com"
115
+ "&oidc_iam_role=arn:aws:iam::123456789012:role/LakeBaseRole"
116
+ )
117
+ ```
118
+
119
+ ### Client API
120
+
121
+ All clients implement the `SqlClient` interface:
122
+
123
+ | Method | Description |
124
+ |--------|-------------|
125
+ | `connect(dsn=None, **kwargs) → self` | Establish connection, returns self for chaining |
126
+ | `execute(sql, params)` | Run a statement, return rowcount |
127
+ | `fetchall(sql, params)` | Run a query, return `list[tuple]` |
128
+ | `fetchone(sql, params)` | Run a query, return first row or `None` |
129
+ | `cursor()` | Raw cursor for advanced use |
130
+ | `raw_connection` | Underlying `psycopg2` connection |
131
+ | `close()` | Close the connection |
132
+ | `__enter__` / `__exit__` | Context manager — closes on exit |
133
+
134
+ ### Notes
135
+
136
+ - **Credential caching** — auth tokens are cached for 4 minutes per connection parameters to avoid repeated browser prompts within the same process.
137
+ - **Callback port** — the local auth callback server defaults to port `8888` at path `/lakebase`. Override with `auth_callback_port` and `auth_callback_path`.
138
+
139
+ ## Onehouse Resources (Control-Plane API)
140
+
141
+ `OnehouseResources` wraps the Onehouse REST API for managing platform resources — clusters, lakes, flows, jobs, table services, and more. It posts SQL statements to `https://api.onehouse.ai/v1/resource/` and polls `/v1/status/{requestId}` for the result.
142
+
143
+ ### Installation
144
+
145
+ ```bash
146
+ pip install onehouse-python-sdk[resources]
147
+ ```
148
+
149
+ ### Quickstart
150
+
151
+ ```python
152
+ from onehouse_python_sdk import OnehouseResources
153
+
154
+ client = OnehouseResources(
155
+ account_uid="...", project_uid="...",
156
+ api_key="...", api_secret="...",
157
+ link_uid="...", region="us-west-2", user_uid="...",
158
+ )
159
+
160
+ # Typed helper — blocks until the operation reaches a terminal status.
161
+ result = client.create_cluster(
162
+ "prod",
163
+ type="Managed",
164
+ max_ocu=10,
165
+ min_ocu=1,
166
+ options={"worker.type": "oh-general-4"},
167
+ )
168
+ print(result.api_status) # ApiStatus.SUCCESS
169
+ print(result.api_response) # raw API payload
170
+ ```
171
+
172
+ ### Credentials
173
+
174
+ Credentials resolve from three sources, in order (highest wins):
175
+
176
+ 1. **Explicit constructor arguments** (shown above).
177
+ 2. **Environment variables** — `ONEHOUSE_ACCOUNT_UID`, `ONEHOUSE_PROJECT_UID`, `ONEHOUSE_API_KEY`, `ONEHOUSE_API_SECRET`, `ONEHOUSE_LINK_UID`, `ONEHOUSE_REGION`, `ONEHOUSE_USER_UID`. Optional: `ONEHOUSE_BASE_URL`, `ONEHOUSE_PROFILE`, `ONEHOUSE_CREDENTIALS_FILE`.
178
+ 3. **INI credentials file** at `~/.onehouse/credentials` (override with `ONEHOUSE_CREDENTIALS_FILE`).
179
+
180
+ ```ini
181
+ # ~/.onehouse/credentials
182
+ [default]
183
+ account_uid = 92e5f1ab-...
184
+ project_uid = 3afe72cd-...
185
+ api_key = j+m8wRhgpKYFTLxCHNDzQA==
186
+ api_secret = tXpzrqfUBNK9yhS5+FmLM37xwfhVeZygJntCzHG4Dpq=
187
+ link_uid = da56fe8b-...
188
+ region = us-west-2
189
+ user_uid = ...
190
+
191
+ [staging]
192
+ account_uid = ...
193
+ ```
194
+
195
+ ```python
196
+ # Read from environment / default profile.
197
+ client = OnehouseResources()
198
+
199
+ # Pick a named profile.
200
+ client = OnehouseResources(profile="staging")
201
+ ```
202
+
203
+ Missing fields produce an `AuthError` listing which fields are unset and how to supply them. A world-readable credentials file triggers a warning — `chmod 600 ~/.onehouse/credentials`.
204
+
205
+ ### Three ways to run a command
206
+
207
+ ```python
208
+ # (1) Blocking — submit, poll, return the terminal status. Most common.
209
+ result = client.execute("SHOW CLUSTERS")
210
+
211
+ # (2) Non-blocking — submit now, poll later. Good for long-running ops or
212
+ # parallel orchestration where you don't want to hold a thread.
213
+ submitted = client.submit("CREATE CLUSTER `prod` TYPE = 'Managed' MAX_OCU = 10 MIN_OCU = 1")
214
+ # ... do other work, persist submitted.request_id ...
215
+ status = client.get_status(submitted.request_id)
216
+ while status.api_status == ApiStatus.PENDING:
217
+ time.sleep(5)
218
+ status = client.get_status(submitted.request_id)
219
+
220
+ # (3) Typed helpers — same blocking semantics as execute(), but build the SQL for you.
221
+ client.create_cluster("prod", type="Managed", max_ocu=10, min_ocu=1)
222
+ client.show_clusters()
223
+ client.delete_cluster("prod")
224
+ ```
225
+
226
+ ### Typed helpers
227
+
228
+ `OnehouseResources` exposes one method per Phase 1 SQL command (~50 methods across 11 resource families): Clusters, Lakes, Databases, Tables, Catalogs, Sources, Flows, Transformations, Validations, Table Services, Jobs, Service Principals, API Tokens. Every typed helper accepts the same trailing kwargs: `unsafe_raw`, `timeout`, `poll_interval`.
229
+
230
+ ```python
231
+ client.create_lake(
232
+ "analytics",
233
+ lake_type="MANAGED",
234
+ bucket_path="s3://my-bucket/lake",
235
+ default_services_cluster="services",
236
+ )
237
+
238
+ from onehouse_python_sdk.resources.sql.commands import PartitionKeyField
239
+
240
+ client.create_flow(
241
+ "events_pipeline",
242
+ source="my_kafka_source",
243
+ lake="analytics",
244
+ database="events",
245
+ table_name="page_views",
246
+ write_mode="MUTABLE",
247
+ cluster="ingest_cluster",
248
+ catalogs=["my_glue_catalog"],
249
+ record_key_fields=["id"],
250
+ partition_key_fields=[
251
+ PartitionKeyField("date", partition_type="DATE_STRING",
252
+ input_format="yyyy-mm-dd", output_format="yyyy-mm-dd"),
253
+ ],
254
+ min_sync_frequency_mins=5,
255
+ options={"kafka.topic.name": "page_views"},
256
+ )
257
+ ```
258
+
259
+ ACL / privilege / role / group commands aren't exposed as typed helpers yet — use `client.execute("GRANT ...")` until they land in a future release.
260
+
261
+ #### `unsafe_raw` escape hatch
262
+
263
+ The builder validates resource names against `^[A-Za-z][A-Za-z0-9_-]*$` and rejects unknown enum values to catch obvious typos. If a name doesn't match (e.g. legacy resources with dots in the name) or you're using a SQL feature the SDK hasn't been updated for, pass `unsafe_raw=True` on the call to skip validation:
264
+
265
+ ```python
266
+ client.create_cluster("legacy.name", type="CustomType", unsafe_raw=True)
267
+ ```
268
+
269
+ The argument is deliberately named so its uses are easy to find in `grep`.
270
+
271
+ ### Error handling
272
+
273
+ ```
274
+ OnehouseSdkError
275
+ └── ResourcesError # raised by the resources/ subpackage
276
+ ├── AuthError # missing/invalid credentials
277
+ ├── SqlParseError # HTTP 400 + grpc-message — server rejected the SQL
278
+ ├── OperationFailedError # terminal status FAILED or INVALID
279
+ └── OperationTimeoutError # polling exceeded the configured timeout
280
+ ```
281
+
282
+ `OperationTimeoutError` carries the `request_id` of the in-flight operation — you can resume polling with `get_status(request_id)` rather than re-submitting (which would create a duplicate resource).
283
+
284
+ ### Client API
285
+
286
+ | Method | Description |
287
+ |--------|-------------|
288
+ | `submit(statement) → SubmitResponse` | POST `/v1/resource/`, return the `requestId`. |
289
+ | `get_status(request_id) → StatusResponse` | GET `/v1/status/{id}`, return the parsed status. |
290
+ | `execute(statement, timeout=, poll_interval=) → StatusResponse` | Submit + poll until terminal. Blocks. |
291
+ | `<verb>_<resource>(...)` (~50 methods) | Typed wrappers around `execute()` that build SQL for you. |
292
+
293
+ ### Notes
294
+
295
+ - **Lazy import** — `from onehouse_python_sdk import OnehouseResources` works even when the `[resources]` extra isn't installed; the first network call raises a clear "install `[resources]` extra" error.
296
+ - **Rate limit** — projects are capped at 10 QPS. The transport retries 429 responses with bounded exponential backoff.
297
+ - **Not a SqlClient** — `OnehouseResources` is a control-plane HTTP client and intentionally does not implement the `SqlClient` interface (no `cursor`, `fetchall`, etc.).
@@ -0,0 +1,265 @@
1
+ # onehouse-python-sdk
2
+
3
+ Python SDK for connecting to [Onehouse](https://www.onehouse.ai) data-plane and control-plane services.
4
+
5
+ The base package has **zero required dependencies**. Drivers ship as optional extras so you only install what you need.
6
+
7
+ ```bash
8
+ pip install onehouse-python-sdk[lakebase] # PostgreSQL/LakeBase SQL client
9
+ pip install onehouse-python-sdk[resources] # Control-plane REST client
10
+ ```
11
+
12
+ ## LakeBase
13
+
14
+ LakeBase is Onehouse's PostgreSQL-compatible managed lakehouse engine. This SDK provides a `psycopg2`-backed client that handles the browser-based and federated authentication flows LakeBase clusters require, on top of standard username/password auth.
15
+
16
+ ### Installation
17
+
18
+ ```bash
19
+ pip install onehouse-python-sdk[lakebase]
20
+ ```
21
+
22
+ Requires Python 3.9+.
23
+
24
+ ### Quickstart
25
+
26
+ ```python
27
+ from onehouse_python_sdk import LakebaseClient
28
+
29
+ # Username / password
30
+ with LakebaseClient().connect(
31
+ host="<cluster-host>",
32
+ port=5432,
33
+ dbname="mydb",
34
+ user="admin",
35
+ password="secret",
36
+ ) as client:
37
+ rows = client.fetchall("SELECT * FROM mytable WHERE id = %s", (42,))
38
+ ```
39
+
40
+ ### Authentication flows
41
+
42
+ | Flow | Parameters |
43
+ |------|------------|
44
+ | Username / password | `user`, `password` |
45
+ | OIDC Device Flow (Okta / Auth0) | `browser_auth=true`, `oidc_client_id`, `oidc_issuer_url`, `oidc_iam_role` |
46
+ | Azure AD OAuth2 | `browser_auth=true`, `azure_oauth_tenant_id`, `azure_oauth_client_id`, `azure_oauth_client_secret` |
47
+ | Azure Entra ID SAML | `browser_auth=true`, `azure_tenant_id`, `azure_entity_id` |
48
+ | Built-in login form | `browser_auth=true` _(default when no IdP params are set)_ |
49
+ | External redirect | `browser_auth=true`, `auth_redirect_url` |
50
+
51
+ ```python
52
+ # OIDC Device Flow
53
+ client = LakebaseClient().connect(
54
+ host="<cluster-host>",
55
+ port=5432,
56
+ dbname="mydb",
57
+ browser_auth="true",
58
+ oidc_client_id="0oaXXXXX",
59
+ oidc_issuer_url="https://myorg.okta.com",
60
+ oidc_iam_role="arn:aws:iam::123456789012:role/LakeBaseRole",
61
+ )
62
+
63
+ # Azure AD OAuth2
64
+ client = LakebaseClient().connect(
65
+ host="<cluster-host>",
66
+ port=5432,
67
+ dbname="mydb",
68
+ browser_auth="true",
69
+ azure_oauth_tenant_id="your-tenant-id",
70
+ azure_oauth_client_id="your-client-id",
71
+ azure_oauth_client_secret="your-client-secret",
72
+ )
73
+ ```
74
+
75
+ DSN string form is also supported:
76
+
77
+ ```python
78
+ client = LakebaseClient().connect(
79
+ "postgresql://<cluster-host>:5432/mydb"
80
+ "?browser_auth=true"
81
+ "&oidc_client_id=0oaXXXX"
82
+ "&oidc_issuer_url=https://myorg.okta.com"
83
+ "&oidc_iam_role=arn:aws:iam::123456789012:role/LakeBaseRole"
84
+ )
85
+ ```
86
+
87
+ ### Client API
88
+
89
+ All clients implement the `SqlClient` interface:
90
+
91
+ | Method | Description |
92
+ |--------|-------------|
93
+ | `connect(dsn=None, **kwargs) → self` | Establish connection, returns self for chaining |
94
+ | `execute(sql, params)` | Run a statement, return rowcount |
95
+ | `fetchall(sql, params)` | Run a query, return `list[tuple]` |
96
+ | `fetchone(sql, params)` | Run a query, return first row or `None` |
97
+ | `cursor()` | Raw cursor for advanced use |
98
+ | `raw_connection` | Underlying `psycopg2` connection |
99
+ | `close()` | Close the connection |
100
+ | `__enter__` / `__exit__` | Context manager — closes on exit |
101
+
102
+ ### Notes
103
+
104
+ - **Credential caching** — auth tokens are cached for 4 minutes per connection parameters to avoid repeated browser prompts within the same process.
105
+ - **Callback port** — the local auth callback server defaults to port `8888` at path `/lakebase`. Override with `auth_callback_port` and `auth_callback_path`.
106
+
107
+ ## Onehouse Resources (Control-Plane API)
108
+
109
+ `OnehouseResources` wraps the Onehouse REST API for managing platform resources — clusters, lakes, flows, jobs, table services, and more. It posts SQL statements to `https://api.onehouse.ai/v1/resource/` and polls `/v1/status/{requestId}` for the result.
110
+
111
+ ### Installation
112
+
113
+ ```bash
114
+ pip install onehouse-python-sdk[resources]
115
+ ```
116
+
117
+ ### Quickstart
118
+
119
+ ```python
120
+ from onehouse_python_sdk import OnehouseResources
121
+
122
+ client = OnehouseResources(
123
+ account_uid="...", project_uid="...",
124
+ api_key="...", api_secret="...",
125
+ link_uid="...", region="us-west-2", user_uid="...",
126
+ )
127
+
128
+ # Typed helper — blocks until the operation reaches a terminal status.
129
+ result = client.create_cluster(
130
+ "prod",
131
+ type="Managed",
132
+ max_ocu=10,
133
+ min_ocu=1,
134
+ options={"worker.type": "oh-general-4"},
135
+ )
136
+ print(result.api_status) # ApiStatus.SUCCESS
137
+ print(result.api_response) # raw API payload
138
+ ```
139
+
140
+ ### Credentials
141
+
142
+ Credentials resolve from three sources, in order (highest wins):
143
+
144
+ 1. **Explicit constructor arguments** (shown above).
145
+ 2. **Environment variables** — `ONEHOUSE_ACCOUNT_UID`, `ONEHOUSE_PROJECT_UID`, `ONEHOUSE_API_KEY`, `ONEHOUSE_API_SECRET`, `ONEHOUSE_LINK_UID`, `ONEHOUSE_REGION`, `ONEHOUSE_USER_UID`. Optional: `ONEHOUSE_BASE_URL`, `ONEHOUSE_PROFILE`, `ONEHOUSE_CREDENTIALS_FILE`.
146
+ 3. **INI credentials file** at `~/.onehouse/credentials` (override with `ONEHOUSE_CREDENTIALS_FILE`).
147
+
148
+ ```ini
149
+ # ~/.onehouse/credentials
150
+ [default]
151
+ account_uid = 92e5f1ab-...
152
+ project_uid = 3afe72cd-...
153
+ api_key = j+m8wRhgpKYFTLxCHNDzQA==
154
+ api_secret = tXpzrqfUBNK9yhS5+FmLM37xwfhVeZygJntCzHG4Dpq=
155
+ link_uid = da56fe8b-...
156
+ region = us-west-2
157
+ user_uid = ...
158
+
159
+ [staging]
160
+ account_uid = ...
161
+ ```
162
+
163
+ ```python
164
+ # Read from environment / default profile.
165
+ client = OnehouseResources()
166
+
167
+ # Pick a named profile.
168
+ client = OnehouseResources(profile="staging")
169
+ ```
170
+
171
+ Missing fields produce an `AuthError` listing which fields are unset and how to supply them. A world-readable credentials file triggers a warning — `chmod 600 ~/.onehouse/credentials`.
172
+
173
+ ### Three ways to run a command
174
+
175
+ ```python
176
+ # (1) Blocking — submit, poll, return the terminal status. Most common.
177
+ result = client.execute("SHOW CLUSTERS")
178
+
179
+ # (2) Non-blocking — submit now, poll later. Good for long-running ops or
180
+ # parallel orchestration where you don't want to hold a thread.
181
+ submitted = client.submit("CREATE CLUSTER `prod` TYPE = 'Managed' MAX_OCU = 10 MIN_OCU = 1")
182
+ # ... do other work, persist submitted.request_id ...
183
+ status = client.get_status(submitted.request_id)
184
+ while status.api_status == ApiStatus.PENDING:
185
+ time.sleep(5)
186
+ status = client.get_status(submitted.request_id)
187
+
188
+ # (3) Typed helpers — same blocking semantics as execute(), but build the SQL for you.
189
+ client.create_cluster("prod", type="Managed", max_ocu=10, min_ocu=1)
190
+ client.show_clusters()
191
+ client.delete_cluster("prod")
192
+ ```
193
+
194
+ ### Typed helpers
195
+
196
+ `OnehouseResources` exposes one method per Phase 1 SQL command (~50 methods across 11 resource families): Clusters, Lakes, Databases, Tables, Catalogs, Sources, Flows, Transformations, Validations, Table Services, Jobs, Service Principals, API Tokens. Every typed helper accepts the same trailing kwargs: `unsafe_raw`, `timeout`, `poll_interval`.
197
+
198
+ ```python
199
+ client.create_lake(
200
+ "analytics",
201
+ lake_type="MANAGED",
202
+ bucket_path="s3://my-bucket/lake",
203
+ default_services_cluster="services",
204
+ )
205
+
206
+ from onehouse_python_sdk.resources.sql.commands import PartitionKeyField
207
+
208
+ client.create_flow(
209
+ "events_pipeline",
210
+ source="my_kafka_source",
211
+ lake="analytics",
212
+ database="events",
213
+ table_name="page_views",
214
+ write_mode="MUTABLE",
215
+ cluster="ingest_cluster",
216
+ catalogs=["my_glue_catalog"],
217
+ record_key_fields=["id"],
218
+ partition_key_fields=[
219
+ PartitionKeyField("date", partition_type="DATE_STRING",
220
+ input_format="yyyy-mm-dd", output_format="yyyy-mm-dd"),
221
+ ],
222
+ min_sync_frequency_mins=5,
223
+ options={"kafka.topic.name": "page_views"},
224
+ )
225
+ ```
226
+
227
+ ACL / privilege / role / group commands aren't exposed as typed helpers yet — use `client.execute("GRANT ...")` until they land in a future release.
228
+
229
+ #### `unsafe_raw` escape hatch
230
+
231
+ The builder validates resource names against `^[A-Za-z][A-Za-z0-9_-]*$` and rejects unknown enum values to catch obvious typos. If a name doesn't match (e.g. legacy resources with dots in the name) or you're using a SQL feature the SDK hasn't been updated for, pass `unsafe_raw=True` on the call to skip validation:
232
+
233
+ ```python
234
+ client.create_cluster("legacy.name", type="CustomType", unsafe_raw=True)
235
+ ```
236
+
237
+ The argument is deliberately named so its uses are easy to find in `grep`.
238
+
239
+ ### Error handling
240
+
241
+ ```
242
+ OnehouseSdkError
243
+ └── ResourcesError # raised by the resources/ subpackage
244
+ ├── AuthError # missing/invalid credentials
245
+ ├── SqlParseError # HTTP 400 + grpc-message — server rejected the SQL
246
+ ├── OperationFailedError # terminal status FAILED or INVALID
247
+ └── OperationTimeoutError # polling exceeded the configured timeout
248
+ ```
249
+
250
+ `OperationTimeoutError` carries the `request_id` of the in-flight operation — you can resume polling with `get_status(request_id)` rather than re-submitting (which would create a duplicate resource).
251
+
252
+ ### Client API
253
+
254
+ | Method | Description |
255
+ |--------|-------------|
256
+ | `submit(statement) → SubmitResponse` | POST `/v1/resource/`, return the `requestId`. |
257
+ | `get_status(request_id) → StatusResponse` | GET `/v1/status/{id}`, return the parsed status. |
258
+ | `execute(statement, timeout=, poll_interval=) → StatusResponse` | Submit + poll until terminal. Blocks. |
259
+ | `<verb>_<resource>(...)` (~50 methods) | Typed wrappers around `execute()` that build SQL for you. |
260
+
261
+ ### Notes
262
+
263
+ - **Lazy import** — `from onehouse_python_sdk import OnehouseResources` works even when the `[resources]` extra isn't installed; the first network call raises a clear "install `[resources]` extra" error.
264
+ - **Rate limit** — projects are capped at 10 QPS. The transport retries 429 responses with bounded exponential backoff.
265
+ - **Not a SqlClient** — `OnehouseResources` is a control-plane HTTP client and intentionally does not implement the `SqlClient` interface (no `cursor`, `fetchall`, etc.).
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "onehouse-python-sdk"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "Onehouse Python SDK — modular data-plane clients for LakeBase and beyond"
9
9
  readme = "README.md"
10
10
  license = { text = "Proprietary" }
@@ -30,6 +30,9 @@ lakebase = [
30
30
  "psycopg2-binary>=2.9",
31
31
  "certifi>=2024.1",
32
32
  ]
33
+ resources = [
34
+ "requests>=2.28",
35
+ ]
33
36
  dev = [
34
37
  "pytest>=7.0",
35
38
  "pytest-cov>=4.0",
@@ -52,6 +55,7 @@ testpaths = ["tests"]
52
55
  addopts = "--cov=onehouse_python_sdk --cov-report=term-missing --cov-report=html"
53
56
  markers = [
54
57
  "live: requires a running LakeBase cluster (set LAKEBASE_TEST_DSN)",
58
+ "resources_live: requires a real Onehouse project (set ONEHOUSE_RESOURCES_LIVE=1 plus credentials)",
55
59
  ]
56
60
 
57
61
  [tool.coverage.run]
@@ -3,6 +3,7 @@
3
3
  from . import exceptions
4
4
  from .base import SqlClient
5
5
  from .lakebase.client import LakebaseClient
6
+ from .resources.client import OnehouseResources
6
7
 
7
- __all__ = ["SqlClient", "LakebaseClient", "exceptions"]
8
+ __all__ = ["SqlClient", "LakebaseClient", "OnehouseResources", "exceptions"]
8
9
  __version__ = "0.1.0"
@@ -0,0 +1,9 @@
1
+ """Onehouse control-plane (Resources) API client.
2
+
3
+ Requires the [resources] extra: pip install onehouse-python-sdk[resources]
4
+ """
5
+
6
+ from .client import OnehouseResources
7
+ from .status import ApiStatus, StatusResponse, SubmitResponse
8
+
9
+ __all__ = ["OnehouseResources", "ApiStatus", "StatusResponse", "SubmitResponse"]