diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ad08b7ab..f4178cb3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -72,7 +72,7 @@ The internal Azure Quantum Python SDK client (`azure/quantum/_client`) needs to ### Prerequisites - Python 3.10 (or later) -- NodeJS 18.3 LTS (or later) +- NodeJS 22 LTS (or later) ### Setup your repo - Fork and clone the [azure-sdk-for-python](https://github.com/Azure/azure-sdk-for-python) repo (we call it SDK repo and it's absolute path) diff --git a/azure-quantum/azure/quantum/_client/_client.py b/azure-quantum/azure/quantum/_client/_client.py index e56d6c0e..25b87702 100644 --- a/azure-quantum/azure/quantum/_client/_client.py +++ b/azure-quantum/azure/quantum/_client/_client.py @@ -7,8 +7,8 @@ # -------------------------------------------------------------------------- from copy import deepcopy +import sys from typing import Any, TYPE_CHECKING, Union -from typing_extensions import Self from azure.core import PipelineClient from azure.core.credentials import AzureKeyCredential @@ -19,11 +19,16 @@ from ._utils.serialization import Deserializer, Serializer from .operations import ServicesOperations +if sys.version_info >= (3, 11): + from typing import Self +else: + from typing_extensions import Self # type: ignore + if TYPE_CHECKING: from azure.core.credentials import TokenCredential -class WorkspaceClient: +class WorkspaceClient: # pylint: disable=docstring-keyword-should-match-keyword-only """Azure Quantum Workspace Services. :ivar services: ServicesOperations operations @@ -36,8 +41,9 @@ class WorkspaceClient: :type credential: ~azure.core.credentials.TokenCredential or ~azure.core.credentials.AzureKeyCredential :keyword api_version: The API version to use for this operation. Known values are - "2026-01-15-preview" and None. Default value is "2026-01-15-preview". Note that overriding this - default value may result in unsupported behavior. + "2026-01-15-preview" and None. Default value is None. If not set, the operation's default API + version will be used. Note that overriding this default value may result in unsupported + behavior. :paramtype api_version: str """ diff --git a/azure-quantum/azure/quantum/_client/_configuration.py b/azure-quantum/azure/quantum/_client/_configuration.py index 8346a6b9..0fd8ece7 100644 --- a/azure-quantum/azure/quantum/_client/_configuration.py +++ b/azure-quantum/azure/quantum/_client/_configuration.py @@ -1,3 +1,4 @@ +# pylint: disable=line-too-long,useless-suppression # coding=utf-8 # -------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. @@ -17,7 +18,7 @@ from azure.core.credentials import TokenCredential -class WorkspaceClientConfiguration: # pylint: disable=too-many-instance-attributes +class WorkspaceClientConfiguration: # pylint: disable=too-many-instance-attributes,docstring-keyword-should-match-keyword-only """Configuration for WorkspaceClient. Note that all parameters used to create this instance are saved as instance @@ -31,8 +32,9 @@ class WorkspaceClientConfiguration: # pylint: disable=too-many-instance-attribu :type credential: ~azure.core.credentials.TokenCredential or ~azure.core.credentials.AzureKeyCredential :keyword api_version: The API version to use for this operation. Known values are - "2026-01-15-preview" and None. Default value is "2026-01-15-preview". Note that overriding this - default value may result in unsupported behavior. + "2026-01-15-preview" and None. Default value is None. If not set, the operation's default API + version will be used. Note that overriding this default value may result in unsupported + behavior. :paramtype api_version: str """ diff --git a/azure-quantum/azure/quantum/_client/_patch.py b/azure-quantum/azure/quantum/_client/_patch.py index 87676c65..ea765788 100644 --- a/azure-quantum/azure/quantum/_client/_patch.py +++ b/azure-quantum/azure/quantum/_client/_patch.py @@ -8,7 +8,6 @@ Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ - __all__: list[str] = [] # Add all objects you want publicly available to users at this package level diff --git a/azure-quantum/azure/quantum/_client/_utils/model_base.py b/azure-quantum/azure/quantum/_client/_utils/model_base.py index c402af2a..35d5fc02 100644 --- a/azure-quantum/azure/quantum/_client/_utils/model_base.py +++ b/azure-quantum/azure/quantum/_client/_utils/model_base.py @@ -23,14 +23,19 @@ from json import JSONEncoder import xml.etree.ElementTree as ET from collections.abc import MutableMapping -from typing_extensions import Self import isodate from azure.core.exceptions import DeserializationError from azure.core import CaseInsensitiveEnumMeta from azure.core.pipeline import PipelineResponse from azure.core.serialization import _Null + from azure.core.rest import HttpResponse +if sys.version_info >= (3, 11): + from typing import Self +else: + from typing_extensions import Self + _LOGGER = logging.getLogger(__name__) __all__ = ["SdkJSONEncoder", "Model", "rest_field", "rest_discriminator"] @@ -104,6 +109,29 @@ def _serialize_bytes(o, format: typing.Optional[str] = None) -> str: return encoded +def _serialize_duration(td: timedelta, format: typing.Optional[str] = None): + """Serialize a timedelta to its wire representation. + + For the ``seconds``/``milliseconds`` encodings the value is converted to a + numeric value, otherwise it falls back to an ISO 8601 duration string. + + :param timedelta td: The timedelta to serialize. + :param str format: The duration encoding format. + :rtype: int or float or str + :return: serialized duration + """ + seconds = td.total_seconds() + if format == "duration-seconds-int": + return int(seconds) + if format == "duration-seconds-float": + return seconds + if format == "duration-milliseconds-int": + return int(seconds * 1000) + if format == "duration-milliseconds-float": + return seconds * 1000 + return _timedelta_as_isostr(td) + + def _serialize_datetime(o, format: typing.Optional[str] = None): if hasattr(o, "year") and hasattr(o, "hour"): if format == "rfc7231": @@ -130,7 +158,15 @@ def _is_readonly(p): class SdkJSONEncoder(JSONEncoder): - """A JSON encoder that's capable of serializing datetime objects and bytes.""" + """A JSON encoder that's capable of serializing datetime objects and bytes. + + :param args: Additional positional arguments passed to the base ``JSONEncoder``. + :type args: typing.Any + :keyword exclude_readonly: Whether to exclude readonly properties. Defaults to False. + :paramtype exclude_readonly: bool + :keyword format: The format to use for serialization. Defaults to None. + :paramtype format: typing.Optional[str] + """ def __init__(self, *args, exclude_readonly: bool = False, format: typing.Optional[str] = None, **kwargs): super().__init__(*args, **kwargs) @@ -296,6 +332,12 @@ def _deserialize_duration(attr): return isodate.parse_duration(attr) +def _deserialize_duration_numeric(attr, unit): + if isinstance(attr, timedelta): + return attr + return timedelta(**{unit: float(attr)}) + + def _deserialize_decimal(attr): if isinstance(attr, decimal.Decimal): return attr @@ -308,6 +350,12 @@ def _deserialize_int_as_str(attr): return int(attr) +def _deserialize_bool_as_str(attr): + if isinstance(attr, bool): + return attr + return attr.lower() == "true" + + _DESERIALIZE_MAPPING = { datetime: _deserialize_datetime, date: _deserialize_date, @@ -325,12 +373,18 @@ def _deserialize_int_as_str(attr): "unix-timestamp": _deserialize_datetime_unix_timestamp, "base64": _deserialize_bytes, "base64url": _deserialize_bytes_base64, + "duration-seconds-int": functools.partial(_deserialize_duration_numeric, unit="seconds"), + "duration-seconds-float": functools.partial(_deserialize_duration_numeric, unit="seconds"), + "duration-milliseconds-int": functools.partial(_deserialize_duration_numeric, unit="milliseconds"), + "duration-milliseconds-float": functools.partial(_deserialize_duration_numeric, unit="milliseconds"), } def get_deserializer(annotation: typing.Any, rf: typing.Optional["_RestField"] = None): if annotation is int and rf and rf._format == "str": return _deserialize_int_as_str + if annotation is bool and rf and rf._format == "str": + return _deserialize_bool_as_str if annotation is str and rf and rf._format in _ARRAY_ENCODE_MAPPING: return functools.partial(_deserialize_array_encoded, _ARRAY_ENCODE_MAPPING[rf._format]) if rf and rf._format: @@ -420,21 +474,21 @@ def __ne__(self, other: typing.Any) -> bool: def keys(self) -> typing.KeysView[str]: """ - :returns: a set-like object providing a view on D's keys + :returns: a set-like object providing a view on the mapping's keys :rtype: ~typing.KeysView """ return self._data.keys() def values(self) -> typing.ValuesView[typing.Any]: """ - :returns: an object providing a view on D's values + :returns: an object providing a view on the mapping's values :rtype: ~typing.ValuesView """ return self._data.values() def items(self) -> typing.ItemsView[str, typing.Any]: """ - :returns: set-like object providing a view on D's items + :returns: a set-like object providing a view on the mapping's items :rtype: ~typing.ItemsView """ return self._data.items() @@ -444,7 +498,7 @@ def get(self, key: str, default: typing.Any = None) -> typing.Any: Get the value for key if key is in the dictionary, else default. :param str key: The key to look up. :param any default: The value to return if key is not in the dictionary. Defaults to None - :returns: D[k] if k in D, else d. + :returns: The value for key if key is in the dictionary, else default. :rtype: any """ try: @@ -479,19 +533,19 @@ def popitem(self) -> tuple[str, typing.Any]: Removes and returns some (key, value) pair :returns: The (key, value) pair. :rtype: tuple - :raises KeyError: if D is empty. + :raises KeyError: if the dictionary is empty. """ return self._data.popitem() def clear(self) -> None: """ - Remove all items from D. + Remove all items from the dictionary. """ self._data.clear() def update(self, *args: typing.Any, **kwargs: typing.Any) -> None: # pylint: disable=arguments-differ """ - Updates D from mapping/iterable E and F. + Update the dictionary from a mapping or an iterable of key-value pairs. :param any args: Either a mapping object or an iterable of key-value pairs. """ self._data.update(*args, **kwargs) @@ -504,10 +558,11 @@ def setdefault(self, key: str, default: typing.Any) -> typing.Any: ... # pylint def setdefault(self, key: str, default: typing.Any = _UNSET) -> typing.Any: """ - Same as calling D.get(k, d), and setting D[k]=d if k not found + Return the value for key if key is in the dictionary; otherwise set the key to + default and return default. :param str key: The key to look up. :param any default: The value to set if key is not in the dictionary - :returns: D[k] if k in D, else d. + :returns: The value for key if key is in the dictionary, else default. :rtype: any """ if default is _UNSET: @@ -515,6 +570,8 @@ def setdefault(self, key: str, default: typing.Any = _UNSET) -> typing.Any: return self._data.setdefault(key, default) def __eq__(self, other: typing.Any) -> bool: + if isinstance(other, _MyMutableMapping): + return self._data == other._data try: other_model = self.__class__(other) except Exception: @@ -557,7 +614,7 @@ def _serialize(o, format: typing.Optional[str] = None): # pylint: disable=too-m pass # Last, try datetime.timedelta try: - return _timedelta_as_isostr(o) + return _serialize_duration(o, format) except AttributeError: # This will be raised when it hits value.total_seconds in the method above pass @@ -583,6 +640,239 @@ def _create_value(rf: typing.Optional["_RestField"], value: typing.Any) -> typin return _serialize(value, rf._format) +# ============================================================================ +# Fast-path scalar deserializer functions for rest_field(deserializer=...) +# These are referenced from rest_field declarations to bypass the generic +# _deserialize -> _deserialize_with_callable chain. +# Only simple/primitive types — no models or container types. +# ============================================================================ + + +def _xml_deser_str(value): + if isinstance(value, ET.Element): + return value.text or "" + return str(value) if value is not None else None + + +def _xml_deser_int(value): + if isinstance(value, ET.Element): + return int(value.text) if value.text else None + return int(value) if value is not None else None + + +def _xml_deser_float(value): + if isinstance(value, ET.Element): + return float(value.text) if value.text else None + return float(value) if value is not None else None + + +def _xml_deser_bool(value): + if isinstance(value, ET.Element): + text = value.text + else: + text = value + if text is None: + return None + if text in (True, False): + return text + return text.lower() == "true" + + +# pylint: disable=docstring-missing-param +def _xml_deser_bytes(value): + """Deserialize bytes from XML (base64).""" + if isinstance(value, ET.Element): + text = value.text + else: + text = value + if text is None: + return None + return _deserialize_bytes(text) + + +def _xml_deser_bytes_base64url(value): + """Deserialize bytes from XML (base64url).""" + if isinstance(value, ET.Element): + text = value.text + else: + text = value + if text is None: + return None + return _deserialize_bytes_base64(text) + + +def _xml_deser_datetime(value): + """Deserialize a datetime from XML (ISO 8601 / rfc3339).""" + if isinstance(value, ET.Element): + text = value.text + else: + text = value + if text is None: + return None + return _deserialize_datetime(text) + + +def _xml_deser_datetime_rfc7231(value): + """Deserialize a datetime from XML (RFC7231 format).""" + if isinstance(value, ET.Element): + text = value.text + else: + text = value + if text is None: + return None + return _deserialize_datetime_rfc7231(text) + + +def _xml_deser_datetime_unix_timestamp(value): + """Deserialize a datetime from XML (Unix timestamp).""" + if isinstance(value, ET.Element): + text = value.text + else: + text = value + if text is None: + return None + return _deserialize_datetime_unix_timestamp(float(text)) + + +def _xml_deser_date(value): + """Deserialize a date from XML (ISO 8601).""" + if isinstance(value, ET.Element): + text = value.text + else: + text = value + if text is None: + return None + return _deserialize_date(text) + + +def _xml_deser_time(value): + """Deserialize a time from XML (ISO 8601).""" + if isinstance(value, ET.Element): + text = value.text + else: + text = value + if text is None: + return None + return _deserialize_time(text) + + +def _xml_deser_duration(value): + """Deserialize a timedelta from XML (ISO 8601 duration).""" + if isinstance(value, ET.Element): + text = value.text + else: + text = value + if text is None: + return None + return _deserialize_duration(text) + + +def _xml_deser_decimal(value): + """Deserialize a Decimal from XML.""" + if isinstance(value, ET.Element): + text = value.text + else: + text = value + if text is None: + return None + return _deserialize_decimal(text) + + +def _xml_deser_enum_or_str(enum_cls, value): + """Deserialize a Union[EnumType, str] from XML.""" + text = value.text if isinstance(value, ET.Element) else value + if text is None: + return None + try: + return enum_cls(text) + except ValueError: + return text + + +def _extract_xml_model_type(rf_type): + """Extract the concrete Model class from a resolved rf._type partial chain. + + Unwraps ``Optional[Model]`` and ``_deserialize_model(Model, ...)`` + wrappers. Only handles Model and Optional[Model] — other composite + types (List, Dict, Union, etc.) return None and fall through to the + generic ``_deserialize`` path at runtime. + """ + if rf_type is None: + return None + if isinstance(rf_type, type) and _is_model(rf_type): + return rf_type + if not isinstance(rf_type, functools.partial): + return None + func = rf_type.func + args = rf_type.args + if func is _deserialize_with_optional and args: + return _extract_xml_model_type(args[0]) + if func is _deserialize_model and args: + cls = args[0] + return cls if isinstance(cls, type) and _is_model(cls) else None + return None + + +def _build_xml_field_plan( # pylint: disable=docstring-missing-return, docstring-missing-rtype, unused-variable + cls, attr_to_rest_field: dict +) -> list: + """Build a precomputed XML field plan for fast _init_from_xml iteration. + + Called once per model class in __new__. Returns a list of tuples: + (rest_name, xml_name, kind, deser, rf_type, is_optional, items_name) + + kind: 0=wrapped, 1=attribute, 2=unwrapped, 3=text + + For Model and Optional[Model] fields that lack a scalar + ``_deserializer``, this function precomputes the Model class as the + deserializer so ``_init_from_xml`` can call ``ModelClass(element)`` + directly instead of going through the expensive + ``_get_deserialize_callable_from_annotation`` chain at runtime. + """ + model_meta = getattr(cls, "_xml", {}) + model_ns = model_meta.get("ns") or model_meta.get("namespace") + plan = [] + + for rf in attr_to_rest_field.values(): + prop_meta = getattr(rf, "_xml", {}) + deser = rf._deserializer + + xml_name = prop_meta.get("name", rf._rest_name) + xml_ns = _resolve_xml_ns(prop_meta, model_meta) + if xml_ns: + xml_name = "{" + xml_ns + "}" + xml_name + + is_optional = rf._is_optional + + # For Model / Optional[Model] fields without a scalar deserializer, + # precompute the Model class as the deserializer. + if deser is None and rf._type is not None: + model_cls = _extract_xml_model_type(rf._type) + if model_cls is not None: + deser = model_cls + + if prop_meta.get("attribute", False): + plan.append((rf._rest_name, xml_name, 1, deser, rf._type, is_optional, None)) + elif prop_meta.get("unwrapped", False): + items_name = prop_meta.get("itemsName") + if items_name: + items_ns = prop_meta.get("itemsNs") + if items_ns is not None: + xml_ns = items_ns + if xml_ns: + items_name = "{" + xml_ns + "}" + items_name + else: + items_name = xml_name + plan.append((rf._rest_name, xml_name, 2, deser, rf._type, is_optional, items_name)) + elif prop_meta.get("text", False): + plan.append((rf._rest_name, xml_name, 3, deser, rf._type, is_optional, None)) + else: + plan.append((rf._rest_name, xml_name, 0, deser, rf._type, is_optional, None)) + + return plan + + +# pylint: enable=docstring-missing-param class Model(_MyMutableMapping): _is_model = True # label whether current class's _attr_to_rest_field has been calculated @@ -593,59 +883,10 @@ def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None: class_name = self.__class__.__name__ if len(args) > 1: raise TypeError(f"{class_name}.__init__() takes 2 positional arguments but {len(args) + 1} were given") - dict_to_pass = { - rest_field._rest_name: rest_field._default - for rest_field in self._attr_to_rest_field.values() - if rest_field._default is not _UNSET - } - if args: # pylint: disable=too-many-nested-blocks + dict_to_pass: dict[str, typing.Any] = {} + if args: if isinstance(args[0], ET.Element): - existed_attr_keys = [] - model_meta = getattr(self, "_xml", {}) - - for rf in self._attr_to_rest_field.values(): - prop_meta = getattr(rf, "_xml", {}) - xml_name = prop_meta.get("name", rf._rest_name) - xml_ns = prop_meta.get("ns", model_meta.get("ns", None)) - if xml_ns: - xml_name = "{" + xml_ns + "}" + xml_name - - # attribute - if prop_meta.get("attribute", False) and args[0].get(xml_name) is not None: - existed_attr_keys.append(xml_name) - dict_to_pass[rf._rest_name] = _deserialize(rf._type, args[0].get(xml_name)) - continue - - # unwrapped element is array - if prop_meta.get("unwrapped", False): - # unwrapped array could either use prop items meta/prop meta - if prop_meta.get("itemsName"): - xml_name = prop_meta.get("itemsName") - xml_ns = prop_meta.get("itemNs") - if xml_ns: - xml_name = "{" + xml_ns + "}" + xml_name - items = args[0].findall(xml_name) # pyright: ignore - if len(items) > 0: - existed_attr_keys.append(xml_name) - dict_to_pass[rf._rest_name] = _deserialize(rf._type, items) - continue - - # text element is primitive type - if prop_meta.get("text", False): - if args[0].text is not None: - dict_to_pass[rf._rest_name] = _deserialize(rf._type, args[0].text) - continue - - # wrapped element could be normal property or array, it should only have one element - item = args[0].find(xml_name) - if item is not None: - existed_attr_keys.append(xml_name) - dict_to_pass[rf._rest_name] = _deserialize(rf._type, item) - - # rest thing is additional properties - for e in args[0]: - if e.tag not in existed_attr_keys: - dict_to_pass[e.tag] = _convert_element(e) + dict_to_pass.update(self._init_from_xml(args[0])) else: dict_to_pass.update( {k: _create_value(_get_rest_field(self._attr_to_rest_field, k), v) for k, v in args[0].items()} @@ -662,8 +903,117 @@ def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None: if v is not None } ) + # Apply client default values for fields the caller didn't set so that + # defaults are part of `_data` and therefore included during serialization. + for rf in self._attr_to_rest_field.values(): + if rf._default is _UNSET: + continue + if rf._rest_name in dict_to_pass: + continue + dict_to_pass[rf._rest_name] = _create_value(rf, rf._default) super().__init__(dict_to_pass) + def _init_from_xml( # pylint: disable=too-many-branches, too-many-statements + self, element: ET.Element + ) -> dict[str, typing.Any]: + """Deserialize an XML element into a dict mapping rest field names to values. + + :param ET.Element element: The XML element to deserialize from. + :returns: A dictionary of rest_name to deserialized value pairs. + :rtype: dict + """ + result: dict[str, typing.Any] = {} + existed_attr_keys: list[str] = [] + + field_plan = getattr(self, "_xml_field_plan", None) + if field_plan: + for rest_name, xml_name, kind, deser, rf_type, is_optional, items_name in field_plan: + if kind == 0: # wrapped element (most common) + item = element.find(xml_name) + if item is not None: + existed_attr_keys.append(xml_name) + if deser: + result[rest_name] = deser(item) + else: + result[rest_name] = _deserialize(rf_type, item) + elif kind == 1: # attribute + attr_val = element.get(xml_name) + if attr_val is not None: + existed_attr_keys.append(xml_name) + if deser: + result[rest_name] = deser(attr_val) + else: + result[rest_name] = attr_val + elif kind == 2: # unwrapped array + items = element.findall(items_name) # pyright: ignore + if len(items) > 0: + existed_attr_keys.append(items_name) + if deser: + result[rest_name] = deser(items) + else: + result[rest_name] = _deserialize(rf_type, items) + elif not is_optional: + existed_attr_keys.append(items_name) + result[rest_name] = [] + elif kind == 3: # text + if element.text is not None: + if deser: + result[rest_name] = deser(element.text) + else: + result[rest_name] = element.text + else: + model_meta = getattr(self, "_xml", {}) + for rf in self._attr_to_rest_field.values(): + prop_meta = getattr(rf, "_xml", {}) + xml_name = prop_meta.get("name", rf._rest_name) + xml_ns = _resolve_xml_ns(prop_meta, model_meta) + if xml_ns: + xml_name = "{" + xml_ns + "}" + xml_name + + # attribute + if prop_meta.get("attribute", False) and element.get(xml_name) is not None: + existed_attr_keys.append(xml_name) + result[rf._rest_name] = _deserialize(rf._type, element.get(xml_name)) + continue + + # unwrapped element is array + if prop_meta.get("unwrapped", False): + _items_name = prop_meta.get("itemsName") + if _items_name: + xml_name = _items_name + _items_ns = prop_meta.get("itemsNs") + if _items_ns is not None: + xml_ns = _items_ns + if xml_ns: + xml_name = "{" + xml_ns + "}" + xml_name + items = element.findall(xml_name) # pyright: ignore + if len(items) > 0: + existed_attr_keys.append(xml_name) + result[rf._rest_name] = _deserialize(rf._type, items) + elif not rf._is_optional: + existed_attr_keys.append(xml_name) + result[rf._rest_name] = [] + continue + + # text element is primitive type + if prop_meta.get("text", False): + if element.text is not None: + result[rf._rest_name] = _deserialize(rf._type, element.text) + continue + + # wrapped element could be normal property or array + item = element.find(xml_name) + if item is not None: + existed_attr_keys.append(xml_name) + result[rf._rest_name] = _deserialize(rf._type, item) + + # rest thing is additional properties + for e in element: + if e.tag not in existed_attr_keys: + result[e.tag] = _convert_element(e) + + return result + def copy(self) -> "Model": return Model(self.__dict__) @@ -688,6 +1038,9 @@ def __new__(cls, *args: typing.Any, **kwargs: typing.Any) -> Self: if not rf._rest_name_input: rf._rest_name_input = attr cls._attr_to_rest_field: dict[str, _RestField] = dict(attr_to_rest_field.items()) + # Build XML field plan for fast _init_from_xml (only for XML models) + if getattr(cls, "_xml", None): + cls._xml_field_plan = _build_xml_field_plan(cls, attr_to_rest_field) cls._calculated.add(f"{cls.__module__}.{cls.__qualname__}") return super().__new__(cls) @@ -716,7 +1069,7 @@ def _deserialize(cls, data, exist_discriminators): model_meta = getattr(cls, "_xml", {}) prop_meta = getattr(discriminator, "_xml", {}) xml_name = prop_meta.get("name", discriminator._rest_name) - xml_ns = prop_meta.get("ns", model_meta.get("ns", None)) + xml_ns = _resolve_xml_ns(prop_meta, model_meta) if xml_ns: xml_name = "{" + xml_ns + "}" + xml_name @@ -889,6 +1242,8 @@ def _get_deserialize_callable_from_annotation( # pylint: disable=too-many-retur # is it optional? try: if any(a is _NONE_TYPE for a in annotation.__args__): # pyright: ignore + if rf: + rf._is_optional = True if len(annotation.__args__) <= 2: # pyright: ignore if_obj_deserializer = _get_deserialize_callable_from_annotation( next(a for a in annotation.__args__ if a is not _NONE_TYPE), module, rf # pyright: ignore @@ -981,16 +1336,20 @@ def _deserialize_with_callable( return float(value.text) if value.text else None if deserializer is bool: return value.text == "true" if value.text else None + if deserializer and deserializer in _DESERIALIZE_MAPPING.values(): + return deserializer(value.text) if value.text else None + if deserializer and deserializer in _DESERIALIZE_MAPPING_WITHFORMAT.values(): + return deserializer(value.text) if value.text else None if deserializer is None: return value if deserializer in [int, float, bool]: return deserializer(value) if isinstance(deserializer, CaseInsensitiveEnumMeta): try: - return deserializer(value) + return deserializer(value.text if isinstance(value, ET.Element) else value) except ValueError: # for unknown value, return raw value - return value + return value.text if isinstance(value, ET.Element) else value if isinstance(deserializer, type) and issubclass(deserializer, Model): return deserializer._deserialize(value, []) return typing.cast(typing.Callable[[typing.Any], typing.Any], deserializer)(value) @@ -1043,6 +1402,7 @@ def _failsafe_deserialize_xml( return None +# pylint: disable=too-many-instance-attributes class _RestField: def __init__( self, @@ -1055,6 +1415,7 @@ def __init__( format: typing.Optional[str] = None, is_multipart_file_input: bool = False, xml: typing.Optional[dict[str, typing.Any]] = None, + deserializer: typing.Optional[typing.Callable] = None, ): self._type = type self._rest_name_input = name @@ -1062,10 +1423,12 @@ def __init__( self._is_discriminator = is_discriminator self._visibility = visibility self._is_model = False + self._is_optional = False self._default = default self._format = format self._is_multipart_file_input = is_multipart_file_input self._xml = xml if xml is not None else {} + self._deserializer = deserializer @property def _class_type(self) -> typing.Any: @@ -1085,7 +1448,10 @@ def __get__(self, obj: Model, type=None): # pylint: disable=redefined-builtin # by this point, type and rest_name will have a value bc we default # them in __new__ of the Model class # Use _data.get() directly to avoid triggering __getitem__ which clears the cache - item = obj._data.get(self._rest_name) + item = obj._data.get(self._rest_name, _UNSET) + if item is _UNSET: + # Field not set by user; return the client default if one exists, otherwise None + return self._default if self._default is not _UNSET else None if item is None: return item if self._is_model: @@ -1098,7 +1464,11 @@ def __get__(self, obj: Model, type=None): # pylint: disable=redefined-builtin # Return the value from _data directly (it's been deserialized in place) return obj._data.get(self._rest_name) - deserialized = _deserialize(self._type, _serialize(item, self._format), rf=self) + # Fast path: use _deserializer directly (avoids _serialize/_deserialize chain) + if self._deserializer: + deserialized = self._deserializer(item) + else: + deserialized = _deserialize(self._type, _serialize(item, self._format), rf=self) # For mutable types, store the deserialized value back in _data # so mutations directly affect _data @@ -1144,6 +1514,7 @@ def rest_field( format: typing.Optional[str] = None, is_multipart_file_input: bool = False, xml: typing.Optional[dict[str, typing.Any]] = None, + deserializer: typing.Optional[typing.Callable] = None, ) -> typing.Any: return _RestField( name=name, @@ -1153,6 +1524,7 @@ def rest_field( format=format, is_multipart_file_input=is_multipart_file_input, xml=xml, + deserializer=deserializer, ) @@ -1177,6 +1549,56 @@ def serialize_xml(model: Model, exclude_readonly: bool = False) -> str: return ET.tostring(_get_element(model, exclude_readonly), encoding="unicode") # type: ignore +def _get_xml_ns(meta: dict[str, typing.Any]) -> typing.Optional[str]: + """Return the XML namespace from a metadata dict, checking both 'ns' (old-style) and 'namespace' (DPG) keys. + + :param dict meta: The metadata dictionary to extract namespace from. + :returns: The namespace string if 'ns' or 'namespace' key is present, None otherwise. + :rtype: str or None + """ + ns = meta.get("ns") + if ns is None: + ns = meta.get("namespace") + return ns + + +def _resolve_xml_ns( + prop_meta: dict[str, typing.Any], model_meta: typing.Optional[dict[str, typing.Any]] = None +) -> typing.Optional[str]: + """Resolve XML namespace for a property, falling back to model namespace when appropriate. + + Checks the property metadata first; if no namespace is found and the model does not declare + an explicit prefix, falls back to the model-level namespace. + + :param dict prop_meta: The property metadata dictionary. + :param dict model_meta: The model metadata dictionary, used as fallback. + :returns: The resolved namespace string, or None. + :rtype: str or None + """ + ns = _get_xml_ns(prop_meta) + if ns is None and model_meta is not None and not model_meta.get("prefix"): + ns = _get_xml_ns(model_meta) + return ns + + +def _set_xml_attribute(element: ET.Element, name: str, value: typing.Any, prop_meta: dict[str, typing.Any]) -> None: + """Set an XML attribute on an element, handling namespace prefix registration. + + :param ET.Element element: The element to set the attribute on. + :param str name: The default attribute name (wire name). + :param any value: The attribute value. + :param dict prop_meta: The property metadata dictionary. + """ + xml_name = prop_meta.get("name", name) + _attr_ns = _get_xml_ns(prop_meta) + if _attr_ns: + _attr_prefix = prop_meta.get("prefix") + if _attr_prefix: + _safe_register_namespace(_attr_prefix, _attr_ns) + xml_name = "{" + _attr_ns + "}" + xml_name + element.set(xml_name, _get_primitive_type_value(value)) + + def _get_element( o: typing.Any, exclude_readonly: bool = False, @@ -1188,10 +1610,16 @@ def _get_element( # if prop is a model, then use the prop element directly, else generate a wrapper of model if wrapped_element is None: + # When serializing as an array item (parent_meta is set), check if the parent has an + # explicit itemsName. This ensures correct element names for unwrapped arrays (where + # the element tag is the property/items name, not the model type name). + _items_name = parent_meta.get("itemsName") if parent_meta is not None else None + element_name = _items_name if _items_name else (model_meta.get("name") or o.__class__.__name__) + _model_ns = _get_xml_ns(model_meta) wrapped_element = _create_xml_element( - model_meta.get("name", o.__class__.__name__), + element_name, model_meta.get("prefix"), - model_meta.get("ns"), + _model_ns, ) readonly_props = [] @@ -1213,7 +1641,9 @@ def _get_element( # additional properties will not have rest field, use the wire name as xml name prop_meta = {"name": k} - # if no ns for prop, use model's + # Propagate model namespace to properties only for old-style "ns"-keyed models. + # DPG-generated models use the "namespace" key and explicitly declare namespace on + # each property that needs it, so propagation is intentionally skipped for them. if prop_meta.get("ns") is None and model_meta.get("ns"): prop_meta["ns"] = model_meta.get("ns") prop_meta["prefix"] = model_meta.get("prefix") @@ -1225,12 +1655,7 @@ def _get_element( # text could only set on primitive type wrapped_element.text = _get_primitive_type_value(v) elif prop_meta.get("attribute", False): - xml_name = prop_meta.get("name", k) - if prop_meta.get("ns"): - ET.register_namespace(prop_meta.get("prefix"), prop_meta.get("ns")) # pyright: ignore - xml_name = "{" + prop_meta.get("ns") + "}" + xml_name # pyright: ignore - # attribute should be primitive type - wrapped_element.set(xml_name, _get_primitive_type_value(v)) + _set_xml_attribute(wrapped_element, k, v, prop_meta) else: # other wrapped prop element wrapped_element.append(_get_wrapped_element(v, exclude_readonly, prop_meta)) @@ -1239,6 +1664,7 @@ def _get_element( return [_get_element(x, exclude_readonly, parent_meta) for x in o] # type: ignore if isinstance(o, dict): result = [] + _dict_ns = _get_xml_ns(parent_meta) if parent_meta else None for k, v in o.items(): result.append( _get_wrapped_element( @@ -1246,7 +1672,7 @@ def _get_element( exclude_readonly, { "name": k, - "ns": parent_meta.get("ns") if parent_meta else None, + "ns": _dict_ns, "prefix": parent_meta.get("prefix") if parent_meta else None, }, ) @@ -1255,13 +1681,16 @@ def _get_element( # primitive case need to create element based on parent_meta if parent_meta: + _items_ns = parent_meta.get("itemsNs") + if _items_ns is None: + _items_ns = _get_xml_ns(parent_meta) return _get_wrapped_element( o, exclude_readonly, { "name": parent_meta.get("itemsName", parent_meta.get("name")), "prefix": parent_meta.get("itemsPrefix", parent_meta.get("prefix")), - "ns": parent_meta.get("itemsNs", parent_meta.get("ns")), + "ns": _items_ns, }, ) @@ -1273,8 +1702,9 @@ def _get_wrapped_element( exclude_readonly: bool, meta: typing.Optional[dict[str, typing.Any]], ) -> ET.Element: + _meta_ns = _get_xml_ns(meta) if meta else None wrapped_element = _create_xml_element( - meta.get("name") if meta else None, meta.get("prefix") if meta else None, meta.get("ns") if meta else None + meta.get("name") if meta else None, meta.get("prefix") if meta else None, _meta_ns ) if isinstance(v, (dict, list)): wrapped_element.extend(_get_element(v, exclude_readonly, meta)) @@ -1295,11 +1725,29 @@ def _get_primitive_type_value(v) -> str: return str(v) +def _safe_register_namespace(prefix: str, ns: str) -> None: + """Register an XML namespace prefix, handling reserved prefix patterns. + + Some prefixes (e.g. 'ns2') match Python's reserved 'ns\\d+' pattern used for + auto-generated prefixes, causing register_namespace to raise ValueError. + Falls back to directly registering in the internal namespace map. + + :param str prefix: The namespace prefix to register. + :param str ns: The namespace URI. + """ + try: + ET.register_namespace(prefix, ns) + except ValueError: + _ns_map = getattr(ET, "_namespace_map", None) + if _ns_map is not None: + _ns_map[ns] = prefix + + def _create_xml_element( tag: typing.Any, prefix: typing.Optional[str] = None, ns: typing.Optional[str] = None ) -> ET.Element: if prefix and ns: - ET.register_namespace(prefix, ns) + _safe_register_namespace(prefix, ns) if ns: return ET.Element("{" + ns + "}" + tag) return ET.Element(tag) @@ -1310,6 +1758,8 @@ def _deserialize_xml( value: str, ) -> typing.Any: element = ET.fromstring(value) # nosec + if _is_model(deserializer): + return deserializer._deserialize(element, []) return _deserialize(deserializer, element) diff --git a/azure-quantum/azure/quantum/_client/_utils/serialization.py b/azure-quantum/azure/quantum/_client/_utils/serialization.py index 81ec1de5..ae08f9d8 100644 --- a/azure-quantum/azure/quantum/_client/_utils/serialization.py +++ b/azure-quantum/azure/quantum/_client/_utils/serialization.py @@ -39,11 +39,15 @@ import xml.etree.ElementTree as ET import isodate # type: ignore -from typing_extensions import Self from azure.core.exceptions import DeserializationError, SerializationError from azure.core.serialization import NULL as CoreNull +if sys.version_info >= (3, 11): + from typing import Self +else: + from typing_extensions import Self + _BOM = codecs.BOM_UTF8.decode(encoding="utf-8") JSON = MutableMapping[str, Any] @@ -476,7 +480,11 @@ def _decode_attribute_map_key(key): class Serializer: # pylint: disable=too-many-public-methods - """Request object model serializer.""" + """Request object model serializer. + + :param classes: Mapping of model names to model types, used to resolve models during serialization. + :type classes: typing.Optional[typing.Mapping[str, type]] + """ basic_types = {str: "str", int: "int", bool: "bool", float: "float"} @@ -516,6 +524,10 @@ def __init__(self, classes: Optional[Mapping[str, type]] = None) -> None: "rfc-1123": Serializer.serialize_rfc, "unix-time": Serializer.serialize_unix, "duration": Serializer.serialize_duration, + "duration-seconds-int": Serializer.serialize_duration_seconds_int, + "duration-seconds-float": Serializer.serialize_duration_seconds_float, + "duration-milliseconds-int": Serializer.serialize_duration_milliseconds_int, + "duration-milliseconds-float": Serializer.serialize_duration_milliseconds_float, "date": Serializer.serialize_date, "time": Serializer.serialize_time, "decimal": Serializer.serialize_decimal, @@ -1105,6 +1117,61 @@ def serialize_duration(attr, **kwargs): # pylint: disable=unused-argument attr = isodate.parse_duration(attr) return isodate.duration_isoformat(attr) + @staticmethod + def _serialize_duration_numeric(attr, scale, as_int): + """Serialize a TimeDelta into a numeric value scaled to the wire unit. + + :param TimeDelta attr: Object to be serialized. + :param int scale: Multiplier applied to total seconds (1 for seconds, 1000 for milliseconds). + :param bool as_int: Whether to truncate the result to an int. + :rtype: int or float + :return: serialized duration + """ + if isinstance(attr, str): + attr = isodate.parse_duration(attr) + value = attr.total_seconds() * scale if isinstance(attr, datetime.timedelta) else attr + return int(value) if as_int else float(value) + + @staticmethod + def serialize_duration_seconds_int(attr, **kwargs): # pylint: disable=unused-argument + """Serialize TimeDelta object into an integer number of seconds. + + :param TimeDelta attr: Object to be serialized. + :rtype: int + :return: serialized duration + """ + return Serializer._serialize_duration_numeric(attr, 1, True) + + @staticmethod + def serialize_duration_seconds_float(attr, **kwargs): # pylint: disable=unused-argument + """Serialize TimeDelta object into a floating point number of seconds. + + :param TimeDelta attr: Object to be serialized. + :rtype: float + :return: serialized duration + """ + return Serializer._serialize_duration_numeric(attr, 1, False) + + @staticmethod + def serialize_duration_milliseconds_int(attr, **kwargs): # pylint: disable=unused-argument + """Serialize TimeDelta object into an integer number of milliseconds. + + :param TimeDelta attr: Object to be serialized. + :rtype: int + :return: serialized duration + """ + return Serializer._serialize_duration_numeric(attr, 1000, True) + + @staticmethod + def serialize_duration_milliseconds_float(attr, **kwargs): # pylint: disable=unused-argument + """Serialize TimeDelta object into a floating point number of milliseconds. + + :param TimeDelta attr: Object to be serialized. + :rtype: float + :return: serialized duration + """ + return Serializer._serialize_duration_numeric(attr, 1000, False) + @staticmethod def serialize_rfc(attr, **kwargs): # pylint: disable=unused-argument """Serialize Datetime object into RFC-1123 formatted string. @@ -1377,6 +1444,10 @@ def __init__(self, classes: Optional[Mapping[str, type]] = None) -> None: "rfc-1123": Deserializer.deserialize_rfc, "unix-time": Deserializer.deserialize_unix, "duration": Deserializer.deserialize_duration, + "duration-seconds-int": Deserializer.deserialize_duration_seconds, + "duration-seconds-float": Deserializer.deserialize_duration_seconds, + "duration-milliseconds-int": Deserializer.deserialize_duration_milliseconds, + "duration-milliseconds-float": Deserializer.deserialize_duration_milliseconds, "date": Deserializer.deserialize_date, "time": Deserializer.deserialize_time, "decimal": Deserializer.deserialize_decimal, @@ -1389,6 +1460,10 @@ def __init__(self, classes: Optional[Mapping[str, type]] = None) -> None: } self.deserialize_expected_types = { "duration": (isodate.Duration, datetime.timedelta), + "duration-seconds-int": (isodate.Duration, datetime.timedelta), + "duration-seconds-float": (isodate.Duration, datetime.timedelta), + "duration-milliseconds-int": (isodate.Duration, datetime.timedelta), + "duration-milliseconds-float": (isodate.Duration, datetime.timedelta), "iso-8601": (datetime.datetime), } self.dependencies: dict[str, type] = dict(classes) if classes else {} @@ -1401,7 +1476,7 @@ def __init__(self, classes: Optional[Mapping[str, type]] = None) -> None: # Otherwise, result are unexpected self.additional_properties_detection = True - def __call__(self, target_obj, response_data, content_type=None): + def __call__(self, target_obj, response_data, content_type=None): # pylint: disable=too-many-return-statements """Call the deserializer to process a REST response. :param str target_obj: Target data type to deserialize to. @@ -1411,6 +1486,27 @@ def __call__(self, target_obj, response_data, content_type=None): :return: Deserialized object. :rtype: object """ + # Fast path for header deserialization: response_data is a plain str or None + # and target_obj is a simple scalar type. This avoids the expensive + # _unpack_content → _deserialize → _classify_target → deserialize_data chain. + if response_data is None: + return None + if target_obj == "str" and isinstance(response_data, str): + return response_data + if isinstance(response_data, str): + if target_obj == "int": + return int(response_data) + if target_obj == "bool": + if response_data in ("true", "1", "True"): + return True + if response_data in ("false", "0", "False"): + return False + return bool(response_data) + if target_obj == "rfc-1123": + return Deserializer.deserialize_rfc(response_data) + if target_obj == "bytearray": + return Deserializer.deserialize_bytearray(response_data) + data = self._unpack_content(response_data, content_type) return self._deserialize(target_obj, data) @@ -1929,6 +2025,48 @@ def deserialize_duration(attr): raise DeserializationError(msg) from err return duration + @staticmethod + def _deserialize_duration_numeric(attr, unit): + """Deserialize a numeric duration value into a TimeDelta object. + + :param float attr: response value to be deserialized. + :param str unit: The wire unit, used as the ``timedelta`` keyword + (``"seconds"`` or ``"milliseconds"``). + :return: Deserialized duration + :rtype: TimeDelta + :raises DeserializationError: if value is invalid. + """ + if isinstance(attr, ET.Element): + attr = attr.text + try: + duration = datetime.timedelta(**{unit: float(attr)}) # type: ignore + except (ValueError, OverflowError, TypeError) as err: + msg = "Cannot deserialize duration object." + raise DeserializationError(msg) from err + return duration + + @staticmethod + def deserialize_duration_seconds(attr): + """Deserialize a numeric number of seconds into a TimeDelta object. + + :param float attr: response value to be deserialized. + :return: Deserialized duration + :rtype: TimeDelta + :raises DeserializationError: if value is invalid. + """ + return Deserializer._deserialize_duration_numeric(attr, "seconds") + + @staticmethod + def deserialize_duration_milliseconds(attr): + """Deserialize a numeric number of milliseconds into a TimeDelta object. + + :param float attr: response value to be deserialized. + :return: Deserialized duration + :rtype: TimeDelta + :raises DeserializationError: if value is invalid. + """ + return Deserializer._deserialize_duration_numeric(attr, "milliseconds") + @staticmethod def deserialize_date(attr): """Deserialize ISO-8601 formatted string into Date object. diff --git a/azure-quantum/azure/quantum/_client/aio/_client.py b/azure-quantum/azure/quantum/_client/aio/_client.py index 21f48db1..32b01953 100644 --- a/azure-quantum/azure/quantum/_client/aio/_client.py +++ b/azure-quantum/azure/quantum/_client/aio/_client.py @@ -7,8 +7,8 @@ # -------------------------------------------------------------------------- from copy import deepcopy +import sys from typing import Any, Awaitable, TYPE_CHECKING, Union -from typing_extensions import Self from azure.core import AsyncPipelineClient from azure.core.credentials import AzureKeyCredential @@ -19,11 +19,16 @@ from ._configuration import WorkspaceClientConfiguration from .operations import ServicesOperations +if sys.version_info >= (3, 11): + from typing import Self +else: + from typing_extensions import Self # type: ignore + if TYPE_CHECKING: from azure.core.credentials_async import AsyncTokenCredential -class WorkspaceClient: +class WorkspaceClient: # pylint: disable=docstring-keyword-should-match-keyword-only """Azure Quantum Workspace Services. :ivar services: ServicesOperations operations @@ -36,8 +41,9 @@ class WorkspaceClient: :type credential: ~azure.core.credentials_async.AsyncTokenCredential or ~azure.core.credentials.AzureKeyCredential :keyword api_version: The API version to use for this operation. Known values are - "2026-01-15-preview" and None. Default value is "2026-01-15-preview". Note that overriding this - default value may result in unsupported behavior. + "2026-01-15-preview" and None. Default value is None. If not set, the operation's default API + version will be used. Note that overriding this default value may result in unsupported + behavior. :paramtype api_version: str """ diff --git a/azure-quantum/azure/quantum/_client/aio/_configuration.py b/azure-quantum/azure/quantum/_client/aio/_configuration.py index 5d1aff54..5109ff7e 100644 --- a/azure-quantum/azure/quantum/_client/aio/_configuration.py +++ b/azure-quantum/azure/quantum/_client/aio/_configuration.py @@ -1,3 +1,4 @@ +# pylint: disable=line-too-long,useless-suppression # coding=utf-8 # -------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. @@ -17,7 +18,7 @@ from azure.core.credentials_async import AsyncTokenCredential -class WorkspaceClientConfiguration: # pylint: disable=too-many-instance-attributes +class WorkspaceClientConfiguration: # pylint: disable=too-many-instance-attributes,docstring-keyword-should-match-keyword-only """Configuration for WorkspaceClient. Note that all parameters used to create this instance are saved as instance @@ -31,8 +32,9 @@ class WorkspaceClientConfiguration: # pylint: disable=too-many-instance-attribu :type credential: ~azure.core.credentials_async.AsyncTokenCredential or ~azure.core.credentials.AzureKeyCredential :keyword api_version: The API version to use for this operation. Known values are - "2026-01-15-preview" and None. Default value is "2026-01-15-preview". Note that overriding this - default value may result in unsupported behavior. + "2026-01-15-preview" and None. Default value is None. If not set, the operation's default API + version will be used. Note that overriding this default value may result in unsupported + behavior. :paramtype api_version: str """ diff --git a/azure-quantum/azure/quantum/_client/aio/_patch.py b/azure-quantum/azure/quantum/_client/aio/_patch.py index 87676c65..ea765788 100644 --- a/azure-quantum/azure/quantum/_client/aio/_patch.py +++ b/azure-quantum/azure/quantum/_client/aio/_patch.py @@ -8,7 +8,6 @@ Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ - __all__: list[str] = [] # Add all objects you want publicly available to users at this package level diff --git a/azure-quantum/azure/quantum/_client/aio/operations/_operations.py b/azure-quantum/azure/quantum/_client/aio/operations/_operations.py index 64077acd..493adab1 100644 --- a/azure-quantum/azure/quantum/_client/aio/operations/_operations.py +++ b/azure-quantum/azure/quantum/_client/aio/operations/_operations.py @@ -30,7 +30,7 @@ from azure.core.tracing.decorator_async import distributed_trace_async from azure.core.utils import case_insensitive_dict -from ... import models as _models +from ... import models as _models, types as _types from ..._utils.model_base import SdkJSONEncoder, _deserialize from ..._utils.serialization import Deserializer, Serializer from ..._validation import api_version_validation @@ -43,22 +43,24 @@ build_services_jobs_update_request, build_services_providers_list_request, build_services_quotas_list_request, + build_services_quotas_list_workspace_usages_request, build_services_sessions_close_request, build_services_sessions_get_request, build_services_sessions_jobs_list_request, build_services_sessions_listv2_request, build_services_sessions_open_request, build_services_storage_get_sas_uri_request, + build_services_suite_offers_get_provider_status_request, + build_services_suite_offers_list_quota_usages_request, build_services_top_level_items_listv2_request, ) from .._configuration import WorkspaceClientConfiguration T = TypeVar("T") ClsType = Optional[Callable[[PipelineResponse[HttpRequest, AsyncHttpResponse], T, dict[str, Any]], Any]] -JSON = MutableMapping[str, Any] -class ServicesOperations: +class ServicesOperations: # pylint: disable=docstring-missing-param,too-many-instance-attributes """ .. warning:: **DO NOT** instantiate this class directly. @@ -83,9 +85,12 @@ def __init__(self, *args, **kwargs) -> None: self.quotas = ServicesQuotasOperations(self._client, self._config, self._serialize, self._deserialize) self.sessions = ServicesSessionsOperations(self._client, self._config, self._serialize, self._deserialize) self.storage = ServicesStorageOperations(self._client, self._config, self._serialize, self._deserialize) + self.suite_offers = ServicesSuiteOffersOperations( + self._client, self._config, self._serialize, self._deserialize + ) -class ServicesTopLevelItemsOperations: +class ServicesTopLevelItemsOperations: # pylint: disable=docstring-missing-param """ .. warning:: **DO NOT** instantiate this class directly. @@ -198,7 +203,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + headers=_headers, + params=_next_request_params, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -211,7 +219,10 @@ def prepare_request(next_link=None): async def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize(list[_models.ItemDetails], deserialized.get("value", [])) + list_of_elem = _deserialize( + list[_models.ItemDetails], + deserialized.get("value", []), + ) if cls: list_of_elem = cls(list_of_elem) # type: ignore return deserialized.get("nextLink") or None, AsyncList(list_of_elem) @@ -234,7 +245,7 @@ async def get_next(next_link=None): return AsyncItemPaged(get_next, extract_data) -class ServicesJobsOperations: +class ServicesJobsOperations: # pylint: disable=docstring-missing-param """ .. warning:: **DO NOT** instantiate this class directly. @@ -290,7 +301,7 @@ async def create( resource_group_name: str, workspace_name: str, job_id: str, - resource: JSON, + resource: _types.JobDetails, *, content_type: str = "application/json", **kwargs: Any @@ -306,7 +317,7 @@ async def create( :param job_id: Id of the job. Required. :type job_id: str :param resource: The resource instance. Required. - :type resource: JSON + :type resource: ~azure.quantum.types.JobDetails :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str @@ -354,7 +365,7 @@ async def create( resource_group_name: str, workspace_name: str, job_id: str, - resource: Union[_models.JobDetails, JSON, IO[bytes]], + resource: Union[_models.JobDetails, _types.JobDetails, IO[bytes]], **kwargs: Any ) -> _models.JobDetails: """Create a new job. @@ -367,9 +378,10 @@ async def create( :type workspace_name: str :param job_id: Id of the job. Required. :type job_id: str - :param resource: The resource instance. Is one of the following types: JobDetails, JSON, - IO[bytes] Required. - :type resource: ~azure.quantum.models.JobDetails or JSON or IO[bytes] + :param resource: The resource instance. Is either a JobDetails type or a IO[bytes] type. + Required. + :type resource: ~azure.quantum.models.JobDetails or ~azure.quantum.types.JobDetails or + IO[bytes] :return: JobDetails. The JobDetails is compatible with MutableMapping :rtype: ~azure.quantum.models.JobDetails :raises ~azure.core.exceptions.HttpResponseError: @@ -411,6 +423,7 @@ async def create( } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -428,7 +441,7 @@ async def create( raise HttpResponseError(response=response) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.JobDetails, response.json()) @@ -448,7 +461,7 @@ async def update( *, content_type: str = "application/merge-patch+json", **kwargs: Any - ) -> _models.JobUpdateOptions: + ) -> _models.JobUpdateResponse: """Update job properties. :param subscription_id: The Azure subscription ID. Required. @@ -464,8 +477,8 @@ async def update( :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/merge-patch+json". :paramtype content_type: str - :return: JobUpdateOptions. The JobUpdateOptions is compatible with MutableMapping - :rtype: ~azure.quantum.models.JobUpdateOptions + :return: JobUpdateResponse. The JobUpdateResponse is compatible with MutableMapping + :rtype: ~azure.quantum.models.JobUpdateResponse :raises ~azure.core.exceptions.HttpResponseError: """ @@ -476,11 +489,11 @@ async def update( resource_group_name: str, workspace_name: str, job_id: str, - resource: JSON, + resource: _types.JobUpdateOptions, *, content_type: str = "application/merge-patch+json", **kwargs: Any - ) -> _models.JobUpdateOptions: + ) -> _models.JobUpdateResponse: """Update job properties. :param subscription_id: The Azure subscription ID. Required. @@ -492,12 +505,12 @@ async def update( :param job_id: Id of the job. Required. :type job_id: str :param resource: The resource instance. Required. - :type resource: JSON + :type resource: ~azure.quantum.types.JobUpdateOptions :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/merge-patch+json". :paramtype content_type: str - :return: JobUpdateOptions. The JobUpdateOptions is compatible with MutableMapping - :rtype: ~azure.quantum.models.JobUpdateOptions + :return: JobUpdateResponse. The JobUpdateResponse is compatible with MutableMapping + :rtype: ~azure.quantum.models.JobUpdateResponse :raises ~azure.core.exceptions.HttpResponseError: """ @@ -512,7 +525,7 @@ async def update( *, content_type: str = "application/merge-patch+json", **kwargs: Any - ) -> _models.JobUpdateOptions: + ) -> _models.JobUpdateResponse: """Update job properties. :param subscription_id: The Azure subscription ID. Required. @@ -528,8 +541,8 @@ async def update( :keyword content_type: Body Parameter content-type. Content type parameter for binary body. Default value is "application/merge-patch+json". :paramtype content_type: str - :return: JobUpdateOptions. The JobUpdateOptions is compatible with MutableMapping - :rtype: ~azure.quantum.models.JobUpdateOptions + :return: JobUpdateResponse. The JobUpdateResponse is compatible with MutableMapping + :rtype: ~azure.quantum.models.JobUpdateResponse :raises ~azure.core.exceptions.HttpResponseError: """ @@ -555,9 +568,9 @@ async def update( resource_group_name: str, workspace_name: str, job_id: str, - resource: Union[_models.JobUpdateOptions, JSON, IO[bytes]], + resource: Union[_models.JobUpdateOptions, _types.JobUpdateOptions, IO[bytes]], **kwargs: Any - ) -> _models.JobUpdateOptions: + ) -> _models.JobUpdateResponse: """Update job properties. :param subscription_id: The Azure subscription ID. Required. @@ -568,11 +581,12 @@ async def update( :type workspace_name: str :param job_id: Id of the job. Required. :type job_id: str - :param resource: The resource instance. Is one of the following types: JobUpdateOptions, JSON, - IO[bytes] Required. - :type resource: ~azure.quantum.models.JobUpdateOptions or JSON or IO[bytes] - :return: JobUpdateOptions. The JobUpdateOptions is compatible with MutableMapping - :rtype: ~azure.quantum.models.JobUpdateOptions + :param resource: The resource instance. Is either a JobUpdateOptions type or a IO[bytes] type. + Required. + :type resource: ~azure.quantum.models.JobUpdateOptions or ~azure.quantum.types.JobUpdateOptions + or IO[bytes] + :return: JobUpdateResponse. The JobUpdateResponse is compatible with MutableMapping + :rtype: ~azure.quantum.models.JobUpdateResponse :raises ~azure.core.exceptions.HttpResponseError: """ error_map: MutableMapping = { @@ -587,7 +601,7 @@ async def update( _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.JobUpdateOptions] = kwargs.pop("cls", None) + cls: ClsType[_models.JobUpdateResponse] = kwargs.pop("cls", None) content_type = content_type or "application/merge-patch+json" _content = None @@ -612,6 +626,7 @@ async def update( } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -629,9 +644,9 @@ async def update( raise HttpResponseError(response=response) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.JobUpdateOptions, response.json()) + deserialized = _deserialize(_models.JobUpdateResponse, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -756,6 +771,7 @@ async def cancel( } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -773,7 +789,7 @@ async def cancel( raise HttpResponseError(response=response) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.JobDetails, response.json()) @@ -827,6 +843,7 @@ async def get( } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -844,7 +861,7 @@ async def get( raise HttpResponseError(response=response) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.JobDetails, response.json()) @@ -942,7 +959,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + headers=_headers, + params=_next_request_params, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -955,7 +975,10 @@ def prepare_request(next_link=None): async def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize(list[_models.JobDetails], deserialized.get("value", [])) + list_of_elem = _deserialize( + list[_models.JobDetails], + deserialized.get("value", []), + ) if cls: list_of_elem = cls(list_of_elem) # type: ignore return deserialized.get("nextLink") or None, AsyncList(list_of_elem) @@ -978,7 +1001,7 @@ async def get_next(next_link=None): return AsyncItemPaged(get_next, extract_data) -class ServicesProvidersOperations: +class ServicesProvidersOperations: # pylint: disable=docstring-missing-param """ .. warning:: **DO NOT** instantiate this class directly. @@ -1053,7 +1076,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + headers=_headers, + params=_next_request_params, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -1066,7 +1092,10 @@ def prepare_request(next_link=None): async def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize(list[_models.ProviderStatus], deserialized.get("value", [])) + list_of_elem = _deserialize( + list[_models.ProviderStatus], + deserialized.get("value", []), + ) if cls: list_of_elem = cls(list_of_elem) # type: ignore return deserialized.get("nextLink") or None, AsyncList(list_of_elem) @@ -1089,7 +1118,7 @@ async def get_next(next_link=None): return AsyncItemPaged(get_next, extract_data) -class ServicesQuotasOperations: +class ServicesQuotasOperations: # pylint: disable=docstring-missing-param """ .. warning:: **DO NOT** instantiate this class directly. @@ -1164,7 +1193,126 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + headers=_headers, + params=_next_request_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url( + "self._config.endpoint", self._config.endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + return _request + + async def extract_data(pipeline_response): + deserialized = pipeline_response.http_response.json() + list_of_elem = _deserialize( + list[_models.Quota], + deserialized.get("value", []), + ) + if cls: + list_of_elem = cls(list_of_elem) # type: ignore + return deserialized.get("nextLink") or None, AsyncList(list_of_elem) + + async def get_next(next_link=None): + _request = prepare_request(next_link) + + _stream = False + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + response = pipeline_response.http_response + + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) + + return pipeline_response + + return AsyncItemPaged(get_next, extract_data) + + @distributed_trace + @api_version_validation( + method_added_on="2026-01-15-preview", + params_added_on={ + "2026-01-15-preview": [ + "api_version", + "subscription_id", + "resource_group_name", + "workspace_name", + "provider_id", + "accept", + ] + }, + api_versions_list=["2026-01-15-preview"], + ) + def list_workspace_usages( + self, subscription_id: str, resource_group_name: str, workspace_name: str, *, provider_id: str, **kwargs: Any + ) -> AsyncItemPaged["_models.QuotaUsage"]: + """List quota usages for the given workspace. This operation is only available for v2 workspaces. + + :param subscription_id: The Azure subscription ID. Required. + :type subscription_id: str + :param resource_group_name: Name of the Azure resource group. Required. + :type resource_group_name: str + :param workspace_name: Name of the Azure Quantum workspace. Required. + :type workspace_name: str + :keyword provider_id: The unique identifier for the provider to get quota usages for. Required. + :paramtype provider_id: str + :return: An iterator like instance of QuotaUsage + :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.quantum.models.QuotaUsage] + :raises ~azure.core.exceptions.HttpResponseError: + """ + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[list[_models.QuotaUsage]] = kwargs.pop("cls", None) + + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + def prepare_request(next_link=None): + if not next_link: + + _request = build_services_quotas_list_workspace_usages_request( + subscription_id=subscription_id, + resource_group_name=resource_group_name, + workspace_name=workspace_name, + provider_id=provider_id, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url( + "self._config.endpoint", self._config.endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + else: + # make call to next link with the client's api-version + _parsed_next_link = urllib.parse.urlparse(next_link) + _next_request_params = case_insensitive_dict( + { + key: [urllib.parse.quote(v) for v in value] + for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items() + } + ) + _next_request_params["api-version"] = self._config.api_version + _request = HttpRequest( + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + headers=_headers, + params=_next_request_params, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -1177,7 +1325,10 @@ def prepare_request(next_link=None): async def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize(list[_models.Quota], deserialized.get("value", [])) + list_of_elem = _deserialize( + list[_models.QuotaUsage], + deserialized.get("value", []), + ) if cls: list_of_elem = cls(list_of_elem) # type: ignore return deserialized.get("nextLink") or None, AsyncList(list_of_elem) @@ -1200,7 +1351,7 @@ async def get_next(next_link=None): return AsyncItemPaged(get_next, extract_data) -class ServicesSessionsOperations: +class ServicesSessionsOperations: # pylint: disable=docstring-missing-param """ .. warning:: **DO NOT** instantiate this class directly. @@ -1256,7 +1407,7 @@ async def open( resource_group_name: str, workspace_name: str, session_id: str, - resource: JSON, + resource: _types.SessionDetails, *, content_type: str = "application/json", **kwargs: Any @@ -1272,7 +1423,7 @@ async def open( :param session_id: Id of the session. Required. :type session_id: str :param resource: The resource instance. Required. - :type resource: JSON + :type resource: ~azure.quantum.types.SessionDetails :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str @@ -1320,7 +1471,7 @@ async def open( resource_group_name: str, workspace_name: str, session_id: str, - resource: Union[_models.SessionDetails, JSON, IO[bytes]], + resource: Union[_models.SessionDetails, _types.SessionDetails, IO[bytes]], **kwargs: Any ) -> _models.SessionDetails: """Open a new session. @@ -1333,9 +1484,10 @@ async def open( :type workspace_name: str :param session_id: Id of the session. Required. :type session_id: str - :param resource: The resource instance. Is one of the following types: SessionDetails, JSON, - IO[bytes] Required. - :type resource: ~azure.quantum.models.SessionDetails or JSON or IO[bytes] + :param resource: The resource instance. Is either a SessionDetails type or a IO[bytes] type. + Required. + :type resource: ~azure.quantum.models.SessionDetails or ~azure.quantum.types.SessionDetails or + IO[bytes] :return: SessionDetails. The SessionDetails is compatible with MutableMapping :rtype: ~azure.quantum.models.SessionDetails :raises ~azure.core.exceptions.HttpResponseError: @@ -1377,6 +1529,7 @@ async def open( } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -1394,7 +1547,7 @@ async def open( raise HttpResponseError(response=response) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.SessionDetails, response.json()) @@ -1448,6 +1601,7 @@ async def close( } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -1465,7 +1619,7 @@ async def close( raise HttpResponseError(response=response) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.SessionDetails, response.json()) @@ -1519,6 +1673,7 @@ async def get( } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -1536,7 +1691,7 @@ async def get( raise HttpResponseError(response=response) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.SessionDetails, response.json()) @@ -1641,7 +1796,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + headers=_headers, + params=_next_request_params, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -1654,7 +1812,10 @@ def prepare_request(next_link=None): async def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize(list[_models.SessionDetails], deserialized.get("value", [])) + list_of_elem = _deserialize( + list[_models.SessionDetails], + deserialized.get("value", []), + ) if cls: list_of_elem = cls(list_of_elem) # type: ignore return deserialized.get("nextLink") or None, AsyncList(list_of_elem) @@ -1769,7 +1930,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + headers=_headers, + params=_next_request_params, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -1782,7 +1946,10 @@ def prepare_request(next_link=None): async def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize(list[_models.JobDetails], deserialized.get("value", [])) + list_of_elem = _deserialize( + list[_models.JobDetails], + deserialized.get("value", []), + ) if cls: list_of_elem = cls(list_of_elem) # type: ignore return deserialized.get("nextLink") or None, AsyncList(list_of_elem) @@ -1805,7 +1972,7 @@ async def get_next(next_link=None): return AsyncItemPaged(get_next, extract_data) -class ServicesStorageOperations: +class ServicesStorageOperations: # pylint: disable=docstring-missing-param """ .. warning:: **DO NOT** instantiate this class directly. @@ -1860,7 +2027,7 @@ async def get_sas_uri( subscription_id: str, resource_group_name: str, workspace_name: str, - blob_details: JSON, + blob_details: _types.BlobDetails, *, content_type: str = "application/json", **kwargs: Any @@ -1877,7 +2044,7 @@ async def get_sas_uri( :param workspace_name: Name of the Azure Quantum workspace. Required. :type workspace_name: str :param blob_details: The details (name and container) of the blob. Required. - :type blob_details: JSON + :type blob_details: ~azure.quantum.types.BlobDetails :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str @@ -1924,7 +2091,7 @@ async def get_sas_uri( subscription_id: str, resource_group_name: str, workspace_name: str, - blob_details: Union[_models.BlobDetails, JSON, IO[bytes]], + blob_details: Union[_models.BlobDetails, _types.BlobDetails, IO[bytes]], **kwargs: Any ) -> _models.SasUriResponse: """Gets a URL with SAS token for a container/blob in the storage account associated with the @@ -1938,9 +2105,10 @@ async def get_sas_uri( :type resource_group_name: str :param workspace_name: Name of the Azure Quantum workspace. Required. :type workspace_name: str - :param blob_details: The details (name and container) of the blob. Is one of the following - types: BlobDetails, JSON, IO[bytes] Required. - :type blob_details: ~azure.quantum.models.BlobDetails or JSON or IO[bytes] + :param blob_details: The details (name and container) of the blob. Is either a BlobDetails type + or a IO[bytes] type. Required. + :type blob_details: ~azure.quantum.models.BlobDetails or ~azure.quantum.types.BlobDetails or + IO[bytes] :return: SasUriResponse. The SasUriResponse is compatible with MutableMapping :rtype: ~azure.quantum.models.SasUriResponse :raises ~azure.core.exceptions.HttpResponseError: @@ -1981,6 +2149,7 @@ async def get_sas_uri( } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -1998,7 +2167,7 @@ async def get_sas_uri( raise HttpResponseError(response=response) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.SasUriResponse, response.json()) @@ -2006,3 +2175,196 @@ async def get_sas_uri( return cls(pipeline_response, deserialized, {}) # type: ignore return deserialized # type: ignore + + +class ServicesSuiteOffersOperations: # pylint: disable=docstring-missing-param + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.quantum.aio.WorkspaceClient`'s + :attr:`suite_offers` attribute. + """ + + def __init__(self, *args, **kwargs) -> None: + input_args = list(args) + self._client: AsyncPipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") + self._config: WorkspaceClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") + self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") + self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") + + @distributed_trace + @api_version_validation( + method_added_on="2026-01-15-preview", + params_added_on={"2026-01-15-preview": ["api_version", "subscription_id", "provider_id", "accept"]}, + api_versions_list=["2026-01-15-preview"], + ) + def list_quota_usages( + self, subscription_id: str, provider_id: str, **kwargs: Any + ) -> AsyncItemPaged["_models.QuotaUsage"]: + """List quota usages for the given suite offer provider in the subscription. + + :param subscription_id: The Azure subscription ID. Required. + :type subscription_id: str + :param provider_id: The unique identifier for the provider. Required. + :type provider_id: str + :return: An iterator like instance of QuotaUsage + :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.quantum.models.QuotaUsage] + :raises ~azure.core.exceptions.HttpResponseError: + """ + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[list[_models.QuotaUsage]] = kwargs.pop("cls", None) + + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + def prepare_request(next_link=None): + if not next_link: + + _request = build_services_suite_offers_list_quota_usages_request( + subscription_id=subscription_id, + provider_id=provider_id, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url( + "self._config.endpoint", self._config.endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + else: + # make call to next link with the client's api-version + _parsed_next_link = urllib.parse.urlparse(next_link) + _next_request_params = case_insensitive_dict( + { + key: [urllib.parse.quote(v) for v in value] + for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items() + } + ) + _next_request_params["api-version"] = self._config.api_version + _request = HttpRequest( + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + headers=_headers, + params=_next_request_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url( + "self._config.endpoint", self._config.endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + return _request + + async def extract_data(pipeline_response): + deserialized = pipeline_response.http_response.json() + values = deserialized if isinstance(deserialized, list) else deserialized.get("value", []) + list_of_elem = _deserialize( + list[_models.QuotaUsage], + values, + ) + if cls: + list_of_elem = cls(list_of_elem) # type: ignore + next_link = None if isinstance(deserialized, list) else deserialized.get("nextLink") or None + return next_link, AsyncList(list_of_elem) + + async def get_next(next_link=None): + _request = prepare_request(next_link) + + _stream = False + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + response = pipeline_response.http_response + + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) + + return pipeline_response + + return AsyncItemPaged(get_next, extract_data) + + @distributed_trace_async + @api_version_validation( + method_added_on="2026-01-15-preview", + params_added_on={"2026-01-15-preview": ["api_version", "subscription_id", "provider_id", "accept"]}, + api_versions_list=["2026-01-15-preview"], + ) + async def get_provider_status( + self, subscription_id: str, provider_id: str, **kwargs: Any + ) -> _models.ProviderStatus: + """Get the provider status, including target statuses, for the given suite offer provider in the + subscription. + + :param subscription_id: The Azure subscription ID. Required. + :type subscription_id: str + :param provider_id: The unique identifier for the provider. Required. + :type provider_id: str + :return: ProviderStatus. The ProviderStatus is compatible with MutableMapping + :rtype: ~azure.quantum.models.ProviderStatus + :raises ~azure.core.exceptions.HttpResponseError: + """ + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[_models.ProviderStatus] = kwargs.pop("cls", None) + + _request = build_services_suite_offers_get_provider_status_request( + subscription_id=subscription_id, + provider_id=provider_id, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + if _stream: + try: + await response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) + + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.ProviderStatus, response.json()) + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore diff --git a/azure-quantum/azure/quantum/_client/aio/operations/_patch.py b/azure-quantum/azure/quantum/_client/aio/operations/_patch.py index 87676c65..ea765788 100644 --- a/azure-quantum/azure/quantum/_client/aio/operations/_patch.py +++ b/azure-quantum/azure/quantum/_client/aio/operations/_patch.py @@ -8,7 +8,6 @@ Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ - __all__: list[str] = [] # Add all objects you want publicly available to users at this package level diff --git a/azure-quantum/azure/quantum/_client/models/__init__.py b/azure-quantum/azure/quantum/_client/models/__init__.py index 219a54d2..b7821448 100644 --- a/azure-quantum/azure/quantum/_client/models/__init__.py +++ b/azure-quantum/azure/quantum/_client/models/__init__.py @@ -20,9 +20,11 @@ ItemDetails, JobDetails, JobUpdateOptions, + JobUpdateResponse, ProviderStatus, QuantumComputingData, Quota, + QuotaUsage, SasUriResponse, SessionDetails, TargetStatus, @@ -42,6 +44,7 @@ ProviderAvailability, SessionJobFailurePolicy, SessionStatus, + SuiteOfferScope, TargetAvailability, ) from ._patch import __all__ as _patch_all @@ -55,9 +58,11 @@ "ItemDetails", "JobDetails", "JobUpdateOptions", + "JobUpdateResponse", "ProviderStatus", "QuantumComputingData", "Quota", + "QuotaUsage", "SasUriResponse", "SessionDetails", "TargetStatus", @@ -74,6 +79,7 @@ "ProviderAvailability", "SessionJobFailurePolicy", "SessionStatus", + "SuiteOfferScope", "TargetAvailability", ] __all__.extend([p for p in _patch_all if p not in __all__]) # pyright: ignore diff --git a/azure-quantum/azure/quantum/_client/models/_enums.py b/azure-quantum/azure/quantum/_client/models/_enums.py index 7cd9eff3..55c079af 100644 --- a/azure-quantum/azure/quantum/_client/models/_enums.py +++ b/azure-quantum/azure/quantum/_client/models/_enums.py @@ -134,6 +134,17 @@ class SessionStatus(str, Enum, metaclass=CaseInsensitiveEnumMeta): """The session timed out.""" +class SuiteOfferScope(str, Enum, metaclass=CaseInsensitiveEnumMeta): + """The scope at which the suite offer quota usage is applied.""" + + TARGET = "Target" + """The usage is applied at the target level.""" + SUBSCRIPTION_TARGET = "SubscriptionTarget" + """The usage is applied at the subscription target level.""" + WORKSPACE_TARGET = "WorkspaceTarget" + """The usage is applied at the workspace target level.""" + + class TargetAvailability(str, Enum, metaclass=CaseInsensitiveEnumMeta): """Target availability.""" diff --git a/azure-quantum/azure/quantum/_client/models/_models.py b/azure-quantum/azure/quantum/_client/models/_models.py index b1ae5481..9c69ce89 100644 --- a/azure-quantum/azure/quantum/_client/models/_models.py +++ b/azure-quantum/azure/quantum/_client/models/_models.py @@ -20,7 +20,7 @@ from .. import models as _models -class BlobDetails(_Model): +class BlobDetails(_Model): # pylint: disable=docstring-keyword-should-match-keyword-only """The details (name and container) of the blob to store or download data. :ivar container_name: The container name. Required. @@ -53,7 +53,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) -class CostEstimate(_Model): +class CostEstimate(_Model): # pylint: disable=docstring-keyword-should-match-keyword-only """The job cost billed by the provider. The final cost on your bill might be slightly different due to added taxes and currency conversion rates. @@ -96,7 +96,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) -class InnerError(_Model): +class InnerError(_Model): # pylint: disable=docstring-keyword-should-match-keyword-only """An object containing more specific information about the error. As per Azure REST API guidelines - `https://aka.ms/AzureRestApiGuidelines#handling-errors `_. @@ -131,7 +131,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) -class ItemDetails(_Model): +class ItemDetails(_Model): # pylint: disable=docstring-keyword-should-match-keyword-only """A workspace item. You probably want to use the sub-classes and not this class directly. Known sub-classes are: @@ -262,7 +262,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) -class JobDetails(ItemDetails, discriminator="Job"): +class JobDetails(ItemDetails, discriminator="Job"): # pylint: disable=docstring-keyword-should-match-keyword-only """A job to be run in the workspace. :ivar name: The name of the item. It is not required for the name to be unique and it's only @@ -412,7 +412,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: self.item_type = ItemType.JOB # type: ignore -class JobUpdateOptions(_Model): +class JobUpdateOptions(_Model): # pylint: disable=docstring-keyword-should-match-keyword-only """Options for updating a job. :ivar id: Id of the job. Required. @@ -454,6 +454,46 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) +class JobUpdateResponse(_Model): # pylint: disable=docstring-keyword-should-match-keyword-only + """Response returned when a job update succeeds. + + :ivar name: The name of the job. Required. + :vartype name: str + :ivar priority: Priority of job. Known values are: "Standard" and "High". + :vartype priority: str or ~azure.quantum.models.Priority + :ivar tags: List of user-supplied tags associated with the job. Required. + :vartype tags: list[str] + """ + + name: str = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """The name of the job. Required.""" + priority: Optional[Union[str, "_models.Priority"]] = rest_field( + visibility=["read", "create", "update", "delete", "query"] + ) + """Priority of job. Known values are: \"Standard\" and \"High\".""" + tags: list[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """List of user-supplied tags associated with the job. Required.""" + + @overload + def __init__( + self, + *, + name: str, + tags: list[str], + priority: Optional[Union[str, "_models.Priority"]] = None, + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + class ProviderStatus(_Model): """Provider status. @@ -531,6 +571,39 @@ class Quota(_Model): 'None' is used for concurrent quotas. Required. Known values are: \"None\" and \"Monthly\".""" +class QuotaUsage(_Model): + """Quota usage for a suite offer provider. + + :ivar provider_id: The unique identifier for the provider. Required. + :vartype provider_id: str + :ivar scope: The scope at which the quota usage is applied. Required. Known values are: + "Target", "SubscriptionTarget", and "WorkspaceTarget". + :vartype scope: str or ~azure.quantum.models.SuiteOfferScope + :ivar target_id: The unique identifier for the target, when the usage is scoped to a target. + :vartype target_id: str + :ivar usage: The accumulated quota usage values. Required. + :vartype usage: ~azure.quantum.models.Usage + :ivar last_modified_time: The time when the quota usage was last modified. Required. + :vartype last_modified_time: ~datetime.datetime + :ivar metadata: Additional metadata associated with the quota usage. + :vartype metadata: dict[str, str] + """ + + provider_id: str = rest_field(name="providerId", visibility=["read"]) + """The unique identifier for the provider. Required.""" + scope: Union[str, "_models.SuiteOfferScope"] = rest_field(visibility=["read"]) + """The scope at which the quota usage is applied. Required. Known values are: \"Target\", + \"SubscriptionTarget\", and \"WorkspaceTarget\".""" + target_id: Optional[str] = rest_field(name="targetId", visibility=["read"]) + """The unique identifier for the target, when the usage is scoped to a target.""" + usage: "_models.Usage" = rest_field(visibility=["read"]) + """The accumulated quota usage values. Required.""" + last_modified_time: datetime.datetime = rest_field(name="lastModifiedTime", visibility=["read"], format="rfc3339") + """The time when the quota usage was last modified. Required.""" + metadata: Optional[dict[str, str]] = rest_field(visibility=["read"]) + """Additional metadata associated with the quota usage.""" + + class SasUriResponse(_Model): """SAS URI operation response. @@ -543,7 +616,9 @@ class SasUriResponse(_Model): """A URL with a SAS token to upload a blob for execution in the given workspace. Required.""" -class SessionDetails(ItemDetails, discriminator="Session"): +class SessionDetails( + ItemDetails, discriminator="Session" +): # pylint: disable=docstring-keyword-should-match-keyword-only """Session, a logical grouping of jobs. :ivar name: The name of the item. It is not required for the name to be unique and it's only @@ -643,6 +718,12 @@ class TargetStatus(_Model): :vartype current_availability: str or ~azure.quantum.models.TargetAvailability :ivar average_queue_time: Average queue time in seconds. Required. :vartype average_queue_time: int + :ivar average_queue_time_high_priority: Average high-priority queue time in seconds. Only + populated for v2 workspaces and suite offers; omitted otherwise. + :vartype average_queue_time_high_priority: int + :ivar average_queue_time_standard_priority: Average standard-priority queue time in seconds. + Only populated for v2 workspaces and suite offers; omitted otherwise. + :vartype average_queue_time_standard_priority: int :ivar status_page: A page with detailed status of the provider. :vartype status_page: str :ivar num_qubits: The qubit number. @@ -662,6 +743,16 @@ class TargetStatus(_Model): \"Unavailable\".""" average_queue_time: int = rest_field(name="averageQueueTime", visibility=["read"]) """Average queue time in seconds. Required.""" + average_queue_time_high_priority: Optional[int] = rest_field( + name="averageQueueTimeHighPriority", visibility=["read"] + ) + """Average high-priority queue time in seconds. Only populated for v2 workspaces and suite offers; + omitted otherwise.""" + average_queue_time_standard_priority: Optional[int] = rest_field( + name="averageQueueTimeStandardPriority", visibility=["read"] + ) + """Average standard-priority queue time in seconds. Only populated for v2 workspaces and suite + offers; omitted otherwise.""" status_page: Optional[str] = rest_field(name="statusPage", visibility=["read"]) """A page with detailed status of the provider.""" num_qubits: Optional[int] = rest_field(name="numQubits", visibility=["read"]) @@ -680,7 +771,7 @@ class Usage(_Model): """ -class UsageEvent(_Model): +class UsageEvent(_Model): # pylint: disable=docstring-keyword-should-match-keyword-only """Usage event details. :ivar dimension_id: The dimension id. Required. @@ -735,7 +826,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) -class WorkspaceItemError(_Model): +class WorkspaceItemError(_Model): # pylint: disable=docstring-keyword-should-match-keyword-only """The error object. :ivar code: One of a server-defined set of error codes. Required. diff --git a/azure-quantum/azure/quantum/_client/models/_patch.py b/azure-quantum/azure/quantum/_client/models/_patch.py index 87676c65..ea765788 100644 --- a/azure-quantum/azure/quantum/_client/models/_patch.py +++ b/azure-quantum/azure/quantum/_client/models/_patch.py @@ -8,7 +8,6 @@ Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ - __all__: list[str] = [] # Add all objects you want publicly available to users at this package level diff --git a/azure-quantum/azure/quantum/_client/operations/_operations.py b/azure-quantum/azure/quantum/_client/operations/_operations.py index 4f6b49cc..7b9bf3ac 100644 --- a/azure-quantum/azure/quantum/_client/operations/_operations.py +++ b/azure-quantum/azure/quantum/_client/operations/_operations.py @@ -29,7 +29,7 @@ from azure.core.tracing.decorator import distributed_trace from azure.core.utils import case_insensitive_dict -from .. import models as _models +from .. import models as _models, types as _types from .._configuration import WorkspaceClientConfiguration from .._utils.model_base import SdkJSONEncoder, _deserialize from .._utils.serialization import Deserializer, Serializer @@ -37,7 +37,6 @@ T = TypeVar("T") ClsType = Optional[Callable[[PipelineResponse[HttpRequest, HttpResponse], T, dict[str, Any]], Any]] -JSON = MutableMapping[str, Any] _SERIALIZER = Serializer() _SERIALIZER.client_side_validation = False @@ -130,7 +129,7 @@ def build_services_jobs_update_request( accept = _headers.pop("Accept", "application/json") # Construct URL - _url = "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Quantum/workspaces/{workspaceName}/jobUpdateOptions/{jobId}" + _url = "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Quantum/workspaces/{workspaceName}/jobs/{jobId}" path_format_arguments = { "subscriptionId": _SERIALIZER.url("subscription_id", subscription_id, "str"), "resourceGroupName": _SERIALIZER.url("resource_group_name", resource_group_name, "str"), @@ -332,6 +331,35 @@ def build_services_quotas_list_request( return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) +def build_services_quotas_list_workspace_usages_request( # pylint: disable=name-too-long + subscription_id: str, resource_group_name: str, workspace_name: str, *, provider_id: str, **kwargs: Any +) -> HttpRequest: + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) + + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-01-15-preview")) + accept = _headers.pop("Accept", "application/json") + + # Construct URL + _url = "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Quantum/workspaces/{workspaceName}/quotaUsages" + path_format_arguments = { + "subscriptionId": _SERIALIZER.url("subscription_id", subscription_id, "str"), + "resourceGroupName": _SERIALIZER.url("resource_group_name", resource_group_name, "str"), + "workspaceName": _SERIALIZER.url("workspace_name", workspace_name, "str"), + } + + _url: str = _url.format(**path_format_arguments) # type: ignore + + # Construct parameters + _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") + _params["providerId"] = _SERIALIZER.query("provider_id", provider_id, "str") + + # Construct headers + _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") + + return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) + + def build_services_sessions_open_request( subscription_id: str, resource_group_name: str, workspace_name: str, session_id: str, **kwargs: Any ) -> HttpRequest: @@ -543,7 +571,61 @@ def build_services_storage_get_sas_uri_request( # pylint: disable=name-too-long return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, **kwargs) -class ServicesOperations: +def build_services_suite_offers_list_quota_usages_request( # pylint: disable=name-too-long + subscription_id: str, provider_id: str, **kwargs: Any +) -> HttpRequest: + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) + + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-01-15-preview")) + accept = _headers.pop("Accept", "application/json") + + # Construct URL + _url = "/subscriptions/{subscriptionId}/providers/Microsoft.Quantum/suiteOffers/{providerId}/quotaUsages" + path_format_arguments = { + "subscriptionId": _SERIALIZER.url("subscription_id", subscription_id, "str"), + "providerId": _SERIALIZER.url("provider_id", provider_id, "str"), + } + + _url: str = _url.format(**path_format_arguments) # type: ignore + + # Construct parameters + _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") + + # Construct headers + _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") + + return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) + + +def build_services_suite_offers_get_provider_status_request( # pylint: disable=name-too-long + subscription_id: str, provider_id: str, **kwargs: Any +) -> HttpRequest: + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) + + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-01-15-preview")) + accept = _headers.pop("Accept", "application/json") + + # Construct URL + _url = "/subscriptions/{subscriptionId}/providers/Microsoft.Quantum/suiteOffers/{providerId}/providerStatus" + path_format_arguments = { + "subscriptionId": _SERIALIZER.url("subscription_id", subscription_id, "str"), + "providerId": _SERIALIZER.url("provider_id", provider_id, "str"), + } + + _url: str = _url.format(**path_format_arguments) # type: ignore + + # Construct parameters + _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") + + # Construct headers + _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") + + return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) + + +class ServicesOperations: # pylint: disable=docstring-missing-param,too-many-instance-attributes """ .. warning:: **DO NOT** instantiate this class directly. @@ -568,9 +650,12 @@ def __init__(self, *args, **kwargs) -> None: self.quotas = ServicesQuotasOperations(self._client, self._config, self._serialize, self._deserialize) self.sessions = ServicesSessionsOperations(self._client, self._config, self._serialize, self._deserialize) self.storage = ServicesStorageOperations(self._client, self._config, self._serialize, self._deserialize) + self.suite_offers = ServicesSuiteOffersOperations( + self._client, self._config, self._serialize, self._deserialize + ) -class ServicesTopLevelItemsOperations: +class ServicesTopLevelItemsOperations: # pylint: disable=docstring-missing-param """ .. warning:: **DO NOT** instantiate this class directly. @@ -683,7 +768,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + headers=_headers, + params=_next_request_params, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -696,7 +784,10 @@ def prepare_request(next_link=None): def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize(list[_models.ItemDetails], deserialized.get("value", [])) + list_of_elem = _deserialize( + list[_models.ItemDetails], + deserialized.get("value", []), + ) if cls: list_of_elem = cls(list_of_elem) # type: ignore return deserialized.get("nextLink") or None, iter(list_of_elem) @@ -719,7 +810,7 @@ def get_next(next_link=None): return ItemPaged(get_next, extract_data) -class ServicesJobsOperations: +class ServicesJobsOperations: # pylint: disable=docstring-missing-param """ .. warning:: **DO NOT** instantiate this class directly. @@ -775,7 +866,7 @@ def create( resource_group_name: str, workspace_name: str, job_id: str, - resource: JSON, + resource: _types.JobDetails, *, content_type: str = "application/json", **kwargs: Any @@ -791,7 +882,7 @@ def create( :param job_id: Id of the job. Required. :type job_id: str :param resource: The resource instance. Required. - :type resource: JSON + :type resource: ~azure.quantum.types.JobDetails :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str @@ -839,7 +930,7 @@ def create( resource_group_name: str, workspace_name: str, job_id: str, - resource: Union[_models.JobDetails, JSON, IO[bytes]], + resource: Union[_models.JobDetails, _types.JobDetails, IO[bytes]], **kwargs: Any ) -> _models.JobDetails: """Create a new job. @@ -852,9 +943,10 @@ def create( :type workspace_name: str :param job_id: Id of the job. Required. :type job_id: str - :param resource: The resource instance. Is one of the following types: JobDetails, JSON, - IO[bytes] Required. - :type resource: ~azure.quantum.models.JobDetails or JSON or IO[bytes] + :param resource: The resource instance. Is either a JobDetails type or a IO[bytes] type. + Required. + :type resource: ~azure.quantum.models.JobDetails or ~azure.quantum.types.JobDetails or + IO[bytes] :return: JobDetails. The JobDetails is compatible with MutableMapping :rtype: ~azure.quantum.models.JobDetails :raises ~azure.core.exceptions.HttpResponseError: @@ -896,6 +988,7 @@ def create( } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -913,7 +1006,7 @@ def create( raise HttpResponseError(response=response) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.JobDetails, response.json()) @@ -933,7 +1026,7 @@ def update( *, content_type: str = "application/merge-patch+json", **kwargs: Any - ) -> _models.JobUpdateOptions: + ) -> _models.JobUpdateResponse: """Update job properties. :param subscription_id: The Azure subscription ID. Required. @@ -949,8 +1042,8 @@ def update( :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/merge-patch+json". :paramtype content_type: str - :return: JobUpdateOptions. The JobUpdateOptions is compatible with MutableMapping - :rtype: ~azure.quantum.models.JobUpdateOptions + :return: JobUpdateResponse. The JobUpdateResponse is compatible with MutableMapping + :rtype: ~azure.quantum.models.JobUpdateResponse :raises ~azure.core.exceptions.HttpResponseError: """ @@ -961,11 +1054,11 @@ def update( resource_group_name: str, workspace_name: str, job_id: str, - resource: JSON, + resource: _types.JobUpdateOptions, *, content_type: str = "application/merge-patch+json", **kwargs: Any - ) -> _models.JobUpdateOptions: + ) -> _models.JobUpdateResponse: """Update job properties. :param subscription_id: The Azure subscription ID. Required. @@ -977,12 +1070,12 @@ def update( :param job_id: Id of the job. Required. :type job_id: str :param resource: The resource instance. Required. - :type resource: JSON + :type resource: ~azure.quantum.types.JobUpdateOptions :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/merge-patch+json". :paramtype content_type: str - :return: JobUpdateOptions. The JobUpdateOptions is compatible with MutableMapping - :rtype: ~azure.quantum.models.JobUpdateOptions + :return: JobUpdateResponse. The JobUpdateResponse is compatible with MutableMapping + :rtype: ~azure.quantum.models.JobUpdateResponse :raises ~azure.core.exceptions.HttpResponseError: """ @@ -997,7 +1090,7 @@ def update( *, content_type: str = "application/merge-patch+json", **kwargs: Any - ) -> _models.JobUpdateOptions: + ) -> _models.JobUpdateResponse: """Update job properties. :param subscription_id: The Azure subscription ID. Required. @@ -1013,8 +1106,8 @@ def update( :keyword content_type: Body Parameter content-type. Content type parameter for binary body. Default value is "application/merge-patch+json". :paramtype content_type: str - :return: JobUpdateOptions. The JobUpdateOptions is compatible with MutableMapping - :rtype: ~azure.quantum.models.JobUpdateOptions + :return: JobUpdateResponse. The JobUpdateResponse is compatible with MutableMapping + :rtype: ~azure.quantum.models.JobUpdateResponse :raises ~azure.core.exceptions.HttpResponseError: """ @@ -1040,9 +1133,9 @@ def update( resource_group_name: str, workspace_name: str, job_id: str, - resource: Union[_models.JobUpdateOptions, JSON, IO[bytes]], + resource: Union[_models.JobUpdateOptions, _types.JobUpdateOptions, IO[bytes]], **kwargs: Any - ) -> _models.JobUpdateOptions: + ) -> _models.JobUpdateResponse: """Update job properties. :param subscription_id: The Azure subscription ID. Required. @@ -1053,11 +1146,12 @@ def update( :type workspace_name: str :param job_id: Id of the job. Required. :type job_id: str - :param resource: The resource instance. Is one of the following types: JobUpdateOptions, JSON, - IO[bytes] Required. - :type resource: ~azure.quantum.models.JobUpdateOptions or JSON or IO[bytes] - :return: JobUpdateOptions. The JobUpdateOptions is compatible with MutableMapping - :rtype: ~azure.quantum.models.JobUpdateOptions + :param resource: The resource instance. Is either a JobUpdateOptions type or a IO[bytes] type. + Required. + :type resource: ~azure.quantum.models.JobUpdateOptions or ~azure.quantum.types.JobUpdateOptions + or IO[bytes] + :return: JobUpdateResponse. The JobUpdateResponse is compatible with MutableMapping + :rtype: ~azure.quantum.models.JobUpdateResponse :raises ~azure.core.exceptions.HttpResponseError: """ error_map: MutableMapping = { @@ -1072,7 +1166,7 @@ def update( _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.JobUpdateOptions] = kwargs.pop("cls", None) + cls: ClsType[_models.JobUpdateResponse] = kwargs.pop("cls", None) content_type = content_type or "application/merge-patch+json" _content = None @@ -1097,6 +1191,7 @@ def update( } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -1114,9 +1209,9 @@ def update( raise HttpResponseError(response=response) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.JobUpdateOptions, response.json()) + deserialized = _deserialize(_models.JobUpdateResponse, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -1241,6 +1336,7 @@ def cancel( } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -1258,7 +1354,7 @@ def cancel( raise HttpResponseError(response=response) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.JobDetails, response.json()) @@ -1312,6 +1408,7 @@ def get( } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -1329,7 +1426,7 @@ def get( raise HttpResponseError(response=response) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.JobDetails, response.json()) @@ -1427,7 +1524,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + headers=_headers, + params=_next_request_params, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -1440,7 +1540,10 @@ def prepare_request(next_link=None): def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize(list[_models.JobDetails], deserialized.get("value", [])) + list_of_elem = _deserialize( + list[_models.JobDetails], + deserialized.get("value", []), + ) if cls: list_of_elem = cls(list_of_elem) # type: ignore return deserialized.get("nextLink") or None, iter(list_of_elem) @@ -1463,7 +1566,7 @@ def get_next(next_link=None): return ItemPaged(get_next, extract_data) -class ServicesProvidersOperations: +class ServicesProvidersOperations: # pylint: disable=docstring-missing-param """ .. warning:: **DO NOT** instantiate this class directly. @@ -1538,7 +1641,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + headers=_headers, + params=_next_request_params, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -1551,7 +1657,10 @@ def prepare_request(next_link=None): def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize(list[_models.ProviderStatus], deserialized.get("value", [])) + list_of_elem = _deserialize( + list[_models.ProviderStatus], + deserialized.get("value", []), + ) if cls: list_of_elem = cls(list_of_elem) # type: ignore return deserialized.get("nextLink") or None, iter(list_of_elem) @@ -1574,7 +1683,7 @@ def get_next(next_link=None): return ItemPaged(get_next, extract_data) -class ServicesQuotasOperations: +class ServicesQuotasOperations: # pylint: disable=docstring-missing-param """ .. warning:: **DO NOT** instantiate this class directly. @@ -1649,7 +1758,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + headers=_headers, + params=_next_request_params, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -1662,7 +1774,10 @@ def prepare_request(next_link=None): def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize(list[_models.Quota], deserialized.get("value", [])) + list_of_elem = _deserialize( + list[_models.Quota], + deserialized.get("value", []), + ) if cls: list_of_elem = cls(list_of_elem) # type: ignore return deserialized.get("nextLink") or None, iter(list_of_elem) @@ -1684,8 +1799,124 @@ def get_next(next_link=None): return ItemPaged(get_next, extract_data) + @distributed_trace + @api_version_validation( + method_added_on="2026-01-15-preview", + params_added_on={ + "2026-01-15-preview": [ + "api_version", + "subscription_id", + "resource_group_name", + "workspace_name", + "provider_id", + "accept", + ] + }, + api_versions_list=["2026-01-15-preview"], + ) + def list_workspace_usages( + self, subscription_id: str, resource_group_name: str, workspace_name: str, *, provider_id: str, **kwargs: Any + ) -> ItemPaged["_models.QuotaUsage"]: + """List quota usages for the given workspace. This operation is only available for v2 workspaces. + + :param subscription_id: The Azure subscription ID. Required. + :type subscription_id: str + :param resource_group_name: Name of the Azure resource group. Required. + :type resource_group_name: str + :param workspace_name: Name of the Azure Quantum workspace. Required. + :type workspace_name: str + :keyword provider_id: The unique identifier for the provider to get quota usages for. Required. + :paramtype provider_id: str + :return: An iterator like instance of QuotaUsage + :rtype: ~azure.core.paging.ItemPaged[~azure.quantum.models.QuotaUsage] + :raises ~azure.core.exceptions.HttpResponseError: + """ + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[list[_models.QuotaUsage]] = kwargs.pop("cls", None) -class ServicesSessionsOperations: + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + def prepare_request(next_link=None): + if not next_link: + + _request = build_services_quotas_list_workspace_usages_request( + subscription_id=subscription_id, + resource_group_name=resource_group_name, + workspace_name=workspace_name, + provider_id=provider_id, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url( + "self._config.endpoint", self._config.endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + else: + # make call to next link with the client's api-version + _parsed_next_link = urllib.parse.urlparse(next_link) + _next_request_params = case_insensitive_dict( + { + key: [urllib.parse.quote(v) for v in value] + for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items() + } + ) + _next_request_params["api-version"] = self._config.api_version + _request = HttpRequest( + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + headers=_headers, + params=_next_request_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url( + "self._config.endpoint", self._config.endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + return _request + + def extract_data(pipeline_response): + deserialized = pipeline_response.http_response.json() + list_of_elem = _deserialize( + list[_models.QuotaUsage], + deserialized.get("value", []), + ) + if cls: + list_of_elem = cls(list_of_elem) # type: ignore + return deserialized.get("nextLink") or None, iter(list_of_elem) + + def get_next(next_link=None): + _request = prepare_request(next_link) + + _stream = False + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + response = pipeline_response.http_response + + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) + + return pipeline_response + + return ItemPaged(get_next, extract_data) + + +class ServicesSessionsOperations: # pylint: disable=docstring-missing-param """ .. warning:: **DO NOT** instantiate this class directly. @@ -1741,7 +1972,7 @@ def open( resource_group_name: str, workspace_name: str, session_id: str, - resource: JSON, + resource: _types.SessionDetails, *, content_type: str = "application/json", **kwargs: Any @@ -1757,7 +1988,7 @@ def open( :param session_id: Id of the session. Required. :type session_id: str :param resource: The resource instance. Required. - :type resource: JSON + :type resource: ~azure.quantum.types.SessionDetails :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str @@ -1805,7 +2036,7 @@ def open( resource_group_name: str, workspace_name: str, session_id: str, - resource: Union[_models.SessionDetails, JSON, IO[bytes]], + resource: Union[_models.SessionDetails, _types.SessionDetails, IO[bytes]], **kwargs: Any ) -> _models.SessionDetails: """Open a new session. @@ -1818,9 +2049,10 @@ def open( :type workspace_name: str :param session_id: Id of the session. Required. :type session_id: str - :param resource: The resource instance. Is one of the following types: SessionDetails, JSON, - IO[bytes] Required. - :type resource: ~azure.quantum.models.SessionDetails or JSON or IO[bytes] + :param resource: The resource instance. Is either a SessionDetails type or a IO[bytes] type. + Required. + :type resource: ~azure.quantum.models.SessionDetails or ~azure.quantum.types.SessionDetails or + IO[bytes] :return: SessionDetails. The SessionDetails is compatible with MutableMapping :rtype: ~azure.quantum.models.SessionDetails :raises ~azure.core.exceptions.HttpResponseError: @@ -1862,6 +2094,7 @@ def open( } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -1879,7 +2112,7 @@ def open( raise HttpResponseError(response=response) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.SessionDetails, response.json()) @@ -1933,6 +2166,7 @@ def close( } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -1950,7 +2184,7 @@ def close( raise HttpResponseError(response=response) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.SessionDetails, response.json()) @@ -2004,6 +2238,7 @@ def get( } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -2021,7 +2256,7 @@ def get( raise HttpResponseError(response=response) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.SessionDetails, response.json()) @@ -2126,7 +2361,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + headers=_headers, + params=_next_request_params, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -2139,7 +2377,10 @@ def prepare_request(next_link=None): def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize(list[_models.SessionDetails], deserialized.get("value", [])) + list_of_elem = _deserialize( + list[_models.SessionDetails], + deserialized.get("value", []), + ) if cls: list_of_elem = cls(list_of_elem) # type: ignore return deserialized.get("nextLink") or None, iter(list_of_elem) @@ -2254,7 +2495,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + headers=_headers, + params=_next_request_params, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -2267,7 +2511,10 @@ def prepare_request(next_link=None): def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize(list[_models.JobDetails], deserialized.get("value", [])) + list_of_elem = _deserialize( + list[_models.JobDetails], + deserialized.get("value", []), + ) if cls: list_of_elem = cls(list_of_elem) # type: ignore return deserialized.get("nextLink") or None, iter(list_of_elem) @@ -2290,7 +2537,7 @@ def get_next(next_link=None): return ItemPaged(get_next, extract_data) -class ServicesStorageOperations: +class ServicesStorageOperations: # pylint: disable=docstring-missing-param """ .. warning:: **DO NOT** instantiate this class directly. @@ -2345,7 +2592,7 @@ def get_sas_uri( subscription_id: str, resource_group_name: str, workspace_name: str, - blob_details: JSON, + blob_details: _types.BlobDetails, *, content_type: str = "application/json", **kwargs: Any @@ -2362,7 +2609,7 @@ def get_sas_uri( :param workspace_name: Name of the Azure Quantum workspace. Required. :type workspace_name: str :param blob_details: The details (name and container) of the blob. Required. - :type blob_details: JSON + :type blob_details: ~azure.quantum.types.BlobDetails :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str @@ -2409,7 +2656,7 @@ def get_sas_uri( subscription_id: str, resource_group_name: str, workspace_name: str, - blob_details: Union[_models.BlobDetails, JSON, IO[bytes]], + blob_details: Union[_models.BlobDetails, _types.BlobDetails, IO[bytes]], **kwargs: Any ) -> _models.SasUriResponse: """Gets a URL with SAS token for a container/blob in the storage account associated with the @@ -2423,9 +2670,10 @@ def get_sas_uri( :type resource_group_name: str :param workspace_name: Name of the Azure Quantum workspace. Required. :type workspace_name: str - :param blob_details: The details (name and container) of the blob. Is one of the following - types: BlobDetails, JSON, IO[bytes] Required. - :type blob_details: ~azure.quantum.models.BlobDetails or JSON or IO[bytes] + :param blob_details: The details (name and container) of the blob. Is either a BlobDetails type + or a IO[bytes] type. Required. + :type blob_details: ~azure.quantum.models.BlobDetails or ~azure.quantum.types.BlobDetails or + IO[bytes] :return: SasUriResponse. The SasUriResponse is compatible with MutableMapping :rtype: ~azure.quantum.models.SasUriResponse :raises ~azure.core.exceptions.HttpResponseError: @@ -2466,6 +2714,7 @@ def get_sas_uri( } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -2483,7 +2732,7 @@ def get_sas_uri( raise HttpResponseError(response=response) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.SasUriResponse, response.json()) @@ -2491,3 +2740,194 @@ def get_sas_uri( return cls(pipeline_response, deserialized, {}) # type: ignore return deserialized # type: ignore + + +class ServicesSuiteOffersOperations: # pylint: disable=docstring-missing-param + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.quantum.WorkspaceClient`'s + :attr:`suite_offers` attribute. + """ + + def __init__(self, *args, **kwargs) -> None: + input_args = list(args) + self._client: PipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") + self._config: WorkspaceClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") + self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") + self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") + + @distributed_trace + @api_version_validation( + method_added_on="2026-01-15-preview", + params_added_on={"2026-01-15-preview": ["api_version", "subscription_id", "provider_id", "accept"]}, + api_versions_list=["2026-01-15-preview"], + ) + def list_quota_usages( + self, subscription_id: str, provider_id: str, **kwargs: Any + ) -> ItemPaged["_models.QuotaUsage"]: + """List quota usages for the given suite offer provider in the subscription. + + :param subscription_id: The Azure subscription ID. Required. + :type subscription_id: str + :param provider_id: The unique identifier for the provider. Required. + :type provider_id: str + :return: An iterator like instance of QuotaUsage + :rtype: ~azure.core.paging.ItemPaged[~azure.quantum.models.QuotaUsage] + :raises ~azure.core.exceptions.HttpResponseError: + """ + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[list[_models.QuotaUsage]] = kwargs.pop("cls", None) + + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + def prepare_request(next_link=None): + if not next_link: + + _request = build_services_suite_offers_list_quota_usages_request( + subscription_id=subscription_id, + provider_id=provider_id, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url( + "self._config.endpoint", self._config.endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + else: + # make call to next link with the client's api-version + _parsed_next_link = urllib.parse.urlparse(next_link) + _next_request_params = case_insensitive_dict( + { + key: [urllib.parse.quote(v) for v in value] + for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items() + } + ) + _next_request_params["api-version"] = self._config.api_version + _request = HttpRequest( + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + headers=_headers, + params=_next_request_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url( + "self._config.endpoint", self._config.endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + return _request + + def extract_data(pipeline_response): + deserialized = pipeline_response.http_response.json() + values = deserialized if isinstance(deserialized, list) else deserialized.get("value", []) + list_of_elem = _deserialize( + list[_models.QuotaUsage], + values, + ) + if cls: + list_of_elem = cls(list_of_elem) # type: ignore + next_link = None if isinstance(deserialized, list) else deserialized.get("nextLink") or None + return next_link, iter(list_of_elem) + + def get_next(next_link=None): + _request = prepare_request(next_link) + + _stream = False + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + response = pipeline_response.http_response + + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) + + return pipeline_response + + return ItemPaged(get_next, extract_data) + + @distributed_trace + @api_version_validation( + method_added_on="2026-01-15-preview", + params_added_on={"2026-01-15-preview": ["api_version", "subscription_id", "provider_id", "accept"]}, + api_versions_list=["2026-01-15-preview"], + ) + def get_provider_status(self, subscription_id: str, provider_id: str, **kwargs: Any) -> _models.ProviderStatus: + """Get the provider status, including target statuses, for the given suite offer provider in the + subscription. + + :param subscription_id: The Azure subscription ID. Required. + :type subscription_id: str + :param provider_id: The unique identifier for the provider. Required. + :type provider_id: str + :return: ProviderStatus. The ProviderStatus is compatible with MutableMapping + :rtype: ~azure.quantum.models.ProviderStatus + :raises ~azure.core.exceptions.HttpResponseError: + """ + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[_models.ProviderStatus] = kwargs.pop("cls", None) + + _request = build_services_suite_offers_get_provider_status_request( + subscription_id=subscription_id, + provider_id=provider_id, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + if _stream: + try: + response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) + + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.ProviderStatus, response.json()) + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore diff --git a/azure-quantum/azure/quantum/_client/operations/_patch.py b/azure-quantum/azure/quantum/_client/operations/_patch.py index 87676c65..ea765788 100644 --- a/azure-quantum/azure/quantum/_client/operations/_patch.py +++ b/azure-quantum/azure/quantum/_client/operations/_patch.py @@ -8,7 +8,6 @@ Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ - __all__: list[str] = [] # Add all objects you want publicly available to users at this package level diff --git a/azure-quantum/azure/quantum/_client/types.py b/azure-quantum/azure/quantum/_client/types.py new file mode 100644 index 00000000..fc304f33 --- /dev/null +++ b/azure-quantum/azure/quantum/_client/types.py @@ -0,0 +1,431 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) Python Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- + +from typing import Any, Literal, TYPE_CHECKING, Union +from typing_extensions import Required, TypedDict + +from azure.core.exceptions import ODataV4Format + +from .models._enums import ItemType + +if TYPE_CHECKING: + from .models import CreatedByType, JobStatus, JobType, Priority, SessionJobFailurePolicy, SessionStatus + + +class BlobDetails(TypedDict, total=False): + """The details (name and container) of the blob to store or download data. + + :ivar containerName: The container name. Required. + :vartype containerName: str + :ivar blobName: The blob name. + :vartype blobName: str + """ + + containerName: Required[str] + """The container name. Required.""" + blobName: str + """The blob name.""" + + +class CostEstimate(TypedDict, total=False): + """The job cost billed by the provider. The final cost on your bill might be slightly different + due to added taxes and currency conversion rates. + + :ivar currencyCode: The currency code. Required. + :vartype currencyCode: str + :ivar events: List of usage events. + :vartype events: list["UsageEvent"] + :ivar estimatedTotal: The estimated total. Required. + :vartype estimatedTotal: float + """ + + currencyCode: Required[str] + """The currency code. Required.""" + events: list["UsageEvent"] + """List of usage events.""" + estimatedTotal: Required[float] + """The estimated total. Required.""" + + +class InnerError(TypedDict, total=False): + """An object containing more specific information about the error. As per Azure REST API + guidelines - `https://aka.ms/AzureRestApiGuidelines#handling-errors + `_. + + :ivar code: One of a server-defined set of error codes. + :vartype code: str + :ivar innererror: Inner error. + :vartype innererror: "InnerError" + """ + + code: str + """One of a server-defined set of error codes.""" + innererror: "InnerError" + """Inner error.""" + + +class JobDetails(TypedDict, total=False): + """A job to be run in the workspace. + + :ivar name: The name of the item. It is not required for the name to be unique and it's only + used for display purposes. Required. + :vartype name: str + :ivar providerId: The unique identifier for the provider. Required. + :vartype providerId: str + :ivar target: The target identifier to run the job. Required. + :vartype target: str + :ivar creationTime: The creation time of the item. + :vartype creationTime: str + :ivar createdBy: The identity that created the item. + :vartype createdBy: str + :ivar createdByType: The type of identity that created the item. Known values are: "User", + "Application", "ManagedIdentity", and "Key". + :vartype createdByType: Union[str, "CreatedByType"] + :ivar lastModifiedTime: The timestamp of the item last modification initiated by the customer. + :vartype lastModifiedTime: str + :ivar lastModifiedBy: The identity that last modified the item. + :vartype lastModifiedBy: str + :ivar lastModifiedByType: The type of identity that last modified the item. Known values are: + "User", "Application", "ManagedIdentity", and "Key". + :vartype lastModifiedByType: Union[str, "CreatedByType"] + :ivar lastUpdatedTime: The last time the item was updated by the system. + :vartype lastUpdatedTime: str + :ivar beginExecutionTime: The time when the item began execution. + :vartype beginExecutionTime: str + :ivar endExecutionTime: The time when the item finished execution. + :vartype endExecutionTime: str + :ivar costEstimate: Cost estimate. + :vartype costEstimate: "CostEstimate" + :ivar errorData: Error information. + :vartype errorData: "WorkspaceItemError" + :ivar priority: Priority of job or session. Known values are: "Standard" and "High". + :vartype priority: Union[str, "Priority"] + :ivar tags: List of user-supplied tags associated with the job. + :vartype tags: list[str] + :ivar usage: Resource consumption metrics containing provider-specific usage data such as + execution time, quantum shots consumed etc. + :vartype usage: "Usage" + :ivar id: Id of the job. Required. + :vartype id: str + :ivar itemType: Type of the Quantum Workspace item is Job. Required. A program, problem, or + application submitted for processing. + :vartype itemType: Literal[ItemType.JOB] + :ivar jobType: The type of job. Known values are: "Unknown", "QuantumComputing", and + "Optimization". + :vartype jobType: Union[str, "JobType"] + :ivar sessionId: The ID of the session that the job is part of. + :vartype sessionId: str + :ivar containerUri: The blob container SAS uri, the container is used to host job data. + Required. + :vartype containerUri: str + :ivar inputDataUri: The input blob URI, if specified, it will override the default input blob + in the container. + :vartype inputDataUri: str + :ivar inputDataFormat: The format of the input data. + :vartype inputDataFormat: str + :ivar status: The status of the job. Known values are: "Queued", "Waiting", "Executing", + "CancellationRequested", "Cancelling", "Finishing", "Completed", "Succeeded", "Failed", and + "Cancelled". + :vartype status: Union[str, "JobStatus"] + :ivar metadata: The job metadata. Metadata provides client the ability to store client-specific + information. + :vartype metadata: Any + :ivar cancellationTime: The time when a job was successfully cancelled. + :vartype cancellationTime: str + :ivar quantumComputingData: Quantum computing data. + :vartype quantumComputingData: "QuantumComputingData" + :ivar inputParams: The input parameters for the job. JSON object used by the target solver. It + is expected that the size of this object is small and only used to specify parameters for the + execution target, not the input data. + :vartype inputParams: Any + :ivar outputDataUri: The output blob uri. When a job finishes successfully, results will be + uploaded to this blob. + :vartype outputDataUri: str + :ivar outputDataFormat: The format of the output data. + :vartype outputDataFormat: str + """ + + name: Required[str] + """The name of the item. It is not required for the name to be unique and it's only used for + display purposes. Required.""" + providerId: Required[str] + """The unique identifier for the provider. Required.""" + target: Required[str] + """The target identifier to run the job. Required.""" + creationTime: str + """The creation time of the item.""" + createdBy: str + """The identity that created the item.""" + createdByType: Union[str, "CreatedByType"] + """The type of identity that created the item. Known values are: \"User\", \"Application\", + \"ManagedIdentity\", and \"Key\".""" + lastModifiedTime: str + """The timestamp of the item last modification initiated by the customer.""" + lastModifiedBy: str + """The identity that last modified the item.""" + lastModifiedByType: Union[str, "CreatedByType"] + """The type of identity that last modified the item. Known values are: \"User\", \"Application\", + \"ManagedIdentity\", and \"Key\".""" + lastUpdatedTime: str + """The last time the item was updated by the system.""" + beginExecutionTime: str + """The time when the item began execution.""" + endExecutionTime: str + """The time when the item finished execution.""" + costEstimate: "CostEstimate" + """Cost estimate.""" + errorData: "WorkspaceItemError" + """Error information.""" + priority: Union[str, "Priority"] + """Priority of job or session. Known values are: \"Standard\" and \"High\".""" + tags: list[str] + """List of user-supplied tags associated with the job.""" + usage: "Usage" + """Resource consumption metrics containing provider-specific usage data such as execution time, + quantum shots consumed etc.""" + id: Required[str] + """Id of the job. Required.""" + itemType: Required[Literal[ItemType.JOB]] + """Type of the Quantum Workspace item is Job. Required. A program, problem, or application + submitted for processing.""" + jobType: Union[str, "JobType"] + """The type of job. Known values are: \"Unknown\", \"QuantumComputing\", and \"Optimization\".""" + sessionId: str + """The ID of the session that the job is part of.""" + containerUri: Required[str] + """The blob container SAS uri, the container is used to host job data. Required.""" + inputDataUri: str + """The input blob URI, if specified, it will override the default input blob in the container.""" + inputDataFormat: str + """The format of the input data.""" + status: Union[str, "JobStatus"] + """The status of the job. Known values are: \"Queued\", \"Waiting\", \"Executing\", + \"CancellationRequested\", \"Cancelling\", \"Finishing\", \"Completed\", \"Succeeded\", + \"Failed\", and \"Cancelled\".""" + metadata: Any + """The job metadata. Metadata provides client the ability to store client-specific information.""" + cancellationTime: str + """The time when a job was successfully cancelled.""" + quantumComputingData: "QuantumComputingData" + """Quantum computing data.""" + inputParams: Any + """The input parameters for the job. JSON object used by the target solver. It is expected that + the size of this object is small and only used to specify parameters for the execution target, + not the input data.""" + outputDataUri: str + """The output blob uri. When a job finishes successfully, results will be uploaded to this blob.""" + outputDataFormat: str + """The format of the output data.""" + + +class JobUpdateOptions(TypedDict, total=False): + """Options for updating a job. + + :ivar id: Id of the job. Required. + :vartype id: str + :ivar priority: Priority of job. Known values are: "Standard" and "High". + :vartype priority: Union[str, "Priority"] + :ivar name: The name of the job. + :vartype name: str + :ivar tags: List of user-supplied tags associated with the job. + :vartype tags: list[str] + """ + + id: Required[str] + """Id of the job. Required.""" + priority: Union[str, "Priority"] + """Priority of job. Known values are: \"Standard\" and \"High\".""" + name: str + """The name of the job.""" + tags: list[str] + """List of user-supplied tags associated with the job.""" + + +class QuantumComputingData(TypedDict, total=False): + """Quantum computing data. + + :ivar count: The number of quantum computing items in the job. Required. + :vartype count: int + """ + + count: Required[int] + """The number of quantum computing items in the job. Required.""" + + +class SessionDetails(TypedDict, total=False): + """Session, a logical grouping of jobs. + + :ivar name: The name of the item. It is not required for the name to be unique and it's only + used for display purposes. Required. + :vartype name: str + :ivar providerId: The unique identifier for the provider. Required. + :vartype providerId: str + :ivar target: The target identifier to run the job. Required. + :vartype target: str + :ivar creationTime: The creation time of the item. + :vartype creationTime: str + :ivar createdBy: The identity that created the item. + :vartype createdBy: str + :ivar createdByType: The type of identity that created the item. Known values are: "User", + "Application", "ManagedIdentity", and "Key". + :vartype createdByType: Union[str, "CreatedByType"] + :ivar lastModifiedTime: The timestamp of the item last modification initiated by the customer. + :vartype lastModifiedTime: str + :ivar lastModifiedBy: The identity that last modified the item. + :vartype lastModifiedBy: str + :ivar lastModifiedByType: The type of identity that last modified the item. Known values are: + "User", "Application", "ManagedIdentity", and "Key". + :vartype lastModifiedByType: Union[str, "CreatedByType"] + :ivar lastUpdatedTime: The last time the item was updated by the system. + :vartype lastUpdatedTime: str + :ivar beginExecutionTime: The time when the item began execution. + :vartype beginExecutionTime: str + :ivar endExecutionTime: The time when the item finished execution. + :vartype endExecutionTime: str + :ivar costEstimate: Cost estimate. + :vartype costEstimate: "CostEstimate" + :ivar errorData: Error information. + :vartype errorData: "WorkspaceItemError" + :ivar priority: Priority of job or session. Known values are: "Standard" and "High". + :vartype priority: Union[str, "Priority"] + :ivar tags: List of user-supplied tags associated with the job. + :vartype tags: list[str] + :ivar usage: Resource consumption metrics containing provider-specific usage data such as + execution time, quantum shots consumed etc. + :vartype usage: "Usage" + :ivar id: Id of the session. Required. + :vartype id: str + :ivar itemType: Type of the Quantum Workspace item is Session. Required. A logical grouping of + jobs. + :vartype itemType: Literal[ItemType.SESSION] + :ivar jobFailurePolicy: Policy controlling the behavior of the Session when a job in the + session fails. Required. Known values are: "Abort" and "Continue". + :vartype jobFailurePolicy: Union[str, "SessionJobFailurePolicy"] + :ivar status: The status of the session. Known values are: "Waiting", "Executing", "Succeeded", + "Failed", "Failure(s)", and "TimedOut". + :vartype status: Union[str, "SessionStatus"] + """ + + name: Required[str] + """The name of the item. It is not required for the name to be unique and it's only used for + display purposes. Required.""" + providerId: Required[str] + """The unique identifier for the provider. Required.""" + target: Required[str] + """The target identifier to run the job. Required.""" + creationTime: str + """The creation time of the item.""" + createdBy: str + """The identity that created the item.""" + createdByType: Union[str, "CreatedByType"] + """The type of identity that created the item. Known values are: \"User\", \"Application\", + \"ManagedIdentity\", and \"Key\".""" + lastModifiedTime: str + """The timestamp of the item last modification initiated by the customer.""" + lastModifiedBy: str + """The identity that last modified the item.""" + lastModifiedByType: Union[str, "CreatedByType"] + """The type of identity that last modified the item. Known values are: \"User\", \"Application\", + \"ManagedIdentity\", and \"Key\".""" + lastUpdatedTime: str + """The last time the item was updated by the system.""" + beginExecutionTime: str + """The time when the item began execution.""" + endExecutionTime: str + """The time when the item finished execution.""" + costEstimate: "CostEstimate" + """Cost estimate.""" + errorData: "WorkspaceItemError" + """Error information.""" + priority: Union[str, "Priority"] + """Priority of job or session. Known values are: \"Standard\" and \"High\".""" + tags: list[str] + """List of user-supplied tags associated with the job.""" + usage: "Usage" + """Resource consumption metrics containing provider-specific usage data such as execution time, + quantum shots consumed etc.""" + id: Required[str] + """Id of the session. Required.""" + itemType: Required[Literal[ItemType.SESSION]] + """Type of the Quantum Workspace item is Session. Required. A logical grouping of jobs.""" + jobFailurePolicy: Required[Union[str, "SessionJobFailurePolicy"]] + """Policy controlling the behavior of the Session when a job in the session fails. Required. Known + values are: \"Abort\" and \"Continue\".""" + status: Union[str, "SessionStatus"] + """The status of the session. Known values are: \"Waiting\", \"Executing\", \"Succeeded\", + \"Failed\", \"Failure(s)\", and \"TimedOut\".""" + + +class Usage(TypedDict, total=False): + """Resource usage metrics represented as key-value pairs. Keys are provider-defined metric names + (e.g. "standardMinutes", "shots") and values are the corresponding consumption amounts. The + specific metrics available depend on the quantum provider and target used. + + """ + + +class UsageEvent(TypedDict, total=False): + """Usage event details. + + :ivar dimensionId: The dimension id. Required. + :vartype dimensionId: str + :ivar dimensionName: The dimension name. Required. + :vartype dimensionName: str + :ivar measureUnit: The unit of measure. Required. + :vartype measureUnit: str + :ivar amountBilled: The amount billed. Required. + :vartype amountBilled: float + :ivar amountConsumed: The amount consumed. Required. + :vartype amountConsumed: float + :ivar unitPrice: The unit price. Required. + :vartype unitPrice: float + """ + + dimensionId: Required[str] + """The dimension id. Required.""" + dimensionName: Required[str] + """The dimension name. Required.""" + measureUnit: Required[str] + """The unit of measure. Required.""" + amountBilled: Required[float] + """The amount billed. Required.""" + amountConsumed: Required[float] + """The amount consumed. Required.""" + unitPrice: Required[float] + """The unit price. Required.""" + + +class WorkspaceItemError(TypedDict, total=False): + """The error object. + + :ivar code: One of a server-defined set of error codes. Required. + :vartype code: str + :ivar message: A human-readable representation of the error. Required. + :vartype message: str + :ivar target: The target of the error. + :vartype target: str + :ivar details: An array of details about specific errors that led to this reported error. + :vartype details: list[ODataV4Format] + :ivar innererror: An object containing more specific information than the current object about + the error. + :vartype innererror: "InnerError" + """ + + code: Required[str] + """One of a server-defined set of error codes. Required.""" + message: Required[str] + """A human-readable representation of the error. Required.""" + target: str + """The target of the error.""" + details: list[ODataV4Format] + """An array of details about specific errors that led to this reported error.""" + innererror: "InnerError" + """An object containing more specific information than the current object about the error.""" + + +ItemDetails = Union[JobDetails, SessionDetails]