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.
Files changed (45) hide show
  1. honeyframeapi-0.1.0/PKG-INFO +272 -0
  2. honeyframeapi-0.1.0/README.md +254 -0
  3. honeyframeapi-0.1.0/honeyframeapi/__init__.py +63 -0
  4. honeyframeapi-0.1.0/honeyframeapi/_compat.py +20 -0
  5. honeyframeapi-0.1.0/honeyframeapi/_sql.py +143 -0
  6. honeyframeapi-0.1.0/honeyframeapi/agent.py +49 -0
  7. honeyframeapi-0.1.0/honeyframeapi/auth.py +141 -0
  8. honeyframeapi-0.1.0/honeyframeapi/cli.py +218 -0
  9. honeyframeapi-0.1.0/honeyframeapi/client.py +438 -0
  10. honeyframeapi-0.1.0/honeyframeapi/connector.py +52 -0
  11. honeyframeapi-0.1.0/honeyframeapi/dashboard.py +187 -0
  12. honeyframeapi-0.1.0/honeyframeapi/dataset.py +146 -0
  13. honeyframeapi-0.1.0/honeyframeapi/dbt.py +113 -0
  14. honeyframeapi-0.1.0/honeyframeapi/exceptions.py +66 -0
  15. honeyframeapi-0.1.0/honeyframeapi/pipeline.py +70 -0
  16. honeyframeapi-0.1.0/honeyframeapi/project.py +283 -0
  17. honeyframeapi-0.1.0/honeyframeapi/publishing.py +102 -0
  18. honeyframeapi-0.1.0/honeyframeapi/py.typed +0 -0
  19. honeyframeapi-0.1.0/honeyframeapi/recipe.py +110 -0
  20. honeyframeapi-0.1.0/honeyframeapi/scenario.py +65 -0
  21. honeyframeapi-0.1.0/honeyframeapi/webapp.py +110 -0
  22. honeyframeapi-0.1.0/honeyframeapi.egg-info/PKG-INFO +272 -0
  23. honeyframeapi-0.1.0/honeyframeapi.egg-info/SOURCES.txt +43 -0
  24. honeyframeapi-0.1.0/honeyframeapi.egg-info/dependency_links.txt +1 -0
  25. honeyframeapi-0.1.0/honeyframeapi.egg-info/entry_points.txt +2 -0
  26. honeyframeapi-0.1.0/honeyframeapi.egg-info/requires.txt +9 -0
  27. honeyframeapi-0.1.0/honeyframeapi.egg-info/top_level.txt +1 -0
  28. honeyframeapi-0.1.0/pyproject.toml +51 -0
  29. honeyframeapi-0.1.0/setup.cfg +4 -0
  30. honeyframeapi-0.1.0/tests/test_auth.py +81 -0
  31. honeyframeapi-0.1.0/tests/test_cli.py +49 -0
  32. honeyframeapi-0.1.0/tests/test_cli_warehouse_cmds.py +67 -0
  33. honeyframeapi-0.1.0/tests/test_client.py +79 -0
  34. honeyframeapi-0.1.0/tests/test_client_retry.py +100 -0
  35. honeyframeapi-0.1.0/tests/test_dashboards.py +73 -0
  36. honeyframeapi-0.1.0/tests/test_datasets.py +74 -0
  37. honeyframeapi-0.1.0/tests/test_devapi.py +131 -0
  38. honeyframeapi-0.1.0/tests/test_engine.py +49 -0
  39. honeyframeapi-0.1.0/tests/test_example_end_to_end.py +94 -0
  40. honeyframeapi-0.1.0/tests/test_export.py +57 -0
  41. honeyframeapi-0.1.0/tests/test_lineage.py +45 -0
  42. honeyframeapi-0.1.0/tests/test_parity.py +170 -0
  43. honeyframeapi-0.1.0/tests/test_py_typed.py +17 -0
  44. honeyframeapi-0.1.0/tests/test_upload.py +62 -0
  45. 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}