search1api 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.
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## 0.4.0 - 2026-10-12
4
+
5
+ Breaking: search/news engine-specific fields move from each result's top level to optional `metadata` (`kind`, `stars`, `language`, `num_comments`, `points`, `story_url`). Read e.g. `result.metadata.stars` instead of `result.stars`. `published_date` stays at the top level. No legacy field aliases.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: search1api
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Official Python client for Search1API
5
5
  Project-URL: Homepage, https://s1.dev
6
6
  Project-URL: Documentation, https://s1.dev/docs/integrations/sdks
@@ -81,7 +81,11 @@ for result in response["results"]:
81
81
  `bingcn`, `yandex`, `reddit`, `github`, `arxiv`, `wikipedia`, or `grokipedia`.
82
82
  `page` requests a later results page on engines with native pagination
83
83
  (`bing`, `bingcn`, `baidu`, `grokipedia`). Results carry `published_date` when the source
84
- exposes one.
84
+ exposes one. Engine-specific fields are in each result's optional `metadata`:
85
+ GitHub uses `metadata.kind`, `metadata.stars`, `metadata.language`, and
86
+ `metadata.num_comments`; Hacker News news results use `metadata.story_url`,
87
+ `metadata.points`, and `metadata.num_comments`. The former top-level paths
88
+ are removed in 0.4.0; `published_date` stays at the top level.
85
89
 
86
90
  Use the client as a context manager when it owns the HTTP connection pool:
87
91
 
@@ -30,7 +30,11 @@ for result in response["results"]:
30
30
  `bingcn`, `yandex`, `reddit`, `github`, `arxiv`, `wikipedia`, or `grokipedia`.
31
31
  `page` requests a later results page on engines with native pagination
32
32
  (`bing`, `bingcn`, `baidu`, `grokipedia`). Results carry `published_date` when the source
33
- exposes one.
33
+ exposes one. Engine-specific fields are in each result's optional `metadata`:
34
+ GitHub uses `metadata.kind`, `metadata.stars`, `metadata.language`, and
35
+ `metadata.num_comments`; Hacker News news results use `metadata.story_url`,
36
+ `metadata.points`, and `metadata.num_comments`. The former top-level paths
37
+ are removed in 0.4.0; `published_date` stays at the top level.
34
38
 
35
39
  Use the client as a context manager when it owns the HTTP connection pool:
36
40
 
@@ -348,6 +348,16 @@
348
348
  "description": "When the page was published, as ISO 8601: `YYYY-MM-DD` when only the day is known (web search engines), or `YYYY-MM-DDTHH:MM:SSZ` in UTC when the source carries a time (x, reddit, github, arxiv, youtube, bilibili, wechat, news feeds). Omitted when the source exposes no date, so treat it as optional per result.",
349
349
  "example": "2026-09-03"
350
350
  },
351
+ "metadata": {
352
+ "$ref": "#/components/schemas/SearchResultMetadata"
353
+ }
354
+ },
355
+ "additionalProperties": true
356
+ },
357
+ "SearchResultMetadata": {
358
+ "type": "object",
359
+ "description": "Optional engine-specific fields. Omitted when none apply. GitHub fields appear on POST /search; Hacker News fields appear on POST /news.",
360
+ "properties": {
351
361
  "kind": {
352
362
  "type": "string",
353
363
  "enum": [
@@ -360,11 +370,11 @@
360
370
  },
361
371
  "stars": {
362
372
  "type": "integer",
363
- "description": "Stargazer count of a `github` repository result (`kind: \"repo\"`)."
373
+ "description": "Stargazer count of a `github` repository result (`metadata.kind: \"repo\"`)."
364
374
  },
365
375
  "language": {
366
376
  "type": "string",
367
- "description": "Primary language of a `github` repository result (`kind: \"repo\"`). Omitted when GitHub reports none."
377
+ "description": "Primary language of a `github` repository result (`metadata.kind: \"repo\"`). Omitted when GitHub reports none."
368
378
  },
369
379
  "num_comments": {
370
380
  "type": "integer",
@@ -373,7 +383,7 @@
373
383
  "story_url": {
374
384
  "type": "string",
375
385
  "format": "uri",
376
- "description": "POST /news with `search_service: \"hackernews\"` only: the submitted article. `link` is the Hacker News discussion thread; `story_url` is empty for Ask HN / Show HN text posts."
386
+ "description": "POST /news with `search_service: \"hackernews\"` only: the submitted article. `link` is the Hacker News discussion thread; `metadata.story_url` is omitted for Ask HN / Show HN text posts."
377
387
  },
378
388
  "points": {
379
389
  "type": "integer",
@@ -631,7 +641,7 @@
631
641
  "properties": {
632
642
  "search_query": {
633
643
  "type": "string",
634
- "description": "Keywords sent to the engines, with platform and time phrases removed."
644
+ "description": "Query sent to the first engine in `sources`. Each engine searches its own span of the request words, without platform names; the time window is applied as a filter."
635
645
  },
636
646
  "sources": {
637
647
  "type": "array",
@@ -652,7 +662,7 @@
652
662
  "year",
653
663
  null
654
664
  ],
655
- "description": "Publication window applied, or null for none."
665
+ "description": "Publication window applied, or null for none. year means the past 365 days, not the current calendar year."
656
666
  }
657
667
  }
658
668
  },
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "search1api"
7
- version = "0.3.0"
7
+ version = "0.4.0"
8
8
  description = "Official Python client for Search1API"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -35,4 +35,4 @@ __all__ = [
35
35
  "UnprocessableEntityError",
36
36
  ]
37
37
 
38
- __version__ = "0.3.0"
38
+ __version__ = "0.4.0"
@@ -54,9 +54,7 @@ class SearchResultRequired(TypedDict):
54
54
  snippet: str
55
55
 
56
56
 
57
- class SearchResult(SearchResultRequired, total=False):
58
- content: str
59
- published_date: str
57
+ class SearchResultMetadata(TypedDict, total=False):
60
58
  kind: Literal["repo", "issue", "pr", "discussion"]
61
59
  stars: int
62
60
  language: str
@@ -65,6 +63,12 @@ class SearchResult(SearchResultRequired, total=False):
65
63
  story_url: str
66
64
 
67
65
 
66
+ class SearchResult(SearchResultRequired, total=False):
67
+ content: str
68
+ published_date: str
69
+ metadata: SearchResultMetadata
70
+
71
+
68
72
  class SearchResponseRequired(TypedDict):
69
73
  searchParameters: SearchRequest
70
74
  results: List[SearchResult]
@@ -24,6 +24,39 @@ def test_requires_api_key(monkeypatch):
24
24
  Search1API()
25
25
 
26
26
 
27
+ @pytest.mark.parametrize("method", ["search", "news"])
28
+ @pytest.mark.parametrize("asynchronous", [False, True])
29
+ def test_preserves_nested_metadata(method, asynchronous):
30
+ body = {
31
+ "searchParameters": {"query": "q"},
32
+ "results": [
33
+ {
34
+ "title": "Thread",
35
+ "link": "https://example.com/thread",
36
+ "snippet": "Discussion",
37
+ "published_date": "2026-10-12",
38
+ "metadata": {"num_comments": 0},
39
+ }
40
+ ],
41
+ }
42
+ transport = httpx.MockTransport(lambda request: json_response(200, body, request))
43
+
44
+ async def run():
45
+ async with httpx.AsyncClient(transport=transport) as http_client:
46
+ async with AsyncSearch1API("test-key", client=http_client) as client:
47
+ return await getattr(client, method)("q")
48
+
49
+ if asynchronous:
50
+ response = asyncio.run(run())
51
+ else:
52
+ with httpx.Client(transport=transport) as http_client:
53
+ with Search1API("test-key", client=http_client) as client:
54
+ response = getattr(client, method)("q")
55
+ assert response == body
56
+ assert response["results"][0]["metadata"]["num_comments"] == 0
57
+ assert "num_comments" not in response["results"][0]
58
+
59
+
27
60
  def test_rejects_invalid_retry_and_timeout_settings():
28
61
  with pytest.raises(Search1APIConfigurationError, match="max_retries"):
29
62
  Search1API("test-key", max_retries=-1)
File without changes
File without changes
File without changes