newsdataapi 0.3.1__tar.gz → 0.3.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.
Files changed (24) hide show
  1. {newsdataapi-0.3.1 → newsdataapi-0.3.2}/PKG-INFO +1 -1
  2. {newsdataapi-0.3.1 → newsdataapi-0.3.2}/src/newsdataapi/__init__.py +1 -1
  3. {newsdataapi-0.3.1 → newsdataapi-0.3.2}/src/newsdataapi/client.py +18 -2
  4. {newsdataapi-0.3.1 → newsdataapi-0.3.2}/src/newsdataapi/websocket.py +2 -0
  5. {newsdataapi-0.3.1 → newsdataapi-0.3.2}/tests/test_unit.py +73 -1
  6. {newsdataapi-0.3.1 → newsdataapi-0.3.2}/.github/workflows/ci.yml +0 -0
  7. {newsdataapi-0.3.1 → newsdataapi-0.3.2}/.github/workflows/publish.yml +0 -0
  8. {newsdataapi-0.3.1 → newsdataapi-0.3.2}/.gitignore +0 -0
  9. {newsdataapi-0.3.1 → newsdataapi-0.3.2}/.python-version +0 -0
  10. {newsdataapi-0.3.1 → newsdataapi-0.3.2}/LICENSE +0 -0
  11. {newsdataapi-0.3.1 → newsdataapi-0.3.2}/README.md +0 -0
  12. {newsdataapi-0.3.1 → newsdataapi-0.3.2}/examples/basic_usage.py +0 -0
  13. {newsdataapi-0.3.1 → newsdataapi-0.3.2}/examples/websocket_usage.py +0 -0
  14. {newsdataapi-0.3.1 → newsdataapi-0.3.2}/newsdata-logo.png +0 -0
  15. {newsdataapi-0.3.1 → newsdataapi-0.3.2}/pyproject.toml +0 -0
  16. {newsdataapi-0.3.1 → newsdataapi-0.3.2}/src/newsdataapi/constants.py +0 -0
  17. {newsdataapi-0.3.1 → newsdataapi-0.3.2}/src/newsdataapi/csv_export.py +0 -0
  18. {newsdataapi-0.3.1 → newsdataapi-0.3.2}/src/newsdataapi/exceptions.py +0 -0
  19. {newsdataapi-0.3.1 → newsdataapi-0.3.2}/src/newsdataapi/py.typed +0 -0
  20. {newsdataapi-0.3.1 → newsdataapi-0.3.2}/tests/__init__.py +0 -0
  21. {newsdataapi-0.3.1 → newsdataapi-0.3.2}/tests/conftest.py +0 -0
  22. {newsdataapi-0.3.1 → newsdataapi-0.3.2}/tests/test_integration.py +0 -0
  23. {newsdataapi-0.3.1 → newsdataapi-0.3.2}/tests/test_websocket.py +0 -0
  24. {newsdataapi-0.3.1 → newsdataapi-0.3.2}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: newsdataapi
3
- Version: 0.3.1
3
+ Version: 0.3.2
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.1"
18
+ __version__ = "0.3.2"
19
19
 
