Skip to content

Add switchable HTTP transports#2877

Merged
adamtheturtle merged 8 commits intomainfrom
adamtheturtle/switchable-transport
Feb 24, 2026
Merged

Add switchable HTTP transports#2877
adamtheturtle merged 8 commits intomainfrom
adamtheturtle/switchable-transport

Conversation

@adamtheturtle
Copy link
Member

@adamtheturtle adamtheturtle commented Feb 24, 2026

Summary

Introduces a Transport protocol and two concrete implementations (RequestsTransport and HTTPXTransport) to allow users to choose between requests and httpx as the HTTP backend. All three client classes now accept an optional transport parameter, defaulting to RequestsTransport() for full backwards compatibility.

Changes

  • New module src/vws/transports.py with Transport protocol and both transport implementations
  • All client constructors (VWS, CloudRecoService, VuMarkService) accept optional transport parameter
  • Internal HTTP call sites now use the transport abstraction instead of calling requests directly
  • Added httpx as a required dependency (alongside requests)
  • Updated API reference documentation and spelling dictionary

Testing

All 184 existing tests pass unchanged. The default transport (requests) maintains identical behavior to the original implementation.

🤖 Generated with Claude Code


Note

Medium Risk
Touches the core HTTP request path across all clients and adds a new required dependency (httpx), so regressions could affect all API calls despite defaulting to the existing requests behavior.

Overview
Adds a pluggable HTTP layer via a new vws.transports module (Transport protocol plus RequestsTransport and HTTPXTransport) so callers can choose the underlying HTTP client.

Refactors VWS, CloudRecoService, VuMarkService, and the internal target_api_request helper to accept/use an optional transport (defaulting to RequestsTransport()), and updates packaging/docs/tests accordingly (adds httpx dependency, API reference entry, spelling/vulture ignores, and new HTTPXTransport tests).

Written by Cursor Bugbot for commit b977b52. This will update automatically on new commits. Configure here.

Introduce a Transport protocol and two concrete implementations (RequestsTransport and HTTPXTransport) to allow users to choose between requests and httpx as the HTTP backend. All three client classes (VWS, CloudRecoService, VuMarkService) now accept an optional transport parameter, defaulting to RequestsTransport for backwards compatibility. The transport abstraction handles the full HTTP lifecycle and returns the library's Response dataclass.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Users should import transports from vws.transports directly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Make VuMarkService.__init__ keyword-only to fix
too-many-positional-arguments. Suppress unnecessary-ellipsis
on Transport protocol method body.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

def __init__(
self,
*,
Copy link

Choose a reason for hiding this comment

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

Breaking API change: VuMarkService now requires keyword-only arguments

High Severity

The bare * added to VuMarkService.__init__ forces server_access_key and server_secret_key to be keyword-only arguments. Previously these could be passed positionally (e.g., VuMarkService("key", "secret")). The other two clients (VWS and CloudRecoService) already had *, but VuMarkService did not, so this is a new breaking change to the public API — contradicting the PR's "full backwards compatibility" claim.

Fix in Cursor Fix in Web

Copy link
Member Author

Choose a reason for hiding this comment

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

This is an intentional change. VWS and CloudRecoService already used * for keyword-only arguments, so VuMarkService was the odd one out. Making it consistent also avoids a pylint too-many-positional-arguments error now that transport is a new parameter.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch. Fixed in e2bf8ce — the float path now explicitly sets connect=timeout, read=timeout, write=None, pool=None to match the tuple path and requests semantics.

Remove `from __future__ import annotations` from transports.py.
Add tests for HTTPXTransport covering both float and tuple
timeout branches to achieve 100% coverage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
text=httpx_response.text,
url=str(object=httpx_response.url),
status_code=httpx_response.status_code,
headers=dict(httpx_response.headers),
Copy link

Choose a reason for hiding this comment

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

HTTPXTransport produces lowercase header keys unlike RequestsTransport

Low Severity

dict(httpx_response.headers) normalizes all header keys to lowercase (e.g., "content-type"), while dict(requests_response.headers) preserves the server's original casing (e.g., "Content-Type"). Since Response.headers is a plain dict[str, str] exposed as public API, any consumer doing case-sensitive key lookups like response.headers["Content-Type"] would get a KeyError with HTTPXTransport but work fine with RequestsTransport.

Fix in Cursor Fix in Web

Copy link
Member Author

Choose a reason for hiding this comment

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

HTTP headers are case-insensitive per RFC 7230. Both requests and httpx use case-insensitive header mappings internally — the difference only surfaces after calling dict(). In practice, this library's consumers access headers through the Response object, and any code doing case-sensitive key lookups on HTTP headers has a latent bug regardless of transport. Not changing this.

- Set tell_position to len(content) instead of 0
- Add follow_redirects=True to httpx.request()
- Handle request_body None when content is empty

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use @staticmethod on test methods that don't reference self.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@adamtheturtle adamtheturtle force-pushed the adamtheturtle/switchable-transport branch from 0e93cf7 to c307654 Compare February 24, 2026 13:17
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

Both paths now only set connect and read timeouts, matching
requests behavior. Previously the float path also set write and
pool timeouts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@adamtheturtle adamtheturtle merged commit 024a523 into main Feb 24, 2026
16 checks passed
@adamtheturtle adamtheturtle deleted the adamtheturtle/switchable-transport branch February 24, 2026 15:47
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.

1 participant