11import requests
2- from requests import JSONDecodeError , Response , Timeout , TooManyRedirects , codes
2+ from requests import JSONDecodeError , Response , codes
33
44
55class ResourceNotFound (Exception ):
@@ -10,6 +10,10 @@ class ResourceForbidden(Exception):
1010 pass
1111
1212
13+ class ResourceUnauthorized (Exception ):
14+ pass
15+
16+
1317class SimpleJsonApiClient :
1418 """
1519 A simple JSON API client that provides basic functionality for making GET requests to a specified API endpoint.
@@ -19,101 +23,119 @@ class SimpleJsonApiClient:
1923 The client also includes error handling for connection issues, timeouts, and non-JSON responses.
2024
2125 :param api_url: The base URL of the API.
22- :param defaultHeaders : Optional dictionary of default headers to include in every request.
23- :param defaultParams : Optional dictionary of default parameters to include in every request.
26+ :param default_headers : Optional dictionary of default headers to include in every request.
27+ :param default_params : Optional dictionary of default parameters to include in every request.
2428 """
2529
2630 def __init__ (
27- self , api_url : str , defaultHeaders : dict = {}, defaultParams : dict = {}
31+ self , api_url : str , default_headers : dict = {}, default_params : dict = {}
2832 ):
2933 self .api_url : str = api_url .rstrip ("/" )
3034 self .headers : dict = (
3135 {
3236 "Cache-Control" : "no-cache" ,
3337 "Accept" : "application/json" ,
3438 }
35- if defaultHeaders
36- else defaultHeaders
39+ if default_headers
40+ else default_headers
3741 )
38- self .params : dict = defaultParams
42+ self .params : dict = default_params
3943
40- def add_parameter (self , key : str , value ):
44+ def add_default_parameter (self , key : str , value ) -> "SimpleJsonApiClient" :
4145 """
42- Add a single parameter to the request .
46+ Add a single default parameter to the requests .
4347 """
4448
4549 self .params [key ] = value
50+ return self
4651
47- def add_parameters (self , params : dict ):
52+ def add_default_parameters (self , params : dict ) -> "SimpleJsonApiClient" :
4853 """
49- Add multiple parameters to the request .
54+ Add multiple default parameters to the requests .
5055 """
5156
5257 self .params = self .params | params
58+ return self
5359
54- def add_header (self , key : str , value ):
60+ def add_default_header (self , key : str , value ) -> "SimpleJsonApiClient" :
5561 """
56- Add a single header to the request .
62+ Add a single default header to the requests .
5763 """
5864
5965 self .headers [key ] = value
66+ return self
6067
61- def add_headers (self , headers : dict ):
68+ def add_default_headers (self , headers : dict ) -> "SimpleJsonApiClient" :
6269 """
63- Add multiple headers to the request .
70+ Add multiple default headers to the requests .
6471 """
6572
6673 self .headers = self .headers | headers
74+ return self
75+
76+ def _normalise_url (self , path : str ) -> str :
77+ """
78+ Normalise a URL, avoiding duplicated slashes
79+ """
6780
68- def get (self , path : str = "/" ):
81+ return f"{ self .api_url } /{ path .lstrip ('/' )} "
82+
83+ def get (
84+ self ,
85+ path : str = "/" ,
86+ params : dict | None = None ,
87+ headers : dict | None = None ,
88+ timeout : int = 10 ,
89+ ) -> dict :
6990 """
7091 Make a GET request to the specified path of the API endpoint.
92+
93+ :param path: The path to append to the base API URL for the request.
94+ :param params: Optional dictionary of query parameters to include in the request. These will be merged with any default parameters set for the client.
95+ :param headers: Optional dictionary of headers to include in the request. These will be merged with any default headers set for the client.
7196 """
7297
73- url = f"{ self .api_url } /{ path .lstrip ('/' )} "
74- try :
75- response = requests .get (
76- url ,
77- params = self .params ,
78- headers = self .headers ,
79- )
80- except ConnectionError :
81- raise Exception ("A connection error occured" )
82- except Timeout :
83- raise Exception ("The request timed out" )
84- except TooManyRedirects :
85- raise Exception ("Too many redirects" )
86- except Exception as e :
87- raise Exception (e )
98+ url = self ._normalise_url (path )
99+ response = requests .get (
100+ url ,
101+ params = self .params if params is None else {** self .params , ** params },
102+ headers = self .headers if headers is None else {** self .headers , ** headers },
103+ timeout = timeout ,
104+ )
88105 return self ._handle_response (response )
89106
90107 def post (
91- self , path : str = "/" , data : dict | None = None , json : dict | str | None = None
92- ):
108+ self ,
109+ path : str = "/" ,
110+ data : dict | None = None ,
111+ json : dict | str | None = None ,
112+ params : dict | None = None ,
113+ headers : dict | None = None ,
114+ timeout : int = 10 ,
115+ ) -> dict :
93116 """
94117 Make a POST request to the specified path of the API endpoint.
118+
119+ :param path: The path to append to the base API URL for the request.
120+ :param data: Optional dictionary, list of tuples, bytes, or file-like
121+ object to include in the request body.
122+ :param json: Optional JSON serialisable Python object to send in the request body.
123+ :param params: Optional dictionary of query parameters to include in the request. These will be merged with any default parameters set for the client.
124+ :param headers: Optional dictionary of headers to include in the request. These will be merged with any default headers set for the client.
95125 """
96126
97- url = f"{ self .api_url } /{ path .lstrip ('/' )} "
98- try :
99- response = requests .post (
100- url ,
101- params = self .params ,
102- headers = self .headers ,
103- data = data ,
104- json = json ,
105- )
106- except ConnectionError :
107- raise Exception ("A connection error occured" )
108- except Timeout :
109- raise Exception ("The request timed out" )
110- except TooManyRedirects :
111- raise Exception ("Too many redirects" )
112- except Exception as e :
113- raise Exception (e )
127+ url = self ._normalise_url (path )
128+ response = requests .post (
129+ url ,
130+ params = self .params if params is None else {** self .params , ** params },
131+ headers = self .headers if headers is None else {** self .headers , ** headers },
132+ data = data ,
133+ json = json ,
134+ timeout = timeout ,
135+ )
114136 return self ._handle_response (response )
115137
116- def _handle_response (self , response : Response ):
138+ def _handle_response (self , response : Response ) -> dict :
117139 """
118140 Handle the API response, checking for common HTTP status codes and returning the JSON content if the request was successful.
119141 """
@@ -125,8 +147,10 @@ def _handle_response(self, response: Response):
125147 raise Exception ("Non-JSON response provided" )
126148 if response .status_code == 400 :
127149 raise Exception ("Bad request" )
150+ if response .status_code == 401 :
151+ raise ResourceUnauthorized ("Unauthorised" )
128152 if response .status_code == 403 :
129153 raise ResourceForbidden ("Forbidden" )
130154 if response .status_code == 404 :
131155 raise ResourceNotFound ("Resource not found" )
132- raise Exception ("Request failed" )
156+ raise Exception (f "Request failed with { response . status_code } " )
0 commit comments