vws-python 2026.2.22__tar.gz → 2026.2.24__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {vws_python-2026.2.22 → vws_python-2026.2.24}/CHANGELOG.rst +8 -0
- {vws_python-2026.2.22/src/vws_python.egg-info → vws_python-2026.2.24}/PKG-INFO +8 -7
- {vws_python-2026.2.22 → vws_python-2026.2.24}/conftest.py +2 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/docs/source/api-reference.rst +4 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/pyproject.toml +9 -6
- {vws_python-2026.2.22 → vws_python-2026.2.24}/spelling_private_dict.txt +1 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/src/vws/_vws_request.py +15 -22
- {vws_python-2026.2.22 → vws_python-2026.2.24}/src/vws/query.py +11 -16
- vws_python-2026.2.24/src/vws/transports.py +158 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/src/vws/vumark_service.py +12 -4
- {vws_python-2026.2.22 → vws_python-2026.2.24}/src/vws/vws.py +32 -19
- {vws_python-2026.2.22 → vws_python-2026.2.24/src/vws_python.egg-info}/PKG-INFO +8 -7
- {vws_python-2026.2.22 → vws_python-2026.2.24}/src/vws_python.egg-info/SOURCES.txt +2 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/src/vws_python.egg-info/requires.txt +7 -6
- {vws_python-2026.2.22 → vws_python-2026.2.24}/tests/conftest.py +6 -3
- vws_python-2026.2.24/tests/test_transports.py +61 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/.git_archival.txt +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/.gitattributes +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/.github/dependabot.yml +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/.github/workflows/ci.yml +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/.github/workflows/dependabot-merge.yml +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/.github/workflows/lint.yml +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/.github/workflows/publish-site.yml +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/.github/workflows/release.yml +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/.gitignore +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/.pre-commit-config.yaml +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/.prettierrc +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/.vscode/extensions.json +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/.vscode/settings.json +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/CODE_OF_CONDUCT.rst +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/LICENSE +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/README.rst +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/docs/source/__init__.py +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/docs/source/changelog.rst +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/docs/source/conf.py +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/docs/source/contributing.rst +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/docs/source/exceptions.rst +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/docs/source/index.rst +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/docs/source/release-process.rst +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/setup.cfg +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/src/vws/__init__.py +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/src/vws/exceptions/__init__.py +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/src/vws/exceptions/base_exceptions.py +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/src/vws/exceptions/cloud_reco_exceptions.py +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/src/vws/exceptions/custom_exceptions.py +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/src/vws/exceptions/vws_exceptions.py +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/src/vws/include_target_data.py +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/src/vws/py.typed +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/src/vws/reports.py +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/src/vws/response.py +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/src/vws/vumark_accept.py +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/src/vws_python.egg-info/dependency_links.txt +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/src/vws_python.egg-info/not-zip-safe +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/src/vws_python.egg-info/top_level.txt +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/tests/__init__.py +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/tests/test_cloud_reco_exceptions.py +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/tests/test_query.py +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/tests/test_vws.py +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/tests/test_vws_exceptions.py +0 -0
- {vws_python-2026.2.22 → vws_python-2026.2.24}/zizmor.yml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vws-python
|
|
3
|
-
Version: 2026.2.
|
|
3
|
+
Version: 2026.2.24
|
|
4
4
|
Summary: Interact with the Vuforia Web Services (VWS) API.
|
|
5
5
|
Author-email: Adam Dangoor <adamdangoor@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -18,6 +18,7 @@ Requires-Python: >=3.13
|
|
|
18
18
|
Description-Content-Type: text/x-rst
|
|
19
19
|
License-File: LICENSE
|
|
20
20
|
Requires-Dist: beartype>=0.22.9
|
|
21
|
+
Requires-Dist: httpx>=0.28.0
|
|
21
22
|
Requires-Dist: requests>=2.32.3
|
|
22
23
|
Requires-Dist: urllib3>=2.2.3
|
|
23
24
|
Requires-Dist: vws-auth-tools>=2024.7.12
|
|
@@ -36,16 +37,16 @@ Requires-Dist: prek==0.3.3; extra == "dev"
|
|
|
36
37
|
Requires-Dist: pydocstringformatter==0.7.5; extra == "dev"
|
|
37
38
|
Requires-Dist: pydocstyle==6.3; extra == "dev"
|
|
38
39
|
Requires-Dist: pygments==2.19.2; extra == "dev"
|
|
39
|
-
Requires-Dist: pylint[spelling]==4.0.
|
|
40
|
+
Requires-Dist: pylint[spelling]==4.0.5; extra == "dev"
|
|
40
41
|
Requires-Dist: pylint-per-file-ignores==3.2.0; extra == "dev"
|
|
41
|
-
Requires-Dist: pyproject-fmt==2.16.
|
|
42
|
-
Requires-Dist: pyrefly==0.
|
|
42
|
+
Requires-Dist: pyproject-fmt==2.16.2; extra == "dev"
|
|
43
|
+
Requires-Dist: pyrefly==0.54.0; extra == "dev"
|
|
43
44
|
Requires-Dist: pyright==1.1.408; extra == "dev"
|
|
44
45
|
Requires-Dist: pyroma==5.0.1; extra == "dev"
|
|
45
46
|
Requires-Dist: pytest==9.0.2; extra == "dev"
|
|
46
47
|
Requires-Dist: pytest-cov==7.0.0; extra == "dev"
|
|
47
48
|
Requires-Dist: pyyaml==6.0.3; extra == "dev"
|
|
48
|
-
Requires-Dist: ruff==0.15.
|
|
49
|
+
Requires-Dist: ruff==0.15.2; extra == "dev"
|
|
49
50
|
Requires-Dist: shellcheck-py==0.11.0.1; extra == "dev"
|
|
50
51
|
Requires-Dist: shfmt-py==3.12.0.2; extra == "dev"
|
|
51
52
|
Requires-Dist: sphinx==9.1.0; extra == "dev"
|
|
@@ -57,10 +58,10 @@ Requires-Dist: sphinxcontrib-spelling==8.0.2; extra == "dev"
|
|
|
57
58
|
Requires-Dist: sybil==9.3.0; extra == "dev"
|
|
58
59
|
Requires-Dist: torch>=2.5.1; extra == "dev"
|
|
59
60
|
Requires-Dist: torchvision>=0.20.1; extra == "dev"
|
|
60
|
-
Requires-Dist: ty==0.0.
|
|
61
|
+
Requires-Dist: ty==0.0.18; extra == "dev"
|
|
61
62
|
Requires-Dist: types-requests==2.32.4.20260107; extra == "dev"
|
|
62
63
|
Requires-Dist: vulture==2.14; extra == "dev"
|
|
63
|
-
Requires-Dist: vws-python-mock==2026.2.22.
|
|
64
|
+
Requires-Dist: vws-python-mock==2026.2.22.3; extra == "dev"
|
|
64
65
|
Requires-Dist: vws-test-fixtures==2023.3.5; extra == "dev"
|
|
65
66
|
Requires-Dist: yamlfix==1.19.1; extra == "dev"
|
|
66
67
|
Requires-Dist: zizmor==1.22.0; extra == "dev"
|
|
@@ -28,6 +28,7 @@ def pytest_collection_modifyitems(items: list[pytest.Item]) -> None:
|
|
|
28
28
|
|
|
29
29
|
@pytest.fixture(name="make_image_file")
|
|
30
30
|
def fixture_make_image_file(
|
|
31
|
+
*,
|
|
31
32
|
high_quality_image: io.BytesIO,
|
|
32
33
|
) -> Generator[None]:
|
|
33
34
|
"""Make an image file available in the test directory.
|
|
@@ -43,6 +44,7 @@ def fixture_make_image_file(
|
|
|
43
44
|
|
|
44
45
|
@pytest.fixture(name="mock_vws")
|
|
45
46
|
def fixture_mock_vws(
|
|
47
|
+
*,
|
|
46
48
|
monkeypatch: pytest.MonkeyPatch,
|
|
47
49
|
) -> Generator[None]:
|
|
48
50
|
"""Yield a mock VWS.
|
|
@@ -33,6 +33,7 @@ dynamic = [
|
|
|
33
33
|
]
|
|
34
34
|
dependencies = [
|
|
35
35
|
"beartype>=0.22.9",
|
|
36
|
+
"httpx>=0.28.0",
|
|
36
37
|
"requests>=2.32.3",
|
|
37
38
|
"urllib3>=2.2.3",
|
|
38
39
|
"vws-auth-tools>=2024.7.12",
|
|
@@ -52,16 +53,16 @@ optional-dependencies.dev = [
|
|
|
52
53
|
"pydocstringformatter==0.7.5",
|
|
53
54
|
"pydocstyle==6.3",
|
|
54
55
|
"pygments==2.19.2",
|
|
55
|
-
"pylint[spelling]==4.0.
|
|
56
|
+
"pylint[spelling]==4.0.5",
|
|
56
57
|
"pylint-per-file-ignores==3.2.0",
|
|
57
|
-
"pyproject-fmt==2.16.
|
|
58
|
-
"pyrefly==0.
|
|
58
|
+
"pyproject-fmt==2.16.2",
|
|
59
|
+
"pyrefly==0.54.0",
|
|
59
60
|
"pyright==1.1.408",
|
|
60
61
|
"pyroma==5.0.1",
|
|
61
62
|
"pytest==9.0.2",
|
|
62
63
|
"pytest-cov==7.0.0",
|
|
63
64
|
"pyyaml==6.0.3",
|
|
64
|
-
"ruff==0.15.
|
|
65
|
+
"ruff==0.15.2",
|
|
65
66
|
# We add shellcheck-py not only for shell scripts and shell code blocks,
|
|
66
67
|
# but also because having it installed means that ``actionlint-py`` will
|
|
67
68
|
# use it to lint shell commands in GitHub workflow files.
|
|
@@ -79,10 +80,10 @@ optional-dependencies.dev = [
|
|
|
79
80
|
# See: https://vws-python.github.io/vws-python-mock/installation.html#faster-installation
|
|
80
81
|
"torch>=2.5.1",
|
|
81
82
|
"torchvision>=0.20.1",
|
|
82
|
-
"ty==0.0.
|
|
83
|
+
"ty==0.0.18",
|
|
83
84
|
"types-requests==2.32.4.20260107",
|
|
84
85
|
"vulture==2.14",
|
|
85
|
-
"vws-python-mock==2026.2.22.
|
|
86
|
+
"vws-python-mock==2026.2.22.3",
|
|
86
87
|
"vws-test-fixtures==2023.3.5",
|
|
87
88
|
"yamlfix==1.19.1",
|
|
88
89
|
"zizmor==1.22.0",
|
|
@@ -357,6 +358,8 @@ ignore_path = [
|
|
|
357
358
|
# Ideally we would limit the paths to the source code where we want to ignore names,
|
|
358
359
|
# but Vulture does not enable this.
|
|
359
360
|
ignore_names = [
|
|
361
|
+
# Public API classes imported by users from vws.transports
|
|
362
|
+
"HTTPXTransport",
|
|
360
363
|
# pytest configuration
|
|
361
364
|
"pytest_collect_file",
|
|
362
365
|
"pytest_collection_modifyitems",
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
API.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
import requests
|
|
6
5
|
from beartype import BeartypeConf, beartype
|
|
7
6
|
from vws_auth_tools import authorization_header, rfc_1123_date
|
|
8
7
|
|
|
9
8
|
from vws.response import Response
|
|
9
|
+
from vws.transports import Transport
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
@beartype(conf=BeartypeConf(is_pep484_tower=True))
|
|
@@ -21,27 +21,30 @@ def target_api_request(
|
|
|
21
21
|
base_vws_url: str,
|
|
22
22
|
request_timeout_seconds: float | tuple[float, float],
|
|
23
23
|
extra_headers: dict[str, str],
|
|
24
|
+
transport: Transport,
|
|
24
25
|
) -> Response:
|
|
25
26
|
"""Make a request to the Vuforia Target API.
|
|
26
27
|
|
|
27
|
-
This uses `requests` to make a request against https://vws.vuforia.com.
|
|
28
|
-
|
|
29
28
|
Args:
|
|
30
29
|
content_type: The content type of the request.
|
|
31
30
|
server_access_key: A VWS server access key.
|
|
32
31
|
server_secret_key: A VWS server secret key.
|
|
33
|
-
method: The HTTP method which will be used in the
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
method: The HTTP method which will be used in the
|
|
33
|
+
request.
|
|
34
|
+
data: The request body which will be used in the
|
|
36
35
|
request.
|
|
36
|
+
request_path: The path to the endpoint which will be
|
|
37
|
+
used in the request.
|
|
37
38
|
base_vws_url: The base URL for the VWS API.
|
|
38
|
-
request_timeout_seconds: The timeout for the request
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
extra_headers: Additional headers to include in the
|
|
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.
|
|
42
45
|
|
|
43
46
|
Returns:
|
|
44
|
-
The response to the request
|
|
47
|
+
The response to the request.
|
|
45
48
|
"""
|
|
46
49
|
date_string = rfc_1123_date()
|
|
47
50
|
|
|
@@ -64,20 +67,10 @@ def target_api_request(
|
|
|
64
67
|
|
|
65
68
|
url = base_vws_url.rstrip("/") + request_path
|
|
66
69
|
|
|
67
|
-
|
|
70
|
+
return transport(
|
|
68
71
|
method=method,
|
|
69
72
|
url=url,
|
|
70
73
|
headers=headers,
|
|
71
74
|
data=data,
|
|
72
75
|
timeout=request_timeout_seconds,
|
|
73
76
|
)
|
|
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
|
-
)
|
|
@@ -6,7 +6,6 @@ import json
|
|
|
6
6
|
from http import HTTPMethod, HTTPStatus
|
|
7
7
|
from typing import Any, BinaryIO
|
|
8
8
|
|
|
9
|
-
import requests
|
|
10
9
|
from beartype import BeartypeConf, beartype
|
|
11
10
|
from urllib3.filepost import encode_multipart_formdata
|
|
12
11
|
from vws_auth_tools import authorization_header, rfc_1123_date
|
|
@@ -24,7 +23,7 @@ from vws.exceptions.custom_exceptions import (
|
|
|
24
23
|
)
|
|
25
24
|
from vws.include_target_data import CloudRecoIncludeTargetData
|
|
26
25
|
from vws.reports import QueryResult, TargetData
|
|
27
|
-
from vws.
|
|
26
|
+
from vws.transports import RequestsTransport, Transport
|
|
28
27
|
|
|
29
28
|
_ImageType = io.BytesIO | BinaryIO
|
|
30
29
|
|
|
@@ -50,21 +49,26 @@ class CloudRecoService:
|
|
|
50
49
|
client_secret_key: str,
|
|
51
50
|
base_vwq_url: str = "https://cloudreco.vuforia.com",
|
|
52
51
|
request_timeout_seconds: float | tuple[float, float] = 30.0,
|
|
52
|
+
transport: Transport | None = None,
|
|
53
53
|
) -> None:
|
|
54
54
|
"""
|
|
55
55
|
Args:
|
|
56
56
|
client_access_key: A VWS client access key.
|
|
57
57
|
client_secret_key: A VWS client secret key.
|
|
58
58
|
base_vwq_url: The base URL for the VWQ API.
|
|
59
|
-
request_timeout_seconds: The timeout for each
|
|
60
|
-
|
|
61
|
-
|
|
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()``.
|
|
63
66
|
"""
|
|
64
67
|
self._client_access_key = client_access_key
|
|
65
68
|
self._client_secret_key = client_secret_key
|
|
66
69
|
self._base_vwq_url = base_vwq_url
|
|
67
70
|
self._request_timeout_seconds = request_timeout_seconds
|
|
71
|
+
self._transport = transport or RequestsTransport()
|
|
68
72
|
|
|
69
73
|
def query(
|
|
70
74
|
self,
|
|
@@ -143,22 +147,13 @@ class CloudRecoService:
|
|
|
143
147
|
"Content-Type": content_type_header,
|
|
144
148
|
}
|
|
145
149
|
|
|
146
|
-
|
|
150
|
+
response = self._transport(
|
|
147
151
|
method=method,
|
|
148
152
|
url=self._base_vwq_url.rstrip("/") + request_path,
|
|
149
153
|
headers=headers,
|
|
150
154
|
data=content,
|
|
151
155
|
timeout=self._request_timeout_seconds,
|
|
152
156
|
)
|
|
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
|
-
)
|
|
162
157
|
|
|
163
158
|
if response.status_code == HTTPStatus.REQUEST_ENTITY_TOO_LARGE:
|
|
164
159
|
raise RequestEntityTooLargeError(response=response)
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""HTTP transport implementations for VWS clients."""
|
|
2
|
+
|
|
3
|
+
from typing import Protocol, runtime_checkable
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
import requests
|
|
7
|
+
from beartype import BeartypeConf, beartype
|
|
8
|
+
|
|
9
|
+
from vws.response import Response
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@runtime_checkable
|
|
13
|
+
class Transport(Protocol):
|
|
14
|
+
"""Protocol for HTTP transports used by VWS clients.
|
|
15
|
+
|
|
16
|
+
A transport is a callable that makes an HTTP request and
|
|
17
|
+
returns a ``Response``.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __call__(
|
|
21
|
+
self,
|
|
22
|
+
*,
|
|
23
|
+
method: str,
|
|
24
|
+
url: str,
|
|
25
|
+
headers: dict[str, str],
|
|
26
|
+
data: bytes,
|
|
27
|
+
timeout: float | tuple[float, float],
|
|
28
|
+
) -> Response:
|
|
29
|
+
"""Make an HTTP request.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
method: The HTTP method (e.g. "GET", "POST").
|
|
33
|
+
url: The full URL to request.
|
|
34
|
+
headers: Headers to send with the request.
|
|
35
|
+
data: The request body as bytes.
|
|
36
|
+
timeout: The timeout for the request. A float
|
|
37
|
+
sets both the connect and read timeouts. A
|
|
38
|
+
(connect, read) tuple sets them individually.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
A Response populated from the HTTP response.
|
|
42
|
+
"""
|
|
43
|
+
... # pylint: disable=unnecessary-ellipsis
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@beartype(conf=BeartypeConf(is_pep484_tower=True))
|
|
47
|
+
class RequestsTransport:
|
|
48
|
+
"""HTTP transport using the ``requests`` library.
|
|
49
|
+
|
|
50
|
+
This is the default transport.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __call__(
|
|
54
|
+
self,
|
|
55
|
+
*,
|
|
56
|
+
method: str,
|
|
57
|
+
url: str,
|
|
58
|
+
headers: dict[str, str],
|
|
59
|
+
data: bytes,
|
|
60
|
+
timeout: float | tuple[float, float],
|
|
61
|
+
) -> Response:
|
|
62
|
+
"""Make an HTTP request using ``requests``.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
method: The HTTP method.
|
|
66
|
+
url: The full URL.
|
|
67
|
+
headers: Request headers.
|
|
68
|
+
data: The request body.
|
|
69
|
+
timeout: The request timeout.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
A Response populated from the requests response.
|
|
73
|
+
"""
|
|
74
|
+
requests_response = requests.request(
|
|
75
|
+
method=method,
|
|
76
|
+
url=url,
|
|
77
|
+
headers=headers,
|
|
78
|
+
data=data,
|
|
79
|
+
timeout=timeout,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
return Response(
|
|
83
|
+
text=requests_response.text,
|
|
84
|
+
url=requests_response.url,
|
|
85
|
+
status_code=requests_response.status_code,
|
|
86
|
+
headers=dict(requests_response.headers),
|
|
87
|
+
request_body=requests_response.request.body,
|
|
88
|
+
tell_position=requests_response.raw.tell(),
|
|
89
|
+
content=bytes(requests_response.content),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@beartype(conf=BeartypeConf(is_pep484_tower=True))
|
|
94
|
+
class HTTPXTransport:
|
|
95
|
+
"""HTTP transport using the ``httpx`` library.
|
|
96
|
+
|
|
97
|
+
Use this transport for environments where ``httpx`` is
|
|
98
|
+
preferred over ``requests``.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
def __call__(
|
|
102
|
+
self,
|
|
103
|
+
*,
|
|
104
|
+
method: str,
|
|
105
|
+
url: str,
|
|
106
|
+
headers: dict[str, str],
|
|
107
|
+
data: bytes,
|
|
108
|
+
timeout: float | tuple[float, float],
|
|
109
|
+
) -> Response:
|
|
110
|
+
"""Make an HTTP request using ``httpx``.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
method: The HTTP method.
|
|
114
|
+
url: The full URL.
|
|
115
|
+
headers: Request headers.
|
|
116
|
+
data: The request body.
|
|
117
|
+
timeout: The request timeout.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
A Response populated from the httpx response.
|
|
121
|
+
"""
|
|
122
|
+
if isinstance(timeout, tuple):
|
|
123
|
+
connect_timeout, read_timeout = timeout
|
|
124
|
+
httpx_timeout = httpx.Timeout(
|
|
125
|
+
connect=connect_timeout,
|
|
126
|
+
read=read_timeout,
|
|
127
|
+
write=None,
|
|
128
|
+
pool=None,
|
|
129
|
+
)
|
|
130
|
+
else:
|
|
131
|
+
httpx_timeout = httpx.Timeout(
|
|
132
|
+
connect=timeout,
|
|
133
|
+
read=timeout,
|
|
134
|
+
write=None,
|
|
135
|
+
pool=None,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
httpx_response = httpx.request(
|
|
139
|
+
method=method,
|
|
140
|
+
url=url,
|
|
141
|
+
headers=headers,
|
|
142
|
+
content=data,
|
|
143
|
+
timeout=httpx_timeout,
|
|
144
|
+
follow_redirects=True,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
content = bytes(httpx_response.content)
|
|
148
|
+
request_content = httpx_response.request.content
|
|
149
|
+
|
|
150
|
+
return Response(
|
|
151
|
+
text=httpx_response.text,
|
|
152
|
+
url=str(object=httpx_response.url),
|
|
153
|
+
status_code=httpx_response.status_code,
|
|
154
|
+
headers=dict(httpx_response.headers),
|
|
155
|
+
request_body=bytes(request_content) or None,
|
|
156
|
+
tell_position=len(content),
|
|
157
|
+
content=content,
|
|
158
|
+
)
|
|
@@ -20,6 +20,7 @@ from vws.exceptions.vws_exceptions import (
|
|
|
20
20
|
TooManyRequestsError,
|
|
21
21
|
UnknownTargetError,
|
|
22
22
|
)
|
|
23
|
+
from vws.transports import RequestsTransport, Transport
|
|
23
24
|
from vws.vumark_accept import VuMarkAccept
|
|
24
25
|
|
|
25
26
|
|
|
@@ -29,25 +30,31 @@ class VuMarkService:
|
|
|
29
30
|
|
|
30
31
|
def __init__(
|
|
31
32
|
self,
|
|
33
|
+
*,
|
|
32
34
|
server_access_key: str,
|
|
33
35
|
server_secret_key: str,
|
|
34
36
|
base_vws_url: str = "https://vws.vuforia.com",
|
|
35
37
|
request_timeout_seconds: float | tuple[float, float] = 30.0,
|
|
38
|
+
transport: Transport | None = None,
|
|
36
39
|
) -> None:
|
|
37
40
|
"""
|
|
38
41
|
Args:
|
|
39
42
|
server_access_key: A VWS server access key.
|
|
40
43
|
server_secret_key: A VWS server secret key.
|
|
41
44
|
base_vws_url: The base URL for the VWS API.
|
|
42
|
-
request_timeout_seconds: The timeout for each
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
tuple.
|
|
45
|
+
request_timeout_seconds: The timeout for each
|
|
46
|
+
HTTP request. This can be a float to set both
|
|
47
|
+
the connect and read timeouts, or a
|
|
48
|
+
(connect, read) tuple.
|
|
49
|
+
transport: The HTTP transport to use for
|
|
50
|
+
requests. Defaults to
|
|
51
|
+
``RequestsTransport()``.
|
|
46
52
|
"""
|
|
47
53
|
self._server_access_key = server_access_key
|
|
48
54
|
self._server_secret_key = server_secret_key
|
|
49
55
|
self._base_vws_url = base_vws_url
|
|
50
56
|
self._request_timeout_seconds = request_timeout_seconds
|
|
57
|
+
self._transport = transport or RequestsTransport()
|
|
51
58
|
|
|
52
59
|
def generate_vumark_instance(
|
|
53
60
|
self,
|
|
@@ -109,6 +116,7 @@ class VuMarkService:
|
|
|
109
116
|
base_vws_url=self._base_vws_url,
|
|
110
117
|
request_timeout_seconds=self._request_timeout_seconds,
|
|
111
118
|
extra_headers={"Accept": accept},
|
|
119
|
+
transport=self._transport,
|
|
112
120
|
)
|
|
113
121
|
|
|
114
122
|
if (
|
|
@@ -43,6 +43,7 @@ from vws.reports import (
|
|
|
43
43
|
TargetSummaryReport,
|
|
44
44
|
)
|
|
45
45
|
from vws.response import Response
|
|
46
|
+
from vws.transports import RequestsTransport, Transport
|
|
46
47
|
|
|
47
48
|
_ImageType = io.BytesIO | BinaryIO
|
|
48
49
|
|
|
@@ -68,21 +69,26 @@ class VWS:
|
|
|
68
69
|
server_secret_key: str,
|
|
69
70
|
base_vws_url: str = "https://vws.vuforia.com",
|
|
70
71
|
request_timeout_seconds: float | tuple[float, float] = 30.0,
|
|
72
|
+
transport: Transport | None = None,
|
|
71
73
|
) -> None:
|
|
72
74
|
"""
|
|
73
75
|
Args:
|
|
74
76
|
server_access_key: A VWS server access key.
|
|
75
77
|
server_secret_key: A VWS server secret key.
|
|
76
78
|
base_vws_url: The base URL for the VWS API.
|
|
77
|
-
request_timeout_seconds: The timeout for each
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
tuple.
|
|
79
|
+
request_timeout_seconds: The timeout for each
|
|
80
|
+
HTTP request. This can be a float to set both
|
|
81
|
+
the connect and read timeouts, or a
|
|
82
|
+
(connect, read) tuple.
|
|
83
|
+
transport: The HTTP transport to use for
|
|
84
|
+
requests. Defaults to
|
|
85
|
+
``RequestsTransport()``.
|
|
81
86
|
"""
|
|
82
87
|
self._server_access_key = server_access_key
|
|
83
88
|
self._server_secret_key = server_secret_key
|
|
84
89
|
self._base_vws_url = base_vws_url
|
|
85
90
|
self._request_timeout_seconds = request_timeout_seconds
|
|
91
|
+
self._transport = transport or RequestsTransport()
|
|
86
92
|
|
|
87
93
|
def make_request(
|
|
88
94
|
self,
|
|
@@ -96,29 +102,31 @@ class VWS:
|
|
|
96
102
|
) -> Response:
|
|
97
103
|
"""Make a request to the Vuforia Target API.
|
|
98
104
|
|
|
99
|
-
This uses `requests` to make a request against Vuforia.
|
|
100
|
-
|
|
101
105
|
Args:
|
|
102
|
-
method: The HTTP method which will be used in
|
|
103
|
-
|
|
104
|
-
|
|
106
|
+
method: The HTTP method which will be used in
|
|
107
|
+
the request.
|
|
108
|
+
data: The request body which will be used in the
|
|
105
109
|
request.
|
|
106
|
-
|
|
110
|
+
request_path: The path to the endpoint which
|
|
111
|
+
will be used in the request.
|
|
112
|
+
expected_result_code: See
|
|
113
|
+
"VWS API Result Codes" on
|
|
107
114
|
https://developer.vuforia.com/library/web-api/cloud-targets-web-services-api.
|
|
108
115
|
content_type: The content type of the request.
|
|
109
|
-
extra_headers: Additional headers to include in
|
|
116
|
+
extra_headers: Additional headers to include in
|
|
117
|
+
the request.
|
|
110
118
|
|
|
111
119
|
Returns:
|
|
112
|
-
The response to the request
|
|
120
|
+
The response to the request.
|
|
113
121
|
|
|
114
122
|
Raises:
|
|
115
|
-
~vws.exceptions.custom_exceptions.ServerError:
|
|
116
|
-
with Vuforia's servers.
|
|
117
|
-
~vws.exceptions.vws_exceptions.TooManyRequestsError:
|
|
118
|
-
rate limiting access.
|
|
119
|
-
json.JSONDecodeError: The server did not respond
|
|
120
|
-
This may happen if the
|
|
121
|
-
server.
|
|
123
|
+
~vws.exceptions.custom_exceptions.ServerError:
|
|
124
|
+
There is an error with Vuforia's servers.
|
|
125
|
+
~vws.exceptions.vws_exceptions.TooManyRequestsError:
|
|
126
|
+
Vuforia is rate limiting access.
|
|
127
|
+
json.JSONDecodeError: The server did not respond
|
|
128
|
+
with valid JSON. This may happen if the
|
|
129
|
+
server address is not a valid Vuforia server.
|
|
122
130
|
"""
|
|
123
131
|
response = target_api_request(
|
|
124
132
|
content_type=content_type,
|
|
@@ -130,6 +138,7 @@ class VWS:
|
|
|
130
138
|
base_vws_url=self._base_vws_url,
|
|
131
139
|
request_timeout_seconds=self._request_timeout_seconds,
|
|
132
140
|
extra_headers=extra_headers or {},
|
|
141
|
+
transport=self._transport,
|
|
133
142
|
)
|
|
134
143
|
|
|
135
144
|
if (
|
|
@@ -343,6 +352,10 @@ class VWS:
|
|
|
343
352
|
while True:
|
|
344
353
|
report = self.get_target_summary_report(target_id=target_id)
|
|
345
354
|
if report.status != TargetStatuses.PROCESSING:
|
|
355
|
+
# Guard against the target still being seen as
|
|
356
|
+
# processing by other endpoints due to eventual
|
|
357
|
+
# consistency.
|
|
358
|
+
time.sleep(seconds_between_requests)
|
|
346
359
|
return
|
|
347
360
|
|
|
348
361
|
elapsed_time = time.monotonic() - start_time
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vws-python
|
|
3
|
-
Version: 2026.2.
|
|
3
|
+
Version: 2026.2.24
|
|
4
4
|
Summary: Interact with the Vuforia Web Services (VWS) API.
|
|
5
5
|
Author-email: Adam Dangoor <adamdangoor@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -18,6 +18,7 @@ Requires-Python: >=3.13
|
|
|
18
18
|
Description-Content-Type: text/x-rst
|
|
19
19
|
License-File: LICENSE
|
|
20
20
|
Requires-Dist: beartype>=0.22.9
|
|
21
|
+
Requires-Dist: httpx>=0.28.0
|
|
21
22
|
Requires-Dist: requests>=2.32.3
|
|
22
23
|
Requires-Dist: urllib3>=2.2.3
|
|
23
24
|
Requires-Dist: vws-auth-tools>=2024.7.12
|
|
@@ -36,16 +37,16 @@ Requires-Dist: prek==0.3.3; extra == "dev"
|
|
|
36
37
|
Requires-Dist: pydocstringformatter==0.7.5; extra == "dev"
|
|
37
38
|
Requires-Dist: pydocstyle==6.3; extra == "dev"
|
|
38
39
|
Requires-Dist: pygments==2.19.2; extra == "dev"
|
|
39
|
-
Requires-Dist: pylint[spelling]==4.0.
|
|
40
|
+
Requires-Dist: pylint[spelling]==4.0.5; extra == "dev"
|
|
40
41
|
Requires-Dist: pylint-per-file-ignores==3.2.0; extra == "dev"
|
|
41
|
-
Requires-Dist: pyproject-fmt==2.16.
|
|
42
|
-
Requires-Dist: pyrefly==0.
|
|
42
|
+
Requires-Dist: pyproject-fmt==2.16.2; extra == "dev"
|
|
43
|
+
Requires-Dist: pyrefly==0.54.0; extra == "dev"
|
|
43
44
|
Requires-Dist: pyright==1.1.408; extra == "dev"
|
|
44
45
|
Requires-Dist: pyroma==5.0.1; extra == "dev"
|
|
45
46
|
Requires-Dist: pytest==9.0.2; extra == "dev"
|
|
46
47
|
Requires-Dist: pytest-cov==7.0.0; extra == "dev"
|
|
47
48
|
Requires-Dist: pyyaml==6.0.3; extra == "dev"
|
|
48
|
-
Requires-Dist: ruff==0.15.
|
|
49
|
+
Requires-Dist: ruff==0.15.2; extra == "dev"
|
|
49
50
|
Requires-Dist: shellcheck-py==0.11.0.1; extra == "dev"
|
|
50
51
|
Requires-Dist: shfmt-py==3.12.0.2; extra == "dev"
|
|
51
52
|
Requires-Dist: sphinx==9.1.0; extra == "dev"
|
|
@@ -57,10 +58,10 @@ Requires-Dist: sphinxcontrib-spelling==8.0.2; extra == "dev"
|
|
|
57
58
|
Requires-Dist: sybil==9.3.0; extra == "dev"
|
|
58
59
|
Requires-Dist: torch>=2.5.1; extra == "dev"
|
|
59
60
|
Requires-Dist: torchvision>=0.20.1; extra == "dev"
|
|
60
|
-
Requires-Dist: ty==0.0.
|
|
61
|
+
Requires-Dist: ty==0.0.18; extra == "dev"
|
|
61
62
|
Requires-Dist: types-requests==2.32.4.20260107; extra == "dev"
|
|
62
63
|
Requires-Dist: vulture==2.14; extra == "dev"
|
|
63
|
-
Requires-Dist: vws-python-mock==2026.2.22.
|
|
64
|
+
Requires-Dist: vws-python-mock==2026.2.22.3; extra == "dev"
|
|
64
65
|
Requires-Dist: vws-test-fixtures==2023.3.5; extra == "dev"
|
|
65
66
|
Requires-Dist: yamlfix==1.19.1; extra == "dev"
|
|
66
67
|
Requires-Dist: zizmor==1.22.0; extra == "dev"
|
|
@@ -34,6 +34,7 @@ src/vws/py.typed
|
|
|
34
34
|
src/vws/query.py
|
|
35
35
|
src/vws/reports.py
|
|
36
36
|
src/vws/response.py
|
|
37
|
+
src/vws/transports.py
|
|
37
38
|
src/vws/vumark_accept.py
|
|
38
39
|
src/vws/vumark_service.py
|
|
39
40
|
src/vws/vws.py
|
|
@@ -52,5 +53,6 @@ tests/__init__.py
|
|
|
52
53
|
tests/conftest.py
|
|
53
54
|
tests/test_cloud_reco_exceptions.py
|
|
54
55
|
tests/test_query.py
|
|
56
|
+
tests/test_transports.py
|
|
55
57
|
tests/test_vws.py
|
|
56
58
|
tests/test_vws_exceptions.py
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
beartype>=0.22.9
|
|
2
|
+
httpx>=0.28.0
|
|
2
3
|
requests>=2.32.3
|
|
3
4
|
urllib3>=2.2.3
|
|
4
5
|
vws-auth-tools>=2024.7.12
|
|
@@ -18,16 +19,16 @@ prek==0.3.3
|
|
|
18
19
|
pydocstringformatter==0.7.5
|
|
19
20
|
pydocstyle==6.3
|
|
20
21
|
pygments==2.19.2
|
|
21
|
-
pylint[spelling]==4.0.
|
|
22
|
+
pylint[spelling]==4.0.5
|
|
22
23
|
pylint-per-file-ignores==3.2.0
|
|
23
|
-
pyproject-fmt==2.16.
|
|
24
|
-
pyrefly==0.
|
|
24
|
+
pyproject-fmt==2.16.2
|
|
25
|
+
pyrefly==0.54.0
|
|
25
26
|
pyright==1.1.408
|
|
26
27
|
pyroma==5.0.1
|
|
27
28
|
pytest==9.0.2
|
|
28
29
|
pytest-cov==7.0.0
|
|
29
30
|
pyyaml==6.0.3
|
|
30
|
-
ruff==0.15.
|
|
31
|
+
ruff==0.15.2
|
|
31
32
|
shellcheck-py==0.11.0.1
|
|
32
33
|
shfmt-py==3.12.0.2
|
|
33
34
|
sphinx==9.1.0
|
|
@@ -39,10 +40,10 @@ sphinxcontrib-spelling==8.0.2
|
|
|
39
40
|
sybil==9.3.0
|
|
40
41
|
torch>=2.5.1
|
|
41
42
|
torchvision>=0.20.1
|
|
42
|
-
ty==0.0.
|
|
43
|
+
ty==0.0.18
|
|
43
44
|
types-requests==2.32.4.20260107
|
|
44
45
|
vulture==2.14
|
|
45
|
-
vws-python-mock==2026.2.22.
|
|
46
|
+
vws-python-mock==2026.2.22.3
|
|
46
47
|
vws-test-fixtures==2023.3.5
|
|
47
48
|
yamlfix==1.19.1
|
|
48
49
|
zizmor==1.22.0
|
|
@@ -35,6 +35,7 @@ def fixture_mock_vumark_database() -> Generator[VuMarkDatabase]:
|
|
|
35
35
|
|
|
36
36
|
@pytest.fixture
|
|
37
37
|
def vumark_service_client(
|
|
38
|
+
*,
|
|
38
39
|
_mock_vumark_database: VuMarkDatabase,
|
|
39
40
|
) -> VuMarkService:
|
|
40
41
|
"""A ``VuMarkService`` client which connects to a mock VuMark database."""
|
|
@@ -45,14 +46,14 @@ def vumark_service_client(
|
|
|
45
46
|
|
|
46
47
|
|
|
47
48
|
@pytest.fixture
|
|
48
|
-
def vumark_target_id(_mock_vumark_database: VuMarkDatabase) -> str:
|
|
49
|
+
def vumark_target_id(*, _mock_vumark_database: VuMarkDatabase) -> str:
|
|
49
50
|
"""The ID of the VuMark template target."""
|
|
50
51
|
(target,) = _mock_vumark_database.vumark_targets
|
|
51
52
|
return target.target_id
|
|
52
53
|
|
|
53
54
|
|
|
54
55
|
@pytest.fixture
|
|
55
|
-
def vws_client(_mock_database: CloudDatabase) -> VWS:
|
|
56
|
+
def vws_client(*, _mock_database: CloudDatabase) -> VWS:
|
|
56
57
|
"""A VWS client which connects to a mock database."""
|
|
57
58
|
return VWS(
|
|
58
59
|
server_access_key=_mock_database.server_access_key,
|
|
@@ -61,7 +62,7 @@ def vws_client(_mock_database: CloudDatabase) -> VWS:
|
|
|
61
62
|
|
|
62
63
|
|
|
63
64
|
@pytest.fixture
|
|
64
|
-
def cloud_reco_client(_mock_database: CloudDatabase) -> CloudRecoService:
|
|
65
|
+
def cloud_reco_client(*, _mock_database: CloudDatabase) -> CloudRecoService:
|
|
65
66
|
"""A ``CloudRecoService`` client which connects to a mock database."""
|
|
66
67
|
return CloudRecoService(
|
|
67
68
|
client_access_key=_mock_database.client_access_key,
|
|
@@ -71,6 +72,7 @@ def cloud_reco_client(_mock_database: CloudDatabase) -> CloudRecoService:
|
|
|
71
72
|
|
|
72
73
|
@pytest.fixture(name="image_file", params=["r+b", "rb"])
|
|
73
74
|
def fixture_image_file(
|
|
75
|
+
*,
|
|
74
76
|
high_quality_image: io.BytesIO,
|
|
75
77
|
tmp_path: Path,
|
|
76
78
|
request: pytest.FixtureRequest,
|
|
@@ -86,6 +88,7 @@ def fixture_image_file(
|
|
|
86
88
|
|
|
87
89
|
@pytest.fixture(params=["high_quality_image", "image_file"])
|
|
88
90
|
def image(
|
|
91
|
+
*,
|
|
89
92
|
request: pytest.FixtureRequest,
|
|
90
93
|
high_quality_image: io.BytesIO,
|
|
91
94
|
image_file: BinaryIO,
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Tests for HTTP transport implementations."""
|
|
2
|
+
|
|
3
|
+
from http import HTTPStatus
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
import respx
|
|
7
|
+
|
|
8
|
+
from vws.response import Response
|
|
9
|
+
from vws.transports import HTTPXTransport
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestHTTPXTransport:
|
|
13
|
+
"""Tests for ``HTTPXTransport``."""
|
|
14
|
+
|
|
15
|
+
@staticmethod
|
|
16
|
+
@respx.mock
|
|
17
|
+
def test_float_timeout() -> None:
|
|
18
|
+
"""``HTTPXTransport`` works with a float timeout."""
|
|
19
|
+
route = respx.post(url="https://example.com/test").mock(
|
|
20
|
+
return_value=httpx.Response(
|
|
21
|
+
status_code=HTTPStatus.OK,
|
|
22
|
+
text="OK",
|
|
23
|
+
),
|
|
24
|
+
)
|
|
25
|
+
transport = HTTPXTransport()
|
|
26
|
+
response = transport(
|
|
27
|
+
method="POST",
|
|
28
|
+
url="https://example.com/test",
|
|
29
|
+
headers={"Content-Type": "text/plain"},
|
|
30
|
+
data=b"hello",
|
|
31
|
+
timeout=30.0,
|
|
32
|
+
)
|
|
33
|
+
assert route.called
|
|
34
|
+
assert isinstance(response, Response)
|
|
35
|
+
assert response.status_code == HTTPStatus.OK
|
|
36
|
+
assert response.text == "OK"
|
|
37
|
+
assert response.tell_position == len(b"OK")
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
@respx.mock
|
|
41
|
+
def test_tuple_timeout() -> None:
|
|
42
|
+
"""``HTTPXTransport`` works with a (connect, read) timeout
|
|
43
|
+
tuple.
|
|
44
|
+
"""
|
|
45
|
+
route = respx.post(url="https://example.com/test").mock(
|
|
46
|
+
return_value=httpx.Response(
|
|
47
|
+
status_code=HTTPStatus.OK,
|
|
48
|
+
text="OK",
|
|
49
|
+
),
|
|
50
|
+
)
|
|
51
|
+
transport = HTTPXTransport()
|
|
52
|
+
response = transport(
|
|
53
|
+
method="POST",
|
|
54
|
+
url="https://example.com/test",
|
|
55
|
+
headers={"Content-Type": "text/plain"},
|
|
56
|
+
data=b"hello",
|
|
57
|
+
timeout=(5.0, 30.0),
|
|
58
|
+
)
|
|
59
|
+
assert route.called
|
|
60
|
+
assert isinstance(response, Response)
|
|
61
|
+
assert response.status_code == HTTPStatus.OK
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|