newsdataapi 0.3.2__tar.gz → 0.3.3__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.
- {newsdataapi-0.3.2 → newsdataapi-0.3.3}/.github/workflows/ci.yml +1 -1
- {newsdataapi-0.3.2 → newsdataapi-0.3.3}/PKG-INFO +7 -5
- {newsdataapi-0.3.2 → newsdataapi-0.3.3}/README.md +2 -2
- {newsdataapi-0.3.2 → newsdataapi-0.3.3}/pyproject.toml +9 -4
- {newsdataapi-0.3.2 → newsdataapi-0.3.3}/src/newsdataapi/__init__.py +1 -1
- {newsdataapi-0.3.2 → newsdataapi-0.3.3}/src/newsdataapi/websocket.py +19 -4
- {newsdataapi-0.3.2 → newsdataapi-0.3.3}/tests/conftest.py +4 -0
- {newsdataapi-0.3.2 → newsdataapi-0.3.3}/tests/test_unit.py +30 -0
- {newsdataapi-0.3.2 → newsdataapi-0.3.3}/uv.lock +639 -33
- {newsdataapi-0.3.2 → newsdataapi-0.3.3}/.github/workflows/publish.yml +0 -0
- {newsdataapi-0.3.2 → newsdataapi-0.3.3}/.gitignore +0 -0
- {newsdataapi-0.3.2 → newsdataapi-0.3.3}/.python-version +0 -0
- {newsdataapi-0.3.2 → newsdataapi-0.3.3}/LICENSE +0 -0
- {newsdataapi-0.3.2 → newsdataapi-0.3.3}/examples/basic_usage.py +0 -0
- {newsdataapi-0.3.2 → newsdataapi-0.3.3}/examples/websocket_usage.py +0 -0
- {newsdataapi-0.3.2 → newsdataapi-0.3.3}/newsdata-logo.png +0 -0
- {newsdataapi-0.3.2 → newsdataapi-0.3.3}/src/newsdataapi/client.py +0 -0
- {newsdataapi-0.3.2 → newsdataapi-0.3.3}/src/newsdataapi/constants.py +0 -0
- {newsdataapi-0.3.2 → newsdataapi-0.3.3}/src/newsdataapi/csv_export.py +0 -0
- {newsdataapi-0.3.2 → newsdataapi-0.3.3}/src/newsdataapi/exceptions.py +0 -0
- {newsdataapi-0.3.2 → newsdataapi-0.3.3}/src/newsdataapi/py.typed +0 -0
- {newsdataapi-0.3.2 → newsdataapi-0.3.3}/tests/__init__.py +0 -0
- {newsdataapi-0.3.2 → newsdataapi-0.3.3}/tests/test_integration.py +0 -0
- {newsdataapi-0.3.2 → newsdataapi-0.3.3}/tests/test_websocket.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: newsdataapi
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
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
|
|
@@ -35,6 +35,8 @@ Classifier: Intended Audience :: Developers
|
|
|
35
35
|
Classifier: License :: OSI Approved :: MIT License
|
|
36
36
|
Classifier: Operating System :: OS Independent
|
|
37
37
|
Classifier: Programming Language :: Python :: 3
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
38
40
|
Classifier: Programming Language :: Python :: 3.10
|
|
39
41
|
Classifier: Programming Language :: Python :: 3.11
|
|
40
42
|
Classifier: Programming Language :: Python :: 3.12
|
|
@@ -43,9 +45,9 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
43
45
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
44
46
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
45
47
|
Classifier: Typing :: Typed
|
|
46
|
-
Requires-Python: >=3.
|
|
48
|
+
Requires-Python: >=3.8
|
|
47
49
|
Requires-Dist: requests<3,>=2.25
|
|
48
|
-
Requires-Dist: websockets<17,>=16
|
|
50
|
+
Requires-Dist: websockets<17,>=16; python_version >= '3.10'
|
|
49
51
|
Description-Content-Type: text/markdown
|
|
50
52
|
|
|
51
53
|
<div align="center">
|
|
@@ -77,7 +79,7 @@ If you use [uv](https://github.com/astral-sh/uv):
|
|
|
77
79
|
uv add newsdataapi
|
|
78
80
|
```
|
|
79
81
|
|
|
80
|
-
Supports Python 3.
|
|
82
|
+
Supports Python 3.8 through 3.14. The runtime dependencies are `requests` (REST) and `websockets` (real-time streaming). `NewsDataApiWebSocket` requires Python 3.10 or newer, so `websockets` is only installed there; on 3.8 and 3.9 the REST client works as usual.
|
|
81
83
|
|
|
82
84
|
## Quickstart
|
|
83
85
|
|
|
@@ -139,7 +141,7 @@ for page in client.latest_api(q="news", paginate=True, max_pages=5):
|
|
|
139
141
|
|
|
140
142
|
## Real-time news (WebSocket)
|
|
141
143
|
|
|
142
|
-
Register a query first — the returned `registration_id` identifies it from then on:
|
|
144
|
+
Requires Python 3.10 or newer. Register a query first — the returned `registration_id` identifies it from then on:
|
|
143
145
|
|
|
144
146
|
```python
|
|
145
147
|
from newsdataapi import NewsDataApiClient, NewsDataApiWebSocket
|
|
@@ -27,7 +27,7 @@ If you use [uv](https://github.com/astral-sh/uv):
|
|
|
27
27
|
uv add newsdataapi
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
Supports Python 3.
|
|
30
|
+
Supports Python 3.8 through 3.14. The runtime dependencies are `requests` (REST) and `websockets` (real-time streaming). `NewsDataApiWebSocket` requires Python 3.10 or newer, so `websockets` is only installed there; on 3.8 and 3.9 the REST client works as usual.
|
|
31
31
|
|
|
32
32
|
## Quickstart
|
|
33
33
|
|
|
@@ -89,7 +89,7 @@ for page in client.latest_api(q="news", paginate=True, max_pages=5):
|
|
|
89
89
|
|
|
90
90
|
## Real-time news (WebSocket)
|
|
91
91
|
|
|
92
|
-
Register a query first — the returned `registration_id` identifies it from then on:
|
|
92
|
+
Requires Python 3.10 or newer. Register a query first — the returned `registration_id` identifies it from then on:
|
|
93
93
|
|
|
94
94
|
```python
|
|
95
95
|
from newsdataapi import NewsDataApiClient, NewsDataApiWebSocket
|
|
@@ -5,11 +5,13 @@ dynamic = ["version"]
|
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { file = "LICENSE" }
|
|
7
7
|
authors = [{ name = "NewsData.io", email = "contact@newsdata.io" }]
|
|
8
|
-
requires-python = ">=3.
|
|
8
|
+
requires-python = ">=3.8"
|
|
9
9
|
|
|
10
10
|
dependencies = [
|
|
11
11
|
"requests>=2.25,<3",
|
|
12
|
-
|
|
12
|
+
# websockets 16 needs Python 3.10+. On 3.8/3.9 it is not installed: the
|
|
13
|
+
# REST client works there, and NewsDataApiWebSocket raises on construction.
|
|
14
|
+
"websockets>=16,<17; python_version >= '3.10'",
|
|
13
15
|
]
|
|
14
16
|
|
|
15
17
|
keywords = [
|
|
@@ -37,6 +39,8 @@ classifiers = [
|
|
|
37
39
|
"License :: OSI Approved :: MIT License",
|
|
38
40
|
"Operating System :: OS Independent",
|
|
39
41
|
"Programming Language :: Python :: 3",
|
|
42
|
+
"Programming Language :: Python :: 3.8",
|
|
43
|
+
"Programming Language :: Python :: 3.9",
|
|
40
44
|
"Programming Language :: Python :: 3.10",
|
|
41
45
|
"Programming Language :: Python :: 3.11",
|
|
42
46
|
"Programming Language :: Python :: 3.12",
|
|
@@ -90,7 +94,7 @@ addopts = [
|
|
|
90
94
|
|
|
91
95
|
[tool.ruff]
|
|
92
96
|
line-length = 100
|
|
93
|
-
target-version = "
|
|
97
|
+
target-version = "py38"
|
|
94
98
|
|
|
95
99
|
[tool.ruff.lint]
|
|
96
100
|
select = [
|
|
@@ -107,6 +111,7 @@ select = [
|
|
|
107
111
|
]
|
|
108
112
|
|
|
109
113
|
[tool.mypy]
|
|
110
|
-
|
|
114
|
+
# mypy no longer accepts 3.8 as a target; the 3.8 CI job covers it at runtime.
|
|
115
|
+
python_version = "3.9"
|
|
111
116
|
strict = true
|
|
112
117
|
files = ["src/newsdataapi"]
|
|
@@ -11,14 +11,11 @@ from __future__ import annotations
|
|
|
11
11
|
import asyncio
|
|
12
12
|
import json
|
|
13
13
|
import logging
|
|
14
|
+
import sys
|
|
14
15
|
import time
|
|
15
16
|
from collections.abc import AsyncIterator, Iterator
|
|
16
17
|
from typing import Any
|
|
17
18
|
|
|
18
|
-
from websockets.asyncio import client as _asyncio_client
|
|
19
|
-
from websockets.exceptions import ConnectionClosedError, InvalidStatus
|
|
20
|
-
from websockets.sync import client as _sync_client
|
|
21
|
-
|
|
22
19
|
from . import constants
|
|
23
20
|
from .client import NewsDataApiClient, _redact_url, _validate_params
|
|
24
21
|
from .exceptions import (
|
|
@@ -27,6 +24,15 @@ from .exceptions import (
|
|
|
27
24
|
NewsdataWebSocketError,
|
|
28
25
|
)
|
|
29
26
|
|
|
27
|
+
# websockets requires Python 3.10+ and is not installed on older versions,
|
|
28
|
+
# where only the REST client is available.
|
|
29
|
+
_WEBSOCKET_SUPPORTED = sys.version_info >= (3, 10)
|
|
30
|
+
|
|
31
|
+
if _WEBSOCKET_SUPPORTED:
|
|
32
|
+
from websockets.asyncio import client as _asyncio_client
|
|
33
|
+
from websockets.exceptions import ConnectionClosedError, InvalidStatus
|
|
34
|
+
from websockets.sync import client as _sync_client
|
|
35
|
+
|
|
30
36
|
logger = logging.getLogger(__name__)
|
|
31
37
|
|
|
32
38
|
|
|
@@ -109,6 +115,9 @@ class NewsDataApiWebSocket:
|
|
|
109
115
|
always raises :class:`~newsdataapi.NewsdataWebSocketAuthError` and is
|
|
110
116
|
never retried.
|
|
111
117
|
|
|
118
|
+
Requires Python 3.10 or newer; on older versions the constructor raises
|
|
119
|
+
:class:`~newsdataapi.NewsdataWebSocketError`.
|
|
120
|
+
|
|
112
121
|
Args:
|
|
113
122
|
api_client: The :class:`~newsdataapi.NewsDataApiClient` that supplies
|
|
114
123
|
the API key and performs the management HTTP calls. Not closed by
|
|
@@ -147,6 +156,12 @@ class NewsDataApiWebSocket:
|
|
|
147
156
|
additional_headers: dict[str, str] | None = None,
|
|
148
157
|
proxy: str | None = None,
|
|
149
158
|
) -> None:
|
|
159
|
+
if not _WEBSOCKET_SUPPORTED:
|
|
160
|
+
raise NewsdataWebSocketError(
|
|
161
|
+
"NewsDataApiWebSocket requires Python 3.10 or newer (this is Python "
|
|
162
|
+
f"{sys.version_info.major}.{sys.version_info.minor}); "
|
|
163
|
+
"NewsDataApiClient works on Python 3.8+"
|
|
164
|
+
)
|
|
150
165
|
self.api_client = api_client
|
|
151
166
|
self._base_url = base_url
|
|
152
167
|
self._reconnect = reconnect
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import sys
|
|
5
6
|
from collections.abc import Iterator
|
|
6
7
|
|
|
7
8
|
import pytest
|
|
@@ -9,6 +10,9 @@ import responses
|
|
|
9
10
|
|
|
10
11
|
from newsdataapi import NewsDataApiClient
|
|
11
12
|
|
|
13
|
+
# test_websocket.py imports websockets, which is only installed on Python 3.10+.
|
|
14
|
+
collect_ignore = ["test_websocket.py"] if sys.version_info < (3, 10) else []
|
|
15
|
+
|
|
12
16
|
|
|
13
17
|
@pytest.fixture
|
|
14
18
|
def mocked_responses() -> Iterator[responses.RequestsMock]:
|
|
@@ -7,6 +7,7 @@ API key.
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
+
import sys
|
|
10
11
|
from datetime import datetime, timedelta, timezone
|
|
11
12
|
from email.utils import format_datetime
|
|
12
13
|
from pathlib import Path
|
|
@@ -25,6 +26,7 @@ from newsdataapi import (
|
|
|
25
26
|
NewsdataRateLimitError,
|
|
26
27
|
NewsdataServerError,
|
|
27
28
|
NewsdataValidationError,
|
|
29
|
+
NewsdataWebSocketError,
|
|
28
30
|
save_to_csv,
|
|
29
31
|
)
|
|
30
32
|
from newsdataapi.client import (
|
|
@@ -1348,7 +1350,23 @@ WS_REGISTER_URL = "https://newsdata.io/api/1/websocket/register"
|
|
|
1348
1350
|
WS_FETCH_URL = "https://newsdata.io/api/1/websocket/fetch"
|
|
1349
1351
|
WS_DELETE_URL = "https://newsdata.io/api/1/websocket/delete"
|
|
1350
1352
|
|
|
1353
|
+
# NewsDataApiWebSocket can't be constructed below Python 3.10.
|
|
1354
|
+
requires_websocket = pytest.mark.skipif(
|
|
1355
|
+
sys.version_info < (3, 10), reason="WebSocket support requires Python 3.10+"
|
|
1356
|
+
)
|
|
1357
|
+
|
|
1358
|
+
|
|
1359
|
+
def test_websocket_requires_python_310(
|
|
1360
|
+
client: NewsDataApiClient,
|
|
1361
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
1362
|
+
) -> None:
|
|
1363
|
+
"""Below Python 3.10, construction fails up front with a clear error."""
|
|
1364
|
+
monkeypatch.setattr("newsdataapi.websocket._WEBSOCKET_SUPPORTED", False)
|
|
1365
|
+
with pytest.raises(NewsdataWebSocketError, match="requires Python 3.10"):
|
|
1366
|
+
NewsDataApiWebSocket(client)
|
|
1367
|
+
|
|
1351
1368
|
|
|
1369
|
+
@requires_websocket
|
|
1352
1370
|
def test_websocket_register_posts_query_params(
|
|
1353
1371
|
client: NewsDataApiClient,
|
|
1354
1372
|
mocked_responses: responses.RequestsMock,
|
|
@@ -1375,6 +1393,7 @@ def test_websocket_register_posts_query_params(
|
|
|
1375
1393
|
assert not sent.body # params travel in the query string, not a body
|
|
1376
1394
|
|
|
1377
1395
|
|
|
1396
|
+
@requires_websocket
|
|
1378
1397
|
def test_websocket_register_always_sends_news_type_latest(
|
|
1379
1398
|
client: NewsDataApiClient,
|
|
1380
1399
|
mocked_responses: responses.RequestsMock,
|
|
@@ -1390,6 +1409,7 @@ def test_websocket_register_always_sends_news_type_latest(
|
|
|
1390
1409
|
assert "news_type=latest" in sent_url
|
|
1391
1410
|
|
|
1392
1411
|
|
|
1412
|
+
@requires_websocket
|
|
1393
1413
|
def test_websocket_register_mutex_validation_applies(
|
|
1394
1414
|
client: NewsDataApiClient,
|
|
1395
1415
|
) -> None:
|
|
@@ -1398,6 +1418,7 @@ def test_websocket_register_mutex_validation_applies(
|
|
|
1398
1418
|
NewsDataApiWebSocket(client).websocket_register(q="a", qInTitle="b")
|
|
1399
1419
|
|
|
1400
1420
|
|
|
1421
|
+
@requires_websocket
|
|
1401
1422
|
def test_websocket_register_raw_query(
|
|
1402
1423
|
client: NewsDataApiClient,
|
|
1403
1424
|
mocked_responses: responses.RequestsMock,
|
|
@@ -1414,6 +1435,7 @@ def test_websocket_register_raw_query(
|
|
|
1414
1435
|
assert "news_type=latest" in sent_url
|
|
1415
1436
|
|
|
1416
1437
|
|
|
1438
|
+
@requires_websocket
|
|
1417
1439
|
def test_websocket_register_raw_query_rejects_unknown_param(
|
|
1418
1440
|
client: NewsDataApiClient,
|
|
1419
1441
|
) -> None:
|
|
@@ -1422,6 +1444,7 @@ def test_websocket_register_raw_query_rejects_unknown_param(
|
|
|
1422
1444
|
NewsDataApiWebSocket(client).websocket_register(raw_query="q=pizza&size=10")
|
|
1423
1445
|
|
|
1424
1446
|
|
|
1447
|
+
@requires_websocket
|
|
1425
1448
|
def test_websocket_register_sends_excludedomainurl(
|
|
1426
1449
|
client: NewsDataApiClient,
|
|
1427
1450
|
mocked_responses: responses.RequestsMock,
|
|
@@ -1439,6 +1462,7 @@ def test_websocket_register_sends_excludedomainurl(
|
|
|
1439
1462
|
assert "excludedomainurl=bbc.com%2Ccnn.com" in sent_url
|
|
1440
1463
|
|
|
1441
1464
|
|
|
1465
|
+
@requires_websocket
|
|
1442
1466
|
def test_websocket_register_duplicate_409(
|
|
1443
1467
|
client: NewsDataApiClient,
|
|
1444
1468
|
mocked_responses: responses.RequestsMock,
|
|
@@ -1466,6 +1490,7 @@ def test_websocket_register_duplicate_409(
|
|
|
1466
1490
|
assert len(mocked_responses.calls) == 1 # 4xx is never retried
|
|
1467
1491
|
|
|
1468
1492
|
|
|
1493
|
+
@requires_websocket
|
|
1469
1494
|
def test_websocket_register_403_raises_auth(
|
|
1470
1495
|
client: NewsDataApiClient,
|
|
1471
1496
|
mocked_responses: responses.RequestsMock,
|
|
@@ -1476,6 +1501,7 @@ def test_websocket_register_403_raises_auth(
|
|
|
1476
1501
|
NewsDataApiWebSocket(client).websocket_register(q="pizza")
|
|
1477
1502
|
|
|
1478
1503
|
|
|
1504
|
+
@requires_websocket
|
|
1479
1505
|
def test_websocket_register_429_exhausts_to_rate_limit(
|
|
1480
1506
|
client: NewsDataApiClient,
|
|
1481
1507
|
mocked_responses: responses.RequestsMock,
|
|
@@ -1488,6 +1514,7 @@ def test_websocket_register_429_exhausts_to_rate_limit(
|
|
|
1488
1514
|
NewsDataApiWebSocket(client).websocket_register(q="pizza")
|
|
1489
1515
|
|
|
1490
1516
|
|
|
1517
|
+
@requires_websocket
|
|
1491
1518
|
def test_websocket_fetch_returns_queries(
|
|
1492
1519
|
client: NewsDataApiClient,
|
|
1493
1520
|
mocked_responses: responses.RequestsMock,
|
|
@@ -1518,6 +1545,7 @@ def test_websocket_fetch_returns_queries(
|
|
|
1518
1545
|
assert "apikey=test_key_xxx" in sent_url
|
|
1519
1546
|
|
|
1520
1547
|
|
|
1548
|
+
@requires_websocket
|
|
1521
1549
|
def test_websocket_delete_sends_registration_id(
|
|
1522
1550
|
client: NewsDataApiClient,
|
|
1523
1551
|
mocked_responses: responses.RequestsMock,
|
|
@@ -1537,6 +1565,7 @@ def test_websocket_delete_sends_registration_id(
|
|
|
1537
1565
|
assert "registration_id=abc123" in sent_url
|
|
1538
1566
|
|
|
1539
1567
|
|
|
1568
|
+
@requires_websocket
|
|
1540
1569
|
def test_websocket_delete_unknown_id_404(
|
|
1541
1570
|
client: NewsDataApiClient,
|
|
1542
1571
|
mocked_responses: responses.RequestsMock,
|
|
@@ -1547,6 +1576,7 @@ def test_websocket_delete_unknown_id_404(
|
|
|
1547
1576
|
assert exc_info.value.status_code == 404
|
|
1548
1577
|
|
|
1549
1578
|
|
|
1579
|
+
@requires_websocket
|
|
1550
1580
|
@pytest.mark.parametrize("bad_id", ["", None, 123])
|
|
1551
1581
|
def test_websocket_delete_rejects_bad_registration_id(
|
|
1552
1582
|
client: NewsDataApiClient,
|