deepsights-api 1.2.2__tar.gz → 1.2.4__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 (81) hide show
  1. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/CHANGELOG.md +8 -0
  2. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/PKG-INFO +1 -1
  3. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/api/api.py +53 -8
  4. deepsights_api-1.2.4/deepsights/contentstore/resources/_download.py +51 -0
  5. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/contentstore/resources/_search.py +23 -15
  6. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/contentstore/resources/news.py +12 -6
  7. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/contentstore/resources/secondary.py +12 -5
  8. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/pyproject.toml +1 -1
  9. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/tests/contentstore/test_news_search.py +31 -31
  10. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/tests/contentstore/test_secondary_search.py +27 -25
  11. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/tests/data/test_data.json +3 -3
  12. deepsights_api-1.2.4/uv.lock +584 -0
  13. deepsights_api-1.2.2/uv.lock +0 -248
  14. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/.github/workflows/build_docs.yml +0 -0
  15. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/.github/workflows/run_pylint.yml +0 -0
  16. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/.github/workflows/run_tests.yml +0 -0
  17. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/.gitignore +0 -0
  18. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/.pylintrc +0 -0
  19. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/.python-version +0 -0
  20. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/.vscode/settings.json +0 -0
  21. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/LICENSE +0 -0
  22. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/README.md +0 -0
  23. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/__init__.py +0 -0
  24. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/api/__init__.py +0 -0
  25. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/api/resource.py +0 -0
  26. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/contentstore/__init__.py +0 -0
  27. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/contentstore/contentstore.py +0 -0
  28. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/contentstore/resources/__init__.py +0 -0
  29. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/contentstore/resources/_model.py +0 -0
  30. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/deepsights/__init__.py +0 -0
  31. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/deepsights/_mip_identity.py +0 -0
  32. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/deepsights/deepsights.py +0 -0
  33. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/deepsights/resources/__init__.py +0 -0
  34. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/deepsights/resources/quota/__init__.py +0 -0
  35. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/deepsights/resources/quota/_model.py +0 -0
  36. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/deepsights/resources/quota/quota.py +0 -0
  37. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/documentstore/__init__.py +0 -0
  38. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/documentstore/documentstore.py +0 -0
  39. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/documentstore/resources/__init__.py +0 -0
  40. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/documentstore/resources/documents/__init__.py +0 -0
  41. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/documentstore/resources/documents/_cache.py +0 -0
  42. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/documentstore/resources/documents/_delete.py +0 -0
  43. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/documentstore/resources/documents/_download.py +0 -0
  44. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/documentstore/resources/documents/_list.py +0 -0
  45. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/documentstore/resources/documents/_load.py +0 -0
  46. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/documentstore/resources/documents/_model.py +0 -0
  47. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/documentstore/resources/documents/_search.py +0 -0
  48. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/documentstore/resources/documents/_segmenter.py +0 -0
  49. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/documentstore/resources/documents/_upload.py +0 -0
  50. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/documentstore/resources/documents/documents.py +0 -0
  51. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/userclient/__init__.py +0 -0
  52. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/userclient/resources/__init__.py +0 -0
  53. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/userclient/resources/_model.py +0 -0
  54. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/userclient/resources/answersV2/__init__.py +0 -0
  55. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/userclient/resources/answersV2/_model.py +0 -0
  56. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/userclient/resources/answersV2/answerV2.py +0 -0
  57. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/userclient/resources/reports/__init__.py +0 -0
  58. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/userclient/resources/reports/_model.py +0 -0
  59. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/userclient/resources/reports/report.py +0 -0
  60. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/userclient/userclient.py +0 -0
  61. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/utils/__init__.py +0 -0
  62. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/utils/_cache.py +0 -0
  63. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/utils/_ranking.py +0 -0
  64. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/utils/_utils.py +0 -0
  65. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/deepsights/utils/model.py +0 -0
  66. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/docs/.gitkeep +0 -0
  67. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/main.py +0 -0
  68. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/requirements.txt +0 -0
  69. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/setup.py +0 -0
  70. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/tests/data/test_presentation.pdf +0 -0
  71. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/tests/data/test_text.txt +0 -0
  72. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/tests/deepsights/identity/test_mip_identity.py +0 -0
  73. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/tests/deepsights/quota/test_quota.py +0 -0
  74. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/tests/deepsights/userclient/test_userclient.py +0 -0
  75. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/tests/documentstore/test_download.py +0 -0
  76. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/tests/documentstore/test_lifecycle.py +0 -0
  77. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/tests/documentstore/test_list.py +0 -0
  78. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/tests/documentstore/test_load.py +0 -0
  79. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/tests/documentstore/test_search.py +0 -0
  80. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/tests/userclient/answersV2/test_answersV2.py +0 -0
  81. {deepsights_api-1.2.2 → deepsights_api-1.2.4}/tests/userclient/reports/test_reports.py +0 -0
