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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,31 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.2.4]

### Added
- `TooManyRequestsError` is raised by all API calls when the TIND API returns an HTTP 429 response.


## [0.2.3]

### Added
- parameter to client.write_search_results_to_file() to specify an output directory for the file, with fallback to default_storage_dir if not provided
- also uses PATH


## [0.2.2]

### Changed
- build package and publish to pypi, using setuptools_scm for version management


## [0.2.1]

### Changed
- reconciling version agreement between the releases, this file, and the pyproject.toml


## [0.2.0]

### Added
Expand Down
17 changes: 16 additions & 1 deletion tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import requests_mock as req_mock # noqa: F401 — activates the requests_mock fixture

from tind_client.api import tind_download, tind_get
from tind_client.errors import AuthorizationError
from tind_client.errors import AuthorizationError, TooManyRequestsError

BASE_URL = "https://tind.example.edu"
API_KEY = "test-api-key"
Expand Down Expand Up @@ -39,6 +39,13 @@ def test_tind_get_raises_on_401(requests_mock: req_mock.Mocker) -> None:
tind_get("record/1/", api_key=API_KEY, api_url=BASE_URL)


def test_tind_get_raises_on_429(requests_mock: req_mock.Mocker) -> None:
"""Ensure tind_get raises TooManyRequestsError on HTTP 429."""
requests_mock.get(f"{BASE_URL}/record/1/", status_code=429)
with pytest.raises(TooManyRequestsError):
tind_get("record/1/", api_key=API_KEY, api_url=BASE_URL)


def test_tind_get_raises_on_500(requests_mock: req_mock.Mocker) -> None:
"""tind_get propagates an HTTPError on HTTP 5xx."""
requests_mock.get(f"{BASE_URL}/record/1/", status_code=500)
Expand Down Expand Up @@ -90,6 +97,14 @@ def test_tind_download_raises_on_401(requests_mock: req_mock.Mocker) -> None:
tind_download(url, "/tmp", api_key=API_KEY)


def test_tind_download_raises_on_429(requests_mock: req_mock.Mocker) -> None:
"""Ensure tind_download raises TooManyRequestsError on HTTP 429."""
url = f"{BASE_URL}/files/12345/download"
requests_mock.get(url, status_code=429)
with pytest.raises(TooManyRequestsError):
tind_download(url, "/tmp", api_key=API_KEY)


def test_tind_download_non_200_returns_empty(requests_mock: req_mock.Mocker) -> None:
"""tind_download returns (status, '') for non-200, non-error responses."""
url = f"{BASE_URL}/files/12345/download"
Expand Down
10 changes: 9 additions & 1 deletion tind_client/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from typing import Tuple
import requests

from .errors import AuthorizationError, TINDError
from .errors import AuthorizationError, TINDError, TooManyRequestsError


TIMEOUT: int = 30
Expand Down Expand Up @@ -39,6 +39,8 @@ def tind_get(
:param dict|None params: Extra query parameters to send.
For example, ``{'of': 'xm'}``.
:raises AuthorizationError: If an invalid TIND API key is provided.
:raises TooManyRequestsError: If the TIND server is overloaded with requests.
:raises TINDError: If an internal server error occurs during request processing.
:returns: A tuple of the HTTP status code and response text (if any).
:rtype: Tuple[int, str]
"""
Expand All @@ -54,6 +56,8 @@ def tind_get(
)
if resp.status_code == 401:
raise AuthorizationError("Invalid TIND API key provided")
if resp.status_code == 429:
raise TooManyRequestsError("Enhance your calm")
if resp.status_code >= 500:
raise TINDError.from_json(resp.status_code, resp.text)
return resp.status_code, resp.text
Expand All @@ -66,13 +70,17 @@ def tind_download(url: str, output_dir: str, api_key: str) -> Tuple[int, str]:
:param str output_dir: The path to the directory in which to save the file.
:param str api_key: The TIND API token.
:raises AuthorizationError: If an invalid TIND API key is provided.
:raises TooManyRequestsError: If the TIND server is overloaded with requests.
:raises TINDError: If an internal server error occurs during request processing.
:returns: A tuple of the HTTP status code and the path to the downloaded file (if successful).
:rtype: Tuple[int, str]
"""
resp = requests.get(url, headers=_auth_header(api_key), timeout=TIMEOUT)
status = resp.status_code
if status == 401:
raise AuthorizationError("Invalid TIND API key provided")
if status == 429:
raise TooManyRequestsError("Enhance your calm")
if status >= 500:
raise TINDError.from_json(status, resp.text)
if status != 200:
Expand Down
4 changes: 4 additions & 0 deletions tind_client/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,9 @@ class AuthorizationError(TINDError):
"""Raised when authorization with the TIND API fails."""


class TooManyRequestsError(TINDError):
"""Raised when the TIND API has had too many requests in a short period."""


class RecordNotFoundError(TINDError):
"""Raised when a requested record or file is not found in TIND."""
Loading