Skip to content

Commit 92459c6

Browse files
adamtheturtleclaude
andcommitted
Add switchable HTTP transports (requests and httpx)
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>
1 parent 768e043 commit 92459c6

9 files changed

Lines changed: 226 additions & 61 deletions

File tree

docs/source/api-reference.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,7 @@ API Reference
2020
.. automodule:: vws.response
2121
:undoc-members:
2222
:members:
23+
24+
.. automodule:: vws.transports
25+
:undoc-members:
26+
:members:

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ dynamic = [
3333
]
3434
dependencies = [
3535
"beartype>=0.22.9",
36+
"httpx>=0.28.0",
3637
"requests>=2.32.3",
3738
"urllib3>=2.2.3",
3839
"vws-auth-tools>=2024.7.12",

spelling_private_dict.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ hmac
5454
html
5555
http
5656
https
57+
httpx
5758
iff
5859
io
5960
issuecomment

src/vws/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
"""A library for Vuforia Web Services."""
22

33
from .query import CloudRecoService
4+
from .transports import HTTPXTransport, RequestsTransport, Transport
45
from .vumark_service import VuMarkService
56
from .vws import VWS
67

78
__all__ = [
89
"VWS",
910
"CloudRecoService",
11+
"HTTPXTransport",
12+
"RequestsTransport",
13+
"Transport",
1014
"VuMarkService",
1115
]

src/vws/_vws_request.py

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
API.
33
"""
44

5-
import requests
65
from beartype import BeartypeConf, beartype
76
from vws_auth_tools import authorization_header, rfc_1123_date
87

98
from vws.response import Response
9+
from vws.transports import Transport
1010

1111

1212
@beartype(conf=BeartypeConf(is_pep484_tower=True))
@@ -21,27 +21,30 @@ def target_api_request(
2121
base_vws_url: str,
2222
request_timeout_seconds: float | tuple[float, float],
2323
extra_headers: dict[str, str],
24+
transport: Transport,
2425
) -> Response:
2526
"""Make a request to the Vuforia Target API.
2627
27-
This uses `requests` to make a request against https://vws.vuforia.com.
28-
2928
Args:
3029
content_type: The content type of the request.
3130
server_access_key: A VWS server access key.
3231
server_secret_key: A VWS server secret key.
33-
method: The HTTP method which will be used in the request.
34-
data: The request body which will be used in the request.
35-
request_path: The path to the endpoint which will be used in the
32+
method: The HTTP method which will be used in the
33+
request.
34+
data: The request body which will be used in the
3635
request.
36+
request_path: The path to the endpoint which will be
37+
used in the request.
3738
base_vws_url: The base URL for the VWS API.
38-
request_timeout_seconds: The timeout for the request, as used by
39-
``requests.request``. This can be a float to set both the
40-
connect and read timeouts, or a (connect, read) tuple.
41-
extra_headers: Additional headers to include in the request.
39+
request_timeout_seconds: The timeout for the request.
40+
This can be a float to set both the connect and
41+
read timeouts, or a (connect, read) tuple.
42+
extra_headers: Additional headers to include in the
43+
request.
44+
transport: The HTTP transport to use for the request.
4245
4346
Returns:
44-
The response to the request made by `requests`.
47+
The response to the request.
4548
"""
4649
date_string = rfc_1123_date()
4750

@@ -64,20 +67,10 @@ def target_api_request(
6467

6568
url = base_vws_url.rstrip("/") + request_path
6669

67-
requests_response = requests.request(
70+
return transport(
6871
method=method,
6972
url=url,
7073
headers=headers,
7174
data=data,
7275
timeout=request_timeout_seconds,
7376
)
74-
75-
return Response(
76-
text=requests_response.text,
77-
url=requests_response.url,
78-
status_code=requests_response.status_code,
79-
headers=dict(requests_response.headers),
80-
request_body=requests_response.request.body,
81-
tell_position=requests_response.raw.tell(),
82-
content=bytes(requests_response.content),
83-
)

src/vws/query.py

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from http import HTTPMethod, HTTPStatus
77
from typing import Any, BinaryIO
88

9-
import requests
109
from beartype import BeartypeConf, beartype
1110
from urllib3.filepost import encode_multipart_formdata
1211
from vws_auth_tools import authorization_header, rfc_1123_date
@@ -24,7 +23,7 @@
2423
)
2524
from vws.include_target_data import CloudRecoIncludeTargetData
2625
from vws.reports import QueryResult, TargetData
27-
from vws.response import Response
26+
from vws.transports import RequestsTransport, Transport
2827

2928
_ImageType = io.BytesIO | BinaryIO
3029

@@ -50,21 +49,26 @@ def __init__(
5049
client_secret_key: str,
5150
base_vwq_url: str = "https://cloudreco.vuforia.com",
5251
request_timeout_seconds: float | tuple[float, float] = 30.0,
52+
transport: Transport | None = None,
5353
) -> None:
5454
"""
5555
Args:
5656
client_access_key: A VWS client access key.
5757
client_secret_key: A VWS client secret key.
5858
base_vwq_url: The base URL for the VWQ API.
59-
request_timeout_seconds: The timeout for each HTTP request, as
60-
used by ``requests.request``. This can be a float to set
61-
both the connect and read timeouts, or a (connect, read)
62-
tuple.
59+
request_timeout_seconds: The timeout for each
60+
HTTP request. This can be a float to set both
61+
the connect and read timeouts, or a
62+
(connect, read) tuple.
63+
transport: The HTTP transport to use for
64+
requests. Defaults to
65+
``RequestsTransport()``.
6366
"""
6467
self._client_access_key = client_access_key
6568
self._client_secret_key = client_secret_key
6669
self._base_vwq_url = base_vwq_url
6770
self._request_timeout_seconds = request_timeout_seconds
71+
self._transport = transport or RequestsTransport()
6872

6973
def query(
7074
self,
@@ -143,22 +147,13 @@ def query(
143147
"Content-Type": content_type_header,
144148
}
145149

146-
requests_response = requests.request(
150+
response = self._transport(
147151
method=method,
148152
url=self._base_vwq_url.rstrip("/") + request_path,
149153
headers=headers,
150154
data=content,
151155
timeout=self._request_timeout_seconds,
152156
)
153-
response = Response(
154-
text=requests_response.text,
155-
url=requests_response.url,
156-
status_code=requests_response.status_code,
157-
headers=dict(requests_response.headers),
158-
request_body=requests_response.request.body,
159-
tell_position=requests_response.raw.tell(),
160-
content=bytes(requests_response.content),
161-
)
162157

163158
if response.status_code == HTTPStatus.REQUEST_ENTITY_TOO_LARGE:
164159
raise RequestEntityTooLargeError(response=response)

src/vws/transports.py

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
"""HTTP transport implementations for VWS clients."""
2+
3+
from __future__ import annotations
4+
5+
from typing import Protocol, runtime_checkable
6+
7+
import httpx
8+
import requests
9+
from beartype import BeartypeConf, beartype
10+
11+
from vws.response import Response
12+
13+
14+
@runtime_checkable
15+
class Transport(Protocol):
16+
"""Protocol for HTTP transports used by VWS clients.
17+
18+
A transport is a callable that makes an HTTP request and
19+
returns a ``Response``.
20+
"""
21+
22+
def __call__(
23+
self,
24+
*,
25+
method: str,
26+
url: str,
27+
headers: dict[str, str],
28+
data: bytes,
29+
timeout: float | tuple[float, float],
30+
) -> Response:
31+
"""Make an HTTP request.
32+
33+
Args:
34+
method: The HTTP method (e.g. "GET", "POST").
35+
url: The full URL to request.
36+
headers: Headers to send with the request.
37+
data: The request body as bytes.
38+
timeout: The timeout for the request. A float
39+
sets both the connect and read timeouts. A
40+
(connect, read) tuple sets them individually.
41+
42+
Returns:
43+
A Response populated from the HTTP response.
44+
"""
45+
...
46+
47+
48+
@beartype(conf=BeartypeConf(is_pep484_tower=True))
49+
class RequestsTransport:
50+
"""HTTP transport using the ``requests`` library.
51+
52+
This is the default transport.
53+
"""
54+
55+
def __call__(
56+
self,
57+
*,
58+
method: str,
59+
url: str,
60+
headers: dict[str, str],
61+
data: bytes,
62+
timeout: float | tuple[float, float],
63+
) -> Response:
64+
"""Make an HTTP request using ``requests``.
65+
66+
Args:
67+
method: The HTTP method.
68+
url: The full URL.
69+
headers: Request headers.
70+
data: The request body.
71+
timeout: The request timeout.
72+
73+
Returns:
74+
A Response populated from the requests response.
75+
"""
76+
requests_response = requests.request(
77+
method=method,
78+
url=url,
79+
headers=headers,
80+
data=data,
81+
timeout=timeout,
82+
)
83+
84+
return Response(
85+
text=requests_response.text,
86+
url=requests_response.url,
87+
status_code=requests_response.status_code,
88+
headers=dict(requests_response.headers),
89+
request_body=requests_response.request.body,
90+
tell_position=requests_response.raw.tell(),
91+
content=bytes(requests_response.content),
92+
)
93+
94+
95+
@beartype(conf=BeartypeConf(is_pep484_tower=True))
96+
class HTTPXTransport:
97+
"""HTTP transport using the ``httpx`` library.
98+
99+
Use this transport for environments where ``httpx`` is
100+
preferred over ``requests``.
101+
"""
102+
103+
def __call__(
104+
self,
105+
*,
106+
method: str,
107+
url: str,
108+
headers: dict[str, str],
109+
data: bytes,
110+
timeout: float | tuple[float, float],
111+
) -> Response:
112+
"""Make an HTTP request using ``httpx``.
113+
114+
Args:
115+
method: The HTTP method.
116+
url: The full URL.
117+
headers: Request headers.
118+
data: The request body.
119+
timeout: The request timeout.
120+
121+
Returns:
122+
A Response populated from the httpx response.
123+
"""
124+
if isinstance(timeout, tuple):
125+
connect_timeout, read_timeout = timeout
126+
httpx_timeout = httpx.Timeout(
127+
connect=connect_timeout,
128+
read=read_timeout,
129+
write=None,
130+
pool=None,
131+
)
132+
else:
133+
httpx_timeout = httpx.Timeout(timeout=timeout)
134+
135+
httpx_response = httpx.request(
136+
method=method,
137+
url=url,
138+
headers=headers,
139+
content=data,
140+
timeout=httpx_timeout,
141+
)
142+
143+
return Response(
144+
text=httpx_response.text,
145+
url=str(object=httpx_response.url),
146+
status_code=httpx_response.status_code,
147+
headers=dict(httpx_response.headers),
148+
request_body=bytes(httpx_response.request.content),
149+
tell_position=0,
150+
content=bytes(httpx_response.content),
151+
)

src/vws/vumark_service.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
TooManyRequestsError,
2121
UnknownTargetError,
2222
)
23+
from vws.transports import RequestsTransport, Transport
2324
from vws.vumark_accept import VuMarkAccept
2425

2526

@@ -33,21 +34,26 @@ def __init__(
3334
server_secret_key: str,
3435
base_vws_url: str = "https://vws.vuforia.com",
3536
request_timeout_seconds: float | tuple[float, float] = 30.0,
37+
transport: Transport | None = None,
3638
) -> None:
3739
"""
3840
Args:
3941
server_access_key: A VWS server access key.
4042
server_secret_key: A VWS server secret key.
4143
base_vws_url: The base URL for the VWS API.
42-
request_timeout_seconds: The timeout for each HTTP request, as
43-
used by ``requests.request``. This can be a float to set
44-
both the connect and read timeouts, or a (connect, read)
45-
tuple.
44+
request_timeout_seconds: The timeout for each
45+
HTTP request. This can be a float to set both
46+
the connect and read timeouts, or a
47+
(connect, read) tuple.
48+
transport: The HTTP transport to use for
49+
requests. Defaults to
50+
``RequestsTransport()``.
4651
"""
4752
self._server_access_key = server_access_key
4853
self._server_secret_key = server_secret_key
4954
self._base_vws_url = base_vws_url
5055
self._request_timeout_seconds = request_timeout_seconds
56+
self._transport = transport or RequestsTransport()
5157

5258
def generate_vumark_instance(
5359
self,
@@ -109,6 +115,7 @@ def generate_vumark_instance(
109115
base_vws_url=self._base_vws_url,
110116
request_timeout_seconds=self._request_timeout_seconds,
111117
extra_headers={"Accept": accept},
118+
transport=self._transport,
112119
)
113120

114121
if (

0 commit comments

Comments
 (0)