11from json import JSONDecodeError
2+ from types import TracebackType
23from typing import Dict
34from typing import List
45from typing import Optional
89import httpx
910from httpx import Request
1011from httpx import Response
12+ from httpx ._types import URLTypes
1113
14+ from . import endpoints
15+ from . import errors
1216from . import models
13- from .errors import CheckedIDAuthenticationError
14- from .errors import CheckedIDError
15- from .errors import CheckedIDNotFoundError
16- from .errors import CheckedIDValidationError
1717
1818
1919_T = TypeVar ("_T" )
2020
2121
22- class Client :
23- ERROR_RESPONSE_MAPPING : Dict [int , Type [CheckedIDError ]] = {
24- 422 : CheckedIDValidationError ,
25- 403 : CheckedIDAuthenticationError ,
26- 404 : CheckedIDNotFoundError ,
22+ class BaseClient :
23+ ERROR_RESPONSE_MAPPING : Dict [int , Type [errors . CheckedIDError ]] = {
24+ 422 : errors . CheckedIDValidationError ,
25+ 403 : errors . CheckedIDAuthenticationError ,
26+ 404 : errors . CheckedIDNotFoundError ,
2727 }
2828
2929 def __init__ (self , customer_code : str , base_url : str = "https://api.checkedid.eu/" ):
30- self .httpx = httpx . Client ( base_url = base_url , auth = self . authenticate_request )
30+ self .base_url = base_url
3131 self .access_token : Optional [str ] = None
3232 self .customer_code = customer_code
33+ self .create_client (base_url )
34+
35+ def create_client (self , base_url : URLTypes ) -> None :
36+ raise NotImplementedError
3337
3438 def authenticate_request (self , request : Request ) -> Request :
3539 if self .access_token :
@@ -46,10 +50,38 @@ def process_response(
4650
4751 return None
4852
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+
4981 def oauth_token (
5082 self , grant_type : str , username : str , password : str
5183 ) -> Optional [models .OAuthToken ]:
52- response = self .httpx .post (
84+ response = self .client .post (
5385 "/oauth/token" ,
5486 data = {"grant_type" : grant_type , "username" : username , "password" : password },
5587 )
@@ -62,8 +94,14 @@ def oauth_token(
6294 return typed_response
6395 return None
6496
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+
65103 def invitation_status (self , invitation_code : str ) -> Optional [models .Invitation ]:
66- response : Response = self .httpx .get (
104+ response : Response = self .client .get (
67105 f"/result/status/{ invitation_code } " ,
68106 headers = {"Accept" : "application/json" },
69107 )
@@ -77,7 +115,7 @@ def invitations_create(
77115 CustomerCode = self .customer_code , Invitations = invitations
78116 )
79117
80- response : Response = self .httpx .post (
118+ response : Response = self .client .post (
81119 "/invitations" ,
82120 json = obj .dict (),
83121 headers = {"Accept" : "application/json" , "Content-Type" : "application/json" },
@@ -86,7 +124,7 @@ def invitations_create(
86124 return self .process_response (response , models .CustomerDetails )
87125
88126 def invitation_delete (self , invitation_code : str ) -> bool :
89- response : Response = self .httpx .delete (
127+ response : Response = self .client .delete (
90128 f"/invitation/{ self .customer_code } /{ invitation_code } " ,
91129 headers = {"Accept" : "application/json" },
92130 )
@@ -99,37 +137,69 @@ def invitation_delete(self, invitation_code: str) -> bool:
99137 return False
100138
101139 def dossier (self , dossier_number : str ) -> Optional [models .ReportResponse ]:
102- response = self .httpx .get (f"/report/{ dossier_number } " )
140+ response = self .client .get (f"/report/{ dossier_number } " )
103141
104142 return self .process_response (response , models .ReportResponse )
105143
106144 def dossier_with_scope (
107145 self , dossier_number : str , scope : str
108146 ) -> Optional [models .ReportDataV3 ]:
109- response = self .httpx .get (f"/reportdata/{ dossier_number } /{ scope } " )
147+ response = self .client .get (f"/reportdata/{ dossier_number } /{ scope } " )
110148
111149 return self .process_response (response , models .ReportDataV3 )
112150
113- def handle_error_response (self , response : Response ) -> None :
114- if response .status_code == 400 :
115- raise CheckedIDValidationError (
116- response .text , status_code = response .status_code
117- )
118151
119- try :
120- json = response .json ()
121- except JSONDecodeError :
122- json = {"message" : response .text }
152+ class ClientAsync (BaseClient ):
153+ """for asyncio"""
123154
124- json [ "status_code" ] = response . status_code
155+ client : httpx . AsyncClient
125156
126- exception_type = self .map_exception (response )
127- raise exception_type (
128- status_code = response .status_code , json = json , message = "Error from server"
157+ def __init__ (self , customer_code : str , base_url : str = "https://api.checkedid.eu/" ):
158+ super ().__init__ (customer_code , base_url )
159+
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 },
129166 )
130167
131- def map_exception (self , response : Response ) -> Type [CheckedIDError ]:
132- exception_type = self .ERROR_RESPONSE_MAPPING .get (
133- response .status_code , CheckedIDError
168+ typed_response = self .process_response (response , models .OAuthToken )
169+
170+ if typed_response :
171+ self .access_token = typed_response .access_token
172+
173+ return typed_response
174+ return None
175+
176+ def create_client (self , base_url : URLTypes ) -> None :
177+ self .client = httpx .AsyncClient (base_url = base_url )
178+
179+ async def dossier (self , dossier_number : str ) -> Optional [models .ReportResponse ]:
180+ response = await self .client .get (
181+ url = endpoints .DossierEndpoint .url (dossier_number = dossier_number )
134182 )
135- return exception_type
183+
184+ return self .process_response (response , endpoints .DossierEndpoint .response )
185+
186+ async def close (self ) -> None :
187+ if self .client :
188+ await self .client .aclose ()
189+
190+ def open (self ) -> None :
191+ self .create_client (self .base_url )
192+
193+ async def __aenter__ (self ) -> "ClientAsync" :
194+ """Open the httpx client"""
195+ self .open ()
196+ return self
197+
198+ async def __aexit__ (
199+ self ,
200+ exc_type : Optional [Type [BaseException ]] = None ,
201+ exc_value : Optional [BaseException ] = None ,
202+ traceback : Optional [TracebackType ] = None ,
203+ ) -> None :
204+ """Close the httpx client"""
205+ await self .close ()
0 commit comments