tinyfish 0.2.5__tar.gz → 0.3.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.
- tinyfish-0.3.0/CHANGELOG.md +9 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/PKG-INFO +121 -2
- {tinyfish-0.2.5 → tinyfish-0.3.0}/README.md +120 -1
- {tinyfish-0.2.5 → tinyfish-0.3.0}/RELEASE.md +16 -16
- {tinyfish-0.2.5 → tinyfish-0.3.0}/pyproject.toml +9 -3
- {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/__init__.py +27 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/_utils/client/async_.py +10 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/_utils/client/sync.py +10 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/_utils/resource.py +2 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/agent/__init__.py +146 -6
- {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/client.py +5 -0
- tinyfish-0.3.0/src/tinyfish/fetch/__init__.py +273 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/fetch/types.py +14 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/runs/types.py +4 -0
- tinyfish-0.3.0/src/tinyfish/search/__init__.py +202 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/search/types.py +8 -0
- tinyfish-0.3.0/src/tinyfish/vault/__init__.py +208 -0
- tinyfish-0.3.0/src/tinyfish/vault/types.py +106 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/tests/test_agent.py +249 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/tests/test_fetch.py +217 -1
- {tinyfish-0.2.5 → tinyfish-0.3.0}/tests/test_runs.py +159 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/tests/test_search.py +190 -5
- tinyfish-0.3.0/tests/test_vault.py +438 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/uv.lock +19 -12
- tinyfish-0.2.5/src/tinyfish/fetch/__init__.py +0 -99
- tinyfish-0.2.5/src/tinyfish/search/__init__.py +0 -79
- {tinyfish-0.2.5 → tinyfish-0.3.0}/.gitignore +0 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/.pre-commit-config.yaml +0 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/AGENTS.md +0 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/CLAUDE.md +0 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/docs/exceptions-and-errors-guide.md +0 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/docs/internal/api-integration-header.md +0 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/docs/internal/exceptions-and-errors-guide.md +0 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/docs/internal/publishing-private-guide.md +0 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/docs/pagination-guide.md +0 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/docs/proxy-and-browser-profiles.md +0 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/docs/streaming-guide.md +0 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/_utils/__init__.py +0 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/_utils/client/__init__.py +0 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/_utils/client/_base.py +0 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/_utils/exceptions.py +0 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/_utils/sse_parser.py +0 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/agent/types.py +0 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/browser/__init__.py +0 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/browser/types.py +0 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/py.typed +0 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/runs/__init__.py +0 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/tests/__init__.py +0 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/tests/conftest.py +0 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/tests/test_browser.py +0 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/tests/test_client.py +0 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/tests/test_errors.py +0 -0
- {tinyfish-0.2.5 → tinyfish-0.3.0}/tests/testing_guide.md +0 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.3.0](https://github.com/tinyfish-io/ux-labs/compare/sdk-py/v0.2.6...sdk-py/v0.3.0) (2026-07-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **ci:** automate SDK/CLI releases via release-please ([#3632](https://github.com/tinyfish-io/ux-labs/issues/3632)) ([1b311ea](https://github.com/tinyfish-io/ux-labs/commit/1b311ea6d6277fad21b57baf0b7cfdc6619a3b93))
|
|
9
|
+
* **sdk:** add domain_type to search.query() in TypeScript and Python SDKs ([#3432](https://github.com/tinyfish-io/ux-labs/issues/3432)) ([1c2954c](https://github.com/tinyfish-io/ux-labs/commit/1c2954cb192324cdca1ce206515de72df2cd231d))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tinyfish
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Official Python SDK for the TinyFish API
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Requires-Dist: httpx>=0.27.0
|
|
@@ -53,8 +53,10 @@ Every method below is available on both `TinyFish` (sync) and `AsyncTinyFish` (a
|
|
|
53
53
|
| [`agent.run()`](#agentrun--block-until-done) | Run an automation, wait for the result | `AgentRunResponse` | Yes |
|
|
54
54
|
| [`agent.queue()`](#agentqueue--fire-and-forget) | Start an automation, return immediately | `AgentRunAsyncResponse` | No |
|
|
55
55
|
| [`agent.stream()`](#agentstream--real-time-events) | Stream live SSE events as the agent works | `AgentStream` | No |
|
|
56
|
+
| [`fetch.get_contents()`](#fetchget_contents--clean-content) | Fetch extracted page content without running a browser agent | `FetchResponse` | Yes |
|
|
56
57
|
| [`runs.get()`](#runsget--retrieve-a-single-run) | Retrieve a single run by ID | `Run` | — |
|
|
57
58
|
| [`runs.list()`](#runslist--list-and-filter-runs) | List runs with filtering, sorting, pagination | `RunListResponse` | — |
|
|
59
|
+
| [`search.query()`](#searchquery--search-the-web) | Search the web for ranked results | `SearchQueryResponse` | Yes |
|
|
58
60
|
|
|
59
61
|
---
|
|
60
62
|
|
|
@@ -75,6 +77,14 @@ response = client.agent.run(
|
|
|
75
77
|
enabled=True,
|
|
76
78
|
country_code=ProxyCountryCode.US, # optional — US, GB, CA, DE, FR, JP, AU
|
|
77
79
|
),
|
|
80
|
+
output_schema={ # require structured output
|
|
81
|
+
"type": "object",
|
|
82
|
+
"properties": {
|
|
83
|
+
"headline_count": {"type": "integer"},
|
|
84
|
+
"top_headline": {"type": "string"},
|
|
85
|
+
},
|
|
86
|
+
"required": ["headline_count", "top_headline"],
|
|
87
|
+
},
|
|
78
88
|
)
|
|
79
89
|
|
|
80
90
|
if response.status == RunStatus.COMPLETED:
|
|
@@ -83,6 +93,12 @@ else:
|
|
|
83
93
|
print(f"Failed: {response.error.message}")
|
|
84
94
|
```
|
|
85
95
|
|
|
96
|
+
`output_schema` must be a top-level object. The SDK sends the schema to the API as-is; invalid schemas are rejected by
|
|
97
|
+
the API before execution.
|
|
98
|
+
|
|
99
|
+
The same `output_schema=` keyword is available on `client.agent.queue()` and `client.agent.stream()`.
|
|
100
|
+
Persisted runs return the requested contract back as `run.output_schema`.
|
|
101
|
+
|
|
86
102
|
**Returns `AgentRunResponse`:**
|
|
87
103
|
|
|
88
104
|
| Field | Type | Description |
|
|
@@ -101,6 +117,8 @@ else:
|
|
|
101
117
|
|
|
102
118
|
Starts the automation in the background and returns a `run_id` immediately. Poll with `runs.get()` when you're ready for the result.
|
|
103
119
|
|
|
120
|
+
`queue()` accepts the same structured-output parameters as `run()`, including `output_schema=...`.
|
|
121
|
+
|
|
104
122
|
```python
|
|
105
123
|
import time
|
|
106
124
|
from tinyfish import TinyFish, RunStatus
|
|
@@ -167,6 +185,41 @@ See the [Streaming Guide](docs/streaming-guide.md) for the full event lifecycle,
|
|
|
167
185
|
|
|
168
186
|
---
|
|
169
187
|
|
|
188
|
+
### `fetch.get_contents()` — clean content
|
|
189
|
+
|
|
190
|
+
Fetch extracted content from one or more URLs without a browser-agent run.
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
from tinyfish import TinyFish
|
|
194
|
+
|
|
195
|
+
client = TinyFish()
|
|
196
|
+
|
|
197
|
+
response = client.fetch.get_contents(
|
|
198
|
+
[
|
|
199
|
+
"https://example.com",
|
|
200
|
+
"https://example.org",
|
|
201
|
+
],
|
|
202
|
+
format="markdown",
|
|
203
|
+
links=True,
|
|
204
|
+
image_links=False,
|
|
205
|
+
per_url_timeout_ms=45_000,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
print(response.results)
|
|
209
|
+
print(response.errors)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
`fetch.get_contents()` accepts 1 to 10 URLs. Set `per_url_timeout_ms` to apply an independent timeout budget to each URL in the batch; slow URLs return in `errors` with `timeout` while siblings can still complete.
|
|
213
|
+
|
|
214
|
+
**Returns `FetchResponse`:**
|
|
215
|
+
|
|
216
|
+
| Field | Type | Description |
|
|
217
|
+
|-------|------|-------------|
|
|
218
|
+
| `results` | `list[FetchResult]` | Successfully fetched URLs |
|
|
219
|
+
| `errors` | `list[FetchError]` | URLs that failed to fetch or extract |
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
170
223
|
### `runs.get()` — retrieve a single run
|
|
171
224
|
|
|
172
225
|
Fetch the full details of a run by its ID.
|
|
@@ -179,6 +232,7 @@ run = client.runs.get(
|
|
|
179
232
|
print(run.status) # PENDING, RUNNING, COMPLETED, FAILED, CANCELLED
|
|
180
233
|
print(run.result)
|
|
181
234
|
print(run.goal)
|
|
235
|
+
print(run.output_schema) # requested structured-output contract, if one was provided
|
|
182
236
|
print(run.streaming_url) # live browser URL (while RUNNING)
|
|
183
237
|
print(run.browser_config) # proxy/browser settings that were used
|
|
184
238
|
```
|
|
@@ -191,6 +245,7 @@ print(run.browser_config) # proxy/browser settings that were used
|
|
|
191
245
|
| `status` | `RunStatus` | `PENDING`, `RUNNING`, `COMPLETED`, `FAILED`, `CANCELLED` |
|
|
192
246
|
| `goal` | `str` | The goal that was given |
|
|
193
247
|
| `result` | `dict \| None` | Extracted data (`None` if not completed) |
|
|
248
|
+
| `output_schema` | `dict \| None` | JSON Schema contract originally requested for the run |
|
|
194
249
|
| `error` | `RunError \| None` | Error details (`None` if succeeded) |
|
|
195
250
|
| `streaming_url` | `str \| None` | Live browser URL (available while running) |
|
|
196
251
|
| `browser_config` | `BrowserConfig \| None` | Proxy/browser settings used |
|
|
@@ -240,6 +295,56 @@ See the [Pagination Guide](docs/pagination-guide.md) for full pagination loop ex
|
|
|
240
295
|
|
|
241
296
|
---
|
|
242
297
|
|
|
298
|
+
### `search.query()` — search the web
|
|
299
|
+
|
|
300
|
+
Returns ranked web search results with titles, snippets, and URLs.
|
|
301
|
+
|
|
302
|
+
```python
|
|
303
|
+
from tinyfish import TinyFish
|
|
304
|
+
|
|
305
|
+
client = TinyFish()
|
|
306
|
+
|
|
307
|
+
response = client.search.query("FIFA")
|
|
308
|
+
|
|
309
|
+
print(response.query)
|
|
310
|
+
print(response.total_results)
|
|
311
|
+
print(response.results[0].title if response.results else "No results")
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Optional parameters:
|
|
315
|
+
|
|
316
|
+
- `location` — country code for geo-targeted results (e.g. `"US"`, `"GB"`)
|
|
317
|
+
- `language` — language code (e.g. `"en"`, `"fr"`)
|
|
318
|
+
- `page` — page number, 0-indexed, max `10`
|
|
319
|
+
- `recency_minutes` — freshness window in minutes (`1` to `5256000`)
|
|
320
|
+
- `after_date` / `before_date` — calendar date range in `YYYY-MM-DD`
|
|
321
|
+
- `domain_type` — result category: `"web"` (default), `"news"`, or `"research_paper"`
|
|
322
|
+
|
|
323
|
+
```python
|
|
324
|
+
# geo-targeted
|
|
325
|
+
response = client.search.query("FIFA", location="US", language="en")
|
|
326
|
+
|
|
327
|
+
# freshness window
|
|
328
|
+
response = client.search.query("FIFA", recency_minutes=60)
|
|
329
|
+
|
|
330
|
+
# calendar date range
|
|
331
|
+
response = client.search.query("FIFA", after_date="2026-06-01", before_date="2026-06-18")
|
|
332
|
+
|
|
333
|
+
# domain type
|
|
334
|
+
response = client.search.query("FIFA", domain_type="news")
|
|
335
|
+
response = client.search.query("machine learning", domain_type="research_paper")
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Filter validation rules:
|
|
339
|
+
|
|
340
|
+
- `recency_minutes` must be an integer from `1` to `5256000`
|
|
341
|
+
- `after_date` and `before_date` must use `YYYY-MM-DD`
|
|
342
|
+
- `recency_minutes` cannot be combined with `after_date` or `before_date`
|
|
343
|
+
- if both dates are present, `after_date` must be less than or equal to `before_date`
|
|
344
|
+
- `domain_type` must be one of `"web"`, `"news"`, or `"research_paper"`
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
243
348
|
## Sync vs Async
|
|
244
349
|
|
|
245
350
|
Use `AsyncTinyFish` when you're in an async context (FastAPI, aiohttp, etc.):
|
|
@@ -262,7 +367,7 @@ client = AsyncTinyFish()
|
|
|
262
367
|
response = await client.agent.run(goal="...", url="...")
|
|
263
368
|
```
|
|
264
369
|
|
|
265
|
-
All
|
|
370
|
+
All seven methods (`agent.run()`, `agent.queue()`, `agent.stream()`, `fetch.get_contents()`, `runs.get()`, `runs.list()`, `search.query()`) work the same way — same parameters, just `await`-ed.
|
|
266
371
|
|
|
267
372
|
## Configuration
|
|
268
373
|
|
|
@@ -296,6 +401,20 @@ response = client.agent.run(
|
|
|
296
401
|
)
|
|
297
402
|
```
|
|
298
403
|
|
|
404
|
+
### Browser Context Profiles
|
|
405
|
+
|
|
406
|
+
Use Browser Context Profiles when a run should start from saved logged-in state. Pass `use_profile=True` for your default profile, or add `profile_id` for a specific profile. Pair with `use_vault=True` when TinyFish should repair stale sessions with saved credentials.
|
|
407
|
+
|
|
408
|
+
```python
|
|
409
|
+
response = client.agent.run(
|
|
410
|
+
goal="Summarize the dashboard",
|
|
411
|
+
url="https://app.example.com/dashboard",
|
|
412
|
+
use_profile=True,
|
|
413
|
+
profile_id="prof_abc123def4567890",
|
|
414
|
+
use_vault=True,
|
|
415
|
+
)
|
|
416
|
+
```
|
|
417
|
+
|
|
299
418
|
### Proxy configuration
|
|
300
419
|
|
|
301
420
|
Route requests through a proxy, optionally pinned to a country:
|
|
@@ -43,8 +43,10 @@ Every method below is available on both `TinyFish` (sync) and `AsyncTinyFish` (a
|
|
|
43
43
|
| [`agent.run()`](#agentrun--block-until-done) | Run an automation, wait for the result | `AgentRunResponse` | Yes |
|
|
44
44
|
| [`agent.queue()`](#agentqueue--fire-and-forget) | Start an automation, return immediately | `AgentRunAsyncResponse` | No |
|
|
45
45
|
| [`agent.stream()`](#agentstream--real-time-events) | Stream live SSE events as the agent works | `AgentStream` | No |
|
|
46
|
+
| [`fetch.get_contents()`](#fetchget_contents--clean-content) | Fetch extracted page content without running a browser agent | `FetchResponse` | Yes |
|
|
46
47
|
| [`runs.get()`](#runsget--retrieve-a-single-run) | Retrieve a single run by ID | `Run` | — |
|
|
47
48
|
| [`runs.list()`](#runslist--list-and-filter-runs) | List runs with filtering, sorting, pagination | `RunListResponse` | — |
|
|
49
|
+
| [`search.query()`](#searchquery--search-the-web) | Search the web for ranked results | `SearchQueryResponse` | Yes |
|
|
48
50
|
|
|
49
51
|
---
|
|
50
52
|
|
|
@@ -65,6 +67,14 @@ response = client.agent.run(
|
|
|
65
67
|
enabled=True,
|
|
66
68
|
country_code=ProxyCountryCode.US, # optional — US, GB, CA, DE, FR, JP, AU
|
|
67
69
|
),
|
|
70
|
+
output_schema={ # require structured output
|
|
71
|
+
"type": "object",
|
|
72
|
+
"properties": {
|
|
73
|
+
"headline_count": {"type": "integer"},
|
|
74
|
+
"top_headline": {"type": "string"},
|
|
75
|
+
},
|
|
76
|
+
"required": ["headline_count", "top_headline"],
|
|
77
|
+
},
|
|
68
78
|
)
|
|
69
79
|
|
|
70
80
|
if response.status == RunStatus.COMPLETED:
|
|
@@ -73,6 +83,12 @@ else:
|
|
|
73
83
|
print(f"Failed: {response.error.message}")
|
|
74
84
|
```
|
|
75
85
|
|
|
86
|
+
`output_schema` must be a top-level object. The SDK sends the schema to the API as-is; invalid schemas are rejected by
|
|
87
|
+
the API before execution.
|
|
88
|
+
|
|
89
|
+
The same `output_schema=` keyword is available on `client.agent.queue()` and `client.agent.stream()`.
|
|
90
|
+
Persisted runs return the requested contract back as `run.output_schema`.
|
|
91
|
+
|
|
76
92
|
**Returns `AgentRunResponse`:**
|
|
77
93
|
|
|
78
94
|
| Field | Type | Description |
|
|
@@ -91,6 +107,8 @@ else:
|
|
|
91
107
|
|
|
92
108
|
Starts the automation in the background and returns a `run_id` immediately. Poll with `runs.get()` when you're ready for the result.
|
|
93
109
|
|
|
110
|
+
`queue()` accepts the same structured-output parameters as `run()`, including `output_schema=...`.
|
|
111
|
+
|
|
94
112
|
```python
|
|
95
113
|
import time
|
|
96
114
|
from tinyfish import TinyFish, RunStatus
|
|
@@ -157,6 +175,41 @@ See the [Streaming Guide](docs/streaming-guide.md) for the full event lifecycle,
|
|
|
157
175
|
|
|
158
176
|
---
|
|
159
177
|
|
|
178
|
+
### `fetch.get_contents()` — clean content
|
|
179
|
+
|
|
180
|
+
Fetch extracted content from one or more URLs without a browser-agent run.
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
from tinyfish import TinyFish
|
|
184
|
+
|
|
185
|
+
client = TinyFish()
|
|
186
|
+
|
|
187
|
+
response = client.fetch.get_contents(
|
|
188
|
+
[
|
|
189
|
+
"https://example.com",
|
|
190
|
+
"https://example.org",
|
|
191
|
+
],
|
|
192
|
+
format="markdown",
|
|
193
|
+
links=True,
|
|
194
|
+
image_links=False,
|
|
195
|
+
per_url_timeout_ms=45_000,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
print(response.results)
|
|
199
|
+
print(response.errors)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
`fetch.get_contents()` accepts 1 to 10 URLs. Set `per_url_timeout_ms` to apply an independent timeout budget to each URL in the batch; slow URLs return in `errors` with `timeout` while siblings can still complete.
|
|
203
|
+
|
|
204
|
+
**Returns `FetchResponse`:**
|
|
205
|
+
|
|
206
|
+
| Field | Type | Description |
|
|
207
|
+
|-------|------|-------------|
|
|
208
|
+
| `results` | `list[FetchResult]` | Successfully fetched URLs |
|
|
209
|
+
| `errors` | `list[FetchError]` | URLs that failed to fetch or extract |
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
160
213
|
### `runs.get()` — retrieve a single run
|
|
161
214
|
|
|
162
215
|
Fetch the full details of a run by its ID.
|
|
@@ -169,6 +222,7 @@ run = client.runs.get(
|
|
|
169
222
|
print(run.status) # PENDING, RUNNING, COMPLETED, FAILED, CANCELLED
|
|
170
223
|
print(run.result)
|
|
171
224
|
print(run.goal)
|
|
225
|
+
print(run.output_schema) # requested structured-output contract, if one was provided
|
|
172
226
|
print(run.streaming_url) # live browser URL (while RUNNING)
|
|
173
227
|
print(run.browser_config) # proxy/browser settings that were used
|
|
174
228
|
```
|
|
@@ -181,6 +235,7 @@ print(run.browser_config) # proxy/browser settings that were used
|
|
|
181
235
|
| `status` | `RunStatus` | `PENDING`, `RUNNING`, `COMPLETED`, `FAILED`, `CANCELLED` |
|
|
182
236
|
| `goal` | `str` | The goal that was given |
|
|
183
237
|
| `result` | `dict \| None` | Extracted data (`None` if not completed) |
|
|
238
|
+
| `output_schema` | `dict \| None` | JSON Schema contract originally requested for the run |
|
|
184
239
|
| `error` | `RunError \| None` | Error details (`None` if succeeded) |
|
|
185
240
|
| `streaming_url` | `str \| None` | Live browser URL (available while running) |
|
|
186
241
|
| `browser_config` | `BrowserConfig \| None` | Proxy/browser settings used |
|
|
@@ -230,6 +285,56 @@ See the [Pagination Guide](docs/pagination-guide.md) for full pagination loop ex
|
|
|
230
285
|
|
|
231
286
|
---
|
|
232
287
|
|
|
288
|
+
### `search.query()` — search the web
|
|
289
|
+
|
|
290
|
+
Returns ranked web search results with titles, snippets, and URLs.
|
|
291
|
+
|
|
292
|
+
```python
|
|
293
|
+
from tinyfish import TinyFish
|
|
294
|
+
|
|
295
|
+
client = TinyFish()
|
|
296
|
+
|
|
297
|
+
response = client.search.query("FIFA")
|
|
298
|
+
|
|
299
|
+
print(response.query)
|
|
300
|
+
print(response.total_results)
|
|
301
|
+
print(response.results[0].title if response.results else "No results")
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Optional parameters:
|
|
305
|
+
|
|
306
|
+
- `location` — country code for geo-targeted results (e.g. `"US"`, `"GB"`)
|
|
307
|
+
- `language` — language code (e.g. `"en"`, `"fr"`)
|
|
308
|
+
- `page` — page number, 0-indexed, max `10`
|
|
309
|
+
- `recency_minutes` — freshness window in minutes (`1` to `5256000`)
|
|
310
|
+
- `after_date` / `before_date` — calendar date range in `YYYY-MM-DD`
|
|
311
|
+
- `domain_type` — result category: `"web"` (default), `"news"`, or `"research_paper"`
|
|
312
|
+
|
|
313
|
+
```python
|
|
314
|
+
# geo-targeted
|
|
315
|
+
response = client.search.query("FIFA", location="US", language="en")
|
|
316
|
+
|
|
317
|
+
# freshness window
|
|
318
|
+
response = client.search.query("FIFA", recency_minutes=60)
|
|
319
|
+
|
|
320
|
+
# calendar date range
|
|
321
|
+
response = client.search.query("FIFA", after_date="2026-06-01", before_date="2026-06-18")
|
|
322
|
+
|
|
323
|
+
# domain type
|
|
324
|
+
response = client.search.query("FIFA", domain_type="news")
|
|
325
|
+
response = client.search.query("machine learning", domain_type="research_paper")
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Filter validation rules:
|
|
329
|
+
|
|
330
|
+
- `recency_minutes` must be an integer from `1` to `5256000`
|
|
331
|
+
- `after_date` and `before_date` must use `YYYY-MM-DD`
|
|
332
|
+
- `recency_minutes` cannot be combined with `after_date` or `before_date`
|
|
333
|
+
- if both dates are present, `after_date` must be less than or equal to `before_date`
|
|
334
|
+
- `domain_type` must be one of `"web"`, `"news"`, or `"research_paper"`
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
233
338
|
## Sync vs Async
|
|
234
339
|
|
|
235
340
|
Use `AsyncTinyFish` when you're in an async context (FastAPI, aiohttp, etc.):
|
|
@@ -252,7 +357,7 @@ client = AsyncTinyFish()
|
|
|
252
357
|
response = await client.agent.run(goal="...", url="...")
|
|
253
358
|
```
|
|
254
359
|
|
|
255
|
-
All
|
|
360
|
+
All seven methods (`agent.run()`, `agent.queue()`, `agent.stream()`, `fetch.get_contents()`, `runs.get()`, `runs.list()`, `search.query()`) work the same way — same parameters, just `await`-ed.
|
|
256
361
|
|
|
257
362
|
## Configuration
|
|
258
363
|
|
|
@@ -286,6 +391,20 @@ response = client.agent.run(
|
|
|
286
391
|
)
|
|
287
392
|
```
|
|
288
393
|
|
|
394
|
+
### Browser Context Profiles
|
|
395
|
+
|
|
396
|
+
Use Browser Context Profiles when a run should start from saved logged-in state. Pass `use_profile=True` for your default profile, or add `profile_id` for a specific profile. Pair with `use_vault=True` when TinyFish should repair stale sessions with saved credentials.
|
|
397
|
+
|
|
398
|
+
```python
|
|
399
|
+
response = client.agent.run(
|
|
400
|
+
goal="Summarize the dashboard",
|
|
401
|
+
url="https://app.example.com/dashboard",
|
|
402
|
+
use_profile=True,
|
|
403
|
+
profile_id="prof_abc123def4567890",
|
|
404
|
+
use_vault=True,
|
|
405
|
+
)
|
|
406
|
+
```
|
|
407
|
+
|
|
289
408
|
### Proxy configuration
|
|
290
409
|
|
|
291
410
|
Route requests through a proxy, optionally pinned to a country:
|
|
@@ -19,25 +19,22 @@ This is a one-time change. Once merged and applied, the workflow will have acces
|
|
|
19
19
|
|
|
20
20
|
## Release process
|
|
21
21
|
|
|
22
|
-
### Step 1:
|
|
22
|
+
### Step 1: Merge SDK changes to main
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
Use conventional commits (`feat:`, `fix:`, `feat!:`) for changes under `sdk/sdk-python/`.
|
|
25
|
+
After merge, `release-please.yml` opens or updates a Python SDK release PR with:
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
- `sdk/sdk-python/pyproject.toml` version bump
|
|
28
|
+
- `sdk/sdk-python/CHANGELOG.md` release notes
|
|
29
|
+
- `.github/release-please/manifest.json` version tracking
|
|
30
|
+
|
|
31
|
+
Review and merge that release PR when you're ready to publish.
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
### Step 2: Release Please creates the GitHub Release
|
|
32
34
|
|
|
33
|
-
|
|
35
|
+
Merging the release PR creates a GitHub Release tagged with the Python SDK component:
|
|
34
36
|
|
|
35
|
-
|
|
36
|
-
2. Set the tag to match the version exactly, prefixed with `sdk-py/v`:
|
|
37
|
-
- Version `0.2.0` → tag `sdk-py/v0.2.0`
|
|
38
|
-
3. Set the target to `main`
|
|
39
|
-
4. Write release notes summarizing what changed
|
|
40
|
-
5. Click **Publish release**
|
|
37
|
+
- Version `0.2.7` → tag `sdk-py/v0.2.7`
|
|
41
38
|
|
|
42
39
|
The CD workflow (`CD_sdk_python.yml`) only triggers on tags prefixed with `sdk-py/v`. It validates that the version portion of the tag matches `pyproject.toml` — if they don't match, the build fails before anything is published.
|
|
43
40
|
|
|
@@ -52,15 +49,18 @@ Monitor progress at: `github.com/tinyfish-io/ux-labs/actions`
|
|
|
52
49
|
|
|
53
50
|
### Step 4: Verify
|
|
54
51
|
|
|
55
|
-
After the workflow completes, verify the release:
|
|
52
|
+
After the workflow completes, verify the release (replace `0.2.7` with the released version):
|
|
56
53
|
|
|
57
54
|
```bash
|
|
58
|
-
pip install tinyfish==0.2.
|
|
55
|
+
pip install tinyfish==0.2.7
|
|
59
56
|
python -c "from tinyfish import TinyFish; print('ok')"
|
|
60
57
|
```
|
|
61
58
|
|
|
62
59
|
## Troubleshooting
|
|
63
60
|
|
|
61
|
+
**Manual release escape hatch**
|
|
62
|
+
Use `workflow_dispatch` on `CD_sdk_python.yml` at a `sdk-py/v*` tag if Release Please created the tag but the publish workflow did not run.
|
|
63
|
+
|
|
64
64
|
**Tag does not match pyproject.toml version**
|
|
65
65
|
The build job validates that the git tag (e.g., `sdk-py/v0.2.0`) matches the version in `pyproject.toml` (e.g., `0.2.0`). Fix by updating `pyproject.toml` to match the tag before publishing, then delete and recreate the release.
|
|
66
66
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tinyfish"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.0"
|
|
4
4
|
description = "Official Python SDK for the TinyFish API"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -10,6 +10,12 @@ dependencies = [
|
|
|
10
10
|
"tenacity>=8.0.0",
|
|
11
11
|
]
|
|
12
12
|
|
|
13
|
+
[tool.uv]
|
|
14
|
+
exclude-newer = "7 days"
|
|
15
|
+
constraint-dependencies = [
|
|
16
|
+
"pygments>=2.20.0",
|
|
17
|
+
]
|
|
18
|
+
|
|
13
19
|
[build-system]
|
|
14
20
|
requires = ["hatchling"]
|
|
15
21
|
build-backend = "hatchling.build"
|
|
@@ -20,8 +26,8 @@ packages = ["src/tinyfish"]
|
|
|
20
26
|
[dependency-groups]
|
|
21
27
|
dev = [
|
|
22
28
|
"ruff>=0.15.2",
|
|
23
|
-
"pytest>=
|
|
24
|
-
"pytest-asyncio>=
|
|
29
|
+
"pytest>=9.0.3",
|
|
30
|
+
"pytest-asyncio>=1.3.0",
|
|
25
31
|
"respx>=0.22.0",
|
|
26
32
|
"python-dotenv>=1.2.2",
|
|
27
33
|
]
|
|
@@ -65,6 +65,21 @@ from .runs.types import (
|
|
|
65
65
|
# Search types
|
|
66
66
|
from .search.types import SearchQueryResponse, SearchResult
|
|
67
67
|
|
|
68
|
+
# Vault types
|
|
69
|
+
from .vault.types import (
|
|
70
|
+
FieldMetadata,
|
|
71
|
+
SyncSummary,
|
|
72
|
+
VaultConnection,
|
|
73
|
+
VaultConnectResponse,
|
|
74
|
+
VaultDisconnectResponse,
|
|
75
|
+
VaultFieldType,
|
|
76
|
+
VaultItem,
|
|
77
|
+
VaultListConnectionsResponse,
|
|
78
|
+
VaultListItemsResponse,
|
|
79
|
+
VaultProvider,
|
|
80
|
+
VaultSyncItemsResponse,
|
|
81
|
+
)
|
|
82
|
+
|
|
68
83
|
__version__ = version("tinyfish")
|
|
69
84
|
|
|
70
85
|
__all__ = [
|
|
@@ -122,4 +137,16 @@ __all__ = [
|
|
|
122
137
|
"RunStatus",
|
|
123
138
|
"RunListResponse",
|
|
124
139
|
"PaginationInfo",
|
|
140
|
+
# Vault types
|
|
141
|
+
"VaultProvider",
|
|
142
|
+
"VaultFieldType",
|
|
143
|
+
"FieldMetadata",
|
|
144
|
+
"VaultItem",
|
|
145
|
+
"VaultConnection",
|
|
146
|
+
"SyncSummary",
|
|
147
|
+
"VaultConnectResponse",
|
|
148
|
+
"VaultListConnectionsResponse",
|
|
149
|
+
"VaultDisconnectResponse",
|
|
150
|
+
"VaultListItemsResponse",
|
|
151
|
+
"VaultSyncItemsResponse",
|
|
125
152
|
]
|
|
@@ -131,6 +131,16 @@ class BaseAsyncAPIClient(_BaseClient):
|
|
|
131
131
|
response = await self._request("POST", path, json=json)
|
|
132
132
|
return self._parse_response(response, cast_to)
|
|
133
133
|
|
|
134
|
+
async def _delete(
|
|
135
|
+
self,
|
|
136
|
+
path: str,
|
|
137
|
+
*,
|
|
138
|
+
cast_to: type[ResponseT],
|
|
139
|
+
) -> ResponseT:
|
|
140
|
+
"""Make DELETE request and parse the response into cast_to."""
|
|
141
|
+
response = await self._request("DELETE", path)
|
|
142
|
+
return self._parse_response(response, cast_to)
|
|
143
|
+
|
|
134
144
|
async def _post_stream(
|
|
135
145
|
self,
|
|
136
146
|
path: str,
|
|
@@ -131,6 +131,16 @@ class BaseSyncAPIClient(_BaseClient):
|
|
|
131
131
|
response = self._request("POST", path, json=json)
|
|
132
132
|
return self._parse_response(response, cast_to)
|
|
133
133
|
|
|
134
|
+
def _delete(
|
|
135
|
+
self,
|
|
136
|
+
path: str,
|
|
137
|
+
*,
|
|
138
|
+
cast_to: type[ResponseT],
|
|
139
|
+
) -> ResponseT:
|
|
140
|
+
"""Make DELETE request and parse the response into cast_to."""
|
|
141
|
+
response = self._request("DELETE", path)
|
|
142
|
+
return self._parse_response(response, cast_to)
|
|
143
|
+
|
|
134
144
|
def _post_stream(
|
|
135
145
|
self,
|
|
136
146
|
path: str,
|
|
@@ -10,6 +10,7 @@ class BaseSyncAPIResource:
|
|
|
10
10
|
self._client = client
|
|
11
11
|
self._get = client._get
|
|
12
12
|
self._post = client._post
|
|
13
|
+
self._delete = client._delete
|
|
13
14
|
self._post_stream = client._post_stream
|
|
14
15
|
|
|
15
16
|
|
|
@@ -20,4 +21,5 @@ class BaseAsyncAPIResource:
|
|
|
20
21
|
self._client = client
|
|
21
22
|
self._get = client._get
|
|
22
23
|
self._post = client._post
|
|
24
|
+
self._delete = client._delete
|
|
23
25
|
self._post_stream = client._post_stream
|