Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "1.0.0-alpha.12"
".": "1.0.0-alpha.13"
}
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Changelog

## 1.0.0-alpha.13 (2025-06-03)

Full Changelog: [v1.0.0-alpha.12...v1.0.0-alpha.13](https://github.com/hubmapconsortium/search-python-sdk/compare/v1.0.0-alpha.12...v1.0.0-alpha.13)

### Features

* **client:** add follow_redirects request option ([55b10c1](https://github.com/hubmapconsortium/search-python-sdk/commit/55b10c18cfea334e5ea9558f70ded70c2b21e34c))


### Chores

* **docs:** remove reference to rye shell ([ef26230](https://github.com/hubmapconsortium/search-python-sdk/commit/ef262304d809361ee6d56932bdb9328157e8aef6))

## 1.0.0-alpha.12 (2025-05-28)

Full Changelog: [v1.0.0-alpha.11...v1.0.0-alpha.12](https://github.com/hubmapconsortium/search-python-sdk/compare/v1.0.0-alpha.11...v1.0.0-alpha.12)
Expand Down
3 changes: 1 addition & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ $ rye sync --all-features
You can then run scripts using `rye run python script.py` or by activating the virtual environment:

```sh
$ rye shell
# or manually activate - https://docs.python.org/3/library/venv.html#how-venvs-work
# Activate the virtual environment - https://docs.python.org/3/library/venv.html#how-venvs-work
$ source .venv/bin/activate

# now you can omit the `rye run` prefix
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "hubmap_search_sdk"
version = "1.0.0-alpha.12"
version = "1.0.0-alpha.13"
description = "The official Python library for the hubmap-search-sdk API"
dynamic = ["readme"]
license = "MIT"
Expand Down
6 changes: 6 additions & 0 deletions src/hubmap_search_sdk/_base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -963,6 +963,9 @@ def request(
if self.custom_auth is not None:
kwargs["auth"] = self.custom_auth

if options.follow_redirects is not None:
kwargs["follow_redirects"] = options.follow_redirects

log.debug("Sending HTTP Request: %s %s", request.method, request.url)

response = None
Expand Down Expand Up @@ -1472,6 +1475,9 @@ async def request(
if self.custom_auth is not None:
kwargs["auth"] = self.custom_auth

if options.follow_redirects is not None:
kwargs["follow_redirects"] = options.follow_redirects

log.debug("Sending HTTP Request: %s %s", request.method, request.url)

response = None
Expand Down
2 changes: 2 additions & 0 deletions src/hubmap_search_sdk/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,7 @@ class FinalRequestOptionsInput(TypedDict, total=False):
idempotency_key: str
json_data: Body
extra_json: AnyMapping
follow_redirects: bool


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

# It should be noted that we cannot use `json` here as that would override
# a BaseModel method in an incompatible fashion.
Expand Down
2 changes: 2 additions & 0 deletions src/hubmap_search_sdk/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ class RequestOptions(TypedDict, total=False):
params: Query
extra_json: AnyMapping
idempotency_key: str
follow_redirects: bool


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

class HttpxSendArgs(TypedDict, total=False):
auth: httpx.Auth
follow_redirects: bool
2 changes: 1 addition & 1 deletion src/hubmap_search_sdk/_version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

__title__ = "hubmap_search_sdk"
__version__ = "1.0.0-alpha.12" # x-release-please-version
__version__ = "1.0.0-alpha.13" # x-release-please-version
54 changes: 54 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,33 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:

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

@pytest.mark.respx(base_url=base_url)
def test_follow_redirects(self, respx_mock: MockRouter) -> None:
# Test that the default follow_redirects=True allows following redirects
respx_mock.post("/redirect").mock(
return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
)
respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"}))

response = self.client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response)
assert response.status_code == 200
assert response.json() == {"status": "ok"}

@pytest.mark.respx(base_url=base_url)
def test_follow_redirects_disabled(self, respx_mock: MockRouter) -> None:
# Test that follow_redirects=False prevents following redirects
respx_mock.post("/redirect").mock(
return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
)

with pytest.raises(APIStatusError) as exc_info:
self.client.post(
"/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response
)

assert exc_info.value.response.status_code == 302
assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected"


class TestAsyncHubmapSearchSDK:
client = AsyncHubmapSearchSDK(base_url=base_url, bearer_token=bearer_token, _strict_response_validation=True)
Expand Down Expand Up @@ -1669,3 +1696,30 @@ async def test_main() -> None:
raise AssertionError("calling get_platform using asyncify resulted in a hung process")

time.sleep(0.1)

@pytest.mark.respx(base_url=base_url)
async def test_follow_redirects(self, respx_mock: MockRouter) -> None:
# Test that the default follow_redirects=True allows following redirects
respx_mock.post("/redirect").mock(
return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
)
respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"}))

response = await self.client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response)
assert response.status_code == 200
assert response.json() == {"status": "ok"}

@pytest.mark.respx(base_url=base_url)
async def test_follow_redirects_disabled(self, respx_mock: MockRouter) -> None:
# Test that follow_redirects=False prevents following redirects
respx_mock.post("/redirect").mock(
return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
)

with pytest.raises(APIStatusError) as exc_info:
await self.client.post(
"/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response
)

assert exc_info.value.response.status_code == 302
assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected"
Loading