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 (
@@ -144,7 +158,8 @@ def reverse(
144158 params ["q" ] = coordinate # "lat,lng"
145159 data = None
146160
147- response = self ._request ("POST" if data else "GET" , endpoint , params , json = data )
161+ timeout = self .batch_timeout if data else self .single_timeout
162+ response = self ._request ("POST" if data else "GET" , endpoint , params , json = data , timeout = timeout )
148163 return self ._parse_geocoding_response (response .json ())
149164
150165 # ──────────────────────────────────────────────────────────────────────────
@@ -158,13 +173,18 @@ def _request(
158173 params : dict ,
159174 json : Optional [dict ] = None ,
160175 files : Optional [dict ] = None ,
176+ timeout : Optional [float ] = None ,
161177 ) -> httpx .Response :
162178 logger .debug (f"Making Request: { method } { endpoint } " )
163179 logger .debug (f"Params: { params } " )
164180 logger .debug (f"JSON body: { json } " )
165181 logger .debug (f"Files: { files } " )
166182
167- resp = self ._http .request (method , endpoint , params = params , json = json , files = files , timeout = 60 )
183+ if timeout is None :
184+ timeout = self .single_timeout
185+
186+ logger .debug (f"Using timeout: { timeout } s" )
187+ resp = self ._http .request (method , endpoint , params = params , json = json , files = files , timeout = timeout )
168188
169189 logger .debug (f"Response status code: { resp .status_code } " )
170190 logger .debug (f"Response headers: { resp .headers } " )
@@ -290,7 +310,7 @@ def create_list(
290310 # Join fields with commas as required by the API
291311 params ["fields" ] = "," .join (fields )
292312
293- response = self ._request ("POST" , endpoint , params , files = files )
313+ response = self ._request ("POST" , endpoint , params , files = files , timeout = self . list_timeout )
294314 logger .debug (f"Response content: { response .text } " )
295315 return self ._parse_list_response (response .json (), response = response )
296316
@@ -304,7 +324,7 @@ def get_lists(self) -> PaginatedResponse:
304324 params : Dict [str , Union [str , int ]] = {"api_key" : self .api_key }
305325 endpoint = f"{ self .BASE_PATH } /lists"
306326
307- response = self ._request ("GET" , endpoint , params )
327+ response = self ._request ("GET" , endpoint , params , timeout = self . list_timeout )
308328 pagination_info = response .json ()
309329
310330 logger .debug (f"Pagination info: { pagination_info } " )
@@ -339,7 +359,7 @@ def get_list(self, list_id: str) -> ListResponse:
339359 params : Dict [str , Union [str , int ]] = {"api_key" : self .api_key }
340360 endpoint = f"{ self .BASE_PATH } /lists/{ list_id } "
341361
342- response = self ._request ("GET" , endpoint , params )
362+ response = self ._request ("GET" , endpoint , params , timeout = self . list_timeout )
343363 return self ._parse_list_response (response .json (), response = response )
344364
345365 def delete_list (self , list_id : str ) -> None :
@@ -352,7 +372,7 @@ def delete_list(self, list_id: str) -> None:
352372 params : Dict [str , Union [str , int ]] = {"api_key" : self .api_key }
353373 endpoint = f"{ self .BASE_PATH } /lists/{ list_id } "
354374
355- self ._request ("DELETE" , endpoint , params )
375+ self ._request ("DELETE" , endpoint , params , timeout = self . list_timeout )
356376
357377 @staticmethod
358378 def _parse_list_response (response_json : dict , response : httpx .Response = None ) -> ListResponse :
@@ -521,7 +541,7 @@ def download(self, list_id: str, filename: Optional[str] = None) -> str | bytes:
521541 params = {"api_key" : self .api_key }
522542 endpoint = f"{ self .BASE_PATH } /lists/{ list_id } /download"
523543
524- response : httpx .Response = self ._request ("GET" , endpoint , params )
544+ response : httpx .Response = self ._request ("GET" , endpoint , params , timeout = self . list_timeout )
525545 if response .headers .get ("content-type" , "" ).startswith ("application/json" ):
526546 try :
527547 error = response .json ()
0 commit comments