2727
2828class GeocodioClient :
2929 BASE_PATH = "/v1.8" # keep in sync with Geocodio's current version
30+ DEFAULT_SINGLE_TIMEOUT = 5.0
31+ DEFAULT_BATCH_TIMEOUT = 1800.0 # 30 minutes
32+ LIST_API_TIMEOUT = 60.0
3033
3134 @staticmethod
3235 def get_status_exception_mappings () -> Dict [
@@ -43,13 +46,23 @@ def get_status_exception_mappings() -> Dict[
4346 500 : GeocodioServerError ,
4447 }
4548
46- def __init__ (self , api_key : Optional [str ] = None , hostname : str = "api.geocod.io" ):
49+ def __init__ (
50+ self ,
51+ api_key : Optional [str ] = None ,
52+ hostname : str = "api.geocod.io" ,
53+ single_timeout : Optional [float ] = None ,
54+ batch_timeout : Optional [float ] = None ,
55+ list_timeout : Optional [float ] = None ,
56+ ):
4757 self .api_key : str = api_key or os .getenv ("GEOCODIO_API_KEY" , "" )
4858 if not self .api_key :
4959 raise AuthenticationError (
5060 detail = "No API key supplied and GEOCODIO_API_KEY is not set."
5161 )
5262 self .hostname = hostname .rstrip ("/" )
63+ self .single_timeout = single_timeout or self .DEFAULT_SINGLE_TIMEOUT
64+ self .batch_timeout = batch_timeout or self .DEFAULT_BATCH_TIMEOUT
65+ self .list_timeout = list_timeout or self .LIST_API_TIMEOUT
5366 self ._http = httpx .Client (base_url = f"https://{ self .hostname } " )
5467
5568 # ──────────────────────────────────────────────────────────────────────────
@@ -108,7 +121,8 @@ def geocode(
108121 params ["q" ] = address
109122 data = None
110123
111- response = self ._request ("POST" if data else "GET" , endpoint , params , json = data )
124+ timeout = self .batch_timeout if data else self .single_timeout
125+ response = self ._request ("POST" if data else "GET" , endpoint , params , json = data , timeout = timeout )
112126 return self ._parse_geocoding_response (response .json ())
113127
114128 def reverse (
@@ -138,7 +152,8 @@ def reverse(
138152 params ["q" ] = coordinate # "lat,lng"
139153 data = None
140154
141- response = self ._request ("POST" if data else "GET" , endpoint , params , json = data )
155+ timeout = self .batch_timeout if data else self .single_timeout
156+ response = self ._request ("POST" if data else "GET" , endpoint , params , json = data , timeout = timeout )
142157 return self ._parse_geocoding_response (response .json ())
143158
144159 # ──────────────────────────────────────────────────────────────────────────
@@ -152,13 +167,18 @@ def _request(
152167 params : dict ,
153168 json : Optional [dict ] = None ,
154169 files : Optional [dict ] = None ,
170+ timeout : Optional [float ] = None ,
155171 ) -> httpx .Response :
156172 logger .debug (f"Making Request: { method } { endpoint } " )
157173 logger .debug (f"Params: { params } " )
158174 logger .debug (f"JSON body: { json } " )
159175 logger .debug (f"Files: { files } " )
160176
161- resp = self ._http .request (method , endpoint , params = params , json = json , files = files , timeout = 30 )
177+ if timeout is None :
178+ timeout = self .single_timeout
179+
180+ logger .debug (f"Using timeout: { timeout } s" )
181+ resp = self ._http .request (method , endpoint , params = params , json = json , files = files , timeout = timeout )
162182
163183 logger .debug (f"Response status code: { resp .status_code } " )
164184 logger .debug (f"Response headers: { resp .headers } " )
@@ -284,7 +304,7 @@ def create_list(
284304 # Join fields with commas as required by the API
285305 params ["fields" ] = "," .join (fields )
286306
287- response = self ._request ("POST" , endpoint , params , files = files )
307+ response = self ._request ("POST" , endpoint , params , files = files , timeout = self . list_timeout )
288308 logger .debug (f"Response content: { response .text } " )
289309 return self ._parse_list_response (response .json (), response = response )
290310
@@ -298,7 +318,7 @@ def get_lists(self) -> PaginatedResponse:
298318 params : Dict [str , Union [str , int ]] = {"api_key" : self .api_key }
299319 endpoint = f"{ self .BASE_PATH } /lists"
300320
301- response = self ._request ("GET" , endpoint , params )
321+ response = self ._request ("GET" , endpoint , params , timeout = self . list_timeout )
302322 pagination_info = response .json ()
303323
304324 logger .debug (f"Pagination info: { pagination_info } " )
@@ -333,7 +353,7 @@ def get_list(self, list_id: str) -> ListResponse:
333353 params : Dict [str , Union [str , int ]] = {"api_key" : self .api_key }
334354 endpoint = f"{ self .BASE_PATH } /lists/{ list_id } "
335355
336- response = self ._request ("GET" , endpoint , params )
356+ response = self ._request ("GET" , endpoint , params , timeout = self . list_timeout )
337357 return self ._parse_list_response (response .json (), response = response )
338358
339359 def delete_list (self , list_id : str ) -> None :
@@ -346,7 +366,7 @@ def delete_list(self, list_id: str) -> None:
346366 params : Dict [str , Union [str , int ]] = {"api_key" : self .api_key }
347367 endpoint = f"{ self .BASE_PATH } /lists/{ list_id } "
348368
349- self ._request ("DELETE" , endpoint , params )
369+ self ._request ("DELETE" , endpoint , params , timeout = self . list_timeout )
350370
351371 @staticmethod
352372 def _parse_list_response (response_json : dict , response : httpx .Response = None ) -> ListResponse :
@@ -515,7 +535,7 @@ def download(self, list_id: str, filename: Optional[str] = None) -> str | bytes:
515535 params = {"api_key" : self .api_key }
516536 endpoint = f"{ self .BASE_PATH } /lists/{ list_id } /download"
517537
518- response : httpx .Response = self ._request ("GET" , endpoint , params )
538+ response : httpx .Response = self ._request ("GET" , endpoint , params , timeout = self . list_timeout )
519539 if response .headers .get ("content-type" , "" ).startswith ("application/json" ):
520540 try :
521541 error = response .json ()
0 commit comments