newsdataapi 0.1.30__tar.gz → 0.2.0__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 (40) hide show
  1. newsdataapi-0.2.0/.github/workflows/ci.yml +86 -0
  2. newsdataapi-0.2.0/.github/workflows/publish.yml +56 -0
  3. newsdataapi-0.2.0/.gitignore +26 -0
  4. newsdataapi-0.2.0/.python-version +1 -0
  5. {newsdataapi-0.1.30 → newsdataapi-0.2.0}/LICENSE +1 -1
  6. newsdataapi-0.2.0/PKG-INFO +243 -0
  7. newsdataapi-0.2.0/README.md +192 -0
  8. newsdataapi-0.2.0/examples/basic_usage.py +94 -0
  9. newsdataapi-0.2.0/newsdata-logo.png +0 -0
  10. newsdataapi-0.2.0/pyproject.toml +96 -0
  11. newsdataapi-0.2.0/src/newsdataapi/__init__.py +28 -0
  12. newsdataapi-0.2.0/src/newsdataapi/client.py +1360 -0
  13. newsdataapi-0.2.0/src/newsdataapi/constants.py +24 -0
  14. newsdataapi-0.2.0/src/newsdataapi/csv_export.py +98 -0
  15. newsdataapi-0.2.0/src/newsdataapi/exceptions.py +98 -0
  16. newsdataapi-0.2.0/src/newsdataapi/py.typed +0 -0
  17. newsdataapi-0.2.0/tests/__init__.py +0 -0
  18. newsdataapi-0.2.0/tests/conftest.py +49 -0
  19. newsdataapi-0.2.0/tests/test_integration.py +105 -0
  20. newsdataapi-0.2.0/tests/test_unit.py +1292 -0
  21. newsdataapi-0.2.0/uv.lock +1336 -0
  22. newsdataapi-0.1.30/MANIFEST.in +0 -4
  23. newsdataapi-0.1.30/PKG-INFO +0 -43
  24. newsdataapi-0.1.30/README.md +0 -21
  25. newsdataapi-0.1.30/newsdataapi/__init__.py +0 -1
  26. newsdataapi-0.1.30/newsdataapi/constants.py +0 -31
  27. newsdataapi-0.1.30/newsdataapi/helpers.py +0 -45
  28. newsdataapi-0.1.30/newsdataapi/newsdataapi_client.py +0 -899
  29. newsdataapi-0.1.30/newsdataapi/newsdataapi_exception.py +0 -6
  30. newsdataapi-0.1.30/newsdataapi/utils.py +0 -13
  31. newsdataapi-0.1.30/newsdataapi.egg-info/PKG-INFO +0 -43
  32. newsdataapi-0.1.30/newsdataapi.egg-info/SOURCES.txt +0 -18
  33. newsdataapi-0.1.30/newsdataapi.egg-info/dependency_links.txt +0 -1
  34. newsdataapi-0.1.30/newsdataapi.egg-info/requires.txt +0 -1
  35. newsdataapi-0.1.30/newsdataapi.egg-info/top_level.txt +0 -1
  36. newsdataapi-0.1.30/requirements.txt +0 -16
  37. newsdataapi-0.1.30/setup.cfg +0 -4
  38. newsdataapi-0.1.30/setup.py +0 -40
  39. newsdataapi-0.1.30/tests/__init__.py +0 -1
  40. 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
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) [year] [fullname]
3
+ Copyright (c) 2023-2026 NewsData.io
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -0,0 +1,243 @@
1
+ Metadata-Version: 2.4
2
+ Name: newsdataapi
3
+ Version: 0.2.0
4
+ Summary: Official Python SDK for the NewsData.io REST API
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,news,news-data,newsdata,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
+ [![NewsData.io logo](https://raw.githubusercontent.com/bytesview/python-client/main/newsdata-logo.png)](https://newsdata.io)
55
+
56
+ # NewsData.io Python Client
57
+
58
+ [![Build Status](https://img.shields.io/github/actions/workflow/status/bytesview/python-client/ci.yml)](https://github.com/bytesview/python-client/actions/workflows/ci.yml)
59
+ [![License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/bytesview/python-client/blob/main/LICENSE)
60
+ [![PyPI](https://img.shields.io/pypi/v/newsdataapi?color=084298)](https://pypi.org/project/newsdataapi)
61
+ [![PyPI - Downloads](https://img.shields.io/pypi/dm/newsdataapi)](https://pypi.org/project/newsdataapi)
62
+ [![Supported Python versions](https://img.shields.io/pypi/pyversions/newsdataapi)](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
+ [![NewsData.io logo](https://raw.githubusercontent.com/bytesview/python-client/main/newsdata-logo.png)](https://newsdata.io)
4
+
5
+ # NewsData.io Python Client
6
+
7
+ [![Build Status](https://img.shields.io/github/actions/workflow/status/bytesview/python-client/ci.yml)](https://github.com/bytesview/python-client/actions/workflows/ci.yml)
8
+ [![License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/bytesview/python-client/blob/main/LICENSE)
9
+ [![PyPI](https://img.shields.io/pypi/v/newsdataapi?color=084298)](https://pypi.org/project/newsdataapi)
10
+ [![PyPI - Downloads](https://img.shields.io/pypi/dm/newsdataapi)](https://pypi.org/project/newsdataapi)
11
+ [![Supported Python versions](https://img.shields.io/pypi/pyversions/newsdataapi)](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.