windowsml 1.8.2192__py3-none-win_arm64.whl

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.
@@ -0,0 +1,973 @@
1
+ # Copyright (C) Microsoft Corporation. All rights reserved.
2
+ """Python bindings for the Windows AI Machine Learning API."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import ctypes
7
+ from typing import Callable, Optional
8
+
9
+ from windowsml._bindings import (
10
+ AsyncBlock,
11
+ AsyncCompletionCallback,
12
+ AsyncProgressCallback,
13
+ CatalogModelInfoEnumCallback,
14
+ CatalogModelInfoHandle,
15
+ CatalogModelInstanceHandle,
16
+ CatalogModelInstanceResult,
17
+ CatalogModelInstanceStatus,
18
+ CatalogModelStatus,
19
+ EpCatalogHandle,
20
+ EpCertification,
21
+ EpEnumCallback,
22
+ EpReadyState,
23
+ ModelCatalogHandle,
24
+ ModelCatalogSourceHandle,
25
+ StringEnumCallback,
26
+ WinMLAsyncCancel,
27
+ WinMLAsyncClose,
28
+ WinMLAsyncGetStatus,
29
+ WinMLCatalogModelInfoEnumExecutionProviders,
30
+ WinMLCatalogModelInfoGetId,
31
+ WinMLCatalogModelInfoGetIdSize,
32
+ WinMLCatalogModelInfoGetInstanceAsync,
33
+ WinMLCatalogModelInfoGetInstanceResult,
34
+ WinMLCatalogModelInfoGetInstanceWithHeadersAsync,
35
+ WinMLCatalogModelInfoGetLicense,
36
+ WinMLCatalogModelInfoGetLicenseSize,
37
+ WinMLCatalogModelInfoGetLicenseText,
38
+ WinMLCatalogModelInfoGetLicenseTextSize,
39
+ WinMLCatalogModelInfoGetLicenseUri,
40
+ WinMLCatalogModelInfoGetLicenseUriSize,
41
+ WinMLCatalogModelInfoGetModelSizeInBytes,
42
+ WinMLCatalogModelInfoGetName,
43
+ WinMLCatalogModelInfoGetNameSize,
44
+ WinMLCatalogModelInfoGetPublisher,
45
+ WinMLCatalogModelInfoGetPublisherSize,
46
+ WinMLCatalogModelInfoGetSourceId,
47
+ WinMLCatalogModelInfoGetSourceIdSize,
48
+ WinMLCatalogModelInfoGetStatus,
49
+ WinMLCatalogModelInfoGetUri,
50
+ WinMLCatalogModelInfoGetUriSize,
51
+ WinMLCatalogModelInfoGetVersion,
52
+ WinMLCatalogModelInfoGetVersionSize,
53
+ WinMLCatalogModelInfoRelease,
54
+ WinMLCatalogModelInstanceClose,
55
+ WinMLCatalogModelInstanceEnumModelPaths,
56
+ WinMLCatalogModelInstanceGetModelInfo,
57
+ WinMLCatalogModelInstanceRelease,
58
+ WinMLEpCatalogCreate,
59
+ WinMLEpCatalogEnumProviders,
60
+ WinMLEpCatalogRelease,
61
+ WinMLEpEnsureReady,
62
+ WinMLEpEnsureReadyAsync,
63
+ WinMLEpGetCertification,
64
+ WinMLEpGetLibraryPath,
65
+ WinMLEpGetLibraryPathSize,
66
+ WinMLEpGetName,
67
+ WinMLEpGetNameSize,
68
+ WinMLEpGetPackageFamilyName,
69
+ WinMLEpGetPackageFamilyNameSize,
70
+ WinMLEpGetPackageRootPath,
71
+ WinMLEpGetPackageRootPathSize,
72
+ WinMLEpGetReadyState,
73
+ WinMLEpGetVersion,
74
+ WinMLEpGetVersionSize,
75
+ WinMLModelCatalogCreate,
76
+ WinMLModelCatalogEnumExecutionProviders,
77
+ WinMLModelCatalogFindAllModels,
78
+ WinMLModelCatalogFindAllModelsAsync,
79
+ WinMLModelCatalogFindAllModelsGetResult,
80
+ WinMLModelCatalogFindModel,
81
+ WinMLModelCatalogFindModelAsync,
82
+ WinMLModelCatalogFindModelGetResult,
83
+ WinMLModelCatalogGetAvailableModel,
84
+ WinMLModelCatalogGetAvailableModels,
85
+ WinMLModelCatalogRelease,
86
+ WinMLModelCatalogSetExecutionProviders,
87
+ WinMLModelCatalogSourceCreateFromUri,
88
+ WinMLModelCatalogSourceCreateFromUriAsync,
89
+ WinMLModelCatalogSourceCreateFromUriWithHeaders,
90
+ WinMLModelCatalogSourceCreateFromUriWithHeadersAsync,
91
+ WinMLModelCatalogSourceGetId,
92
+ WinMLModelCatalogSourceGetIdSize,
93
+ WinMLModelCatalogSourceGetResult,
94
+ WinMLModelCatalogSourceGetUri,
95
+ WinMLModelCatalogSourceGetUriSize,
96
+ WinMLModelCatalogSourceRelease,
97
+ )
98
+
99
+ __all__ = [
100
+ "EpReadyState",
101
+ "EpCertification",
102
+ "CatalogModelInstanceStatus",
103
+ "CatalogModelStatus",
104
+ "ExecutionProvider",
105
+ "EpCatalog",
106
+ "AsyncOperation",
107
+ "ModelCatalogSource",
108
+ "ModelCatalog",
109
+ "CatalogModelInfo",
110
+ "CatalogModelInstance",
111
+ ]
112
+
113
+
114
+ # ---------------------------------------------------------------------------
115
+ # Helpers
116
+ # ---------------------------------------------------------------------------
117
+
118
+
119
+ def _get_ep_string(handle, get_size_fn, get_fn) -> str:
120
+ """Read a variable-length string property from an EP handle."""
121
+ size = ctypes.c_size_t()
122
+ get_size_fn(handle, ctypes.byref(size))
123
+ if size.value == 0:
124
+ return ""
125
+ buf = ctypes.create_string_buffer(size.value)
126
+ used = ctypes.c_size_t()
127
+ get_fn(handle, size.value, buf, ctypes.byref(used))
128
+ return buf.value.decode("utf-8")
129
+
130
+
131
+ def _enum_strings(handle, enum_fn) -> list[str]:
132
+ """Collect strings from a WinMLStringEnumCallback-based enumeration."""
133
+ results: list[str] = []
134
+
135
+ @StringEnumCallback
136
+ def _callback(value, context):
137
+ try:
138
+ results.append(value.decode("utf-8"))
139
+ except Exception:
140
+ return False
141
+ return True
142
+
143
+ enum_fn(handle, _callback, None)
144
+ return results
145
+
146
+
147
+ def _make_header_arrays(headers: dict[str, str]):
148
+ """Convert a dict of headers to ctypes arrays for C API calls."""
149
+ keys = list(headers.keys())
150
+ values = list(headers.values())
151
+ count = len(keys)
152
+ keys_arr = (ctypes.c_char_p * count)(*(k.encode("utf-8") for k in keys))
153
+ values_arr = (ctypes.c_char_p * count)(*(v.encode("utf-8") for v in values))
154
+ return count, keys_arr, values_arr
155
+
156
+
157
+ # ---------------------------------------------------------------------------
158
+ # ExecutionProvider
159
+ # ---------------------------------------------------------------------------
160
+
161
+ class ExecutionProvider:
162
+ """Handle to a discovered Windows ML execution provider.
163
+
164
+ Instances are obtained from :meth:`EpCatalog.find_provider`.
165
+ The handle is owned by the catalog and must not outlive it.
166
+ """
167
+
168
+ def __init__(self, handle) -> None:
169
+ self._handle = handle
170
+
171
+ @property
172
+ def name(self) -> str:
173
+ return _get_ep_string(self._handle, WinMLEpGetNameSize, WinMLEpGetName)
174
+
175
+ @property
176
+ def version(self) -> str:
177
+ return _get_ep_string(self._handle, WinMLEpGetVersionSize, WinMLEpGetVersion)
178
+
179
+ @property
180
+ def package_family_name(self) -> str:
181
+ return _get_ep_string(
182
+ self._handle, WinMLEpGetPackageFamilyNameSize, WinMLEpGetPackageFamilyName
183
+ )
184
+
185
+ @property
186
+ def library_path(self) -> str:
187
+ return _get_ep_string(
188
+ self._handle, WinMLEpGetLibraryPathSize, WinMLEpGetLibraryPath
189
+ )
190
+
191
+ @property
192
+ def package_root_path(self) -> str:
193
+ return _get_ep_string(
194
+ self._handle, WinMLEpGetPackageRootPathSize, WinMLEpGetPackageRootPath
195
+ )
196
+
197
+ @property
198
+ def ready_state(self) -> EpReadyState:
199
+ state = ctypes.c_int()
200
+ WinMLEpGetReadyState(self._handle, ctypes.byref(state))
201
+ return EpReadyState(state.value)
202
+
203
+ @property
204
+ def certification(self) -> EpCertification:
205
+ cert = ctypes.c_int()
206
+ WinMLEpGetCertification(self._handle, ctypes.byref(cert))
207
+ return EpCertification(cert.value)
208
+
209
+ def ensure_ready(self) -> None:
210
+ """Block until the execution provider is ready.
211
+
212
+ Raises :class:`OSError` on failure.
213
+ """
214
+ WinMLEpEnsureReady(self._handle)
215
+
216
+ def ensure_ready_async(
217
+ self,
218
+ on_complete: Optional[Callable[[], None]] = None,
219
+ on_progress: Optional[Callable[[float], None]] = None,
220
+ ) -> "AsyncOperation":
221
+ """Start an asynchronous readiness check.
222
+
223
+ Returns an :class:`AsyncOperation` that can be used to poll, wait,
224
+ or cancel the operation.
225
+ """
226
+ return AsyncOperation(
227
+ WinMLEpEnsureReadyAsync, self._handle,
228
+ on_complete=on_complete, on_progress=on_progress,
229
+ )
230
+
231
+ def __repr__(self) -> str:
232
+ return f"ExecutionProvider(name={self.name!r})"
233
+
234
+
235
+ # ---------------------------------------------------------------------------
236
+ # AsyncOperation
237
+ # ---------------------------------------------------------------------------
238
+
239
+ # Prevent GC of in-flight async operations. The completion callback removes
240
+ # the entry once the native operation finishes.
241
+ _live_operations: set["AsyncOperation"] = set()
242
+
243
+
244
+ class AsyncOperation:
245
+ """Wraps a ``WinMLAsyncBlock`` for asynchronous operations."""
246
+
247
+ def __init__(
248
+ self,
249
+ start_fn,
250
+ *args,
251
+ result_fn: Optional[Callable] = None,
252
+ on_complete: Optional[Callable[[], None]] = None,
253
+ on_progress: Optional[Callable[[float], None]] = None,
254
+ ) -> None:
255
+ """Create and start an async operation.
256
+
257
+ Args:
258
+ start_fn: Native function that initiates the async work. It must
259
+ accept ``(*args, async_block)`` - i.e. the caller-supplied
260
+ positional arguments followed by a pointer to a
261
+ ``WinMLAsyncBlock`` as the **last** parameter.
262
+ *args: Positional arguments forwarded to *start_fn* before the
263
+ async block.
264
+ result_fn: Optional callable to extract a result from the
265
+ completed async block. If provided, :meth:`get_result`
266
+ becomes available.
267
+ on_complete: Optional callback invoked when the operation finishes.
268
+ on_progress: Optional callback invoked with progress (0.0-1.0).
269
+ """
270
+ self._closed = False
271
+ self._result_fn = result_fn
272
+
273
+ # Track live operations so the GC cannot destroy an AsyncOperation
274
+ # (and its native AsyncBlock) while the native thread is still running.
275
+ _live_operations.add(self)
276
+
277
+ # Build native callbacks. Must be stored as instance attributes to
278
+ # prevent garbage collection while the async operation is in flight.
279
+ # Exceptions in user callbacks are caught to avoid propagating Python
280
+ # exceptions through native call frames (undefined behavior).
281
+ if on_complete is not None:
282
+ _user_complete = on_complete
283
+
284
+ @AsyncCompletionCallback
285
+ def _on_complete(async_ptr):
286
+ try:
287
+ _user_complete()
288
+ except Exception:
289
+ pass
290
+ finally:
291
+ _live_operations.discard(self)
292
+
293
+ self._completion_cb = _on_complete
294
+ else:
295
+ @AsyncCompletionCallback
296
+ def _on_complete_default(async_ptr):
297
+ _live_operations.discard(self)
298
+
299
+ self._completion_cb = _on_complete_default
300
+
301
+ if on_progress is not None:
302
+ _user_progress = on_progress
303
+
304
+ @AsyncProgressCallback
305
+ def _on_progress(async_ptr, value):
306
+ try:
307
+ _user_progress(value)
308
+ except Exception:
309
+ pass
310
+
311
+ self._progress_cb = _on_progress
312
+ else:
313
+ self._progress_cb = AsyncProgressCallback()
314
+
315
+ self._block = AsyncBlock()
316
+ self._block.context = None
317
+ self._block.callback = self._completion_cb
318
+ self._block.progress = self._progress_cb
319
+
320
+ try:
321
+ start_fn(*args, ctypes.byref(self._block))
322
+ except BaseException:
323
+ _live_operations.discard(self)
324
+ raise
325
+
326
+ def get_status(self, wait: bool = False) -> None:
327
+ """Poll or wait for the async operation status.
328
+
329
+ Raises :class:`OSError` if the operation failed.
330
+ """
331
+ WinMLAsyncGetStatus(ctypes.byref(self._block), wait)
332
+
333
+ def wait(self) -> None:
334
+ """Block until the async operation completes.
335
+
336
+ Raises :class:`OSError` if the operation failed.
337
+ """
338
+ self.get_status(wait=True)
339
+
340
+ def cancel(self) -> None:
341
+ """Request cancellation of the async operation."""
342
+ WinMLAsyncCancel(ctypes.byref(self._block))
343
+
344
+ def get_result(self):
345
+ """Extract the result after the async operation has completed.
346
+
347
+ Only available for operations started with a *result_fn*.
348
+
349
+ Raises:
350
+ RuntimeError: If no result function was provided.
351
+ """
352
+ if self._result_fn is None:
353
+ raise RuntimeError("This operation does not produce a result")
354
+ return self._result_fn(ctypes.byref(self._block))
355
+
356
+ def close(self) -> None:
357
+ """Release async resources.
358
+
359
+ Waits for the operation to complete before releasing the async block
360
+ to avoid destroying it while the native thread is still running.
361
+ """
362
+ if not self._closed:
363
+ # Wait for the native operation to finish before freeing the block.
364
+ try:
365
+ WinMLAsyncGetStatus(ctypes.byref(self._block), True)
366
+ except OSError:
367
+ pass
368
+ WinMLAsyncClose(ctypes.byref(self._block))
369
+ self._closed = True
370
+ _live_operations.discard(self)
371
+
372
+ def __enter__(self) -> "AsyncOperation":
373
+ return self
374
+
375
+ def __exit__(self, *args) -> None:
376
+ self.close()
377
+
378
+ def __del__(self) -> None:
379
+ self.close()
380
+
381
+
382
+ # ---------------------------------------------------------------------------
383
+ # EpCatalog
384
+ # ---------------------------------------------------------------------------
385
+
386
+ class EpCatalog:
387
+ """Windows ML Execution Provider Catalog.
388
+
389
+ Use as a context manager::
390
+
391
+ with EpCatalog() as catalog:
392
+ for ep in catalog.find_all_providers():
393
+ print(ep.name, ep.ready_state)
394
+ """
395
+
396
+ def __init__(self) -> None:
397
+ handle = EpCatalogHandle()
398
+ WinMLEpCatalogCreate(ctypes.byref(handle))
399
+ self._handle = handle
400
+
401
+ def find_all_providers(self) -> list[ExecutionProvider]:
402
+ """Discover all registered execution providers.
403
+
404
+ Returns a list of :class:`ExecutionProvider` handles.
405
+
406
+ Raises:
407
+ RuntimeError: If the catalog has been closed.
408
+ """
409
+ if not self._handle:
410
+ raise RuntimeError("EpCatalog has been closed")
411
+ providers: list[ExecutionProvider] = []
412
+
413
+ @EpEnumCallback
414
+ def _callback(ep, info_ptr, context):
415
+ try:
416
+ providers.append(ExecutionProvider(ep))
417
+ except Exception:
418
+ return False
419
+ return True
420
+
421
+ WinMLEpCatalogEnumProviders(self._handle, _callback, None)
422
+
423
+ return providers
424
+
425
+ def close(self) -> None:
426
+ """Release the catalog and all associated EP handles."""
427
+ if self._handle:
428
+ WinMLEpCatalogRelease(self._handle)
429
+ self._handle = None
430
+
431
+ def __enter__(self) -> "EpCatalog":
432
+ return self
433
+
434
+ def __exit__(self, *args) -> None:
435
+ self.close()
436
+
437
+ def __del__(self) -> None:
438
+ try:
439
+ self.close()
440
+ except Exception:
441
+ pass
442
+
443
+
444
+ # ---------------------------------------------------------------------------
445
+ # ModelCatalogSource
446
+ # ---------------------------------------------------------------------------
447
+
448
+ class ModelCatalogSource:
449
+ """Represents a model catalog source endpoint.
450
+
451
+ Create from a URI::
452
+
453
+ source = ModelCatalogSource.create_from_uri("https://example.com/catalog")
454
+
455
+ Or with custom headers::
456
+
457
+ source = ModelCatalogSource.create_from_uri_with_headers(
458
+ "https://example.com/catalog",
459
+ {"Authorization": "Bearer token"},
460
+ )
461
+ """
462
+
463
+ def __init__(self, handle) -> None:
464
+ self._handle = handle
465
+
466
+ @staticmethod
467
+ def create_from_uri(uri: str) -> "ModelCatalogSource":
468
+ """Create a catalog source from a URI (blocking)."""
469
+ handle = ModelCatalogSourceHandle()
470
+ WinMLModelCatalogSourceCreateFromUri(
471
+ uri.encode("utf-8"), ctypes.byref(handle)
472
+ )
473
+ return ModelCatalogSource(handle)
474
+
475
+ @staticmethod
476
+ def create_from_uri_async(
477
+ uri: str,
478
+ on_complete: Optional[Callable[[], None]] = None,
479
+ on_progress: Optional[Callable[[float], None]] = None,
480
+ ) -> "AsyncOperation":
481
+ """Create a catalog source from a URI (async).
482
+
483
+ Call :meth:`AsyncOperation.get_result` after completion to obtain
484
+ the :class:`ModelCatalogSource`.
485
+ """
486
+ def _extract(block_ptr):
487
+ handle = ModelCatalogSourceHandle()
488
+ WinMLModelCatalogSourceGetResult(block_ptr, ctypes.byref(handle))
489
+ return ModelCatalogSource(handle)
490
+
491
+ return AsyncOperation(
492
+ WinMLModelCatalogSourceCreateFromUriAsync,
493
+ uri.encode("utf-8"),
494
+ result_fn=_extract,
495
+ on_complete=on_complete,
496
+ on_progress=on_progress,
497
+ )
498
+
499
+ @staticmethod
500
+ def create_from_uri_with_headers(
501
+ uri: str, headers: dict[str, str],
502
+ ) -> "ModelCatalogSource":
503
+ """Create a catalog source from a URI with custom HTTP headers (blocking)."""
504
+ count, keys_arr, values_arr = _make_header_arrays(headers)
505
+ handle = ModelCatalogSourceHandle()
506
+ WinMLModelCatalogSourceCreateFromUriWithHeaders(
507
+ uri.encode("utf-8"), count, keys_arr, values_arr, ctypes.byref(handle)
508
+ )
509
+ return ModelCatalogSource(handle)
510
+
511
+ @staticmethod
512
+ def create_from_uri_with_headers_async(
513
+ uri: str,
514
+ headers: dict[str, str],
515
+ on_complete: Optional[Callable[[], None]] = None,
516
+ on_progress: Optional[Callable[[float], None]] = None,
517
+ ) -> "AsyncOperation":
518
+ """Create a catalog source from a URI with custom HTTP headers (async).
519
+
520
+ Call :meth:`AsyncOperation.get_result` after completion to obtain
521
+ the :class:`ModelCatalogSource`.
522
+ """
523
+ count, keys_arr, values_arr = _make_header_arrays(headers)
524
+
525
+ def _extract(block_ptr):
526
+ handle = ModelCatalogSourceHandle()
527
+ WinMLModelCatalogSourceGetResult(block_ptr, ctypes.byref(handle))
528
+ return ModelCatalogSource(handle)
529
+
530
+ return AsyncOperation(
531
+ WinMLModelCatalogSourceCreateFromUriWithHeadersAsync,
532
+ uri.encode("utf-8"), count, keys_arr, values_arr,
533
+ result_fn=_extract,
534
+ on_complete=on_complete,
535
+ on_progress=on_progress,
536
+ )
537
+
538
+ @property
539
+ def id(self) -> str:
540
+ return _get_ep_string(
541
+ self._handle, WinMLModelCatalogSourceGetIdSize, WinMLModelCatalogSourceGetId
542
+ )
543
+
544
+ @property
545
+ def uri(self) -> str:
546
+ return _get_ep_string(
547
+ self._handle, WinMLModelCatalogSourceGetUriSize, WinMLModelCatalogSourceGetUri
548
+ )
549
+
550
+ def close(self) -> None:
551
+ """Release the catalog source handle."""
552
+ if self._handle:
553
+ WinMLModelCatalogSourceRelease(self._handle)
554
+ self._handle = None
555
+
556
+ def __enter__(self) -> "ModelCatalogSource":
557
+ return self
558
+
559
+ def __exit__(self, *args) -> None:
560
+ self.close()
561
+
562
+ def __del__(self) -> None:
563
+ try:
564
+ self.close()
565
+ except Exception:
566
+ pass
567
+
568
+ def __repr__(self) -> str:
569
+ return f"ModelCatalogSource(id={self.id!r})"
570
+
571
+
572
+ # ---------------------------------------------------------------------------
573
+ # ModelCatalog
574
+ # ---------------------------------------------------------------------------
575
+
576
+ class ModelCatalog:
577
+ """Windows ML Model Catalog.
578
+
579
+ Use as a context manager::
580
+
581
+ with ModelCatalogSource.create_from_uri(uri) as source:
582
+ with ModelCatalog([source]) as catalog:
583
+ for model in catalog.find_all_models():
584
+ print(model.name)
585
+ """
586
+
587
+ def __init__(self, sources: list[ModelCatalogSource]) -> None:
588
+ count = len(sources)
589
+ arr = (ModelCatalogSourceHandle * count)(
590
+ *(s._handle for s in sources)
591
+ )
592
+ handle = ModelCatalogHandle()
593
+ WinMLModelCatalogCreate(count, arr, ctypes.byref(handle))
594
+ self._handle = handle
595
+
596
+ @property
597
+ def execution_providers(self) -> list[str]:
598
+ """List the execution providers configured on this catalog."""
599
+ if not self._handle:
600
+ raise RuntimeError("ModelCatalog has been closed")
601
+ return _enum_strings(self._handle, WinMLModelCatalogEnumExecutionProviders)
602
+
603
+ def set_execution_providers(self, providers: list[str]) -> None:
604
+ """Set the execution providers to use for model queries."""
605
+ if not self._handle:
606
+ raise RuntimeError("ModelCatalog has been closed")
607
+ count = len(providers)
608
+ arr = (ctypes.c_char_p * count)(*(p.encode("utf-8") for p in providers))
609
+ WinMLModelCatalogSetExecutionProviders(self._handle, count, arr)
610
+
611
+ def get_available_model(self, id_or_name: str) -> "CatalogModelInfo":
612
+ """Look up a single available model by ID or name."""
613
+ if not self._handle:
614
+ raise RuntimeError("ModelCatalog has been closed")
615
+ handle = CatalogModelInfoHandle()
616
+ WinMLModelCatalogGetAvailableModel(
617
+ self._handle, id_or_name.encode("utf-8"), ctypes.byref(handle)
618
+ )
619
+ return CatalogModelInfo(handle)
620
+
621
+ def get_available_models(self) -> list["CatalogModelInfo"]:
622
+ """Enumerate all available models."""
623
+ if not self._handle:
624
+ raise RuntimeError("ModelCatalog has been closed")
625
+ models: list[CatalogModelInfo] = []
626
+
627
+ @CatalogModelInfoEnumCallback
628
+ def _callback(model_info, context):
629
+ try:
630
+ models.append(CatalogModelInfo(model_info))
631
+ except Exception:
632
+ return False
633
+ return True
634
+
635
+ WinMLModelCatalogGetAvailableModels(self._handle, _callback, None)
636
+ return models
637
+
638
+ def find_model(self, id_or_name: str) -> "CatalogModelInfo":
639
+ """Find a model by ID or name (blocking)."""
640
+ if not self._handle:
641
+ raise RuntimeError("ModelCatalog has been closed")
642
+ handle = CatalogModelInfoHandle()
643
+ WinMLModelCatalogFindModel(
644
+ self._handle, id_or_name.encode("utf-8"), ctypes.byref(handle)
645
+ )
646
+ return CatalogModelInfo(handle)
647
+
648
+ def find_model_async(
649
+ self,
650
+ id_or_name: str,
651
+ on_complete: Optional[Callable[[], None]] = None,
652
+ on_progress: Optional[Callable[[float], None]] = None,
653
+ ) -> "AsyncOperation":
654
+ """Find a model by ID or name (async).
655
+
656
+ Call :meth:`AsyncOperation.get_result` after completion to obtain
657
+ the :class:`CatalogModelInfo`.
658
+ """
659
+ if not self._handle:
660
+ raise RuntimeError("ModelCatalog has been closed")
661
+
662
+ def _extract(block_ptr):
663
+ handle = CatalogModelInfoHandle()
664
+ WinMLModelCatalogFindModelGetResult(block_ptr, ctypes.byref(handle))
665
+ return CatalogModelInfo(handle)
666
+
667
+ return AsyncOperation(
668
+ WinMLModelCatalogFindModelAsync,
669
+ self._handle, id_or_name.encode("utf-8"),
670
+ result_fn=_extract,
671
+ on_complete=on_complete,
672
+ on_progress=on_progress,
673
+ )
674
+
675
+ def find_all_models(self) -> list["CatalogModelInfo"]:
676
+ """Find all models (blocking)."""
677
+ if not self._handle:
678
+ raise RuntimeError("ModelCatalog has been closed")
679
+ models: list[CatalogModelInfo] = []
680
+
681
+ @CatalogModelInfoEnumCallback
682
+ def _callback(model_info, context):
683
+ try:
684
+ models.append(CatalogModelInfo(model_info))
685
+ except Exception:
686
+ return False
687
+ return True
688
+
689
+ WinMLModelCatalogFindAllModels(self._handle, _callback, None)
690
+ return models
691
+
692
+ def find_all_models_async(
693
+ self,
694
+ on_complete: Optional[Callable[[], None]] = None,
695
+ on_progress: Optional[Callable[[float], None]] = None,
696
+ ) -> "AsyncOperation":
697
+ """Find all models (async).
698
+
699
+ Call :meth:`AsyncOperation.get_result` after completion to obtain
700
+ a list of :class:`CatalogModelInfo`.
701
+ """
702
+ if not self._handle:
703
+ raise RuntimeError("ModelCatalog has been closed")
704
+
705
+ def _extract(block_ptr):
706
+ models: list[CatalogModelInfo] = []
707
+
708
+ @CatalogModelInfoEnumCallback
709
+ def _callback(model_info, context):
710
+ try:
711
+ models.append(CatalogModelInfo(model_info))
712
+ except Exception:
713
+ return False
714
+ return True
715
+
716
+ WinMLModelCatalogFindAllModelsGetResult(block_ptr, _callback, None)
717
+ return models
718
+
719
+ return AsyncOperation(
720
+ WinMLModelCatalogFindAllModelsAsync,
721
+ self._handle,
722
+ result_fn=_extract,
723
+ on_complete=on_complete,
724
+ on_progress=on_progress,
725
+ )
726
+
727
+ def close(self) -> None:
728
+ """Release the model catalog."""
729
+ if self._handle:
730
+ WinMLModelCatalogRelease(self._handle)
731
+ self._handle = None
732
+
733
+ def __enter__(self) -> "ModelCatalog":
734
+ return self
735
+
736
+ def __exit__(self, *args) -> None:
737
+ self.close()
738
+
739
+ def __del__(self) -> None:
740
+ try:
741
+ self.close()
742
+ except Exception:
743
+ pass
744
+
745
+
746
+ # ---------------------------------------------------------------------------
747
+ # CatalogModelInfo
748
+ # ---------------------------------------------------------------------------
749
+
750
+ class CatalogModelInfo:
751
+ """Metadata about a model in the catalog.
752
+
753
+ Obtained from :meth:`ModelCatalog.find_model` or enumeration methods.
754
+ """
755
+
756
+ def __init__(self, handle) -> None:
757
+ self._handle = handle
758
+
759
+ @property
760
+ def id(self) -> str:
761
+ return _get_ep_string(
762
+ self._handle, WinMLCatalogModelInfoGetIdSize, WinMLCatalogModelInfoGetId
763
+ )
764
+
765
+ @property
766
+ def name(self) -> str:
767
+ return _get_ep_string(
768
+ self._handle, WinMLCatalogModelInfoGetNameSize, WinMLCatalogModelInfoGetName
769
+ )
770
+
771
+ @property
772
+ def publisher(self) -> str:
773
+ return _get_ep_string(
774
+ self._handle,
775
+ WinMLCatalogModelInfoGetPublisherSize,
776
+ WinMLCatalogModelInfoGetPublisher,
777
+ )
778
+
779
+ @property
780
+ def source_id(self) -> str:
781
+ return _get_ep_string(
782
+ self._handle,
783
+ WinMLCatalogModelInfoGetSourceIdSize,
784
+ WinMLCatalogModelInfoGetSourceId,
785
+ )
786
+
787
+ @property
788
+ def version(self) -> str:
789
+ return _get_ep_string(
790
+ self._handle,
791
+ WinMLCatalogModelInfoGetVersionSize,
792
+ WinMLCatalogModelInfoGetVersion,
793
+ )
794
+
795
+ @property
796
+ def license(self) -> str:
797
+ return _get_ep_string(
798
+ self._handle,
799
+ WinMLCatalogModelInfoGetLicenseSize,
800
+ WinMLCatalogModelInfoGetLicense,
801
+ )
802
+
803
+ @property
804
+ def license_uri(self) -> str:
805
+ return _get_ep_string(
806
+ self._handle,
807
+ WinMLCatalogModelInfoGetLicenseUriSize,
808
+ WinMLCatalogModelInfoGetLicenseUri,
809
+ )
810
+
811
+ @property
812
+ def license_text(self) -> str:
813
+ return _get_ep_string(
814
+ self._handle,
815
+ WinMLCatalogModelInfoGetLicenseTextSize,
816
+ WinMLCatalogModelInfoGetLicenseText,
817
+ )
818
+
819
+ @property
820
+ def uri(self) -> str:
821
+ return _get_ep_string(
822
+ self._handle, WinMLCatalogModelInfoGetUriSize, WinMLCatalogModelInfoGetUri
823
+ )
824
+
825
+ @property
826
+ def model_size_in_bytes(self) -> int:
827
+ size = ctypes.c_uint64()
828
+ WinMLCatalogModelInfoGetModelSizeInBytes(self._handle, ctypes.byref(size))
829
+ return size.value
830
+
831
+ @property
832
+ def execution_providers(self) -> list[str]:
833
+ return _enum_strings(
834
+ self._handle, WinMLCatalogModelInfoEnumExecutionProviders
835
+ )
836
+
837
+ @property
838
+ def status(self) -> CatalogModelStatus:
839
+ val = ctypes.c_int()
840
+ WinMLCatalogModelInfoGetStatus(self._handle, ctypes.byref(val))
841
+ return CatalogModelStatus(val.value)
842
+
843
+ def get_instance_async(
844
+ self,
845
+ on_complete: Optional[Callable[[], None]] = None,
846
+ on_progress: Optional[Callable[[float], None]] = None,
847
+ ) -> "AsyncOperation":
848
+ """Download or prepare a model instance (async).
849
+
850
+ Call :meth:`AsyncOperation.get_result` after completion to obtain
851
+ the :class:`CatalogModelInstance`.
852
+ """
853
+ def _extract(block_ptr):
854
+ result = CatalogModelInstanceResult()
855
+ WinMLCatalogModelInfoGetInstanceResult(block_ptr, ctypes.byref(result))
856
+ if result.extendedError < 0:
857
+ raise OSError(
858
+ f"GetInstance failed: 0x{result.extendedError & 0xFFFFFFFF:08X}"
859
+ )
860
+ return CatalogModelInstance(result.instance)
861
+
862
+ return AsyncOperation(
863
+ WinMLCatalogModelInfoGetInstanceAsync,
864
+ self._handle,
865
+ result_fn=_extract,
866
+ on_complete=on_complete,
867
+ on_progress=on_progress,
868
+ )
869
+
870
+ def get_instance_with_headers_async(
871
+ self,
872
+ headers: dict[str, str],
873
+ on_complete: Optional[Callable[[], None]] = None,
874
+ on_progress: Optional[Callable[[float], None]] = None,
875
+ ) -> "AsyncOperation":
876
+ """Download or prepare a model instance with custom HTTP headers (async).
877
+
878
+ Call :meth:`AsyncOperation.get_result` after completion to obtain
879
+ the :class:`CatalogModelInstance`.
880
+ """
881
+ count, keys_arr, values_arr = _make_header_arrays(headers)
882
+
883
+ def _extract(block_ptr):
884
+ result = CatalogModelInstanceResult()
885
+ WinMLCatalogModelInfoGetInstanceResult(block_ptr, ctypes.byref(result))
886
+ if result.extendedError < 0:
887
+ raise OSError(
888
+ f"GetInstance failed: 0x{result.extendedError & 0xFFFFFFFF:08X}"
889
+ )
890
+ return CatalogModelInstance(result.instance)
891
+
892
+ return AsyncOperation(
893
+ WinMLCatalogModelInfoGetInstanceWithHeadersAsync,
894
+ self._handle, count, keys_arr, values_arr,
895
+ result_fn=_extract,
896
+ on_complete=on_complete,
897
+ on_progress=on_progress,
898
+ )
899
+
900
+ def close(self) -> None:
901
+ """Release the model info handle."""
902
+ if self._handle:
903
+ WinMLCatalogModelInfoRelease(self._handle)
904
+ self._handle = None
905
+
906
+ def __enter__(self) -> "CatalogModelInfo":
907
+ return self
908
+
909
+ def __exit__(self, *args) -> None:
910
+ self.close()
911
+
912
+ def __del__(self) -> None:
913
+ try:
914
+ self.close()
915
+ except Exception:
916
+ pass
917
+
918
+ def __repr__(self) -> str:
919
+ return f"CatalogModelInfo(name={self.name!r})"
920
+
921
+
922
+ # ---------------------------------------------------------------------------
923
+ # CatalogModelInstance
924
+ # ---------------------------------------------------------------------------
925
+
926
+ class CatalogModelInstance:
927
+ """A downloaded or local instance of a catalog model.
928
+
929
+ Obtained from :meth:`CatalogModelInfo.get_instance_async`.
930
+ """
931
+
932
+ def __init__(self, handle) -> None:
933
+ self._handle = handle
934
+
935
+ @property
936
+ def model_paths(self) -> list[str]:
937
+ """List of local file paths for the model files."""
938
+ return _enum_strings(
939
+ self._handle, WinMLCatalogModelInstanceEnumModelPaths
940
+ )
941
+
942
+ @property
943
+ def model_info(self) -> CatalogModelInfo:
944
+ """Get the model info associated with this instance."""
945
+ handle = CatalogModelInfoHandle()
946
+ WinMLCatalogModelInstanceGetModelInfo(self._handle, ctypes.byref(handle))
947
+ return CatalogModelInfo(handle)
948
+
949
+ def close(self) -> None:
950
+ """Close and release the model instance."""
951
+ if self._handle:
952
+ try:
953
+ WinMLCatalogModelInstanceClose(self._handle)
954
+ except OSError:
955
+ pass
956
+ WinMLCatalogModelInstanceRelease(self._handle)
957
+ self._handle = None
958
+
959
+ def __enter__(self) -> "CatalogModelInstance":
960
+ return self
961
+
962
+ def __exit__(self, *args) -> None:
963
+ self.close()
964
+
965
+ def __del__(self) -> None:
966
+ try:
967
+ self.close()
968
+ except Exception:
969
+ pass
970
+
971
+ def __repr__(self) -> str:
972
+ paths = self.model_paths
973
+ return f"CatalogModelInstance(paths={paths!r})"