Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/newsdataapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
)
from .websocket import NewsDataApiWebSocket

__version__ = "0.3.1"
__version__ = "0.3.2"

__all__ = [
"NewsDataApiClient",
Expand Down
20 changes: 18 additions & 2 deletions src/newsdataapi/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"domain",
"domainurl",
"excludedomain",
"excludedomainurl",
"timezone",
"from_date",
"to_date",
Expand Down Expand Up @@ -81,7 +82,7 @@
("country", "excludecountry"),
("category", "excludecategory"),
("language", "excludelanguage"),
("domain", "domainurl", "excludedomain"),
("domain", "domainurl", "excludedomain", "excludedomainurl"),
)

_QUOTA_EXHAUSTED_CODES = frozenset({"ApiKeyLimitExceeded", "ApiLimitExceeded"})
Expand All @@ -103,7 +104,8 @@ def _validate_params(user_params: Mapping[str, Any]) -> dict[str, Any]:
* Server-side mutex groups (``q``/``qInTitle``/``qInMeta``,
``country``/``excludecountry``, ``category``/``excludecategory``,
``language``/``excludelanguage``, and
``domain``/``domainurl``/``excludedomain``) are enforced client-side;
``domain``/``domainurl``/``excludedomain``/``excludedomainurl``) are
enforced client-side;
setting more than one from any group raises
``NewsdataValidationError`` before the request leaves.
* ``sentiment_score`` requires ``sentiment`` to be set; passing
Expand Down Expand Up @@ -480,6 +482,7 @@ def latest_api(
domain: str | list[str] | None = None,
domainurl: str | list[str] | None = None,
excludedomain: str | list[str] | None = None,
excludedomainurl: str | list[str] | None = None,
prioritydomain: str | None = None,
timeframe: int | str | None = None,
timezone: str | None = None,
Expand Down Expand Up @@ -528,6 +531,7 @@ def latest_api(
"domain": domain,
"domainurl": domainurl,
"excludedomain": excludedomain,
"excludedomainurl": excludedomainurl,
"prioritydomain": prioritydomain,
"timeframe": str(timeframe) if timeframe is not None else None,
"timezone": timezone,
Expand Down Expand Up @@ -571,6 +575,7 @@ def archive_api(
domain: str | list[str] | None = None,
domainurl: str | list[str] | None = None,
excludedomain: str | list[str] | None = None,
excludedomainurl: str | list[str] | None = None,
prioritydomain: str | None = None,
timezone: str | None = None,
size: int | None = None,
Expand Down Expand Up @@ -617,6 +622,7 @@ def archive_api(
"domain": domain,
"domainurl": domainurl,
"excludedomain": excludedomain,
"excludedomainurl": excludedomainurl,
"prioritydomain": prioritydomain,
"timezone": timezone,
"size": size,
Expand Down Expand Up @@ -684,6 +690,7 @@ def crypto_api(
domain: str | list[str] | None = None,
domainurl: str | list[str] | None = None,
excludedomain: str | list[str] | None = None,
excludedomainurl: str | list[str] | None = None,
prioritydomain: str | None = None,
timeframe: int | str | None = None,
timezone: str | None = None,
Expand Down Expand Up @@ -723,6 +730,7 @@ def crypto_api(
"domain": domain,
"domainurl": domainurl,
"excludedomain": excludedomain,
"excludedomainurl": excludedomainurl,
"prioritydomain": prioritydomain,
"timeframe": str(timeframe) if timeframe is not None else None,
"timezone": timezone,
Expand Down Expand Up @@ -762,6 +770,7 @@ def market_api(
domain: str | list[str] | None = None,
domainurl: str | list[str] | None = None,
excludedomain: str | list[str] | None = None,
excludedomainurl: str | list[str] | None = None,
language: str | list[str] | None = None,
excludelanguage: str | list[str] | None = None,
prioritydomain: str | None = None,
Expand Down Expand Up @@ -807,6 +816,7 @@ def market_api(
"domain": domain,
"domainurl": domainurl,
"excludedomain": excludedomain,
"excludedomainurl": excludedomainurl,
"language": language,
"excludelanguage": excludelanguage,
"prioritydomain": prioritydomain,
Expand Down Expand Up @@ -854,6 +864,7 @@ def count_api(
domain: str | list[str] | None = None,
domainurl: str | list[str] | None = None,
excludedomain: str | list[str] | None = None,
excludedomainurl: str | list[str] | None = None,
full_content: bool | None = None,
image: bool | None = None,
video: bool | None = None,
Expand Down Expand Up @@ -901,6 +912,7 @@ def count_api(
"domain": domain,
"domainurl": domainurl,
"excludedomain": excludedomain,
"excludedomainurl": excludedomainurl,
"full_content": full_content,
"image": image,
"video": video,
Expand Down Expand Up @@ -940,6 +952,7 @@ def crypto_count_api(
domain: str | list[str] | None = None,
domainurl: str | list[str] | None = None,
excludedomain: str | list[str] | None = None,
excludedomainurl: str | list[str] | None = None,
full_content: bool | None = None,
image: bool | None = None,
video: bool | None = None,
Expand Down Expand Up @@ -978,6 +991,7 @@ def crypto_count_api(
"domain": domain,
"domainurl": domainurl,
"excludedomain": excludedomain,
"excludedomainurl": excludedomainurl,
"full_content": full_content,
"image": image,
"video": video,
Expand Down Expand Up @@ -1011,6 +1025,7 @@ def market_count_api(
domain: str | list[str] | None = None,
domainurl: str | list[str] | None = None,
excludedomain: str | list[str] | None = None,
excludedomainurl: str | list[str] | None = None,
language: str | list[str] | None = None,
excludelanguage: str | list[str] | None = None,
full_content: bool | None = None,
Expand Down Expand Up @@ -1055,6 +1070,7 @@ def market_count_api(
"domain": domain,
"domainurl": domainurl,
"excludedomain": excludedomain,
"excludedomainurl": excludedomainurl,
"language": language,
"excludelanguage": excludelanguage,
"full_content": full_content,
Expand Down
2 changes: 2 additions & 0 deletions src/newsdataapi/websocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ def websocket_register(
domain: str | list[str] | None = None,
domainurl: str | list[str] | None = None,
excludedomain: str | list[str] | None = None,
excludedomainurl: str | list[str] | None = None,
prioritydomain: str | None = None,
timezone: str | None = None,
full_content: bool | None = None,
Expand Down Expand Up @@ -212,6 +213,7 @@ def websocket_register(
"domain": domain,
"domainurl": domainurl,
"excludedomain": excludedomain,
"excludedomainurl": excludedomainurl,
"prioritydomain": prioritydomain,
"timezone": timezone,
"full_content": full_content,
Expand Down
74 changes: 73 additions & 1 deletion tests/test_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,13 +148,23 @@ def test_validate_size_negative_rejected() -> None:
({"country": "us", "excludecountry": "gb"}, "country"),
({"category": "business", "excludecategory": "sports"}, "category"),
({"language": "en", "excludelanguage": "fr"}, "language"),
# Domain 3-way mutex (any two of domain/domainurl/excludedomain)
# Domain 4-way mutex (any two of
# domain/domainurl/excludedomain/excludedomainurl)
({"domain": "cnn.com", "domainurl": "https://bbc.com"}, "domain"),
({"domain": "cnn.com", "excludedomain": "fox.com"}, "domain"),
(
{"domainurl": "https://bbc.com", "excludedomain": "fox.com"},
"domainurl",
),
({"domain": "cnn.com", "excludedomainurl": "fox.com"}, "domain"),
(
{"domainurl": "https://bbc.com", "excludedomainurl": "fox.com"},
"domainurl",
),
(
{"excludedomain": "cnn.com", "excludedomainurl": "fox.com"},
"excludedomain",
),
(
{
"domain": "cnn.com",
Expand Down Expand Up @@ -1285,6 +1295,51 @@ def test_count_endpoint_url_resolves(
assert len(mocked_responses.calls) == 1


@pytest.mark.parametrize(
("method_name", "endpoint_path", "args"),
[
("latest_api", "latest", ()),
("archive_api", "archive", ()),
("crypto_api", "crypto", ()),
("market_api", "market", ()),
("count_api", "count", ("2024-01-01", "2024-01-31")),
("crypto_count_api", "crypto/count", ("2024-01-01", "2024-01-31")),
("market_count_api", "market/count", ("2024-01-01", "2024-01-31")),
],
)
def test_excludedomainurl_sent_as_csv(
client: NewsDataApiClient,
mocked_responses: responses.RequestsMock,
method_name: str,
endpoint_path: str,
args: tuple[str, ...],
) -> None:
mocked_responses.get(
f"https://newsdata.io/api/1/{endpoint_path}",
json={"status": "success", "results": []},
status=200,
)
getattr(client, method_name)(*args, excludedomainurl=["bbc.com", "cnn.com"])
sent_url = mocked_responses.calls[0].request.url
assert sent_url is not None
assert "excludedomainurl=bbc.com%2Ccnn.com" in sent_url


def test_raw_query_accepts_excludedomainurl(
client: NewsDataApiClient,
mocked_responses: responses.RequestsMock,
) -> None:
mocked_responses.get(
LATEST_URL,
json={"status": "success", "results": []},
status=200,
)
client.latest_api(raw_query="excludedomainurl=bbc.com")
sent_url = mocked_responses.calls[0].request.url
assert sent_url is not None
assert "excludedomainurl=bbc.com" in sent_url


# ===========================================================================
# WebSocket query management (register / fetch / delete)
# ===========================================================================
Expand Down Expand Up @@ -1367,6 +1422,23 @@ def test_websocket_register_raw_query_rejects_unknown_param(
NewsDataApiWebSocket(client).websocket_register(raw_query="q=pizza&size=10")


def test_websocket_register_sends_excludedomainurl(
client: NewsDataApiClient,
mocked_responses: responses.RequestsMock,
) -> None:
mocked_responses.post(
WS_REGISTER_URL,
json={"status": "success", "results": {"registration_id": "abc"}},
status=200,
)
NewsDataApiWebSocket(client).websocket_register(
q="pizza", excludedomainurl=["bbc.com", "cnn.com"]
)
sent_url = mocked_responses.calls[0].request.url
assert sent_url is not None
assert "excludedomainurl=bbc.com%2Ccnn.com" in sent_url


def test_websocket_register_duplicate_409(
client: NewsDataApiClient,
mocked_responses: responses.RequestsMock,
Expand Down
Loading