Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit d606061

Browse files
feat(client): add follow_redirects request option
1 parent 1dfaea4 commit d606061

File tree

4 files changed

+64
-0
lines changed

4 files changed

+64
-0
lines changed

‎src/replicate/_base_client.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -960,6 +960,9 @@ def request(
960960
if self.custom_auth is not None:
961961
kwargs["auth"] = self.custom_auth
962962

963+
if options.follow_redirects is not None:
964+
kwargs["follow_redirects"] = options.follow_redirects
965+
963966
log.debug("Sending HTTP Request: %s %s", request.method, request.url)
964967

965968
response = None
@@ -1474,6 +1477,9 @@ async def request(
14741477
if self.custom_auth is not None:
14751478
kwargs["auth"] = self.custom_auth
14761479

1480+
if options.follow_redirects is not None:
1481+
kwargs["follow_redirects"] = options.follow_redirects
1482+
14771483
log.debug("Sending HTTP Request: %s %s", request.method, request.url)
14781484

14791485
response = None

‎src/replicate/_models.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,7 @@ class FinalRequestOptionsInput(TypedDict, total=False):
737737
idempotency_key: str
738738
json_data: Body
739739
extra_json: AnyMapping
740+
follow_redirects: bool
740741

741742

742743
@final
@@ -750,6 +751,7 @@ class FinalRequestOptions(pydantic.BaseModel):
750751
files: Union[HttpxRequestFiles, None] = None
751752
idempotency_key: Union[str, None] = None
752753
post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven()
754+
follow_redirects: Union[bool, None] = None
753755

754756
# It should be noted that we cannot use `json` here as that would override
755757
# a BaseModel method in an incompatible fashion.

‎src/replicate/_types.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ class RequestOptions(TypedDict, total=False):
100100
params: Query
101101
extra_json: AnyMapping
102102
idempotency_key: str
103+
follow_redirects: bool
103104

104105

105106
# Sentinel class used until PEP 0661 is accepted
@@ -215,3 +216,4 @@ class _GenericAlias(Protocol):
215216

216217
class HttpxSendArgs(TypedDict, total=False):
217218
auth: httpx.Auth
219+
follow_redirects: bool

‎tests/test_client.py‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -868,6 +868,33 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
868868

869869
assert response.http_request.headers.get("x-stainless-retry-count") == "42"
870870

871+
@pytest.mark.respx(base_url=base_url)
872+
def test_follow_redirects(self, respx_mock: MockRouter) -> None:
873+
# Test that the default follow_redirects=True allows following redirects
874+
respx_mock.post("/redirect").mock(
875+
return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
876+
)
877+
respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"}))
878+
879+
response = self.client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response)
880+
assert response.status_code == 200
881+
assert response.json() == {"status": "ok"}
882+
883+
@pytest.mark.respx(base_url=base_url)
884+
def test_follow_redirects_disabled(self, respx_mock: MockRouter) -> None:
885+
# Test that follow_redirects=False prevents following redirects
886+
respx_mock.post("/redirect").mock(
887+
return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
888+
)
889+
890+
with pytest.raises(APIStatusError) as exc_info:
891+
self.client.post(
892+
"/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response
893+
)
894+
895+
assert exc_info.value.response.status_code == 302
896+
assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected"
897+
871898

872899
class TestAsyncReplicate:
873900
client = AsyncReplicate(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True)
@@ -1731,3 +1758,30 @@ async def test_main() -> None:
17311758
raise AssertionError("calling get_platform using asyncify resulted in a hung process")
17321759

17331760
time.sleep(0.1)
1761+
1762+
@pytest.mark.respx(base_url=base_url)
1763+
async def test_follow_redirects(self, respx_mock: MockRouter) -> None:
1764+
# Test that the default follow_redirects=True allows following redirects
1765+
respx_mock.post("/redirect").mock(
1766+
return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
1767+
)
1768+
respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"}))
1769+
1770+
response = await self.client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response)
1771+
assert response.status_code == 200
1772+
assert response.json() == {"status": "ok"}
1773+
1774+
@pytest.mark.respx(base_url=base_url)
1775+
async def test_follow_redirects_disabled(self, respx_mock: MockRouter) -> None:
1776+
# Test that follow_redirects=False prevents following redirects
1777+
respx_mock.post("/redirect").mock(
1778+
return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
1779+
)
1780+
1781+
with pytest.raises(APIStatusError) as exc_info:
1782+
await self.client.post(
1783+
"/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response
1784+
)
1785+
1786+
assert exc_info.value.response.status_code == 302
1787+
assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected"

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /