tinyfish 0.3.0__tar.gz → 0.4.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 (52) hide show
  1. tinyfish-0.4.0/CHANGELOG.md +17 -0
  2. tinyfish-0.3.0/README.md → tinyfish-0.4.0/PKG-INFO +18 -0
  3. tinyfish-0.3.0/PKG-INFO → tinyfish-0.4.0/README.md +8 -10
  4. {tinyfish-0.3.0 → tinyfish-0.4.0}/pyproject.toml +1 -1
  5. {tinyfish-0.3.0 → tinyfish-0.4.0}/src/tinyfish/search/__init__.py +58 -0
  6. {tinyfish-0.3.0 → tinyfish-0.4.0}/tests/test_search.py +116 -0
  7. tinyfish-0.3.0/CHANGELOG.md +0 -9
  8. {tinyfish-0.3.0 → tinyfish-0.4.0}/.gitignore +0 -0
  9. {tinyfish-0.3.0 → tinyfish-0.4.0}/.pre-commit-config.yaml +0 -0
  10. {tinyfish-0.3.0 → tinyfish-0.4.0}/AGENTS.md +0 -0
  11. {tinyfish-0.3.0 → tinyfish-0.4.0}/CLAUDE.md +0 -0
  12. {tinyfish-0.3.0 → tinyfish-0.4.0}/RELEASE.md +0 -0
  13. {tinyfish-0.3.0 → tinyfish-0.4.0}/docs/exceptions-and-errors-guide.md +0 -0
  14. {tinyfish-0.3.0 → tinyfish-0.4.0}/docs/internal/api-integration-header.md +0 -0
  15. {tinyfish-0.3.0 → tinyfish-0.4.0}/docs/internal/exceptions-and-errors-guide.md +0 -0
  16. {tinyfish-0.3.0 → tinyfish-0.4.0}/docs/internal/publishing-private-guide.md +0 -0
  17. {tinyfish-0.3.0 → tinyfish-0.4.0}/docs/pagination-guide.md +0 -0
  18. {tinyfish-0.3.0 → tinyfish-0.4.0}/docs/proxy-and-browser-profiles.md +0 -0
  19. {tinyfish-0.3.0 → tinyfish-0.4.0}/docs/streaming-guide.md +0 -0
  20. {tinyfish-0.3.0 → tinyfish-0.4.0}/src/tinyfish/__init__.py +0 -0
  21. {tinyfish-0.3.0 → tinyfish-0.4.0}/src/tinyfish/_utils/__init__.py +0 -0
  22. {tinyfish-0.3.0 → tinyfish-0.4.0}/src/tinyfish/_utils/client/__init__.py +0 -0
  23. {tinyfish-0.3.0 → tinyfish-0.4.0}/src/tinyfish/_utils/client/_base.py +0 -0
  24. {tinyfish-0.3.0 → tinyfish-0.4.0}/src/tinyfish/_utils/client/async_.py +0 -0
  25. {tinyfish-0.3.0 → tinyfish-0.4.0}/src/tinyfish/_utils/client/sync.py +0 -0
  26. {tinyfish-0.3.0 → tinyfish-0.4.0}/src/tinyfish/_utils/exceptions.py +0 -0
  27. {tinyfish-0.3.0 → tinyfish-0.4.0}/src/tinyfish/_utils/resource.py +0 -0
  28. {tinyfish-0.3.0 → tinyfish-0.4.0}/src/tinyfish/_utils/sse_parser.py +0 -0
  29. {tinyfish-0.3.0 → tinyfish-0.4.0}/src/tinyfish/agent/__init__.py +0 -0
  30. {tinyfish-0.3.0 → tinyfish-0.4.0}/src/tinyfish/agent/types.py +0 -0
  31. {tinyfish-0.3.0 → tinyfish-0.4.0}/src/tinyfish/browser/__init__.py +0 -0
  32. {tinyfish-0.3.0 → tinyfish-0.4.0}/src/tinyfish/browser/types.py +0 -0
  33. {tinyfish-0.3.0 → tinyfish-0.4.0}/src/tinyfish/client.py +0 -0
  34. {tinyfish-0.3.0 → tinyfish-0.4.0}/src/tinyfish/fetch/__init__.py +0 -0
  35. {tinyfish-0.3.0 → tinyfish-0.4.0}/src/tinyfish/fetch/types.py +0 -0
  36. {tinyfish-0.3.0 → tinyfish-0.4.0}/src/tinyfish/py.typed +0 -0
  37. {tinyfish-0.3.0 → tinyfish-0.4.0}/src/tinyfish/runs/__init__.py +0 -0
  38. {tinyfish-0.3.0 → tinyfish-0.4.0}/src/tinyfish/runs/types.py +0 -0
  39. {tinyfish-0.3.0 → tinyfish-0.4.0}/src/tinyfish/search/types.py +0 -0
  40. {tinyfish-0.3.0 → tinyfish-0.4.0}/src/tinyfish/vault/__init__.py +0 -0
  41. {tinyfish-0.3.0 → tinyfish-0.4.0}/src/tinyfish/vault/types.py +0 -0
  42. {tinyfish-0.3.0 → tinyfish-0.4.0}/tests/__init__.py +0 -0
  43. {tinyfish-0.3.0 → tinyfish-0.4.0}/tests/conftest.py +0 -0
  44. {tinyfish-0.3.0 → tinyfish-0.4.0}/tests/test_agent.py +0 -0
  45. {tinyfish-0.3.0 → tinyfish-0.4.0}/tests/test_browser.py +0 -0
  46. {tinyfish-0.3.0 → tinyfish-0.4.0}/tests/test_client.py +0 -0
  47. {tinyfish-0.3.0 → tinyfish-0.4.0}/tests/test_errors.py +0 -0
  48. {tinyfish-0.3.0 → tinyfish-0.4.0}/tests/test_fetch.py +0 -0
  49. {tinyfish-0.3.0 → tinyfish-0.4.0}/tests/test_runs.py +0 -0
  50. {tinyfish-0.3.0 → tinyfish-0.4.0}/tests/test_vault.py +0 -0
  51. {tinyfish-0.3.0 → tinyfish-0.4.0}/tests/testing_guide.md +0 -0
  52. {tinyfish-0.3.0 → tinyfish-0.4.0}/uv.lock +0 -0
