databar 2.1.0__tar.gz → 2.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.
- {databar-2.1.0/src/databar.egg-info → databar-2.2.0}/PKG-INFO +39 -3
- {databar-2.1.0 → databar-2.2.0}/README.md +38 -2
- {databar-2.1.0 → databar-2.2.0}/pyproject.toml +1 -1
- {databar-2.1.0 → databar-2.2.0}/src/databar/__init__.py +9 -1
- {databar-2.1.0 → databar-2.2.0}/src/databar/cli/_guide.py +18 -0
- {databar-2.1.0 → databar-2.2.0}/src/databar/cli/app.py +2 -1
- databar-2.2.0/src/databar/cli/flows.py +122 -0
- {databar-2.1.0 → databar-2.2.0}/src/databar/client.py +59 -3
- {databar-2.1.0 → databar-2.2.0}/src/databar/models.py +62 -1
- {databar-2.1.0 → databar-2.2.0/src/databar.egg-info}/PKG-INFO +39 -3
- {databar-2.1.0 → databar-2.2.0}/src/databar.egg-info/SOURCES.txt +1 -0
- {databar-2.1.0 → databar-2.2.0}/tests/test_cli.py +45 -1
- {databar-2.1.0 → databar-2.2.0}/tests/test_client.py +38 -0
- {databar-2.1.0 → databar-2.2.0}/LICENSE +0 -0
- {databar-2.1.0 → databar-2.2.0}/setup.cfg +0 -0
- {databar-2.1.0 → databar-2.2.0}/src/databar/cli/__init__.py +0 -0
- {databar-2.1.0 → databar-2.2.0}/src/databar/cli/_auth.py +0 -0
- {databar-2.1.0 → databar-2.2.0}/src/databar/cli/_onboard.py +0 -0
- {databar-2.1.0 → databar-2.2.0}/src/databar/cli/_output.py +0 -0
- {databar-2.1.0 → databar-2.2.0}/src/databar/cli/enrichments.py +0 -0
- {databar-2.1.0 → databar-2.2.0}/src/databar/cli/tables.py +0 -0
- {databar-2.1.0 → databar-2.2.0}/src/databar/cli/tasks.py +0 -0
- {databar-2.1.0 → databar-2.2.0}/src/databar/cli/waterfalls.py +0 -0
- {databar-2.1.0 → databar-2.2.0}/src/databar/exceptions.py +0 -0
- {databar-2.1.0 → databar-2.2.0}/src/databar.egg-info/dependency_links.txt +0 -0
- {databar-2.1.0 → databar-2.2.0}/src/databar.egg-info/entry_points.txt +0 -0
- {databar-2.1.0 → databar-2.2.0}/src/databar.egg-info/requires.txt +0 -0
- {databar-2.1.0 → databar-2.2.0}/src/databar.egg-info/top_level.txt +0 -0
- {databar-2.1.0 → databar-2.2.0}/tests/test_new_features.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: databar
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Official Databar.ai Python SDK and CLI — connect to enrichments, waterfalls, and tables via api.databar.ai
|
|
5
5
|
Author-email: "Databar.ai Team" <info@databar.ai>
|
|
6
6
|
License: MIT License
|
|
@@ -55,7 +55,7 @@ Dynamic: license-file
|
|
|
55
55
|
|
|
56
56
|
# Databar Python SDK
|
|
57
57
|
|
|
58
|
-
Official Python SDK and CLI for [Databar.ai](https://databar.ai) — run data enrichments, waterfall lookups, and manage tables via `api.databar.ai/v1`.
|
|
58
|
+
Official Python SDK and CLI for [Databar.ai](https://databar.ai) — run data enrichments, waterfall lookups, flows, and manage tables via `api.databar.ai/v1`.
|
|
59
59
|
|
|
60
60
|
[](https://pypi.org/project/databar-ai/)
|
|
61
61
|
[](https://pypi.org/project/databar-ai/)
|
|
@@ -154,7 +154,8 @@ data = client.run_enrichment_sync(123, {"email": "alice@example.com"})
|
|
|
154
154
|
# Run with pagination (for list-style enrichments)
|
|
155
155
|
data = client.run_enrichment_sync(123, {"query": "CEO"}, pages=3)
|
|
156
156
|
|
|
157
|
-
# Bulk run
|
|
157
|
+
# Bulk run — results are aligned to inputs: one element per input, in input
|
|
158
|
+
# order, with None for inputs that returned no data (len(data) == len(inputs)).
|
|
158
159
|
data = client.run_enrichment_bulk_sync(123, [
|
|
159
160
|
{"email": "alice@example.com"},
|
|
160
161
|
{"email": "bob@example.com"},
|
|
@@ -192,6 +193,26 @@ results = client.run_waterfall_bulk_sync(
|
|
|
192
193
|
)
|
|
193
194
|
```
|
|
194
195
|
|
|
196
|
+
### Flows
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
# List saved flows
|
|
200
|
+
flows = client.list_flows()
|
|
201
|
+
|
|
202
|
+
# Inspect a flow's declared inputs
|
|
203
|
+
flow = client.get_flow("flow-uuid")
|
|
204
|
+
for inp in flow.inputs:
|
|
205
|
+
print(f" {inp.id} (required={inp.required}): {inp.description}")
|
|
206
|
+
|
|
207
|
+
# Run a flow (submit + poll in one call).
|
|
208
|
+
# inputs maps each flow input id → value.
|
|
209
|
+
result = client.run_flow_sync("flow-uuid", {"email": "alice@example.com"})
|
|
210
|
+
|
|
211
|
+
# Or submit and poll manually
|
|
212
|
+
task = client.run_flow("flow-uuid", {"email": "alice@example.com"})
|
|
213
|
+
result = client.poll_task(task.task_id)
|
|
214
|
+
```
|
|
215
|
+
|
|
195
216
|
### Tables
|
|
196
217
|
|
|
197
218
|
```python
|
|
@@ -427,6 +448,21 @@ databar waterfall run email_getter --params '{"linkedin_url": "https://linkedin.
|
|
|
427
448
|
databar waterfall bulk email_getter --input leads.csv --out results.csv
|
|
428
449
|
```
|
|
429
450
|
|
|
451
|
+
### Flows
|
|
452
|
+
|
|
453
|
+
```bash
|
|
454
|
+
# List saved flows
|
|
455
|
+
databar flow list
|
|
456
|
+
databar flow list --query "buyer"
|
|
457
|
+
|
|
458
|
+
# Get flow details (declared inputs)
|
|
459
|
+
databar flow get <flow-id>
|
|
460
|
+
|
|
461
|
+
# Run a flow (--inputs maps each flow input id → value)
|
|
462
|
+
databar flow run <flow-id> --inputs '{"email": "alice@example.com"}'
|
|
463
|
+
databar flow run <flow-id> --inputs '{"email": "alice@example.com"}' --format json
|
|
464
|
+
```
|
|
465
|
+
|
|
430
466
|
### Tables
|
|
431
467
|
|
|
432
468
|
```bash
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Databar Python SDK
|
|
2
2
|
|
|
3
|
-
Official Python SDK and CLI for [Databar.ai](https://databar.ai) — run data enrichments, waterfall lookups, and manage tables via `api.databar.ai/v1`.
|
|
3
|
+
Official Python SDK and CLI for [Databar.ai](https://databar.ai) — run data enrichments, waterfall lookups, flows, and manage tables via `api.databar.ai/v1`.
|
|
4
4
|
|
|
5
5
|
[](https://pypi.org/project/databar-ai/)
|
|
6
6
|
[](https://pypi.org/project/databar-ai/)
|
|
@@ -99,7 +99,8 @@ data = client.run_enrichment_sync(123, {"email": "alice@example.com"})
|
|
|
99
99
|
# Run with pagination (for list-style enrichments)
|
|
100
100
|
data = client.run_enrichment_sync(123, {"query": "CEO"}, pages=3)
|
|
101
101
|
|
|
102
|
-
# Bulk run
|
|
102
|
+
# Bulk run — results are aligned to inputs: one element per input, in input
|
|
103
|
+
# order, with None for inputs that returned no data (len(data) == len(inputs)).
|
|
103
104
|
data = client.run_enrichment_bulk_sync(123, [
|
|
104
105
|
{"email": "alice@example.com"},
|
|
105
106
|
{"email": "bob@example.com"},
|
|
@@ -137,6 +138,26 @@ results = client.run_waterfall_bulk_sync(
|
|
|
137
138
|
)
|
|
138
139
|
```
|
|
139
140
|
|
|
141
|
+
### Flows
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
# List saved flows
|
|
145
|
+
flows = client.list_flows()
|
|
146
|
+
|
|
147
|
+
# Inspect a flow's declared inputs
|
|
148
|
+
flow = client.get_flow("flow-uuid")
|
|
149
|
+
for inp in flow.inputs:
|
|
150
|
+
print(f" {inp.id} (required={inp.required}): {inp.description}")
|
|
151
|
+
|
|
152
|
+
# Run a flow (submit + poll in one call).
|
|
153
|
+
# inputs maps each flow input id → value.
|
|
154
|
+
result = client.run_flow_sync("flow-uuid", {"email": "alice@example.com"})
|
|
155
|
+
|
|
156
|
+
# Or submit and poll manually
|
|
157
|
+
task = client.run_flow("flow-uuid", {"email": "alice@example.com"})
|
|
158
|
+
result = client.poll_task(task.task_id)
|
|
159
|
+
```
|
|
160
|
+
|
|
140
161
|
### Tables
|
|
141
162
|
|
|
142
163
|
```python
|
|
@@ -372,6 +393,21 @@ databar waterfall run email_getter --params '{"linkedin_url": "https://linkedin.
|
|
|
372
393
|
databar waterfall bulk email_getter --input leads.csv --out results.csv
|
|
373
394
|
```
|
|
374
395
|
|
|
396
|
+
### Flows
|
|
397
|
+
|
|
398
|
+
```bash
|
|
399
|
+
# List saved flows
|
|
400
|
+
databar flow list
|
|
401
|
+
databar flow list --query "buyer"
|
|
402
|
+
|
|
403
|
+
# Get flow details (declared inputs)
|
|
404
|
+
databar flow get <flow-id>
|
|
405
|
+
|
|
406
|
+
# Run a flow (--inputs maps each flow input id → value)
|
|
407
|
+
databar flow run <flow-id> --inputs '{"email": "alice@example.com"}'
|
|
408
|
+
databar flow run <flow-id> --inputs '{"email": "alice@example.com"}' --format json
|
|
409
|
+
```
|
|
410
|
+
|
|
375
411
|
### Tables
|
|
376
412
|
|
|
377
413
|
```bash
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "databar"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.2.0"
|
|
8
8
|
description = "Official Databar.ai Python SDK and CLI — connect to enrichments, waterfalls, and tables via api.databar.ai"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -59,6 +59,10 @@ from .models import (
|
|
|
59
59
|
# Waterfalls
|
|
60
60
|
Waterfall,
|
|
61
61
|
WaterfallEnrichment,
|
|
62
|
+
# Flows
|
|
63
|
+
Flow,
|
|
64
|
+
FlowInput,
|
|
65
|
+
FlowOutput,
|
|
62
66
|
# Tables
|
|
63
67
|
Table,
|
|
64
68
|
Column,
|
|
@@ -95,7 +99,7 @@ from .models import (
|
|
|
95
99
|
Folder,
|
|
96
100
|
)
|
|
97
101
|
|
|
98
|
-
__version__ = "2.
|
|
102
|
+
__version__ = "2.2.0"
|
|
99
103
|
__all__ = [
|
|
100
104
|
"DatabarClient",
|
|
101
105
|
# exceptions
|
|
@@ -131,6 +135,10 @@ __all__ = [
|
|
|
131
135
|
# waterfalls
|
|
132
136
|
"Waterfall",
|
|
133
137
|
"WaterfallEnrichment",
|
|
138
|
+
# flows
|
|
139
|
+
"Flow",
|
|
140
|
+
"FlowInput",
|
|
141
|
+
"FlowOutput",
|
|
134
142
|
# tables
|
|
135
143
|
"Table",
|
|
136
144
|
"Column",
|
|
@@ -128,6 +128,16 @@ databar waterfall run <identifier> --params '{"key": "value"}' --format json
|
|
|
128
128
|
databar waterfall bulk <identifier> --input data.csv --out results.csv
|
|
129
129
|
```
|
|
130
130
|
|
|
131
|
+
### Flows
|
|
132
|
+
```bash
|
|
133
|
+
databar flow list --format json
|
|
134
|
+
databar flow get <flow-id> --format json # declared inputs
|
|
135
|
+
databar flow run <flow-id> --inputs '{"input_id": "value"}' --format json
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
NOTE: `flow run` uses `--inputs` (maps each flow input id → value), NOT `--params`.
|
|
139
|
+
See `flow get` for the list of declared inputs.
|
|
140
|
+
|
|
131
141
|
### Tables
|
|
132
142
|
```bash
|
|
133
143
|
databar table list --format json
|
|
@@ -174,6 +184,13 @@ result = client.run_enrichment_sync(123, {"email": "alice@example.com"})
|
|
|
174
184
|
result = client.run_waterfall_sync("email_getter", {"linkedin_url": "..."})
|
|
175
185
|
# waterfall.identifier (also .slug) → slug like "email_getter"
|
|
176
186
|
|
|
187
|
+
# Flows
|
|
188
|
+
flows = client.list_flows()
|
|
189
|
+
flow = client.get_flow("flow-uuid")
|
|
190
|
+
# flow.id (also .identifier) → flow UUID
|
|
191
|
+
# flow.inputs[i].id → input key to use in the run inputs dict
|
|
192
|
+
result = client.run_flow_sync("flow-uuid", {"email": "alice@example.com"})
|
|
193
|
+
|
|
177
194
|
# Tables
|
|
178
195
|
tables = client.list_tables()
|
|
179
196
|
# table.identifier (also .id, .uuid) → UUID string
|
|
@@ -201,6 +218,7 @@ client.create_rows(table.identifier, [InsertRow(fields={"email": "alice@example.
|
|
|
201
218
|
|
|
202
219
|
- `Table`: `.id`, `.uuid` → `.identifier`
|
|
203
220
|
- `Waterfall`: `.slug` → `.identifier`
|
|
221
|
+
- `Flow`: `.identifier` → `.id`
|
|
204
222
|
- `EnrichmentParam`: `.slug` → `.name`, `.label` → `.description`, `.required` → `.is_required`
|
|
205
223
|
- `EnrichmentResponseField`: `.slug` → `.name`
|
|
206
224
|
|
|
@@ -10,7 +10,7 @@ import typer
|
|
|
10
10
|
|
|
11
11
|
from databar import __version__
|
|
12
12
|
|
|
13
|
-
from . import enrichments, tables, tasks, waterfalls
|
|
13
|
+
from . import enrichments, flows, tables, tasks, waterfalls
|
|
14
14
|
from ._auth import app as auth_app
|
|
15
15
|
|
|
16
16
|
app = typer.Typer(
|
|
@@ -29,6 +29,7 @@ app.add_typer(auth_app, name=None) # merged at root level
|
|
|
29
29
|
# Subcommand groups
|
|
30
30
|
app.add_typer(enrichments.app, name="enrich")
|
|
31
31
|
app.add_typer(waterfalls.app, name="waterfall")
|
|
32
|
+
app.add_typer(flows.app, name="flow")
|
|
32
33
|
app.add_typer(tables.app, name="table")
|
|
33
34
|
app.add_typer(tasks.app, name="task")
|
|
34
35
|
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CLI commands for flows.
|
|
3
|
+
|
|
4
|
+
databar flow list [--query] [--format]
|
|
5
|
+
databar flow get <flow-id> [--format]
|
|
6
|
+
databar flow run <flow-id> --inputs '{"email":"a@b.com"}' [--format] [--raw]
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
from typing import Optional
|
|
13
|
+
|
|
14
|
+
import typer
|
|
15
|
+
|
|
16
|
+
from databar.exceptions import DatabarError
|
|
17
|
+
|
|
18
|
+
from ._auth import get_client
|
|
19
|
+
from ._output import OutputFormat, console, error, info, output
|
|
20
|
+
|
|
21
|
+
app = typer.Typer(help="List and run saved flows.")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@app.command("list")
|
|
25
|
+
def list_flows(
|
|
26
|
+
query: Optional[str] = typer.Option(None, "--query", "-q", help="Filter by name/description."),
|
|
27
|
+
fmt: OutputFormat = typer.Option(OutputFormat.TABLE, "--format", "--output", "-f"),
|
|
28
|
+
) -> None:
|
|
29
|
+
"""List saved flows in the workspace."""
|
|
30
|
+
client = get_client()
|
|
31
|
+
try:
|
|
32
|
+
flows = client.list_flows()
|
|
33
|
+
except DatabarError as e:
|
|
34
|
+
error(str(e))
|
|
35
|
+
finally:
|
|
36
|
+
client.close()
|
|
37
|
+
|
|
38
|
+
if query:
|
|
39
|
+
q = query.lower()
|
|
40
|
+
flows = [
|
|
41
|
+
f for f in flows
|
|
42
|
+
if q in f.name.lower() or q in f.description.lower() or q in f.id.lower()
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
if not flows:
|
|
46
|
+
info("No flows found.")
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
rows = [
|
|
50
|
+
{
|
|
51
|
+
"id": f.id,
|
|
52
|
+
"name": f.name,
|
|
53
|
+
"inputs": len(f.inputs),
|
|
54
|
+
"description": f.description[:60] + ("…" if len(f.description) > 60 else ""),
|
|
55
|
+
}
|
|
56
|
+
for f in flows
|
|
57
|
+
]
|
|
58
|
+
output(rows, fmt, table_columns=["id", "name", "inputs", "description"])
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@app.command("get")
|
|
62
|
+
def get_flow(
|
|
63
|
+
flow_id: str = typer.Argument(..., help="Flow id."),
|
|
64
|
+
fmt: OutputFormat = typer.Option(OutputFormat.TABLE, "--format", "--output", "-f"),
|
|
65
|
+
) -> None:
|
|
66
|
+
"""Get details for a specific flow."""
|
|
67
|
+
client = get_client()
|
|
68
|
+
try:
|
|
69
|
+
f = client.get_flow(flow_id)
|
|
70
|
+
except DatabarError as exc:
|
|
71
|
+
error(str(exc))
|
|
72
|
+
finally:
|
|
73
|
+
client.close()
|
|
74
|
+
|
|
75
|
+
if fmt == OutputFormat.JSON:
|
|
76
|
+
output(f.model_dump(), fmt)
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
console.print(f"\n[bold cyan]Flow:[/bold cyan] {f.name} ({f.id})")
|
|
80
|
+
console.print(f"\n{f.description}\n")
|
|
81
|
+
|
|
82
|
+
if f.inputs:
|
|
83
|
+
console.print("[bold]Inputs:[/bold]")
|
|
84
|
+
rows = [
|
|
85
|
+
{
|
|
86
|
+
"id": inp.id,
|
|
87
|
+
"required": "yes" if inp.required else "no",
|
|
88
|
+
"type": inp.type,
|
|
89
|
+
"description": inp.description,
|
|
90
|
+
}
|
|
91
|
+
for inp in f.inputs
|
|
92
|
+
]
|
|
93
|
+
output(rows, OutputFormat.TABLE, table_columns=["id", "required", "type", "description"])
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@app.command("run")
|
|
97
|
+
def run_flow(
|
|
98
|
+
flow_id: str = typer.Argument(..., help="Flow id."),
|
|
99
|
+
inputs_json: str = typer.Option(..., "--inputs", "-i", help='JSON inputs, e.g. \'{"email":"a@b.com"}\''),
|
|
100
|
+
fmt: OutputFormat = typer.Option(OutputFormat.TABLE, "--format", "--output", "-f"),
|
|
101
|
+
raw: bool = typer.Option(False, "--raw", help="Print raw result without formatting."),
|
|
102
|
+
) -> None:
|
|
103
|
+
"""Run a flow and wait for results."""
|
|
104
|
+
try:
|
|
105
|
+
inputs = json.loads(inputs_json)
|
|
106
|
+
except json.JSONDecodeError as e:
|
|
107
|
+
error(f"Invalid JSON for --inputs: {e}")
|
|
108
|
+
|
|
109
|
+
client = get_client()
|
|
110
|
+
try:
|
|
111
|
+
info(f"Running flow '{flow_id}'…")
|
|
112
|
+
result = client.run_flow_sync(flow_id, inputs)
|
|
113
|
+
except DatabarError as exc:
|
|
114
|
+
error(str(exc))
|
|
115
|
+
finally:
|
|
116
|
+
client.close()
|
|
117
|
+
|
|
118
|
+
if raw:
|
|
119
|
+
console.print(result)
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
output(result, fmt)
|
|
@@ -15,6 +15,7 @@ Endpoint groups:
|
|
|
15
15
|
- Enrichments: list_enrichments, get_enrichment, run_enrichment[_bulk][_sync],
|
|
16
16
|
get_param_choices
|
|
17
17
|
- Waterfalls: list_waterfalls, get_waterfall, run_waterfall[_bulk][_sync]
|
|
18
|
+
- Flows: list_flows, get_flow, run_flow[_sync]
|
|
18
19
|
- Tables: create_table, list_tables, delete_table, rename_table,
|
|
19
20
|
get_columns, create_column, rename_column, delete_column,
|
|
20
21
|
get_table_enrichments, add_enrichment, run_table_enrichment,
|
|
@@ -70,6 +71,7 @@ from .models import (
|
|
|
70
71
|
ExporterListResponse,
|
|
71
72
|
ExporterParam,
|
|
72
73
|
ExporterResponseField,
|
|
74
|
+
Flow,
|
|
73
75
|
Folder,
|
|
74
76
|
InsertOptions,
|
|
75
77
|
InsertRow,
|
|
@@ -265,6 +267,11 @@ class DatabarClient:
|
|
|
265
267
|
Returns the task's data payload on success (status completed or partially_completed).
|
|
266
268
|
Raises DatabarTaskFailedError, DatabarGoneError or DatabarTimeoutError otherwise.
|
|
267
269
|
|
|
270
|
+
For bulk runs the payload is a list aligned to the inputs: one element
|
|
271
|
+
per input in input order, with ``None`` for inputs that returned no data
|
|
272
|
+
(so ``len(data) == number of inputs`` and ``data[i]`` ↔ ``input[i]``). A
|
|
273
|
+
single run returns the bare result object.
|
|
274
|
+
|
|
268
275
|
Task data is stored for 24 hours. After that the status becomes 'gone'.
|
|
269
276
|
"""
|
|
270
277
|
for _ in range(self._max_poll_attempts):
|
|
@@ -383,6 +390,11 @@ class DatabarClient:
|
|
|
383
390
|
"""
|
|
384
391
|
Submit a bulk enrichment run for multiple inputs.
|
|
385
392
|
|
|
393
|
+
The polled result is aligned to the inputs: one element per input, in the
|
|
394
|
+
same order as ``params``, with ``None`` for inputs that returned no data.
|
|
395
|
+
So ``len(result) == len(params)`` and ``result[i]`` is the result for
|
|
396
|
+
``params[i]`` — join results back to inputs by position.
|
|
397
|
+
|
|
386
398
|
Args:
|
|
387
399
|
enrichment_id: The enrichment to run.
|
|
388
400
|
params: List of per-row input parameter dicts.
|
|
@@ -410,7 +422,11 @@ class DatabarClient:
|
|
|
410
422
|
params: List[Dict[str, Any]],
|
|
411
423
|
pages: Optional[int] = None,
|
|
412
424
|
) -> Any:
|
|
413
|
-
"""Submit and poll a bulk enrichment, returning final data when complete.
|
|
425
|
+
"""Submit and poll a bulk enrichment, returning final data when complete.
|
|
426
|
+
|
|
427
|
+
Returns a list aligned to ``params``: one element per input in input
|
|
428
|
+
order, ``None`` for misses (``len(result) == len(params)``).
|
|
429
|
+
"""
|
|
414
430
|
task = self.run_enrichment_bulk(enrichment_id, params, pages=pages)
|
|
415
431
|
return self.poll_task(task.task_id)
|
|
416
432
|
|
|
@@ -478,7 +494,12 @@ class DatabarClient:
|
|
|
478
494
|
enrichments: Optional[List[int]] = None,
|
|
479
495
|
email_verifier: Optional[int] = None,
|
|
480
496
|
) -> RunResponse:
|
|
481
|
-
"""Submit a bulk waterfall run for multiple inputs.
|
|
497
|
+
"""Submit a bulk waterfall run for multiple inputs.
|
|
498
|
+
|
|
499
|
+
The polled result is aligned to the inputs: one element per input, in the
|
|
500
|
+
same order as ``params``, with ``None`` for inputs that returned no data
|
|
501
|
+
(``len(result) == len(params)``, ``result[i]`` ↔ ``params[i]``).
|
|
502
|
+
"""
|
|
482
503
|
if not enrichments:
|
|
483
504
|
waterfall = self.get_waterfall(identifier)
|
|
484
505
|
enrichments = [e.id for e in waterfall.available_enrichments]
|
|
@@ -508,10 +529,45 @@ class DatabarClient:
|
|
|
508
529
|
enrichments: Optional[List[int]] = None,
|
|
509
530
|
email_verifier: Optional[int] = None,
|
|
510
531
|
) -> Any:
|
|
511
|
-
"""Submit and poll a bulk waterfall, returning final data when complete.
|
|
532
|
+
"""Submit and poll a bulk waterfall, returning final data when complete.
|
|
533
|
+
|
|
534
|
+
Returns a list aligned to ``params``: one element per input in input
|
|
535
|
+
order, ``None`` for misses (``len(result) == len(params)``).
|
|
536
|
+
"""
|
|
512
537
|
task = self.run_waterfall_bulk(identifier, params, enrichments, email_verifier)
|
|
513
538
|
return self.poll_task(task.task_id)
|
|
514
539
|
|
|
540
|
+
# -----------------------------------------------------------------------
|
|
541
|
+
# Flows
|
|
542
|
+
# -----------------------------------------------------------------------
|
|
543
|
+
|
|
544
|
+
def list_flows(self) -> List[Flow]:
|
|
545
|
+
"""List all saved flows in the workspace."""
|
|
546
|
+
data = self._request("GET", "/flows")
|
|
547
|
+
return [Flow.model_validate(f) for f in data]
|
|
548
|
+
|
|
549
|
+
def get_flow(self, flow_id: str) -> Flow:
|
|
550
|
+
"""Get details for a specific flow, including its declared inputs."""
|
|
551
|
+
data = self._request("GET", f"/flows/{flow_id}")
|
|
552
|
+
return Flow.model_validate(data)
|
|
553
|
+
|
|
554
|
+
def run_flow(self, flow_id: str, inputs: Dict[str, str]) -> RunResponse:
|
|
555
|
+
"""
|
|
556
|
+
Submit a flow run. Returns a task — use poll_task() or run_flow_sync().
|
|
557
|
+
|
|
558
|
+
Args:
|
|
559
|
+
flow_id: The flow UUID (from list_flows / get_flow).
|
|
560
|
+
inputs: Maps each flow input id → value. See get_flow() for the
|
|
561
|
+
list of declared inputs.
|
|
562
|
+
"""
|
|
563
|
+
data = self._request("POST", f"/flows/{flow_id}/run", json={"inputs": inputs})
|
|
564
|
+
return RunResponse.model_validate(data)
|
|
565
|
+
|
|
566
|
+
def run_flow_sync(self, flow_id: str, inputs: Dict[str, str]) -> Any:
|
|
567
|
+
"""Submit and poll a flow, returning final data when complete."""
|
|
568
|
+
task = self.run_flow(flow_id, inputs)
|
|
569
|
+
return self.poll_task(task.task_id)
|
|
570
|
+
|
|
515
571
|
# -----------------------------------------------------------------------
|
|
516
572
|
# Tables — CRUD
|
|
517
573
|
# -----------------------------------------------------------------------
|
|
@@ -261,7 +261,12 @@ class TaskResponse(BaseModel):
|
|
|
261
261
|
)
|
|
262
262
|
data: Optional[Union[List[Any], Dict[str, Any]]] = Field(
|
|
263
263
|
default=None,
|
|
264
|
-
description=
|
|
264
|
+
description=(
|
|
265
|
+
"Resulting data once completed. For bulk runs this is a list aligned to "
|
|
266
|
+
"the inputs: one element per input in input order, with null for inputs "
|
|
267
|
+
"that returned no data (len(data) == number of inputs). A single run "
|
|
268
|
+
"returns the bare result object."
|
|
269
|
+
),
|
|
265
270
|
)
|
|
266
271
|
error: Optional[Union[str, List[str]]] = None
|
|
267
272
|
credits_spent: float = 0
|
|
@@ -315,6 +320,62 @@ class Waterfall(BaseModel):
|
|
|
315
320
|
return self.identifier
|
|
316
321
|
|
|
317
322
|
|
|
323
|
+
# ===========================================================================
|
|
324
|
+
# Flows
|
|
325
|
+
# ===========================================================================
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
class FlowInput(BaseModel):
|
|
329
|
+
"""A declared input of a flow.
|
|
330
|
+
|
|
331
|
+
Fields: id, description, type, required.
|
|
332
|
+
|
|
333
|
+
The ``id`` is the key to use in the inputs dict passed to run_flow().
|
|
334
|
+
"""
|
|
335
|
+
|
|
336
|
+
id: str = Field(description="Input id — the key to use in the run_flow() inputs dict.")
|
|
337
|
+
description: str
|
|
338
|
+
type: str
|
|
339
|
+
required: bool
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
class FlowOutput(BaseModel):
|
|
343
|
+
"""A field produced by a flow node.
|
|
344
|
+
|
|
345
|
+
Fields: id, response_field_id.
|
|
346
|
+
"""
|
|
347
|
+
|
|
348
|
+
id: str
|
|
349
|
+
response_field_id: Optional[int] = None
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
class Flow(BaseModel):
|
|
353
|
+
"""A saved workspace flow (multi-step enrichment pipeline).
|
|
354
|
+
|
|
355
|
+
Fields: id, name, description, inputs, outputs, created_at, updated_at.
|
|
356
|
+
|
|
357
|
+
Property aliases: .identifier → .id.
|
|
358
|
+
|
|
359
|
+
Usage::
|
|
360
|
+
|
|
361
|
+
flow = client.get_flow("flow-uuid")
|
|
362
|
+
result = client.run_flow_sync(flow.id, {"email": "alice@example.com"})
|
|
363
|
+
"""
|
|
364
|
+
|
|
365
|
+
id: str = Field(description="Flow UUID. Use this when calling get_flow()/run_flow().")
|
|
366
|
+
name: str
|
|
367
|
+
description: str
|
|
368
|
+
inputs: List[FlowInput] = Field(default_factory=list)
|
|
369
|
+
outputs: List[FlowOutput] = Field(default_factory=list)
|
|
370
|
+
created_at: str = ""
|
|
371
|
+
updated_at: str = ""
|
|
372
|
+
|
|
373
|
+
@property
|
|
374
|
+
def identifier(self) -> str:
|
|
375
|
+
"""Alias for id."""
|
|
376
|
+
return self.id
|
|
377
|
+
|
|
378
|
+
|
|
318
379
|
# ===========================================================================
|
|
319
380
|
# Tables
|
|
320
381
|
# ===========================================================================
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: databar
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Official Databar.ai Python SDK and CLI — connect to enrichments, waterfalls, and tables via api.databar.ai
|
|
5
5
|
Author-email: "Databar.ai Team" <info@databar.ai>
|
|
6
6
|
License: MIT License
|
|
@@ -55,7 +55,7 @@ Dynamic: license-file
|
|
|
55
55
|
|
|
56
56
|
# Databar Python SDK
|
|
57
57
|
|
|
58
|
-
Official Python SDK and CLI for [Databar.ai](https://databar.ai) — run data enrichments, waterfall lookups, and manage tables via `api.databar.ai/v1`.
|
|
58
|
+
Official Python SDK and CLI for [Databar.ai](https://databar.ai) — run data enrichments, waterfall lookups, flows, and manage tables via `api.databar.ai/v1`.
|
|
59
59
|
|
|
60
60
|
[](https://pypi.org/project/databar-ai/)
|
|
61
61
|
[](https://pypi.org/project/databar-ai/)
|
|
@@ -154,7 +154,8 @@ data = client.run_enrichment_sync(123, {"email": "alice@example.com"})
|
|
|
154
154
|
# Run with pagination (for list-style enrichments)
|
|
155
155
|
data = client.run_enrichment_sync(123, {"query": "CEO"}, pages=3)
|
|
156
156
|
|
|
157
|
-
# Bulk run
|
|
157
|
+
# Bulk run — results are aligned to inputs: one element per input, in input
|
|
158
|
+
# order, with None for inputs that returned no data (len(data) == len(inputs)).
|
|
158
159
|
data = client.run_enrichment_bulk_sync(123, [
|
|
159
160
|
{"email": "alice@example.com"},
|
|
160
161
|
{"email": "bob@example.com"},
|
|
@@ -192,6 +193,26 @@ results = client.run_waterfall_bulk_sync(
|
|
|
192
193
|
)
|
|
193
194
|
```
|
|
194
195
|
|
|
196
|
+
### Flows
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
# List saved flows
|
|
200
|
+
flows = client.list_flows()
|
|
201
|
+
|
|
202
|
+
# Inspect a flow's declared inputs
|
|
203
|
+
flow = client.get_flow("flow-uuid")
|
|
204
|
+
for inp in flow.inputs:
|
|
205
|
+
print(f" {inp.id} (required={inp.required}): {inp.description}")
|
|
206
|
+
|
|
207
|
+
# Run a flow (submit + poll in one call).
|
|
208
|
+
# inputs maps each flow input id → value.
|
|
209
|
+
result = client.run_flow_sync("flow-uuid", {"email": "alice@example.com"})
|
|
210
|
+
|
|
211
|
+
# Or submit and poll manually
|
|
212
|
+
task = client.run_flow("flow-uuid", {"email": "alice@example.com"})
|
|
213
|
+
result = client.poll_task(task.task_id)
|
|
214
|
+
```
|
|
215
|
+
|
|
195
216
|
### Tables
|
|
196
217
|
|
|
197
218
|
```python
|
|
@@ -427,6 +448,21 @@ databar waterfall run email_getter --params '{"linkedin_url": "https://linkedin.
|
|
|
427
448
|
databar waterfall bulk email_getter --input leads.csv --out results.csv
|
|
428
449
|
```
|
|
429
450
|
|
|
451
|
+
### Flows
|
|
452
|
+
|
|
453
|
+
```bash
|
|
454
|
+
# List saved flows
|
|
455
|
+
databar flow list
|
|
456
|
+
databar flow list --query "buyer"
|
|
457
|
+
|
|
458
|
+
# Get flow details (declared inputs)
|
|
459
|
+
databar flow get <flow-id>
|
|
460
|
+
|
|
461
|
+
# Run a flow (--inputs maps each flow input id → value)
|
|
462
|
+
databar flow run <flow-id> --inputs '{"email": "alice@example.com"}'
|
|
463
|
+
databar flow run <flow-id> --inputs '{"email": "alice@example.com"}' --format json
|
|
464
|
+
```
|
|
465
|
+
|
|
430
466
|
### Tables
|
|
431
467
|
|
|
432
468
|
```bash
|
|
@@ -28,6 +28,8 @@ from databar.models import (
|
|
|
28
28
|
Enrichment,
|
|
29
29
|
EnrichmentParam,
|
|
30
30
|
EnrichmentSummary,
|
|
31
|
+
Flow,
|
|
32
|
+
FlowInput,
|
|
31
33
|
Table,
|
|
32
34
|
TableEnrichment,
|
|
33
35
|
TaskResponse,
|
|
@@ -88,6 +90,18 @@ def _client_mock(**method_overrides):
|
|
|
88
90
|
m.run_waterfall_sync.return_value = [{"email": "alice@example.com"}]
|
|
89
91
|
m.run_waterfall_bulk_sync.return_value = [{"email": "alice@example.com"}]
|
|
90
92
|
|
|
93
|
+
m.list_flows.return_value = [
|
|
94
|
+
Flow(
|
|
95
|
+
id="flow-uuid-1",
|
|
96
|
+
name="Find buyer",
|
|
97
|
+
description="Enrich a lead",
|
|
98
|
+
inputs=[FlowInput(id="email", description="Email", type="text", required=True)],
|
|
99
|
+
outputs=[],
|
|
100
|
+
)
|
|
101
|
+
]
|
|
102
|
+
m.get_flow.return_value = m.list_flows.return_value[0]
|
|
103
|
+
m.run_flow_sync.return_value = {"full_name": "Alice"}
|
|
104
|
+
|
|
91
105
|
m.list_tables.return_value = [
|
|
92
106
|
Table(identifier="tbl-1", name="My Table", created_at="2024-01-01", updated_at="2024-01-01")
|
|
93
107
|
]
|
|
@@ -241,6 +255,36 @@ def test_waterfall_run(monkeypatch):
|
|
|
241
255
|
mock.run_waterfall_sync.assert_called_once()
|
|
242
256
|
|
|
243
257
|
|
|
258
|
+
# ===========================================================================
|
|
259
|
+
# Flows
|
|
260
|
+
# ===========================================================================
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def test_flow_list(monkeypatch):
|
|
264
|
+
mock = _client_mock()
|
|
265
|
+
monkeypatch.setattr("databar.cli.flows.get_client", lambda: mock)
|
|
266
|
+
result = invoke(["flow", "list"])
|
|
267
|
+
assert result.exit_code == 0
|
|
268
|
+
assert "flow-uuid-1" in result.output
|
|
269
|
+
assert "Find buyer" in result.output
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def test_flow_get(monkeypatch):
|
|
273
|
+
mock = _client_mock()
|
|
274
|
+
monkeypatch.setattr("databar.cli.flows.get_client", lambda: mock)
|
|
275
|
+
result = invoke(["flow", "get", "flow-uuid-1"])
|
|
276
|
+
assert result.exit_code == 0
|
|
277
|
+
assert "Find buyer" in result.output
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def test_flow_run(monkeypatch):
|
|
281
|
+
mock = _client_mock()
|
|
282
|
+
monkeypatch.setattr("databar.cli.flows.get_client", lambda: mock)
|
|
283
|
+
result = invoke(["flow", "run", "flow-uuid-1", "--inputs", '{"email":"alice@example.com"}'])
|
|
284
|
+
assert result.exit_code == 0
|
|
285
|
+
mock.run_flow_sync.assert_called_once_with("flow-uuid-1", {"email": "alice@example.com"})
|
|
286
|
+
|
|
287
|
+
|
|
244
288
|
# ===========================================================================
|
|
245
289
|
# Tables
|
|
246
290
|
# ===========================================================================
|
|
@@ -329,4 +373,4 @@ def test_task_get_poll(monkeypatch):
|
|
|
329
373
|
def test_version_flag():
|
|
330
374
|
result = runner.invoke(app, ["--version"])
|
|
331
375
|
assert result.exit_code == 0
|
|
332
|
-
assert "2.0
|
|
376
|
+
assert "2.2.0" in result.output
|
|
@@ -38,6 +38,7 @@ from .conftest import (
|
|
|
38
38
|
BASE_URL,
|
|
39
39
|
enrichment_payload,
|
|
40
40
|
enrichment_summary_payload,
|
|
41
|
+
flow_payload,
|
|
41
42
|
table_payload,
|
|
42
43
|
task_payload,
|
|
43
44
|
user_payload,
|
|
@@ -200,6 +201,43 @@ def test_run_waterfall_auto_resolves_providers(client: DatabarClient, httpx_mock
|
|
|
200
201
|
assert body["enrichments"] == [10, 11]
|
|
201
202
|
|
|
202
203
|
|
|
204
|
+
# ===========================================================================
|
|
205
|
+
# Flows
|
|
206
|
+
# ===========================================================================
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def test_list_flows(client: DatabarClient, httpx_mock: HTTPXMock):
|
|
210
|
+
httpx_mock.add_response(url=f"{BASE_URL}/flows", json=[flow_payload()])
|
|
211
|
+
result = client.list_flows()
|
|
212
|
+
assert len(result) == 1
|
|
213
|
+
assert result[0].id == "flow-uuid-1"
|
|
214
|
+
assert result[0].identifier == "flow-uuid-1"
|
|
215
|
+
assert result[0].inputs[0].id == "email"
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def test_get_flow(client: DatabarClient, httpx_mock: HTTPXMock):
|
|
219
|
+
httpx_mock.add_response(url=f"{BASE_URL}/flows/flow-uuid-1", json=flow_payload())
|
|
220
|
+
f = client.get_flow("flow-uuid-1")
|
|
221
|
+
assert f.name == "Find buyer"
|
|
222
|
+
assert f.inputs[0].required is True
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def test_run_flow_returns_task(client: DatabarClient, httpx_mock: HTTPXMock):
|
|
226
|
+
httpx_mock.add_response(url=f"{BASE_URL}/flows/flow-uuid-1/run", json=task_payload("processing"))
|
|
227
|
+
task = client.run_flow("flow-uuid-1", {"email": "alice@example.com"})
|
|
228
|
+
assert task.task_id == "task-123"
|
|
229
|
+
req = httpx_mock.get_requests()[-1]
|
|
230
|
+
body = json.loads(req.content)
|
|
231
|
+
assert body == {"inputs": {"email": "alice@example.com"}}
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def test_run_flow_sync(client: DatabarClient, httpx_mock: HTTPXMock):
|
|
235
|
+
httpx_mock.add_response(url=f"{BASE_URL}/flows/flow-uuid-1/run", json=task_payload("processing"))
|
|
236
|
+
httpx_mock.add_response(url=f"{BASE_URL}/tasks/task-123", json=task_payload("completed", data={"full_name": "Alice"}))
|
|
237
|
+
result = client.run_flow_sync("flow-uuid-1", {"email": "alice@example.com"})
|
|
238
|
+
assert result == {"full_name": "Alice"}
|
|
239
|
+
|
|
240
|
+
|
|
203
241
|
# ===========================================================================
|
|
204
242
|
# Tables
|
|
205
243
|
# ===========================================================================
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|