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.
- onehouse_python_sdk-0.2.0/PKG-INFO +297 -0
- onehouse_python_sdk-0.2.0/README.md +265 -0
- {onehouse_python_sdk-0.1.0 → onehouse_python_sdk-0.2.0}/pyproject.toml +5 -1
- {onehouse_python_sdk-0.1.0 → onehouse_python_sdk-0.2.0}/src/onehouse_python_sdk/__init__.py +2 -1
- onehouse_python_sdk-0.2.0/src/onehouse_python_sdk/resources/__init__.py +9 -0
- onehouse_python_sdk-0.2.0/src/onehouse_python_sdk/resources/client.py +1048 -0
- onehouse_python_sdk-0.2.0/src/onehouse_python_sdk/resources/credentials.py +290 -0
- onehouse_python_sdk-0.2.0/src/onehouse_python_sdk/resources/exceptions.py +54 -0
- onehouse_python_sdk-0.2.0/src/onehouse_python_sdk/resources/sql/__init__.py +19 -0
- onehouse_python_sdk-0.2.0/src/onehouse_python_sdk/resources/sql/builder.py +226 -0
- onehouse_python_sdk-0.2.0/src/onehouse_python_sdk/resources/sql/commands.py +1149 -0
- onehouse_python_sdk-0.2.0/src/onehouse_python_sdk/resources/status.py +119 -0
- onehouse_python_sdk-0.2.0/src/onehouse_python_sdk/resources/transport.py +145 -0
- onehouse_python_sdk-0.1.0/PKG-INFO +0 -134
- onehouse_python_sdk-0.1.0/README.md +0 -104
- {onehouse_python_sdk-0.1.0 → onehouse_python_sdk-0.2.0}/.gitignore +0 -0
- {onehouse_python_sdk-0.1.0 → onehouse_python_sdk-0.2.0}/src/onehouse_python_sdk/base.py +0 -0
- {onehouse_python_sdk-0.1.0 → onehouse_python_sdk-0.2.0}/src/onehouse_python_sdk/exceptions.py +0 -0
- {onehouse_python_sdk-0.1.0 → onehouse_python_sdk-0.2.0}/src/onehouse_python_sdk/lakebase/__init__.py +0 -0
- {onehouse_python_sdk-0.1.0 → onehouse_python_sdk-0.2.0}/src/onehouse_python_sdk/lakebase/auth/__init__.py +0 -0
- {onehouse_python_sdk-0.1.0 → onehouse_python_sdk-0.2.0}/src/onehouse_python_sdk/lakebase/auth/_ssl.py +0 -0
- {onehouse_python_sdk-0.1.0 → onehouse_python_sdk-0.2.0}/src/onehouse_python_sdk/lakebase/auth/azure_oauth.py +0 -0
- {onehouse_python_sdk-0.1.0 → onehouse_python_sdk-0.2.0}/src/onehouse_python_sdk/lakebase/auth/browser_auth.py +0 -0
- {onehouse_python_sdk-0.1.0 → onehouse_python_sdk-0.2.0}/src/onehouse_python_sdk/lakebase/auth/local_auth_server.py +0 -0
- {onehouse_python_sdk-0.1.0 → onehouse_python_sdk-0.2.0}/src/onehouse_python_sdk/lakebase/auth/oidc_device_flow.py +0 -0
- {onehouse_python_sdk-0.1.0 → onehouse_python_sdk-0.2.0}/src/onehouse_python_sdk/lakebase/auth/saml.py +0 -0
- {onehouse_python_sdk-0.1.0 → onehouse_python_sdk-0.2.0}/src/onehouse_python_sdk/lakebase/client.py +0 -0
- {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.
|
|
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"]
|