@@ -0,0 +1,17 @@
1
+ # Changelog
2
+
3
+ ## [0.4.0](https://github.com/tinyfish-io/ux-labs/compare/sdk-py/v0.3.0...sdk-py/v0.4.0) (2026-07-23)
4
+
5
+
6
+ ### Features
7
+
8
+ * **search:** add pub_year_min/pub_year_max publication-year filter ([#3691](https://github.com/tinyfish-io/ux-labs/issues/3691)) ([8728754](https://github.com/tinyfish-io/ux-labs/commit/8728754206fe4b7cf3a4ee8226c74bb364532869))
9
+ * **search:** expose include_domains/exclude_domains in SDKs, CLI, MCP prose (ML-2084) ([#3692](https://github.com/tinyfish-io/ux-labs/issues/3692)) ([6c2079f](https://github.com/tinyfish-io/ux-labs/commit/6c2079f52872f9c9472cde8ec95d70280f25defa))
10
+
11
+ ## [0.3.0](https://github.com/tinyfish-io/ux-labs/compare/sdk-py/v0.2.6...sdk-py/v0.3.0) (2026-07-22)
12
+
13
+
14
+ ### Features
15
+
16
+ * **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))
17
+ * **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,3 +1,13 @@
1
+ Metadata-Version: 2.4
2
+ Name: tinyfish
3
+ Version: 0.4.0
4
+ Summary: Official Python SDK for the TinyFish API
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: httpx>=0.27.0
7
+ Requires-Dist: pydantic>=2.0.0
8
+ Requires-Dist: tenacity>=8.0.0
9
+ Description-Content-Type: text/markdown
10
+
1
11
  # TinyFish Python SDK
2
12
 
3
13
  The official Python SDK for [TinyFish](https://agent.tinyfish.ai)
@@ -309,6 +319,7 @@ Optional parameters:
309
319
  - `recency_minutes` — freshness window in minutes (`1` to `5256000`)
310
320
  - `after_date` / `before_date` — calendar date range in `YYYY-MM-DD`
311
321
  - `domain_type` — result category: `"web"` (default), `"news"`, or `"research_paper"`
322
+ - `pub_year_min` / `pub_year_max` — publication-year range, inclusive (`0` to `9999`). Only supported for `domain_type="research_paper"`
312
323
 
313
324
  ```python
314
325
  # geo-targeted
@@ -323,6 +334,11 @@ response = client.search.query("FIFA", after_date="2026-06-01", before_date="202
323
334
  # domain type
324
335
  response = client.search.query("FIFA", domain_type="news")
325
336
  response = client.search.query("machine learning", domain_type="research_paper")
337
+
338
+ # publication-year range (research_paper only)
339
+ response = client.search.query(
340
+ "transformer architecture", domain_type="research_paper", pub_year_min=2019, pub_year_max=2022
341
+ )
326
342
  ```
327
343
 
328
344
  Filter validation rules:
@@ -332,6 +348,8 @@ Filter validation rules:
332
348
  - `recency_minutes` cannot be combined with `after_date` or `before_date`
333
349
  - if both dates are present, `after_date` must be less than or equal to `before_date`
334
350
  - `domain_type` must be one of `"web"`, `"news"`, or `"research_paper"`
351
+ - `pub_year_min` and `pub_year_max` must be integers from `0` to `9999`
352
+ - if both are present, `pub_year_min` must be less than or equal to `pub_year_max`
335
353
 
336
354
  ---
337
355
 
@@ -1,13 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: tinyfish
3
- Version: 0.3.0
4
- Summary: Official Python SDK for the TinyFish API
5
- Requires-Python: >=3.11
6
- Requires-Dist: httpx>=0.27.0
7
- Requires-Dist: pydantic>=2.0.0
8
- Requires-Dist: tenacity>=8.0.0
9
- Description-Content-Type: text/markdown
10
-
11
1
  # TinyFish Python SDK
12
2
 
13
3
  The official Python SDK for [TinyFish](https://agent.tinyfish.ai)
@@ -319,6 +309,7 @@ Optional parameters:
319
309
  - `recency_minutes` — freshness window in minutes (`1` to `5256000`)
320
310
  - `after_date` / `before_date` — calendar date range in `YYYY-MM-DD`
321
311
  - `domain_type` — result category: `"web"` (default), `"news"`, or `"research_paper"`
312
+ - `pub_year_min` / `pub_year_max` — publication-year range, inclusive (`0` to `9999`). Only supported for `domain_type="research_paper"`
322
313
 
323
314
  ```python
324
315
  # geo-targeted
@@ -333,6 +324,11 @@ response = client.search.query("FIFA", after_date="2026-06-01", before_date="202
333
324
  # domain type
334
325
  response = client.search.query("FIFA", domain_type="news")
335
326
  response = client.search.query("machine learning", domain_type="research_paper")
327
+
328
+ # publication-year range (research_paper only)
329
+ response = client.search.query(
330
+ "transformer architecture", domain_type="research_paper", pub_year_min=2019, pub_year_max=2022
331
+ )
336
332
  ```
337
333
 
338
334
  Filter validation rules:
@@ -342,6 +338,8 @@ Filter validation rules:
342
338
  - `recency_minutes` cannot be combined with `after_date` or `before_date`
343
339
  - if both dates are present, `after_date` must be less than or equal to `before_date`
344
340
  - `domain_type` must be one of `"web"`, `"news"`, or `"research_paper"`
341
+ - `pub_year_min` and `pub_year_max` must be integers from `0` to `9999`
342
+ - if both are present, `pub_year_min` must be less than or equal to `pub_year_max`
345
343
 
346
344
  ---
347
345
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tinyfish"
3
- version = "0.3.0"
3
+ version = "0.4.0"
4
4
  description = "Official Python SDK for the TinyFish API"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -26,16 +26,27 @@ def _validate_iso_date(field_name: str, value: str) -> str:
26
26
  return value
27
27
 
28
28
 
29
+ def _validate_pub_year(field_name: str, value: int) -> None:
30
+ if not isinstance(value, int) or isinstance(value, bool):
31
+ raise ValueError(f"{field_name} must be an integer")
32
+ if not 0 <= value <= 9999:
33
+ raise ValueError(f"{field_name} must be between 0 and 9999")
34
+
35
+
29
36
  def _build_search_params(
30
37
  *,
31
38
  query: str,
32
39
  purpose: str | None = None,
33
40
  location: str | None = None,
34
41
  language: str | None = None,
42
+ include_domains: str | None = None,
43
+ exclude_domains: str | None = None,
35
44
  after_date: str | None = None,
36
45
  before_date: str | None = None,
37
46
  recency_minutes: int | None = None,
38
47
  domain_type: str | None = None,
48
+ pub_year_min: int | None = None,
49
+ pub_year_max: int | None = None,
39
50
  page: int | None = None,
40
51
  ) -> dict[str, str]:
41
52
  if not query or not query.strip():
@@ -67,6 +78,12 @@ def _build_search_params(
67
78
  raise ValueError("recency_minutes cannot be combined with after_date or before_date")
68
79
  if after_date is not None and before_date is not None and after_date > before_date:
69
80
  raise ValueError("after_date must be on or before before_date")
81
+ if pub_year_min is not None:
82
+ _validate_pub_year("pub_year_min", pub_year_min)
83
+ if pub_year_max is not None:
84
+ _validate_pub_year("pub_year_max", pub_year_max)
85
+ if pub_year_min is not None and pub_year_max is not None and pub_year_min > pub_year_max:
86
+ raise ValueError("pub_year_min must be less than or equal to pub_year_max")
70
87
 
71
88
  params: dict[str, str] = {"query": query}
72
89
  if purpose is not None:
@@ -75,6 +92,10 @@ def _build_search_params(
75
92
  params["location"] = location
76
93
  if language is not None:
77
94
  params["language"] = language
95
+ if include_domains is not None:
96
+ params["include_domains"] = include_domains
97
+ if exclude_domains is not None:
98
+ params["exclude_domains"] = exclude_domains
78
99
  if after_date is not None:
79
100
  params["after_date"] = after_date
80
101
  if before_date is not None:
@@ -83,6 +104,11 @@ def _build_search_params(
83
104
  params["recency_minutes"] = str(recency_minutes)
84
105
  if domain_type is not None:
85
106
  params["domain_type"] = domain_type
107
+ # browser-search requires a 4-character year string, so zero-pad (e.g. 500 -> "0500").
108
+ if pub_year_min is not None:
109
+ params["pub_year_min"] = f"{pub_year_min:04d}"
110
+ if pub_year_max is not None:
111
+ params["pub_year_max"] = f"{pub_year_max:04d}"
86
112
  if page is not None:
87
113
  params["page"] = str(page)
88
114
  return params
@@ -98,10 +124,14 @@ class SearchResource(BaseSyncAPIResource):
98
124
  purpose: str | None = None,
99
125
  location: str | None = None,
100
126
  language: str | None = None,
127
+ include_domains: str | None = None,
128
+ exclude_domains: str | None = None,
101
129
  after_date: str | None = None,
102
130
  before_date: str | None = None,
103
131
  recency_minutes: int | None = None,
104
132
  domain_type: str | None = None,
133
+ pub_year_min: int | None = None,
134
+ pub_year_max: int | None = None,
105
135
  page: int | None = None,
106
136
  ) -> SearchQueryResponse:
107
137
  """Execute a web search query.
@@ -115,10 +145,18 @@ class SearchResource(BaseSyncAPIResource):
115
145
  characters.
116
146
  location: Optional location to scope results (e.g. "United States").
117
147
  language: Optional language code (e.g. "en").
148
+ include_domains: Optional comma-separated list of domains to restrict
149
+ results to (e.g. "github.com,arxiv.org").
150
+ exclude_domains: Optional comma-separated list of domains to exclude
151
+ from results (e.g. "pinterest.com,quora.com").
118
152
  after_date: Optional date cutoff in YYYY-MM-DD format.
119
153
  before_date: Optional date cutoff in YYYY-MM-DD format.
120
154
  recency_minutes: Optional rolling freshness window in minutes (1 to 5256000).
121
155
  domain_type: Optional search type — "web" (default), "news", or "research_paper".
156
+ pub_year_min: Optional minimum publication year, inclusive (0 to 9999). Only
157
+ supported for domain_type="research_paper".
158
+ pub_year_max: Optional maximum publication year, inclusive (0 to 9999). Only
159
+ supported for domain_type="research_paper".
122
160
  page: Result page for pagination.
123
161
 
124
162
  Returns:
@@ -135,10 +173,14 @@ class SearchResource(BaseSyncAPIResource):
135
173
  purpose=purpose,
136
174
  location=location,
137
175
  language=language,
176
+ include_domains=include_domains,
177
+ exclude_domains=exclude_domains,
138
178
  after_date=after_date,
139
179
  before_date=before_date,
140
180
  recency_minutes=recency_minutes,
141
181
  domain_type=domain_type,
182
+ pub_year_min=pub_year_min,
183
+ pub_year_max=pub_year_max,
142
184
  page=page,
143
185
  )
144
186
  return self._get("/v1/search", params=params, cast_to=SearchQueryResponse)
@@ -154,10 +196,14 @@ class AsyncSearchResource(BaseAsyncAPIResource):
154
196
  purpose: str | None = None,
155
197
  location: str | None = None,
156
198
  language: str | None = None,
199
+ include_domains: str | None = None,
200
+ exclude_domains: str | None = None,
157
201
  after_date: str | None = None,
158
202
  before_date: str | None = None,
159
203
  recency_minutes: int | None = None,
160
204
  domain_type: str | None = None,
205
+ pub_year_min: int | None = None,
206
+ pub_year_max: int | None = None,
161
207
  page: int | None = None,
162
208
  ) -> SearchQueryResponse:
163
209
  """Execute a web search query.
@@ -173,10 +219,18 @@ class AsyncSearchResource(BaseAsyncAPIResource):
173
219
  characters.
174
220
  location: Optional location to scope results (e.g. "United States").
175
221
  language: Optional language code (e.g. "en").
222
+ include_domains: Optional comma-separated list of domains to restrict
223
+ results to (e.g. "github.com,arxiv.org").
224
+ exclude_domains: Optional comma-separated list of domains to exclude
225
+ from results (e.g. "pinterest.com,quora.com").
176
226
  after_date: Optional date cutoff in YYYY-MM-DD format.
177
227
  before_date: Optional date cutoff in YYYY-MM-DD format.
178
228
  recency_minutes: Optional rolling freshness window in minutes (1 to 5256000).
179
229
  domain_type: Optional search type — "web" (default), "news", or "research_paper".
230
+ pub_year_min: Optional minimum publication year, inclusive (0 to 9999). Only
231
+ supported for domain_type="research_paper".
232
+ pub_year_max: Optional maximum publication year, inclusive (0 to 9999). Only
233
+ supported for domain_type="research_paper".
180
234
  page: Result page for pagination.
181
235
 
182
236
  Returns:
@@ -193,10 +247,14 @@ class AsyncSearchResource(BaseAsyncAPIResource):
193
247
  purpose=purpose,
194
248
  location=location,
195
249
  language=language,
250
+ include_domains=include_domains,
251
+ exclude_domains=exclude_domains,
196
252
  after_date=after_date,
197
253
  before_date=before_date,
198
254
  recency_minutes=recency_minutes,
199
255
  domain_type=domain_type,
256
+ pub_year_min=pub_year_min,
257
+ pub_year_max=pub_year_max,
200
258
  page=page,
201
259
  )
202
260
  return await self._get("/v1/search", params=params, cast_to=SearchQueryResponse)
@@ -88,6 +88,8 @@ def test_query_parses_result_fields(client: TinyFish, respx_mock: MockRouter):
88
88
  ),
89
89
  ({"location": "United States"}, "location", "United States"),
90
90
  ({"language": "en"}, "language", "en"),
91
+ ({"include_domains": "github.com,arxiv.org"}, "include_domains", "github.com,arxiv.org"),
92
+ ({"exclude_domains": "pinterest.com"}, "exclude_domains", "pinterest.com"),
91
93
  ({"after_date": "2026-06-01"}, "after_date", "2026-06-01"),
92
94
  ({"before_date": "2026-06-18"}, "before_date", "2026-06-18"),
93
95
  ({"recency_minutes": 60}, "recency_minutes", "60"),
@@ -195,6 +197,17 @@ def test_query_rejects_non_integer_page(bad_page: Any, client: TinyFish):
195
197
  {"after_date": "2026-06-18", "before_date": "2026-06-01"},
196
198
  "after_date must be on or before before_date",
197
199
  ),
200
+ ({"pub_year_min": True}, "pub_year_min must be an integer"),
201
+ ({"pub_year_min": 1.5}, "pub_year_min must be an integer"),
202
+ ({"pub_year_min": -1}, "pub_year_min must be between 0 and 9999"),
203
+ ({"pub_year_max": True}, "pub_year_max must be an integer"),
204
+ ({"pub_year_max": 1.5}, "pub_year_max must be an integer"),
205
+ ({"pub_year_max": -1}, "pub_year_max must be between 0 and 9999"),
206
+ ({"pub_year_max": 10_000}, "pub_year_max must be between 0 and 9999"),
207
+ (
208
+ {"pub_year_min": 2022, "pub_year_max": 2019},
209
+ "pub_year_min must be less than or equal to pub_year_max",
210
+ ),
198
211
  ],
199
212
  )
200
213
  def test_query_rejects_invalid_search_filters(
@@ -238,6 +251,48 @@ def test_query_rejects_invalid_domain_type(client: TinyFish):
238
251
  client.search.query("test", domain_type="video")
239
252
 
240
253
 
254
+ @pytest.mark.parametrize(
255
+ ("pub_year_min", "pub_year_max", "expected_min", "expected_max"),
256
+ [
257
+ (2019, 2022, "2019", "2022"),
258
+ (500, 999, "0500", "0999"),
259
+ (0, 0, "0000", "0000"),
260
+ ],
261
+ )
262
+ @pytest.mark.respx(base_url=BASE_URL)
263
+ def test_query_forwards_pub_year_zero_padded(
264
+ pub_year_min: int,
265
+ pub_year_max: int,
266
+ expected_min: str,
267
+ expected_max: str,
268
+ client: TinyFish,
269
+ respx_mock: MockRouter,
270
+ ):
271
+ route = respx_mock.get(_SEARCH_PATH).mock(return_value=httpx.Response(200, json=_search_response()))
272
+ client.search.query("test", domain_type="research_paper", pub_year_min=pub_year_min, pub_year_max=pub_year_max)
273
+ params = route.calls[0].request.url.params
274
+ assert params["pub_year_min"] == expected_min
275
+ assert params["pub_year_max"] == expected_max
276
+
277
+
278
+ @pytest.mark.respx(base_url=BASE_URL)
279
+ def test_query_forwards_pub_year_min_only(client: TinyFish, respx_mock: MockRouter):
280
+ route = respx_mock.get(_SEARCH_PATH).mock(return_value=httpx.Response(200, json=_search_response()))
281
+ client.search.query("test", domain_type="research_paper", pub_year_min=2019)
282
+ params = route.calls[0].request.url.params
283
+ assert params["pub_year_min"] == "2019"
284
+ assert "pub_year_max" not in params
285
+
286
+
287
+ @pytest.mark.respx(base_url=BASE_URL)
288
+ def test_query_forwards_pub_year_max_only(client: TinyFish, respx_mock: MockRouter):
289
+ route = respx_mock.get(_SEARCH_PATH).mock(return_value=httpx.Response(200, json=_search_response()))
290
+ client.search.query("test", domain_type="research_paper", pub_year_max=2022)
291
+ params = route.calls[0].request.url.params
292
+ assert "pub_year_min" not in params
293
+ assert params["pub_year_max"] == "2022"
294
+
295
+
241
296
  # ═══════════════════════════════════════════════════════════════════════════════
242
297
  # search.query() — async (mirrors sync tests above)
243
298
  # ═══════════════════════════════════════════════════════════════════════════════
@@ -275,6 +330,8 @@ async def test_async_query_parses_result_fields(async_client: AsyncTinyFish, res
275
330
  ),
276
331
  ({"location": "United States"}, "location", "United States"),
277
332
  ({"language": "en"}, "language", "en"),
333
+ ({"include_domains": "github.com,arxiv.org"}, "include_domains", "github.com,arxiv.org"),
334
+ ({"exclude_domains": "pinterest.com"}, "exclude_domains", "pinterest.com"),
278
335
  ({"after_date": "2026-06-01"}, "after_date", "2026-06-01"),
279
336
  ({"before_date": "2026-06-18"}, "before_date", "2026-06-18"),
280
337
  ({"recency_minutes": 60}, "recency_minutes", "60"),
@@ -371,6 +428,17 @@ async def test_async_query_rejects_non_integer_page(
371
428
  {"after_date": "2026-06-18", "before_date": "2026-06-01"},
372
429
  "after_date must be on or before before_date",
373
430
  ),
431
+ ({"pub_year_min": True}, "pub_year_min must be an integer"),
432
+ ({"pub_year_min": 1.5}, "pub_year_min must be an integer"),
433
+ ({"pub_year_min": -1}, "pub_year_min must be between 0 and 9999"),
434
+ ({"pub_year_max": True}, "pub_year_max must be an integer"),
435
+ ({"pub_year_max": 1.5}, "pub_year_max must be an integer"),
436
+ ({"pub_year_max": -1}, "pub_year_max must be between 0 and 9999"),
437
+ ({"pub_year_max": 10_000}, "pub_year_max must be between 0 and 9999"),
438
+ (
439
+ {"pub_year_min": 2022, "pub_year_max": 2019},
440
+ "pub_year_min must be less than or equal to pub_year_max",
441
+ ),
374
442
  ],
375
443
  )
