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.
Files changed (53) hide show
  1. tinyfish-0.3.0/CHANGELOG.md +9 -0
  2. {tinyfish-0.2.5 → tinyfish-0.3.0}/PKG-INFO +121 -2
  3. {tinyfish-0.2.5 → tinyfish-0.3.0}/README.md +120 -1
  4. {tinyfish-0.2.5 → tinyfish-0.3.0}/RELEASE.md +16 -16
  5. {tinyfish-0.2.5 → tinyfish-0.3.0}/pyproject.toml +9 -3
  6. {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/__init__.py +27 -0
  7. {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/_utils/client/async_.py +10 -0
  8. {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/_utils/client/sync.py +10 -0
  9. {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/_utils/resource.py +2 -0
  10. {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/agent/__init__.py +146 -6
  11. {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/client.py +5 -0
  12. tinyfish-0.3.0/src/tinyfish/fetch/__init__.py +273 -0
  13. {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/fetch/types.py +14 -0
  14. {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/runs/types.py +4 -0
  15. tinyfish-0.3.0/src/tinyfish/search/__init__.py +202 -0
  16. {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/search/types.py +8 -0
  17. tinyfish-0.3.0/src/tinyfish/vault/__init__.py +208 -0
  18. tinyfish-0.3.0/src/tinyfish/vault/types.py +106 -0
  19. {tinyfish-0.2.5 → tinyfish-0.3.0}/tests/test_agent.py +249 -0
  20. {tinyfish-0.2.5 → tinyfish-0.3.0}/tests/test_fetch.py +217 -1
  21. {tinyfish-0.2.5 → tinyfish-0.3.0}/tests/test_runs.py +159 -0
  22. {tinyfish-0.2.5 → tinyfish-0.3.0}/tests/test_search.py +190 -5
  23. tinyfish-0.3.0/tests/test_vault.py +438 -0
  24. {tinyfish-0.2.5 → tinyfish-0.3.0}/uv.lock +19 -12
  25. tinyfish-0.2.5/src/tinyfish/fetch/__init__.py +0 -99
  26. tinyfish-0.2.5/src/tinyfish/search/__init__.py +0 -79
  27. {tinyfish-0.2.5 → tinyfish-0.3.0}/.gitignore +0 -0
  28. {tinyfish-0.2.5 → tinyfish-0.3.0}/.pre-commit-config.yaml +0 -0
  29. {tinyfish-0.2.5 → tinyfish-0.3.0}/AGENTS.md +0 -0
  30. {tinyfish-0.2.5 → tinyfish-0.3.0}/CLAUDE.md +0 -0
  31. {tinyfish-0.2.5 → tinyfish-0.3.0}/docs/exceptions-and-errors-guide.md +0 -0
  32. {tinyfish-0.2.5 → tinyfish-0.3.0}/docs/internal/api-integration-header.md +0 -0
  33. {tinyfish-0.2.5 → tinyfish-0.3.0}/docs/internal/exceptions-and-errors-guide.md +0 -0
  34. {tinyfish-0.2.5 → tinyfish-0.3.0}/docs/internal/publishing-private-guide.md +0 -0
  35. {tinyfish-0.2.5 → tinyfish-0.3.0}/docs/pagination-guide.md +0 -0
  36. {tinyfish-0.2.5 → tinyfish-0.3.0}/docs/proxy-and-browser-profiles.md +0 -0
  37. {tinyfish-0.2.5 → tinyfish-0.3.0}/docs/streaming-guide.md +0 -0
  38. {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/_utils/__init__.py +0 -0
  39. {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/_utils/client/__init__.py +0 -0
  40. {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/_utils/client/_base.py +0 -0
  41. {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/_utils/exceptions.py +0 -0
  42. {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/_utils/sse_parser.py +0 -0
  43. {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/agent/types.py +0 -0
  44. {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/browser/__init__.py +0 -0
  45. {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/browser/types.py +0 -0
  46. {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/py.typed +0 -0
  47. {tinyfish-0.2.5 → tinyfish-0.3.0}/src/tinyfish/runs/__init__.py +0 -0
  48. {tinyfish-0.2.5 → tinyfish-0.3.0}/tests/__init__.py +0 -0
  49. {tinyfish-0.2.5 → tinyfish-0.3.0}/tests/conftest.py +0 -0
  50. {tinyfish-0.2.5 → tinyfish-0.3.0}/tests/test_browser.py +0 -0
  51. {tinyfish-0.2.5 → tinyfish-0.3.0}/tests/test_client.py +0 -0
  52. {tinyfish-0.2.5 → tinyfish-0.3.0}/tests/test_errors.py +0 -0
  53. {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.2.5
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 five methods (`agent.run()`, `agent.queue()`, `agent.stream()`, `runs.get()`, `runs.list()`) work the same way — same parameters, just `await`-ed.
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 five methods (`agent.run()`, `agent.queue()`, `agent.stream()`, `runs.get()`, `runs.list()`) work the same way — same parameters, just `await`-ed.
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: Bump the version
22
+ ### Step 1: Merge SDK changes to main
23
23
 
24
- Edit `sdk/sdk-python/pyproject.toml` and update the `version` field:
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
- ```toml
27
- [project]
28
- version = "0.2.0" # bump this
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
- Commit and merge to main.
33
+ ### Step 2: Release Please creates the GitHub Release
32
34
 
33
- ### Step 2: Create a GitHub Release
35
+ Merging the release PR creates a GitHub Release tagged with the Python SDK component:
34
36
 
35
- 1. Go to the `ux-labs` repo on GitHub **Releases** → **Draft a new release**
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.0
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.2.5"
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>=8.0.0",
24
- "pytest-asyncio>=0.25.0",
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