Skip to content

[FEEDS-1373]feat: allow custom HTTP transport and client configuration#235

Merged
itsmeadi merged 3 commits intomainfrom
feat/configurable-http-transport
Apr 10, 2026
Merged

[FEEDS-1373]feat: allow custom HTTP transport and client configuration#235
itsmeadi merged 3 commits intomainfrom
feat/configurable-http-transport

Conversation

@itsmeadi
Copy link
Copy Markdown
Contributor

@itsmeadi itsmeadi commented Apr 10, 2026

Summary

  • Adds transport and http_client parameters to Stream / AsyncStream, allowing users to configure connection pool limits, retries, SSL, HTTP/2, and other httpx transport-level settings.
  • Mirrors the Java SDK pattern where a pre-built OkHttpClient can be passed in and the SDK layers its auth on top.
  • When custom HTTP config is provided, all sub-clients (video, chat, moderation, feeds) share a single underlying httpx client instead of each creating their own.

Two-tier API

Parameter Use case Lifecycle
transport (primary) Pass an httpx.HTTPTransport with pool limits, retries, SSL config. SDK builds its own client. SDK-managed
http_client (escape hatch) Pass a fully built httpx.Client for event hooks or other advanced config. SDK adds auth headers/params. Caller-managed

Usage

import httpx
from getstream import Stream

# Primary: configure transport
transport = httpx.HTTPTransport(
    retries=3,
    limits=httpx.Limits(max_connections=100, max_keepalive_connections=20),
    http2=True,
)
client = Stream(api_key="...", api_secret="...", transport=transport)

# Escape hatch: full control
custom = httpx.Client(transport=transport, event_hooks={"request": [my_hook]})
client = Stream(api_key="...", api_secret="...", http_client=custom)

Design decisions

  • No generated code modified_http_client / _transport are set as instance attributes before super().__init__() in hand-written wrappers, picked up by BaseClient via getattr(self, ...).
  • Backward-compatible — both params are optional; when omitted the code follows the exact same path as before.
  • Mutual exclusion — passing both transport and http_client raises ValueError.
  • Type-safe — passing an httpx.AsyncClient to sync Stream (or vice versa) raises TypeError immediately.

Test plan

  • 20 new tests in tests/test_http_client.py covering transport, http_client, sharing, close semantics, validation
  • Full existing test suite passes with no regressions (455 passed, 47 skipped; pre-existing failures unchanged)

Made with Cursor

Summary by CodeRabbit

  • New Features

    • Added optional http_client and transport parameters to configure a custom HTTP client/transport.
    • Sub-clients now share the parent client's HTTP connection for improved efficiency.
    • SDK preserves and uses user-supplied HTTP clients without taking ownership (won’t close them).
  • Bug Fixes

    • Validates that transport and http_client are mutually exclusive and raises on invalid combos.
  • Tests

    • Added comprehensive tests for custom HTTP client/transport behavior and lifecycle.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 10, 2026

📝 Walkthrough

Walkthrough

Adds optional transport/http_client injection and shared HTTPX client reuse across the SDK; client instances may use an externally supplied HTTPX client (mutated in-place) or construct an owned client, with ownership tracked to control close behavior.

Changes

Cohort / File(s) Summary
Base HTTP client management
getstream/base.py
Resolve external httpx.Client/httpx.AsyncClient via _resolve_http_client(), reuse or build clients, apply instance headers/params/base_url/timeout in-place, and track ownership with _owns_http_client; only owned clients are closed.
Stream construction
getstream/stream.py
BaseStream.__init__ gains transport=None, http_client=None, validates mutual exclusion (raises ValueError if both provided), stores _transport/_http_client, and sets _shared_client when a custom transport/http_client is supplied.
Sub-client initialization order
getstream/chat/client.py, getstream/chat/async_client.py, getstream/video/client.py, getstream/video/async_client.py, getstream/moderation/client.py, getstream/moderation/async_client.py, getstream/feeds/client.py
Moved self.stream = stream assignment to occur before calling parent __init__ (ordering change only); sub-clients receive/resolve shared HTTP client from Stream.
Tests: HTTP client & transport
tests/test_http_client.py
New test suite covering transport injection, user-supplied httpx client escape hatch, header/auth propagation, shared client instance across sub-clients, ownership-based close semantics, custom limits propagation, and validation/type-error cases (sync/async mismatches, wrong types, mutually exclusive args).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Stream as BaseStream
    participant BaseClient as BaseClient / AsyncBaseClient
    participant Sub as Sub-Clients (Chat/Video/Moderation/Feeds)
    participant HTTPX as HTTPX Client

    User->>Stream: __init__(transport=X or http_client=Y)
    Stream->>Stream: store _transport/_http_client
    Stream->>BaseClient: construct client (passes _transport/_http_client)
    alt external http_client provided
        BaseClient->>HTTPX: use provided client (mutate headers/params/base_url/timeout)
        BaseClient->>BaseClient: _owns_http_client = False
    else no external client
        BaseClient->>HTTPX: create new HTTPX client (inject transport if present)
        BaseClient->>BaseClient: _owns_http_client = True
    end
    Stream->>Stream: _shared_client = self.client (when custom config)
    User->>Sub: access sub-client (e.g., stream.chat)
    Sub->>BaseClient: __init__(http_client=_shared_client or resolved)
    Sub->>HTTPX: reuse shared HTTPX client for requests
    User->>Stream: close()
    alt _owns_http_client is True
        Stream->>HTTPX: close()
    else
        Stream->>HTTPX: skip close (no-op)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

