honeyframeapi 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.
- honeyframeapi-0.1.0/PKG-INFO +272 -0
- honeyframeapi-0.1.0/README.md +254 -0
- honeyframeapi-0.1.0/honeyframeapi/__init__.py +63 -0
- honeyframeapi-0.1.0/honeyframeapi/_compat.py +20 -0
- honeyframeapi-0.1.0/honeyframeapi/_sql.py +143 -0
- honeyframeapi-0.1.0/honeyframeapi/agent.py +49 -0
- honeyframeapi-0.1.0/honeyframeapi/auth.py +141 -0
- honeyframeapi-0.1.0/honeyframeapi/cli.py +218 -0
- honeyframeapi-0.1.0/honeyframeapi/client.py +438 -0
- honeyframeapi-0.1.0/honeyframeapi/connector.py +52 -0
- honeyframeapi-0.1.0/honeyframeapi/dashboard.py +187 -0
- honeyframeapi-0.1.0/honeyframeapi/dataset.py +146 -0
- honeyframeapi-0.1.0/honeyframeapi/dbt.py +113 -0
- honeyframeapi-0.1.0/honeyframeapi/exceptions.py +66 -0
- honeyframeapi-0.1.0/honeyframeapi/pipeline.py +70 -0
- honeyframeapi-0.1.0/honeyframeapi/project.py +283 -0
- honeyframeapi-0.1.0/honeyframeapi/publishing.py +102 -0
- honeyframeapi-0.1.0/honeyframeapi/py.typed +0 -0
- honeyframeapi-0.1.0/honeyframeapi/recipe.py +110 -0
- honeyframeapi-0.1.0/honeyframeapi/scenario.py +65 -0
- honeyframeapi-0.1.0/honeyframeapi/webapp.py +110 -0
- honeyframeapi-0.1.0/honeyframeapi.egg-info/PKG-INFO +272 -0
- honeyframeapi-0.1.0/honeyframeapi.egg-info/SOURCES.txt +43 -0
- honeyframeapi-0.1.0/honeyframeapi.egg-info/dependency_links.txt +1 -0
- honeyframeapi-0.1.0/honeyframeapi.egg-info/entry_points.txt +2 -0
- honeyframeapi-0.1.0/honeyframeapi.egg-info/requires.txt +9 -0
- honeyframeapi-0.1.0/honeyframeapi.egg-info/top_level.txt +1 -0
- honeyframeapi-0.1.0/pyproject.toml +51 -0
- honeyframeapi-0.1.0/setup.cfg +4 -0
- honeyframeapi-0.1.0/tests/test_auth.py +81 -0
- honeyframeapi-0.1.0/tests/test_cli.py +49 -0
- honeyframeapi-0.1.0/tests/test_cli_warehouse_cmds.py +67 -0
- honeyframeapi-0.1.0/tests/test_client.py +79 -0
- honeyframeapi-0.1.0/tests/test_client_retry.py +100 -0
- honeyframeapi-0.1.0/tests/test_dashboards.py +73 -0
- honeyframeapi-0.1.0/tests/test_datasets.py +74 -0
- honeyframeapi-0.1.0/tests/test_devapi.py +131 -0
- honeyframeapi-0.1.0/tests/test_engine.py +49 -0
- honeyframeapi-0.1.0/tests/test_example_end_to_end.py +94 -0
- honeyframeapi-0.1.0/tests/test_export.py +57 -0
- honeyframeapi-0.1.0/tests/test_lineage.py +45 -0
- honeyframeapi-0.1.0/tests/test_parity.py +170 -0
- honeyframeapi-0.1.0/tests/test_py_typed.py +17 -0
- honeyframeapi-0.1.0/tests/test_upload.py +62 -0
- honeyframeapi-0.1.0/tests/test_webapp.py +119 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: honeyframeapi
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python client for the Honeyframe data platform — a dataikuapi-style SDK
|
|
5
|
+
Author: HubStudio-id
|
|
6
|
+
License: Proprietary
|
|
7
|
+
Project-URL: Homepage, https://github.com/HubStudio-id/hubstudio-data-intel
|
|
8
|
+
Keywords: honeyframe,hubstudio,data-platform,dataiku,analytics
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: httpx>=0.24
|
|
12
|
+
Provides-Extra: pandas
|
|
13
|
+
Requires-Dist: pandas>=1.5; extra == "pandas"
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
16
|
+
Requires-Dist: pandas>=1.5; extra == "dev"
|
|
17
|
+
Requires-Dist: mypy>=1.8; extra == "dev"
|
|
18
|
+
|
|
19
|
+
# honeyframeapi
|
|
20
|
+
|
|
21
|
+
A `dataikuapi`-style Python client for the **Honeyframe** data platform
|
|
22
|
+
(Honeyframe Platform / `platform.hubstudio.id`). Script the platform from a
|
|
23
|
+
notebook: read data into pandas, build dashboards and cards, browse datasets
|
|
24
|
+
and connectors.
|
|
25
|
+
|
|
26
|
+
If you've used `dataikuapi`, this will feel identical — construct one client,
|
|
27
|
+
then navigate to project / dataset / dashboard handles.
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
import dataikuapi
|
|
31
|
+
client = dataikuapi.DSSClient(host, api_key) # Dataiku
|
|
32
|
+
```
|
|
33
|
+
```python
|
|
34
|
+
import honeyframeapi
|
|
35
|
+
client = honeyframeapi.HoneyframeClient(host, username=..., password=..., project_id=1) # Honeyframe
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install -e sdk/python # core (httpx only)
|
|
42
|
+
pip install -e "sdk/python[pandas]" # + pandas for get_dataframe()/sql()
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Authenticate
|
|
46
|
+
|
|
47
|
+
Three ways, in precedence order:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import honeyframeapi
|
|
51
|
+
|
|
52
|
+
# 1. email / password (works against any deployment today; auto-refreshes the 8h JWT)
|
|
53
|
+
client = honeyframeapi.HoneyframeClient(
|
|
54
|
+
"https://platform.hubstudio.id",
|
|
55
|
+
username="you@hubstudio.id", password="…",
|
|
56
|
+
project_id=1,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# 2. a token you already hold — a JWT, or (preferred for automation) a
|
|
60
|
+
# long-lived Personal Access Token minted from the UI
|
|
61
|
+
# (avatar menu → "Personal access tokens") or via client.create_pat()
|
|
62
|
+
client = honeyframeapi.HoneyframeClient("https://platform.hubstudio.id", token="hf_…", project_id=1)
|
|
63
|
+
|
|
64
|
+
# 3. from environment variables
|
|
65
|
+
# HONEYFRAME_URL (or HUB_API_BASE), HONEYFRAME_TOKEN,
|
|
66
|
+
# HONEYFRAME_USERNAME/PASSWORD (or HUB_USERNAME/PASSWORD), HONEYFRAME_PROJECT_ID
|
|
67
|
+
client = honeyframeapi.HoneyframeClient.from_env()
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Vibe-code the warehouse, end to end
|
|
71
|
+
|
|
72
|
+
One governed loop, and it adapts to the deployment — see
|
|
73
|
+
[`examples/vibe_code_end_to_end.py`](examples/vibe_code_end_to_end.py):
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
proj = client.get_project(1)
|
|
77
|
+
proj.engine() # 'dbt' or 'native' — does this install have dbt?
|
|
78
|
+
proj.upload_dataframe("raw", df, materialize=True) # push local data in
|
|
79
|
+
if proj.uses_dbt():
|
|
80
|
+
proj.dbt.create_model("stg_x", sql="SELECT … FROM {{ ref('raw') }}")
|
|
81
|
+
proj.dbt.run(select="stg_x") # author + build a dbt model
|
|
82
|
+
df = proj.sql("SELECT * FROM stg_x") # read results back
|
|
83
|
+
proj.get_dataset("raw").upstream() # trace lineage
|
|
84
|
+
|
|
85
|
+
# …then share it: dashboard → webapp → token-gated public link
|
|
86
|
+
dash = proj.create_dashboard("Demo")
|
|
87
|
+
dash.add_card("By region", "bar", "SELECT region, total FROM stg_x")
|
|
88
|
+
app = client.publish_webapp("demo", "Demo", config={"pages": [...]})
|
|
89
|
+
print(app.create_public_link(expires_days=7)["share_url"])
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
(On a native install the authoring lines change — see `proj.engine()` — but the
|
|
93
|
+
upload → read → dashboard → webapp → share legs are identical.)
|
|
94
|
+
|
|
95
|
+
## Read data into pandas
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
# ad-hoc SQL — the analogue of dwh-adira's run_sql()
|
|
99
|
+
df = client.sql("""
|
|
100
|
+
SELECT booking_source, COUNT(*) AS n
|
|
101
|
+
FROM marts.fact_appointment
|
|
102
|
+
GROUP BY booking_source ORDER BY n DESC
|
|
103
|
+
""")
|
|
104
|
+
|
|
105
|
+
# or read a whole dataset
|
|
106
|
+
proj = client.get_project(1)
|
|
107
|
+
df = proj.get_dataset("fact_appointment").get_dataframe()
|
|
108
|
+
head = proj.get_dataset("fact_appointment").head(20)
|
|
109
|
+
stats = proj.get_dataset("fact_appointment").profile()
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Build a dashboard from code
|
|
113
|
+
|
|
114
|
+
This replaces the hand-rolled `requests` script in dwh-adira
|
|
115
|
+
(`scripts/upload_dashboards.py`) — same card dicts, ergonomic API:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
proj = client.get_project(1)
|
|
119
|
+
dash = proj.create_dashboard("Appointments Overview",
|
|
120
|
+
description="Booking channels & volume")
|
|
121
|
+
|
|
122
|
+
dash.add_card("Total Appointments", "kpi",
|
|
123
|
+
"SELECT COUNT(*) AS n FROM marts.fact_appointment",
|
|
124
|
+
config={"color_theme": "ocean"}, size_x=6, size_y=2)
|
|
125
|
+
|
|
126
|
+
dash.add_card("By Booking Source", "donut",
|
|
127
|
+
"SELECT booking_source AS name, COUNT(*) AS value "
|
|
128
|
+
"FROM marts.fact_appointment GROUP BY booking_source",
|
|
129
|
+
config={"nameField": "name", "valueField": "value"})
|
|
130
|
+
|
|
131
|
+
results = dash.execute_all()
|
|
132
|
+
print("Open:", dash.url)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Porting an existing `upload_dashboards.py` block? Pass the dicts straight through:
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
dash.add_cards(DASHBOARDS[0]["cards"]) # list of {"title","card_type","sql_query","card_config",…}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Publish a webapp + share it by link
|
|
142
|
+
|
|
143
|
+
The last mile: wrap dashboard cards in a webapp and hand a stakeholder a URL
|
|
144
|
+
they can open without an account (token-gated, PII-masked).
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
app = client.publish_webapp(
|
|
148
|
+
"ops-overview", "Ops Overview",
|
|
149
|
+
config={"pages": [{"key": "home", "title": "Home", "blocks": [
|
|
150
|
+
{"kind": "card_ref", "dashboard_id": dash.dashboard_id,
|
|
151
|
+
"card_id": dash.cards[0].card_id},
|
|
152
|
+
]}]},
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
link = app.create_public_link(label="exec share", expires_days=30)
|
|
156
|
+
print(link["token"]) # shown ONCE — store it
|
|
157
|
+
print(app.public_url(link["token"], # anonymous URL on the SaaS host
|
|
158
|
+
host="https://hospital.hubstudio.id"))
|
|
159
|
+
|
|
160
|
+
# later: audit + revoke
|
|
161
|
+
for l in app.public_links():
|
|
162
|
+
print(l["label"], l["status"], l["view_count"])
|
|
163
|
+
app.revoke_public_link(link["id"])
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`publish_webapp` is project-scoped — set `project_id` on the client first.
|
|
167
|
+
|
|
168
|
+
## Scenarios, jobs & recipes (dataikuapi parity)
|
|
169
|
+
|
|
170
|
+
Closes the gap behind dwh-adira's `run_instinct_daily_scenario.py` and
|
|
171
|
+
`scaffold_*` scripts:
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
proj = client.get_project(1)
|
|
175
|
+
|
|
176
|
+
# scenarios — trigger and poll like dataikuapi's scenario.run()
|
|
177
|
+
sc = proj.get_scenario("daily_pipeline")
|
|
178
|
+
final = sc.run_and_wait() # polls to terminal state
|
|
179
|
+
print(final["status"], final.get("steps_failed"))
|
|
180
|
+
|
|
181
|
+
# jobs / pipeline
|
|
182
|
+
runs = client.jobs.list_runs(status="failed")
|
|
183
|
+
run = client.jobs.trigger("dbt_run", select="int_appointments_unioned+")
|
|
184
|
+
print(client.jobs.logs("dbt_run", lines=100))
|
|
185
|
+
|
|
186
|
+
# recipes (dbt-engine build)
|
|
187
|
+
proj.get_recipe("dim_patient").build(downstream=True, wait=True)
|
|
188
|
+
|
|
189
|
+
# datasets — create like create_sql_table_dataset()
|
|
190
|
+
proj.create_dataset("ekko", connector_id=5,
|
|
191
|
+
connection_config={"schema_name": "RAW_RSL", "table_name": "EKKO"})
|
|
192
|
+
|
|
193
|
+
# connectors (org-level)
|
|
194
|
+
conn = client.create_connector("SAP Prod", "postgresql", config={"host": "…"})
|
|
195
|
+
print(conn.test())
|
|
196
|
+
|
|
197
|
+
# publish a data API + mint a key
|
|
198
|
+
asset = client.create_asset("data_api", "appointments", "Appointments API", config={...})
|
|
199
|
+
asset.publish()
|
|
200
|
+
print(asset.create_api_key(name="readonly").key) # dk_… shown once
|
|
201
|
+
|
|
202
|
+
# chat with an agent
|
|
203
|
+
print(client.get_agent(42).ask("Cancellations last month?")["answer"])
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## CLI
|
|
207
|
+
|
|
208
|
+
Installing the package also installs a `honeyframe` command. Flags go *after*
|
|
209
|
+
the subcommand (kubectl/gh style).
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
export HONEYFRAME_URL=https://platform.hubstudio.id
|
|
213
|
+
export HONEYFRAME_TOKEN=hf_… # a PAT (preferred) or JWT
|
|
214
|
+
|
|
215
|
+
honeyframe whoami
|
|
216
|
+
honeyframe projects -f csv
|
|
217
|
+
honeyframe datasets -p 1
|
|
218
|
+
honeyframe sql "SELECT COUNT(*) FROM marts.fact_appointment" -p 1 -f csv
|
|
219
|
+
honeyframe engine -p 1 # dbt or native install?
|
|
220
|
+
honeyframe upload sales sales.csv --materialize -p 1
|
|
221
|
+
honeyframe lineage fact_appointment --down -p 1
|
|
222
|
+
honeyframe export fact_appointment out.parquet --max-rows 100000 -p 1
|
|
223
|
+
honeyframe pat create --name laptop --expires-days 90
|
|
224
|
+
honeyframe scenario run daily_pipeline -p 1 --wait
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Resource map
|
|
228
|
+
|
|
229
|
+
| Handle | Get it from | Key methods |
|
|
230
|
+
|---|---|---|
|
|
231
|
+
| `HoneyframeClient` | constructor | `sql`, `list_projects`, `get_project`, `get_dashboard`, `jobs`, `list_connectors`, `create_connector`, `get_connector`, `list_assets`, `create_asset`, `get_asset`, `list_webapps`, `get_webapp`, `publish_webapp`, `list_agents`, `get_agent`, `chat`, `get_auth_info` |
|
|
232
|
+
| `Project` | `client.get_project(id_or_slug)` | `list_datasets`, `get_dataset`, `create_dataset`, `import_dataset`, `sql`, `create_dashboard`, `list_scenarios`, `get_scenario`, `create_scenario`, `get_recipe`, `run_dbt`, `get_preferences`, `set_preferences`, `list_connectors` |
|
|
233
|
+
| `Dataset` | `project.get_dataset(name)` | `get_dataframe`, `head`, `profile`, `get_metadata`, `ask`, `materialize`, `delete` |
|
|
234
|
+
| `Dashboard` | `project.create_dashboard(...)` / `client.get_dashboard(id)` | `add_card`, `add_cards`, `execute_all`, `cards`, `update`, `url` |
|
|
235
|
+
| `Card` | `dashboard.cards` / `dashboard.add_card(...)` | `execute`, `update`, `delete` |
|
|
236
|
+
| `Connector` | `client.create_connector(...)` / `client.get_connector(id)` | `test`, `update`, `delete` |
|
|
237
|
+
| `Scenario` | `project.get_scenario(id_or_name)` | `run`, `run_and_wait`, `get_last_runs`, `update`, `delete` |
|
|
238
|
+
| `Recipe` | `project.get_recipe(model_name)` | `get_metadata`, `runs`, `build`, `save_prepare_steps` |
|
|
239
|
+
| `JobsAPI` | `client.jobs` | `list_runs`, `get_run`, `wait`, `abort_run`, `trigger`, `logs` |
|
|
240
|
+
| `PublishedAsset` | `client.create_asset(...)` / `client.get_asset(id)` | `publish`, `unpublish`, `update`, `create_api_key`, `list_api_keys`, `revoke_api_key` |
|
|
241
|
+
| `Webapp` | `client.publish_webapp(...)` / `client.get_webapp(key)` | `config`, `public_links`, `create_public_link`, `revoke_public_link`, `url`, `public_url` |
|
|
242
|
+
| `Agent` | `client.get_agent(id)` | `ask` |
|
|
243
|
+
|
|
244
|
+
## Errors
|
|
245
|
+
|
|
246
|
+
Every failure raises a subclass of `honeyframeapi.HoneyframeError`:
|
|
247
|
+
`HoneyframeAuthError` (401/403), `HoneyframeNotFoundError` (404),
|
|
248
|
+
`HoneyframeValidationError` (400/409/422), `HoneyframeAPIError` (other non-2xx),
|
|
249
|
+
each carrying `.status_code` and `.detail`.
|
|
250
|
+
|
|
251
|
+
## Scope
|
|
252
|
+
|
|
253
|
+
Shipped: auth (login + token), projects, datasets→pandas + create/import/
|
|
254
|
+
materialize, ad-hoc SQL, dashboards + cards, connectors (full CRUD + test),
|
|
255
|
+
scenarios (run/wait/history), jobs + pipeline triggers + logs, recipes
|
|
256
|
+
(inspect + dbt build + author prepare steps), published assets + data-API-key
|
|
257
|
+
minting, agent chat. This covers the dataikuapi surface used across the
|
|
258
|
+
dwh-adira scripts.
|
|
259
|
+
|
|
260
|
+
Personal Access Tokens (`hf_…`) are first-class: mint from the settings UI or
|
|
261
|
+
`client.create_pat(name, expires_days=…)`, then authenticate with
|
|
262
|
+
`token="hf_…"`. They're independently revocable, scoped to the minting user,
|
|
263
|
+
and don't carry the 8h JWT expiry — the right credential for cron/automation.
|
|
264
|
+
|
|
265
|
+
Still planned: richer recipe authoring (SQL/Python recipe payloads beyond
|
|
266
|
+
prepare steps).
|
|
267
|
+
|
|
268
|
+
> **`client.sql()` note:** there's no public raw-SQL endpoint yet, so `sql()`
|
|
269
|
+
> runs through a hidden per-project scratch dashboard card (zero backend
|
|
270
|
+
> change). It honors the platform's per-card row cap — for full-table extracts
|
|
271
|
+
> use `Dataset.get_dataframe()`. When a `POST /api/sql` endpoint lands, only the
|
|
272
|
+
> internals change; `sql()`'s signature is stable.
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# honeyframeapi
|
|
2
|
+
|
|
3
|
+
A `dataikuapi`-style Python client for the **Honeyframe** data platform
|
|
4
|
+
(Honeyframe Platform / `platform.hubstudio.id`). Script the platform from a
|
|
5
|
+
notebook: read data into pandas, build dashboards and cards, browse datasets
|
|
6
|
+
and connectors.
|
|
7
|
+
|
|
8
|
+
If you've used `dataikuapi`, this will feel identical — construct one client,
|
|
9
|
+
then navigate to project / dataset / dashboard handles.
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
import dataikuapi
|
|
13
|
+
client = dataikuapi.DSSClient(host, api_key) # Dataiku
|
|
14
|
+
```
|
|
15
|
+
```python
|
|
16
|
+
import honeyframeapi
|
|
17
|
+
client = honeyframeapi.HoneyframeClient(host, username=..., password=..., project_id=1) # Honeyframe
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install -e sdk/python # core (httpx only)
|
|
24
|
+
pip install -e "sdk/python[pandas]" # + pandas for get_dataframe()/sql()
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Authenticate
|
|
28
|
+
|
|
29
|
+
Three ways, in precedence order:
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
import honeyframeapi
|
|
33
|
+
|
|
34
|
+
# 1. email / password (works against any deployment today; auto-refreshes the 8h JWT)
|
|
35
|
+
client = honeyframeapi.HoneyframeClient(
|
|
36
|
+
"https://platform.hubstudio.id",
|
|
37
|
+
username="you@hubstudio.id", password="…",
|
|
38
|
+
project_id=1,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# 2. a token you already hold — a JWT, or (preferred for automation) a
|
|
42
|
+
# long-lived Personal Access Token minted from the UI
|
|
43
|
+
# (avatar menu → "Personal access tokens") or via client.create_pat()
|
|
44
|
+
client = honeyframeapi.HoneyframeClient("https://platform.hubstudio.id", token="hf_…", project_id=1)
|
|
45
|
+
|
|
46
|
+
# 3. from environment variables
|
|
47
|
+
# HONEYFRAME_URL (or HUB_API_BASE), HONEYFRAME_TOKEN,
|
|
48
|
+
# HONEYFRAME_USERNAME/PASSWORD (or HUB_USERNAME/PASSWORD), HONEYFRAME_PROJECT_ID
|
|
49
|
+
client = honeyframeapi.HoneyframeClient.from_env()
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Vibe-code the warehouse, end to end
|
|
53
|
+
|
|
54
|
+
One governed loop, and it adapts to the deployment — see
|
|
55
|
+
[`examples/vibe_code_end_to_end.py`](examples/vibe_code_end_to_end.py):
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
proj = client.get_project(1)
|
|
59
|
+
proj.engine() # 'dbt' or 'native' — does this install have dbt?
|
|
60
|
+
proj.upload_dataframe("raw", df, materialize=True) # push local data in
|
|
61
|
+
if proj.uses_dbt():
|
|
62
|
+
proj.dbt.create_model("stg_x", sql="SELECT … FROM {{ ref('raw') }}")
|
|
63
|
+
proj.dbt.run(select="stg_x") # author + build a dbt model
|
|
64
|
+
df = proj.sql("SELECT * FROM stg_x") # read results back
|
|
65
|
+
proj.get_dataset("raw").upstream() # trace lineage
|
|
66
|
+
|
|
67
|
+
# …then share it: dashboard → webapp → token-gated public link
|
|
68
|
+
dash = proj.create_dashboard("Demo")
|
|
69
|
+
dash.add_card("By region", "bar", "SELECT region, total FROM stg_x")
|
|
70
|
+
app = client.publish_webapp("demo", "Demo", config={"pages": [...]})
|
|
71
|
+
print(app.create_public_link(expires_days=7)["share_url"])
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
(On a native install the authoring lines change — see `proj.engine()` — but the
|
|
75
|
+
upload → read → dashboard → webapp → share legs are identical.)
|
|
76
|
+
|
|
77
|
+
## Read data into pandas
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
# ad-hoc SQL — the analogue of dwh-adira's run_sql()
|
|
81
|
+
df = client.sql("""
|
|
82
|
+
SELECT booking_source, COUNT(*) AS n
|
|
83
|
+
FROM marts.fact_appointment
|
|
84
|
+
GROUP BY booking_source ORDER BY n DESC
|
|
85
|
+
""")
|
|
86
|
+
|
|
87
|
+
# or read a whole dataset
|
|
88
|
+
proj = client.get_project(1)
|
|
89
|
+
df = proj.get_dataset("fact_appointment").get_dataframe()
|
|
90
|
+
head = proj.get_dataset("fact_appointment").head(20)
|
|
91
|
+
stats = proj.get_dataset("fact_appointment").profile()
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Build a dashboard from code
|
|
95
|
+
|
|
96
|
+
This replaces the hand-rolled `requests` script in dwh-adira
|
|
97
|
+
(`scripts/upload_dashboards.py`) — same card dicts, ergonomic API:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
proj = client.get_project(1)
|
|
101
|
+
dash = proj.create_dashboard("Appointments Overview",
|
|
102
|
+
description="Booking channels & volume")
|
|
103
|
+
|
|
104
|
+
dash.add_card("Total Appointments", "kpi",
|
|
105
|
+
"SELECT COUNT(*) AS n FROM marts.fact_appointment",
|
|
106
|
+
config={"color_theme": "ocean"}, size_x=6, size_y=2)
|
|
107
|
+
|
|
108
|
+
dash.add_card("By Booking Source", "donut",
|
|
109
|
+
"SELECT booking_source AS name, COUNT(*) AS value "
|
|
110
|
+
"FROM marts.fact_appointment GROUP BY booking_source",
|
|
111
|
+
config={"nameField": "name", "valueField": "value"})
|
|
112
|
+
|
|
113
|
+
results = dash.execute_all()
|
|
114
|
+
print("Open:", dash.url)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Porting an existing `upload_dashboards.py` block? Pass the dicts straight through:
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
dash.add_cards(DASHBOARDS[0]["cards"]) # list of {"title","card_type","sql_query","card_config",…}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Publish a webapp + share it by link
|
|
124
|
+
|
|
125
|
+
The last mile: wrap dashboard cards in a webapp and hand a stakeholder a URL
|
|
126
|
+
they can open without an account (token-gated, PII-masked).
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
app = client.publish_webapp(
|
|
130
|
+
"ops-overview", "Ops Overview",
|
|
131
|
+
config={"pages": [{"key": "home", "title": "Home", "blocks": [
|
|
132
|
+
{"kind": "card_ref", "dashboard_id": dash.dashboard_id,
|
|
133
|
+
"card_id": dash.cards[0].card_id},
|
|
134
|
+
]}]},
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
link = app.create_public_link(label="exec share", expires_days=30)
|
|
138
|
+
print(link["token"]) # shown ONCE — store it
|
|
139
|
+
print(app.public_url(link["token"], # anonymous URL on the SaaS host
|
|
140
|
+
host="https://hospital.hubstudio.id"))
|
|
141
|
+
|
|
142
|
+
# later: audit + revoke
|
|
143
|
+
for l in app.public_links():
|
|
144
|
+
print(l["label"], l["status"], l["view_count"])
|
|
145
|
+
app.revoke_public_link(link["id"])
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`publish_webapp` is project-scoped — set `project_id` on the client first.
|
|
149
|
+
|
|
150
|
+
## Scenarios, jobs & recipes (dataikuapi parity)
|
|
151
|
+
|
|
152
|
+
Closes the gap behind dwh-adira's `run_instinct_daily_scenario.py` and
|
|
153
|
+
`scaffold_*` scripts:
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
proj = client.get_project(1)
|
|
157
|
+
|
|
158
|
+
# scenarios — trigger and poll like dataikuapi's scenario.run()
|
|
159
|
+
sc = proj.get_scenario("daily_pipeline")
|
|
160
|
+
final = sc.run_and_wait() # polls to terminal state
|
|
161
|
+
print(final["status"], final.get("steps_failed"))
|
|
162
|
+
|
|
163
|
+
# jobs / pipeline
|
|
164
|
+
runs = client.jobs.list_runs(status="failed")
|
|
165
|
+
run = client.jobs.trigger("dbt_run", select="int_appointments_unioned+")
|
|
166
|
+
print(client.jobs.logs("dbt_run", lines=100))
|
|
167
|
+
|
|
168
|
+
# recipes (dbt-engine build)
|
|
169
|
+
proj.get_recipe("dim_patient").build(downstream=True, wait=True)
|
|
170
|
+
|
|
171
|
+
# datasets — create like create_sql_table_dataset()
|
|
172
|
+
proj.create_dataset("ekko", connector_id=5,
|
|
173
|
+
connection_config={"schema_name": "RAW_RSL", "table_name": "EKKO"})
|
|
174
|
+
|
|
175
|
+
# connectors (org-level)
|
|
176
|
+
conn = client.create_connector("SAP Prod", "postgresql", config={"host": "…"})
|
|
177
|
+
print(conn.test())
|
|
178
|
+
|
|
179
|
+
# publish a data API + mint a key
|
|
180
|
+
asset = client.create_asset("data_api", "appointments", "Appointments API", config={...})
|
|
181
|
+
asset.publish()
|
|
182
|
+
print(asset.create_api_key(name="readonly").key) # dk_… shown once
|
|
183
|
+
|
|
184
|
+
# chat with an agent
|
|
185
|
+
print(client.get_agent(42).ask("Cancellations last month?")["answer"])
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## CLI
|
|
189
|
+
|
|
190
|
+
Installing the package also installs a `honeyframe` command. Flags go *after*
|
|
191
|
+
the subcommand (kubectl/gh style).
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
export HONEYFRAME_URL=https://platform.hubstudio.id
|
|
195
|
+
export HONEYFRAME_TOKEN=hf_… # a PAT (preferred) or JWT
|
|
196
|
+
|
|
197
|
+
honeyframe whoami
|
|
198
|
+
honeyframe projects -f csv
|
|
199
|
+
honeyframe datasets -p 1
|
|
200
|
+
honeyframe sql "SELECT COUNT(*) FROM marts.fact_appointment" -p 1 -f csv
|
|
201
|
+
honeyframe engine -p 1 # dbt or native install?
|
|
202
|
+
honeyframe upload sales sales.csv --materialize -p 1
|
|
203
|
+
honeyframe lineage fact_appointment --down -p 1
|
|
204
|
+
honeyframe export fact_appointment out.parquet --max-rows 100000 -p 1
|
|
205
|
+
honeyframe pat create --name laptop --expires-days 90
|
|
206
|
+
honeyframe scenario run daily_pipeline -p 1 --wait
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Resource map
|
|
210
|
+
|
|
211
|
+
| Handle | Get it from | Key methods |
|
|
212
|
+
|---|---|---|
|
|
213
|
+
| `HoneyframeClient` | constructor | `sql`, `list_projects`, `get_project`, `get_dashboard`, `jobs`, `list_connectors`, `create_connector`, `get_connector`, `list_assets`, `create_asset`, `get_asset`, `list_webapps`, `get_webapp`, `publish_webapp`, `list_agents`, `get_agent`, `chat`, `get_auth_info` |
|
|
214
|
+
| `Project` | `client.get_project(id_or_slug)` | `list_datasets`, `get_dataset`, `create_dataset`, `import_dataset`, `sql`, `create_dashboard`, `list_scenarios`, `get_scenario`, `create_scenario`, `get_recipe`, `run_dbt`, `get_preferences`, `set_preferences`, `list_connectors` |
|
|
215
|
+
| `Dataset` | `project.get_dataset(name)` | `get_dataframe`, `head`, `profile`, `get_metadata`, `ask`, `materialize`, `delete` |
|
|
216
|
+
| `Dashboard` | `project.create_dashboard(...)` / `client.get_dashboard(id)` | `add_card`, `add_cards`, `execute_all`, `cards`, `update`, `url` |
|
|
217
|
+
| `Card` | `dashboard.cards` / `dashboard.add_card(...)` | `execute`, `update`, `delete` |
|
|
218
|
+
| `Connector` | `client.create_connector(...)` / `client.get_connector(id)` | `test`, `update`, `delete` |
|
|
219
|
+
| `Scenario` | `project.get_scenario(id_or_name)` | `run`, `run_and_wait`, `get_last_runs`, `update`, `delete` |
|
|
220
|
+
| `Recipe` | `project.get_recipe(model_name)` | `get_metadata`, `runs`, `build`, `save_prepare_steps` |
|
|
221
|
+
| `JobsAPI` | `client.jobs` | `list_runs`, `get_run`, `wait`, `abort_run`, `trigger`, `logs` |
|
|
222
|
+
| `PublishedAsset` | `client.create_asset(...)` / `client.get_asset(id)` | `publish`, `unpublish`, `update`, `create_api_key`, `list_api_keys`, `revoke_api_key` |
|
|
223
|
+
| `Webapp` | `client.publish_webapp(...)` / `client.get_webapp(key)` | `config`, `public_links`, `create_public_link`, `revoke_public_link`, `url`, `public_url` |
|
|
224
|
+
| `Agent` | `client.get_agent(id)` | `ask` |
|
|
225
|
+
|
|
226
|
+
## Errors
|
|
227
|
+
|
|
228
|
+
Every failure raises a subclass of `honeyframeapi.HoneyframeError`:
|
|
229
|
+
`HoneyframeAuthError` (401/403), `HoneyframeNotFoundError` (404),
|
|
230
|
+
`HoneyframeValidationError` (400/409/422), `HoneyframeAPIError` (other non-2xx),
|
|
231
|
+
each carrying `.status_code` and `.detail`.
|
|
232
|
+
|
|
233
|
+
## Scope
|
|
234
|
+
|
|
235
|
+
Shipped: auth (login + token), projects, datasets→pandas + create/import/
|
|
236
|
+
materialize, ad-hoc SQL, dashboards + cards, connectors (full CRUD + test),
|
|
237
|
+
scenarios (run/wait/history), jobs + pipeline triggers + logs, recipes
|
|
238
|
+
(inspect + dbt build + author prepare steps), published assets + data-API-key
|
|
239
|
+
minting, agent chat. This covers the dataikuapi surface used across the
|
|
240
|
+
dwh-adira scripts.
|
|
241
|
+
|
|
242
|
+
Personal Access Tokens (`hf_…`) are first-class: mint from the settings UI or
|
|
243
|
+
`client.create_pat(name, expires_days=…)`, then authenticate with
|
|
244
|
+
`token="hf_…"`. They're independently revocable, scoped to the minting user,
|
|
245
|
+
and don't carry the 8h JWT expiry — the right credential for cron/automation.
|
|
246
|
+
|
|
247
|
+
Still planned: richer recipe authoring (SQL/Python recipe payloads beyond
|
|
248
|
+
prepare steps).
|
|
249
|
+
|
|
250
|
+
> **`client.sql()` note:** there's no public raw-SQL endpoint yet, so `sql()`
|
|
251
|
+
> runs through a hidden per-project scratch dashboard card (zero backend
|
|
252
|
+
> change). It honors the platform's per-card row cap — for full-table extracts
|
|
253
|
+
> use `Dataset.get_dataframe()`. When a `POST /api/sql` endpoint lands, only the
|
|
254
|
+
> internals change; `sql()`'s signature is stable.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""honeyframeapi — a ``dataikuapi``-style Python client for the Honeyframe platform.
|
|
2
|
+
|
|
3
|
+
import honeyframeapi
|
|
4
|
+
client = honeyframeapi.HoneyframeClient(
|
|
5
|
+
"https://platform.hubstudio.id",
|
|
6
|
+
username="you@hubstudio.id", password="…", project_id=1)
|
|
7
|
+
|
|
8
|
+
df = client.sql("SELECT * FROM marts.fact_appointment FETCH FIRST 100 ROWS ONLY")
|
|
9
|
+
proj = client.get_project(1)
|
|
10
|
+
df2 = proj.get_dataset("fact_appointment").get_dataframe()
|
|
11
|
+
|
|
12
|
+
dash = proj.create_dashboard("Ops Overview")
|
|
13
|
+
dash.add_card("Total Visits", "kpi", "SELECT COUNT(*) AS n FROM marts.fact_appointment")
|
|
14
|
+
dash.execute_all()
|
|
15
|
+
"""
|
|
16
|
+
from .client import HoneyframeClient
|
|
17
|
+
from .project import Project
|
|
18
|
+
from .dataset import Dataset
|
|
19
|
+
from .dashboard import Dashboard, Card
|
|
20
|
+
from .connector import Connector
|
|
21
|
+
from .scenario import Scenario
|
|
22
|
+
from .recipe import Recipe
|
|
23
|
+
from .pipeline import JobsAPI
|
|
24
|
+
from .publishing import PublishedAsset, ApiKey
|
|
25
|
+
from .webapp import Webapp
|
|
26
|
+
from .agent import Agent
|
|
27
|
+
from .auth import AuthBase, LoginAuth, TokenAuth
|
|
28
|
+
from .exceptions import (
|
|
29
|
+
HoneyframeError,
|
|
30
|
+
HoneyframeConfigError,
|
|
31
|
+
HoneyframeAPIError,
|
|
32
|
+
HoneyframeAuthError,
|
|
33
|
+
HoneyframeNotFoundError,
|
|
34
|
+
HoneyframeValidationError,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
__version__ = "0.1.0"
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
"HoneyframeClient",
|
|
41
|
+
"Project",
|
|
42
|
+
"Dataset",
|
|
43
|
+
"Dashboard",
|
|
44
|
+
"Card",
|
|
45
|
+
"Connector",
|
|
46
|
+
"Scenario",
|
|
47
|
+
"Recipe",
|
|
48
|
+
"JobsAPI",
|
|
49
|
+
"PublishedAsset",
|
|
50
|
+
"ApiKey",
|
|
51
|
+
"Webapp",
|
|
52
|
+
"Agent",
|
|
53
|
+
"AuthBase",
|
|
54
|
+
"LoginAuth",
|
|
55
|
+
"TokenAuth",
|
|
56
|
+
"HoneyframeError",
|
|
57
|
+
"HoneyframeConfigError",
|
|
58
|
+
"HoneyframeAPIError",
|
|
59
|
+
"HoneyframeAuthError",
|
|
60
|
+
"HoneyframeNotFoundError",
|
|
61
|
+
"HoneyframeValidationError",
|
|
62
|
+
"__version__",
|
|
63
|
+
]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Optional-pandas plumbing.
|
|
2
|
+
|
|
3
|
+
The core SDK has no hard pandas dependency. Helpers here return a DataFrame
|
|
4
|
+
when pandas is importable, and otherwise fall back to a plain dict so scripts
|
|
5
|
+
that only need raw rows still work on a bare install.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
import pandas as _pd # noqa: F401
|
|
11
|
+
HAS_PANDAS = True
|
|
12
|
+
except Exception: # pragma: no cover - exercised only on bare installs
|
|
13
|
+
HAS_PANDAS = False
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def rows_to_dataframe(columns, rows):
|
|
17
|
+
"""Build a DataFrame from columns + positional rows, or a dict fallback."""
|
|
18
|
+
if HAS_PANDAS:
|
|
19
|
+
return _pd.DataFrame(rows, columns=columns)
|
|
20
|
+
return {"columns": columns, "rows": rows}
|