1010
1111
1212class HTTPTransport (ABC ):
13+ """Base class for HTTP transports.
14+
15+ Subclasses implement ``send`` to perform the raw HTTP call.
16+ The public ``request`` method adds cross-cutting concerns on top:
17+ authentication, logging hooks, and automatic token refresh on 401.
18+ """
19+
1320 def __init__ (self , sinch ):
1421 self .sinch = sinch
1522
23+ # ------------------------------------------------------------------
24+ # Subclass contract
25+ # ------------------------------------------------------------------
26+
1627 @abstractmethod
28+ def send (self , endpoint : HTTPEndpoint ) -> HTTPResponse :
29+ """Execute a single HTTP round-trip and return the response.
30+
31+ Implementations must prepare the request, authenticate, perform the
32+ HTTP call, deserialize the response, and return an ``HTTPResponse``.
33+ They should **not** handle token refresh — that is done by
34+ ``request``.
35+ """
36+
37+ # ------------------------------------------------------------------
38+ # Public API
39+ # ------------------------------------------------------------------
40+
1741 def request (self , endpoint : HTTPEndpoint ) -> HTTPResponse :
18- pass
42+ """Send a request with automatic OAuth token refresh on 401.
43+
44+ If the server responds with 401 *and* the token is detected as
45+ expired, the token is invalidated and **one** retry is attempted
46+ with a fresh token. A second consecutive 401 is handed straight
47+ to the endpoint's error handler — no further retries.
48+ """
49+ http_response = self .send (endpoint )
50+
51+ if self ._should_refresh_token (endpoint , http_response ):
52+ self .sinch .configuration .token_manager .handle_invalid_token (
53+ http_response
54+ )
55+ if (
56+ self .sinch .configuration .token_manager .token_state
57+ == TokenState .EXPIRED
58+ ):
59+ http_response = self .send (endpoint )
60+
61+ return endpoint .handle_response (http_response )
62+
63+ # ------------------------------------------------------------------
64+ # Internals
65+ # ------------------------------------------------------------------
1966
2067 def authenticate (self , endpoint , request_data ):
2168 if endpoint .HTTP_AUTHENTICATION in (HTTPAuthentication .BASIC .value , HTTPAuthentication .OAUTH .value ):
@@ -83,10 +130,7 @@ def deserialize_json_response(response):
83130 response_body = response .json ()
84131 except ValueError as err :
85132 raise SinchException (
86- message = (
87- "Error while parsing json response." ,
88- err .msg
89- ),
133+ message = f"Error while parsing json response. { err } " ,
90134 is_from_server = True ,
91135 response = response
92136 )
@@ -95,10 +139,11 @@ def deserialize_json_response(response):
95139
96140 return response_body
97141
98- def handle_response (self , endpoint : HTTPEndpoint , http_response : HTTPResponse ):
99- if http_response .status_code == 401 and endpoint .HTTP_AUTHENTICATION == HTTPAuthentication .OAUTH .value :
100- self .sinch .configuration .token_manager .handle_invalid_token (http_response )
101- if self .sinch .configuration .token_manager .token_state == TokenState .EXPIRED :
102- return self .request (endpoint = endpoint )
103-
104- return endpoint .handle_response (http_response )
142+ @staticmethod
143+ def _should_refresh_token (endpoint , http_response ):
144+ """Return True when a 401 response should trigger a token refresh."""
145+ return (
146+ http_response .status_code == 401
147+ and endpoint .HTTP_AUTHENTICATION
148+ == HTTPAuthentication .OAUTH .value
149+ )
0 commit comments