A rabbit nibbles on bytes so fleet,
One client shared makes connections sweet.
Headers hop in, timeouts tucked tight,
Close only what you own — that’s right! 🐇✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.91% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main feature addition: allowing custom HTTP transport and client configuration. It directly reflects the primary change across all modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/configurable-http-transport

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Add `transport` and `http_client` parameters to `Stream` and `AsyncStream`
so users can configure connection pool limits, retries, SSL, HTTP/2, and
other httpx transport-level settings — matching the Java SDK pattern where
a pre-built OkHttpClient can be passed in.

- `transport` (primary): user provides an `httpx.HTTPTransport` /
  `httpx.AsyncHTTPTransport`; the SDK builds its own client with it and
  manages the lifecycle.
- `http_client` (escape hatch): user provides a fully built
  `httpx.Client` / `httpx.AsyncClient`; the SDK layers auth headers and
  params on top. Caller manages lifecycle.

When either is provided, all sub-clients (video, chat, moderation, feeds)
share a single underlying httpx client via the `stream` back-reference
instead of each creating their own.

Made-with: Cursor
@itsmeadi itsmeadi force-pushed the feat/configurable-http-transport branch from 25b1a31 to 87378dd Compare April 10, 2026 14:32
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
getstream/stream.py (1)

50-88: ⚠️ Potential issue | 🟠 Major

as_async() now drops the new HTTP configuration.

After this change, transport/http_client are part of Stream state, but Stream.as_async() still rebuilds AsyncStream with only credentials, URL, timeout, and user agent. That means a caller can configure retries/limits/SSL on the sync client and then silently lose them when switching to async. Please either propagate this explicitly where possible or fail fast when as_async() is used with custom HTTP config.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@getstream/stream.py` around lines 50 - 88, as_async() currently drops custom
HTTP configuration because Stream stores transport/http_client but as_async()
rebuilds AsyncStream only with api_key/api_secret/base_url/timeout/user_agent;
update Stream.as_async (and/or AsyncStream constructor usage) to propagate
transport and http_client (and preserve _shared_client) when constructing the
async instance, or alternatively raise a clear ValueError if transport or
http_client is set to prevent silent loss; refer to the Stream.as_async method,
the AsyncStream constructor, and members transport, http_client, _shared_client
to ensure the same HTTP config is passed through or disallowed.
🧹 Nitpick comments (2)
tests/test_http_client.py (2)

79-87: Avoid asserting private httpx internals in this test.

_transport, _pool, and the pool limit fields are internal implementation details, so this test is likely to break on an httpx upgrade without any SDK regression. Prefer asserting the observable contract here, e.g. that the provided transport instance is the one wired into the client.

Lower-coupling alternative
     def test_custom_limits_propagated(self):
         limits = httpx.Limits(max_connections=42, max_keepalive_connections=10)
         transport = httpx.HTTPTransport(limits=limits)
         client = Stream(
             api_key="k", api_secret="s", base_url="http://test", transport=transport
         )
-        pool = client.client._transport._pool
-        assert pool._max_connections == 42
-        assert pool._max_keepalive_connections == 10
+        assert client.client._transport is transport
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_http_client.py` around lines 79 - 87, The test
test_custom_limits_propagated is asserting private httpx internals (_transport,
_pool and pool limit fields); instead replace those fragile assertions with a
check that the provided transport instance is the one wired into the Stream
client (e.g., assert client.client._transport is transport or otherwise access
the public/expected attribute on the Stream instance that holds the transport),
and remove any assertions referencing _pool, _max_connections or
_max_keepalive_connections.

7-23: Please convert these transport helpers into pytest fixtures and avoid MockTransport here.

This helper pattern drives the whole file toward inline client construction plus httpx.MockTransport, which conflicts with the repo’s test conventions. A small set of fixtures that yield Stream/AsyncStream clients would also make cleanup of caller-managed httpx clients deterministic.