@@ -2,6 +2,14 @@
2
2
 
3
3
  <!--next-version-placeholder-->
4
4
 
5
+ ## v1.2.4 (16-Arp-2025)
6
+
7
+ - Sort order in SCS text search
8
+
9
+ ## v1.2.3 (25-Jan-2025)
10
+
11
+ - Support item text download from cont
12
+
5
13
  ## v1.2.2 (12-Jan-2025)
6
14
 
7
15
  - Add AI generated title to model
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deepsights-api
3
- Version: 1.2.2
3
+ Version: 1.2.4
4
4
  Summary: Python library for the DeepSights APIs
5
5
  Project-URL: Documentation, https://marketlogicsoftware.github.io/deepsights-api/
6
6
  Project-URL: Repository, https://github.com/marketlogicsoftware/deepsights-api
@@ -16,18 +16,19 @@
16
16
  This module contains base API client classes.
17
17
  """
18
18
 
19
- import os
20
19
  import logging
20
+ import os
21
21
  from typing import Dict
22
+
23
+ from ratelimit import limits, sleep_and_retry
24
+ from requests import Session
25
+ from requests.exceptions import Timeout
22
26
  from tenacity import (
23
27
  retry,
28
+ retry_if_exception_type,
24
29
  stop_after_attempt,
25
30
  wait_random_exponential,
26
- retry_if_exception_type,
27
31
  )
28
- from requests import Session
29
- from requests.exceptions import Timeout
30
- from ratelimit import limits, sleep_and_retry
31
32
 
32
33
 
33
34
  #################################################
@@ -107,6 +108,50 @@ class API:
107
108
 
108
109
  return response.json()
109
110
 
111
+ #######################################
112
+ @retry(
113
+ stop=stop_after_attempt(3),
114
+ wait=wait_random_exponential(max=5),
115
+ retry=retry_if_exception_type(Timeout),
116
+ )
117
+ @sleep_and_retry
118
+ @limits(calls=1000, period=60)
119
+ def get_content(
120
+ self, path: str, params: Dict = None, timeout=15, expected_statuscodes=[]
121
+ ) -> bytes:
122
+ """
123
+ Sends a GET request to the specified path and returns the raw response content.
124
+
125
+ This method is similar to get() but returns the raw response content instead of parsing it as JSON.
126
+ Useful for downloading binary data like files or images.
127
+
128
+ Args:
129
+ path (str): The path to send the GET request to.
130
+ params (Dict, optional): Optional parameters to include in the request. Defaults to None.
131
+ timeout (int, optional): The timeout in seconds for the request. Defaults to 15.
132
+ expected_statuscodes (List[int], optional): List of expected status codes. Defaults to an empty list.
133
+
134
+ Returns:
135
+ bytes: The raw content of the server's response.
136
+
137
+ Raises:
138
+ HTTPError: If the GET request fails with a non-200 status code and not in the expected_statuscodes list.
139
+ """
140
+ response = self._session.get(
141
+ self._endpoint(path), params=params, timeout=timeout
142
+ )
143
+
144
+ if (
145
+ response.status_code != 200
146
+ and not response.status_code in expected_statuscodes
147
+ ):
148
+ logging.error(
149
+ "GET %s failed with status code %s", path, response.status_code
150
+ )
151
+ response.raise_for_status()
152
+
153
+ return response.content.decode("utf-8")
154
+
110
155
  #######################################
111
156
  @retry(
112
157
  stop=stop_after_attempt(3),
@@ -208,9 +253,9 @@ class APIKeyAPI(API):
208
253
  super().__init__(endpoint_base)
209
254
 
210
255
  # set api key
211
- assert (
212
- api_key or api_key_env_var
213
- ), "Must provide either API key or environment variable"
256
+ assert api_key or api_key_env_var, (
257
+ "Must provide either API key or environment variable"
258
+ )
214
259
  if not api_key:
215
260
  api_key = os.environ.get(api_key_env_var)
216
261
  self._api_key = api_key
@@ -0,0 +1,51 @@
1
+ # Copyright 2024 Market Logic Software AG. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """
16
+ This module contains the base functions to download content from the ContentStore.
17
+ """
18
+
19
+ import os
20
+ import tempfile
21
+ import urllib.request
22
+
23
+ from deepsights.api import APIResource
24
+
25
+
26
+ #################################################
27
+ def contentstore_download(resource: APIResource, item_id: str) -> str:
28
+ """
29
+ Downloads and returns the extracted text content of an item from the ContentStore.
30
+
31
+ This function retrieves a signed download link for the specified item's extracted text content
32
+ from the ContentStore API, downloads the content to a temporary file, reads the text content,
33
+ and returns it as a string. The temporary file is automatically cleaned up after use.
34
+
35
+ Args:
36
+ resource (APIResource): An instance of the DeepSights API resource used to make API calls
37
+ to the ContentStore service.
38
+ item_id (str): The unique identifier of the item whose content should be downloaded.
39
+
40
+ Raises:
41
+ ValueError: If the download fails for any reason, such as network errors, invalid item ID,
42
+ or issues reading the downloaded content.
43
+
44
+ Returns:
45
+ str: The extracted text content of the downloaded item as a string.
46
+ """
47
+ return str(
48
+ resource.api.get_content(
49
+ f"/item-service/items/{item_id}/_download-content-extracted-text",
50
+ )
51
+ )
@@ -16,9 +16,11 @@
16
16
  This module contains the base functions to search the ContentStore.
17
17
  """
