Skip to content

Commit 8640d39

Browse files
Retain explicitly provided falsy custom transports.
Only None should select the default transport across all sync and async clients. Closes #3092. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent f257c27 commit 8640d39

8 files changed

Lines changed: 220 additions & 6 deletions

File tree

newsfragments/3092.change.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Retain explicitly provided falsy custom transports instead of replacing them with defaults.

src/vws/async_query.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ def __init__(
6060
self._client_secret_key = client_secret_key
6161
self._base_vwq_url = base_vwq_url
6262
self._request_timeout_seconds = request_timeout_seconds
63-
self._transport = transport or AsyncHTTPXTransport()
63+
self._transport = (
64+
transport if transport is not None else AsyncHTTPXTransport()
65+
)
6466

6567
async def aclose(self) -> None:
6668
"""Close the underlying transport if it supports closing."""

src/vws/async_vumark_service.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ def __init__(
4646
self._server_secret_key = server_secret_key
4747
self._base_vws_url = base_vws_url
4848
self._request_timeout_seconds = request_timeout_seconds
49-
self._transport = transport or AsyncHTTPXTransport()
49+
self._transport = (
50+
transport if transport is not None else AsyncHTTPXTransport()
51+
)
5052

5153
async def aclose(self) -> None:
5254
"""Close the underlying transport if it supports closing."""

src/vws/async_vws.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,9 @@ def __init__(
5757
self._server_secret_key = server_secret_key
5858
self._base_vws_url = base_vws_url
5959
self._request_timeout_seconds = request_timeout_seconds
60-
self._transport = transport or AsyncHTTPXTransport()
60+
self._transport = (
61+
transport if transport is not None else AsyncHTTPXTransport()
62+
)
6163

6264
async def aclose(self) -> None:
6365
"""Close the underlying transport if it supports closing."""

src/vws/query.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ def __init__(
5656
self._client_secret_key = client_secret_key
5757
self._base_vwq_url = base_vwq_url
5858
self._request_timeout_seconds = request_timeout_seconds
59-
self._transport = transport or RequestsTransport()
59+
self._transport = (
60+
transport if transport is not None else RequestsTransport()
61+
)
6062

6163
def query(
6264
self,

src/vws/vumark_service.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ def __init__(
4343
self._server_secret_key = server_secret_key
4444
self._base_vws_url = base_vws_url
4545
self._request_timeout_seconds = request_timeout_seconds
46-
self._transport = transport or RequestsTransport()
46+
self._transport = (
47+
transport if transport is not None else RequestsTransport()
48+
)
4749

4850
def generate_vumark_instance(
4951
self,

src/vws/vws.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ def __init__(
5656
self._server_secret_key = server_secret_key
5757
self._base_vws_url = base_vws_url
5858
self._request_timeout_seconds = request_timeout_seconds
59-
self._transport = transport or RequestsTransport()
59+
self._transport = (
60+
transport if transport is not None else RequestsTransport()
61+
)
6062

6163
def make_request(
6264
self,

tests/test_transports.py

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
11
"""Tests for HTTP transport implementations."""
22

3+
import io
4+
import uuid
35
from http import HTTPStatus
46

57
import httpx
68
import pytest
79
import respx
810

11+
from vws import (
12+
VWS,
13+
AsyncCloudRecoService,
14+
AsyncVuMarkService,
15+
AsyncVWS,
16+
CloudRecoService,
17+
VuMarkService,
18+
)
919
from vws.response import Response
1020
from vws.transports import AsyncHTTPXTransport, HTTPXTransport
21+
from vws.vumark_accept import VuMarkAccept
1122

1223

1324
class TestHTTPXTransport:
@@ -206,3 +217,193 @@ async def test_context_manager() -> None:
206217
assert route.called
207218
assert isinstance(response, Response)
208219
assert response.status_code == HTTPStatus.OK
220+
221+
222+
class _FalsyTransport:
223+
"""A sync transport that is falsy but protocol-conforming."""
224+
225+
def __bool__(self) -> bool:
226+
"""Return ``False`` so truthiness checks would skip this
227+
transport.
228+
"""
229+
return False
230+
231+
def close(self) -> None:
232+
"""Close the transport."""
233+
234+
def __call__(
235+
self,
236+
*,
237+
method: str,
238+
url: str,
239+
headers: dict[str, str],
240+
data: bytes,
241+
request_timeout: float | tuple[float, float],
242+
) -> Response:
243+
"""Return a successful API response for the requested URL."""
244+
del method, headers, request_timeout
245+
if url.endswith("/query"):
246+
body = '{"result_code":"Success","results":[]}'
247+
return Response(
248+
text=body,
249+
url=url,
250+
status_code=HTTPStatus.OK,
251+
headers={},
252+
request_body=data,
253+
tell_position=0,
254+
content=body.encode(),
255+
)
256+
if "/instances" in url:
257+
content = b"vumark-bytes"
258+
return Response(
259+
text="",
260+
url=url,
261+
status_code=HTTPStatus.OK,
262+
headers={},
263+
request_body=data,
264+
tell_position=0,
265+
content=content,
266+
)
267+
body = '{"result_code":"Success","results":[]}'
268+
return Response(
269+
text=body,
270+
url=url,
271+
status_code=HTTPStatus.OK,
272+
headers={},
273+
request_body=data,
274+
tell_position=0,
275+
content=body.encode(),
276+
)
277+
278+
279+
class _FalsyAsyncTransport:
280+
"""An async transport that is falsy but protocol-conforming."""
281+
282+
def __bool__(self) -> bool:
283+
"""Return ``False`` so truthiness checks would skip this
284+
transport.
285+
"""
286+
return False
287+
288+
async def aclose(self) -> None:
289+
"""Close the transport."""
290+
291+
async def __call__(
292+
self,
293+
*,
294+
method: str,
295+
url: str,
296+
headers: dict[str, str],
297+
data: bytes,
298+
request_timeout: float | tuple[float, float],
299+
) -> Response:
300+
"""Return a successful API response for the requested URL."""
301+
del method, headers, request_timeout
302+
if url.endswith("/query"):
303+
body = '{"result_code":"Success","results":[]}'
304+
return Response(
305+
text=body,
306+
url=url,
307+
status_code=HTTPStatus.OK,
308+
headers={},
309+
request_body=data,
310+
tell_position=0,
311+
content=body.encode(),
312+
)
313+
if "/instances" in url:
314+
content = b"vumark-bytes"
315+
return Response(
316+
text="",
317+
url=url,
318+
status_code=HTTPStatus.OK,
319+
headers={},
320+
request_body=data,
321+
tell_position=0,
322+
content=content,
323+
)
324+
body = '{"result_code":"Success","results":[]}'
325+
return Response(
326+
text=body,
327+
url=url,
328+
status_code=HTTPStatus.OK,
329+
headers={},
330+
request_body=data,
331+
tell_position=0,
332+
content=body.encode(),
333+
)
334+
335+
336+
def test_falsy_sync_transport_is_retained(
337+
high_quality_image: io.BytesIO,
338+
) -> None:
339+
"""Falsy custom sync transports are not replaced by the default."""
340+
access_key = uuid.uuid4().hex
341+
secret_key = uuid.uuid4().hex
342+
transport = _FalsyTransport()
343+
344+
assert (
345+
VWS(
346+
server_access_key=access_key,
347+
server_secret_key=secret_key,
348+
transport=transport,
349+
).list_targets()
350+
== []
351+
)
352+
assert (
353+
CloudRecoService(
354+
client_access_key=access_key,
355+
client_secret_key=secret_key,
356+
transport=transport,
357+
).query(image=high_quality_image)
358+
== []
359+
)
360+
assert (
361+
VuMarkService(
362+
server_access_key=access_key,
363+
server_secret_key=secret_key,
364+
transport=transport,
365+
).generate_vumark_instance(
366+
target_id="target",
367+
instance_id="instance",
368+
accept=VuMarkAccept.PNG,
369+
)
370+
== b"vumark-bytes"
371+
)
372+
373+
374+
@pytest.mark.asyncio
375+
async def test_falsy_async_transport_is_retained(
376+
high_quality_image: io.BytesIO,
377+
) -> None:
378+
"""Falsy custom async transports are not replaced by the default."""
379+
access_key = uuid.uuid4().hex
380+
secret_key = uuid.uuid4().hex
381+
transport = _FalsyAsyncTransport()
382+
383+
async with AsyncVWS(
384+
server_access_key=access_key,
385+
server_secret_key=secret_key,
386+
transport=transport,
387+
) as vws_client:
388+
assert await vws_client.list_targets() == []
389+
390+
async with AsyncCloudRecoService(
391+
client_access_key=access_key,
392+
client_secret_key=secret_key,
393+
transport=transport,
394+
) as cloud_reco_client:
395+
assert await cloud_reco_client.query(image=high_quality_image) == []
396+
397+
async with AsyncVuMarkService(
398+
server_access_key=access_key,
399+
server_secret_key=secret_key,
400+
transport=transport,
401+
) as vumark_client:
402+
assert (
403+
await vumark_client.generate_vumark_instance(
404+
target_id="target",
405+
instance_id="instance",
406+
accept=VuMarkAccept.PNG,
407+
)
408+
== b"vumark-bytes"
409+
)

0 commit comments

Comments
 (0)