Skip to content

Commit 2be99fd

Browse files
authored
Merge pull request #6 from cloudblue/cumulative_fixes
Cumulative fixes
2 parents 94a0d2c + 340fd66 commit 2be99fd

12 files changed

Lines changed: 162 additions & 137 deletions

File tree

cnct/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# This file is part of the Ingram Micro Cloud Blue Connect connect-fluent-client.
44
# Copyright (c) 2019-2020 Ingram Micro. All Rights Reserved.
55

6-
from cnct.client.exceptions import APIError, HttpError, NotFoundError # noqa
6+
from cnct.client.exceptions import ClientError, NotFoundError # noqa
77
from cnct.client.fluent import ConnectClient # noqa
88
from cnct.client.version import get_version # noqa
99
from cnct.rql import R # noqa

cnct/client/exceptions.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,30 @@
1-
from requests.exceptions import HTTPError as RequestHttpError
1+
from http import HTTPStatus
22

33

44
class NotFoundError(AttributeError):
55
pass
66

77

8-
class APIError(Exception):
9-
def __init__(self, status_code, error_code, errors):
8+
class ClientError(Exception):
9+
def __init__(self, message=None, status_code=None, error_code=None, errors=None):
10+
self.message = message
1011
self.status_code = status_code
1112
self.error_code = error_code
1213
self.errors = errors
1314

1415
def __repr__(self):
15-
return f'<APIError {self.status_code}: {self.error_code}>'
16+
return f'<ClientError {self.status_code}: {self.error_code}>'
1617

1718
def __str__(self):
18-
errors = ','.join(self.errors)
19-
return f'{self.error_code}: {errors}'
20-
21-
22-
class HttpError(RequestHttpError):
23-
pass
19+
message = self.message or self._get_status_description() or 'Unexpected error'
20+
if self.error_code and self.errors:
21+
errors = ','.join(self.errors)
22+
return f'{message}: {self.error_code} - {errors}'
23+
return message
24+
25+
def _get_status_description(self):
26+
if not self.status_code:
27+
return
28+
status = HTTPStatus(self.status_code)
29+
description = status.name.replace('_', ' ').title()
30+
return f'{self.status_code} {description}'

cnct/client/fluent.py

Lines changed: 32 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
from keyword import iskeyword
33

44
import requests
5+
from requests.exceptions import RequestException
56

67
from cnct.client.constants import CONNECT_ENDPOINT_URL, CONNECT_SPECS_URL
7-
from cnct.client.exceptions import APIError, HttpError, NotFoundError
8+
from cnct.client.exceptions import ClientError, NotFoundError
89
from cnct.client.models import Collection, NS
910
from cnct.client.utils import get_headers
1011
from cnct.help import DefaultFormatter
@@ -143,28 +144,28 @@ def collection(self, name):
143144
raise NotFoundError(f'The collection {name} does not exist.')
144145

145146
def get(self, url, **kwargs):
146-
return self.execute('get', url, 200, **kwargs)
147+
return self.execute('get', url, **kwargs)
147148

148149
def create(self, url, payload=None, **kwargs):
149150
kwargs = kwargs or {}
150151

151152
if payload:
152153
kwargs['json'] = payload
153154

154-
return self.execute('post', url, 201, **kwargs)
155+
return self.execute('post', url, **kwargs)
155156

156157
def update(self, url, payload=None, **kwargs):
157158
kwargs = kwargs or {}
158159

159160
if payload:
160161
kwargs['json'] = payload
161162

162-
return self.execute('put', url, 200, **kwargs)
163+
return self.execute('put', url, **kwargs)
163164

164165
def delete(self, url, **kwargs):
165-
return self.execute('delete', url, 204, **kwargs)
166+
return self.execute('delete', url, **kwargs)
166167

167-
def execute(self, method, url, expected_status, **kwargs):
168+
def execute(self, method, url, **kwargs):
168169
kwargs = kwargs or {}
169170
if 'headers' in kwargs:
170171
kwargs['headers'].update(get_headers(self.api_key))
@@ -174,48 +175,36 @@ def execute(self, method, url, expected_status, **kwargs):
174175
if self.default_headers:
175176
kwargs['headers'].update(self.default_headers)
176177

