1+ import time
2+
13import requests
24from requests .adapters import HTTPAdapter
35from urllib3 .util .retry import Retry
46
57from libzapi .domain .errors import NotFound , RateLimited , Unauthorized , UnprocessableEntity
68
9+ CONNECTION_MAX_AGE = 300 # 5 minutes
10+
711
812class HttpClient :
913 def __init__ (self , base_url : str , headers : dict [str , str ], timeout : float = 30.0 ) -> None :
1014 self .base_url = base_url .rstrip ("/" )
11- self .session = requests .Session ()
12- self .session .headers .update (
13- {
14- "Accept" : "application/json" ,
15- "Content-Type" : "application/json" ,
16- ** headers ,
17- }
18- )
19- retry = Retry (
15+ self ._headers = {
16+ "Accept" : "application/json" ,
17+ "Content-Type" : "application/json" ,
18+ ** headers ,
19+ }
20+ self ._retry = Retry (
2021 total = 5 ,
2122 backoff_factor = 0.3 ,
2223 status_forcelist = [429 , 500 , 502 , 503 , 504 ],
2324 respect_retry_after_header = True ,
2425 )
25- adapter = HTTPAdapter (max_retries = retry )
26- self .session .mount ("https://" , adapter )
2726 self .timeout = timeout
27+ self ._last_refresh = 0.0
28+ self .session = self ._new_session ()
29+
30+ def _new_session (self ) -> requests .Session :
31+ session = requests .Session ()
32+ session .headers .update (self ._headers )
33+ adapter = HTTPAdapter (max_retries = self ._retry )
34+ session .mount ("https://" , adapter )
35+ self ._last_refresh = time .monotonic ()
36+ return session
37+
38+ def _refresh_if_stale (self ) -> None :
39+ if time .monotonic () - self ._last_refresh > CONNECTION_MAX_AGE :
40+ self .session .close ()
41+ self .session = self ._new_session ()
2842
2943 def get (self , path : str ) -> dict :
44+ self ._refresh_if_stale ()
3045 resp = self .session .get (f"{ self .base_url } { path } " , timeout = self .timeout )
3146 self ._raise (resp )
3247 return resp .json ()
3348
3449 def post (self , path : str , json : dict ) -> dict :
50+ self ._refresh_if_stale ()
3551 resp = self .session .post (f"{ self .base_url } { path } " , json = json , timeout = self .timeout )
3652 self ._raise (resp )
3753 return resp .json ()
3854
3955 def put (self , path : str , json : dict ) -> dict :
56+ self ._refresh_if_stale ()
4057 resp = self .session .put (f"{ self .base_url } { path } " , json = json , timeout = self .timeout )
4158 self ._raise (resp )
4259 return resp .json ()
4360
4461 def patch (self , path : str , json : dict ) -> dict :
62+ self ._refresh_if_stale ()
4563 resp = self .session .patch (f"{ self .base_url } { path } " , json = json , timeout = self .timeout )
4664 self ._raise (resp )
4765 return resp .json ()
4866
4967 def post_multipart (self , path : str , files : dict , data : dict | None = None ) -> dict :
68+ self ._refresh_if_stale ()
5069 resp = self .session .post (
5170 f"{ self .base_url } { path } " ,
5271 files = files ,
@@ -58,6 +77,7 @@ def post_multipart(self, path: str, files: dict, data: dict | None = None) -> di
5877 return resp .json ()
5978
6079 def delete (self , path : str ) -> None :
80+ self ._refresh_if_stale ()
6181 resp = self .session .delete (f"{ self .base_url } { path } " , timeout = self .timeout )
6282 self ._raise (resp )
6383
0 commit comments