deepsights-api 1.3.2__tar.gz → 1.3.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.
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/CHANGELOG.md +10 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/PKG-INFO +77 -10
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/README.md +76 -9
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/__init__.py +11 -1
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/api/api.py +66 -21
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/documentstore/resources/documents/_search.py +4 -2
- deepsights_api-1.3.4/deepsights/exceptions.py +44 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/userclient/resources/answersV2/answerV2.py +26 -5
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/userclient/resources/documents/documents.py +7 -3
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/userclient/resources/reports/report.py +19 -4
- deepsights_api-1.3.4/deepsights/userclient/userclient.py +260 -0
- deepsights_api-1.3.4/main.py +298 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/pyproject.toml +1 -1
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/tests/deepsights/quota/test_quota.py +13 -13
- deepsights_api-1.3.4/tests/userclient/test_rate_limiting.py +266 -0
- deepsights_api-1.3.4/tests/userclient/test_userclient_refresh.py +272 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/uv.lock +1 -1
- deepsights_api-1.3.2/deepsights/userclient/userclient.py +0 -56
- deepsights_api-1.3.2/main.py +0 -163
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/.claude/settings.local.json +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/.github/workflows/build_docs.yml +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/.github/workflows/run_pylint.yml +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/.github/workflows/run_tests.yml +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/.gitignore +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/.pylintrc +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/.python-version +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/.vscode/settings.json +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/CLAUDE.md +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/LICENSE +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/api/__init__.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/api/resource.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/contentstore/__init__.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/contentstore/contentstore.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/contentstore/resources/__init__.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/contentstore/resources/_download.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/contentstore/resources/_model.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/contentstore/resources/_search.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/contentstore/resources/news.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/contentstore/resources/secondary.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/deepsights/__init__.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/deepsights/_mip_identity.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/deepsights/deepsights.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/deepsights/resources/__init__.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/deepsights/resources/quota/__init__.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/deepsights/resources/quota/_model.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/deepsights/resources/quota/quota.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/documentstore/__init__.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/documentstore/documentstore.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/documentstore/resources/__init__.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/documentstore/resources/documents/__init__.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/documentstore/resources/documents/_cache.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/documentstore/resources/documents/_delete.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/documentstore/resources/documents/_download.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/documentstore/resources/documents/_list.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/documentstore/resources/documents/_load.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/documentstore/resources/documents/_model.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/documentstore/resources/documents/_segmenter.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/documentstore/resources/documents/_upload.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/documentstore/resources/documents/documents.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/userclient/__init__.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/userclient/resources/__init__.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/userclient/resources/_model.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/userclient/resources/answersV2/__init__.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/userclient/resources/answersV2/_model.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/userclient/resources/documents/__init__.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/userclient/resources/reports/__init__.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/userclient/resources/reports/_model.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/userclient/resources/search/__init__.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/userclient/resources/search/_model.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/userclient/resources/search/search.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/utils/__init__.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/utils/_cache.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/utils/_ranking.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/utils/_utils.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/utils/model.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/docs/.gitkeep +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/requirements.txt +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/setup.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/tests/conftest.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/tests/contentstore/test_news_search.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/tests/contentstore/test_secondary_search.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/tests/data/test_data.json +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/tests/data/test_presentation.pdf +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/tests/data/test_text.txt +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/tests/deepsights/identity/test_mip_identity.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/tests/deepsights/userclient/test_userclient.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/tests/documentstore/test_download.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/tests/documentstore/test_lifecycle.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/tests/documentstore/test_list.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/tests/documentstore/test_load.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/tests/documentstore/test_search.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/tests/helpers/__init__.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/tests/helpers/common.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/tests/helpers/validation.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/tests/userclient/answersV2/test_answersV2.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/tests/userclient/documents/test_document_pages_load.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/tests/userclient/documents/test_documents_list.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/tests/userclient/documents/test_documents_load.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/tests/userclient/documents/test_documents_search.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/tests/userclient/reports/test_reports.py +0 -0
- {deepsights_api-1.3.2 → deepsights_api-1.3.4}/tests/userclient/search/test_topicsearch.py +0 -0
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
<!--next-version-placeholder-->
|
|
4
4
|
|
|
5
|
+
## v1.3.4 (11-Jul-2025)
|
|
6
|
+
|
|
7
|
+
- Extended hybrid search query length limitation to 512 chars
|
|
8
|
+
|
|
9
|
+
## v1.3.3 (26-Jun-2025)
|
|
10
|
+
|
|
11
|
+
- Auto-refresh token for userclient
|
|
12
|
+
- Streamlined rate limiting and error handling
|
|
13
|
+
|
|
14
|
+
|
|
5
15
|
## v1.3.2 (18-Jun-2025)
|
|
6
16
|
|
|
7
17
|
- Improved error logging for answer polling
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deepsights-api
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.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
|
|
@@ -202,14 +202,48 @@ for item in content.results:
|
|
|
202
202
|
print(f"{item.title} - {item.source}")
|
|
203
203
|
```
|
|
204
204
|
|
|
205
|
-
#### Error Handling
|
|
205
|
+
#### Error Handling & Rate Limiting
|
|
206
206
|
```python
|
|
207
|
+
import time
|
|
208
|
+
import deepsights
|
|
209
|
+
|
|
210
|
+
ds = deepsights.DeepSights()
|
|
211
|
+
uc = ds.get_userclient("analyst@company.com")
|
|
212
|
+
|
|
213
|
+
try:
|
|
214
|
+
# Ask multiple questions - will demonstrate rate limiting
|
|
215
|
+
for i in range(15): # Exceeds 10/minute limit for answers
|
|
216
|
+
response = uc.answersV2.create(f"Question {i}: Market trends?")
|
|
217
|
+
print(f"✅ Question {i+1} processed")
|
|
218
|
+
|
|
219
|
+
except deepsights.RateLimitError as e:
|
|
220
|
+
print(f"🚫 Rate limit exceeded: {e}")
|
|
221
|
+
|
|
222
|
+
if e.retry_after:
|
|
223
|
+
print(f"⏱️ Client-side limit - wait {e.retry_after} seconds")
|
|
224
|
+
time.sleep(e.retry_after)
|
|
225
|
+
else:
|
|
226
|
+
print("⏱️ Server busy - try again later")
|
|
227
|
+
|
|
228
|
+
except deepsights.AuthenticationError as e:
|
|
229
|
+
print(f"🔐 Authentication failed: {e}")
|
|
230
|
+
|
|
231
|
+
except deepsights.DeepSightsError as e:
|
|
232
|
+
print(f"⚠️ API error: {e}")
|
|
233
|
+
|
|
234
|
+
# Alternative: Handle all rate limiting uniformly
|
|
207
235
|
try:
|
|
208
236
|
response = uc.answersV2.create_and_wait("Your question here")
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
print("Rate limit
|
|
237
|
+
|
|
238
|
+
except deepsights.RateLimitError as e:
|
|
239
|
+
# Handles both client-side (10/min) and server-side (persistent 429) limits
|
|
240
|
+
print(f"Rate limit hit: {e}")
|
|
241
|
+
|
|
242
|
+
# Implement your retry strategy
|
|
243
|
+
if e.retry_after:
|
|
244
|
+
time.sleep(e.retry_after) # Known wait time
|
|
245
|
+
else:
|
|
246
|
+
time.sleep(60) # Conservative fallback for server limits
|
|
213
247
|
```
|
|
214
248
|
|
|
215
249
|
All return values are [Pydantic objects](https://docs.pydantic.dev/latest/) with `.schema_human()` for exploring available properties. See [main.py](https://github.com/marketlogicsoftware/deepsights-api/blob/main/main.py) for more examples.
|
|
@@ -217,10 +251,43 @@ All return values are [Pydantic objects](https://docs.pydantic.dev/latest/) with
|
|
|
217
251
|
|
|
218
252
|
## Developer Information
|
|
219
253
|
|
|
220
|
-
### Rate Limits
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
254
|
+
### Rate Limits & Error Handling
|
|
255
|
+
|
|
256
|
+
The DeepSights API implements **comprehensive rate limiting** to ensure fair usage and optimal performance:
|
|
257
|
+
|
|
258
|
+
#### Client-Side Rate Limits
|
|
259
|
+
- **AI Answers**: 10 requests per 60 seconds (`uc.answersV2.create()`)
|
|
260
|
+
- **AI Reports**: 3 requests per 60 seconds (`uc.reports.create()`)
|
|
261
|
+
- **General GET requests**: 1,000 per 60 seconds
|
|
262
|
+
- **General POST requests**: 100 per 60 seconds
|
|
263
|
+
|
|
264
|
+
#### Behavior
|
|
265
|
+
- **Client-side limits**: Immediate `RateLimitError` with `retry_after` information
|
|
266
|
+
- **Server-side limits**: Automatic retry with exponential backoff (up to 3 attempts)
|
|
267
|
+
- **Persistent server limits**: Convert to `RateLimitError` after retries for consistent handling
|
|
268
|
+
|
|
269
|
+
#### Exception Hierarchy
|
|
270
|
+
```python
|
|
271
|
+
deepsights.DeepSightsError # Base exception
|
|
272
|
+
├── deepsights.RateLimitError # Rate limiting (client + server)
|
|
273
|
+
└── deepsights.AuthenticationError # Invalid API keys/permissions
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
#### Best Practices
|
|
277
|
+
```python
|
|
278
|
+
# Recommended error handling pattern
|
|
279
|
+
try:
|
|
280
|
+
response = uc.answersV2.create_and_wait("Your question")
|
|
281
|
+
|
|
282
|
+
except deepsights.RateLimitError as e:
|
|
283
|
+
# Single handler for all rate limiting scenarios
|
|
284
|
+
wait_time = e.retry_after or 60 # Use provided time or conservative fallback
|
|
285
|
+
print(f"Rate limited, waiting {wait_time} seconds...")
|
|
286
|
+
time.sleep(wait_time)
|
|
287
|
+
|
|
288
|
+
except deepsights.AuthenticationError:
|
|
289
|
+
print("Check your API keys and permissions")
|
|
290
|
+
```
|
|
224
291
|
|
|
225
292
|
### Caching
|
|
226
293
|
- User client responses cached for 240 seconds (TTL)
|
|
@@ -172,14 +172,48 @@ for item in content.results:
|
|
|
172
172
|
print(f"{item.title} - {item.source}")
|
|
173
173
|
```
|
|
174
174
|
|
|
175
|
-
#### Error Handling
|
|
175
|
+
#### Error Handling & Rate Limiting
|
|
176
176
|
```python
|
|
177
|
+
import time
|
|
178
|
+
import deepsights
|
|
179
|
+
|
|
180
|
+
ds = deepsights.DeepSights()
|
|
181
|
+
uc = ds.get_userclient("analyst@company.com")
|
|
182
|
+
|
|
183
|
+
try:
|
|
184
|
+
# Ask multiple questions - will demonstrate rate limiting
|
|
185
|
+
for i in range(15): # Exceeds 10/minute limit for answers
|
|
186
|
+
response = uc.answersV2.create(f"Question {i}: Market trends?")
|
|
187
|
+
print(f"✅ Question {i+1} processed")
|
|
188
|
+
|
|
189
|
+
except deepsights.RateLimitError as e:
|
|
190
|
+
print(f"🚫 Rate limit exceeded: {e}")
|
|
191
|
+
|
|
192
|
+
if e.retry_after:
|
|
193
|
+
print(f"⏱️ Client-side limit - wait {e.retry_after} seconds")
|
|
194
|
+
time.sleep(e.retry_after)
|
|
195
|
+
else:
|
|
196
|
+
print("⏱️ Server busy - try again later")
|
|
197
|
+
|
|
198
|
+
except deepsights.AuthenticationError as e:
|
|
199
|
+
print(f"🔐 Authentication failed: {e}")
|
|
200
|
+
|
|
201
|
+
except deepsights.DeepSightsError as e:
|
|
202
|
+
print(f"⚠️ API error: {e}")
|
|
203
|
+
|
|
204
|
+
# Alternative: Handle all rate limiting uniformly
|
|
177
205
|
try:
|
|
178
206
|
response = uc.answersV2.create_and_wait("Your question here")
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
print("Rate limit
|
|
207
|
+
|
|
208
|
+
except deepsights.RateLimitError as e:
|
|
209
|
+
# Handles both client-side (10/min) and server-side (persistent 429) limits
|
|
210
|
+
print(f"Rate limit hit: {e}")
|
|
211
|
+
|
|
212
|
+
# Implement your retry strategy
|
|
213
|
+
if e.retry_after:
|
|
214
|
+
time.sleep(e.retry_after) # Known wait time
|
|
215
|
+
else:
|
|
216
|
+
time.sleep(60) # Conservative fallback for server limits
|
|
183
217
|
```
|
|
184
218
|
|
|
185
219
|
All return values are [Pydantic objects](https://docs.pydantic.dev/latest/) with `.schema_human()` for exploring available properties. See [main.py](https://github.com/marketlogicsoftware/deepsights-api/blob/main/main.py) for more examples.
|
|
@@ -187,10 +221,43 @@ All return values are [Pydantic objects](https://docs.pydantic.dev/latest/) with
|
|
|
187
221
|
|
|
188
222
|
## Developer Information
|
|
189
223
|
|
|
190
|
-
### Rate Limits
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
224
|
+
### Rate Limits & Error Handling
|
|
225
|
+
|
|
226
|
+
The DeepSights API implements **comprehensive rate limiting** to ensure fair usage and optimal performance:
|
|
227
|
+
|
|
228
|
+
#### Client-Side Rate Limits
|
|
229
|
+
- **AI Answers**: 10 requests per 60 seconds (`uc.answersV2.create()`)
|
|
230
|
+
- **AI Reports**: 3 requests per 60 seconds (`uc.reports.create()`)
|
|
231
|
+
- **General GET requests**: 1,000 per 60 seconds
|
|
232
|
+
- **General POST requests**: 100 per 60 seconds
|
|
233
|
+
|
|
234
|
+
#### Behavior
|
|
235
|
+
- **Client-side limits**: Immediate `RateLimitError` with `retry_after` information
|
|
236
|
+
- **Server-side limits**: Automatic retry with exponential backoff (up to 3 attempts)
|
|
237
|
+
- **Persistent server limits**: Convert to `RateLimitError` after retries for consistent handling
|
|
238
|
+
|
|
239
|
+
#### Exception Hierarchy
|
|
240
|
+
```python
|
|
241
|
+
deepsights.DeepSightsError # Base exception
|
|
242
|
+
├── deepsights.RateLimitError # Rate limiting (client + server)
|
|
243
|
+
└── deepsights.AuthenticationError # Invalid API keys/permissions
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
#### Best Practices
|
|
247
|
+
```python
|
|
248
|
+
# Recommended error handling pattern
|
|
249
|
+
try:
|
|
250
|
+
response = uc.answersV2.create_and_wait("Your question")
|
|
251
|
+
|
|
252
|
+
except deepsights.RateLimitError as e:
|
|
253
|
+
# Single handler for all rate limiting scenarios
|
|
254
|
+
wait_time = e.retry_after or 60 # Use provided time or conservative fallback
|
|
255
|
+
print(f"Rate limited, waiting {wait_time} seconds...")
|
|
256
|
+
time.sleep(wait_time)
|
|
257
|
+
|
|
258
|
+
except deepsights.AuthenticationError:
|
|
259
|
+
print("Check your API keys and permissions")
|
|
260
|
+
```
|
|
194
261
|
|
|
195
262
|
### Caching
|
|
196
263
|
- User client responses cached for 240 seconds (TTL)
|
|
@@ -16,7 +16,17 @@
|
|
|
16
16
|
This module contains the client library to interact with the Market Logic DeepSights and ContentStore APIs.
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
|
+
from deepsights import exceptions
|
|
19
20
|
from deepsights.deepsights import DeepSights
|
|
20
21
|
from deepsights.documentstore.resources import SortingField, SortingOrder
|
|
22
|
+
from deepsights.exceptions import AuthenticationError, DeepSightsError, RateLimitError
|
|
21
23
|
|
|
22
|
-
__all__ = [
|
|
24
|
+
__all__ = [
|
|
25
|
+
"SortingField",
|
|
26
|
+
"SortingOrder",
|
|
27
|
+
"DeepSights",
|
|
28
|
+
"exceptions",
|
|
29
|
+
"AuthenticationError",
|
|
30
|
+
"DeepSightsError",
|
|
31
|
+
"RateLimitError",
|
|
32
|
+
]
|
|
@@ -29,6 +29,8 @@ from tenacity import (
|
|
|
29
29
|
wait_random_exponential,
|
|
30
30
|
)
|
|
31
31
|
|
|
32
|
+
from deepsights.exceptions import AuthenticationError, RateLimitError
|
|
33
|
+
|
|
32
34
|
|
|
33
35
|
def _should_retry_http_error(exception: Exception) -> bool:
|
|
34
36
|
"""
|
|
@@ -53,6 +55,50 @@ def _should_retry_http_error(exception: Exception) -> bool:
|
|
|
53
55
|
return isinstance(exception, (ConnectionError, Timeout))
|
|
54
56
|
|
|
55
57
|
|
|
58
|
+
def _handle_http_error(response):
|
|
59
|
+
"""
|
|
60
|
+
Handles HTTP errors and raises appropriate custom exceptions.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
response: The HTTP response object
|
|
64
|
+
|
|
65
|
+
Raises:
|
|
66
|
+
AuthenticationError: If status code is 401 (Unauthorized)
|
|
67
|
+
HTTPError: For other HTTP errors
|
|
68
|
+
"""
|
|
69
|
+
if response.status_code == 401:
|
|
70
|
+
raise AuthenticationError("Invalid API key or insufficient permissions")
|
|
71
|
+
elif response.status_code in [429, 502, 503]:
|
|
72
|
+
raise HTTPError(f"Retriable error {response.status_code}", response=response)
|
|
73
|
+
else:
|
|
74
|
+
response.raise_for_status()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _handle_persistent_rate_limit(func):
|
|
78
|
+
"""
|
|
79
|
+
Decorator to catch persistent 429 errors after retries and convert them to RateLimitError.
|
|
80
|
+
|
|
81
|
+
This ensures consistent exception handling for both client-side and server-side rate limiting.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
def wrapper(*args, **kwargs):
|
|
85
|
+
try:
|
|
86
|
+
return func(*args, **kwargs)
|
|
87
|
+
except HTTPError as e:
|
|
88
|
+
if (
|
|
89
|
+
hasattr(e, "response")
|
|
90
|
+
and e.response is not None
|
|
91
|
+
and e.response.status_code == 429
|
|
92
|
+
):
|
|
93
|
+
raise RateLimitError(
|
|
94
|
+
"Server rate limit exceeded after retries. Please wait before making another request.",
|
|
95
|
+
retry_after=None, # Server didn't provide retry-after info
|
|
96
|
+
) from e
|
|
97
|
+
raise
|
|
98
|
+
|
|
99
|
+
return wrapper
|
|
100
|
+
|
|
101
|
+
|
|
56
102
|
#################################################
|
|
57
103
|
class API:
|
|
58
104
|
"""
|
|
@@ -98,7 +144,7 @@ class API:
|
|
|
98
144
|
|
|
99
145
|
# set keep-alive headers
|
|
100
146
|
self._session.headers.update(
|
|
101
|
-
{"Connection": "keep-alive", "User-Agent": "deepsights-api/1.3.
|
|
147
|
+
{"Connection": "keep-alive", "User-Agent": "deepsights-api/1.3.4"}
|
|
102
148
|
)
|
|
103
149
|
|
|
104
150
|
# store default timeout
|
|
@@ -120,6 +166,7 @@ class API:
|
|
|
120
166
|
return self._endpoint_base + path.strip("/")
|
|
121
167
|
|
|
122
168
|
#######################################
|
|
169
|
+
@_handle_persistent_rate_limit
|
|
123
170
|
@retry(
|
|
124
171
|
stop=stop_after_attempt(3),
|
|
125
172
|
wait=wait_random_exponential(max=5),
|
|
@@ -143,6 +190,8 @@ class API:
|
|
|
143
190
|
The JSON body of the server's response to the request.
|
|
144
191
|
|
|
145
192
|
Raises:
|
|
193
|
+
AuthenticationError: If the request fails with a 401 status code.
|
|
194
|
+
RateLimitError: If the request fails with persistent 429 status code after retries.
|
|
146
195
|
HTTPError: If the GET request fails with a non-200 status code and not in the expected_statuscodes list.
|
|
147
196
|
"""
|
|
148
197
|
timeout = timeout or self._default_timeout
|
|
@@ -154,15 +203,12 @@ class API:
|
|
|
154
203
|
response.status_code not in [200, 201, 202]
|
|
155
204
|
and response.status_code not in expected_statuscodes
|
|
156
205
|
):
|
|
157
|
-
|
|
158
|
-
raise HTTPError(
|
|
159
|
-
f"Retriable error {response.status_code}", response=response
|
|
160
|
-
)
|
|
161
|
-
response.raise_for_status()
|
|
206
|
+
_handle_http_error(response)
|
|
162
207
|
|
|
163
208
|
return response.json()
|
|
164
209
|
|
|
165
210
|
#######################################
|
|
211
|
+
@_handle_persistent_rate_limit
|
|
166
212
|
@retry(
|
|
167
213
|
stop=stop_after_attempt(3),
|
|
168
214
|
wait=wait_random_exponential(max=5),
|
|
@@ -189,6 +235,8 @@ class API:
|
|
|
189
235
|
bytes: The raw content of the server's response.
|
|
190
236
|
|
|
191
237
|
Raises:
|
|
238
|
+
AuthenticationError: If the request fails with a 401 status code.
|
|
239
|
+
RateLimitError: If the request fails with persistent 429 status code after retries.
|
|
192
240
|
HTTPError: If the GET request fails with a non-200 status code and not in the expected_statuscodes list.
|
|
193
241
|
"""
|
|
194
242
|
timeout = timeout or self._default_timeout
|
|
@@ -200,15 +248,12 @@ class API:
|
|
|
200
248
|
response.status_code not in [200, 201, 202]
|
|
201
249
|
and response.status_code not in expected_statuscodes
|
|
202
250
|
):
|
|
203
|
-
|
|
204
|
-
raise HTTPError(
|
|
205
|
-
f"Retriable error {response.status_code}", response=response
|
|
206
|
-
)
|
|
207
|
-
response.raise_for_status()
|
|
251
|
+
_handle_http_error(response)
|
|
208
252
|
|
|
209
253
|
return response.content
|
|
210
254
|
|
|
211
255
|
#######################################
|
|
256
|
+
@_handle_persistent_rate_limit
|
|
212
257
|
@retry(
|
|
213
258
|
stop=stop_after_attempt(3),
|
|
214
259
|
wait=wait_random_exponential(max=5),
|
|
@@ -236,6 +281,11 @@ class API:
|
|
|
236
281
|
|
|
237
282
|
Returns:
|
|
238
283
|
Dict: The JSON body of the server's response to the request.
|
|
284
|
+
|
|
285
|
+
Raises:
|
|
286
|
+
AuthenticationError: If the request fails with a 401 status code.
|
|
287
|
+
RateLimitError: If the request fails with persistent 429 status code after retries.
|
|
288
|
+
HTTPError: For other HTTP errors.
|
|
239
289
|
"""
|
|
240
290
|
timeout = timeout or self._default_timeout
|
|
241
291
|
response = self._session.post(
|
|
@@ -246,15 +296,12 @@ class API:
|
|
|
246
296
|
response.status_code not in [200, 201, 202]
|
|
247
297
|
and response.status_code not in expected_statuscodes
|
|
248
298
|
):
|
|
249
|
-
|
|
250
|
-
raise HTTPError(
|
|
251
|
-
f"Retriable error {response.status_code}", response=response
|
|
252
|
-
)
|
|
253
|
-
response.raise_for_status()
|
|
299
|
+
_handle_http_error(response)
|
|
254
300
|
|
|
255
301
|
return response.json()
|
|
256
302
|
|
|
257
303
|
#######################################
|
|
304
|
+
@_handle_persistent_rate_limit
|
|
258
305
|
@retry(
|
|
259
306
|
stop=stop_after_attempt(3),
|
|
260
307
|
wait=wait_random_exponential(max=5),
|
|
@@ -273,6 +320,8 @@ class API:
|
|
|
273
320
|
|
|
274
321
|
Raises:
|
|
275
322
|
|
|
323
|
+
AuthenticationError: If the request fails with a 401 status code.
|
|
324
|
+
RateLimitError: If the request fails with persistent 429 status code after retries.
|
|
276
325
|
HTTPError: If the DELETE request fails with a non-200 status code.
|
|
277
326
|
"""
|
|
278
327
|
timeout = timeout or self._default_timeout
|
|
@@ -282,11 +331,7 @@ class API:
|
|
|
282
331
|
response.status_code not in [200, 204]
|
|
283
332
|
and response.status_code not in expected_statuscodes
|
|
284
333
|
):
|
|
285
|
-
|
|
286
|
-
raise HTTPError(
|
|
287
|
-
f"Retriable error {response.status_code}", response=response
|
|
288
|
-
)
|
|
289
|
-
response.raise_for_status()
|
|
334
|
+
_handle_http_error(response)
|
|
290
335
|
|
|
291
336
|
|
|
292
337
|
#################################################
|
|
@@ -219,6 +219,8 @@ def hybrid_search(
|
|
|
219
219
|
Returns:
|
|
220
220
|
List[HybridSearchResult]: The list of hybrid search results.
|
|
221
221
|
"""
|
|
222
|
+
MAX_QUERY_LENGTH = 512
|
|
223
|
+
|
|
222
224
|
# Input validation
|
|
223
225
|
if query is None:
|
|
224
226
|
raise ValueError("The 'query' argument is required.")
|
|
@@ -227,8 +229,8 @@ def hybrid_search(
|
|
|
227
229
|
query = query.strip()
|
|
228
230
|
if len(query) == 0:
|
|
229
231
|
raise ValueError("The 'query' cannot be empty.")
|
|
230
|
-
if len(query) >
|
|
231
|
-
raise ValueError("The 'query' must be
|
|
232
|
+
if len(query) > MAX_QUERY_LENGTH:
|
|
233
|
+
raise ValueError(f"The 'query' must be {MAX_QUERY_LENGTH} characters or less.")
|
|
232
234
|
|
|
233
235
|
body = {
|
|
234
236
|
"query": query,
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Copyright 2024-2025 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 custom exception classes for the DeepSights API.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DeepSightsError(Exception):
|
|
21
|
+
"""Base exception class for all DeepSights API errors."""
|
|
22
|
+
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AuthenticationError(DeepSightsError):
|
|
27
|
+
"""Raised when authentication fails due to invalid credentials or permissions."""
|
|
28
|
+
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class RateLimitError(DeepSightsError):
|
|
33
|
+
"""Raised when API rate limits are exceeded."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, message: str = "Rate limit exceeded", retry_after: float = None):
|
|
36
|
+
"""
|
|
37
|
+
Initialize RateLimitError.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
message: Error message describing the rate limit violation
|
|
41
|
+
retry_after: Optional seconds to wait before retrying
|
|
42
|
+
"""
|
|
43
|
+
super().__init__(message)
|
|
44
|
+
self.retry_after = retry_after
|
{deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/userclient/resources/answersV2/answerV2.py
RENAMED
|
@@ -16,7 +16,10 @@
|
|
|
16
16
|
This module contains the functions to retrieve reports from the DeepSights self.
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
|
+
from ratelimit import RateLimitException, limits
|
|
20
|
+
|
|
19
21
|
from deepsights.api import APIResource
|
|
22
|
+
from deepsights.exceptions import RateLimitError
|
|
20
23
|
from deepsights.userclient.resources.answersV2._model import AnswerV2
|
|
21
24
|
from deepsights.utils import (
|
|
22
25
|
PollingFailedError,
|
|
@@ -32,6 +35,7 @@ class AnswerV2Resource(APIResource):
|
|
|
32
35
|
"""
|
|
33
36
|
|
|
34
37
|
#################################################
|
|
38
|
+
@limits(calls=10, period=60)
|
|
35
39
|
def create(self, question: str) -> str:
|
|
36
40
|
"""
|
|
37
41
|
Creates a new answer V2 by submitting a question to the DeepSights self.
|
|
@@ -43,12 +47,21 @@ class AnswerV2Resource(APIResource):
|
|
|
43
47
|
Returns:
|
|
44
48
|
|
|
45
49
|
str: The ID of the created answer's minion job.
|
|
46
|
-
"""
|
|
47
50
|
|
|
48
|
-
|
|
49
|
-
response = self.api.post("/end-user-gateway-service/answers-v2", body=body)
|
|
51
|
+
Raises:
|
|
50
52
|
|
|
51
|
-
|
|
53
|
+
RateLimitError: If the rate limit of 10 calls per 60 seconds is exceeded.
|
|
54
|
+
"""
|
|
55
|
+
try:
|
|
56
|
+
body = {"input": question}
|
|
57
|
+
response = self.api.post("/end-user-gateway-service/answers-v2", body=body)
|
|
58
|
+
|
|
59
|
+
return response["answer_v2"]["minion_job"]["id"]
|
|
60
|
+
except RateLimitException as e:
|
|
61
|
+
raise RateLimitError(
|
|
62
|
+
"Answer creation rate limit exceeded (10 calls per 60 seconds). Please wait before making another request.",
|
|
63
|
+
retry_after=60,
|
|
64
|
+
) from e
|
|
52
65
|
|
|
53
66
|
#################################################
|
|
54
67
|
def wait_for_answer(self, answer_id: str, timeout=90) -> AnswerV2:
|
|
@@ -148,6 +161,7 @@ class AnswerV2Resource(APIResource):
|
|
|
148
161
|
)
|
|
149
162
|
|
|
150
163
|
#################################################
|
|
164
|
+
@limits(calls=10, period=60)
|
|
151
165
|
def create_and_wait(self, question: str, timeout=90) -> AnswerV2:
|
|
152
166
|
"""
|
|
153
167
|
Submits a question to the DeepSights API and waits for the answer to complete.
|
|
@@ -163,7 +177,14 @@ class AnswerV2Resource(APIResource):
|
|
|
163
177
|
|
|
164
178
|
Raises:
|
|
165
179
|
|
|
180
|
+
RateLimitError: If the rate limit of 10 calls per 60 seconds is exceeded.
|
|
166
181
|
PollingTimeoutError: If the answer fails to complete within timeout.
|
|
167
182
|
PollingFailedError: If the answer fails to complete.
|
|
168
183
|
"""
|
|
169
|
-
|
|
184
|
+
try:
|
|
185
|
+
return self.wait_for_answer(self.create(question), timeout=timeout)
|
|
186
|
+
except RateLimitException as e:
|
|
187
|
+
raise RateLimitError(
|
|
188
|
+
"Answer creation rate limit exceeded (10 calls per 60 seconds). Please wait before making another request.",
|
|
189
|
+
retry_after=60,
|
|
190
|
+
) from e
|
{deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/userclient/resources/documents/documents.py
RENAMED
|
@@ -48,6 +48,8 @@ class DocumentResource(APIResource):
|
|
|
48
48
|
Represents a resource for performing hybrid searches via the DeepSights API.
|
|
49
49
|
"""
|
|
50
50
|
|
|
51
|
+
MAX_QUERY_LENGTH = 512
|
|
52
|
+
|
|
51
53
|
#################################################
|
|
52
54
|
def search(
|
|
53
55
|
self, query: str, extended_search: bool = False
|
|
@@ -68,8 +70,10 @@ class DocumentResource(APIResource):
|
|
|
68
70
|
query = query.strip()
|
|
69
71
|
if len(query) == 0:
|
|
70
72
|
raise ValueError("The 'query' cannot be empty.")
|
|
71
|
-
if len(query) >
|
|
72
|
-
raise ValueError(
|
|
73
|
+
if len(query) > self.MAX_QUERY_LENGTH:
|
|
74
|
+
raise ValueError(
|
|
75
|
+
f"The 'query' must be {self.MAX_QUERY_LENGTH} characters or less."
|
|
76
|
+
)
|
|
73
77
|
|
|
74
78
|
body = {
|
|
75
79
|
"query": query,
|
|
@@ -171,7 +175,7 @@ class DocumentResource(APIResource):
|
|
|
171
175
|
# load uncached document pages
|
|
172
176
|
def _load_document_page(page_id: str) -> DocumentPage:
|
|
173
177
|
result = self.api.get(
|
|
174
|
-
f"/end-user-gateway-service/pages/{page_id}", timeout=5
|
|
178
|
+
f"/end-user-gateway-service/artifacts/pages/{page_id}", timeout=5
|
|
175
179
|
)
|
|
176
180
|
|
|
177
181
|
# map the document page
|
{deepsights_api-1.3.2 → deepsights_api-1.3.4}/deepsights/userclient/resources/reports/report.py
RENAMED
|
@@ -16,7 +16,10 @@
|
|
|
16
16
|
This module contains the functions to retrieve reports from the DeepSights self.
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
|
+
from ratelimit import RateLimitException, limits
|
|
20
|
+
|
|
19
21
|
from deepsights.api import APIResource
|
|
22
|
+
from deepsights.exceptions import RateLimitError
|
|
20
23
|
from deepsights.userclient.resources.reports._model import Report
|
|
21
24
|
from deepsights.utils import (
|
|
22
25
|
PollingFailedError,
|
|
@@ -32,6 +35,7 @@ class ReportResource(APIResource):
|
|
|
32
35
|
"""
|
|
33
36
|
|
|
34
37
|
#################################################
|
|
38
|
+
@limits(calls=3, period=60)
|
|
35
39
|
def create(self, question: str) -> str:
|
|
36
40
|
"""
|
|
37
41
|
Creates a new report by submitting a question to the DeepSights self.
|
|
@@ -43,12 +47,23 @@ class ReportResource(APIResource):
|
|
|
43
47
|
Returns:
|
|
44
48
|
|
|
45
49
|
str: The ID of the created report's minion job.
|
|
46
|
-
"""
|
|
47
50
|
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
Raises:
|
|
52
|
+
|
|
53
|
+
RateLimitError: If the rate limit of 3 calls per 60 seconds is exceeded.
|
|
54
|
+
"""
|
|
55
|
+
try:
|
|
56
|
+
body = {"input": question}
|
|
57
|
+
response = self.api.post(
|
|
58
|
+
"/end-user-gateway-service/desk-researches", body=body
|
|
59
|
+
)
|
|
50
60
|
|
|
51
|
-
|
|
61
|
+
return response["desk_research"]["minion_job"]["id"]
|
|
62
|
+
except RateLimitException as e:
|
|
63
|
+
raise RateLimitError(
|
|
64
|
+
"Report creation rate limit exceeded (3 calls per 60 seconds). Please wait before making another request.",
|
|
65
|
+
retry_after=60,
|
|
66
|
+
) from e
|
|
52
67
|
|
|
53
68
|
#################################################
|
|
54
69
|
def wait_for_report(self, report_id: str, timeout=600) -> Report:
|