376
444
  async def test_async_query_rejects_invalid_search_filters(
@@ -407,6 +475,54 @@ async def test_async_query_rejects_invalid_domain_type(async_client: AsyncTinyFi
407
475
  await _do_query(async_client, "test", domain_type="video")
408
476
 
409
477
 
478
+ @pytest.mark.parametrize(
479
+ ("pub_year_min", "pub_year_max", "expected_min", "expected_max"),
480
+ [
481
+ (2019, 2022, "2019", "2022"),
482
+ (500, 999, "0500", "0999"),
483
+ (0, 0, "0000", "0000"),
484
+ ],
485
+ )
486
+ @pytest.mark.respx(base_url=BASE_URL)
487
+ async def test_async_query_forwards_pub_year_zero_padded(
488
+ pub_year_min: int,
489
+ pub_year_max: int,
490
+ expected_min: str,
491
+ expected_max: str,
492
+ async_client: AsyncTinyFish,
493
+ respx_mock: MockRouter,
494
+ ):
495
+ route = respx_mock.get(_SEARCH_PATH).mock(return_value=httpx.Response(200, json=_search_response()))
496
+ await _do_query(
497
+ async_client,
498
+ "test",
499
+ domain_type="research_paper",
500
+ pub_year_min=pub_year_min,
501
+ pub_year_max=pub_year_max,
502
+ )
503
+ params = route.calls[0].request.url.params
504
+ assert params["pub_year_min"] == expected_min
505
+ assert params["pub_year_max"] == expected_max
506
+
507
+
508
+ @pytest.mark.respx(base_url=BASE_URL)
509
+ async def test_async_query_forwards_pub_year_min_only(async_client: AsyncTinyFish, respx_mock: MockRouter):
510
+ route = respx_mock.get(_SEARCH_PATH).mock(return_value=httpx.Response(200, json=_search_response()))
511
+ await _do_query(async_client, "test", domain_type="research_paper", pub_year_min=2019)
512
+ params = route.calls[0].request.url.params
513
+ assert params["pub_year_min"] == "2019"
514
+ assert "pub_year_max" not in params
515
+
516
+
517
+ @pytest.mark.respx(base_url=BASE_URL)
518
+ async def test_async_query_forwards_pub_year_max_only(async_client: AsyncTinyFish, respx_mock: MockRouter):
519
+ route = respx_mock.get(_SEARCH_PATH).mock(return_value=httpx.Response(200, json=_search_response()))
520
+ await _do_query(async_client, "test", domain_type="research_paper", pub_year_max=2022)
521
+ params = route.calls[0].request.url.params
522
+ assert "pub_year_min" not in params
523
+ assert params["pub_year_max"] == "2022"
524
+
525
+
410
526
  @pytest.mark.respx(base_url=BASE_URL)
411
527
  async def test_async_query_401(async_client: AsyncTinyFish, respx_mock: MockRouter):
412
528
  respx_mock.get(_SEARCH_PATH).mock(return_value=httpx.Response(401, json={"error": {"message": "unauthorized"}}))
@@ -1,9 +0,0 @@
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))
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