newsdataapi 0.3.0__tar.gz → 0.3.1__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 (24) hide show
  1. {newsdataapi-0.3.0 → newsdataapi-0.3.1}/PKG-INFO +1 -1
  2. {newsdataapi-0.3.0 → newsdataapi-0.3.1}/src/newsdataapi/__init__.py +1 -1
  3. {newsdataapi-0.3.0 → newsdataapi-0.3.1}/src/newsdataapi/client.py +17 -1
  4. {newsdataapi-0.3.0 → newsdataapi-0.3.1}/tests/test_unit.py +16 -0
  5. {newsdataapi-0.3.0 → newsdataapi-0.3.1}/.github/workflows/ci.yml +0 -0
  6. {newsdataapi-0.3.0 → newsdataapi-0.3.1}/.github/workflows/publish.yml +0 -0
  7. {newsdataapi-0.3.0 → newsdataapi-0.3.1}/.gitignore +0 -0
  8. {newsdataapi-0.3.0 → newsdataapi-0.3.1}/.python-version +0 -0
  9. {newsdataapi-0.3.0 → newsdataapi-0.3.1}/LICENSE +0 -0
  10. {newsdataapi-0.3.0 → newsdataapi-0.3.1}/README.md +0 -0
  11. {newsdataapi-0.3.0 → newsdataapi-0.3.1}/examples/basic_usage.py +0 -0
  12. {newsdataapi-0.3.0 → newsdataapi-0.3.1}/examples/websocket_usage.py +0 -0
  13. {newsdataapi-0.3.0 → newsdataapi-0.3.1}/newsdata-logo.png +0 -0
  14. {newsdataapi-0.3.0 → newsdataapi-0.3.1}/pyproject.toml +0 -0
  15. {newsdataapi-0.3.0 → newsdataapi-0.3.1}/src/newsdataapi/constants.py +0 -0
  16. {newsdataapi-0.3.0 → newsdataapi-0.3.1}/src/newsdataapi/csv_export.py +0 -0
  17. {newsdataapi-0.3.0 → newsdataapi-0.3.1}/src/newsdataapi/exceptions.py +0 -0
  18. {newsdataapi-0.3.0 → newsdataapi-0.3.1}/src/newsdataapi/py.typed +0 -0
  19. {newsdataapi-0.3.0 → newsdataapi-0.3.1}/src/newsdataapi/websocket.py +0 -0
  20. {newsdataapi-0.3.0 → newsdataapi-0.3.1}/tests/__init__.py +0 -0
  21. {newsdataapi-0.3.0 → newsdataapi-0.3.1}/tests/conftest.py +0 -0
  22. {newsdataapi-0.3.0 → newsdataapi-0.3.1}/tests/test_integration.py +0 -0
  23. {newsdataapi-0.3.0 → newsdataapi-0.3.1}/tests/test_websocket.py +0 -0
  24. {newsdataapi-0.3.0 → newsdataapi-0.3.1}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: newsdataapi
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Official Python client (SDK) for the Newsdata.io News API — fetch real-time, historical, crypto, and stock-market news via REST, with validation, retries, pagination, and typed exceptions.
5
5
  Project-URL: Homepage, https://newsdata.io
6
6
  Project-URL: Documentation, https://newsdata.io/documentation
@@ -15,7 +15,7 @@ from .exceptions import (
15
15
  )
16
16
  from .websocket import NewsDataApiWebSocket
17
17
 
18
- __version__ = "0.3.0"
18
+ __version__ = "0.3.1"
19
19
 
20
20
  __all__ = [
21
21
  "NewsDataApiClient",
@@ -84,6 +84,8 @@ _MUTEX_GROUPS: tuple[tuple[str, ...], ...] = (
84
84
  ("domain", "domainurl", "excludedomain"),
85
85
  )
86
86
 
87
+ _QUOTA_EXHAUSTED_CODES = frozenset({"ApiKeyLimitExceeded", "ApiLimitExceeded"})
88
+
87
89
 
88
90
  def _validate_params(user_params: Mapping[str, Any]) -> dict[str, Any]:
89
91
  """Validate and normalize user-provided endpoint parameters.
@@ -298,6 +300,17 @@ def _parse_retry_after(value: str | None) -> int | None:
298
300
  return None
299
301
 
300
302
 
303
+ def _extract_error_code(body: Any) -> str | None:
304
+ """Return the API error code from an error response body, if present."""
305
+ if isinstance(body, dict):
306
+ results = body.get("results")
307
+ if isinstance(results, dict):
308
+ code = results.get("code")
309
+ if isinstance(code, str):
310
+ return code
311
+ return None
312
+
313
+
301
314
  def _redact_url(url: str, param: str = "apikey") -> str:
302
315
  """Return ``url`` with the value of ``param`` replaced by ``REDACTED``.
303
316
 
@@ -1201,7 +1214,10 @@ class NewsDataApiClient:
1201
1214
  # Rate limit.
1202
1215
  if response.status_code == 429:
1203
1216
  retry_after = _parse_retry_after(response.headers.get("Retry-After"))
1204
- if attempt >= self.max_retries:
1217
+ if (
1218
+ _extract_error_code(body) in _QUOTA_EXHAUSTED_CODES
1219
+ or attempt >= self.max_retries
1220
+ ):
1205
1221
  raise NewsdataRateLimitError(
1206
1222
  body,
1207
1223
  status_code=429,
@@ -823,6 +823,22 @@ def test_429_exhausted_raises_rate_limit_with_retry_after(
823
823
  assert exc_info.value.retry_after == 5
824
824
 
825
825
 
826
+ @pytest.mark.parametrize("code", ["ApiKeyLimitExceeded", "ApiLimitExceeded"])
827
+ def test_429_quota_exhausted_raises_without_retry(
828
+ code: str,
829
+ client: NewsDataApiClient,
830
+ mocked_responses: responses.RequestsMock,
831
+ no_sleep: None,
832
+ ) -> None:
833
+ """A 429 whose error code means exhausted credits is never retried."""
834
+ body = {"status": "error", "results": {"message": "limit", "code": code}}
835
+ mocked_responses.get(LATEST_URL, json=body, status=429)
836
+ with pytest.raises(NewsdataRateLimitError) as exc_info:
837
+ client.latest_api()
838
+ assert len(mocked_responses.calls) == 1
839
+ assert exc_info.value.response_body == body
840
+
841
+
826
842
  def test_500_then_200_succeeds(
827
843
  client: NewsDataApiClient,
828
844
  mocked_responses: responses.RequestsMock,
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes