Skip to content

Commit cf2f049

Browse files
adamtheturtleclaude
andcommitted
Add Model Target Web API client support
Add ``ModelTargetService`` and ``AsyncModelTargetService``, which cover the create, poll, download and delete lifecycle for standard and advanced Model Target datasets. The clients authenticate with OAuth2 client credentials and reuse the returned bearer token until it expires. Typed request structures describe the models, guide views and State-Based Model Target configuration which Vuforia accepts, and the Model Target error envelope is parsed into public exceptions. Closes #3119. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent cb8a5fb commit cf2f049

17 files changed

Lines changed: 2981 additions & 0 deletions

conftest.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@ def fixture_mock_vws(
6161
monkeypatch.setenv(name="VWS_CLIENT_ACCESS_KEY", value=client_access_key)
6262
monkeypatch.setenv(name="VWS_CLIENT_SECRET_KEY", value=client_secret_key)
6363
monkeypatch.setenv(name="VWS_DATABASE_ID", value=database_id)
64+
# The mock accepts one hard-coded pair of Model Target Web API OAuth2
65+
# credentials, which it does not expose.
66+
monkeypatch.setenv(
67+
name="VWS_MODEL_TARGET_CLIENT_ID",
68+
value="client-id",
69+
)
70+
monkeypatch.setenv(
71+
name="VWS_MODEL_TARGET_CLIENT_SECRET",
72+
value="client-secret",
73+
)
6474
# We use a low processing time so that tests run quickly.
6575
with MockVWS(processing_time_seconds=0.2) as mock:
6676
mock.add_cloud_database(cloud_database=database)

docs/source/api-reference.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,18 @@ API Reference
1717
:undoc-members:
1818
:members:
1919

20+
.. automodule:: vws.model_target_service
21+
:undoc-members:
22+
:members:
23+
24+
.. automodule:: vws.async_model_target_service
25+
:undoc-members:
26+
:members:
27+
28+
.. automodule:: vws.model_target_datasets
29+
:undoc-members:
30+
:members:
31+
2032
.. automodule:: vws.reports
2133
:undoc-members:
2234
:members:

docs/source/exceptions.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ CloudRecoService exceptions
2828
:inherited-members: Exception
2929
:exclude-members: errno, filename, filename2, strerror
3030

31+
ModelTargetService exceptions
32+
-----------------------------
33+
34+
.. automodule:: vws.exceptions.model_target_exceptions
35+
:members:
36+
:show-inheritance:
37+
:inherited-members: Exception
38+
:exclude-members: errno, filename, filename2, strerror
39+
3140
Custom exceptions
3241
-----------------
3342

docs/source/index.rst

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,82 @@ The report is generated in the background, and the URL it is served from expires
110110
# This database has no targets, so nothing has been recognized.
111111
assert not reco_counts_by_target_id
112112
113+
Model Targets
114+
-------------
115+
116+
Vuforia generates Model Target datasets from CAD models.
117+
This uses OAuth2 client credentials, which are separate from the VWS server keys.
118+
119+
Dataset generation happens in the background, and the generated dataset is downloaded as a zip file.
120+
121+
.. clear-namespace
122+
123+
.. code-block:: python
124+
125+
"""Generate a Model Target dataset and download it."""
126+
127+
import os
128+
129+
from vws import ModelTargetService
130+
from vws.model_target_datasets import (
131+
CadDataFormat,
132+
GuideViewPosition,
133+
ModelTargetDatasetType,
134+
ModelTargetModel,
135+
ModelTargetView,
136+
)
137+
from vws.reports import ModelTargetDatasetStatuses
138+
139+
client_id = os.environ["VWS_MODEL_TARGET_CLIENT_ID"]
140+
client_secret = os.environ["VWS_MODEL_TARGET_CLIENT_SECRET"]
141+
142+
model_target_client = ModelTargetService(
143+
client_id=client_id,
144+
client_secret=client_secret,
145+
)
146+
147+
model = ModelTargetModel(
148+
name="my_model",
149+
cad_data_url="https://example.com/my_model.zip",
150+
cad_data_format=CadDataFormat.ZIP,
151+
views=[
152+
ModelTargetView(
153+
name="front",
154+
guide_view_position=GuideViewPosition(
155+
rotation=[0.0, 0.0, 0.0, 1.0],
156+
translation=[0.0, 0.0, 1.0],
157+
),
158+
),
159+
],
160+
)
161+
162+
dataset_uuid = model_target_client.create_dataset(
163+
name="my_dataset",
164+
target_sdk="11.0",
165+
models=[model],
166+
dataset_type=ModelTargetDatasetType.STANDARD,
167+
)
168+
169+
report = model_target_client.wait_for_dataset_generated(
170+
dataset_uuid=dataset_uuid,
171+
dataset_type=ModelTargetDatasetType.STANDARD,
172+
)
173+
174+
assert report.status == ModelTargetDatasetStatuses.DONE
175+
176+
dataset = model_target_client.download_dataset(
177+
dataset_uuid=dataset_uuid,
178+
dataset_type=ModelTargetDatasetType.STANDARD,
179+
)
180+
181+
# The dataset is a zip file.
182+
assert dataset.startswith(b"PK")
183+
184+
model_target_client.delete_dataset(
185+
dataset_uuid=dataset_uuid,
186+
dataset_type=ModelTargetDatasetType.STANDARD,
187+
)
188+
113189
Testing
114190
-------
115191

newsfragments/3119.change.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add support for the Model Target Web API.
2+
``ModelTargetService`` and ``AsyncModelTargetService`` create standard and advanced Model Target datasets, wait for them to be generated, download them and delete them.

pyproject.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,14 +322,26 @@ exclude = [ ".venv" ]
322322
# Ideally we would limit the paths to the source code where we want to ignore names,
323323
# but Vulture does not enable this.
324324
ignore_names = [
325+
# Model Target model option values which this library does not use
326+
# itself, from vws.model_target_datasets
327+
"ADAPTIVE",
328+
"ALWAYS",
329+
"AR_CONTROLLER",
325330
# Public API classes imported by users from vws.transports
326331
"AsyncHTTPXTransport",
332+
"AUTO",
327333
# Sphinx
328334
"autoclass_content",
329335
"autoclass_content",
330336
"autodoc_member_order",
337+
"CAR",
331338
"copybutton_exclude",
339+
"DAE",
340+
"DEFAULT",
341+
"DYNAMIC",
332342
"extensions",
343+
"FALSE",
344+
"FBX",
333345
# pytest fixtures - we name fixtures like this for this purpose
334346
"fixture_*",
335347
"html_show_copyright",
@@ -340,25 +352,34 @@ ignore_names = [
340352
"html_title",
341353
"htmlhelp_basename",
342354
"HTTPXTransport",
355+
"IGES",
343356
"intersphinx_mapping",
344357
"language",
345358
"linkcheck_ignore",
346359
"linkcheck_retries",
360+
"LOW_FEATURE_OBJECTS",
347361
"master_doc",
362+
"NEVER",
348363
"nitpick_ignore",
349364
"nitpicky",
365+
"OBJ",
350366
"project_copyright",
367+
"PVZ",
351368
"pygments_style",
352369
# pytest configuration
353370
"pytest_collect_file",
354371
"pytest_plugins",
355372
"rst_prolog",
373+
"SCAN",
356374
"source_suffix",
357375
"spelling_word_list_filename",
376+
"STATIC",
377+
"STL",
358378
"templates_path",
359379
"towncrier_draft_autoversion_mode",
360380
"towncrier_draft_include_empty",
361381
"towncrier_draft_working_directory",
382+
"VRML",
362383
"warning_is_error",
363384
]
364385

spelling_private_dict.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ LicenseCheckFailed
1111
MatchProcessing
1212
MaxNumResultsOutOfRange
1313
MetadataTooLarge
14+
OAuth
1415
OopsAnErrorOccurredPossiblyBadName
1516
OopsAnErrorOccurredPossiblyBadNameError
1617
ProjectHasNoApiAccess
@@ -33,6 +34,7 @@ args
3334
ascii
3435
async
3536
asyncio
37+
balancer
3638
beartype
3739
bool
3840
boolean
@@ -71,6 +73,7 @@ json
7173
keyring
7274
kib
7375
kwargs
76+
lifecycle
7477
linters
7578
linting
7679
login

src/vws/__init__.py

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

3+
from .async_model_target_service import AsyncModelTargetService
34
from .async_query import AsyncCloudRecoService
45
from .async_vumark_service import AsyncVuMarkService
56
from .async_vws import AsyncVWS
7+
from .model_target_service import ModelTargetService
68
from .query import CloudRecoService
79
from .vumark_service import VuMarkService
810
from .vws import VWS
911

1012
__all__ = [
1113
"VWS",
1214
"AsyncCloudRecoService",
15+
"AsyncModelTargetService",
1316
"AsyncVWS",
1417
"AsyncVuMarkService",
1518
"CloudRecoService",
19+
"ModelTargetService",
1620
"VuMarkService",
1721
]

0 commit comments

Comments
 (0)