Skip to content

Commit 33fd801

Browse files
committed
feat: BaseClient to share methods between Client & ClientAsync
1 parent 145c4da commit 33fd801

2 files changed

Lines changed: 65 additions & 37 deletions

File tree

src/checkedid/client.py

Lines changed: 64 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,21 @@
1919
_T = TypeVar("_T")
2020

2121

22-
class Client:
22+
class BaseClient:
2323
ERROR_RESPONSE_MAPPING: Dict[int, Type[errors.CheckedIDError]] = {
2424
422: errors.CheckedIDValidationError,
2525
403: errors.CheckedIDAuthenticationError,
2626
404: errors.CheckedIDNotFoundError,
2727
}
2828

2929
def __init__(self, customer_code: str, base_url: str = "https://api.checkedid.eu/"):
30-
self.client: Optional[httpx.Client] = None
3130
self.base_url = base_url
32-
self.create_client(base_url)
3331
self.access_token: Optional[str] = None
3432
self.customer_code = customer_code
33+
self.create_client(base_url)
3534

3635
def create_client(self, base_url: URLTypes) -> None:
37-
self.httpx = httpx.Client(base_url=base_url, auth=self.authenticate_request)
36+
raise NotImplementedError
3837

3938
def authenticate_request(self, request: Request) -> Request:
4039
if self.access_token:
@@ -51,10 +50,38 @@ def process_response(
5150

5251
return None
5352

53+
def handle_error_response(self, response: Response) -> None:
54+
if response.status_code == 400:
55+
raise errors.CheckedIDValidationError(
56+
response.text, status_code=response.status_code
57+
)
58+
59+
try:
60+
json = response.json()
61+
except JSONDecodeError:
62+
json = {"message": response.text}
63+
64+
json["status_code"] = response.status_code
65+
66+
exception_type = self.map_exception(response)
67+
raise exception_type(
68+
status_code=response.status_code, json=json, message="Error from server"
69+
)
70+
71+
def map_exception(self, response: Response) -> Type[errors.CheckedIDError]:
72+
exception_type = self.ERROR_RESPONSE_MAPPING.get(
73+
response.status_code, errors.CheckedIDError
74+
)
75+
return exception_type
76+
77+
78+
class Client(BaseClient):
79+
client: httpx.Client
80+
5481
def oauth_token(
5582
self, grant_type: str, username: str, password: str
5683
) -> Optional[models.OAuthToken]:
57-
response = self.httpx.post(
84+
response = self.client.post(
5885
"/oauth/token",
5986
data={"grant_type": grant_type, "username": username, "password": password},
6087
)
@@ -67,8 +94,14 @@ def oauth_token(
6794
return typed_response
6895
return None
6996

97+
def __init__(self, customer_code: str, base_url: str = "https://api.checkedid.eu/"):
98+
super().__init__(customer_code, base_url)
99+
100+
def create_client(self, base_url: URLTypes) -> None:
101+
self.client = httpx.Client(base_url=base_url, auth=self.authenticate_request)
102+
70103
def invitation_status(self, invitation_code: str) -> Optional[models.Invitation]:
71-
response: Response = self.httpx.get(
104+
response: Response = self.client.get(
72105
f"/result/status/{invitation_code}",
73106
headers={"Accept": "application/json"},
74107
)
@@ -82,7 +115,7 @@ def invitations_create(
82115
CustomerCode=self.customer_code, Invitations=invitations
83116
)
84117

85-
response: Response = self.httpx.post(
118+
response: Response = self.client.post(
86119
"/invitations",
87120
json=obj.dict(),
88121
headers={"Accept": "application/json", "Content-Type": "application/json"},
@@ -91,7 +124,7 @@ def invitations_create(
91124
return self.process_response(response, models.CustomerDetails)
92125

93126
def invitation_delete(self, invitation_code: str) -> bool:
94-
response: Response = self.httpx.delete(
127+
response: Response = self.client.delete(
95128
f"/invitation/{self.customer_code}/{invitation_code}",
96129
headers={"Accept": "application/json"},
97130
)
@@ -104,60 +137,55 @@ def invitation_delete(self, invitation_code: str) -> bool:
104137
return False
105138

106139
def dossier(self, dossier_number: str) -> Optional[models.ReportResponse]:
107-
response = self.httpx.get(f"/report/{dossier_number}")
140+
response = self.client.get(f"/report/{dossier_number}")
108141

109142
return self.process_response(response, models.ReportResponse)
110143

111144
def dossier_with_scope(
112145
self, dossier_number: str, scope: str
113146
) -> Optional[models.ReportDataV3]:
114-
response = self.httpx.get(f"/reportdata/{dossier_number}/{scope}")
147+
response = self.client.get(f"/reportdata/{dossier_number}/{scope}")
115148

116149
return self.process_response(response, models.ReportDataV3)
117150

118-
def handle_error_response(self, response: Response) -> None:
119-
if response.status_code == 400:
120-
raise errors.CheckedIDValidationError(
121-
response.text, status_code=response.status_code
122-
)
123151

124-
try:
125-
json = response.json()
126-
except JSONDecodeError:
127-
json = {"message": response.text}
152+
class ClientAsync(BaseClient):
153+
"""for asyncio"""
128154

129-
json["status_code"] = response.status_code
155+
client: httpx.AsyncClient
130156

131-
exception_type = self.map_exception(response)
132-
raise exception_type(
133-
status_code=response.status_code, json=json, message="Error from server"
134-
)
157+
def __init__(self, customer_code: str, base_url: str = "https://api.checkedid.eu/"):
158+
super().__init__(customer_code, base_url)
135159

136-
def map_exception(self, response: Response) -> Type[errors.CheckedIDError]:
137-
exception_type = self.ERROR_RESPONSE_MAPPING.get(
138-
response.status_code, errors.CheckedIDError
160+
async def oauth_token(
161+
self, grant_type: str, username: str, password: str
162+
) -> Optional[models.OAuthToken]:
163+
response = await self.client.post(
164+
"/oauth/token",
165+
data={"grant_type": grant_type, "username": username, "password": password},
139166
)
140-
return exception_type
141167

168+
typed_response = self.process_response(response, models.OAuthToken)
142169

143-
class ClientAsync(Client):
144-
"""for asyncio"""
170+
if typed_response:
171+
self.access_token = typed_response.access_token
145172

146-
aclient: httpx.AsyncClient
173+
return typed_response
174+
return None
147175

148176
def create_client(self, base_url: URLTypes) -> None:
149-
self.aclient = httpx.AsyncClient(base_url=base_url)
177+
self.client = httpx.AsyncClient(base_url=base_url)
150178

151-
async def adossier(self, dossier_number: str) -> Optional[models.ReportResponse]:
152-
response = await self.aclient.get(
179+
async def dossier(self, dossier_number: str) -> Optional[models.ReportResponse]:
180+
response = await self.client.get(
153181
url=endpoints.DossierEndpoint.url(dossier_number=dossier_number)
154182
)
155183

156184
return self.process_response(response, endpoints.DossierEndpoint.response)
157185

158186
async def close(self) -> None:
159-
if self.aclient:
160-
await self.aclient.aclose()
187+
if self.client:
188+
await self.client.aclose()
161189

162190
def open(self) -> None:
163191
self.create_client(self.base_url)

tests/test_async_dossiers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ async def test_dossiers(customer_code, dossier_response_200):
88
client = ClientAsync(customer_code)
99

1010
with client as client:
11-
response = await client.dossier("999999-8888800")
11+
response = await client.adossier("999999-8888800")
1212

1313
assert response.DossierNumber == "999999-8888800"

0 commit comments

Comments
 (0)