@@ -92,6 +92,42 @@ def serialize_value(value):
9292 serialized = serialize_value(body)
9393 return json.dumps(serialized)
9494
95+ def _url_origin(self, url: str) -> str:
96+ parts = urlsplit(url)
97+ scheme = parts.scheme.lower()
98+ port = parts.port if parts.port is not None else {"http": 80, "https": 443}.get(scheme)
99+ host = (parts.hostname or "").lower()
100+ return f"{scheme}://{host}:{port}"
101+
102+ def _post(self, url: str, headers: Dict[str, str], data: str) -> requests.Response:
103+ origin = self._url_origin(url)
104+ current_url = url
105+ for _ in range(MAX_REDIRECTS):
106+ response = requests.post(
107+ current_url,
108+ headers=headers,
109+ data=data,
110+ timeout=self.default_timeout,
111+ allow_redirects=False
112+ )
113+ if response.status_code not in (301, 302, 303, 307, 308):
114+ return response
115+
116+ location = response.headers.get("Location")
117+ if response.status_code in (307, 308) and location:
118+ next_url = urljoin(current_url, location)
119+ try:
120+ if self._url_origin(next_url) == origin:
121+ current_url = next_url
122+ continue
123+ except ValueError:
124+ pass
125+ raise requests.RequestException(
126+ f"Not following redirect ({response.status_code}) to {location!r}"
127+ )
128+
129+ raise requests.TooManyRedirects(f"Exceeded {MAX_REDIRECTS} redirects")
130+
95131 def _request(self, url: str, body: Dict[str, Any], response_type: type) -> Any:
96132 \" \" \" Make a request to the Turnkey API.
97133
@@ -117,12 +153,7 @@ def _request(self, url: str, body: Dict[str, Any], response_type: type) -> Any:
117153 }
118154
119155 try:
120- response = requests.post(
121- full_url,
122- headers=headers,
123- data=body_str,
124- timeout=self.default_timeout
125- )
156+ response = self._post(full_url, headers, body_str)
126157 except requests.RequestException as exc:
127158 raise TurnkeyNetworkError(
128159 "Request failed",
@@ -253,12 +284,7 @@ def send_signed_request(self, signed_request: SignedRequest, response_type: type
253284 }
254285
255286 try:
256- response = requests.post(
257- signed_request.url,
258- headers=headers,
259- data=signed_request.body,
260- timeout=self.default_timeout
261- )
287+ response = self._post(signed_request.url, headers, signed_request.body)
262288 except requests.RequestException as exc:
263289 raise TurnkeyNetworkError(
264290 "Signed request failed",
@@ -543,12 +569,13 @@ def main():
543569
544570 # Build full output
545571 output = f"{ COMMENT_HEADER } \n \n "
546- output += "import json\n import time\n from typing import Any, Callable, Dict, Optional, TypeVar, overload\n import requests\n "
572+ output += "import json\n import time\n from typing import Any, Callable, Dict, Optional, TypeVar, overload\n from urllib.parse import urljoin, urlsplit \ n import requests\n "
547573 output += "from turnkey_api_key_stamper import ApiKeyStamper\n "
548574 output += "from turnkey_sdk_types import *\n "
549575 output += "from ..version import VERSION\n \n "
550576 output += "T = TypeVar('T')\n \n "
551577 output += f"TERMINAL_ACTIVITY_STATUSES = { TERMINAL_ACTIVITY_STATUSES } \n \n "
578+ output += "MAX_REDIRECTS = 5\n \n "
552579 output += client_code
553580
554581 # Ensure output directory exists
0 commit comments