As per coding guidelines, "Use fixtures to inject objects in tests; test client fixtures can use the Stream API client" and "Do not use mocks or mock objects in tests unless directly requested".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_http_client.py` around lines 7 - 23, Replace the helper functions
_mock_transport and _capture_transport with pytest fixtures that create and
yield real http client instances (Stream/AsyncStream API) instead of using
httpx.MockTransport; implement one fixture that yields a configured streaming
client preloaded to return the desired status/body for tests (use a test server
or httpx.MockServer/test server helper if available) and another fixture that
yields a streaming client plus a request-capture container for assertions;
ensure fixtures perform proper setup/teardown (closing the client) so tests no
longer construct inline clients or use MockTransport directly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@getstream/chat/client.py`:
- Around line 17-19: The __init__ constructor signature in
getstream/chat/client.py is triggering Ruff/formatting errors due to the current
parameter wrapping; update the __init__ definition to conform to the project's
line-wrapping and trailing-comma conventions (e.g., one parameter per line with
proper type annotations and a trailing comma) and apply Ruff/Black formatting so
the same pattern is consistently fixed across other client constructors as well.

---

Outside diff comments:
In `@getstream/stream.py`:
- Around line 50-88: as_async() currently drops custom HTTP configuration
because Stream stores transport/http_client but as_async() rebuilds AsyncStream
only with api_key/api_secret/base_url/timeout/user_agent; update Stream.as_async
(and/or AsyncStream constructor usage) to propagate transport and http_client
(and preserve _shared_client) when constructing the async instance, or
alternatively raise a clear ValueError if transport or http_client is set to
prevent silent loss; refer to the Stream.as_async method, the AsyncStream
constructor, and members transport, http_client, _shared_client to ensure the
same HTTP config is passed through or disallowed.

---

Nitpick comments:
In `@tests/test_http_client.py`:
- Around line 79-87: The test test_custom_limits_propagated is asserting private
httpx internals (_transport, _pool and pool limit fields); instead replace those
fragile assertions with a check that the provided transport instance is the one
wired into the Stream client (e.g., assert client.client._transport is transport
or otherwise access the public/expected attribute on the Stream instance that
holds the transport), and remove any assertions referencing _pool,
_max_connections or _max_keepalive_connections.
- Around line 7-23: Replace the helper functions _mock_transport and
_capture_transport with pytest fixtures that create and yield real http client
instances (Stream/AsyncStream API) instead of using httpx.MockTransport;
implement one fixture that yields a configured streaming client preloaded to
return the desired status/body for tests (use a test server or
httpx.MockServer/test server helper if available) and another fixture that
yields a streaming client plus a request-capture container for assertions;
ensure fixtures perform proper setup/teardown (closing the client) so tests no
longer construct inline clients or use MockTransport directly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f66eab4b-be50-4072-b8b7-26040e805d72

📥 Commits

Reviewing files that changed from the base of the PR and between bc76d35 and 25b1a31.

📒 Files selected for processing (10)
  • getstream/base.py
  • getstream/chat/async_client.py
  • getstream/chat/client.py
  • getstream/feeds/client.py
  • getstream/moderation/async_client.py
  • getstream/moderation/client.py
  • getstream/stream.py
  • getstream/video/async_client.py
  • getstream/video/client.py
  • tests/test_http_client.py

Comment on lines +17 to +19
def __init__(
self, api_key: str, base_url, token, timeout, stream, user_agent=None, http_client=None
):
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Run Ruff on the new constructor signature.

CI is already failing on formatting here, and this wrapping pattern is repeated across the other touched client constructors as well.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@getstream/chat/client.py` around lines 17 - 19, The __init__ constructor
signature in getstream/chat/client.py is triggering Ruff/formatting errors due
to the current parameter wrapping; update the __init__ definition to conform to
the project's line-wrapping and trailing-comma conventions (e.g., one parameter
per line with proper type annotations and a trailing comma) and apply Ruff/Black
formatting so the same pattern is consistently fixed across other client
constructors as well.

Move _apply_shared_client into BaseStream so sub-client files
need zero HTTP-related changes. Remove _resolve_http_client from
BaseClient/AsyncBaseClient.

Made-with: Cursor
Avoid **dict unpacking which ty cannot type-check.

Made-with: Cursor
@itsmeadi itsmeadi changed the title feat: allow custom HTTP transport and client configuration [FEEDS-1373]feat: allow custom HTTP transport and client configuration Apr 10, 2026
@itsmeadi itsmeadi merged commit 7c4c123 into main Apr 10, 2026
30 of 31 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants