ateve 0.2.0__tar.gz → 0.2.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ateve
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Official Python SDK for the Ateve Search API
5
5
  Project-URL: Homepage, https://ateve.ai
6
6
  Project-URL: Documentation, https://ateve.ai/docs
@@ -81,20 +81,32 @@ decisions remain caller-owned.
81
81
 
82
82
  ## Search contract limits
83
83
 
84
- Use `max_results` (1–100, default 10); `limit` and `offset` have been removed.
85
- `ContentOptions.snippet` is optional and defaults to `True`. To retrieve full
86
- text, pass `content=ContentOptions(text=TextOptions())`, or configure
84
+ Use `max_results` to set the maximum number of results (1–100, default 10).
85
+ `limit` and `offset` have been removed. Both synchronous and asynchronous
86
+ clients accept `content` and `max_results`, or a `SearchRequest` instance.
87
+
88
+ Snippets are always returned and cannot be disabled. To request full text, pass
89
+ `content=ContentOptions(text=TextOptions())`. To customize the output, use
87
90
  `TextOptions(max_characters=8000, format=ContentFormat.MARKDOWN)`.
88
- `format` accepts `"text"` (default) or `"markdown"`. Omitting `text` does not
89
- request full text. `max_characters` remains a strict positive signed 32-bit
90
- integer; its server default is 5000 per result, applied after format conversion.
91
- `summary` and `highlights` have been removed from `ContentOptions`; `favicon`
92
- remains optional. Both synchronous and asynchronous clients accept `content`
93
- and `max_results`, or a `SearchRequest` instance.
94
-
95
- Both formats return content in `SearchResult.text`. It defaults to `None` when
96
- absent or when extraction fails; `model_fields_set` distinguishes those cases.
97
- Snippets may coexist with text; set `ContentOptions(snippet=False)` to omit
98
- them. Obsolete answer, usage, news/video, query tuning and raw content/summary/
99
- highlights response fields and models have been removed. Unknown response
100
- fields continue to be retained by Pydantic for forward compatibility.
91
+ `format` accepts `ContentFormat.TEXT` (default) or `ContentFormat.MARKDOWN`.
92
+ Full text is requested only when `text` is supplied. `max_characters` must be a
93
+ strict positive signed 32-bit integer. The server default is 5,000 characters
94
+ per result, applied after format conversion. Both formats return content in
95
+ `SearchResult.text`, which is `None` when absent or when extraction fails.
96
+ `model_fields_set` distinguishes those cases. Full text is returned alongside
97
+ snippets.
98
+
99
+ Highlights are optional and disabled by default. Set
100
+ `content=ContentOptions(highlights=True)` to return concise, query-relevant
101
+ excerpts in addition to snippets. `SearchResult.highlights` is a `list[str]`, or
102
+ `None` when the field is absent. Highlights can coexist with snippets and full text.
103
+
104
+ For token-efficient workflows, your agent can prioritize highlights when building
105
+ model context and supplement them with snippets when additional context is needed.
106
+ When the API returns both fields, token savings depend on which content your
107
+ agent passes to the model.
108
+
109
+ `summary` and the snippet toggle have been removed from `ContentOptions`.
110
+ `favicon` remains optional under `content`. Obsolete answer, usage, news/video,
111
+ query tuning, and raw content/summary response fields and models have been removed.
112
+ Unknown response fields continue to be retained by Pydantic for forward compatibility.
@@ -55,20 +55,32 @@ decisions remain caller-owned.
55
55
 
56
56
  ## Search contract limits
57
57
 
58
- Use `max_results` (1–100, default 10); `limit` and `offset` have been removed.
59
- `ContentOptions.snippet` is optional and defaults to `True`. To retrieve full
60
- text, pass `content=ContentOptions(text=TextOptions())`, or configure
58
+ Use `max_results` to set the maximum number of results (1–100, default 10).
59
+ `limit` and `offset` have been removed. Both synchronous and asynchronous
60
+ clients accept `content` and `max_results`, or a `SearchRequest` instance.
61
+
62
+ Snippets are always returned and cannot be disabled. To request full text, pass
63
+ `content=ContentOptions(text=TextOptions())`. To customize the output, use
61
64
  `TextOptions(max_characters=8000, format=ContentFormat.MARKDOWN)`.
