Skip to content
Open
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
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,11 @@ you'll interact with mostly.

When you issue a request, the response is a [requests.Response][] object. If
`response.status_code != 200` then a `requests.HTTPError` exception will be
raised — it's your responsibility to handle those exceptions if you want to. The
one case that's handled is when the access token has expired: in this case, the
client will automatically handle the HTTP 400 status and renew the token.
raised — it's your responsibility to handle those exceptions if you want to.
This default can be disabled by passing `raise_for_status=False` when
constructing the client. The one case that's handled is when the access token
has expired: in this case, the client will automatically handle the HTTP 400
status and renew the token.

Note that the Client does not attempt to interpret the data supplied by OPS, so
it's your responsibility to parse the XML or JSON payload for your own purpose.
Expand Down
24 changes: 15 additions & 9 deletions epo_ops/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,13 @@

from . import exceptions
from .middlewares import Throttler
from .models import (
AccessToken,
Docdb,
Epodoc,
Original,
Request,
)
from .models import AccessToken, Docdb, Epodoc, Original, Request

log = logging.getLogger(__name__)

DEFAULT_NETWORK_TIMEOUT = 10.0


class Client(object):
__auth_url__ = "https://ops.epo.org/3.2/auth/accesstoken"
__service_url_prefix__ = "https://ops.epo.org/3.2/rest-services"
Expand All @@ -35,7 +30,15 @@ class Client(object):
__register_path__ = "register"
__register_search_path__ = "register/search"

def __init__(self, key, secret, accept_type="xml", middlewares=None, timeout=DEFAULT_NETWORK_TIMEOUT):
def __init__(
self,
key,
secret,
accept_type="xml",
middlewares=None,
timeout=DEFAULT_NETWORK_TIMEOUT,
raise_for_status=True,
):
self.accept_type = "application/{0}".format(accept_type)
self.middlewares = middlewares
if middlewares is None:
Expand All @@ -44,6 +47,7 @@ def __init__(self, key, secret, accept_type="xml", middlewares=None, timeout=DEF
self.key = key
self.secret = secret
self.timeout = timeout
self.raise_for_status = raise_for_status
self._access_token = None

def family(
Expand Down Expand Up @@ -154,6 +158,7 @@ def legal(
input=input,
)
)

def number(
self,
reference_type: str,
Expand Down Expand Up @@ -395,7 +400,8 @@ def _make_request(
)
response = self._check_for_expired_token(response)
response = self._check_for_exceeded_quota(response)
response.raise_for_status()
if self.raise_for_status:
response.raise_for_status()
return response

# info: {
Expand Down
8 changes: 8 additions & 0 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ def test_instantiate_simple_client():
client = Client("key", "secret")
assert len(client.middlewares) == 1
assert client.middlewares[0].history.db_path == sqlite.DEFAULT_DB_PATH
assert client.raise_for_status is True


def test_instantiate_client_without_raise_for_status():
client = Client("key", "secret", raise_for_status=False)
assert client.raise_for_status is False


def test_family(all_clients):
Expand All @@ -45,9 +51,11 @@ def test_family_legal(all_clients):
def test_image(all_clients):
assert_image_success(all_clients)


def test_legal(all_clients):
assert_legal_success(all_clients)


def test_published_data(all_clients):
assert_published_data_success(all_clients)

Expand Down
67 changes: 67 additions & 0 deletions tests/test_raise_for_status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import pytest
import responses
from pytest import raises
from requests.exceptions import HTTPError

from epo_ops.api import Client
from epo_ops.models import Docdb


@pytest.fixture
def ops_backend_413():
"""
Emulate an OPS backend returning 413 on the fulltext endpoint for an
ambiguous input. The real upstream returns 413 for e.g. EP.0536425/fulltext.
"""
token = responses.Response(
responses.POST,
url="https://ops.epo.org/3.2/auth/accesstoken",
status=200,
json={"access_token": "foo", "expires_in": 42},
)
fulltext_413 = responses.Response(
responses.POST,
url="https://ops.epo.org/3.2/rest-services/published-data/publication/docdb/fulltext",
status=413,
headers={"Content-Type": "application/xml"},
body=(
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n'
' <fault xmlns="http://ops.epo.org">\n'
" <code>CLIENT.AmbiguousRequest</code>\n"
" <message>The request was ambiguous</message>\n"
" <details>\n"
" <cause>Ambiguous input: publication/docdb/EP.0536425</cause>\n"
" <resolution>publication/docdb/EP.0536425.A1</resolution>\n"
" <resolution>publication/docdb/EP.0536425.A4</resolution>\n"
" <resolution>publication/docdb/EP.0536425.B1</resolution>\n"
" <resolution>publication/docdb/EP.0536425.B2</resolution>\n"
" </details>\n"
" </fault>"
),
)
for response in [token, fulltext_413]:
responses.add(response)


def _issue_fulltext_request(client):
return client.published_data(
"publication",
Docdb("0536425", "EP", "B1"),
endpoint="fulltext",
)


@responses.activate
def test_413_raises_by_default(ops_backend_413):
client = Client("key", "secret", middlewares=[])
with raises(HTTPError):
_issue_fulltext_request(client)


@responses.activate
def test_413_returned_when_raise_for_status_disabled(ops_backend_413):
client = Client("key", "secret", middlewares=[], raise_for_status=False)
response = _issue_fulltext_request(client)
assert response.status_code == 413
assert "CLIENT.AmbiguousRequest" in response.text
assert "publication/docdb/EP.0536425.B1" in response.text