Skip to content

Commit 9bb27e9

Browse files
Merge pull request #17 from LeandroDeJesus-S/feature/simplify-base-client
simply and fix client interface
2 parents 92b5df2 + 1f625b2 commit 9bb27e9

6 files changed

Lines changed: 106 additions & 79 deletions

File tree

abacatepay/_base_client.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import requests
22
from typing import Literal
33
from ._constants import USER_AGENT
4+
from .utils._exceptions import raise_for_status, APITimeoutError, APIConnectionError
5+
46

57
class BaseClient:
68
def __init__(self, api_key: str):
@@ -12,7 +14,7 @@ def _request(
1214
method: Literal["GET", "POST", "PUT", "PATCH", "DELETE"] = "GET",
1315
**kwargs,
1416
):
15-
return requests.request(
17+
request = requests.Request(
1618
method,
1719
url,
1820
headers={
@@ -21,3 +23,16 @@ def _request(
2123
},
2224
**kwargs,
2325
)
26+
try:
27+
prepared_request = request.prepare()
28+
with requests.Session() as s:
29+
response = s.send(prepared_request)
30+
31+
raise_for_status(response)
32+
return response
33+
34+
except requests.exceptions.Timeout:
35+
raise APITimeoutError(request=request)
36+
37+
except requests.exceptions.ConnectionError:
38+
raise APIConnectionError(message="Connection error.", request=request)

abacatepay/billing.py

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,8 @@
1-
import requests
21
from ._constants import (
32
BASE_URL,
43
BILLING_KINDS,
54
BILLING_METHODS,
65
)
7-
from .utils._exceptions import (
8-
APITimeoutError,
9-
APIConnectionError,
10-
raise_for_status
11-
)
126
from .models import (
137
Product,
148
BillingResponse,
@@ -62,18 +56,8 @@ def create(
6256
}
6357
)
6458

65-
try:
66-
if response.status_code == 200:
67-
billing_data = BillingResponse(data=response.json()["data"])
68-
return billing_data
69-
raise_for_status(response)
70-
71-
except requests.exceptions.Timeout:
72-
raise APITimeoutError(request=response)
73-
74-
except requests.exceptions.ConnectionError:
75-
raise APIConnectionError(message="Connection error.", request=response)
76-
59+
billing_data = BillingResponse(data=response.json()["data"])
60+
return billing_data
7761

7862
def list(self) -> list[BillingResponse]:
7963
"""
@@ -84,13 +68,4 @@ def list(self) -> list[BillingResponse]:
8468
"""
8569
logger.debug(f"Listing bills with URL: {BASE_URL}/billing/list")
8670
response = self._request(f"{BASE_URL}/billing/list", method="GET")
87-
88-
try:
89-
if response.status_code == 200:
90-
return [BillingResponse(data=bill) for bill in response.json()["data"]]
91-
else:
92-
raise_for_status(response)
93-
except requests.exceptions.Timeout:
94-
raise APITimeoutError(request=response)
95-
except requests.exceptions.ConnectionError:
96-
raise APIConnectionError(message="Connection error", request=response)
71+
return [BillingResponse(data=bill) for bill in response.json()["data"]]

abacatepay/customers.py

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,20 @@
11
from ._constants import (
22
BASE_URL,
33
)
4-
from .utils._exceptions import (
5-
APIConnectionError,
6-
APITimeoutError,
7-
raise_for_status
8-
)
94
from .models import Customer
105
from ._base_client import BaseClient
116
from logging import getLogger
12-
import requests
137

148
logger = getLogger(__name__)
159

10+
1611
class CustomerClient(BaseClient):
1712
def create(self, customer: Customer) -> Customer:
1813
logger.debug(f"Creating customer with URL: {BASE_URL}/customer/create")
1914
response = self._request(f"{BASE_URL}/customer/create", method="POST", json=customer.model_dump())
20-
21-
try:
22-
if response.status_code == 200:
23-
return Customer.from_dict(data=response.json()["data"])
24-
else:
25-
raise_for_status(response)
26-
except requests.exceptions.Timeout:
27-
raise APITimeoutError(request=response)
28-
except requests.exceptions.ConnectionError:
29-
raise APIConnectionError(message="Connection error", request=response)
30-
15+
return Customer.from_dict(data=response.json()["data"])
3116

3217
def list(self) -> list[Customer]:
33-
logger.debug(f"Listing customers with URL: {BASE_URL}/customer/list")
34-
response = self._request(f"{BASE_URL}/customer/list", method="GET")
35-
try:
36-
if response.status_code == 200:
37-
return [Customer.from_dict(data=bill) for bill in response.json()["data"]]
38-
else:
39-
raise_for_status(response)
40-
except requests.exceptions.Timeout:
41-
raise APITimeoutError(request=response)
42-
except requests.exceptions.ConnectionError:
43-
raise APIConnectionError(message="Connection error", request=response)
18+
logger.debug(f"Listing customers with URL: {BASE_URL}/customer/list")
19+
response = self._request(f"{BASE_URL}/customer/list", method="GET")
20+
return [Customer.from_dict(data=bill) for bill in response.json()["data"]]

abacatepay/utils/_exceptions.py

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -127,25 +127,23 @@ def __init__(self, response: requests.Response) -> None:
127127

128128

129129
def raise_for_status(response: requests.Response) -> None:
130-
if response.status_code == 200:
130+
code_exc_dict = {
131+
400: BadRequestError(response=response),
132+
401: UnauthorizedRequest(response=response),
133+
403: ForbiddenRequest(response=response),
134+
404: NotFoundError(response=response),
135+
500: InternalServerError(response=response),
136+
}
137+
138+
code = response.status_code
139+
if code == 200:
131140
return
132-
133-
elif response.status_code == 400:
134-
raise BadRequestError(response=response)
135-
136-
elif response.status_code == 401:
137-
raise ForbiddenRequest(response=response)
138-
139-
elif response.status_code == 403:
140-
raise UnauthorizedRequest(response=response)
141-
142-
elif response.status_code == 404:
143-
raise NotFoundError(response=response)
144-
145-
elif response.status_code == 500:
146-
raise InternalServerError(response=response)
147-
148-
elif response.status_code >= 400:
141+
142+
if code not in code_exc_dict and code >= 400:
149143
raise APIStatusError(message=response.text, response=response)
150-
151-
raise APIError(message=response.text, request=response.request)
144+
145+
raise code_exc_dict.get(
146+
response.status_code,
147+
APIError(message=response.text, request=response.request)
148+
)
149+

tests/test_auth.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import pytest
22
from abacatepay import AbacatePay
3-
from abacatepay.utils._exceptions import ForbiddenRequest
3+
from abacatepay.utils._exceptions import UnauthorizedRequest
44

55

66
def test_wrong_key_running_function(invalid_token_response):
77
rightKey = "Bearer 123456789"
88

99
client = AbacatePay(rightKey)
10-
with pytest.raises(ForbiddenRequest):
10+
with pytest.raises(UnauthorizedRequest):
1111
client.billing.list()

tests/test_base_client.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from http import HTTPStatus as status
2+
3+
import pytest
4+
from requests import Response
5+
from requests.exceptions import ConnectionError, Timeout
6+
7+
from abacatepay._base_client import BaseClient
8+
from abacatepay.utils._exceptions import (
9+
APIConnectionError,
10+
APIError,
11+
APITimeoutError,
12+
BadRequestError,
13+
ForbiddenRequest,
14+
InternalServerError,
15+
NotFoundError,
16+
UnauthorizedRequest,
17+
)
18+
19+
url = "https://api.abacatepay.com/v1/billing/list"
20+
client = BaseClient('fake-api-key')
21+
22+
23+
def test_request_return_the_response_if_status_200(responses):
24+
responses.add(
25+
responses.GET,
26+
url,
27+
status=status.OK,
28+
)
29+
30+
response = client._request(url, "GET")
31+
32+
assert response is not None
33+
assert isinstance(response, Response)
34+
35+
36+
@pytest.mark.parametrize('status_code,exc_classname', [
37+
(status.BAD_REQUEST, BadRequestError),
38+
(status.FORBIDDEN, ForbiddenRequest),
39+
(status.UNAUTHORIZED, UnauthorizedRequest),
40+
(status.NOT_FOUND, NotFoundError),
41+
(status.INTERNAL_SERVER_ERROR, InternalServerError),
42+
(status.IM_A_TEAPOT, APIError)
43+
])
44+
def test_request_raise_the_correct_exception_when_status_is_different_of_200(responses, exc_classname, status_code):
45+
responses.add(
46+
responses.GET,
47+
url,
48+
status=status_code,
49+
)
50+
51+
with pytest.raises(exc_classname):
52+
client._request(url, "GET")
53+
54+
55+
@pytest.mark.parametrize('requests_exc_class,api_exc_class', [
56+
(Timeout, APITimeoutError),
57+
(ConnectionError, APIConnectionError),
58+
])
59+
def test_request_override_requests_timeout_and_connection_error(mocker, requests_exc_class, api_exc_class):
60+
mocker.patch('abacatepay._base_client.requests.Session.send', side_effect=requests_exc_class)
61+
with pytest.raises(api_exc_class):
62+
client._request(url, "GET")

0 commit comments

Comments
 (0)