62
- `format` accepts `"text"` (default) or `"markdown"`. Omitting `text` does not
63
- request full text. `max_characters` remains a strict positive signed 32-bit
64
- integer; its server default is 5000 per result, applied after format conversion.
65
- `summary` and `highlights` have been removed from `ContentOptions`; `favicon`
66
- remains optional. Both synchronous and asynchronous clients accept `content`
67
- and `max_results`, or a `SearchRequest` instance.
68
-
69
- Both formats return content in `SearchResult.text`. It defaults to `None` when
70
- absent or when extraction fails; `model_fields_set` distinguishes those cases.
71
- Snippets may coexist with text; set `ContentOptions(snippet=False)` to omit
72
- them. Obsolete answer, usage, news/video, query tuning and raw content/summary/
73
- highlights response fields and models have been removed. Unknown response
74
- fields continue to be retained by Pydantic for forward compatibility.
65
+ `format` accepts `ContentFormat.TEXT` (default) or `ContentFormat.MARKDOWN`.
66
+ Full text is requested only when `text` is supplied. `max_characters` must be a
67
+ strict positive signed 32-bit integer. The server default is 5,000 characters
68
+ per result, applied after format conversion. Both formats return content in
69
+ `SearchResult.text`, which is `None` when absent or when extraction fails.
70
+ `model_fields_set` distinguishes those cases. Full text is returned alongside
71
+ snippets.
72
+
73
+ Highlights are optional and disabled by default. Set
74
+ `content=ContentOptions(highlights=True)` to return concise, query-relevant
75
+ excerpts in addition to snippets. `SearchResult.highlights` is a `list[str]`, or
76
+ `None` when the field is absent. Highlights can coexist with snippets and full text.
77
+
78
+ For token-efficient workflows, your agent can prioritize highlights when building
79
+ model context and supplement them with snippets when additional context is needed.
80
+ When the API returns both fields, token savings depend on which content your
81
+ agent passes to the model.
82
+
83
+ `summary` and the snippet toggle have been removed from `ContentOptions`.
84
+ `favicon` remains optional under `content`. Obsolete answer, usage, news/video,
85
+ query tuning, and raw content/summary response fields and models have been removed.
86
+ Unknown response fields continue to be retained by Pydantic for forward compatibility.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ateve"
7
- version = "0.2.0"
7
+ version = "0.2.2"
8
8
  description = "Official Python SDK for the Ateve Search API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -0,0 +1 @@
1
+ __version__ = "0.2.2"
@@ -87,7 +87,7 @@ class TextOptions(RequestModel):
87
87
 
88
88
 
89
89
  class ContentOptions(RequestModel):
90
- snippet: Optional[bool] = Field(default=True)
90
+ highlights: Optional[bool] = Field(default=False)
91
91
  text: Optional[TextOptions] = None
92
92
  favicon: Optional[bool] = None
93
93
 
@@ -144,6 +144,7 @@ class SearchResult(ResponseModel):
144
144
  published_at: Optional[datetime] = None
145
145
  score: Optional[float] = None
146
146
  snippet: Optional[str] = None
147
+ highlights: Optional[List[str]] = None
147
148
  text: Optional[str] = None
148
149
  favicon: Optional[str] = None
149
150
  images: List[ImageItem] = Field(default_factory=list)
@@ -57,7 +57,8 @@ def test_sync_golden_contract_and_wire_names() -> None:
57
57
  date_range="2026-01-01..2026-06-30",
58
58
  locale=LocaleSettings(mkt="US", language="en"),
59
59
  content=ContentOptions(
60
- snippet=False, text=TextOptions(format=ContentFormat.MARKDOWN, max_characters=5000)
60
+ highlights=True,
61
+ text=TextOptions(format=ContentFormat.MARKDOWN, max_characters=5000)
61
62
  ),
62
63
  safe_search=False,
63
64
  )
@@ -66,7 +67,8 @@ def test_sync_golden_contract_and_wire_names() -> None:
66
67
  assert payload["date_range"] == "2026-01-01..2026-06-30"
67
68
  assert payload["safe_search"] is False
68
69
  assert payload["max_results"] == 20
69
- assert payload["content"]["snippet"] is False
70
+ assert "snippet" not in payload["content"]
71
+ assert payload["content"]["highlights"] is True
70
72
  assert not {"limit", "offset", "contents"}.intersection(payload)
71
73
  assert payload["content"]["text"]["max_characters"] == 5000
72
74
  assert "images" not in payload["content"]
@@ -77,6 +79,7 @@ def test_sync_golden_contract_and_wire_names() -> None:
77
79
  assert captured[0].headers["User-Agent"] == f"ateve-python/{__version__}"
78
80
  assert response.id == "req_01J9ZK3V6XQ8"
79
81
  assert response.results[0].published_at is not None
82
+ assert response.results[0].highlights == GOLDEN_RESPONSE["results"][0]["highlights"]
80
83
  assert response.query.original == "climate policy analysis"
81
84
  assert response.total_estimated_matches == 128000
82
85
 
@@ -341,7 +344,10 @@ def test_sync_and_async_send_new_defaults_and_nested_text_options() -> None:
341
344
  httpx.AsyncClient(transport=httpx.MockTransport(handler)) as transport,
342
345
  AsyncAteve("sk_test", http_client=transport) as client,
343
346
  ):
344
- await client.search("q", max_results=100, content=ContentOptions(snippet=False))
347
+ response = await client.search(
348
+ "q", max_results=100, content=ContentOptions(highlights=True)
349
+ )
350
+ assert response.results[0].highlights == GOLDEN_RESPONSE["results"][0]["highlights"]
345
351
  await client.search(
346
352
  SearchRequest(query="q", content=ContentOptions(text=TextOptions()))
347
353
  )
@@ -352,12 +358,12 @@ def test_sync_and_async_send_new_defaults_and_nested_text_options() -> None:
352
358
  {
353
359
  "query": "q",
354
360
  "max_results": 10,
355
- "content": {"snippet": True, "text": {"format": "text"}},
361
+ "content": {"highlights": False, "text": {"format": "text"}},
356
362
  },
357
- {"query": "q", "max_results": 100, "content": {"snippet": False}},
363
+ {"query": "q", "max_results": 100, "content": {"highlights": True}},
358
364
  {
359
365
  "query": "q",
360
366
  "max_results": 10,
361
- "content": {"snippet": True, "text": {"format": "text"}},
367
+ "content": {"highlights": False, "text": {"format": "text"}},
362
368
  },
363
369
  ]
@@ -25,6 +25,8 @@ def test_request_models_preserve_validation_and_frozen_configuration() -> None:
25
25
  request.query = "changed"
26
26
  with pytest.raises(ValidationError):
27
27
  TextOptions(max_characters=True)
28
+ with pytest.raises(ValidationError, match="snippet"):
29
+ ContentOptions.model_validate({"snippet": False})
28
30
 
29
31
 
30
32
  def test_response_models_preserve_defaults_nulls_extra_fields_and_dates() -> None:
@@ -54,16 +56,31 @@ def test_request_lists_are_snapshots_and_wire_names_stay_snake_case() -> None:
54
56
 
55
57
  def test_request_defaults_and_nested_text_activation() -> None:
56
58
  assert SearchRequest(query="q").max_results == 10
57
- assert ContentOptions().snippet is True
59
+ assert ContentOptions().highlights is False
58
60
  assert ContentOptions().text is None
59
61
  assert TextOptions().format == ContentFormat.TEXT
60
62
  request = SearchRequest(query="q", content=ContentOptions(text=TextOptions()))
61
63
  assert request.model_dump(mode="json", exclude_none=True) == {
62
64
  "query": "q",
63
65
  "max_results": 10,
64
- "content": {"snippet": True, "text": {"format": "text"}},
66
+ "content": {"highlights": False, "text": {"format": "text"}},
65
67
  }
66
- assert ContentOptions(snippet=False).model_dump(exclude_none=True) == {"snippet": False}
68
+ assert ContentOptions(highlights=True).highlights is True
69
+
70
+
71
+ def test_highlights_are_typed_and_preserve_missing_and_empty_values() -> None:
72
+ missing = SearchResult()
73
+ assert missing.highlights is None
74
+ assert "highlights" not in missing.model_fields_set
75
+ result = SearchResult(snippet="excerpt", highlights=["Answer support", "Further evidence"])
76
+ assert result.snippet == "excerpt"
77
+ assert result.highlights == ["Answer support", "Further evidence"]
78
+ assert result.model_extra == {}
79
+ assert SearchResult(highlights=[]).highlights == []
80
+ with pytest.raises(ValidationError):
81
+ SearchResult.model_validate({"highlights": "excerpt"})
82
+ with pytest.raises(ValidationError):
83
+ SearchResult.model_validate({"highlights": [1]})
67
84
 
68
85
 
69
86
  @pytest.mark.parametrize("value", [0, -1, 101])
@@ -87,7 +104,6 @@ def test_removed_request_fields_and_invalid_text_options_are_rejected() -> None:
87
104
  SearchRequest.model_validate({"query": "q", name: value})
88
105
  for name, value in [
89
106
  ("summary", True),
90
- ("highlights", True),
91
107
  ("raw_content", True),
92
108
  ("max_characters", 100),
93
109
  ("format", "text"),
@@ -99,7 +115,10 @@ def test_removed_request_fields_and_invalid_text_options_are_rejected() -> None:
99
115
  ContentOptions.model_validate({"text": value})
100
116
 
101
117
 
102
- @pytest.mark.parametrize("scenario", ["default", "text", "markdown", "no-snippet"])
118
+ @pytest.mark.parametrize("scenario", [
119
+ "default", "text", "markdown", "no-snippet",
120
+ "highlights", "highlights-no-snippet", "highlights-markdown",
121
+ ])
103
122
  def test_captured_live_responses_preserve_missing_and_null_text(scenario: str) -> None:
104
123
  fixture = (
105
124
  Path(__file__).resolve().parents[2] / f"contract/fixtures/live-{scenario}-response.json"
@@ -112,8 +131,10 @@ def test_captured_live_responses_preserve_missing_and_null_text(scenario: str) -
112
131
  for result, raw in zip(response.results, wire["results"], strict=True):
113
132
  assert result.text == raw.get("text")
114
133
  assert result.snippet == raw.get("snippet")
134
+ assert result.highlights == raw.get("highlights")
115
135
  assert ("text" in result.model_fields_set) == ("text" in raw)
116
136
  assert ("snippet" in result.model_fields_set) == ("snippet" in raw)
137
+ assert ("highlights" in result.model_fields_set) == ("highlights" in raw)
117
138
  assert result.model_extra == {}
118
139
  if scenario in {"text", "markdown"}:
119
140
  assert response.results[0].text is not None and len(response.results[0].text) == 300
@@ -1 +0,0 @@
1
- __version__ = "0.2.0"
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