Skip to content

Commit 4f8c58c

Browse files
committed
Adjust HTTP client request handling
1 parent 221a8d5 commit 4f8c58c

3 files changed

Lines changed: 314 additions & 22 deletions

File tree

codegen/http/generate_http.py

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -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\nimport time\nfrom typing import Any, Callable, Dict, Optional, TypeVar, overload\nimport requests\n"
572+
output += "import json\nimport time\nfrom typing import Any, Callable, Dict, Optional, TypeVar, overload\nfrom urllib.parse import urljoin, urlsplit\nimport 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

packages/http/src/turnkey_http/generated/client.py

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import json
44
import time
55
from typing import Any, Callable, Dict, Optional, TypeVar, overload
6+
from urllib.parse import urljoin, urlsplit
67
import requests
78
from turnkey_api_key_stamper import ApiKeyStamper
89
from turnkey_sdk_types import *
@@ -17,6 +18,8 @@
1718
"ACTIVITY_STATUS_REJECTED",
1819
]
1920

21+
MAX_REDIRECTS = 5
22+
2023

2124
class TurnkeyClient:
2225
"""Turnkey API HTTP client with auto-generated methods."""
@@ -71,6 +74,46 @@ def serialize_value(value):
7174
serialized = serialize_value(body)
7275
return json.dumps(serialized)
7376

77+
def _url_origin(self, url: str) -> str:
78+
parts = urlsplit(url)
79+
scheme = parts.scheme.lower()
80+
port = (
81+
parts.port
82+
if parts.port is not None
83+
else {"http": 80, "https": 443}.get(scheme)
84+
)
85+
host = (parts.hostname or "").lower()
86+
return f"{scheme}://{host}:{port}"
87+
88+
def _post(self, url: str, headers: Dict[str, str], data: str) -> requests.Response:
89+
origin = self._url_origin(url)
90+
current_url = url
91+
for _ in range(MAX_REDIRECTS):
92+
response = requests.post(
93+
current_url,
94+
headers=headers,
95+
data=data,
96+
timeout=self.default_timeout,
97+
allow_redirects=False,
98+
)
99+
if response.status_code not in (301, 302, 303, 307, 308):
100+
return response
101+
102+
location = response.headers.get("Location")
103+
if response.status_code in (307, 308) and location:
104+
next_url = urljoin(current_url, location)
105+
try:
106+
if self._url_origin(next_url) == origin:
107+
current_url = next_url
108+
continue
109+
except ValueError:
110+
pass
111+
raise requests.RequestException(
112+
f"Not following redirect ({response.status_code}) to {location!r}"
113+
)
114+
115+
raise requests.TooManyRedirects(f"Exceeded {MAX_REDIRECTS} redirects")
116+
74117
def _request(self, url: str, body: Dict[str, Any], response_type: type) -> Any:
75118
"""Make a request to the Turnkey API.
76119
@@ -96,9 +139,7 @@ def _request(self, url: str, body: Dict[str, Any], response_type: type) -> Any:
96139
}
97140

98141
try:
99-
response = requests.post(
100-
full_url, headers=headers, data=body_str, timeout=self.default_timeout
101-
)
142+
response = self._post(full_url, headers, body_str)
102143
except requests.RequestException as exc:
103144
raise TurnkeyNetworkError(
104145
"Request failed", None, TurnkeyErrorCodes.NETWORK_ERROR, str(exc)
@@ -239,12 +280,7 @@ def send_signed_request(
239280
}
240281

241282
try:
242-
response = requests.post(
243-
signed_request.url,
244-
headers=headers,
245-
data=signed_request.body,
246-
timeout=self.default_timeout,
247-
)
283+
response = self._post(signed_request.url, headers, signed_request.body)
248284
except requests.RequestException as exc:
249285
raise TurnkeyNetworkError(
250286
"Signed request failed", None, TurnkeyErrorCodes.NETWORK_ERROR, str(exc)

0 commit comments

Comments
 (0)