177-
self.response = requests.request(
178-
method,
179-
url,
180-
**kwargs,
181-
)
182-
183-
if self.response.status_code != expected_status:
184-
try:
185-
error = self.response.json()
186-
if 'error_code' in error and 'errors' in error:
187-
raise APIError(
188-
self.response.status_code,
189-
error['error_code'],
190-
error['errors'],
191-
)
192-
except JSONDecodeError:
193-
pass
194-
195-
self._raise_exception()
178+
self.response = None
196179

197-
if self.response.status_code == 204:
198-
return
180+
try:
181+
self._execute_http_call(method, url, kwargs)
182+
if self.response.status_code == 204:
183+
return None
184+
if self.response.headers['Content-Type'] == 'application/json':
185+
return self.response.json()
186+
else:
187+
return self.response.content
199188

200-
return self.response.json()
189+
except RequestException as re:
190+
api_error = self._get_api_error_details() or {}
191+
status_code = self.response.status_code if self.response is not None else None
192+
raise ClientError(status_code=status_code, **api_error) from re
201193

202194
def help(self):
203195
self._help_formatter.print_help(self.specs)
204196
return self
205197

206-
def _raise_exception(self):
207-
message = ''
208-
if isinstance(self.response.reason, bytes):
209-
try:
210-
reason = self.response.reason.decode('utf-8')
211-
except UnicodeDecodeError:
212-
reason = self.response.reason.decode('iso-8859-1')
213-
else:
214-
reason = self.response.reason
198+
def _execute_http_call(self, method, url, kwargs):
199+
self.response = requests.request(method, url, **kwargs)
200+
if self.response.status_code >= 400:
201+
self.response.raise_for_status()
215202

216-
if 400 <= self.response.status_code < 500:
217-
message = f'{self.response.status_code} Client Error: {reason}'
218-
elif 500 <= self.response.status_code < 600:
219-
message = f'{self.response.status_code} Server Error: {reason}'
220-
221-
raise HttpError(message, response=self.response)
203+
def _get_api_error_details(self):
204+
if self.response is not None:
205+
try:
206+
error = self.response.json()
207+
if 'error_code' in error and 'errors' in error:
208+
return error
209+
except JSONDecodeError:
210+
pass

cnct/client/models/resourceset.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,17 @@ def __iter__(self):
2222
try:
2323
results, cr = self._execute_request()
2424
except StopIteration:
25-
return
25+
pass
2626

27-
if not (results and cr):
27+
if not results:
2828
return
2929

3030
for item in results:
3131
yield self.get_item(item)
32+
33+
if not cr:
34+
# endpoint doesn't support pagination
35+
return
3236
self._config['params']['offset'] += self._config['params']['limit']
3337

3438
def _execute_request(self):

docs/reference.rst

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,7 @@ RQL utility
5353
Exceptions
5454
----------
5555

56-
.. autoclass:: cnct.client.exceptions.APIError
57-
:members:
58-
59-
.. autoclass:: cnct.client.exceptions.HttpError
56+
.. autoclass:: cnct.client.exceptions.ClientError
6057
:members:
6158

6259
.. autoclass:: cnct.client.exceptions.NotFoundError

poetry.lock

Lines changed: 21 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ python = "^3.6"
1313
connect-markdown-renderer = "^1.0.0"
1414
PyYAML = "^5.3.1"
1515
requests = "^2.24.0"
16+
responses = "^0.12.0"
1617

1718
[tool.poetry.dev-dependencies]
1819
pytest = "^6.1.2"
1920
pytest-cov = "^2.10.1"
2021
pytest-mock = "^3.3.1"
21-
requests-mock = "^1.8.0"
2222
coverage = {extras = ["toml"], version = "^5.3"}
2323
flakehell = "^0.7.0"
2424
Sphinx = "^3.2.1"

tests/client/test_exceptions.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
from cnct.client.exceptions import APIError
1+
from cnct.client.exceptions import ClientError
22

33

44
def test_connect_error():
5-
c = APIError(400, 'error_code', ['msg1', 'msg2'])
5+
c = ClientError(status_code=400, error_code='error_code', errors=['msg1', 'msg2'])
66

7-
assert repr(c) == '<APIError 400: error_code>'
8-
assert str(c) == 'error_code: msg1,msg2'
7+
assert repr(c) == '<ClientError 400: error_code>'
8+
assert str(c) == '400 Bad Request: error_code - msg1,msg2'

0 commit comments

Comments
 (0)