newsdataapi 0.1.30__tar.gz → 0.2.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.
- newsdataapi-0.2.1/.github/workflows/ci.yml +86 -0
- newsdataapi-0.2.1/.github/workflows/publish.yml +56 -0
- newsdataapi-0.2.1/.gitignore +26 -0
- newsdataapi-0.2.1/.python-version +1 -0
- {newsdataapi-0.1.30 → newsdataapi-0.2.1}/LICENSE +1 -1
- newsdataapi-0.2.1/PKG-INFO +243 -0
- newsdataapi-0.2.1/README.md +192 -0
- newsdataapi-0.2.1/examples/basic_usage.py +94 -0
- newsdataapi-0.2.1/newsdata-logo.png +0 -0
- newsdataapi-0.2.1/pyproject.toml +109 -0
- newsdataapi-0.2.1/src/newsdataapi/__init__.py +28 -0
- newsdataapi-0.2.1/src/newsdataapi/client.py +1360 -0
- newsdataapi-0.2.1/src/newsdataapi/constants.py +24 -0
- newsdataapi-0.2.1/src/newsdataapi/csv_export.py +98 -0
- newsdataapi-0.2.1/src/newsdataapi/exceptions.py +98 -0
- newsdataapi-0.2.1/src/newsdataapi/py.typed +0 -0
- newsdataapi-0.2.1/tests/__init__.py +0 -0
- newsdataapi-0.2.1/tests/conftest.py +49 -0
- newsdataapi-0.2.1/tests/test_integration.py +105 -0
- newsdataapi-0.2.1/tests/test_unit.py +1292 -0
- newsdataapi-0.2.1/uv.lock +1336 -0
- newsdataapi-0.1.30/MANIFEST.in +0 -4
- newsdataapi-0.1.30/PKG-INFO +0 -43
- newsdataapi-0.1.30/README.md +0 -21
- newsdataapi-0.1.30/newsdataapi/__init__.py +0 -1
- newsdataapi-0.1.30/newsdataapi/constants.py +0 -31
- newsdataapi-0.1.30/newsdataapi/helpers.py +0 -45
- newsdataapi-0.1.30/newsdataapi/newsdataapi_client.py +0 -899
- newsdataapi-0.1.30/newsdataapi/newsdataapi_exception.py +0 -6
- newsdataapi-0.1.30/newsdataapi/utils.py +0 -13
- newsdataapi-0.1.30/newsdataapi.egg-info/PKG-INFO +0 -43
- newsdataapi-0.1.30/newsdataapi.egg-info/SOURCES.txt +0 -18
- newsdataapi-0.1.30/newsdataapi.egg-info/dependency_links.txt +0 -1
- newsdataapi-0.1.30/newsdataapi.egg-info/requires.txt +0 -1
- newsdataapi-0.1.30/newsdataapi.egg-info/top_level.txt +0 -1
- newsdataapi-0.1.30/requirements.txt +0 -16
- newsdataapi-0.1.30/setup.cfg +0 -4
- newsdataapi-0.1.30/setup.py +0 -40
- newsdataapi-0.1.30/tests/__init__.py +0 -1
- newsdataapi-0.1.30/tests/test_newsdataapi.py +0 -62
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
# Cancel in-progress runs for the same branch when a new push arrives.
|
|
10
|
+
concurrency:
|
|
11
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
12
|
+
cancel-in-progress: true
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
test:
|
|
19
|
+
name: pytest (Python ${{ matrix.python-version }})
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
strategy:
|
|
22
|
+
fail-fast: false
|
|
23
|
+
matrix:
|
|
24
|
+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v4
|
|
27
|
+
|
|
28
|
+
- name: Install uv
|
|
29
|
+
uses: astral-sh/setup-uv@v6
|
|
30
|
+
with:
|
|
31
|
+
enable-cache: true
|
|
32
|
+
|
|
33
|
+
- name: Install Python ${{ matrix.python-version }}
|
|
34
|
+
run: uv python install ${{ matrix.python-version }}
|
|
35
|
+
|
|
36
|
+
- name: Sync dependencies
|
|
37
|
+
run: uv sync --python ${{ matrix.python-version }}
|
|
38
|
+
|
|
39
|
+
- name: Run unit tests
|
|
40
|
+
run: uv run --python ${{ matrix.python-version }} pytest
|
|
41
|
+
|
|
42
|
+
lint:
|
|
43
|
+
name: ruff + mypy
|
|
44
|
+
runs-on: ubuntu-latest
|
|
45
|
+
steps:
|
|
46
|
+
- uses: actions/checkout@v4
|
|
47
|
+
|
|
48
|
+
- name: Install uv
|
|
49
|
+
uses: astral-sh/setup-uv@v6
|
|
50
|
+
with:
|
|
51
|
+
enable-cache: true
|
|
52
|
+
|
|
53
|
+
- name: Sync dependencies
|
|
54
|
+
run: uv sync
|
|
55
|
+
|
|
56
|
+
- name: Ruff
|
|
57
|
+
run: uv run ruff check src/ tests/ examples/
|
|
58
|
+
|
|
59
|
+
- name: Mypy
|
|
60
|
+
run: uv run mypy src/
|
|
61
|
+
|
|
62
|
+
integration:
|
|
63
|
+
# Only run live API tests on the canonical repository, on push to main.
|
|
64
|
+
# Forks won't have the secret and would fail otherwise.
|
|
65
|
+
name: live API tests
|
|
66
|
+
runs-on: ubuntu-latest
|
|
67
|
+
needs: [test, lint]
|
|
68
|
+
if: github.repository == 'bytesview/python-client' && github.event_name == 'push'
|
|
69
|
+
steps:
|
|
70
|
+
- uses: actions/checkout@v4
|
|
71
|
+
|
|
72
|
+
- name: Install uv
|
|
73
|
+
uses: astral-sh/setup-uv@v6
|
|
74
|
+
|
|
75
|
+
- name: Sync dependencies
|
|
76
|
+
run: uv sync
|
|
77
|
+
|
|
78
|
+
- name: Run integration tests
|
|
79
|
+
env:
|
|
80
|
+
PYTEST_TOKEN: ${{ secrets.PYTEST_NEWSDATA_API }}
|
|
81
|
+
run: |
|
|
82
|
+
if [ -z "${PYTEST_TOKEN}" ]; then
|
|
83
|
+
echo "PYTEST_NEWSDATA_API secret not set; skipping integration tests."
|
|
84
|
+
exit 0
|
|
85
|
+
fi
|
|
86
|
+
uv run pytest -m integration
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
build:
|
|
12
|
+
name: Build distributions
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Install uv
|
|
18
|
+
uses: astral-sh/setup-uv@v6
|
|
19
|
+
|
|
20
|
+
- name: Sync dependencies
|
|
21
|
+
run: uv sync
|
|
22
|
+
|
|
23
|
+
- name: Run unit tests
|
|
24
|
+
run: uv run pytest
|
|
25
|
+
|
|
26
|
+
- name: Build sdist + wheel
|
|
27
|
+
run: uv build
|
|
28
|
+
|
|
29
|
+
- name: Upload distributions
|
|
30
|
+
uses: actions/upload-artifact@v4
|
|
31
|
+
with:
|
|
32
|
+
name: dist
|
|
33
|
+
path: dist/
|
|
34
|
+
|
|
35
|
+
publish:
|
|
36
|
+
name: Publish to PyPI
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
needs: build
|
|
39
|
+
# OIDC trusted publishing — no API token required.
|
|
40
|
+
# One-time setup at:
|
|
41
|
+
# https://pypi.org/manage/project/newsdataapi/settings/publishing/
|
|
42
|
+
# Workflow filename = "publish.yml", environment = "pypi".
|
|
43
|
+
permissions:
|
|
44
|
+
id-token: write
|
|
45
|
+
environment:
|
|
46
|
+
name: pypi
|
|
47
|
+
url: https://pypi.org/p/newsdataapi
|
|
48
|
+
steps:
|
|
49
|
+
- name: Download distributions
|
|
50
|
+
uses: actions/download-artifact@v4
|
|
51
|
+
with:
|
|
52
|
+
name: dist
|
|
53
|
+
path: dist/
|
|
54
|
+
|
|
55
|
+
- name: Publish to PyPI
|
|
56
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Python bytecode
|
|
2
|
+
__pycache__/
|
|
3
|
+
|
|
4
|
+
# Virtual env (uv)
|
|
5
|
+
.venv/
|
|
6
|
+
|
|
7
|
+
# Build artifacts (uv build / hatchling)
|
|
8
|
+
dist/
|
|
9
|
+
build/
|
|
10
|
+
*.egg-info/
|
|
11
|
+
|
|
12
|
+
# Tool caches
|
|
13
|
+
.pytest_cache/
|
|
14
|
+
.mypy_cache/
|
|
15
|
+
.ruff_cache/
|
|
16
|
+
.coverage
|
|
17
|
+
|
|
18
|
+
# Project scratch
|
|
19
|
+
temp.py
|
|
20
|
+
|
|
21
|
+
# Claude Code
|
|
22
|
+
CLAUDE.md
|
|
23
|
+
.claude/
|
|
24
|
+
|
|
25
|
+
# IDE
|
|
26
|
+
.vscode/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: newsdataapi
|
|
3
|
+
Version: 0.2.1
|
|
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
|
+
Project-URL: Homepage, https://newsdata.io
|
|
6
|
+
Project-URL: Documentation, https://newsdata.io/documentation
|
|
7
|
+
Project-URL: Repository, https://github.com/bytesview/python-client
|
|
8
|
+
Project-URL: Issues, https://github.com/bytesview/python-client/issues
|
|
9
|
+
Author-email: "NewsData.io" <contact@newsdata.io>
|
|
10
|
+
License: MIT License
|
|
11
|
+
|
|
12
|
+
Copyright (c) 2023-2026 NewsData.io
|
|
13
|
+
|
|
14
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
15
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
16
|
+
in the Software without restriction, including without limitation the rights
|
|
17
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
18
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
19
|
+
furnished to do so, subject to the following conditions:
|
|
20
|
+
|
|
21
|
+
The above copyright notice and this permission notice shall be included in all
|
|
22
|
+
copies or substantial portions of the Software.
|
|
23
|
+
|
|
24
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
25
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
26
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
27
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
28
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
29
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
30
|
+
SOFTWARE.
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
Keywords: api-client,crypto-news,financial-news,market-news,news,news-aggregator,news-api,newsdata,newsdata-io,python-sdk,rest-api,sdk
|
|
33
|
+
Classifier: Development Status :: 4 - Beta
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Operating System :: OS Independent
|
|
37
|
+
Classifier: Programming Language :: Python :: 3
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
43
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
44
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
45
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
46
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
47
|
+
Classifier: Typing :: Typed
|
|
48
|
+
Requires-Python: >=3.8
|
|
49
|
+
Requires-Dist: requests<3,>=2.25
|
|
50
|
+
Description-Content-Type: text/markdown
|
|
51
|
+
|
|
52
|
+
<div align="center">
|
|
53
|
+
|
|
54
|
+
[](https://newsdata.io)
|
|
55
|
+
|
|
56
|
+
# NewsData.io Python Client
|
|
57
|
+
|
|
58
|
+
[](https://github.com/bytesview/python-client/actions/workflows/ci.yml)
|
|
59
|
+
[](https://github.com/bytesview/python-client/blob/main/LICENSE)
|
|
60
|
+
[](https://pypi.org/project/newsdataapi)
|
|
61
|
+
[](https://pypi.org/project/newsdataapi)
|
|
62
|
+
[](https://pypi.org/project/newsdataapi)
|
|
63
|
+
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
`newsdataapi` is the official Python SDK for the [NewsData.io](https://newsdata.io) REST API. It wraps every endpoint (`latest`, `archive`, `sources`, `crypto`, `market`, `count`, `crypto/count`, `market/count`) with consistent retry, pagination, and error handling.
|
|
67
|
+
|
|
68
|
+
## Installation
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pip install newsdataapi
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
If you use [uv](https://github.com/astral-sh/uv):
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
uv add newsdataapi
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Supports Python 3.8 through 3.14. The only runtime dependency is `requests`.
|
|
81
|
+
|
|
82
|
+
## Quickstart
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from newsdataapi import NewsDataApiClient
|
|
86
|
+
|
|
87
|
+
with NewsDataApiClient("YOUR_API_KEY") as client:
|
|
88
|
+
response = client.latest_api(q="bitcoin", country="us", language="en")
|
|
89
|
+
for article in response["results"]:
|
|
90
|
+
print(article["title"], "-", article["link"])
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The context-manager form closes the underlying HTTP session cleanly when the block exits. If you prefer not to use `with`, create the client directly and call `client.close()` yourself:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from newsdataapi import NewsDataApiClient
|
|
97
|
+
|
|
98
|
+
client = NewsDataApiClient("YOUR_API_KEY")
|
|
99
|
+
try:
|
|
100
|
+
response = client.latest_api(q="bitcoin", country="us", language="en")
|
|
101
|
+
for article in response["results"]:
|
|
102
|
+
print(article["title"], "-", article["link"])
|
|
103
|
+
finally:
|
|
104
|
+
client.close()
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Endpoints
|
|
108
|
+
|
|
109
|
+
| Method | Endpoint | Notes |
|
|
110
|
+
|--------|----------|-------|
|
|
111
|
+
| `latest_api()` | `/latest` | Real-time news |
|
|
112
|
+
| `archive_api()` | `/archive` | Historical news |
|
|
113
|
+
| `sources_api()` | `/sources` | Available news sources |
|
|
114
|
+
| `crypto_api()` | `/crypto` | Cryptocurrency news |
|
|
115
|
+
| `market_api()` | `/market` | Market / financial news |
|
|
116
|
+
| `count_api(from_date, to_date)` | `/count` | Aggregate counts |
|
|
117
|
+
| `crypto_count_api(from_date, to_date)` | `/crypto/count` | Aggregate crypto counts |
|
|
118
|
+
| `market_count_api(from_date, to_date)` | `/market/count` | Aggregate market counts |
|
|
119
|
+
|
|
120
|
+
All endpoint parameters are keyword-only (except the required `from_date` / `to_date` on the count endpoints). Most accept either a single string or a `list[str]`; lists are comma-joined for the API.
|
|
121
|
+
|
|
122
|
+
See the [NewsData.io documentation](https://newsdata.io/documentation) for the full parameter reference.
|
|
123
|
+
|
|
124
|
+
## Three ways to consume an endpoint
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
# 1. Single request (the default).
|
|
128
|
+
response = client.latest_api(q="news")
|
|
129
|
+
|
|
130
|
+
# 2. Auto-merge — follow nextPage cursors and return one combined dict.
|
|
131
|
+
merged = client.latest_api(q="news", scroll=True, max_result=200)
|
|
132
|
+
|
|
133
|
+
# 3. Iterate one response per page (a generator).
|
|
134
|
+
for page in client.latest_api(q="news", paginate=True, max_pages=5):
|
|
135
|
+
process(page["results"])
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`scroll` and `paginate` are mutually exclusive. `scroll=True` truncates strictly to `max_result`; `paginate=True` stops at `max_pages` or when the API returns no `nextPage`.
|
|
139
|
+
|
|
140
|
+
## Error handling
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from newsdataapi import (
|
|
144
|
+
NewsdataAPIError,
|
|
145
|
+
NewsdataAuthError,
|
|
146
|
+
NewsdataNetworkError,
|
|
147
|
+
NewsdataRateLimitError,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
client.latest_api(q="news")
|
|
152
|
+
except NewsdataAuthError as e:
|
|
153
|
+
print(f"bad API key (HTTP {e.status_code})")
|
|
154
|
+
except NewsdataRateLimitError as e:
|
|
155
|
+
print(f"rate limited; retry after {e.retry_after}s")
|
|
156
|
+
except NewsdataAPIError as e:
|
|
157
|
+
print(f"API error {e.status_code}: {e.response_body}")
|
|
158
|
+
except NewsdataNetworkError as e:
|
|
159
|
+
print(f"network failure: {e.original}")
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The full hierarchy:
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
NewsdataException
|
|
166
|
+
├── NewsdataValidationError (also a ValueError; carries .param)
|
|
167
|
+
├── NewsdataAPIError (carries .status_code, .response_body)
|
|
168
|
+
│ ├── NewsdataAuthError (401 / 403)
|
|
169
|
+
│ ├── NewsdataRateLimitError (429; carries .retry_after)
|
|
170
|
+
│ └── NewsdataServerError (5xx)
|
|
171
|
+
└── NewsdataNetworkError (carries .original)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
`NewsdataException` is always a valid catch-all.
|
|
175
|
+
|
|
176
|
+
## Save results to CSV
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
client.save_to_csv(response, folder_path="./out", filename="latest_news")
|
|
180
|
+
|
|
181
|
+
# Or set folder_path once on the client and reuse:
|
|
182
|
+
client = NewsDataApiClient(apikey, folder_path="./out")
|
|
183
|
+
client.save_to_csv(response, filename="latest_news")
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
`save_to_csv` returns a `pathlib.Path`. Cell values that are dicts or lists are stringified (`key:value,key:value` for dicts, comma-joined for lists). Quoting is delegated to the standard `csv.DictWriter`, so the output round-trips correctly through any CSV reader.
|
|
187
|
+
|
|
188
|
+
The function is also importable as a standalone:
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
from newsdataapi import save_to_csv
|
|
192
|
+
save_to_csv(response, folder_path="./out", filename="latest_news")
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Configuration
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
client = NewsDataApiClient(
|
|
199
|
+
apikey="...",
|
|
200
|
+
request_timeout=30, # seconds; default 30
|
|
201
|
+
max_retries=5, # default 5
|
|
202
|
+
retry_backoff=2.0, # base seconds, exponential; default 2.0
|
|
203
|
+
retry_backoff_max=60.0, # cap on a single retry sleep; default 60.0
|
|
204
|
+
pagination_delay=1.0, # seconds between pages; default 1.0
|
|
205
|
+
max_result=None, # cap on merged results in scroll mode; default None (no cap)
|
|
206
|
+
max_pages=None, # cap on pages yielded in paginate mode; default None (no cap)
|
|
207
|
+
proxies={"https": "..."}, # passed to requests.Session.get
|
|
208
|
+
accept_language="en", # Accept-Language header
|
|
209
|
+
include_headers=False, # if True, returned dicts include response_headers
|
|
210
|
+
base_url="...", # override for staging / proxied environments
|
|
211
|
+
session=my_session, # inject your own requests.Session
|
|
212
|
+
folder_path="./out", # default folder for save_to_csv; default None
|
|
213
|
+
)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Defaults sleep about a minute total across all retries (2 s → 4 s → 8 s → 16 s → 32 s, capped at 60 s); 429 responses honor `Retry-After` (both integer-seconds and HTTP-date forms are parsed). The API key is redacted in log output.
|
|
217
|
+
|
|
218
|
+
## Development
|
|
219
|
+
|
|
220
|
+
This project uses [uv](https://github.com/astral-sh/uv) for environment and lock management.
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
git clone https://github.com/bytesview/python-client
|
|
224
|
+
cd python-client
|
|
225
|
+
uv sync # creates .venv, installs runtime + dev deps from uv.lock
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Run the suite:
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
uv run pytest # unit tests only (default)
|
|
232
|
+
PYTEST_TOKEN=<api-key> uv run pytest -m integration # live-API tests
|
|
233
|
+
PYTEST_TOKEN=<api-key> uv run pytest -m "" # all tests
|
|
234
|
+
|
|
235
|
+
uv run ruff check src/ tests/ examples/
|
|
236
|
+
uv run mypy src/
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Dev dependencies live in PEP 735 `[dependency-groups].dev` (uv-native). Plain `pip install -e ".[dev]"` will not pick them up; if you can't use uv, install the contents of the `dev` group in `pyproject.toml` by hand.
|
|
240
|
+
|
|
241
|
+
## License
|
|
242
|
+
|
|
243
|
+
MIT. See the [LICENSE](LICENSE) file.
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
[](https://newsdata.io)
|
|
4
|
+
|
|
5
|
+
# NewsData.io Python Client
|
|
6
|
+
|
|
7
|
+
[](https://github.com/bytesview/python-client/actions/workflows/ci.yml)
|
|
8
|
+
[](https://github.com/bytesview/python-client/blob/main/LICENSE)
|
|
9
|
+
[](https://pypi.org/project/newsdataapi)
|
|
10
|
+
[](https://pypi.org/project/newsdataapi)
|
|
11
|
+
[](https://pypi.org/project/newsdataapi)
|
|
12
|
+
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
`newsdataapi` is the official Python SDK for the [NewsData.io](https://newsdata.io) REST API. It wraps every endpoint (`latest`, `archive`, `sources`, `crypto`, `market`, `count`, `crypto/count`, `market/count`) with consistent retry, pagination, and error handling.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install newsdataapi
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
If you use [uv](https://github.com/astral-sh/uv):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
uv add newsdataapi
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Supports Python 3.8 through 3.14. The only runtime dependency is `requests`.
|
|
30
|
+
|
|
31
|
+
## Quickstart
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from newsdataapi import NewsDataApiClient
|
|
35
|
+
|
|
36
|
+
with NewsDataApiClient("YOUR_API_KEY") as client:
|
|
37
|
+
response = client.latest_api(q="bitcoin", country="us", language="en")
|
|
38
|
+
for article in response["results"]:
|
|
39
|
+
print(article["title"], "-", article["link"])
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The context-manager form closes the underlying HTTP session cleanly when the block exits. If you prefer not to use `with`, create the client directly and call `client.close()` yourself:
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from newsdataapi import NewsDataApiClient
|
|
46
|
+
|
|
47
|
+
client = NewsDataApiClient("YOUR_API_KEY")
|
|
48
|
+
try:
|
|
49
|
+
response = client.latest_api(q="bitcoin", country="us", language="en")
|
|
50
|
+
for article in response["results"]:
|
|
51
|
+
print(article["title"], "-", article["link"])
|
|
52
|
+
finally:
|
|
53
|
+
client.close()
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Endpoints
|
|
57
|
+
|
|
58
|
+
| Method | Endpoint | Notes |
|
|
59
|
+
|--------|----------|-------|
|
|
60
|
+
| `latest_api()` | `/latest` | Real-time news |
|
|
61
|
+
| `archive_api()` | `/archive` | Historical news |
|
|
62
|
+
| `sources_api()` | `/sources` | Available news sources |
|
|
63
|
+
| `crypto_api()` | `/crypto` | Cryptocurrency news |
|
|
64
|
+
| `market_api()` | `/market` | Market / financial news |
|
|
65
|
+
| `count_api(from_date, to_date)` | `/count` | Aggregate counts |
|
|
66
|
+
| `crypto_count_api(from_date, to_date)` | `/crypto/count` | Aggregate crypto counts |
|
|
67
|
+
| `market_count_api(from_date, to_date)` | `/market/count` | Aggregate market counts |
|
|
68
|
+
|
|
69
|
+
All endpoint parameters are keyword-only (except the required `from_date` / `to_date` on the count endpoints). Most accept either a single string or a `list[str]`; lists are comma-joined for the API.
|
|
70
|
+
|
|
71
|
+
See the [NewsData.io documentation](https://newsdata.io/documentation) for the full parameter reference.
|
|
72
|
+
|
|
73
|
+
## Three ways to consume an endpoint
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
# 1. Single request (the default).
|
|
77
|
+
response = client.latest_api(q="news")
|
|
78
|
+
|
|
79
|
+
# 2. Auto-merge — follow nextPage cursors and return one combined dict.
|
|
80
|
+
merged = client.latest_api(q="news", scroll=True, max_result=200)
|
|
81
|
+
|
|
82
|
+
# 3. Iterate one response per page (a generator).
|
|
83
|
+
for page in client.latest_api(q="news", paginate=True, max_pages=5):
|
|
84
|
+
process(page["results"])
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`scroll` and `paginate` are mutually exclusive. `scroll=True` truncates strictly to `max_result`; `paginate=True` stops at `max_pages` or when the API returns no `nextPage`.
|
|
88
|
+
|
|
89
|
+
## Error handling
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from newsdataapi import (
|
|
93
|
+
NewsdataAPIError,
|
|
94
|
+
NewsdataAuthError,
|
|
95
|
+
NewsdataNetworkError,
|
|
96
|
+
NewsdataRateLimitError,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
client.latest_api(q="news")
|
|
101
|
+
except NewsdataAuthError as e:
|
|
102
|
+
print(f"bad API key (HTTP {e.status_code})")
|
|
103
|
+
except NewsdataRateLimitError as e:
|
|
104
|
+
print(f"rate limited; retry after {e.retry_after}s")
|
|
105
|
+
except NewsdataAPIError as e:
|
|
106
|
+
print(f"API error {e.status_code}: {e.response_body}")
|
|
107
|
+
except NewsdataNetworkError as e:
|
|
108
|
+
print(f"network failure: {e.original}")
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The full hierarchy:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
NewsdataException
|
|
115
|
+
├── NewsdataValidationError (also a ValueError; carries .param)
|
|
116
|
+
├── NewsdataAPIError (carries .status_code, .response_body)
|
|
117
|
+
│ ├── NewsdataAuthError (401 / 403)
|
|
118
|
+
│ ├── NewsdataRateLimitError (429; carries .retry_after)
|
|
119
|
+
│ └── NewsdataServerError (5xx)
|
|
120
|
+
└── NewsdataNetworkError (carries .original)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`NewsdataException` is always a valid catch-all.
|
|
124
|
+
|
|
125
|
+
## Save results to CSV
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
client.save_to_csv(response, folder_path="./out", filename="latest_news")
|
|
129
|
+
|
|
130
|
+
# Or set folder_path once on the client and reuse:
|
|
131
|
+
client = NewsDataApiClient(apikey, folder_path="./out")
|
|
132
|
+
client.save_to_csv(response, filename="latest_news")
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`save_to_csv` returns a `pathlib.Path`. Cell values that are dicts or lists are stringified (`key:value,key:value` for dicts, comma-joined for lists). Quoting is delegated to the standard `csv.DictWriter`, so the output round-trips correctly through any CSV reader.
|
|
136
|
+
|
|
137
|
+
The function is also importable as a standalone:
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
from newsdataapi import save_to_csv
|
|
141
|
+
save_to_csv(response, folder_path="./out", filename="latest_news")
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Configuration
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
client = NewsDataApiClient(
|
|
148
|
+
apikey="...",
|
|
149
|
+
request_timeout=30, # seconds; default 30
|
|
150
|
+
max_retries=5, # default 5
|
|
151
|
+
retry_backoff=2.0, # base seconds, exponential; default 2.0
|
|
152
|
+
retry_backoff_max=60.0, # cap on a single retry sleep; default 60.0
|
|
153
|
+
pagination_delay=1.0, # seconds between pages; default 1.0
|
|
154
|
+
max_result=None, # cap on merged results in scroll mode; default None (no cap)
|
|
155
|
+
max_pages=None, # cap on pages yielded in paginate mode; default None (no cap)
|
|
156
|
+
proxies={"https": "..."}, # passed to requests.Session.get
|
|
157
|
+
accept_language="en", # Accept-Language header
|
|
158
|
+
include_headers=False, # if True, returned dicts include response_headers
|
|
159
|
+
base_url="...", # override for staging / proxied environments
|
|
160
|
+
session=my_session, # inject your own requests.Session
|
|
161
|
+
folder_path="./out", # default folder for save_to_csv; default None
|
|
162
|
+
)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Defaults sleep about a minute total across all retries (2 s → 4 s → 8 s → 16 s → 32 s, capped at 60 s); 429 responses honor `Retry-After` (both integer-seconds and HTTP-date forms are parsed). The API key is redacted in log output.
|
|
166
|
+
|
|
167
|
+
## Development
|
|
168
|
+
|
|
169
|
+
This project uses [uv](https://github.com/astral-sh/uv) for environment and lock management.
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
git clone https://github.com/bytesview/python-client
|
|
173
|
+
cd python-client
|
|
174
|
+
uv sync # creates .venv, installs runtime + dev deps from uv.lock
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Run the suite:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
uv run pytest # unit tests only (default)
|
|
181
|
+
PYTEST_TOKEN=<api-key> uv run pytest -m integration # live-API tests
|
|
182
|
+
PYTEST_TOKEN=<api-key> uv run pytest -m "" # all tests
|
|
183
|
+
|
|
184
|
+
uv run ruff check src/ tests/ examples/
|
|
185
|
+
uv run mypy src/
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Dev dependencies live in PEP 735 `[dependency-groups].dev` (uv-native). Plain `pip install -e ".[dev]"` will not pick them up; if you can't use uv, install the contents of the `dev` group in `pyproject.toml` by hand.
|
|
189
|
+
|
|
190
|
+
## License
|
|
191
|
+
|
|
192
|
+
MIT. See the [LICENSE](LICENSE) file.
|