18
18
 
19
- from typing import List
20
19
  from datetime import datetime
20
+ from typing import List
21
+
21
22
  from pydantic import BaseModel
23
+
22
24
  from deepsights.api import API
23
25
  from deepsights.utils import (
24
26
  promote_exact_matches,
@@ -96,9 +98,9 @@ def contentstore_hybrid_search(
96
98
  assert 0 <= vector_fraction <= 1, "Vector fraction must be between 0 and 1"
97
99
  assert 0 <= vector_weight <= 1, "Vector weught must be between 0 and 1"
98
100
  assert 0 < max_results <= 250, "Maximum results must be between 1 and 250."
99
- assert (
100
- recency_weight is None or 0 <= recency_weight <= 1
101
- ), "Recency weight must be between 0 and 1."
101
+ assert recency_weight is None or 0 <= recency_weight <= 1, (
102
+ "Recency weight must be between 0 and 1."
103
+ )
102
104
 
103
105
  body = {
104
106
  "query": query,
@@ -171,9 +173,9 @@ def contentstore_vector_search(
171
173
  assert len(query_embedding) == 1536, "The 'query_embedding' must be of length 1536."
172
174
  assert 0 <= min_score <= 1, "Minimum score must be between 0 and 1."
173
175
  assert 0 < max_results <= 100, "Maximum results must be between 1 and 100."
174
- assert (
175
- recency_weight is None or 0 <= recency_weight <= 1
176
- ), "Recency weight must be between 0 and 1."
176
+ assert recency_weight is None or 0 <= recency_weight <= 1, (
177
+ "Recency weight must be between 0 and 1."
178
+ )
177
179
 
178
180
  body = {
179
181
  "vector": query_embedding,
@@ -206,7 +208,7 @@ def contentstore_text_search(
206
208
  max_results: int = 50,
207
209
  offset: int = 0,
208
210
  languages: List[str] = None,
209
- recency_weight: float = None,
211
+ sort_descending: bool = True,
210
212
  search_from_timestamp: datetime = None,
211
213
  search_to_timestamp: datetime = None,
212
214
  search_only_ai_allowed_content: bool = True,
@@ -223,7 +225,7 @@ def contentstore_text_search(
223
225
  max_results (int, optional): The maximum number of results to return. Defaults to 50.
224
226
  offset (int, optional): The offset to start the search from. Defaults to 0.
225
227
  languages (List[str], optional): The languages to search for. Defaults to None.
226
- recency_weight (float, optional): The weight assigned to recency in result ranking. Defaults to None.
228
+ most_recent_first (bool, optional): Whether to sort results by most recent first. Defaults to True.
227
229
  search_from_timestamp (datetime, optional): The start timestamp for the search. Defaults to None.
228
230
  search_to_timestamp (datetime, optional): The end timestamp for the search. Defaults to None.
229
231
  search_only_ai_allowed_content (bool, optional): Whether to search only AI-allowed content. Defaults to True.
@@ -233,21 +235,24 @@ def contentstore_text_search(
233
235
  List[BaseModel]: The re-ranked search results.
234
236
  """
235
237
  assert 0 < max_results <= 100, "Maximum results must be between 1 and 100."
236
- assert (
237
- recency_weight is None or 0 <= recency_weight <= 1
238
- ), "Recency weight must be between 0 and 1."
239
238
 
240
239
  # force proper empty search
241
240
  if query is not None and len(query.strip()) == 0:
242
241
  query = None
243
242
 
243
+ # determine sort order
244
+ if query is not None:
245
+ sort_order = "RELEVANCY_DESC" if sort_descending else "RELEVANCY_ASC"
246
+ else:
247
+ sort_order = "PUBLISHED_AT_DESC" if sort_descending else "PUBLISHED_AT_ASC"
248
+
244
249
  body = {
245
250
  "query": query,
246
251
  "source_items_type": item_type,
247
252
  "content_restrictions": "NONE",
248
253
  "limit": max_results,
249
254
  "offset": offset,
250
- "sort": "RELEVANCY_DESC" if query is not None else "PUBLISHED_AT_DESC",
255
+ "sort": sort_order,
251
256
  "languages": languages,
252
257
  "published_at": _get_time_filter(search_from_timestamp, search_to_timestamp),
253
258
  "content_restrictions": (
@@ -259,5 +264,8 @@ def contentstore_text_search(
259
264
  # parse
260
265
  results = [search_result(i) for i in response["items"]]
261
266
 
262
- # re-rank
263
- return rerank_by_recency(results, recency_weight=recency_weight)
267
+ # record rank
268
+ for rank, result in enumerate(results):
269
+ result.rank = rank + 1
270
+
271
+ return results
@@ -16,19 +16,19 @@
16
16
  This module defines the resource to retrieve news content from the DeepSights API.
17
17
  """
18
18
 
19
- from typing import List
20
19
  from datetime import datetime
20
+ from typing import List
21
21
 
22
22
  from deepsights.api import APIResource
23
+ from deepsights.contentstore.resources._download import contentstore_download
23
24
  from deepsights.contentstore.resources._model import ContentStoreSearchResult
24
25
  from deepsights.contentstore.resources._search import (
26
+ contentstore_hybrid_search,
25
27
  contentstore_text_search,
26
28
  contentstore_vector_search,
27
- contentstore_hybrid_search,
28
29
  )
29
30
 
30
31
 
31
-
32
32
  #################################################
33
33
  class NewsSearchResult(ContentStoreSearchResult):
34
34
  """
@@ -42,6 +42,12 @@ class NewsResource(APIResource):
42
42
  Represents a resource for retrieving news articles from the DeepSights content store.
43
43
  """
44
44
 
45
+ def download(self, item_id: str) -> str:
46
+ """
47
+ Download the extracted text content of an item from the ContentStore.
48
+ """
49
+ return contentstore_download(self, item_id)
50
+
45
51
  #################################################
46
52
  def vector_search(
47
53
  self,
@@ -93,7 +99,7 @@ class NewsResource(APIResource):
93
99
  max_results: int = 30,
94
100
  offset: int = 0,
95
101
  languages: List[str] = None,
96
- recency_weight: float = None,
102
+ sort_descending: bool = True,
97
103
  search_from_timestamp: datetime = None,
98
104
  search_to_timestamp: datetime = None,
99
105
  ):
@@ -106,7 +112,7 @@ class NewsResource(APIResource):
106
112
  max_results (int, optional): The maximum number of search results to return. Defaults to 30.
107
113
  offset (int, optional): The offset to start the search from. Defaults to 0.
108
114
  languages (List[str], optional): The list of languages to search for. Defaults to None.
109
- recency_weight (float, optional): The weight to assign to recency in the search ranking. Defaults to None.
115
+ sort_descending (bool, optional): Whether to sort the results in descending order. Defaults to True.
110
116
  search_from_timestamp (datetime, optional): The start timestamp for the search. Defaults to None.
111
117
  search_to_timestamp (datetime, optional): The end timestamp for the search. Defaults to None.
112
118
 
@@ -122,7 +128,7 @@ class NewsResource(APIResource):
122
128
  ),
123
129
  query=query,
124
130
  max_results=max_results,
125
- recency_weight=recency_weight,
131
+ sort_descending=sort_descending,
126
132
  languages=languages,
127
133
  offset=offset,
128
134
  search_from_timestamp=search_from_timestamp,
@@ -16,15 +16,16 @@
16
16
  This module contains the resource to retrieve secondary reports from the DeepSights content store.
17
17
  """
18
18
 
19
- from typing import List
20
19
  from datetime import datetime
20
+ from typing import List
21
21
 
22
22
  from deepsights.api import APIResource
23
+ from deepsights.contentstore.resources._download import contentstore_download
23
24
  from deepsights.contentstore.resources._model import ContentStoreSearchResult
24
25
  from deepsights.contentstore.resources._search import (
26
+ contentstore_hybrid_search,
25
27
  contentstore_text_search,
26
28
  contentstore_vector_search,
27
- contentstore_hybrid_search,
28
29
  )
29
30
 
30
31
 
@@ -41,6 +42,12 @@ class SecondaryResource(APIResource):
41
42
  Represents a resource for retrieving secondary reports from the DeepSights content store.
42
43
  """
43
44
 
45
+ def download(self, item_id: str) -> str:
46
+ """
47
+ Download the extracted text content of an item from the ContentStore.
48
+ """
49
+ return contentstore_download(self, item_id)
50
+
44
51
  #################################################
45
52
  def vector_search(
46
53
  self,
@@ -92,7 +99,7 @@ class SecondaryResource(APIResource):
92
99
  max_results: int = 50,
93
100
  offset: int = 0,
94
101
  languages: List[str] = None,
95
- recency_weight: float = None,
102
+ sort_descending: bool = True,
96
103
  search_from_timestamp: datetime = None,
97
104
  search_to_timestamp: datetime = None,
98
105
  ):
@@ -105,7 +112,7 @@ class SecondaryResource(APIResource):
105
112
  max_results (int, optional): The maximum number of search results to return. Defaults to 50.
106
113
  offset (int, optional): The offset to start the search from. Defaults to 0.
107
114
  languages (List[str], optional): The languages to search for. Defaults to None.
108
- recency_weight (float, optional): The weight to assign to recency in the search ranking. Defaults to None.
115
+ sort_descending (bool, optional): Whether to sort the results in descending order. Defaults to True.
109
116
  search_from_timestamp (datetime, optional): The start timestamp for the search. Defaults to None.
110
117
  search_to_timestamp (datetime, optional): The end timestamp for the search. Defaults to None.
111
118
 
@@ -123,7 +130,7 @@ class SecondaryResource(APIResource):
123
130
  max_results=max_results,
124
131
  offset=offset,
125
132
  languages=languages,
126
- recency_weight=recency_weight,
133
+ sort_descending=sort_descending,
127
134
  search_from_timestamp=search_from_timestamp,
128
135
  search_to_timestamp=search_to_timestamp,
129
136
  )
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "deepsights-api"
3
- version = "1.2.2"
3
+ version = "1.2.4"
4
4
  description = "Python library for the DeepSights APIs"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -79,6 +79,17 @@ def test_news_text_empty_search():
79
79
  assert result.publication_date <= results[ix - 1].publication_date
80
80
 
81
81
 
82
+ def test_news_download():
83
+ """ """
84
+ results = ds.contentstore.news.text_search(
85
+ query="",
86
+ max_results=1,
87
+ )
88
+
89
+ content = ds.contentstore.news.download(results[0].id)
90
+ assert len(content) > 0
91
+
92
+
82
93
  def test_news_text_search_offset():
83
94
  """
84
95
  Test the news text search function with an offset.
@@ -152,51 +163,43 @@ def test_news_text_search_with_date():
152
163
  assert result.publication_date <= end and result.publication_date >= start
153
164
 
154
165
 
155
- def test_news_text_search_with_recency_low():
166
+ def test_news_text_search_descending():
156
167
  """
157
- Test the news text search function with a low recency weight.
158
-
159
- This test verifies that the news text search function returns results
160
- when the recency weight is set to a low value. It checks that the
161
- returned results have valid IDs, ranks, score ranks, and age ranks,
162
- and that the final rank is identical to the score rank.
163
-
164
- Note: This test assumes the existence of a `test_query` variable.
168
+ Test news text search with descending publication date sort order.
169
+ Verifies results have valid IDs, sequential ranks, and descending dates.
165
170
  """
166
-
167
171
  results = ds.contentstore.news.text_search(
168
- query=test_query,
172
+ query=None,
169
173
  max_results=10,
170
- recency_weight=0.00001,
174
+ sort_descending=True,
171
175
  )
172
176
 
173
177
  assert len(results) > 0
174
178
  for ix, result in enumerate(results):
175
179
  assert result.id is not None
176
180
  assert result.rank == ix + 1
181
+ if ix < len(results) - 1:
182
+ assert result.publication_date >= results[ix + 1].publication_date
177
183
 
178
184
 
179
- def test_news_text_search_with_recency_high():
185
+ def test_news_text_search_ascending():
180
186
  """
181
- Test the news text search function with a high recency weight.
182
-
183
- This test verifies that the news text search function returns results
184
- when the recency weight is set to a high value. It checks that the
185
- returned results have valid IDs, ranks, score ranks, and age ranks,
186
- and that the final rank is identical to the age rank.
187
-
188
- Note: This test assumes the existence of a `test_query` variable.
187
+ Test news text search with ascending publication date sort order.
188
+ Verifies results have valid IDs, sequential ranks, and ascending dates.
189
189
  """
190
+
190
191
  results = ds.contentstore.news.text_search(
191
- query=test_query,
192
+ query=None,
192
193
  max_results=10,
193
- recency_weight=0.99999,
194
+ sort_descending=False,
194
195
  )
195
196
 
196
197
  assert len(results) > 0
197
198
  for ix, result in enumerate(results):
198
199
  assert result.id is not None
199
200
  assert result.rank == ix + 1
201
+ if ix < len(results) - 1:
202
+ assert result.publication_date <= results[ix + 1].publication_date
200
203
 
201
204
 
202
205
  def test_news_vector_search():
@@ -349,9 +352,7 @@ def test_news_hybrid_search_only_text():
349
352
  )
350
353
 
351
354
  text_results = ds.contentstore.news.text_search(
352
- query=test_query,
353
- max_results=results,
354
- recency_weight=0.0,
355
+ query=test_query, max_results=results
355
356
  )
356
357
 
357
358
  assert len(hybrid_results) == results
@@ -439,19 +440,19 @@ def test_news_hybrid_search_with_vector_high():
439
440
  """
440
441
  hybrid_results = ds.contentstore.news.search(
441
442
  query=test_query,
442
- max_results=10,
443
- vector_weight=0.99999,
443
+ max_results=5,
444
+ vector_weight=0.9999999999,
444
445
  vector_fraction=1.0,
445
446
  recency_weight=0.0,
446
447
  )
447
448
 
448
449
  vector_results = ds.contentstore.news.vector_search(
449
450
  query_embedding=test_embedding,
450
- max_results=10,
451
+ max_results=5,
451
452
  recency_weight=0.0,
452
453
  )
453
454
 
454
- assert len(hybrid_results) == 10
455
+ assert len(hybrid_results) == 5
455
456
  for ix, result in enumerate(hybrid_results):
456
457
  assert equal_results(result, vector_results[ix])
457
458
 
@@ -476,7 +477,6 @@ def test_news_hybrid_search_with_vector_low():
476
477
  text_results = ds.contentstore.news.text_search(
477
478
  query=test_query,
478
479
  max_results=results,
479
- recency_weight=0.0,
480
480
  )
481
481
 
482
482
  assert len(hybrid_results) == results
@@ -80,6 +80,17 @@ def test_secondary_text_empty_search():
80
80
  assert result.publication_date <= results[ix - 1].publication_date
81
81
 
82
82
 
83
+ def test_news_download():
84
+ """ """
85
+ results = ds.contentstore.secondary.text_search(
86
+ query="",
87
+ max_results=1,
88
+ )
89
+
90
+ content = ds.contentstore.secondary.download(results[0].id)
91
+ assert len(content) > 0
92
+
93
+
83
94
  def test_secondary_text_search_offset():
84
95
  """
85
96
  Test the secondary text search function with an offset.
@@ -153,51 +164,43 @@ def test_secondary_text_search_with_date():
153
164
  assert result.publication_date <= end and result.publication_date >= start
154
165
 
155
166
 
156
- def test_secondary_text_search_with_recency_low():
167
+ def test_secondary_text_search_descending():
157
168
  """
158
- Test the secondary text search function with a low recency weight.
159
-
160
- This test verifies that the secondary text search function returns results
161
- when the recency weight is set to a low value. It checks that the
162
- returned results have valid IDs, ranks, score ranks, and age ranks,
163
- and that the final rank is identical to the score rank.
164
-
165
- Note: This test assumes the existence of a `test_query` variable.
169
+ Test secondary text search with descending publication date sort order.
170
+ Verifies results have valid IDs, sequential ranks, and descending dates.
166
171
  """
167
-
168
172
  results = ds.contentstore.secondary.text_search(
169
- query=test_query,
173
+ query=None,
170
174
  max_results=10,
171
- recency_weight=0.00001,
175
+ sort_descending=True,
172
176
  )
173
177
 
174
178
  assert len(results) > 0
175
179
  for ix, result in enumerate(results):
176
180
  assert result.id is not None
177
181
  assert result.rank == ix + 1
182
+ if ix < len(results) - 1:
183
+ assert result.publication_date >= results[ix + 1].publication_date
178
184
 
179
185
 
180
- def test_secondary_text_search_with_recency_high():
186
+ def test_secondary_text_search_ascending():
181
187
  """
182
- Test the secondary text search function with a high recency weight.
183
-
184
- This test verifies that the secondary text search function returns results
185
- when the recency weight is set to a high value. It checks that the
186
- returned results have valid IDs, ranks, score ranks, and age ranks,
187
- and that the final rank is identical to the age rank.
188
-
189
- Note: This test assumes the existence of a `test_query` variable.
188
+ Test secondary text search with ascending publication date sort order.
189
+ Verifies results have valid IDs, sequential ranks, and ascending dates.
190
190
  """
191
+
191
192
  results = ds.contentstore.secondary.text_search(
192
- query=test_query,
193
+ query=None,
193
194
  max_results=10,
194
- recency_weight=0.99999,
195
+ sort_descending=False,
195
196
  )
196
197
 
197
198
  assert len(results) > 0
198
199
  for ix, result in enumerate(results):
199
200
  assert result.id is not None
200
201
  assert result.rank == ix + 1
202
+ if ix < len(results) - 1:
203
+ assert result.publication_date <= results[ix + 1].publication_date
201
204
 
202
205
 
203
206
  def test_secondary_vector_search():
@@ -348,7 +351,6 @@ def test_secondary_hybrid_search_only_text():
348
351
  text_results = ds.contentstore.secondary.text_search(
349
352
  query=test_query,
350
353
  max_results=5,
351
- recency_weight=0.0,
352
354
  )
353
355
 
354
356
  assert len(hybrid_results) == 5
@@ -480,7 +482,7 @@ def test_secondary_text_search_with_title_promotion():
480
482
  This test case verifies that the hybrid search function returns the expected results
481
483
  when searching for secondary articles based on a query with title promotion enabled.
482
484
  """
483
- query = "consumer spending"
485
+ query = "shopping consumer"
484
486
 
485
487
  # first find top 10 results without title promotion and hard recency weight
486
488
  hybrid_results_no_promotion = ds.contentstore.secondary.search(
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "comment": "text-ada-002 embedding for 'What are emerging food moments with Gen Z consumers?'",
3
3
  "question": "What are emerging food moments with Gen Z consumers?",
4
- "document_id": "900aa7c7-60f4-4b2e-bc33-9dba20f4508f",
5
- "document_page_id": "3d861e4e-37cd-4a7b-b4cf-6986556f9544",
6
- "report_id": "7e9943f3-d9b8-4b36-86b0-c5ef939fcc71",
4
+ "document_id": "b3d728a6-6ef8-4c31-9949-dcc49439f4f1",
5
+ "document_page_id": "319eb5ab-e701-402e-881f-be3ccede65b3",
6
+ "report_id": "44126fc2-2aff-47c0-ac3f-d86bd6afcca2",
7
7
  "answerV2_id": "7511f5dd-bb5d-4082-8799-0d1733d61afc",
8
8
  "embedding": [
9
9
  -0.0012380050029605627,