20
20
  __all__ = [
21
21
  "NewsDataApiClient",
@@ -47,6 +47,7 @@ _STRING_PARAMS = frozenset(
47
47
  "domain",
48
48
  "domainurl",
49
49
  "excludedomain",
50
+ "excludedomainurl",
50
51
  "timezone",
51
52
  "from_date",
52
53
  "to_date",
@@ -81,7 +82,7 @@ _MUTEX_GROUPS: tuple[tuple[str, ...], ...] = (
81
82
  ("country", "excludecountry"),
82
83
  ("category", "excludecategory"),
83
84
  ("language", "excludelanguage"),
84
- ("domain", "domainurl", "excludedomain"),
85
+ ("domain", "domainurl", "excludedomain", "excludedomainurl"),
85
86
  )
86
87
 
87
88
  _QUOTA_EXHAUSTED_CODES = frozenset({"ApiKeyLimitExceeded", "ApiLimitExceeded"})
@@ -103,7 +104,8 @@ def _validate_params(user_params: Mapping[str, Any]) -> dict[str, Any]:
103
104
  * Server-side mutex groups (``q``/``qInTitle``/``qInMeta``,
104
105
  ``country``/``excludecountry``, ``category``/``excludecategory``,
105
106
  ``language``/``excludelanguage``, and
106
- ``domain``/``domainurl``/``excludedomain``) are enforced client-side;
107
+ ``domain``/``domainurl``/``excludedomain``/``excludedomainurl``) are
108
+ enforced client-side;
107
109
  setting more than one from any group raises
108
110
  ``NewsdataValidationError`` before the request leaves.
109
111
  * ``sentiment_score`` requires ``sentiment`` to be set; passing
@@ -480,6 +482,7 @@ class NewsDataApiClient:
480
482
  domain: str | list[str] | None = None,
481
483
  domainurl: str | list[str] | None = None,
482
484
  excludedomain: str | list[str] | None = None,
485
+ excludedomainurl: str | list[str] | None = None,
483
486
  prioritydomain: str | None = None,
484
487
  timeframe: int | str | None = None,
485
488
  timezone: str | None = None,
@@ -528,6 +531,7 @@ class NewsDataApiClient:
528
531
  "domain": domain,
529
532
  "domainurl": domainurl,
530
533
  "excludedomain": excludedomain,
534
+ "excludedomainurl": excludedomainurl,
531
535
  "prioritydomain": prioritydomain,
532
536
  "timeframe": str(timeframe) if timeframe is not None else None,
533
537
  "timezone": timezone,
@@ -571,6 +575,7 @@ class NewsDataApiClient:
571
575
  domain: str | list[str] | None = None,
572
576
  domainurl: str | list[str] | None = None,
573
577
  excludedomain: str | list[str] | None = None,
578
+ excludedomainurl: str | list[str] | None = None,
574
579
  prioritydomain: str | None = None,
575
580
  timezone: str | None = None,
576
581
  size: int | None = None,
@@ -617,6 +622,7 @@ class NewsDataApiClient:
617
622
  "domain": domain,
618
623
  "domainurl": domainurl,
619
624
  "excludedomain": excludedomain,
625
+ "excludedomainurl": excludedomainurl,
620
626
  "prioritydomain": prioritydomain,
621
627
  "timezone": timezone,
622
628
  "size": size,
@@ -684,6 +690,7 @@ class NewsDataApiClient:
684
690
  domain: str | list[str] | None = None,
685
691
  domainurl: str | list[str] | None = None,
686
692
  excludedomain: str | list[str] | None = None,
693
+ excludedomainurl: str | list[str] | None = None,
687
694
  prioritydomain: str | None = None,
688
695
  timeframe: int | str | None = None,
689
696
  timezone: str | None = None,
@@ -723,6 +730,7 @@ class NewsDataApiClient:
723
730
  "domain": domain,
724
731
  "domainurl": domainurl,
725
732
  "excludedomain": excludedomain,
733
+ "excludedomainurl": excludedomainurl,
726
734
  "prioritydomain": prioritydomain,
727
735
  "timeframe": str(timeframe) if timeframe is not None else None,
728
736
  "timezone": timezone,
@@ -762,6 +770,7 @@ class NewsDataApiClient:
762
770
  domain: str | list[str] | None = None,
763
771
  domainurl: str | list[str] | None = None,
764
772
  excludedomain: str | list[str] | None = None,
773
+ excludedomainurl: str | list[str] | None = None,
765
774
  language: str | list[str] | None = None,
766
775
  excludelanguage: str | list[str] | None = None,
767
776
  prioritydomain: str | None = None,
@@ -807,6 +816,7 @@ class NewsDataApiClient:
807
816
  "domain": domain,
808
817
  "domainurl": domainurl,
809
818
  "excludedomain": excludedomain,
819
+ "excludedomainurl": excludedomainurl,
810
820
  "language": language,
811
821
  "excludelanguage": excludelanguage,
812
822
  "prioritydomain": prioritydomain,
@@ -854,6 +864,7 @@ class NewsDataApiClient:
854
864
  domain: str | list[str] | None = None,
855
865
  domainurl: str | list[str] | None = None,
856
866
  excludedomain: str | list[str] | None = None,
867
+ excludedomainurl: str | list[str] | None = None,
857
868
  full_content: bool | None = None,
858
869
  image: bool | None = None,
859
870
  video: bool | None = None,
@@ -901,6 +912,7 @@ class NewsDataApiClient:
901
912
  "domain": domain,
902
913
  "domainurl": domainurl,
903
914
  "excludedomain": excludedomain,
915
+ "excludedomainurl": excludedomainurl,
904
916
  "full_content": full_content,
905
917
  "image": image,
906
918
  "video": video,
@@ -940,6 +952,7 @@ class NewsDataApiClient:
940
952
  domain: str | list[str] | None = None,
941
953
  domainurl: str | list[str] | None = None,
942
954
  excludedomain: str | list[str] | None = None,
955
+ excludedomainurl: str | list[str] | None = None,
943
956
  full_content: bool | None = None,
944
957
  image: bool | None = None,
945
958
  video: bool | None = None,
@@ -978,6 +991,7 @@ class NewsDataApiClient:
978
991
  "domain": domain,
979
992
  "domainurl": domainurl,
980
993
  "excludedomain": excludedomain,
994
+ "excludedomainurl": excludedomainurl,
981
995
  "full_content": full_content,
982
996
  "image": image,
983
997
  "video": video,
@@ -1011,6 +1025,7 @@ class NewsDataApiClient:
1011
1025
  domain: str | list[str] | None = None,
1012
1026
  domainurl: str | list[str] | None = None,
1013
1027
  excludedomain: str | list[str] | None = None,
1028
+ excludedomainurl: str | list[str] | None = None,
1014
1029
  language: str | list[str] | None = None,
1015
1030
  excludelanguage: str | list[str] | None = None,
1016
1031
  full_content: bool | None = None,
@@ -1055,6 +1070,7 @@ class NewsDataApiClient:
1055
1070
  "domain": domain,
1056
1071
  "domainurl": domainurl,
1057
1072
  "excludedomain": excludedomain,
1073
+ "excludedomainurl": excludedomainurl,
1058
1074
  "language": language,
1059
1075
  "excludelanguage": excludelanguage,
1060
1076
  "full_content": full_content,
@@ -177,6 +177,7 @@ class NewsDataApiWebSocket:
177
177
  domain: str | list[str] | None = None,
178
178
  domainurl: str | list[str] | None = None,
179
179
  excludedomain: str | list[str] | None = None,
180
+ excludedomainurl: str | list[str] | None = None,
180
181
  prioritydomain: str | None = None,
181
182
  timezone: str | None = None,
182
183
  full_content: bool | None = None,
@@ -212,6 +213,7 @@ class NewsDataApiWebSocket:
212
213
  "domain": domain,
213
214
  "domainurl": domainurl,
214
215
  "excludedomain": excludedomain,
216
+ "excludedomainurl": excludedomainurl,
215
217
  "prioritydomain": prioritydomain,
216
218
  "timezone": timezone,
217
219
  "full_content": full_content,
@@ -148,13 +148,23 @@ def test_validate_size_negative_rejected() -> None:
148
148
  ({"country": "us", "excludecountry": "gb"}, "country"),
149
149
  ({"category": "business", "excludecategory": "sports"}, "category"),
150
150
  ({"language": "en", "excludelanguage": "fr"}, "language"),
151
- # Domain 3-way mutex (any two of domain/domainurl/excludedomain)
151
+ # Domain 4-way mutex (any two of
152
+ # domain/domainurl/excludedomain/excludedomainurl)
152
153
  ({"domain": "cnn.com", "domainurl": "https://bbc.com"}, "domain"),
153
154
  ({"domain": "cnn.com", "excludedomain": "fox.com"}, "domain"),
154
155
  (
155
156
  {"domainurl": "https://bbc.com", "excludedomain": "fox.com"},
156
157
  "domainurl",
157
158
  ),
159
+ ({"domain": "cnn.com", "excludedomainurl": "fox.com"}, "domain"),
160
+ (
161
+ {"domainurl": "https://bbc.com", "excludedomainurl": "fox.com"},
162
+ "domainurl",
163
+ ),
164
+ (
165
+ {"excludedomain": "cnn.com", "excludedomainurl": "fox.com"},
166
+ "excludedomain",
167
+ ),
158
168
  (
159
169
  {
160
170
  "domain": "cnn.com",
@@ -1285,6 +1295,51 @@ def test_count_endpoint_url_resolves(
1285
1295
  assert len(mocked_responses.calls) == 1
1286
1296
 
1287
1297
 
1298
+ @pytest.mark.parametrize(
1299
+ ("method_name", "endpoint_path", "args"),
1300
+ [
1301
+ ("latest_api", "latest", ()),
1302
+ ("archive_api", "archive", ()),
1303
+ ("crypto_api", "crypto", ()),
1304
+ ("market_api", "market", ()),
1305
+ ("count_api", "count", ("2024-01-01", "2024-01-31")),
1306
+ ("crypto_count_api", "crypto/count", ("2024-01-01", "2024-01-31")),
1307
+ ("market_count_api", "market/count", ("2024-01-01", "2024-01-31")),
1308
+ ],
1309
+ )
1310
+ def test_excludedomainurl_sent_as_csv(
1311
+ client: NewsDataApiClient,
1312
+ mocked_responses: responses.RequestsMock,
1313
+ method_name: str,
1314
+ endpoint_path: str,
1315
+ args: tuple[str, ...],
1316
+ ) -> None:
1317
+ mocked_responses.get(
1318
+ f"https://newsdata.io/api/1/{endpoint_path}",
1319
+ json={"status": "success", "results": []},
1320
+ status=200,
1321
+ )
1322
+ getattr(client, method_name)(*args, excludedomainurl=["bbc.com", "cnn.com"])
1323
+ sent_url = mocked_responses.calls[0].request.url
1324
+ assert sent_url is not None
1325
+ assert "excludedomainurl=bbc.com%2Ccnn.com" in sent_url
1326
+
1327
+
1328
+ def test_raw_query_accepts_excludedomainurl(
1329
+ client: NewsDataApiClient,
1330
+ mocked_responses: responses.RequestsMock,
1331
+ ) -> None:
1332
+ mocked_responses.get(
1333
+ LATEST_URL,
1334
+ json={"status": "success", "results": []},
1335
+ status=200,
1336
+ )
1337
+ client.latest_api(raw_query="excludedomainurl=bbc.com")
1338
+ sent_url = mocked_responses.calls[0].request.url
1339
+ assert sent_url is not None
1340
+ assert "excludedomainurl=bbc.com" in sent_url
1341
+
1342
+
1288
1343
  # ===========================================================================
1289
1344
  # WebSocket query management (register / fetch / delete)
1290
1345
  # ===========================================================================
@@ -1367,6 +1422,23 @@ def test_websocket_register_raw_query_rejects_unknown_param(
1367
1422
  NewsDataApiWebSocket(client).websocket_register(raw_query="q=pizza&size=10")
1368
1423
 
1369
1424
 
1425
+ def test_websocket_register_sends_excludedomainurl(
1426
+ client: NewsDataApiClient,
1427
+ mocked_responses: responses.RequestsMock,
1428
+ ) -> None:
1429
+ mocked_responses.post(
1430
+ WS_REGISTER_URL,
1431
+ json={"status": "success", "results": {"registration_id": "abc"}},
1432
+ status=200,
1433
+ )
1434
+ NewsDataApiWebSocket(client).websocket_register(
1435
+ q="pizza", excludedomainurl=["bbc.com", "cnn.com"]
1436
+ )
1437
+ sent_url = mocked_responses.calls[0].request.url
1438
+ assert sent_url is not None
1439
+ assert "excludedomainurl=bbc.com%2Ccnn.com" in sent_url
1440
+
1441
+
1370
1442
  def test_websocket_register_duplicate_409(
1371
1443
  client: NewsDataApiClient,
1372
1444
  mocked_responses: responses.RequestsMock,
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes