diff --git a/sdk/contentsafety/azure-ai-contentsafety/_meta.json b/sdk/contentsafety/azure-ai-contentsafety/_meta.json new file mode 100644 index 000000000000..3abdbbc93f78 --- /dev/null +++ b/sdk/contentsafety/azure-ai-contentsafety/_meta.json @@ -0,0 +1,6 @@ +{ + "commit": "df98780fe1e53713c0f5c3785409c1e07529355b", + "repository_url": "https://github.com/test-repo-billy/azure-rest-api-specs", + "typespec_src": "specification/cognitiveservices/ContentSafety", + "@azure-tools/typespec-python": "0.37.1" +} \ No newline at end of file diff --git a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/__init__.py b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/__init__.py index 7eee7d8f0096..6d67f6a6204b 100644 --- a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/__init__.py +++ b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/__init__.py @@ -5,16 +5,22 @@ # Code generated by Microsoft (R) Python Code Generator. # Changes may cause incorrect behavior and will be lost if the code is regenerated. # -------------------------------------------------------------------------- +# pylint: disable=wrong-import-position -from ._client import ContentSafetyClient -from ._client import BlocklistClient +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from ._patch import * # pylint: disable=unused-wildcard-import + +from ._client import ContentSafetyClient # type: ignore +from ._client import BlocklistClient # type: ignore from ._version import VERSION __version__ = VERSION try: from ._patch import __all__ as _patch_all - from ._patch import * # pylint: disable=unused-wildcard-import + from ._patch import * except ImportError: _patch_all = [] from ._patch import patch_sdk as _patch_sdk @@ -23,6 +29,6 @@ "ContentSafetyClient", "BlocklistClient", ] -__all__.extend([p for p in _patch_all if p not in __all__]) +__all__.extend([p for p in _patch_all if p not in __all__]) # pyright: ignore _patch_sdk() diff --git a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/_client.py b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/_client.py index e2e1f2849fe6..100c4dd6ecd4 100644 --- a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/_client.py +++ b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/_client.py @@ -8,6 +8,7 @@ from copy import deepcopy from typing import Any, TYPE_CHECKING, Union +from typing_extensions import Self from azure.core import PipelineClient from azure.core.credentials import AzureKeyCredential @@ -19,21 +20,20 @@ from ._serialization import Deserializer, Serializer if TYPE_CHECKING: - # pylint: disable=unused-import,ungrouped-imports from azure.core.credentials import TokenCredential -class ContentSafetyClient(ContentSafetyClientOperationsMixin): # pylint: disable=client-accepts-api-version-keyword +class ContentSafetyClient(ContentSafetyClientOperationsMixin): """ContentSafetyClient. :param endpoint: Supported Cognitive Services endpoints (protocol and hostname, for example: https://:code:``.cognitiveservices.azure.com). Required. :type endpoint: str - :param credential: Credential needed for the client to connect to Azure. Is either a + :param credential: Credential used to authenticate requests to the service. Is either a AzureKeyCredential type or a TokenCredential type. Required. :type credential: ~azure.core.credentials.AzureKeyCredential or ~azure.core.credentials.TokenCredential - :keyword api_version: The API version to use for this operation. Default value is "2023-10-01". + :keyword api_version: The API version to use for this operation. Default value is "2024-09-01". Note that overriding this default value may result in unsupported behavior. :paramtype api_version: str """ @@ -84,7 +84,7 @@ def send_request(self, request: HttpRequest, *, stream: bool = False, **kwargs: request_copy = deepcopy(request) path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str"), } request_copy.url = self._client.format_url(request_copy.url, **path_format_arguments) @@ -93,7 +93,7 @@ def send_request(self, request: HttpRequest, *, stream: bool = False, **kwargs: def close(self) -> None: self._client.close() - def __enter__(self) -> "ContentSafetyClient": + def __enter__(self) -> Self: self._client.__enter__() return self @@ -101,17 +101,17 @@ def __exit__(self, *exc_details: Any) -> None: self._client.__exit__(*exc_details) -class BlocklistClient(BlocklistClientOperationsMixin): # pylint: disable=client-accepts-api-version-keyword +class BlocklistClient(BlocklistClientOperationsMixin): """BlocklistClient. :param endpoint: Supported Cognitive Services endpoints (protocol and hostname, for example: https://:code:``.cognitiveservices.azure.com). Required. :type endpoint: str - :param credential: Credential needed for the client to connect to Azure. Is either a + :param credential: Credential used to authenticate requests to the service. Is either a AzureKeyCredential type or a TokenCredential type. Required. :type credential: ~azure.core.credentials.AzureKeyCredential or ~azure.core.credentials.TokenCredential - :keyword api_version: The API version to use for this operation. Default value is "2023-10-01". + :keyword api_version: The API version to use for this operation. Default value is "2024-09-01". Note that overriding this default value may result in unsupported behavior. :paramtype api_version: str """ @@ -162,7 +162,7 @@ def send_request(self, request: HttpRequest, *, stream: bool = False, **kwargs: request_copy = deepcopy(request) path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str"), } request_copy.url = self._client.format_url(request_copy.url, **path_format_arguments) @@ -171,7 +171,7 @@ def send_request(self, request: HttpRequest, *, stream: bool = False, **kwargs: def close(self) -> None: self._client.close() - def __enter__(self) -> "BlocklistClient": + def __enter__(self) -> Self: self._client.__enter__() return self diff --git a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/_configuration.py b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/_configuration.py index 9d9a723f0355..717cf38fbc1b 100644 --- a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/_configuration.py +++ b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/_configuration.py @@ -14,11 +14,10 @@ from ._version import VERSION if TYPE_CHECKING: - # pylint: disable=unused-import,ungrouped-imports from azure.core.credentials import TokenCredential -class ContentSafetyClientConfiguration: # pylint: disable=too-many-instance-attributes,name-too-long +class ContentSafetyClientConfiguration: # pylint: disable=too-many-instance-attributes """Configuration for ContentSafetyClient. Note that all parameters used to create this instance are saved as instance @@ -27,17 +26,17 @@ class ContentSafetyClientConfiguration: # pylint: disable=too-many-instance-att :param endpoint: Supported Cognitive Services endpoints (protocol and hostname, for example: https://:code:``.cognitiveservices.azure.com). Required. :type endpoint: str - :param credential: Credential needed for the client to connect to Azure. Is either a + :param credential: Credential used to authenticate requests to the service. Is either a AzureKeyCredential type or a TokenCredential type. Required. :type credential: ~azure.core.credentials.AzureKeyCredential or ~azure.core.credentials.TokenCredential - :keyword api_version: The API version to use for this operation. Default value is "2023-10-01". + :keyword api_version: The API version to use for this operation. Default value is "2024-09-01". Note that overriding this default value may result in unsupported behavior. :paramtype api_version: str """ def __init__(self, endpoint: str, credential: Union[AzureKeyCredential, "TokenCredential"], **kwargs: Any) -> None: - api_version: str = kwargs.pop("api_version", "2023-10-01") + api_version: str = kwargs.pop("api_version", "2024-09-01") if endpoint is None: raise ValueError("Parameter 'endpoint' must not be None.") @@ -73,7 +72,7 @@ def _configure(self, **kwargs: Any) -> None: self.authentication_policy = self._infer_policy(**kwargs) -class BlocklistClientConfiguration: # pylint: disable=too-many-instance-attributes,name-too-long +class BlocklistClientConfiguration: # pylint: disable=too-many-instance-attributes """Configuration for BlocklistClient. Note that all parameters used to create this instance are saved as instance @@ -82,17 +81,17 @@ class BlocklistClientConfiguration: # pylint: disable=too-many-instance-attribu :param endpoint: Supported Cognitive Services endpoints (protocol and hostname, for example: https://:code:``.cognitiveservices.azure.com). Required. :type endpoint: str - :param credential: Credential needed for the client to connect to Azure. Is either a + :param credential: Credential used to authenticate requests to the service. Is either a AzureKeyCredential type or a TokenCredential type. Required. :type credential: ~azure.core.credentials.AzureKeyCredential or ~azure.core.credentials.TokenCredential - :keyword api_version: The API version to use for this operation. Default value is "2023-10-01". + :keyword api_version: The API version to use for this operation. Default value is "2024-09-01". Note that overriding this default value may result in unsupported behavior. :paramtype api_version: str """ def __init__(self, endpoint: str, credential: Union[AzureKeyCredential, "TokenCredential"], **kwargs: Any) -> None: - api_version: str = kwargs.pop("api_version", "2023-10-01") + api_version: str = kwargs.pop("api_version", "2024-09-01") if endpoint is None: raise ValueError("Parameter 'endpoint' must not be None.") diff --git a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/_model_base.py b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/_model_base.py index aec056aba7b7..7f73b97b23ef 100644 --- a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/_model_base.py +++ b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/_model_base.py @@ -1,12 +1,13 @@ +# pylint: disable=too-many-lines # 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. # -------------------------------------------------------------------------- -# pylint: disable=protected-access, arguments-differ, signature-differs, broad-except -# pyright: reportGeneralTypeIssues=false +# pylint: disable=protected-access, broad-except +import copy import calendar import decimal import functools @@ -14,11 +15,13 @@ import logging import base64 import re -import copy import typing -import email +import enum +import email.utils from datetime import datetime, date, time, timedelta, timezone from json import JSONEncoder +import xml.etree.ElementTree as ET +from typing_extensions import Self import isodate from azure.core.exceptions import DeserializationError from azure.core import CaseInsensitiveEnumMeta @@ -35,6 +38,7 @@ __all__ = ["SdkJSONEncoder", "Model", "rest_field", "rest_discriminator"] TZ_UTC = timezone.utc +_T = typing.TypeVar("_T") def _timedelta_as_isostr(td: timedelta) -> str: @@ -121,7 +125,7 @@ def _serialize_datetime(o, format: typing.Optional[str] = None): def _is_readonly(p): try: - return p._visibility == ["read"] # pylint: disable=protected-access + return p._visibility == ["read"] except AttributeError: return False @@ -242,7 +246,7 @@ def _deserialize_date(attr: typing.Union[str, date]) -> date: # This must NOT use defaultmonth/defaultday. Using None ensure this raises an exception. if isinstance(attr, date): return attr - return isodate.parse_date(attr, defaultmonth=None, defaultday=None) + return isodate.parse_date(attr, defaultmonth=None, defaultday=None) # type: ignore def _deserialize_time(attr: typing.Union[str, time]) -> time: @@ -284,6 +288,12 @@ def _deserialize_decimal(attr): return decimal.Decimal(str(attr)) +def _deserialize_int_as_str(attr): + if isinstance(attr, int): + return attr + return int(attr) + + _DESERIALIZE_MAPPING = { datetime: _deserialize_datetime, date: _deserialize_date, @@ -305,9 +315,11 @@ def _deserialize_decimal(attr): 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 rf and rf._format: return _DESERIALIZE_MAPPING_WITHFORMAT.get(rf._format) - return _DESERIALIZE_MAPPING.get(annotation) + return _DESERIALIZE_MAPPING.get(annotation) # pyright: ignore def _get_type_alias_type(module_name: str, alias_name: str): @@ -337,7 +349,7 @@ def _get_model(module_name: str, model_name: str): class _MyMutableMapping(MutableMapping[str, typing.Any]): # pylint: disable=unsubscriptable-object def __init__(self, data: typing.Dict[str, typing.Any]) -> None: - self._data = copy.deepcopy(data) + self._data = data def __contains__(self, key: typing.Any) -> bool: return key in self._data @@ -375,13 +387,14 @@ def get(self, key: str, default: typing.Any = None) -> typing.Any: except KeyError: return default - @typing.overload # type: ignore - def pop(self, key: str) -> typing.Any: # pylint: disable=no-member - ... + @typing.overload + def pop(self, key: str) -> typing.Any: ... + + @typing.overload + def pop(self, key: str, default: _T) -> _T: ... @typing.overload - def pop(self, key: str, default: typing.Any) -> typing.Any: - ... + def pop(self, key: str, default: typing.Any) -> typing.Any: ... def pop(self, key: str, default: typing.Any = _UNSET) -> typing.Any: if default is _UNSET: @@ -397,13 +410,11 @@ def clear(self) -> None: def update(self, *args: typing.Any, **kwargs: typing.Any) -> None: self._data.update(*args, **kwargs) - @typing.overload # type: ignore - def setdefault(self, key: str) -> typing.Any: - ... + @typing.overload + def setdefault(self, key: str, default: None = None) -> None: ... @typing.overload - def setdefault(self, key: str, default: typing.Any) -> typing.Any: - ... + def setdefault(self, key: str, default: typing.Any) -> typing.Any: ... def setdefault(self, key: str, default: typing.Any = _UNSET) -> typing.Any: if default is _UNSET: @@ -438,6 +449,12 @@ def _serialize(o, format: typing.Optional[str] = None): # pylint: disable=too-m return _serialize_bytes(o, format) if isinstance(o, decimal.Decimal): return float(o) + if isinstance(o, enum.Enum): + return o.value + if isinstance(o, int): + if format == "str": + return str(o) + return o try: # First try datetime.datetime return _serialize_datetime(o, format) @@ -462,11 +479,22 @@ def _get_rest_field( def _create_value(rf: typing.Optional["_RestField"], value: typing.Any) -> typing.Any: - return _deserialize(rf._type, value) if (rf and rf._is_model) else _serialize(value, rf._format if rf else None) + if not rf: + return _serialize(value, None) + if rf._is_multipart_file_input: + return value + if rf._is_model: + return _deserialize(rf._type, value) + if isinstance(value, ET.Element): + value = _deserialize(rf._type, value) + return _serialize(value, rf._format) class Model(_MyMutableMapping): _is_model = True + # label whether current class's _attr_to_rest_field has been calculated + # could not see _attr_to_rest_field directly because subclass inherits it from parent class + _calculated: typing.Set[str] = set() def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None: class_name = self.__class__.__name__ @@ -477,10 +505,58 @@ def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None: for rest_field in self._attr_to_rest_field.values() if rest_field._default is not _UNSET } - if args: - dict_to_pass.update( - {k: _create_value(_get_rest_field(self._attr_to_rest_field, k), v) for k, v in args[0].items()} - ) + if args: # pylint: disable=too-many-nested-blocks + 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) + 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()} + ) else: non_attr_kwargs = [k for k in kwargs if k not in self._attr_to_rest_field] if non_attr_kwargs: @@ -498,55 +574,70 @@ def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None: def copy(self) -> "Model": return Model(self.__dict__) - def __new__(cls, *args: typing.Any, **kwargs: typing.Any) -> "Model": # pylint: disable=unused-argument - # we know the last three classes in mro are going to be 'Model', 'dict', and 'object' - mros = cls.__mro__[:-3][::-1] # ignore model, dict, and object parents, and reverse the mro order - attr_to_rest_field: typing.Dict[str, _RestField] = { # map attribute name to rest_field property - k: v for mro_class in mros for k, v in mro_class.__dict__.items() if k[0] != "_" and hasattr(v, "_type") - } - annotations = { - k: v - for mro_class in mros - if hasattr(mro_class, "__annotations__") # pylint: disable=no-member - for k, v in mro_class.__annotations__.items() # pylint: disable=no-member - } - for attr, rf in attr_to_rest_field.items(): - rf._module = cls.__module__ - if not rf._type: - rf._type = rf._get_deserialize_callable_from_annotation(annotations.get(attr, None)) - if not rf._rest_name_input: - rf._rest_name_input = attr - cls._attr_to_rest_field: typing.Dict[str, _RestField] = dict(attr_to_rest_field.items()) + def __new__(cls, *args: typing.Any, **kwargs: typing.Any) -> Self: + if f"{cls.__module__}.{cls.__qualname__}" not in cls._calculated: + # we know the last nine classes in mro are going to be 'Model', '_MyMutableMapping', 'MutableMapping', + # 'Mapping', 'Collection', 'Sized', 'Iterable', 'Container' and 'object' + mros = cls.__mro__[:-9][::-1] # ignore parents, and reverse the mro order + attr_to_rest_field: typing.Dict[str, _RestField] = { # map attribute name to rest_field property + k: v for mro_class in mros for k, v in mro_class.__dict__.items() if k[0] != "_" and hasattr(v, "_type") + } + annotations = { + k: v + for mro_class in mros + if hasattr(mro_class, "__annotations__") + for k, v in mro_class.__annotations__.items() + } + for attr, rf in attr_to_rest_field.items(): + rf._module = cls.__module__ + if not rf._type: + rf._type = rf._get_deserialize_callable_from_annotation(annotations.get(attr, None)) + if not rf._rest_name_input: + rf._rest_name_input = attr + cls._attr_to_rest_field: typing.Dict[str, _RestField] = dict(attr_to_rest_field.items()) + cls._calculated.add(f"{cls.__module__}.{cls.__qualname__}") return super().__new__(cls) # pylint: disable=no-value-for-parameter def __init_subclass__(cls, discriminator: typing.Optional[str] = None) -> None: for base in cls.__bases__: - if hasattr(base, "__mapping__"): # pylint: disable=no-member - base.__mapping__[discriminator or cls.__name__] = cls # type: ignore # pylint: disable=no-member + if hasattr(base, "__mapping__"): + base.__mapping__[discriminator or cls.__name__] = cls # type: ignore @classmethod - def _get_discriminator(cls, exist_discriminators) -> typing.Optional[str]: + def _get_discriminator(cls, exist_discriminators) -> typing.Optional["_RestField"]: for v in cls.__dict__.values(): - if ( - isinstance(v, _RestField) and v._is_discriminator and v._rest_name not in exist_discriminators - ): # pylint: disable=protected-access - return v._rest_name # pylint: disable=protected-access + if isinstance(v, _RestField) and v._is_discriminator and v._rest_name not in exist_discriminators: + return v return None @classmethod def _deserialize(cls, data, exist_discriminators): - if not hasattr(cls, "__mapping__"): # pylint: disable=no-member + if not hasattr(cls, "__mapping__"): return cls(data) discriminator = cls._get_discriminator(exist_discriminators) - exist_discriminators.append(discriminator) - mapped_cls = cls.__mapping__.get(data.get(discriminator), cls) # pylint: disable=no-member - if mapped_cls == cls: + if discriminator is None: return cls(data) - return mapped_cls._deserialize(data, exist_discriminators) # pylint: disable=protected-access + exist_discriminators.append(discriminator._rest_name) + if isinstance(data, ET.Element): + 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)) + if xml_ns: + xml_name = "{" + xml_ns + "}" + xml_name + + if data.get(xml_name) is not None: + discriminator_value = data.get(xml_name) + else: + discriminator_value = data.find(xml_name).text # pyright: ignore + else: + discriminator_value = data.get(discriminator._rest_name) + mapped_cls = cls.__mapping__.get(discriminator_value, cls) # pyright: ignore + return mapped_cls._deserialize(data, exist_discriminators) def as_dict(self, *, exclude_readonly: bool = False) -> typing.Dict[str, typing.Any]: - """Return a dict that can be JSONify using json.dump. + """Return a dict that can be turned into json using json.dump. :keyword bool exclude_readonly: Whether to remove the readonly properties. :returns: A dict JSON compatible object @@ -554,12 +645,20 @@ def as_dict(self, *, exclude_readonly: bool = False) -> typing.Dict[str, typing. """ result = {} + readonly_props = [] if exclude_readonly: readonly_props = [p._rest_name for p in self._attr_to_rest_field.values() if _is_readonly(p)] for k, v in self.items(): - if exclude_readonly and k in readonly_props: # pyright: ignore[reportUnboundVariable] + if exclude_readonly and k in readonly_props: # pyright: ignore continue - result[k] = Model._as_dict_value(v, exclude_readonly=exclude_readonly) + is_multipart_file_input = False + try: + is_multipart_file_input = next( + rf for rf in self._attr_to_rest_field.values() if rf._rest_name == k + )._is_multipart_file_input + except StopIteration: + pass + result[k] = v if is_multipart_file_input else Model._as_dict_value(v, exclude_readonly=exclude_readonly) return result @staticmethod @@ -567,18 +666,80 @@ def _as_dict_value(v: typing.Any, exclude_readonly: bool = False) -> typing.Any: if v is None or isinstance(v, _Null): return None if isinstance(v, (list, tuple, set)): - return [Model._as_dict_value(x, exclude_readonly=exclude_readonly) for x in v] + return type(v)(Model._as_dict_value(x, exclude_readonly=exclude_readonly) for x in v) if isinstance(v, dict): return {dk: Model._as_dict_value(dv, exclude_readonly=exclude_readonly) for dk, dv in v.items()} return v.as_dict(exclude_readonly=exclude_readonly) if hasattr(v, "as_dict") else v -def _get_deserialize_callable_from_annotation( # pylint: disable=R0911, R0915, R0912 +def _deserialize_model(model_deserializer: typing.Optional[typing.Callable], obj): + if _is_model(obj): + return obj + return _deserialize(model_deserializer, obj) + + +def _deserialize_with_optional(if_obj_deserializer: typing.Optional[typing.Callable], obj): + if obj is None: + return obj + return _deserialize_with_callable(if_obj_deserializer, obj) + + +def _deserialize_with_union(deserializers, obj): + for deserializer in deserializers: + try: + return _deserialize(deserializer, obj) + except DeserializationError: + pass + raise DeserializationError() + + +def _deserialize_dict( + value_deserializer: typing.Optional[typing.Callable], + module: typing.Optional[str], + obj: typing.Dict[typing.Any, typing.Any], +): + if obj is None: + return obj + if isinstance(obj, ET.Element): + obj = {child.tag: child for child in obj} + return {k: _deserialize(value_deserializer, v, module) for k, v in obj.items()} + + +def _deserialize_multiple_sequence( + entry_deserializers: typing.List[typing.Optional[typing.Callable]], + module: typing.Optional[str], + obj, +): + if obj is None: + return obj + return type(obj)(_deserialize(deserializer, entry, module) for entry, deserializer in zip(obj, entry_deserializers)) + + +def _deserialize_sequence( + deserializer: typing.Optional[typing.Callable], + module: typing.Optional[str], + obj, +): + if obj is None: + return obj + if isinstance(obj, ET.Element): + obj = list(obj) + return type(obj)(_deserialize(deserializer, entry, module) for entry in obj) + + +def _sorted_annotations(types: typing.List[typing.Any]) -> typing.List[typing.Any]: + return sorted( + types, + key=lambda x: hasattr(x, "__name__") and x.__name__.lower() in ("str", "float", "int", "bool"), + ) + + +def _get_deserialize_callable_from_annotation( # pylint: disable=too-many-return-statements, too-many-branches annotation: typing.Any, module: typing.Optional[str], rf: typing.Optional["_RestField"] = None, ) -> typing.Optional[typing.Callable[[typing.Any], typing.Any]]: - if not annotation or annotation in [int, float]: + if not annotation: return None # is it a type alias? @@ -593,113 +754,76 @@ def _get_deserialize_callable_from_annotation( # pylint: disable=R0911, R0915, except AttributeError: model_name = annotation if module is not None: - annotation = _get_model(module, model_name) + annotation = _get_model(module, model_name) # type: ignore try: if module and _is_model(annotation): if rf: rf._is_model = True - def _deserialize_model(model_deserializer: typing.Optional[typing.Callable], obj): - if _is_model(obj): - return obj - return _deserialize(model_deserializer, obj) - - return functools.partial(_deserialize_model, annotation) + return functools.partial(_deserialize_model, annotation) # pyright: ignore except Exception: pass # is it a literal? try: - if sys.version_info >= (3, 8): - from typing import ( - Literal, - ) # pylint: disable=no-name-in-module, ungrouped-imports - else: - from typing_extensions import Literal # type: ignore # pylint: disable=ungrouped-imports - - if annotation.__origin__ == Literal: + if annotation.__origin__ is typing.Literal: # pyright: ignore return None except AttributeError: pass # is it optional? try: - if any(a for a in annotation.__args__ if a == type(None)): - if_obj_deserializer = _get_deserialize_callable_from_annotation( - next(a for a in annotation.__args__ if a != type(None)), module, rf - ) - - def _deserialize_with_optional(if_obj_deserializer: typing.Optional[typing.Callable], obj): - if obj is None: - return obj - return _deserialize_with_callable(if_obj_deserializer, obj) - - return functools.partial(_deserialize_with_optional, if_obj_deserializer) + if any(a for a in annotation.__args__ if a == type(None)): # pyright: ignore + if len(annotation.__args__) <= 2: # pyright: ignore + if_obj_deserializer = _get_deserialize_callable_from_annotation( + next(a for a in annotation.__args__ if a != type(None)), module, rf # pyright: ignore + ) + + return functools.partial(_deserialize_with_optional, if_obj_deserializer) + # the type is Optional[Union[...]], we need to remove the None type from the Union + annotation_copy = copy.copy(annotation) + annotation_copy.__args__ = [a for a in annotation_copy.__args__ if a != type(None)] # pyright: ignore + return _get_deserialize_callable_from_annotation(annotation_copy, module, rf) except AttributeError: pass + # is it union? if getattr(annotation, "__origin__", None) is typing.Union: - deserializers = [_get_deserialize_callable_from_annotation(arg, module, rf) for arg in annotation.__args__] - - def _deserialize_with_union(deserializers, obj): - for deserializer in deserializers: - try: - return _deserialize(deserializer, obj) - except DeserializationError: - pass - raise DeserializationError() + # initial ordering is we make `string` the last deserialization option, because it is often them most generic + deserializers = [ + _get_deserialize_callable_from_annotation(arg, module, rf) + for arg in _sorted_annotations(annotation.__args__) # pyright: ignore + ] return functools.partial(_deserialize_with_union, deserializers) try: - if annotation._name == "Dict": - value_deserializer = _get_deserialize_callable_from_annotation(annotation.__args__[1], module, rf) - - def _deserialize_dict( - value_deserializer: typing.Optional[typing.Callable], - obj: typing.Dict[typing.Any, typing.Any], - ): - if obj is None: - return obj - return {k: _deserialize(value_deserializer, v, module) for k, v in obj.items()} + if annotation._name == "Dict": # pyright: ignore + value_deserializer = _get_deserialize_callable_from_annotation( + annotation.__args__[1], module, rf # pyright: ignore + ) return functools.partial( _deserialize_dict, value_deserializer, + module, ) except (AttributeError, IndexError): pass try: - if annotation._name in ["List", "Set", "Tuple", "Sequence"]: - if len(annotation.__args__) > 1: - - def _deserialize_multiple_sequence( - entry_deserializers: typing.List[typing.Optional[typing.Callable]], - obj, - ): - if obj is None: - return obj - return type(obj)( - _deserialize(deserializer, entry, module) - for entry, deserializer in zip(obj, entry_deserializers) - ) - + if annotation._name in ["List", "Set", "Tuple", "Sequence"]: # pyright: ignore + if len(annotation.__args__) > 1: # pyright: ignore entry_deserializers = [ - _get_deserialize_callable_from_annotation(dt, module, rf) for dt in annotation.__args__ + _get_deserialize_callable_from_annotation(dt, module, rf) + for dt in annotation.__args__ # pyright: ignore ] - return functools.partial(_deserialize_multiple_sequence, entry_deserializers) - deserializer = _get_deserialize_callable_from_annotation(annotation.__args__[0], module, rf) - - def _deserialize_sequence( - deserializer: typing.Optional[typing.Callable], - obj, - ): - if obj is None: - return obj - return type(obj)(_deserialize(deserializer, entry, module) for entry in obj) - - return functools.partial(_deserialize_sequence, deserializer) + return functools.partial(_deserialize_multiple_sequence, entry_deserializers, module) + deserializer = _get_deserialize_callable_from_annotation( + annotation.__args__[0], module, rf # pyright: ignore + ) + + return functools.partial(_deserialize_sequence, deserializer, module) except (TypeError, IndexError, AttributeError, SyntaxError): pass @@ -709,7 +833,11 @@ def _deserialize_default( ): if obj is None: return obj - return _deserialize_with_callable(deserializer, obj) + try: + return _deserialize_with_callable(deserializer, obj) + except Exception: + pass + return obj if get_deserializer(annotation, rf): return functools.partial(_deserialize_default, get_deserializer(annotation, rf)) @@ -720,12 +848,23 @@ def _deserialize_default( def _deserialize_with_callable( deserializer: typing.Optional[typing.Callable[[typing.Any], typing.Any]], value: typing.Any, -): +): # pylint: disable=too-many-return-statements try: if value is None or isinstance(value, _Null): return None + if isinstance(value, ET.Element): + if deserializer is str: + return value.text or "" + if deserializer is int: + return int(value.text) if value.text else None + if deserializer is float: + return float(value.text) if value.text else None + if deserializer is bool: + return value.text == "true" 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) @@ -755,6 +894,22 @@ def _deserialize( return _deserialize_with_callable(deserializer, value) +def _failsafe_deserialize( + deserializer: typing.Any, + value: typing.Any, + module: typing.Optional[str] = None, + rf: typing.Optional["_RestField"] = None, + format: typing.Optional[str] = None, +) -> typing.Any: + try: + return _deserialize(deserializer, value, module, rf, format) + except DeserializationError: + _LOGGER.warning( + "Ran into a deserialization error. Ignoring since this is failsafe deserialization", exc_info=True + ) + return None + + class _RestField: def __init__( self, @@ -765,6 +920,8 @@ def __init__( visibility: typing.Optional[typing.List[str]] = None, default: typing.Any = _UNSET, format: typing.Optional[str] = None, + is_multipart_file_input: bool = False, + xml: typing.Optional[typing.Dict[str, typing.Any]] = None, ): self._type = type self._rest_name_input = name @@ -774,6 +931,12 @@ def __init__( self._is_model = 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 {} + + @property + def _class_type(self) -> typing.Any: + return getattr(self._type, "args", [None])[0] @property def _rest_name(self) -> str: @@ -819,13 +982,194 @@ def rest_field( visibility: typing.Optional[typing.List[str]] = None, default: typing.Any = _UNSET, format: typing.Optional[str] = None, + is_multipart_file_input: bool = False, + xml: typing.Optional[typing.Dict[str, typing.Any]] = None, ) -> typing.Any: - return _RestField(name=name, type=type, visibility=visibility, default=default, format=format) + return _RestField( + name=name, + type=type, + visibility=visibility, + default=default, + format=format, + is_multipart_file_input=is_multipart_file_input, + xml=xml, + ) def rest_discriminator( *, name: typing.Optional[str] = None, type: typing.Optional[typing.Callable] = None, # pylint: disable=redefined-builtin + visibility: typing.Optional[typing.List[str]] = None, + xml: typing.Optional[typing.Dict[str, typing.Any]] = None, ) -> typing.Any: - return _RestField(name=name, type=type, is_discriminator=True) + return _RestField(name=name, type=type, is_discriminator=True, visibility=visibility, xml=xml) + + +def serialize_xml(model: Model, exclude_readonly: bool = False) -> str: + """Serialize a model to XML. + + :param Model model: The model to serialize. + :param bool exclude_readonly: Whether to exclude readonly properties. + :returns: The XML representation of the model. + :rtype: str + """ + return ET.tostring(_get_element(model, exclude_readonly), encoding="unicode") # type: ignore + + +def _get_element( + o: typing.Any, + exclude_readonly: bool = False, + parent_meta: typing.Optional[typing.Dict[str, typing.Any]] = None, + wrapped_element: typing.Optional[ET.Element] = None, +) -> typing.Union[ET.Element, typing.List[ET.Element]]: + if _is_model(o): + model_meta = getattr(o, "_xml", {}) + + # if prop is a model, then use the prop element directly, else generate a wrapper of model + if wrapped_element is None: + wrapped_element = _create_xml_element( + model_meta.get("name", o.__class__.__name__), + model_meta.get("prefix"), + model_meta.get("ns"), + ) + + readonly_props = [] + if exclude_readonly: + readonly_props = [p._rest_name for p in o._attr_to_rest_field.values() if _is_readonly(p)] + + for k, v in o.items(): + # do not serialize readonly properties + if exclude_readonly and k in readonly_props: + continue + + prop_rest_field = _get_rest_field(o._attr_to_rest_field, k) + if prop_rest_field: + prop_meta = getattr(prop_rest_field, "_xml").copy() + # use the wire name as xml name if no specific name is set + if prop_meta.get("name") is None: + prop_meta["name"] = k + else: + # 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 + 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") + + if prop_meta.get("unwrapped", False): + # unwrapped could only set on array + wrapped_element.extend(_get_element(v, exclude_readonly, prop_meta)) + elif prop_meta.get("text", False): + # 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)) + else: + # other wrapped prop element + wrapped_element.append(_get_wrapped_element(v, exclude_readonly, prop_meta)) + return wrapped_element + if isinstance(o, list): + return [_get_element(x, exclude_readonly, parent_meta) for x in o] # type: ignore + if isinstance(o, dict): + result = [] + for k, v in o.items(): + result.append( + _get_wrapped_element( + v, + exclude_readonly, + { + "name": k, + "ns": parent_meta.get("ns") if parent_meta else None, + "prefix": parent_meta.get("prefix") if parent_meta else None, + }, + ) + ) + return result + + # primitive case need to create element based on parent_meta + if 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")), + }, + ) + + raise ValueError("Could not serialize value into xml: " + o) + + +def _get_wrapped_element( + v: typing.Any, + exclude_readonly: bool, + meta: typing.Optional[typing.Dict[str, typing.Any]], +) -> ET.Element: + 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 + ) + if isinstance(v, (dict, list)): + wrapped_element.extend(_get_element(v, exclude_readonly, meta)) + elif _is_model(v): + _get_element(v, exclude_readonly, meta, wrapped_element) + else: + wrapped_element.text = _get_primitive_type_value(v) + return wrapped_element + + +def _get_primitive_type_value(v) -> str: + if v is True: + return "true" + if v is False: + return "false" + if isinstance(v, _Null): + return "" + return str(v) + + +def _create_xml_element(tag, prefix=None, ns=None): + if prefix and ns: + ET.register_namespace(prefix, ns) + if ns: + return ET.Element("{" + ns + "}" + tag) + return ET.Element(tag) + + +def _deserialize_xml( + deserializer: typing.Any, + value: str, +) -> typing.Any: + element = ET.fromstring(value) # nosec + return _deserialize(deserializer, element) + + +def _convert_element(e: ET.Element): + # dict case + if len(e.attrib) > 0 or len({child.tag for child in e}) > 1: + dict_result: typing.Dict[str, typing.Any] = {} + for child in e: + if dict_result.get(child.tag) is not None: + if isinstance(dict_result[child.tag], list): + dict_result[child.tag].append(_convert_element(child)) + else: + dict_result[child.tag] = [dict_result[child.tag], _convert_element(child)] + else: + dict_result[child.tag] = _convert_element(child) + dict_result.update(e.attrib) + return dict_result + # array case + if len(e) > 0: + array_result: typing.List[typing.Any] = [] + for child in e: + array_result.append(_convert_element(child)) + return array_result + # primitive case + return e.text diff --git a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/_operations/__init__.py b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/_operations/__init__.py index 2f88364a4715..2096bab0057c 100644 --- a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/_operations/__init__.py +++ b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/_operations/__init__.py @@ -5,17 +5,23 @@ # Code generated by Microsoft (R) Python Code Generator. # Changes may cause incorrect behavior and will be lost if the code is regenerated. # -------------------------------------------------------------------------- +# pylint: disable=wrong-import-position -from ._operations import ContentSafetyClientOperationsMixin -from ._operations import BlocklistClientOperationsMixin +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from ._patch import * # pylint: disable=unused-wildcard-import + +from ._operations import ContentSafetyClientOperationsMixin # type: ignore +from ._operations import BlocklistClientOperationsMixin # type: ignore from ._patch import __all__ as _patch_all -from ._patch import * # pylint: disable=unused-wildcard-import +from ._patch import * from ._patch import patch_sdk as _patch_sdk __all__ = [ "ContentSafetyClientOperationsMixin", "BlocklistClientOperationsMixin", ] -__all__.extend([p for p in _patch_all if p not in __all__]) +__all__.extend([p for p in _patch_all if p not in __all__]) # pyright: ignore _patch_sdk() diff --git a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/_operations/_operations.py b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/_operations/_operations.py index 175bc5ca04d6..edd5f09ef333 100644 --- a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/_operations/_operations.py +++ b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/_operations/_operations.py @@ -18,6 +18,8 @@ ResourceExistsError, ResourceNotFoundError, ResourceNotModifiedError, + StreamClosedError, + StreamConsumedError, map_error, ) from azure.core.paging import ItemPaged @@ -29,12 +31,13 @@ from .. import models as _models from .._model_base import SdkJSONEncoder, _deserialize from .._serialization import Serializer +from .._validation import api_version_validation from .._vendor import BlocklistClientMixinABC, ContentSafetyClientMixinABC if sys.version_info >= (3, 9): from collections.abc import MutableMapping else: - from typing import MutableMapping # type: ignore # pylint: disable=ungrouped-imports + from typing import MutableMapping # type: ignore JSON = MutableMapping[str, Any] # pylint: disable=unsubscriptable-object T = TypeVar("T") ClsType = Optional[Callable[[PipelineResponse[HttpRequest, HttpResponse], T, Dict[str, Any]], Any]] @@ -43,12 +46,34 @@ _SERIALIZER.client_side_validation = False +def build_content_safety_analyze_image_request(**kwargs: Any) -> HttpRequest: # pylint: disable=name-too-long + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2024-09-01")) + accept = _headers.pop("Accept", "application/json") + + # Construct URL + _url = "/image:analyze" + + # Construct parameters + _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") + + # Construct headers + if content_type is not None: + _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") + _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") + + return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, **kwargs) + + def build_content_safety_analyze_text_request(**kwargs: Any) -> HttpRequest: # pylint: disable=name-too-long _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2023-10-01")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2024-09-01")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -58,31 +83,55 @@ def build_content_safety_analyze_text_request(**kwargs: Any) -> HttpRequest: # _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") if content_type is not None: _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") + _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, **kwargs) -def build_content_safety_analyze_image_request(**kwargs: Any) -> HttpRequest: # pylint: disable=name-too-long +def build_content_safety_detect_text_protected_material_request( # pylint: disable=name-too-long + **kwargs: Any, +) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2023-10-01")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2024-09-01")) accept = _headers.pop("Accept", "application/json") # Construct URL - _url = "/image:analyze" + _url = "/text:detectProtectedMaterial" # Construct parameters _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers + if content_type is not None: + _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") + + return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, **kwargs) + + +def build_content_safety_shield_prompt_request(**kwargs: Any) -> HttpRequest: # pylint: disable=name-too-long + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2024-09-01")) + accept = _headers.pop("Accept", "application/json") + + # Construct URL + _url = "/text:shieldPrompt" + + # Construct parameters + _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") + + # Construct headers if content_type is not None: _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") + _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, **kwargs) @@ -94,7 +143,7 @@ def build_blocklist_add_or_update_blocklist_items_request( # pylint: disable=na _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2023-10-01")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2024-09-01")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -109,9 +158,9 @@ def build_blocklist_add_or_update_blocklist_items_request( # pylint: disable=na _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") if content_type is not None: _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") + _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, **kwargs) @@ -123,7 +172,7 @@ def build_blocklist_create_or_update_text_blocklist_request( # pylint: disable= _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2023-10-01")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2024-09-01")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -148,9 +197,12 @@ def build_blocklist_create_or_update_text_blocklist_request( # pylint: disable= def build_blocklist_delete_text_blocklist_request( # pylint: disable=name-too-long blocklist_name: 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", "2023-10-01")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2024-09-01")) + accept = _headers.pop("Accept", "application/json") + # Construct URL _url = "/text/blocklists/{blocklistName}" path_format_arguments = { @@ -162,7 +214,10 @@ def build_blocklist_delete_text_blocklist_request( # pylint: disable=name-too-l # Construct parameters _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") - return HttpRequest(method="DELETE", url=_url, params=_params, **kwargs) + # Construct headers + _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") + + return HttpRequest(method="DELETE", url=_url, params=_params, headers=_headers, **kwargs) def build_blocklist_get_text_blocklist_request( # pylint: disable=name-too-long @@ -171,7 +226,7 @@ def build_blocklist_get_text_blocklist_request( # pylint: disable=name-too-long _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", "2023-10-01")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2024-09-01")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -197,7 +252,7 @@ def build_blocklist_get_text_blocklist_item_request( # pylint: disable=name-too _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", "2023-10-01")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2024-09-01")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -224,12 +279,12 @@ def build_blocklist_list_text_blocklist_items_request( # pylint: disable=name-t top: Optional[int] = None, skip: Optional[int] = None, maxpagesize: Optional[int] = None, - **kwargs: Any + **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", "2023-10-01")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2024-09-01")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -259,7 +314,7 @@ def build_blocklist_list_text_blocklists_request(**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", "2023-10-01")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2024-09-01")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -281,7 +336,9 @@ def build_blocklist_remove_blocklist_items_request( # pylint: disable=name-too- _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2023-10-01")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2024-09-01")) + accept = _headers.pop("Accept", "application/json") + # Construct URL _url = "/text/blocklists/{blocklistName}:removeBlocklistItems" path_format_arguments = { @@ -296,11 +353,145 @@ def build_blocklist_remove_blocklist_items_request( # pylint: disable=name-too- # Construct headers if content_type is not None: _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") + _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, **kwargs) class ContentSafetyClientOperationsMixin(ContentSafetyClientMixinABC): + + @overload + def analyze_image( + self, options: _models.AnalyzeImageOptions, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.AnalyzeImageResult: + """Analyze Image. + + A synchronous API for the analysis of potentially harmful image content. Currently, it supports + four categories: Hate, SelfHarm, Sexual, and Violence. + + :param options: The image analysis request. Required. + :type options: ~azure.ai.contentsafety.models.AnalyzeImageOptions + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: AnalyzeImageResult. The AnalyzeImageResult is compatible with MutableMapping + :rtype: ~azure.ai.contentsafety.models.AnalyzeImageResult + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + def analyze_image( + self, options: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.AnalyzeImageResult: + """Analyze Image. + + A synchronous API for the analysis of potentially harmful image content. Currently, it supports + four categories: Hate, SelfHarm, Sexual, and Violence. + + :param options: The image analysis request. Required. + :type options: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: AnalyzeImageResult. The AnalyzeImageResult is compatible with MutableMapping + :rtype: ~azure.ai.contentsafety.models.AnalyzeImageResult + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + def analyze_image( + self, options: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.AnalyzeImageResult: + """Analyze Image. + + A synchronous API for the analysis of potentially harmful image content. Currently, it supports + four categories: Hate, SelfHarm, Sexual, and Violence. + + :param options: The image analysis request. Required. + :type options: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: AnalyzeImageResult. The AnalyzeImageResult is compatible with MutableMapping + :rtype: ~azure.ai.contentsafety.models.AnalyzeImageResult + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @distributed_trace + def analyze_image( + self, options: Union[_models.AnalyzeImageOptions, JSON, IO[bytes]], **kwargs: Any + ) -> _models.AnalyzeImageResult: + """Analyze Image. + + A synchronous API for the analysis of potentially harmful image content. Currently, it supports + four categories: Hate, SelfHarm, Sexual, and Violence. + + :param options: The image analysis request. Is one of the following types: AnalyzeImageOptions, + JSON, IO[bytes] Required. + :type options: ~azure.ai.contentsafety.models.AnalyzeImageOptions or JSON or IO[bytes] + :return: AnalyzeImageResult. The AnalyzeImageResult is compatible with MutableMapping + :rtype: ~azure.ai.contentsafety.models.AnalyzeImageResult + :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 = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.AnalyzeImageResult] = kwargs.pop("cls", None) + + content_type = content_type or "application/json" + _content = None + if isinstance(options, (IOBase, bytes)): + _content = options + else: + _content = json.dumps(options, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + + _request = build_content_safety_analyze_image_request( + content_type=content_type, + api_version=self._config.api_version, + content=_content, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str"), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _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() + else: + deserialized = _deserialize(_models.AnalyzeImageResult, response.json()) + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore + @overload def analyze_text( self, options: _models.AnalyzeTextOptions, *, content_type: str = "application/json", **kwargs: Any @@ -315,8 +506,6 @@ def analyze_text( :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. :return: AnalyzeTextResult. The AnalyzeTextResult is compatible with MutableMapping :rtype: ~azure.ai.contentsafety.models.AnalyzeTextResult :raises ~azure.core.exceptions.HttpResponseError: @@ -336,8 +525,6 @@ def analyze_text( :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. :return: AnalyzeTextResult. The AnalyzeTextResult is compatible with MutableMapping :rtype: ~azure.ai.contentsafety.models.AnalyzeTextResult :raises ~azure.core.exceptions.HttpResponseError: @@ -357,8 +544,6 @@ def analyze_text( :keyword content_type: Body Parameter content-type. Content type parameter for binary body. Default value is "application/json". :paramtype content_type: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. :return: AnalyzeTextResult. The AnalyzeTextResult is compatible with MutableMapping :rtype: ~azure.ai.contentsafety.models.AnalyzeTextResult :raises ~azure.core.exceptions.HttpResponseError: @@ -376,16 +561,11 @@ def analyze_text( :param options: The text analysis request. Is one of the following types: AnalyzeTextOptions, JSON, IO[bytes] Required. :type options: ~azure.ai.contentsafety.models.AnalyzeTextOptions or JSON or IO[bytes] - :keyword content_type: Body parameter Content-Type. Known values are: application/json. Default - value is None. - :paramtype content_type: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. :return: AnalyzeTextResult. The AnalyzeTextResult is compatible with MutableMapping :rtype: ~azure.ai.contentsafety.models.AnalyzeTextResult :raises ~azure.core.exceptions.HttpResponseError: """ - error_map = { + error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError, @@ -414,7 +594,7 @@ def analyze_text( params=_params, ) path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str"), } _request.url = self._client.format_url(_request.url, **path_format_arguments) @@ -427,7 +607,10 @@ def analyze_text( if response.status_code not in [200]: if _stream: - response.read() # Load the body in memory and close the socket + 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) @@ -442,90 +625,226 @@ def analyze_text( return deserialized # type: ignore @overload - def analyze_image( - self, options: _models.AnalyzeImageOptions, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AnalyzeImageResult: - """Analyze Image. + def detect_text_protected_material( + self, + options: _models.DetectTextProtectedMaterialOptions, + *, + content_type: str = "application/json", + **kwargs: Any, + ) -> _models.DetectTextProtectedMaterialResult: + """Detect Protected Material for Text. - A synchronous API for the analysis of potentially harmful image content. Currently, it supports - four categories: Hate, SelfHarm, Sexual, and Violence. + A synchronous API for detecting protected material in the given text. - :param options: The image analysis request. Required. - :type options: ~azure.ai.contentsafety.models.AnalyzeImageOptions + :param options: The request body to be detected, which may contain protected material. + Required. + :type options: ~azure.ai.contentsafety.models.DetectTextProtectedMaterialOptions :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. - :return: AnalyzeImageResult. The AnalyzeImageResult is compatible with MutableMapping - :rtype: ~azure.ai.contentsafety.models.AnalyzeImageResult + :return: DetectTextProtectedMaterialResult. The DetectTextProtectedMaterialResult is compatible + with MutableMapping + :rtype: ~azure.ai.contentsafety.models.DetectTextProtectedMaterialResult :raises ~azure.core.exceptions.HttpResponseError: """ @overload - def analyze_image( + def detect_text_protected_material( self, options: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AnalyzeImageResult: - """Analyze Image. + ) -> _models.DetectTextProtectedMaterialResult: + """Detect Protected Material for Text. - A synchronous API for the analysis of potentially harmful image content. Currently, it supports - four categories: Hate, SelfHarm, Sexual, and Violence. + A synchronous API for detecting protected material in the given text. - :param options: The image analysis request. Required. + :param options: The request body to be detected, which may contain protected material. + Required. :type options: JSON :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. - :return: AnalyzeImageResult. The AnalyzeImageResult is compatible with MutableMapping - :rtype: ~azure.ai.contentsafety.models.AnalyzeImageResult + :return: DetectTextProtectedMaterialResult. The DetectTextProtectedMaterialResult is compatible + with MutableMapping + :rtype: ~azure.ai.contentsafety.models.DetectTextProtectedMaterialResult :raises ~azure.core.exceptions.HttpResponseError: """ @overload - def analyze_image( + def detect_text_protected_material( self, options: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AnalyzeImageResult: - """Analyze Image. + ) -> _models.DetectTextProtectedMaterialResult: + """Detect Protected Material for Text. - A synchronous API for the analysis of potentially harmful image content. Currently, it supports - four categories: Hate, SelfHarm, Sexual, and Violence. + A synchronous API for detecting protected material in the given text. - :param options: The image analysis request. Required. + :param options: The request body to be detected, which may contain protected material. + Required. :type options: IO[bytes] :keyword content_type: Body Parameter content-type. Content type parameter for binary body. Default value is "application/json". :paramtype content_type: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. - :return: AnalyzeImageResult. The AnalyzeImageResult is compatible with MutableMapping - :rtype: ~azure.ai.contentsafety.models.AnalyzeImageResult + :return: DetectTextProtectedMaterialResult. The DetectTextProtectedMaterialResult is compatible + with MutableMapping + :rtype: ~azure.ai.contentsafety.models.DetectTextProtectedMaterialResult :raises ~azure.core.exceptions.HttpResponseError: """ @distributed_trace - def analyze_image( - self, options: Union[_models.AnalyzeImageOptions, JSON, IO[bytes]], **kwargs: Any - ) -> _models.AnalyzeImageResult: - """Analyze Image. + @api_version_validation( + method_added_on="2024-09-01", + params_added_on={"2024-09-01": ["api_version", "content_type", "accept"]}, + ) + def detect_text_protected_material( + self, options: Union[_models.DetectTextProtectedMaterialOptions, JSON, IO[bytes]], **kwargs: Any + ) -> _models.DetectTextProtectedMaterialResult: + """Detect Protected Material for Text. + + A synchronous API for detecting protected material in the given text. + + :param options: The request body to be detected, which may contain protected material. Is one + of the following types: DetectTextProtectedMaterialOptions, JSON, IO[bytes] Required. + :type options: ~azure.ai.contentsafety.models.DetectTextProtectedMaterialOptions or JSON or + IO[bytes] + :return: DetectTextProtectedMaterialResult. The DetectTextProtectedMaterialResult is compatible + with MutableMapping + :rtype: ~azure.ai.contentsafety.models.DetectTextProtectedMaterialResult + :raises ~azure.core.exceptions.HttpResponseError: + """ + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) - A synchronous API for the analysis of potentially harmful image content. Currently, it supports - four categories: Hate, SelfHarm, Sexual, and Violence. + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} - :param options: The image analysis request. Is one of the following types: AnalyzeImageOptions, - JSON, IO[bytes] Required. - :type options: ~azure.ai.contentsafety.models.AnalyzeImageOptions or JSON or IO[bytes] - :keyword content_type: Body parameter Content-Type. Known values are: application/json. Default - value is None. + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.DetectTextProtectedMaterialResult] = kwargs.pop("cls", None) + + content_type = content_type or "application/json" + _content = None + if isinstance(options, (IOBase, bytes)): + _content = options + else: + _content = json.dumps(options, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + + _request = build_content_safety_detect_text_protected_material_request( + content_type=content_type, + api_version=self._config.api_version, + content=_content, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str"), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _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() + else: + deserialized = _deserialize(_models.DetectTextProtectedMaterialResult, response.json()) + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore + + @overload + def shield_prompt( + self, options: _models.ShieldPromptOptions, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.ShieldPromptResult: + """Shield Prompt. + + A synchronous API for shielding prompt from direct and indirect injection attacks. + + :param options: The request body to be detected, which may contain direct or indirect injection + attacks. Required. + :type options: ~azure.ai.contentsafety.models.ShieldPromptOptions + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". :paramtype content_type: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. - :return: AnalyzeImageResult. The AnalyzeImageResult is compatible with MutableMapping - :rtype: ~azure.ai.contentsafety.models.AnalyzeImageResult + :return: ShieldPromptResult. The ShieldPromptResult is compatible with MutableMapping + :rtype: ~azure.ai.contentsafety.models.ShieldPromptResult :raises ~azure.core.exceptions.HttpResponseError: """ - error_map = { + + @overload + def shield_prompt( + self, options: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.ShieldPromptResult: + """Shield Prompt. + + A synchronous API for shielding prompt from direct and indirect injection attacks. + + :param options: The request body to be detected, which may contain direct or indirect injection + attacks. Required. + :type options: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: ShieldPromptResult. The ShieldPromptResult is compatible with MutableMapping + :rtype: ~azure.ai.contentsafety.models.ShieldPromptResult + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + def shield_prompt( + self, options: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.ShieldPromptResult: + """Shield Prompt. + + A synchronous API for shielding prompt from direct and indirect injection attacks. + + :param options: The request body to be detected, which may contain direct or indirect injection + attacks. Required. + :type options: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: ShieldPromptResult. The ShieldPromptResult is compatible with MutableMapping + :rtype: ~azure.ai.contentsafety.models.ShieldPromptResult + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @distributed_trace + @api_version_validation( + method_added_on="2024-09-01", + params_added_on={"2024-09-01": ["api_version", "content_type", "accept"]}, + ) + def shield_prompt( + self, options: Union[_models.ShieldPromptOptions, JSON, IO[bytes]], **kwargs: Any + ) -> _models.ShieldPromptResult: + """Shield Prompt. + + A synchronous API for shielding prompt from direct and indirect injection attacks. + + :param options: The request body to be detected, which may contain direct or indirect injection + attacks. Is one of the following types: ShieldPromptOptions, JSON, IO[bytes] Required. + :type options: ~azure.ai.contentsafety.models.ShieldPromptOptions or JSON or IO[bytes] + :return: ShieldPromptResult. The ShieldPromptResult is compatible with MutableMapping + :rtype: ~azure.ai.contentsafety.models.ShieldPromptResult + :raises ~azure.core.exceptions.HttpResponseError: + """ + error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError, @@ -537,7 +856,7 @@ def analyze_image( _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.AnalyzeImageResult] = kwargs.pop("cls", None) + cls: ClsType[_models.ShieldPromptResult] = kwargs.pop("cls", None) content_type = content_type or "application/json" _content = None @@ -546,7 +865,7 @@ def analyze_image( else: _content = json.dumps(options, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_content_safety_analyze_image_request( + _request = build_content_safety_shield_prompt_request( content_type=content_type, api_version=self._config.api_version, content=_content, @@ -554,7 +873,7 @@ def analyze_image( params=_params, ) path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str"), } _request.url = self._client.format_url(_request.url, **path_format_arguments) @@ -567,14 +886,17 @@ def analyze_image( if response.status_code not in [200]: if _stream: - response.read() # Load the body in memory and close the socket + 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() else: - deserialized = _deserialize(_models.AnalyzeImageResult, response.json()) + deserialized = _deserialize(_models.ShieldPromptResult, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -583,6 +905,7 @@ def analyze_image( class BlocklistClientOperationsMixin(BlocklistClientMixinABC): + @overload def add_or_update_blocklist_items( self, @@ -590,7 +913,7 @@ def add_or_update_blocklist_items( options: _models.AddOrUpdateTextBlocklistItemsOptions, *, content_type: str = "application/json", - **kwargs: Any + **kwargs: Any, ) -> _models.AddOrUpdateTextBlocklistItemsResult: """Add or update BlocklistItems To Text Blocklist. @@ -604,8 +927,6 @@ def add_or_update_blocklist_items( :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. :return: AddOrUpdateTextBlocklistItemsResult. The AddOrUpdateTextBlocklistItemsResult is compatible with MutableMapping :rtype: ~azure.ai.contentsafety.models.AddOrUpdateTextBlocklistItemsResult @@ -628,8 +949,6 @@ def add_or_update_blocklist_items( :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. :return: AddOrUpdateTextBlocklistItemsResult. The AddOrUpdateTextBlocklistItemsResult is compatible with MutableMapping :rtype: ~azure.ai.contentsafety.models.AddOrUpdateTextBlocklistItemsResult @@ -652,8 +971,6 @@ def add_or_update_blocklist_items( :keyword content_type: Body Parameter content-type. Content type parameter for binary body. Default value is "application/json". :paramtype content_type: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. :return: AddOrUpdateTextBlocklistItemsResult. The AddOrUpdateTextBlocklistItemsResult is compatible with MutableMapping :rtype: ~azure.ai.contentsafety.models.AddOrUpdateTextBlocklistItemsResult @@ -665,7 +982,7 @@ def add_or_update_blocklist_items( self, blocklist_name: str, options: Union[_models.AddOrUpdateTextBlocklistItemsOptions, JSON, IO[bytes]], - **kwargs: Any + **kwargs: Any, ) -> _models.AddOrUpdateTextBlocklistItemsResult: """Add or update BlocklistItems To Text Blocklist. @@ -678,17 +995,12 @@ def add_or_update_blocklist_items( AddOrUpdateTextBlocklistItemsOptions, JSON, IO[bytes] Required. :type options: ~azure.ai.contentsafety.models.AddOrUpdateTextBlocklistItemsOptions or JSON or IO[bytes] - :keyword content_type: Body parameter Content-Type. Known values are: application/json. Default - value is None. - :paramtype content_type: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. :return: AddOrUpdateTextBlocklistItemsResult. The AddOrUpdateTextBlocklistItemsResult is compatible with MutableMapping :rtype: ~azure.ai.contentsafety.models.AddOrUpdateTextBlocklistItemsResult :raises ~azure.core.exceptions.HttpResponseError: """ - error_map = { + error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError, @@ -718,7 +1030,7 @@ def add_or_update_blocklist_items( params=_params, ) path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str"), } _request.url = self._client.format_url(_request.url, **path_format_arguments) @@ -731,7 +1043,10 @@ def add_or_update_blocklist_items( if response.status_code not in [200]: if _stream: - response.read() # Load the body in memory and close the socket + 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) @@ -752,7 +1067,7 @@ def create_or_update_text_blocklist( options: _models.TextBlocklist, *, content_type: str = "application/merge-patch+json", - **kwargs: Any + **kwargs: Any, ) -> _models.TextBlocklist: """Create Or Update Text Blocklist. @@ -765,8 +1080,6 @@ def create_or_update_text_blocklist( :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/merge-patch+json". :paramtype content_type: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. :return: TextBlocklist. The TextBlocklist is compatible with MutableMapping :rtype: ~azure.ai.contentsafety.models.TextBlocklist :raises ~azure.core.exceptions.HttpResponseError: @@ -787,8 +1100,6 @@ def create_or_update_text_blocklist( :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/merge-patch+json". :paramtype content_type: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. :return: TextBlocklist. The TextBlocklist is compatible with MutableMapping :rtype: ~azure.ai.contentsafety.models.TextBlocklist :raises ~azure.core.exceptions.HttpResponseError: @@ -801,7 +1112,7 @@ def create_or_update_text_blocklist( options: IO[bytes], *, content_type: str = "application/merge-patch+json", - **kwargs: Any + **kwargs: Any, ) -> _models.TextBlocklist: """Create Or Update Text Blocklist. @@ -814,8 +1125,6 @@ def create_or_update_text_blocklist( :keyword content_type: Body Parameter content-type. Content type parameter for binary body. Default value is "application/merge-patch+json". :paramtype content_type: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. :return: TextBlocklist. The TextBlocklist is compatible with MutableMapping :rtype: ~azure.ai.contentsafety.models.TextBlocklist :raises ~azure.core.exceptions.HttpResponseError: @@ -834,15 +1143,11 @@ def create_or_update_text_blocklist( :param options: The resource instance. Is one of the following types: TextBlocklist, JSON, IO[bytes] Required. :type options: ~azure.ai.contentsafety.models.TextBlocklist or JSON or IO[bytes] - :keyword content_type: This request has a JSON Merge Patch body. Default value is None. - :paramtype content_type: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. :return: TextBlocklist. The TextBlocklist is compatible with MutableMapping :rtype: ~azure.ai.contentsafety.models.TextBlocklist :raises ~azure.core.exceptions.HttpResponseError: """ - error_map = { + error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError, @@ -872,7 +1177,7 @@ def create_or_update_text_blocklist( params=_params, ) path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str"), } _request.url = self._client.format_url(_request.url, **path_format_arguments) @@ -885,21 +1190,17 @@ def create_or_update_text_blocklist( if response.status_code not in [200, 201]: if _stream: - response.read() # Load the body in memory and close the socket + 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 response.status_code == 200: - if _stream: - deserialized = response.iter_bytes() - else: - deserialized = _deserialize(_models.TextBlocklist, response.json()) - - if response.status_code == 201: - if _stream: - deserialized = response.iter_bytes() - else: - deserialized = _deserialize(_models.TextBlocklist, response.json()) + if _stream: + deserialized = response.iter_bytes() + else: + deserialized = _deserialize(_models.TextBlocklist, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -920,7 +1221,7 @@ def delete_text_blocklist( # pylint: disable=inconsistent-return-statements :rtype: None :raises ~azure.core.exceptions.HttpResponseError: """ - error_map = { + error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError, @@ -940,7 +1241,7 @@ def delete_text_blocklist( # pylint: disable=inconsistent-return-statements params=_params, ) path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str"), } _request.url = self._client.format_url(_request.url, **path_format_arguments) @@ -952,8 +1253,6 @@ def delete_text_blocklist( # pylint: disable=inconsistent-return-statements response = pipeline_response.http_response if response.status_code not in [204]: - if _stream: - response.read() # Load the body in memory and close the socket map_error(status_code=response.status_code, response=response, error_map=error_map) raise HttpResponseError(response=response) @@ -968,13 +1267,11 @@ def get_text_blocklist(self, blocklist_name: str, **kwargs: Any) -> _models.Text :param blocklist_name: Text blocklist name. Required. :type blocklist_name: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. :return: TextBlocklist. The TextBlocklist is compatible with MutableMapping :rtype: ~azure.ai.contentsafety.models.TextBlocklist :raises ~azure.core.exceptions.HttpResponseError: """ - error_map = { + error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError, @@ -994,7 +1291,7 @@ def get_text_blocklist(self, blocklist_name: str, **kwargs: Any) -> _models.Text params=_params, ) path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str"), } _request.url = self._client.format_url(_request.url, **path_format_arguments) @@ -1007,7 +1304,10 @@ def get_text_blocklist(self, blocklist_name: str, **kwargs: Any) -> _models.Text if response.status_code not in [200]: if _stream: - response.read() # Load the body in memory and close the socket + 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) @@ -1034,13 +1334,11 @@ def get_text_blocklist_item( :param blocklist_item_id: The service will generate a BlocklistItemId, which will be a UUID. Required. :type blocklist_item_id: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. :return: TextBlocklistItem. The TextBlocklistItem is compatible with MutableMapping :rtype: ~azure.ai.contentsafety.models.TextBlocklistItem :raises ~azure.core.exceptions.HttpResponseError: """ - error_map = { + error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError, @@ -1061,7 +1359,7 @@ def get_text_blocklist_item( params=_params, ) path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str"), } _request.url = self._client.format_url(_request.url, **path_format_arguments) @@ -1074,7 +1372,10 @@ def get_text_blocklist_item( if response.status_code not in [200]: if _stream: - response.read() # Load the body in memory and close the socket + 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) @@ -1112,7 +1413,7 @@ def list_text_blocklist_items( maxpagesize = kwargs.pop("maxpagesize", None) cls: ClsType[List[_models.TextBlocklistItem]] = kwargs.pop("cls", None) - error_map = { + error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError, @@ -1133,9 +1434,7 @@ def prepare_request(next_link=None): params=_params, ) path_format_arguments = { - "endpoint": self._serialize.url( - "self._config.endpoint", self._config.endpoint, "str", skip_quote=True - ), + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str"), } _request.url = self._client.format_url(_request.url, **path_format_arguments) @@ -1153,9 +1452,7 @@ def prepare_request(next_link=None): "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params ) path_format_arguments = { - "endpoint": self._serialize.url( - "self._config.endpoint", self._config.endpoint, "str", skip_quote=True - ), + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str"), } _request.url = self._client.format_url(_request.url, **path_format_arguments) @@ -1178,8 +1475,6 @@ def get_next(next_link=None): response = pipeline_response.http_response if response.status_code not in [200]: - if _stream: - response.read() # Load the body in memory and close the socket map_error(status_code=response.status_code, response=response, error_map=error_map) raise HttpResponseError(response=response) @@ -1202,7 +1497,7 @@ def list_text_blocklists(self, **kwargs: Any) -> Iterable["_models.TextBlocklist cls: ClsType[List[_models.TextBlocklist]] = kwargs.pop("cls", None) - error_map = { + error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError, @@ -1219,9 +1514,7 @@ def prepare_request(next_link=None): params=_params, ) path_format_arguments = { - "endpoint": self._serialize.url( - "self._config.endpoint", self._config.endpoint, "str", skip_quote=True - ), + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str"), } _request.url = self._client.format_url(_request.url, **path_format_arguments) @@ -1239,9 +1532,7 @@ def prepare_request(next_link=None): "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params ) path_format_arguments = { - "endpoint": self._serialize.url( - "self._config.endpoint", self._config.endpoint, "str", skip_quote=True - ), + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str"), } _request.url = self._client.format_url(_request.url, **path_format_arguments) @@ -1264,8 +1555,6 @@ def get_next(next_link=None): response = pipeline_response.http_response if response.status_code not in [200]: - if _stream: - response.read() # Load the body in memory and close the socket map_error(status_code=response.status_code, response=response, error_map=error_map) raise HttpResponseError(response=response) @@ -1274,13 +1563,13 @@ def get_next(next_link=None): return ItemPaged(get_next, extract_data) @overload - def remove_blocklist_items( # pylint: disable=inconsistent-return-statements + def remove_blocklist_items( self, blocklist_name: str, options: _models.RemoveTextBlocklistItemsOptions, *, content_type: str = "application/json", - **kwargs: Any + **kwargs: Any, ) -> None: """Remove BlocklistItems From Text Blocklist. @@ -1300,7 +1589,7 @@ def remove_blocklist_items( # pylint: disable=inconsistent-return-statements """ @overload - def remove_blocklist_items( # pylint: disable=inconsistent-return-statements + def remove_blocklist_items( self, blocklist_name: str, options: JSON, *, content_type: str = "application/json", **kwargs: Any ) -> None: """Remove BlocklistItems From Text Blocklist. @@ -1321,7 +1610,7 @@ def remove_blocklist_items( # pylint: disable=inconsistent-return-statements """ @overload - def remove_blocklist_items( # pylint: disable=inconsistent-return-statements + def remove_blocklist_items( self, blocklist_name: str, options: IO[bytes], *, content_type: str = "application/json", **kwargs: Any ) -> None: """Remove BlocklistItems From Text Blocklist. @@ -1346,7 +1635,7 @@ def remove_blocklist_items( # pylint: disable=inconsistent-return-statements self, blocklist_name: str, options: Union[_models.RemoveTextBlocklistItemsOptions, JSON, IO[bytes]], - **kwargs: Any + **kwargs: Any, ) -> None: """Remove BlocklistItems From Text Blocklist. @@ -1359,14 +1648,11 @@ def remove_blocklist_items( # pylint: disable=inconsistent-return-statements RemoveTextBlocklistItemsOptions, JSON, IO[bytes] Required. :type options: ~azure.ai.contentsafety.models.RemoveTextBlocklistItemsOptions or JSON or IO[bytes] - :keyword content_type: Body parameter Content-Type. Known values are: application/json. Default - value is None. - :paramtype content_type: str :return: None :rtype: None :raises ~azure.core.exceptions.HttpResponseError: """ - error_map = { + error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError, @@ -1396,7 +1682,7 @@ def remove_blocklist_items( # pylint: disable=inconsistent-return-statements params=_params, ) path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str"), } _request.url = self._client.format_url(_request.url, **path_format_arguments) @@ -1408,8 +1694,6 @@ def remove_blocklist_items( # pylint: disable=inconsistent-return-statements response = pipeline_response.http_response if response.status_code not in [204]: - if _stream: - response.read() # Load the body in memory and close the socket map_error(status_code=response.status_code, response=response, error_map=error_map) raise HttpResponseError(response=response) diff --git a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/_serialization.py b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/_serialization.py index baa661cb82d2..b24ab2885450 100644 --- a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/_serialization.py +++ b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/_serialization.py @@ -1,3 +1,4 @@ +# pylint: disable=too-many-lines # -------------------------------------------------------------------------- # # Copyright (c) Microsoft Corporation. All rights reserved. @@ -24,7 +25,6 @@ # # -------------------------------------------------------------------------- -# pylint: skip-file # pyright: reportUnnecessaryTypeIgnoreComment=false from base64 import b64decode, b64encode @@ -52,7 +52,6 @@ MutableMapping, Type, List, - Mapping, ) try: @@ -91,6 +90,8 @@ def deserialize_from_text(cls, data: Optional[Union[AnyStr, IO]], content_type: :param data: Input, could be bytes or stream (will be decoded with UTF8) or text :type data: str or bytes or IO :param str content_type: The content type. + :return: The deserialized data. + :rtype: object """ if hasattr(data, "read"): # Assume a stream @@ -112,7 +113,7 @@ def deserialize_from_text(cls, data: Optional[Union[AnyStr, IO]], content_type: try: return json.loads(data_as_str) except ValueError as err: - raise DeserializationError("JSON is invalid: {}".format(err), err) + raise DeserializationError("JSON is invalid: {}".format(err), err) from err elif "xml" in (content_type or []): try: @@ -144,6 +145,8 @@ def _json_attemp(data): # context otherwise. _LOGGER.critical("Wasn't XML not JSON, failing") raise DeserializationError("XML is invalid") from err + elif content_type.startswith("text/"): + return data_as_str raise DeserializationError("Cannot deserialize content-type: {}".format(content_type)) @classmethod @@ -153,6 +156,11 @@ def deserialize_from_http_generics(cls, body_bytes: Optional[Union[AnyStr, IO]], Use bytes and headers to NOT use any requests/aiohttp or whatever specific implementation. Headers will tested for "content-type" + + :param bytes body_bytes: The body of the response. + :param dict headers: The headers of the response. + :returns: The deserialized data. + :rtype: object """ # Try to use content-type from headers if available content_type = None @@ -170,13 +178,6 @@ def deserialize_from_http_generics(cls, body_bytes: Optional[Union[AnyStr, IO]], return None -try: - basestring # type: ignore - unicode_str = unicode # type: ignore -except NameError: - basestring = str - unicode_str = str - _LOGGER = logging.getLogger(__name__) try: @@ -189,15 +190,30 @@ class UTC(datetime.tzinfo): """Time Zone info for handling UTC""" def utcoffset(self, dt): - """UTF offset for UTC is 0.""" + """UTF offset for UTC is 0. + + :param datetime.datetime dt: The datetime + :returns: The offset + :rtype: datetime.timedelta + """ return datetime.timedelta(0) def tzname(self, dt): - """Timestamp representation.""" + """Timestamp representation. + + :param datetime.datetime dt: The datetime + :returns: The timestamp representation + :rtype: str + """ return "Z" def dst(self, dt): - """No daylight saving for UTC.""" + """No daylight saving for UTC. + + :param datetime.datetime dt: The datetime + :returns: The daylight saving time + :rtype: datetime.timedelta + """ return datetime.timedelta(hours=1) @@ -211,7 +227,7 @@ class _FixedOffset(datetime.tzinfo): # type: ignore :param datetime.timedelta offset: offset in timedelta format """ - def __init__(self, offset): + def __init__(self, offset) -> None: self.__offset = offset def utcoffset(self, dt): @@ -240,24 +256,26 @@ def __getinitargs__(self): _FLATTEN = re.compile(r"(? None: self.additional_properties: Optional[Dict[str, Any]] = {} - for k in kwargs: + for k in kwargs: # pylint: disable=consider-using-dict-items if k not in self._attribute_map: _LOGGER.warning("%s is not a known attribute of class %s and will be ignored", k, self.__class__) elif k in self._validation and self._validation[k].get("readonly", False): @@ -305,13 +330,23 @@ def __init__(self, **kwargs: Any) -> None: setattr(self, k, kwargs[k]) def __eq__(self, other: Any) -> bool: - """Compare objects by comparing all attributes.""" + """Compare objects by comparing all attributes. + + :param object other: The object to compare + :returns: True if objects are equal + :rtype: bool + """ if isinstance(other, self.__class__): return self.__dict__ == other.__dict__ return False def __ne__(self, other: Any) -> bool: - """Compare objects by comparing all attributes.""" + """Compare objects by comparing all attributes. + + :param object other: The object to compare + :returns: True if objects are not equal + :rtype: bool + """ return not self.__eq__(other) def __str__(self) -> str: @@ -331,7 +366,11 @@ def is_xml_model(cls) -> bool: @classmethod def _create_xml_node(cls): - """Create XML node.""" + """Create XML node. + + :returns: The XML node + :rtype: xml.etree.ElementTree.Element + """ try: xml_map = cls._xml_map # type: ignore except AttributeError: @@ -351,7 +390,9 @@ def serialize(self, keep_readonly: bool = False, **kwargs: Any) -> JSON: :rtype: dict """ serializer = Serializer(self._infer_class_models()) - return serializer._serialize(self, keep_readonly=keep_readonly, **kwargs) # type: ignore + return serializer._serialize( # type: ignore # pylint: disable=protected-access + self, keep_readonly=keep_readonly, **kwargs + ) def as_dict( self, @@ -385,12 +426,15 @@ def my_key_transformer(key, attr_desc, value): If you want XML serialization, you can pass the kwargs is_xml=True. + :param bool keep_readonly: If you want to serialize the readonly attributes :param function key_transformer: A key transformer function. :returns: A dict JSON compatible object :rtype: dict """ serializer = Serializer(self._infer_class_models()) - return serializer._serialize(self, key_transformer=key_transformer, keep_readonly=keep_readonly, **kwargs) # type: ignore + return serializer._serialize( # type: ignore # pylint: disable=protected-access + self, key_transformer=key_transformer, keep_readonly=keep_readonly, **kwargs + ) @classmethod def _infer_class_models(cls): @@ -400,7 +444,7 @@ def _infer_class_models(cls): client_models = {k: v for k, v in models.__dict__.items() if isinstance(v, type)} if cls.__name__ not in client_models: raise ValueError("Not Autorest generated code") - except Exception: + except Exception: # pylint: disable=broad-exception-caught # Assume it's not Autorest generated (tests?). Add ourselves as dependencies. client_models = {cls.__name__: cls} return client_models @@ -413,6 +457,7 @@ def deserialize(cls: Type[ModelType], data: Any, content_type: Optional[str] = N :param str content_type: JSON by default, set application/xml if XML. :returns: An instance of this model :raises: DeserializationError if something went wrong + :rtype: ModelType """ deserializer = Deserializer(cls._infer_class_models()) return deserializer(cls.__name__, data, content_type=content_type) # type: ignore @@ -431,9 +476,11 @@ def from_dict( and last_rest_key_case_insensitive_extractor) :param dict data: A dict using RestAPI structure + :param function key_extractors: A key extractor function. :param str content_type: JSON by default, set application/xml if XML. :returns: An instance of this model :raises: DeserializationError if something went wrong + :rtype: ModelType """ deserializer = Deserializer(cls._infer_class_models()) deserializer.key_extractors = ( # type: ignore @@ -453,21 +500,25 @@ def _flatten_subtype(cls, key, objects): return {} result = dict(cls._subtype_map[key]) for valuetype in cls._subtype_map[key].values(): - result.update(objects[valuetype]._flatten_subtype(key, objects)) + result.update(objects[valuetype]._flatten_subtype(key, objects)) # pylint: disable=protected-access return result @classmethod def _classify(cls, response, objects): """Check the class _subtype_map for any child classes. We want to ignore any inherited _subtype_maps. - Remove the polymorphic key from the initial data. + + :param dict response: The initial data + :param dict objects: The class objects + :returns: The class to be used + :rtype: class """ for subtype_key in cls.__dict__.get("_subtype_map", {}).keys(): subtype_value = None if not isinstance(response, ET.Element): rest_api_response_key = cls._get_rest_key_parts(subtype_key)[-1] - subtype_value = response.pop(rest_api_response_key, None) or response.pop(subtype_key, None) + subtype_value = response.get(rest_api_response_key, None) or response.get(subtype_key, None) else: subtype_value = xml_key_extractor(subtype_key, cls._attribute_map[subtype_key], response) if subtype_value: @@ -506,11 +557,13 @@ def _decode_attribute_map_key(key): inside the received data. :param str key: A key string from the generated code + :returns: The decoded key + :rtype: str """ return key.replace("\\.", ".") -class Serializer(object): +class Serializer: # pylint: disable=too-many-public-methods """Request object model serializer.""" basic_types = {str: "str", int: "int", bool: "bool", float: "float"} @@ -545,7 +598,7 @@ class Serializer(object): "multiple": lambda x, y: x % y != 0, } - def __init__(self, classes: Optional[Mapping[str, Type[ModelType]]] = None): + def __init__(self, classes: Optional[Mapping[str, type]] = None) -> None: self.serialize_type = { "iso-8601": Serializer.serialize_iso, "rfc-1123": Serializer.serialize_rfc, @@ -561,17 +614,20 @@ def __init__(self, classes: Optional[Mapping[str, Type[ModelType]]] = None): "[]": self.serialize_iter, "{}": self.serialize_dict, } - self.dependencies: Dict[str, Type[ModelType]] = dict(classes) if classes else {} + self.dependencies: Dict[str, type] = dict(classes) if classes else {} self.key_transformer = full_restapi_key_transformer self.client_side_validation = True - def _serialize(self, target_obj, data_type=None, **kwargs): + def _serialize( # pylint: disable=too-many-nested-blocks, too-many-branches, too-many-statements, too-many-locals + self, target_obj, data_type=None, **kwargs + ): """Serialize data into a string according to type. - :param target_obj: The data to be serialized. + :param object target_obj: The data to be serialized. :param str data_type: The type to be serialized from. :rtype: str, dict :raises: SerializationError if serialization fails. + :returns: The serialized data. """ key_transformer = kwargs.get("key_transformer", self.key_transformer) keep_readonly = kwargs.get("keep_readonly", False) @@ -597,12 +653,14 @@ def _serialize(self, target_obj, data_type=None, **kwargs): serialized = {} if is_xml_model_serialization: - serialized = target_obj._create_xml_node() + serialized = target_obj._create_xml_node() # pylint: disable=protected-access try: - attributes = target_obj._attribute_map + attributes = target_obj._attribute_map # pylint: disable=protected-access for attr, attr_desc in attributes.items(): attr_name = attr - if not keep_readonly and target_obj._validation.get(attr_name, {}).get("readonly", False): + if not keep_readonly and target_obj._validation.get( # pylint: disable=protected-access + attr_name, {} + ).get("readonly", False): continue if attr_name == "additional_properties" and attr_desc["key"] == "": @@ -638,7 +696,8 @@ def _serialize(self, target_obj, data_type=None, **kwargs): if isinstance(new_attr, list): serialized.extend(new_attr) # type: ignore elif isinstance(new_attr, ET.Element): - # If the down XML has no XML/Name, we MUST replace the tag with the local tag. But keeping the namespaces. + # If the down XML has no XML/Name, + # we MUST replace the tag with the local tag. But keeping the namespaces. if "name" not in getattr(orig_attr, "_xml_map", {}): splitted_tag = new_attr.tag.split("}") if len(splitted_tag) == 2: # Namespace @@ -649,7 +708,7 @@ def _serialize(self, target_obj, data_type=None, **kwargs): else: # That's a basic type # Integrate namespace if necessary local_node = _create_xml_node(xml_name, xml_prefix, xml_ns) - local_node.text = unicode_str(new_attr) + local_node.text = str(new_attr) serialized.append(local_node) # type: ignore else: # JSON for k in reversed(keys): # type: ignore @@ -669,17 +728,17 @@ def _serialize(self, target_obj, data_type=None, **kwargs): except (AttributeError, KeyError, TypeError) as err: msg = "Attribute {} in object {} cannot be serialized.\n{}".format(attr_name, class_name, str(target_obj)) raise SerializationError(msg) from err - else: - return serialized + return serialized def body(self, data, data_type, **kwargs): """Serialize data intended for a request body. - :param data: The data to be serialized. + :param object data: The data to be serialized. :param str data_type: The type to be serialized from. :rtype: dict :raises: SerializationError if serialization fails. :raises: ValueError if data is None + :returns: The serialized request body """ # Just in case this is a dict @@ -708,7 +767,7 @@ def body(self, data, data_type, **kwargs): attribute_key_case_insensitive_extractor, last_rest_key_case_insensitive_extractor, ] - data = deserializer._deserialize(data_type, data) + data = deserializer._deserialize(data_type, data) # pylint: disable=protected-access except DeserializationError as err: raise SerializationError("Unable to build a model: " + str(err)) from err @@ -717,9 +776,11 @@ def body(self, data, data_type, **kwargs): def url(self, name, data, data_type, **kwargs): """Serialize data intended for a URL path. - :param data: The data to be serialized. + :param str name: The name of the URL path parameter. + :param object data: The data to be serialized. :param str data_type: The type to be serialized from. :rtype: str + :returns: The serialized URL path :raises: TypeError if serialization fails. :raises: ValueError if data is None """ @@ -733,21 +794,20 @@ def url(self, name, data, data_type, **kwargs): output = output.replace("{", quote("{")).replace("}", quote("}")) else: output = quote(str(output), safe="") - except SerializationError: - raise TypeError("{} must be type {}.".format(name, data_type)) - else: - return output + except SerializationError as exc: + raise TypeError("{} must be type {}.".format(name, data_type)) from exc + return output def query(self, name, data, data_type, **kwargs): """Serialize data intended for a URL query. - :param data: The data to be serialized. + :param str name: The name of the query parameter. + :param object data: The data to be serialized. :param str data_type: The type to be serialized from. - :keyword bool skip_quote: Whether to skip quote the serialized result. - Defaults to False. :rtype: str, list :raises: TypeError if serialization fails. :raises: ValueError if data is None + :returns: The serialized query parameter """ try: # Treat the list aside, since we don't want to encode the div separator @@ -764,19 +824,20 @@ def query(self, name, data, data_type, **kwargs): output = str(output) else: output = quote(str(output), safe="") - except SerializationError: - raise TypeError("{} must be type {}.".format(name, data_type)) - else: - return str(output) + except SerializationError as exc: + raise TypeError("{} must be type {}.".format(name, data_type)) from exc + return str(output) def header(self, name, data, data_type, **kwargs): """Serialize data intended for a request header. - :param data: The data to be serialized. + :param str name: The name of the header. + :param object data: The data to be serialized. :param str data_type: The type to be serialized from. :rtype: str :raises: TypeError if serialization fails. :raises: ValueError if data is None + :returns: The serialized header """ try: if data_type in ["[str]"]: @@ -785,21 +846,20 @@ def header(self, name, data, data_type, **kwargs): output = self.serialize_data(data, data_type, **kwargs) if data_type == "bool": output = json.dumps(output) - except SerializationError: - raise TypeError("{} must be type {}.".format(name, data_type)) - else: - return str(output) + except SerializationError as exc: + raise TypeError("{} must be type {}.".format(name, data_type)) from exc + return str(output) def serialize_data(self, data, data_type, **kwargs): """Serialize generic data according to supplied data type. - :param data: The data to be serialized. + :param object data: The data to be serialized. :param str data_type: The type to be serialized from. - :param bool required: Whether it's essential that the data not be - empty or None :raises: AttributeError if required data is None. :raises: ValueError if data is None :raises: SerializationError if serialization fails. + :returns: The serialized data. + :rtype: str, int, float, bool, dict, list """ if data is None: raise ValueError("No value for given attribute") @@ -810,7 +870,7 @@ def serialize_data(self, data, data_type, **kwargs): if data_type in self.basic_types.values(): return self.serialize_basic(data, data_type, **kwargs) - elif data_type in self.serialize_type: + if data_type in self.serialize_type: return self.serialize_type[data_type](data, **kwargs) # If dependencies is empty, try with current data class @@ -826,11 +886,10 @@ def serialize_data(self, data, data_type, **kwargs): except (ValueError, TypeError) as err: msg = "Unable to serialize value: {!r} as type: {!r}." raise SerializationError(msg.format(data, data_type)) from err - else: - return self._serialize(data, **kwargs) + return self._serialize(data, **kwargs) @classmethod - def _get_custom_serializers(cls, data_type, **kwargs): + def _get_custom_serializers(cls, data_type, **kwargs): # pylint: disable=inconsistent-return-statements custom_serializer = kwargs.get("basic_types_serializers", {}).get(data_type) if custom_serializer: return custom_serializer @@ -846,23 +905,26 @@ def serialize_basic(cls, data, data_type, **kwargs): - basic_types_serializers dict[str, callable] : If set, use the callable as serializer - is_xml bool : If set, use xml_basic_types_serializers - :param data: Object to be serialized. + :param obj data: Object to be serialized. :param str data_type: Type of object in the iterable. + :rtype: str, int, float, bool + :return: serialized object """ custom_serializer = cls._get_custom_serializers(data_type, **kwargs) if custom_serializer: return custom_serializer(data) if data_type == "str": return cls.serialize_unicode(data) - return eval(data_type)(data) # nosec + return eval(data_type)(data) # nosec # pylint: disable=eval-used @classmethod def serialize_unicode(cls, data): """Special handling for serializing unicode strings in Py2. Encode to UTF-8 if unicode, otherwise handle as a str. - :param data: Object to be serialized. + :param str data: Object to be serialized. :rtype: str + :return: serialized object """ try: # If I received an enum, return its value return data.value @@ -876,8 +938,7 @@ def serialize_unicode(cls, data): return data except NameError: return str(data) - else: - return str(data) + return str(data) def serialize_iter(self, data, iter_type, div=None, **kwargs): """Serialize iterable. @@ -887,15 +948,13 @@ def serialize_iter(self, data, iter_type, div=None, **kwargs): serialization_ctxt['type'] should be same as data_type. - is_xml bool : If set, serialize as XML - :param list attr: Object to be serialized. + :param list data: Object to be serialized. :param str iter_type: Type of object in the iterable. - :param bool required: Whether the objects in the iterable must - not be None or empty. :param str div: If set, this str will be used to combine the elements in the iterable into a combined string. Default is 'None'. - :keyword bool do_quote: Whether to quote the serialized result of each iterable element. Defaults to False. :rtype: list, str + :return: serialized iterable """ if isinstance(data, str): raise SerializationError("Refuse str type as a valid iter type.") @@ -950,9 +1009,8 @@ def serialize_dict(self, attr, dict_type, **kwargs): :param dict attr: Object to be serialized. :param str dict_type: Type of object in the dictionary. - :param bool required: Whether the objects in the dictionary must - not be None or empty. :rtype: dict + :return: serialized dictionary """ serialization_ctxt = kwargs.get("serialization_ctxt", {}) serialized = {} @@ -976,7 +1034,7 @@ def serialize_dict(self, attr, dict_type, **kwargs): return serialized - def serialize_object(self, attr, **kwargs): + def serialize_object(self, attr, **kwargs): # pylint: disable=too-many-return-statements """Serialize a generic object. This will be handled as a dictionary. If object passed in is not a basic type (str, int, float, dict, list) it will simply be @@ -984,6 +1042,7 @@ def serialize_object(self, attr, **kwargs): :param dict attr: Object to be serialized. :rtype: dict or str + :return: serialized object """ if attr is None: return None @@ -994,7 +1053,7 @@ def serialize_object(self, attr, **kwargs): return self.serialize_basic(attr, self.basic_types[obj_type], **kwargs) if obj_type is _long_type: return self.serialize_long(attr) - if obj_type is unicode_str: + if obj_type is str: return self.serialize_unicode(attr) if obj_type is datetime.datetime: return self.serialize_iso(attr) @@ -1008,7 +1067,7 @@ def serialize_object(self, attr, **kwargs): return self.serialize_decimal(attr) # If it's a model or I know this dependency, serialize as a Model - elif obj_type in self.dependencies.values() or isinstance(attr, Model): + if obj_type in self.dependencies.values() or isinstance(attr, Model): return self._serialize(attr) if obj_type == dict: @@ -1039,56 +1098,61 @@ def serialize_enum(attr, enum_obj=None): try: enum_obj(result) # type: ignore return result - except ValueError: + except ValueError as exc: for enum_value in enum_obj: # type: ignore if enum_value.value.lower() == str(attr).lower(): return enum_value.value error = "{!r} is not valid value for enum {!r}" - raise SerializationError(error.format(attr, enum_obj)) + raise SerializationError(error.format(attr, enum_obj)) from exc @staticmethod - def serialize_bytearray(attr, **kwargs): + def serialize_bytearray(attr, **kwargs): # pylint: disable=unused-argument """Serialize bytearray into base-64 string. - :param attr: Object to be serialized. + :param str attr: Object to be serialized. :rtype: str + :return: serialized base64 """ return b64encode(attr).decode() @staticmethod - def serialize_base64(attr, **kwargs): + def serialize_base64(attr, **kwargs): # pylint: disable=unused-argument """Serialize str into base-64 string. - :param attr: Object to be serialized. + :param str attr: Object to be serialized. :rtype: str + :return: serialized base64 """ encoded = b64encode(attr).decode("ascii") return encoded.strip("=").replace("+", "-").replace("/", "_") @staticmethod - def serialize_decimal(attr, **kwargs): + def serialize_decimal(attr, **kwargs): # pylint: disable=unused-argument """Serialize Decimal object to float. - :param attr: Object to be serialized. + :param decimal attr: Object to be serialized. :rtype: float + :return: serialized decimal """ return float(attr) @staticmethod - def serialize_long(attr, **kwargs): + def serialize_long(attr, **kwargs): # pylint: disable=unused-argument """Serialize long (Py2) or int (Py3). - :param attr: Object to be serialized. + :param int attr: Object to be serialized. :rtype: int/long + :return: serialized long """ return _long_type(attr) @staticmethod - def serialize_date(attr, **kwargs): + def serialize_date(attr, **kwargs): # pylint: disable=unused-argument """Serialize Date object into ISO-8601 formatted string. :param Date attr: Object to be serialized. :rtype: str + :return: serialized date """ if isinstance(attr, str): attr = isodate.parse_date(attr) @@ -1096,11 +1160,12 @@ def serialize_date(attr, **kwargs): return t @staticmethod - def serialize_time(attr, **kwargs): + def serialize_time(attr, **kwargs): # pylint: disable=unused-argument """Serialize Time object into ISO-8601 formatted string. :param datetime.time attr: Object to be serialized. :rtype: str + :return: serialized time """ if isinstance(attr, str): attr = isodate.parse_time(attr) @@ -1110,30 +1175,32 @@ def serialize_time(attr, **kwargs): return t @staticmethod - def serialize_duration(attr, **kwargs): + def serialize_duration(attr, **kwargs): # pylint: disable=unused-argument """Serialize TimeDelta object into ISO-8601 formatted string. :param TimeDelta attr: Object to be serialized. :rtype: str + :return: serialized duration """ if isinstance(attr, str): attr = isodate.parse_duration(attr) return isodate.duration_isoformat(attr) @staticmethod - def serialize_rfc(attr, **kwargs): + def serialize_rfc(attr, **kwargs): # pylint: disable=unused-argument """Serialize Datetime object into RFC-1123 formatted string. :param Datetime attr: Object to be serialized. :rtype: str :raises: TypeError if format invalid. + :return: serialized rfc """ try: if not attr.tzinfo: _LOGGER.warning("Datetime with no tzinfo will be considered UTC.") utc = attr.utctimetuple() - except AttributeError: - raise TypeError("RFC1123 object must be valid Datetime object.") + except AttributeError as exc: + raise TypeError("RFC1123 object must be valid Datetime object.") from exc return "{}, {:02} {} {:04} {:02}:{:02}:{:02} GMT".format( Serializer.days[utc.tm_wday], @@ -1146,12 +1213,13 @@ def serialize_rfc(attr, **kwargs): ) @staticmethod - def serialize_iso(attr, **kwargs): + def serialize_iso(attr, **kwargs): # pylint: disable=unused-argument """Serialize Datetime object into ISO-8601 formatted string. :param Datetime attr: Object to be serialized. :rtype: str :raises: SerializationError if format invalid. + :return: serialized iso """ if isinstance(attr, str): attr = isodate.parse_datetime(attr) @@ -1177,13 +1245,14 @@ def serialize_iso(attr, **kwargs): raise TypeError(msg) from err @staticmethod - def serialize_unix(attr, **kwargs): + def serialize_unix(attr, **kwargs): # pylint: disable=unused-argument """Serialize Datetime object into IntTime format. This is represented as seconds. :param Datetime attr: Object to be serialized. :rtype: int :raises: SerializationError if format invalid + :return: serialied unix """ if isinstance(attr, int): return attr @@ -1191,11 +1260,11 @@ def serialize_unix(attr, **kwargs): if not attr.tzinfo: _LOGGER.warning("Datetime with no tzinfo will be considered UTC.") return int(calendar.timegm(attr.utctimetuple())) - except AttributeError: - raise TypeError("Unix time object must be valid Datetime object.") + except AttributeError as exc: + raise TypeError("Unix time object must be valid Datetime object.") from exc -def rest_key_extractor(attr, attr_desc, data): +def rest_key_extractor(attr, attr_desc, data): # pylint: disable=unused-argument key = attr_desc["key"] working_data = data @@ -1216,7 +1285,9 @@ def rest_key_extractor(attr, attr_desc, data): return working_data.get(key) -def rest_key_case_insensitive_extractor(attr, attr_desc, data): +def rest_key_case_insensitive_extractor( # pylint: disable=unused-argument, inconsistent-return-statements + attr, attr_desc, data +): key = attr_desc["key"] working_data = data @@ -1237,17 +1308,29 @@ def rest_key_case_insensitive_extractor(attr, attr_desc, data): return attribute_key_case_insensitive_extractor(key, None, working_data) -def last_rest_key_extractor(attr, attr_desc, data): - """Extract the attribute in "data" based on the last part of the JSON path key.""" +def last_rest_key_extractor(attr, attr_desc, data): # pylint: disable=unused-argument + """Extract the attribute in "data" based on the last part of the JSON path key. + + :param str attr: The attribute to extract + :param dict attr_desc: The attribute description + :param dict data: The data to extract from + :rtype: object + :returns: The extracted attribute + """ key = attr_desc["key"] dict_keys = _FLATTEN.split(key) return attribute_key_extractor(dict_keys[-1], None, data) -def last_rest_key_case_insensitive_extractor(attr, attr_desc, data): +def last_rest_key_case_insensitive_extractor(attr, attr_desc, data): # pylint: disable=unused-argument """Extract the attribute in "data" based on the last part of the JSON path key. This is the case insensitive version of "last_rest_key_extractor" + :param str attr: The attribute to extract + :param dict attr_desc: The attribute description + :param dict data: The data to extract from + :rtype: object + :returns: The extracted attribute """ key = attr_desc["key"] dict_keys = _FLATTEN.split(key) @@ -1284,7 +1367,7 @@ def _extract_name_from_internal_type(internal_type): return xml_name -def xml_key_extractor(attr, attr_desc, data): +def xml_key_extractor(attr, attr_desc, data): # pylint: disable=unused-argument,too-many-return-statements if isinstance(data, dict): return None @@ -1336,22 +1419,21 @@ def xml_key_extractor(attr, attr_desc, data): if is_iter_type: if is_wrapped: return None # is_wrapped no node, we want None - else: - return [] # not wrapped, assume empty list + return [] # not wrapped, assume empty list return None # Assume it's not there, maybe an optional node. # If is_iter_type and not wrapped, return all found children if is_iter_type: if not is_wrapped: return children - else: # Iter and wrapped, should have found one node only (the wrap one) - if len(children) != 1: - raise DeserializationError( - "Tried to deserialize an array not wrapped, and found several nodes '{}'. Maybe you should declare this array as wrapped?".format( - xml_name - ) + # Iter and wrapped, should have found one node only (the wrap one) + if len(children) != 1: + raise DeserializationError( + "Tried to deserialize an array not wrapped, and found several nodes '{}'. Maybe you should declare this array as wrapped?".format( # pylint: disable=line-too-long + xml_name ) - return list(children[0]) # Might be empty list and that's ok. + ) + return list(children[0]) # Might be empty list and that's ok. # Here it's not a itertype, we should have found one element only or empty if len(children) > 1: @@ -1359,7 +1441,7 @@ def xml_key_extractor(attr, attr_desc, data): return children[0] -class Deserializer(object): +class Deserializer: """Response object model deserializer. :param dict classes: Class type dictionary for deserializing complex types. @@ -1368,9 +1450,9 @@ class Deserializer(object): basic_types = {str: "str", int: "int", bool: "bool", float: "float"} - valid_date = re.compile(r"\d{4}[-]\d{2}[-]\d{2}T\d{2}:\d{2}:\d{2}" r"\.?\d*Z?[-+]?[\d{2}]?:?[\d{2}]?") + valid_date = re.compile(r"\d{4}[-]\d{2}[-]\d{2}T\d{2}:\d{2}:\d{2}\.?\d*Z?[-+]?[\d{2}]?:?[\d{2}]?") - def __init__(self, classes: Optional[Mapping[str, Type[ModelType]]] = None): + def __init__(self, classes: Optional[Mapping[str, type]] = None) -> None: self.deserialize_type = { "iso-8601": Deserializer.deserialize_iso, "rfc-1123": Deserializer.deserialize_rfc, @@ -1390,7 +1472,7 @@ def __init__(self, classes: Optional[Mapping[str, Type[ModelType]]] = None): "duration": (isodate.Duration, datetime.timedelta), "iso-8601": (datetime.datetime), } - self.dependencies: Dict[str, Type[ModelType]] = dict(classes) if classes else {} + self.dependencies: Dict[str, type] = dict(classes) if classes else {} self.key_extractors = [rest_key_extractor, xml_key_extractor] # Additional properties only works if the "rest_key_extractor" is used to # extract the keys. Making it to work whatever the key extractor is too much @@ -1408,11 +1490,12 @@ def __call__(self, target_obj, response_data, content_type=None): :param str content_type: Swagger "produces" if available. :raises: DeserializationError if deserialization fails. :return: Deserialized object. + :rtype: object """ data = self._unpack_content(response_data, content_type) return self._deserialize(target_obj, data) - def _deserialize(self, target_obj, data): + def _deserialize(self, target_obj, data): # pylint: disable=inconsistent-return-statements """Call the deserializer on a model. Data needs to be already deserialized as JSON or XML ElementTree @@ -1421,12 +1504,13 @@ def _deserialize(self, target_obj, data): :param object data: Object to deserialize. :raises: DeserializationError if deserialization fails. :return: Deserialized object. + :rtype: object """ # This is already a model, go recursive just in case if hasattr(data, "_attribute_map"): constants = [name for name, config in getattr(data, "_validation", {}).items() if config.get("constant")] try: - for attr, mapconfig in data._attribute_map.items(): + for attr, mapconfig in data._attribute_map.items(): # pylint: disable=protected-access if attr in constants: continue value = getattr(data, attr) @@ -1443,15 +1527,15 @@ def _deserialize(self, target_obj, data): response, class_name = self._classify_target(target_obj, data) - if isinstance(response, basestring): + if isinstance(response, str): return self.deserialize_data(data, response) - elif isinstance(response, type) and issubclass(response, Enum): + if isinstance(response, type) and issubclass(response, Enum): return self.deserialize_enum(data, response) - if data is None: + if data is None or data is CoreNull: return data try: - attributes = response._attribute_map # type: ignore + attributes = response._attribute_map # type: ignore # pylint: disable=protected-access d_attrs = {} for attr, attr_desc in attributes.items(): # Check empty string. If it's not empty, someone has a real "additionalProperties"... @@ -1481,9 +1565,8 @@ def _deserialize(self, target_obj, data): except (AttributeError, TypeError, KeyError) as err: msg = "Unable to deserialize to object: " + class_name # type: ignore raise DeserializationError(msg) from err - else: - additional_properties = self._build_additional_properties(attributes, data) - return self._instantiate_model(response, d_attrs, additional_properties) + additional_properties = self._build_additional_properties(attributes, data) + return self._instantiate_model(response, d_attrs, additional_properties) def _build_additional_properties(self, attribute_map, data): if not self.additional_properties_detection: @@ -1510,18 +1593,20 @@ def _classify_target(self, target, data): :param str target: The target object type to deserialize to. :param str/dict data: The response data to deserialize. + :return: The classified target object and its class name. + :rtype: tuple """ if target is None: return None, None - if isinstance(target, basestring): + if isinstance(target, str): try: target = self.dependencies[target] except KeyError: return target, target try: - target = target._classify(data, self.dependencies) + target = target._classify(data, self.dependencies) # type: ignore # pylint: disable=protected-access except AttributeError: pass # Target is not a Model, no classify return target, target.__class__.__name__ # type: ignore @@ -1536,10 +1621,12 @@ def failsafe_deserialize(self, target_obj, data, content_type=None): :param str target_obj: The target object type to deserialize to. :param str/dict data: The response data to deserialize. :param str content_type: Swagger "produces" if available. + :return: Deserialized object. + :rtype: object """ try: return self(target_obj, data, content_type=content_type) - except: + except: # pylint: disable=bare-except _LOGGER.debug( "Ran into a deserialization error. Ignoring since this is failsafe deserialization", exc_info=True ) @@ -1557,10 +1644,12 @@ def _unpack_content(raw_data, content_type=None): If raw_data is something else, bypass all logic and return it directly. - :param raw_data: Data to be processed. - :param content_type: How to parse if raw_data is a string/bytes. + :param obj raw_data: Data to be processed. + :param str content_type: How to parse if raw_data is a string/bytes. :raises JSONDecodeError: If JSON is requested and parsing is impossible. :raises UnicodeDecodeError: If bytes is not UTF8 + :rtype: object + :return: Unpacked content. """ # Assume this is enough to detect a Pipeline Response without importing it context = getattr(raw_data, "context", {}) @@ -1577,31 +1666,42 @@ def _unpack_content(raw_data, content_type=None): if hasattr(raw_data, "_content_consumed"): return RawDeserializer.deserialize_from_http_generics(raw_data.text, raw_data.headers) - if isinstance(raw_data, (basestring, bytes)) or hasattr(raw_data, "read"): + if isinstance(raw_data, (str, bytes)) or hasattr(raw_data, "read"): return RawDeserializer.deserialize_from_text(raw_data, content_type) # type: ignore return raw_data def _instantiate_model(self, response, attrs, additional_properties=None): """Instantiate a response model passing in deserialized args. - :param response: The response model class. - :param d_attrs: The deserialized response attributes. + :param Response response: The response model class. + :param dict attrs: The deserialized response attributes. + :param dict additional_properties: Additional properties to be set. + :rtype: Response + :return: The instantiated response model. """ if callable(response): subtype = getattr(response, "_subtype_map", {}) try: - readonly = [k for k, v in response._validation.items() if v.get("readonly")] - const = [k for k, v in response._validation.items() if v.get("constant")] + readonly = [ + k + for k, v in response._validation.items() # pylint: disable=protected-access # type: ignore + if v.get("readonly") + ] + const = [ + k + for k, v in response._validation.items() # pylint: disable=protected-access # type: ignore + if v.get("constant") + ] kwargs = {k: v for k, v in attrs.items() if k not in subtype and k not in readonly + const} response_obj = response(**kwargs) for attr in readonly: setattr(response_obj, attr, attrs.get(attr)) if additional_properties: - response_obj.additional_properties = additional_properties + response_obj.additional_properties = additional_properties # type: ignore return response_obj except TypeError as err: msg = "Unable to deserialize {} into model {}. ".format(kwargs, response) # type: ignore - raise DeserializationError(msg + str(err)) + raise DeserializationError(msg + str(err)) from err else: try: for attr, value in attrs.items(): @@ -1610,15 +1710,16 @@ def _instantiate_model(self, response, attrs, additional_properties=None): except Exception as exp: msg = "Unable to populate response model. " msg += "Type: {}, Error: {}".format(type(response), exp) - raise DeserializationError(msg) + raise DeserializationError(msg) from exp - def deserialize_data(self, data, data_type): + def deserialize_data(self, data, data_type): # pylint: disable=too-many-return-statements """Process data for deserialization according to data type. :param str data: The response string to be deserialized. :param str data_type: The type to deserialize to. :raises: DeserializationError if deserialization fails. :return: Deserialized object. + :rtype: object """ if data is None: return data @@ -1632,7 +1733,11 @@ def deserialize_data(self, data, data_type): if isinstance(data, self.deserialize_expected_types.get(data_type, tuple())): return data - is_a_text_parsing_type = lambda x: x not in ["object", "[]", r"{}"] + is_a_text_parsing_type = lambda x: x not in [ # pylint: disable=unnecessary-lambda-assignment + "object", + "[]", + r"{}", + ] if isinstance(data, ET.Element) and is_a_text_parsing_type(data_type) and not data.text: return None data_val = self.deserialize_type[data_type](data) @@ -1652,14 +1757,14 @@ def deserialize_data(self, data, data_type): msg = "Unable to deserialize response data." msg += " Data: {}, {}".format(data, data_type) raise DeserializationError(msg) from err - else: - return self._deserialize(obj_type, data) + return self._deserialize(obj_type, data) def deserialize_iter(self, attr, iter_type): """Deserialize an iterable. :param list attr: Iterable to be deserialized. :param str iter_type: The type of object in the iterable. + :return: Deserialized iterable. :rtype: list """ if attr is None: @@ -1676,6 +1781,7 @@ def deserialize_dict(self, attr, dict_type): :param dict/list attr: Dictionary to be deserialized. Also accepts a list of key, value pairs. :param str dict_type: The object type of the items in the dictionary. + :return: Deserialized dictionary. :rtype: dict """ if isinstance(attr, list): @@ -1686,11 +1792,12 @@ def deserialize_dict(self, attr, dict_type): attr = {el.tag: el.text for el in attr} return {k: self.deserialize_data(v, dict_type) for k, v in attr.items()} - def deserialize_object(self, attr, **kwargs): + def deserialize_object(self, attr, **kwargs): # pylint: disable=too-many-return-statements """Deserialize a generic object. This will be handled as a dictionary. :param dict attr: Dictionary to be deserialized. + :return: Deserialized object. :rtype: dict :raises: TypeError if non-builtin datatype encountered. """ @@ -1699,7 +1806,7 @@ def deserialize_object(self, attr, **kwargs): if isinstance(attr, ET.Element): # Do no recurse on XML, just return the tree as-is return attr - if isinstance(attr, basestring): + if isinstance(attr, str): return self.deserialize_basic(attr, "str") obj_type = type(attr) if obj_type in self.basic_types: @@ -1725,11 +1832,10 @@ def deserialize_object(self, attr, **kwargs): pass return deserialized - else: - error = "Cannot deserialize generic object with type: " - raise TypeError(error + str(obj_type)) + error = "Cannot deserialize generic object with type: " + raise TypeError(error + str(obj_type)) - def deserialize_basic(self, attr, data_type): + def deserialize_basic(self, attr, data_type): # pylint: disable=too-many-return-statements """Deserialize basic builtin data type from string. Will attempt to convert to str, int, float and bool. This function will also accept '1', '0', 'true' and 'false' as @@ -1737,6 +1843,7 @@ def deserialize_basic(self, attr, data_type): :param str attr: response string to be deserialized. :param str data_type: deserialization data type. + :return: Deserialized basic type. :rtype: str, int, float or bool :raises: TypeError if string format is not valid. """ @@ -1748,24 +1855,23 @@ def deserialize_basic(self, attr, data_type): if data_type == "str": # None or '', node is empty string. return "" - else: - # None or '', node with a strong type is None. - # Don't try to model "empty bool" or "empty int" - return None + # None or '', node with a strong type is None. + # Don't try to model "empty bool" or "empty int" + return None if data_type == "bool": if attr in [True, False, 1, 0]: return bool(attr) - elif isinstance(attr, basestring): + if isinstance(attr, str): if attr.lower() in ["true", "1"]: return True - elif attr.lower() in ["false", "0"]: + if attr.lower() in ["false", "0"]: return False raise TypeError("Invalid boolean value: {}".format(attr)) if data_type == "str": return self.deserialize_unicode(attr) - return eval(data_type)(attr) # nosec + return eval(data_type)(attr) # nosec # pylint: disable=eval-used @staticmethod def deserialize_unicode(data): @@ -1773,6 +1879,7 @@ def deserialize_unicode(data): as a string. :param str data: response string to be deserialized. + :return: Deserialized string. :rtype: str or unicode """ # We might be here because we have an enum modeled as string, @@ -1786,8 +1893,7 @@ def deserialize_unicode(data): return data except NameError: return str(data) - else: - return str(data) + return str(data) @staticmethod def deserialize_enum(data, enum_obj): @@ -1799,6 +1905,7 @@ def deserialize_enum(data, enum_obj): :param str data: Response string to be deserialized. If this value is None or invalid it will be returned as-is. :param Enum enum_obj: Enum object to deserialize to. + :return: Deserialized enum object. :rtype: Enum """ if isinstance(data, enum_obj) or data is None: @@ -1809,9 +1916,9 @@ def deserialize_enum(data, enum_obj): # Workaround. We might consider remove it in the future. try: return list(enum_obj.__members__.values())[data] - except IndexError: + except IndexError as exc: error = "{!r} is not a valid index for enum {!r}" - raise DeserializationError(error.format(data, enum_obj)) + raise DeserializationError(error.format(data, enum_obj)) from exc try: return enum_obj(str(data)) except ValueError: @@ -1827,6 +1934,7 @@ def deserialize_bytearray(attr): """Deserialize string into bytearray. :param str attr: response string to be deserialized. + :return: Deserialized bytearray :rtype: bytearray :raises: TypeError if string format invalid. """ @@ -1839,6 +1947,7 @@ def deserialize_base64(attr): """Deserialize base64 encoded string into string. :param str attr: response string to be deserialized. + :return: Deserialized base64 string :rtype: bytearray :raises: TypeError if string format invalid. """ @@ -1854,8 +1963,9 @@ def deserialize_decimal(attr): """Deserialize string into Decimal object. :param str attr: response string to be deserialized. - :rtype: Decimal + :return: Deserialized decimal :raises: DeserializationError if string format invalid. + :rtype: decimal """ if isinstance(attr, ET.Element): attr = attr.text @@ -1870,6 +1980,7 @@ def deserialize_long(attr): """Deserialize string into long (Py2) or int (Py3). :param str attr: response string to be deserialized. + :return: Deserialized int :rtype: long or int :raises: ValueError if string format invalid. """ @@ -1882,6 +1993,7 @@ def deserialize_duration(attr): """Deserialize ISO-8601 formatted string into TimeDelta object. :param str attr: response string to be deserialized. + :return: Deserialized duration :rtype: TimeDelta :raises: DeserializationError if string format invalid. """ @@ -1892,14 +2004,14 @@ def deserialize_duration(attr): except (ValueError, OverflowError, AttributeError) as err: msg = "Cannot deserialize duration object." raise DeserializationError(msg) from err - else: - return duration + return duration @staticmethod def deserialize_date(attr): """Deserialize ISO-8601 formatted string into Date object. :param str attr: response string to be deserialized. + :return: Deserialized date :rtype: Date :raises: DeserializationError if string format invalid. """ @@ -1915,6 +2027,7 @@ def deserialize_time(attr): """Deserialize ISO-8601 formatted string into time object. :param str attr: response string to be deserialized. + :return: Deserialized time :rtype: datetime.time :raises: DeserializationError if string format invalid. """ @@ -1929,6 +2042,7 @@ def deserialize_rfc(attr): """Deserialize RFC-1123 formatted string into Datetime object. :param str attr: response string to be deserialized. + :return: Deserialized RFC datetime :rtype: Datetime :raises: DeserializationError if string format invalid. """ @@ -1944,14 +2058,14 @@ def deserialize_rfc(attr): except ValueError as err: msg = "Cannot deserialize to rfc datetime object." raise DeserializationError(msg) from err - else: - return date_obj + return date_obj @staticmethod def deserialize_iso(attr): """Deserialize ISO-8601 formatted string into Datetime object. :param str attr: response string to be deserialized. + :return: Deserialized ISO datetime :rtype: Datetime :raises: DeserializationError if string format invalid. """ @@ -1981,8 +2095,7 @@ def deserialize_iso(attr): except (ValueError, OverflowError, AttributeError) as err: msg = "Cannot deserialize datetime object." raise DeserializationError(msg) from err - else: - return date_obj + return date_obj @staticmethod def deserialize_unix(attr): @@ -1990,6 +2103,7 @@ def deserialize_unix(attr): This is represented as seconds. :param int attr: Object to be serialized. + :return: Deserialized datetime :rtype: Datetime :raises: DeserializationError if format invalid """ @@ -2001,5 +2115,4 @@ def deserialize_unix(attr): except ValueError as err: msg = "Cannot deserialize to unix datetime object." raise DeserializationError(msg) from err - else: - return date_obj + return date_obj diff --git a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/_validation.py b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/_validation.py new file mode 100644 index 000000000000..752b2822f9d3 --- /dev/null +++ b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/_validation.py @@ -0,0 +1,50 @@ +# -------------------------------------------------------------------------- +# 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. +# -------------------------------------------------------------------------- +import functools + + +def api_version_validation(**kwargs): + params_added_on = kwargs.pop("params_added_on", {}) + method_added_on = kwargs.pop("method_added_on", "") + + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + try: + # this assumes the client has an _api_version attribute + client = args[0] + client_api_version = client._config.api_version # pylint: disable=protected-access + except AttributeError: + return func(*args, **kwargs) + + if method_added_on > client_api_version: + raise ValueError( + f"'{func.__name__}' is not available in API version " + f"{client_api_version}. Pass service API version {method_added_on} or newer to your client." + ) + + unsupported = { + parameter: api_version + for api_version, parameters in params_added_on.items() + for parameter in parameters + if parameter in kwargs and api_version > client_api_version + } + if unsupported: + raise ValueError( + "".join( + [ + f"'{param}' is not available in API version {client_api_version}. " + f"Use service API version {version} or newer.\n" + for param, version in unsupported.items() + ] + ) + ) + return func(*args, **kwargs) + + return wrapper + + return decorator diff --git a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/_vendor.py b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/_vendor.py index 3a907524ca0c..dbb76566f120 100644 --- a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/_vendor.py +++ b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/_vendor.py @@ -11,7 +11,6 @@ from ._configuration import BlocklistClientConfiguration, ContentSafetyClientConfiguration if TYPE_CHECKING: - # pylint: disable=unused-import,ungrouped-imports from azure.core import PipelineClient from ._serialization import Deserializer, Serializer diff --git a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/aio/__init__.py b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/aio/__init__.py index 93236600dc7b..03c3d4a27675 100644 --- a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/aio/__init__.py +++ b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/aio/__init__.py @@ -5,13 +5,19 @@ # Code generated by Microsoft (R) Python Code Generator. # Changes may cause incorrect behavior and will be lost if the code is regenerated. # -------------------------------------------------------------------------- +# pylint: disable=wrong-import-position -from ._client import ContentSafetyClient -from ._client import BlocklistClient +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from ._patch import * # pylint: disable=unused-wildcard-import + +from ._client import ContentSafetyClient # type: ignore +from ._client import BlocklistClient # type: ignore try: from ._patch import __all__ as _patch_all - from ._patch import * # pylint: disable=unused-wildcard-import + from ._patch import * except ImportError: _patch_all = [] from ._patch import patch_sdk as _patch_sdk @@ -20,6 +26,6 @@ "ContentSafetyClient", "BlocklistClient", ] -__all__.extend([p for p in _patch_all if p not in __all__]) +__all__.extend([p for p in _patch_all if p not in __all__]) # pyright: ignore _patch_sdk() diff --git a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/aio/_client.py b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/aio/_client.py index 5b3f2b75f678..bcd2773e22cd 100644 --- a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/aio/_client.py +++ b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/aio/_client.py @@ -8,6 +8,7 @@ from copy import deepcopy 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,21 +20,20 @@ from ._operations import BlocklistClientOperationsMixin, ContentSafetyClientOperationsMixin if TYPE_CHECKING: - # pylint: disable=unused-import,ungrouped-imports from azure.core.credentials_async import AsyncTokenCredential -class ContentSafetyClient(ContentSafetyClientOperationsMixin): # pylint: disable=client-accepts-api-version-keyword +class ContentSafetyClient(ContentSafetyClientOperationsMixin): """ContentSafetyClient. :param endpoint: Supported Cognitive Services endpoints (protocol and hostname, for example: https://:code:``.cognitiveservices.azure.com). Required. :type endpoint: str - :param credential: Credential needed for the client to connect to Azure. Is either a + :param credential: Credential used to authenticate requests to the service. Is either a AzureKeyCredential type or a TokenCredential type. Required. :type credential: ~azure.core.credentials.AzureKeyCredential or ~azure.core.credentials_async.AsyncTokenCredential - :keyword api_version: The API version to use for this operation. Default value is "2023-10-01". + :keyword api_version: The API version to use for this operation. Default value is "2024-09-01". Note that overriding this default value may result in unsupported behavior. :paramtype api_version: str """ @@ -88,7 +88,7 @@ def send_request( request_copy = deepcopy(request) path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str"), } request_copy.url = self._client.format_url(request_copy.url, **path_format_arguments) @@ -97,7 +97,7 @@ def send_request( async def close(self) -> None: await self._client.close() - async def __aenter__(self) -> "ContentSafetyClient": + async def __aenter__(self) -> Self: await self._client.__aenter__() return self @@ -105,17 +105,17 @@ async def __aexit__(self, *exc_details: Any) -> None: await self._client.__aexit__(*exc_details) -class BlocklistClient(BlocklistClientOperationsMixin): # pylint: disable=client-accepts-api-version-keyword +class BlocklistClient(BlocklistClientOperationsMixin): """BlocklistClient. :param endpoint: Supported Cognitive Services endpoints (protocol and hostname, for example: https://:code:``.cognitiveservices.azure.com). Required. :type endpoint: str - :param credential: Credential needed for the client to connect to Azure. Is either a + :param credential: Credential used to authenticate requests to the service. Is either a AzureKeyCredential type or a TokenCredential type. Required. :type credential: ~azure.core.credentials.AzureKeyCredential or ~azure.core.credentials_async.AsyncTokenCredential - :keyword api_version: The API version to use for this operation. Default value is "2023-10-01". + :keyword api_version: The API version to use for this operation. Default value is "2024-09-01". Note that overriding this default value may result in unsupported behavior. :paramtype api_version: str """ @@ -170,7 +170,7 @@ def send_request( request_copy = deepcopy(request) path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str"), } request_copy.url = self._client.format_url(request_copy.url, **path_format_arguments) @@ -179,7 +179,7 @@ def send_request( async def close(self) -> None: await self._client.close() - async def __aenter__(self) -> "BlocklistClient": + async def __aenter__(self) -> Self: await self._client.__aenter__() return self diff --git a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/aio/_configuration.py b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/aio/_configuration.py index c19698582c6d..18a6604cdfeb 100644 --- a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/aio/_configuration.py +++ b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/aio/_configuration.py @@ -14,11 +14,10 @@ from .._version import VERSION if TYPE_CHECKING: - # pylint: disable=unused-import,ungrouped-imports from azure.core.credentials_async import AsyncTokenCredential -class ContentSafetyClientConfiguration: # pylint: disable=too-many-instance-attributes,name-too-long +class ContentSafetyClientConfiguration: # pylint: disable=too-many-instance-attributes """Configuration for ContentSafetyClient. Note that all parameters used to create this instance are saved as instance @@ -27,11 +26,11 @@ class ContentSafetyClientConfiguration: # pylint: disable=too-many-instance-att :param endpoint: Supported Cognitive Services endpoints (protocol and hostname, for example: https://:code:``.cognitiveservices.azure.com). Required. :type endpoint: str - :param credential: Credential needed for the client to connect to Azure. Is either a + :param credential: Credential used to authenticate requests to the service. Is either a AzureKeyCredential type or a TokenCredential type. Required. :type credential: ~azure.core.credentials.AzureKeyCredential or ~azure.core.credentials_async.AsyncTokenCredential - :keyword api_version: The API version to use for this operation. Default value is "2023-10-01". + :keyword api_version: The API version to use for this operation. Default value is "2024-09-01". Note that overriding this default value may result in unsupported behavior. :paramtype api_version: str """ @@ -39,7 +38,7 @@ class ContentSafetyClientConfiguration: # pylint: disable=too-many-instance-att def __init__( self, endpoint: str, credential: Union[AzureKeyCredential, "AsyncTokenCredential"], **kwargs: Any ) -> None: - api_version: str = kwargs.pop("api_version", "2023-10-01") + api_version: str = kwargs.pop("api_version", "2024-09-01") if endpoint is None: raise ValueError("Parameter 'endpoint' must not be None.") @@ -75,7 +74,7 @@ def _configure(self, **kwargs: Any) -> None: self.authentication_policy = self._infer_policy(**kwargs) -class BlocklistClientConfiguration: # pylint: disable=too-many-instance-attributes,name-too-long +class BlocklistClientConfiguration: # pylint: disable=too-many-instance-attributes """Configuration for BlocklistClient. Note that all parameters used to create this instance are saved as instance @@ -84,11 +83,11 @@ class BlocklistClientConfiguration: # pylint: disable=too-many-instance-attribu :param endpoint: Supported Cognitive Services endpoints (protocol and hostname, for example: https://:code:``.cognitiveservices.azure.com). Required. :type endpoint: str - :param credential: Credential needed for the client to connect to Azure. Is either a + :param credential: Credential used to authenticate requests to the service. Is either a AzureKeyCredential type or a TokenCredential type. Required. :type credential: ~azure.core.credentials.AzureKeyCredential or ~azure.core.credentials_async.AsyncTokenCredential - :keyword api_version: The API version to use for this operation. Default value is "2023-10-01". + :keyword api_version: The API version to use for this operation. Default value is "2024-09-01". Note that overriding this default value may result in unsupported behavior. :paramtype api_version: str """ @@ -96,7 +95,7 @@ class BlocklistClientConfiguration: # pylint: disable=too-many-instance-attribu def __init__( self, endpoint: str, credential: Union[AzureKeyCredential, "AsyncTokenCredential"], **kwargs: Any ) -> None: - api_version: str = kwargs.pop("api_version", "2023-10-01") + api_version: str = kwargs.pop("api_version", "2024-09-01") if endpoint is None: raise ValueError("Parameter 'endpoint' must not be None.") diff --git a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/aio/_operations/__init__.py b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/aio/_operations/__init__.py index 2f88364a4715..2096bab0057c 100644 --- a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/aio/_operations/__init__.py +++ b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/aio/_operations/__init__.py @@ -5,17 +5,23 @@ # Code generated by Microsoft (R) Python Code Generator. # Changes may cause incorrect behavior and will be lost if the code is regenerated. # -------------------------------------------------------------------------- +# pylint: disable=wrong-import-position -from ._operations import ContentSafetyClientOperationsMixin -from ._operations import BlocklistClientOperationsMixin +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from ._patch import * # pylint: disable=unused-wildcard-import + +from ._operations import ContentSafetyClientOperationsMixin # type: ignore +from ._operations import BlocklistClientOperationsMixin # type: ignore from ._patch import __all__ as _patch_all -from ._patch import * # pylint: disable=unused-wildcard-import +from ._patch import * from ._patch import patch_sdk as _patch_sdk __all__ = [ "ContentSafetyClientOperationsMixin", "BlocklistClientOperationsMixin", ] -__all__.extend([p for p in _patch_all if p not in __all__]) +__all__.extend([p for p in _patch_all if p not in __all__]) # pyright: ignore _patch_sdk() diff --git a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/aio/_operations/_operations.py b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/aio/_operations/_operations.py index 51040e157e2b..5c33c8811201 100644 --- a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/aio/_operations/_operations.py +++ b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/aio/_operations/_operations.py @@ -19,6 +19,8 @@ ResourceExistsError, ResourceNotFoundError, ResourceNotModifiedError, + StreamClosedError, + StreamConsumedError, map_error, ) from azure.core.pipeline import PipelineResponse @@ -40,19 +42,155 @@ build_blocklist_remove_blocklist_items_request, build_content_safety_analyze_image_request, build_content_safety_analyze_text_request, + build_content_safety_detect_text_protected_material_request, + build_content_safety_shield_prompt_request, ) +from ..._validation import api_version_validation from .._vendor import BlocklistClientMixinABC, ContentSafetyClientMixinABC if sys.version_info >= (3, 9): from collections.abc import MutableMapping else: - from typing import MutableMapping # type: ignore # pylint: disable=ungrouped-imports + from typing import MutableMapping # type: ignore JSON = MutableMapping[str, Any] # pylint: disable=unsubscriptable-object T = TypeVar("T") ClsType = Optional[Callable[[PipelineResponse[HttpRequest, AsyncHttpResponse], T, Dict[str, Any]], Any]] class ContentSafetyClientOperationsMixin(ContentSafetyClientMixinABC): + + @overload + async def analyze_image( + self, options: _models.AnalyzeImageOptions, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.AnalyzeImageResult: + """Analyze Image. + + A synchronous API for the analysis of potentially harmful image content. Currently, it supports + four categories: Hate, SelfHarm, Sexual, and Violence. + + :param options: The image analysis request. Required. + :type options: ~azure.ai.contentsafety.models.AnalyzeImageOptions + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: AnalyzeImageResult. The AnalyzeImageResult is compatible with MutableMapping + :rtype: ~azure.ai.contentsafety.models.AnalyzeImageResult + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + async def analyze_image( + self, options: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.AnalyzeImageResult: + """Analyze Image. + + A synchronous API for the analysis of potentially harmful image content. Currently, it supports + four categories: Hate, SelfHarm, Sexual, and Violence. + + :param options: The image analysis request. Required. + :type options: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: AnalyzeImageResult. The AnalyzeImageResult is compatible with MutableMapping + :rtype: ~azure.ai.contentsafety.models.AnalyzeImageResult + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + async def analyze_image( + self, options: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.AnalyzeImageResult: + """Analyze Image. + + A synchronous API for the analysis of potentially harmful image content. Currently, it supports + four categories: Hate, SelfHarm, Sexual, and Violence. + + :param options: The image analysis request. Required. + :type options: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: AnalyzeImageResult. The AnalyzeImageResult is compatible with MutableMapping + :rtype: ~azure.ai.contentsafety.models.AnalyzeImageResult + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @distributed_trace_async + async def analyze_image( + self, options: Union[_models.AnalyzeImageOptions, JSON, IO[bytes]], **kwargs: Any + ) -> _models.AnalyzeImageResult: + """Analyze Image. + + A synchronous API for the analysis of potentially harmful image content. Currently, it supports + four categories: Hate, SelfHarm, Sexual, and Violence. + + :param options: The image analysis request. Is one of the following types: AnalyzeImageOptions, + JSON, IO[bytes] Required. + :type options: ~azure.ai.contentsafety.models.AnalyzeImageOptions or JSON or IO[bytes] + :return: AnalyzeImageResult. The AnalyzeImageResult is compatible with MutableMapping + :rtype: ~azure.ai.contentsafety.models.AnalyzeImageResult + :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 = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.AnalyzeImageResult] = kwargs.pop("cls", None) + + content_type = content_type or "application/json" + _content = None + if isinstance(options, (IOBase, bytes)): + _content = options + else: + _content = json.dumps(options, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + + _request = build_content_safety_analyze_image_request( + content_type=content_type, + api_version=self._config.api_version, + content=_content, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str"), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = await self._client._pipeline.run( # type: ignore # 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() + else: + deserialized = _deserialize(_models.AnalyzeImageResult, response.json()) + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore + @overload async def analyze_text( self, options: _models.AnalyzeTextOptions, *, content_type: str = "application/json", **kwargs: Any @@ -67,8 +205,6 @@ async def analyze_text( :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. :return: AnalyzeTextResult. The AnalyzeTextResult is compatible with MutableMapping :rtype: ~azure.ai.contentsafety.models.AnalyzeTextResult :raises ~azure.core.exceptions.HttpResponseError: @@ -88,8 +224,6 @@ async def analyze_text( :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. :return: AnalyzeTextResult. The AnalyzeTextResult is compatible with MutableMapping :rtype: ~azure.ai.contentsafety.models.AnalyzeTextResult :raises ~azure.core.exceptions.HttpResponseError: @@ -109,8 +243,6 @@ async def analyze_text( :keyword content_type: Body Parameter content-type. Content type parameter for binary body. Default value is "application/json". :paramtype content_type: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. :return: AnalyzeTextResult. The AnalyzeTextResult is compatible with MutableMapping :rtype: ~azure.ai.contentsafety.models.AnalyzeTextResult :raises ~azure.core.exceptions.HttpResponseError: @@ -128,16 +260,11 @@ async def analyze_text( :param options: The text analysis request. Is one of the following types: AnalyzeTextOptions, JSON, IO[bytes] Required. :type options: ~azure.ai.contentsafety.models.AnalyzeTextOptions or JSON or IO[bytes] - :keyword content_type: Body parameter Content-Type. Known values are: application/json. Default - value is None. - :paramtype content_type: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. :return: AnalyzeTextResult. The AnalyzeTextResult is compatible with MutableMapping :rtype: ~azure.ai.contentsafety.models.AnalyzeTextResult :raises ~azure.core.exceptions.HttpResponseError: """ - error_map = { + error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError, @@ -166,7 +293,7 @@ async def analyze_text( params=_params, ) path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str"), } _request.url = self._client.format_url(_request.url, **path_format_arguments) @@ -179,7 +306,10 @@ async def analyze_text( if response.status_code not in [200]: if _stream: - await response.read() # Load the body in memory and close the socket + 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) @@ -194,90 +324,226 @@ async def analyze_text( return deserialized # type: ignore @overload - async def analyze_image( - self, options: _models.AnalyzeImageOptions, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AnalyzeImageResult: - """Analyze Image. + async def detect_text_protected_material( + self, + options: _models.DetectTextProtectedMaterialOptions, + *, + content_type: str = "application/json", + **kwargs: Any + ) -> _models.DetectTextProtectedMaterialResult: + """Detect Protected Material for Text. - A synchronous API for the analysis of potentially harmful image content. Currently, it supports - four categories: Hate, SelfHarm, Sexual, and Violence. + A synchronous API for detecting protected material in the given text. - :param options: The image analysis request. Required. - :type options: ~azure.ai.contentsafety.models.AnalyzeImageOptions + :param options: The request body to be detected, which may contain protected material. + Required. + :type options: ~azure.ai.contentsafety.models.DetectTextProtectedMaterialOptions :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. - :return: AnalyzeImageResult. The AnalyzeImageResult is compatible with MutableMapping - :rtype: ~azure.ai.contentsafety.models.AnalyzeImageResult + :return: DetectTextProtectedMaterialResult. The DetectTextProtectedMaterialResult is compatible + with MutableMapping + :rtype: ~azure.ai.contentsafety.models.DetectTextProtectedMaterialResult :raises ~azure.core.exceptions.HttpResponseError: """ @overload - async def analyze_image( + async def detect_text_protected_material( self, options: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AnalyzeImageResult: - """Analyze Image. + ) -> _models.DetectTextProtectedMaterialResult: + """Detect Protected Material for Text. - A synchronous API for the analysis of potentially harmful image content. Currently, it supports - four categories: Hate, SelfHarm, Sexual, and Violence. + A synchronous API for detecting protected material in the given text. - :param options: The image analysis request. Required. + :param options: The request body to be detected, which may contain protected material. + Required. :type options: JSON :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. - :return: AnalyzeImageResult. The AnalyzeImageResult is compatible with MutableMapping - :rtype: ~azure.ai.contentsafety.models.AnalyzeImageResult + :return: DetectTextProtectedMaterialResult. The DetectTextProtectedMaterialResult is compatible + with MutableMapping + :rtype: ~azure.ai.contentsafety.models.DetectTextProtectedMaterialResult :raises ~azure.core.exceptions.HttpResponseError: """ @overload - async def analyze_image( + async def detect_text_protected_material( self, options: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AnalyzeImageResult: - """Analyze Image. + ) -> _models.DetectTextProtectedMaterialResult: + """Detect Protected Material for Text. - A synchronous API for the analysis of potentially harmful image content. Currently, it supports - four categories: Hate, SelfHarm, Sexual, and Violence. + A synchronous API for detecting protected material in the given text. - :param options: The image analysis request. Required. + :param options: The request body to be detected, which may contain protected material. + Required. :type options: IO[bytes] :keyword content_type: Body Parameter content-type. Content type parameter for binary body. Default value is "application/json". :paramtype content_type: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. - :return: AnalyzeImageResult. The AnalyzeImageResult is compatible with MutableMapping - :rtype: ~azure.ai.contentsafety.models.AnalyzeImageResult + :return: DetectTextProtectedMaterialResult. The DetectTextProtectedMaterialResult is compatible + with MutableMapping + :rtype: ~azure.ai.contentsafety.models.DetectTextProtectedMaterialResult :raises ~azure.core.exceptions.HttpResponseError: """ @distributed_trace_async - async def analyze_image( - self, options: Union[_models.AnalyzeImageOptions, JSON, IO[bytes]], **kwargs: Any - ) -> _models.AnalyzeImageResult: - """Analyze Image. + @api_version_validation( + method_added_on="2024-09-01", + params_added_on={"2024-09-01": ["api_version", "content_type", "accept"]}, + ) + async def detect_text_protected_material( + self, options: Union[_models.DetectTextProtectedMaterialOptions, JSON, IO[bytes]], **kwargs: Any + ) -> _models.DetectTextProtectedMaterialResult: + """Detect Protected Material for Text. + + A synchronous API for detecting protected material in the given text. + + :param options: The request body to be detected, which may contain protected material. Is one + of the following types: DetectTextProtectedMaterialOptions, JSON, IO[bytes] Required. + :type options: ~azure.ai.contentsafety.models.DetectTextProtectedMaterialOptions or JSON or + IO[bytes] + :return: DetectTextProtectedMaterialResult. The DetectTextProtectedMaterialResult is compatible + with MutableMapping + :rtype: ~azure.ai.contentsafety.models.DetectTextProtectedMaterialResult + :raises ~azure.core.exceptions.HttpResponseError: + """ + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) - A synchronous API for the analysis of potentially harmful image content. Currently, it supports - four categories: Hate, SelfHarm, Sexual, and Violence. + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} - :param options: The image analysis request. Is one of the following types: AnalyzeImageOptions, - JSON, IO[bytes] Required. - :type options: ~azure.ai.contentsafety.models.AnalyzeImageOptions or JSON or IO[bytes] - :keyword content_type: Body parameter Content-Type. Known values are: application/json. Default - value is None. + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.DetectTextProtectedMaterialResult] = kwargs.pop("cls", None) + + content_type = content_type or "application/json" + _content = None + if isinstance(options, (IOBase, bytes)): + _content = options + else: + _content = json.dumps(options, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + + _request = build_content_safety_detect_text_protected_material_request( + content_type=content_type, + api_version=self._config.api_version, + content=_content, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str"), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = await self._client._pipeline.run( # type: ignore # 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() + else: + deserialized = _deserialize(_models.DetectTextProtectedMaterialResult, response.json()) + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore + + @overload + async def shield_prompt( + self, options: _models.ShieldPromptOptions, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.ShieldPromptResult: + """Shield Prompt. + + A synchronous API for shielding prompt from direct and indirect injection attacks. + + :param options: The request body to be detected, which may contain direct or indirect injection + attacks. Required. + :type options: ~azure.ai.contentsafety.models.ShieldPromptOptions + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". :paramtype content_type: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. - :return: AnalyzeImageResult. The AnalyzeImageResult is compatible with MutableMapping - :rtype: ~azure.ai.contentsafety.models.AnalyzeImageResult + :return: ShieldPromptResult. The ShieldPromptResult is compatible with MutableMapping + :rtype: ~azure.ai.contentsafety.models.ShieldPromptResult + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + async def shield_prompt( + self, options: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.ShieldPromptResult: + """Shield Prompt. + + A synchronous API for shielding prompt from direct and indirect injection attacks. + + :param options: The request body to be detected, which may contain direct or indirect injection + attacks. Required. + :type options: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: ShieldPromptResult. The ShieldPromptResult is compatible with MutableMapping + :rtype: ~azure.ai.contentsafety.models.ShieldPromptResult + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + async def shield_prompt( + self, options: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.ShieldPromptResult: + """Shield Prompt. + + A synchronous API for shielding prompt from direct and indirect injection attacks. + + :param options: The request body to be detected, which may contain direct or indirect injection + attacks. Required. + :type options: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: ShieldPromptResult. The ShieldPromptResult is compatible with MutableMapping + :rtype: ~azure.ai.contentsafety.models.ShieldPromptResult + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @distributed_trace_async + @api_version_validation( + method_added_on="2024-09-01", + params_added_on={"2024-09-01": ["api_version", "content_type", "accept"]}, + ) + async def shield_prompt( + self, options: Union[_models.ShieldPromptOptions, JSON, IO[bytes]], **kwargs: Any + ) -> _models.ShieldPromptResult: + """Shield Prompt. + + A synchronous API for shielding prompt from direct and indirect injection attacks. + + :param options: The request body to be detected, which may contain direct or indirect injection + attacks. Is one of the following types: ShieldPromptOptions, JSON, IO[bytes] Required. + :type options: ~azure.ai.contentsafety.models.ShieldPromptOptions or JSON or IO[bytes] + :return: ShieldPromptResult. The ShieldPromptResult is compatible with MutableMapping + :rtype: ~azure.ai.contentsafety.models.ShieldPromptResult :raises ~azure.core.exceptions.HttpResponseError: """ - error_map = { + error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError, @@ -289,7 +555,7 @@ async def analyze_image( _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.AnalyzeImageResult] = kwargs.pop("cls", None) + cls: ClsType[_models.ShieldPromptResult] = kwargs.pop("cls", None) content_type = content_type or "application/json" _content = None @@ -298,7 +564,7 @@ async def analyze_image( else: _content = json.dumps(options, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_content_safety_analyze_image_request( + _request = build_content_safety_shield_prompt_request( content_type=content_type, api_version=self._config.api_version, content=_content, @@ -306,7 +572,7 @@ async def analyze_image( params=_params, ) path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str"), } _request.url = self._client.format_url(_request.url, **path_format_arguments) @@ -319,14 +585,17 @@ async def analyze_image( if response.status_code not in [200]: if _stream: - await response.read() # Load the body in memory and close the socket + 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() else: - deserialized = _deserialize(_models.AnalyzeImageResult, response.json()) + deserialized = _deserialize(_models.ShieldPromptResult, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -335,6 +604,7 @@ async def analyze_image( class BlocklistClientOperationsMixin(BlocklistClientMixinABC): + @overload async def add_or_update_blocklist_items( self, @@ -356,8 +626,6 @@ async def add_or_update_blocklist_items( :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. :return: AddOrUpdateTextBlocklistItemsResult. The AddOrUpdateTextBlocklistItemsResult is compatible with MutableMapping :rtype: ~azure.ai.contentsafety.models.AddOrUpdateTextBlocklistItemsResult @@ -380,8 +648,6 @@ async def add_or_update_blocklist_items( :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. :return: AddOrUpdateTextBlocklistItemsResult. The AddOrUpdateTextBlocklistItemsResult is compatible with MutableMapping :rtype: ~azure.ai.contentsafety.models.AddOrUpdateTextBlocklistItemsResult @@ -404,8 +670,6 @@ async def add_or_update_blocklist_items( :keyword content_type: Body Parameter content-type. Content type parameter for binary body. Default value is "application/json". :paramtype content_type: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. :return: AddOrUpdateTextBlocklistItemsResult. The AddOrUpdateTextBlocklistItemsResult is compatible with MutableMapping :rtype: ~azure.ai.contentsafety.models.AddOrUpdateTextBlocklistItemsResult @@ -430,17 +694,12 @@ async def add_or_update_blocklist_items( AddOrUpdateTextBlocklistItemsOptions, JSON, IO[bytes] Required. :type options: ~azure.ai.contentsafety.models.AddOrUpdateTextBlocklistItemsOptions or JSON or IO[bytes] - :keyword content_type: Body parameter Content-Type. Known values are: application/json. Default - value is None. - :paramtype content_type: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. :return: AddOrUpdateTextBlocklistItemsResult. The AddOrUpdateTextBlocklistItemsResult is compatible with MutableMapping :rtype: ~azure.ai.contentsafety.models.AddOrUpdateTextBlocklistItemsResult :raises ~azure.core.exceptions.HttpResponseError: """ - error_map = { + error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError, @@ -470,7 +729,7 @@ async def add_or_update_blocklist_items( params=_params, ) path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str"), } _request.url = self._client.format_url(_request.url, **path_format_arguments) @@ -483,7 +742,10 @@ async def add_or_update_blocklist_items( if response.status_code not in [200]: if _stream: - await response.read() # Load the body in memory and close the socket + 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) @@ -517,8 +779,6 @@ async def create_or_update_text_blocklist( :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/merge-patch+json". :paramtype content_type: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. :return: TextBlocklist. The TextBlocklist is compatible with MutableMapping :rtype: ~azure.ai.contentsafety.models.TextBlocklist :raises ~azure.core.exceptions.HttpResponseError: @@ -539,8 +799,6 @@ async def create_or_update_text_blocklist( :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/merge-patch+json". :paramtype content_type: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. :return: TextBlocklist. The TextBlocklist is compatible with MutableMapping :rtype: ~azure.ai.contentsafety.models.TextBlocklist :raises ~azure.core.exceptions.HttpResponseError: @@ -566,8 +824,6 @@ async def create_or_update_text_blocklist( :keyword content_type: Body Parameter content-type. Content type parameter for binary body. Default value is "application/merge-patch+json". :paramtype content_type: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. :return: TextBlocklist. The TextBlocklist is compatible with MutableMapping :rtype: ~azure.ai.contentsafety.models.TextBlocklist :raises ~azure.core.exceptions.HttpResponseError: @@ -586,15 +842,11 @@ async def create_or_update_text_blocklist( :param options: The resource instance. Is one of the following types: TextBlocklist, JSON, IO[bytes] Required. :type options: ~azure.ai.contentsafety.models.TextBlocklist or JSON or IO[bytes] - :keyword content_type: This request has a JSON Merge Patch body. Default value is None. - :paramtype content_type: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. :return: TextBlocklist. The TextBlocklist is compatible with MutableMapping :rtype: ~azure.ai.contentsafety.models.TextBlocklist :raises ~azure.core.exceptions.HttpResponseError: """ - error_map = { + error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError, @@ -624,7 +876,7 @@ async def create_or_update_text_blocklist( params=_params, ) path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str"), } _request.url = self._client.format_url(_request.url, **path_format_arguments) @@ -637,21 +889,17 @@ async def create_or_update_text_blocklist( if response.status_code not in [200, 201]: if _stream: - await response.read() # Load the body in memory and close the socket + 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 response.status_code == 200: - if _stream: - deserialized = response.iter_bytes() - else: - deserialized = _deserialize(_models.TextBlocklist, response.json()) - - if response.status_code == 201: - if _stream: - deserialized = response.iter_bytes() - else: - deserialized = _deserialize(_models.TextBlocklist, response.json()) + if _stream: + deserialized = response.iter_bytes() + else: + deserialized = _deserialize(_models.TextBlocklist, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -659,9 +907,7 @@ async def create_or_update_text_blocklist( return deserialized # type: ignore @distributed_trace_async - async def delete_text_blocklist( # pylint: disable=inconsistent-return-statements - self, blocklist_name: str, **kwargs: Any - ) -> None: + async def delete_text_blocklist(self, blocklist_name: str, **kwargs: Any) -> None: """Delete Text Blocklist By blocklistName. Deletes a text blocklist. @@ -672,7 +918,7 @@ async def delete_text_blocklist( # pylint: disable=inconsistent-return-statemen :rtype: None :raises ~azure.core.exceptions.HttpResponseError: """ - error_map = { + error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError, @@ -692,7 +938,7 @@ async def delete_text_blocklist( # pylint: disable=inconsistent-return-statemen params=_params, ) path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str"), } _request.url = self._client.format_url(_request.url, **path_format_arguments) @@ -704,8 +950,6 @@ async def delete_text_blocklist( # pylint: disable=inconsistent-return-statemen response = pipeline_response.http_response if response.status_code not in [204]: - if _stream: - await response.read() # Load the body in memory and close the socket map_error(status_code=response.status_code, response=response, error_map=error_map) raise HttpResponseError(response=response) @@ -720,13 +964,11 @@ async def get_text_blocklist(self, blocklist_name: str, **kwargs: Any) -> _model :param blocklist_name: Text blocklist name. Required. :type blocklist_name: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. :return: TextBlocklist. The TextBlocklist is compatible with MutableMapping :rtype: ~azure.ai.contentsafety.models.TextBlocklist :raises ~azure.core.exceptions.HttpResponseError: """ - error_map = { + error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError, @@ -746,7 +988,7 @@ async def get_text_blocklist(self, blocklist_name: str, **kwargs: Any) -> _model params=_params, ) path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str"), } _request.url = self._client.format_url(_request.url, **path_format_arguments) @@ -759,7 +1001,10 @@ async def get_text_blocklist(self, blocklist_name: str, **kwargs: Any) -> _model if response.status_code not in [200]: if _stream: - await response.read() # Load the body in memory and close the socket + 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) @@ -786,13 +1031,11 @@ async def get_text_blocklist_item( :param blocklist_item_id: The service will generate a BlocklistItemId, which will be a UUID. Required. :type blocklist_item_id: str - :keyword bool stream: Whether to stream the response of this operation. Defaults to False. You - will have to context manage the returned stream. :return: TextBlocklistItem. The TextBlocklistItem is compatible with MutableMapping :rtype: ~azure.ai.contentsafety.models.TextBlocklistItem :raises ~azure.core.exceptions.HttpResponseError: """ - error_map = { + error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError, @@ -813,7 +1056,7 @@ async def get_text_blocklist_item( params=_params, ) path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str"), } _request.url = self._client.format_url(_request.url, **path_format_arguments) @@ -826,7 +1069,10 @@ async def get_text_blocklist_item( if response.status_code not in [200]: if _stream: - await response.read() # Load the body in memory and close the socket + 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) @@ -865,7 +1111,7 @@ def list_text_blocklist_items( maxpagesize = kwargs.pop("maxpagesize", None) cls: ClsType[List[_models.TextBlocklistItem]] = kwargs.pop("cls", None) - error_map = { + error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError, @@ -886,9 +1132,7 @@ def prepare_request(next_link=None): params=_params, ) path_format_arguments = { - "endpoint": self._serialize.url( - "self._config.endpoint", self._config.endpoint, "str", skip_quote=True - ), + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str"), } _request.url = self._client.format_url(_request.url, **path_format_arguments) @@ -906,9 +1150,7 @@ def prepare_request(next_link=None): "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params ) path_format_arguments = { - "endpoint": self._serialize.url( - "self._config.endpoint", self._config.endpoint, "str", skip_quote=True - ), + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str"), } _request.url = self._client.format_url(_request.url, **path_format_arguments) @@ -931,8 +1173,6 @@ async def get_next(next_link=None): response = pipeline_response.http_response if response.status_code not in [200]: - if _stream: - await response.read() # Load the body in memory and close the socket map_error(status_code=response.status_code, response=response, error_map=error_map) raise HttpResponseError(response=response) @@ -955,7 +1195,7 @@ def list_text_blocklists(self, **kwargs: Any) -> AsyncIterable["_models.TextBloc cls: ClsType[List[_models.TextBlocklist]] = kwargs.pop("cls", None) - error_map = { + error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError, @@ -972,9 +1212,7 @@ def prepare_request(next_link=None): params=_params, ) path_format_arguments = { - "endpoint": self._serialize.url( - "self._config.endpoint", self._config.endpoint, "str", skip_quote=True - ), + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str"), } _request.url = self._client.format_url(_request.url, **path_format_arguments) @@ -992,9 +1230,7 @@ def prepare_request(next_link=None): "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params ) path_format_arguments = { - "endpoint": self._serialize.url( - "self._config.endpoint", self._config.endpoint, "str", skip_quote=True - ), + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str"), } _request.url = self._client.format_url(_request.url, **path_format_arguments) @@ -1017,8 +1253,6 @@ async def get_next(next_link=None): response = pipeline_response.http_response if response.status_code not in [200]: - if _stream: - await response.read() # Load the body in memory and close the socket map_error(status_code=response.status_code, response=response, error_map=error_map) raise HttpResponseError(response=response) @@ -1027,7 +1261,7 @@ async def get_next(next_link=None): return AsyncItemPaged(get_next, extract_data) @overload - async def remove_blocklist_items( # pylint: disable=inconsistent-return-statements + async def remove_blocklist_items( self, blocklist_name: str, options: _models.RemoveTextBlocklistItemsOptions, @@ -1053,7 +1287,7 @@ async def remove_blocklist_items( # pylint: disable=inconsistent-return-stateme """ @overload - async def remove_blocklist_items( # pylint: disable=inconsistent-return-statements + async def remove_blocklist_items( self, blocklist_name: str, options: JSON, *, content_type: str = "application/json", **kwargs: Any ) -> None: """Remove BlocklistItems From Text Blocklist. @@ -1074,7 +1308,7 @@ async def remove_blocklist_items( # pylint: disable=inconsistent-return-stateme """ @overload - async def remove_blocklist_items( # pylint: disable=inconsistent-return-statements + async def remove_blocklist_items( self, blocklist_name: str, options: IO[bytes], *, content_type: str = "application/json", **kwargs: Any ) -> None: """Remove BlocklistItems From Text Blocklist. @@ -1095,7 +1329,7 @@ async def remove_blocklist_items( # pylint: disable=inconsistent-return-stateme """ @distributed_trace_async - async def remove_blocklist_items( # pylint: disable=inconsistent-return-statements + async def remove_blocklist_items( self, blocklist_name: str, options: Union[_models.RemoveTextBlocklistItemsOptions, JSON, IO[bytes]], @@ -1112,14 +1346,11 @@ async def remove_blocklist_items( # pylint: disable=inconsistent-return-stateme RemoveTextBlocklistItemsOptions, JSON, IO[bytes] Required. :type options: ~azure.ai.contentsafety.models.RemoveTextBlocklistItemsOptions or JSON or IO[bytes] - :keyword content_type: Body parameter Content-Type. Known values are: application/json. Default - value is None. - :paramtype content_type: str :return: None :rtype: None :raises ~azure.core.exceptions.HttpResponseError: """ - error_map = { + error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError, @@ -1149,7 +1380,7 @@ async def remove_blocklist_items( # pylint: disable=inconsistent-return-stateme params=_params, ) path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str"), } _request.url = self._client.format_url(_request.url, **path_format_arguments) @@ -1161,8 +1392,6 @@ async def remove_blocklist_items( # pylint: disable=inconsistent-return-stateme response = pipeline_response.http_response if response.status_code not in [204]: - if _stream: - await response.read() # Load the body in memory and close the socket map_error(status_code=response.status_code, response=response, error_map=error_map) raise HttpResponseError(response=response) diff --git a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/aio/_vendor.py b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/aio/_vendor.py index aaf8f213e731..67720ad3024e 100644 --- a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/aio/_vendor.py +++ b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/aio/_vendor.py @@ -11,7 +11,6 @@ from ._configuration import BlocklistClientConfiguration, ContentSafetyClientConfiguration if TYPE_CHECKING: - # pylint: disable=unused-import,ungrouped-imports from azure.core import AsyncPipelineClient from .._serialization import Deserializer, Serializer diff --git a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/models/__init__.py b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/models/__init__.py index a4cd06fb66cf..93791ffb82a1 100644 --- a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/models/__init__.py +++ b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/models/__init__.py @@ -5,27 +5,45 @@ # Code generated by Microsoft (R) Python Code Generator. # Changes may cause incorrect behavior and will be lost if the code is regenerated. # -------------------------------------------------------------------------- +# pylint: disable=wrong-import-position -from ._models import AddOrUpdateTextBlocklistItemsOptions -from ._models import AddOrUpdateTextBlocklistItemsResult -from ._models import AnalyzeImageOptions -from ._models import AnalyzeImageResult -from ._models import AnalyzeTextOptions -from ._models import AnalyzeTextResult -from ._models import ImageCategoriesAnalysis -from ._models import ImageData -from ._models import RemoveTextBlocklistItemsOptions -from ._models import TextBlocklist -from ._models import TextBlocklistItem -from ._models import TextBlocklistMatch -from ._models import TextCategoriesAnalysis +from typing import TYPE_CHECKING -from ._enums import AnalyzeImageOutputType -from ._enums import AnalyzeTextOutputType -from ._enums import ImageCategory -from ._enums import TextCategory +if TYPE_CHECKING: + from ._patch import * # pylint: disable=unused-wildcard-import + + +from ._models import ( # type: ignore + AddOrUpdateTextBlocklistItemsOptions, + AddOrUpdateTextBlocklistItemsResult, + AnalyzeImageOptions, + AnalyzeImageResult, + AnalyzeTextOptions, + AnalyzeTextResult, + DetectTextProtectedMaterialOptions, + DetectTextProtectedMaterialResult, + DocumentInjectionAnalysisResult, + ImageCategoriesAnalysis, + ImageData, + RemoveTextBlocklistItemsOptions, + ShieldPromptOptions, + ShieldPromptResult, + TextBlocklist, + TextBlocklistItem, + TextBlocklistMatch, + TextCategoriesAnalysis, + TextProtectedMaterialAnalysisResult, + UserPromptInjectionAnalysisResult, +) + +from ._enums import ( # type: ignore + AnalyzeImageOutputType, + AnalyzeTextOutputType, + ImageCategory, + TextCategory, +) from ._patch import __all__ as _patch_all -from ._patch import * # pylint: disable=unused-wildcard-import +from ._patch import * from ._patch import patch_sdk as _patch_sdk __all__ = [ @@ -35,17 +53,24 @@ "AnalyzeImageResult", "AnalyzeTextOptions", "AnalyzeTextResult", + "DetectTextProtectedMaterialOptions", + "DetectTextProtectedMaterialResult", + "DocumentInjectionAnalysisResult", "ImageCategoriesAnalysis", "ImageData", "RemoveTextBlocklistItemsOptions", + "ShieldPromptOptions", + "ShieldPromptResult", "TextBlocklist", "TextBlocklistItem", "TextBlocklistMatch", "TextCategoriesAnalysis", + "TextProtectedMaterialAnalysisResult", + "UserPromptInjectionAnalysisResult", "AnalyzeImageOutputType", "AnalyzeTextOutputType", "ImageCategory", "TextCategory", ] -__all__.extend([p for p in _patch_all if p not in __all__]) +__all__.extend([p for p in _patch_all if p not in __all__]) # pyright: ignore _patch_sdk() diff --git a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/models/_enums.py b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/models/_enums.py index 6e91fad1a406..c8f1dbdb7d4b 100644 --- a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/models/_enums.py +++ b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/models/_enums.py @@ -27,18 +27,26 @@ class AnalyzeTextOutputType(str, Enum, metaclass=CaseInsensitiveEnumMeta): class ImageCategory(str, Enum, metaclass=CaseInsensitiveEnumMeta): - """Image analyze category.""" + """The harm category supported in Image content analysis.""" HATE = "Hate" + """The harm category for Image - Hate.""" SELF_HARM = "SelfHarm" + """The harm category for Image - SelfHarm.""" SEXUAL = "Sexual" + """The harm category for Image - Sexual.""" VIOLENCE = "Violence" + """The harm category for Image - Violence.""" class TextCategory(str, Enum, metaclass=CaseInsensitiveEnumMeta): - """Text analyze category.""" + """The harm category supported in Text content analysis.""" HATE = "Hate" + """The harm category for Text - Hate.""" SELF_HARM = "SelfHarm" + """The harm category for Text - SelfHarm.""" SEXUAL = "Sexual" + """The harm category for Text - Sexual.""" VIOLENCE = "Violence" + """The harm category for Text - Violence.""" diff --git a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/models/_models.py b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/models/_models.py index cf0403b1cd4c..b2570f697754 100644 --- a/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/models/_models.py +++ b/sdk/contentsafety/azure-ai-contentsafety/azure/ai/contentsafety/models/_models.py @@ -1,11 +1,11 @@ # coding=utf-8 -# pylint: disable=too-many-lines # -------------------------------------------------------------------------- # 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. # -------------------------------------------------------------------------- +# pylint: disable=useless-super-delegation from typing import Any, List, Mapping, Optional, TYPE_CHECKING, Union, overload @@ -13,7 +13,6 @@ from .._model_base import rest_field if TYPE_CHECKING: - # pylint: disable=unused-import,ungrouped-imports from .. import models as _models @@ -34,24 +33,22 @@ def __init__( self, *, blocklist_items: List["_models.TextBlocklistItem"], - ): - ... + ) -> None: ... @overload - def __init__(self, mapping: Mapping[str, Any]): + 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: # pylint: disable=useless-super-delegation + def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) class AddOrUpdateTextBlocklistItemsResult(_model_base.Model): """The response of adding blocklistItems to the text blocklist. - All required parameters must be populated in order to send to server. :ivar blocklist_items: Array of blocklistItems have been added. Required. :vartype blocklist_items: list[~azure.ai.contentsafety.models.TextBlocklistItem] @@ -65,17 +62,16 @@ def __init__( self, *, blocklist_items: List["_models.TextBlocklistItem"], - ): - ... + ) -> None: ... @overload - def __init__(self, mapping: Mapping[str, Any]): + 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: # pylint: disable=useless-super-delegation + def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) @@ -84,7 +80,7 @@ class AnalyzeImageOptions(_model_base.Model): All required parameters must be populated in order to send to server. - :ivar image: The image needs to be analyzed. Required. + :ivar image: The image to be analyzed. Required. :vartype image: ~azure.ai.contentsafety.models.ImageData :ivar categories: The categories will be analyzed. If they are not assigned, a default set of analysis results for the categories will be returned. @@ -95,7 +91,7 @@ class AnalyzeImageOptions(_model_base.Model): """ image: "_models.ImageData" = rest_field() - """The image needs to be analyzed. Required.""" + """The image to be analyzed. Required.""" categories: Optional[List[Union[str, "_models.ImageCategory"]]] = rest_field() """The categories will be analyzed. If they are not assigned, a default set of analysis results for the categories will be returned.""" @@ -110,24 +106,22 @@ def __init__( image: "_models.ImageData", categories: Optional[List[Union[str, "_models.ImageCategory"]]] = None, output_type: Optional[Union[str, "_models.AnalyzeImageOutputType"]] = None, - ): - ... + ) -> None: ... @overload - def __init__(self, mapping: Mapping[str, Any]): + 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: # pylint: disable=useless-super-delegation + def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) class AnalyzeImageResult(_model_base.Model): """The image analysis response. - All required parameters must be populated in order to send to server. :ivar categories_analysis: Analysis result for categories. Required. :vartype categories_analysis: list[~azure.ai.contentsafety.models.ImageCategoriesAnalysis] @@ -141,17 +135,16 @@ def __init__( self, *, categories_analysis: List["_models.ImageCategoriesAnalysis"], - ): - ... + ) -> None: ... @overload - def __init__(self, mapping: Mapping[str, Any]): + 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: # pylint: disable=useless-super-delegation + def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) @@ -160,9 +153,9 @@ class AnalyzeTextOptions(_model_base.Model): All required parameters must be populated in order to send to server. - :ivar text: The text needs to be analyzed. We support a maximum of 10k Unicode characters - (Unicode code points) in the text of one request. Required. - :vartype text: str + :ivar text2: The text to be analyzed. We support a maximum of 10k Unicode characters (Unicode + code points) in the text of one request. Required. + :vartype text2: str :ivar categories: The categories will be analyzed. If they are not assigned, a default set of analysis results for the categories will be returned. :vartype categories: list[str or ~azure.ai.contentsafety.models.TextCategory] @@ -178,9 +171,9 @@ class AnalyzeTextOptions(_model_base.Model): :vartype output_type: str or ~azure.ai.contentsafety.models.AnalyzeTextOutputType """ - text: str = rest_field() - """The text needs to be analyzed. We support a maximum of 10k Unicode characters (Unicode code - points) in the text of one request. Required.""" + text2: str = rest_field() + """The text to be analyzed. We support a maximum of 10k Unicode characters (Unicode code points) + in the text of one request. Required.""" categories: Optional[List[Union[str, "_models.TextCategory"]]] = rest_field() """The categories will be analyzed. If they are not assigned, a default set of analysis results for the categories will be returned.""" @@ -199,29 +192,27 @@ class AnalyzeTextOptions(_model_base.Model): def __init__( self, *, - text: str, + text2: str, categories: Optional[List[Union[str, "_models.TextCategory"]]] = None, blocklist_names: Optional[List[str]] = None, halt_on_blocklist_hit: Optional[bool] = None, output_type: Optional[Union[str, "_models.AnalyzeTextOutputType"]] = None, - ): - ... + ) -> None: ... @overload - def __init__(self, mapping: Mapping[str, Any]): + 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: # pylint: disable=useless-super-delegation + def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) class AnalyzeTextResult(_model_base.Model): """The text analysis response. - All required parameters must be populated in order to send to server. :ivar blocklists_match: The blocklist match details. :vartype blocklists_match: list[~azure.ai.contentsafety.models.TextBlocklistMatch] @@ -240,24 +231,115 @@ def __init__( *, categories_analysis: List["_models.TextCategoriesAnalysis"], blocklists_match: Optional[List["_models.TextBlocklistMatch"]] = None, - ): - ... + ) -> None: ... @overload - def __init__(self, mapping: Mapping[str, Any]): + 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: # pylint: disable=useless-super-delegation + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + +class DetectTextProtectedMaterialOptions(_model_base.Model): + """The request of detecting potential protected material present in the given text. + + All required parameters must be populated in order to send to server. + + :ivar text: The text to be analyzed, which may contain protected material. The characters will + be counted in Unicode code points. Required. + :vartype text: str + """ + + text: str = rest_field() + """The text to be analyzed, which may contain protected material. The characters will be counted + in Unicode code points. Required.""" + + @overload + def __init__( + self, + *, + text: str, + ) -> 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 DetectTextProtectedMaterialResult(_model_base.Model): + """The combined detection results of potential protected material. + + + :ivar protected_material_analysis: Analysis result for the given text. Required. + :vartype protected_material_analysis: + ~azure.ai.contentsafety.models.TextProtectedMaterialAnalysisResult + """ + + protected_material_analysis: "_models.TextProtectedMaterialAnalysisResult" = rest_field( + name="protectedMaterialAnalysis" + ) + """Analysis result for the given text. Required.""" + + @overload + def __init__( + self, + *, + protected_material_analysis: "_models.TextProtectedMaterialAnalysisResult", + ) -> 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 DocumentInjectionAnalysisResult(_model_base.Model): + """The individual analysis result of potential injection attacks in the given documents. + + + :ivar attack_detected: Whether a potential injection attack is detected or not. Required. + :vartype attack_detected: bool + """ + + attack_detected: bool = rest_field(name="attackDetected") + """Whether a potential injection attack is detected or not. Required.""" + + @overload + def __init__( + self, + *, + attack_detected: bool, + ) -> 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 ImageCategoriesAnalysis(_model_base.Model): """Image analysis result. - All required parameters must be populated in order to send to server. :ivar category: The image analysis category. Required. Known values are: "Hate", "SelfHarm", "Sexual", and "Violence". @@ -282,17 +364,16 @@ def __init__( *, category: Union[str, "_models.ImageCategory"], severity: Optional[int] = None, - ): - ... + ) -> None: ... @overload - def __init__(self, mapping: Mapping[str, Any]): + 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: # pylint: disable=useless-super-delegation + def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) @@ -318,17 +399,16 @@ def __init__( *, content: Optional[bytes] = None, blob_url: Optional[str] = None, - ): - ... + ) -> None: ... @overload - def __init__(self, mapping: Mapping[str, Any]): + 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: # pylint: disable=useless-super-delegation + def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) @@ -349,24 +429,91 @@ def __init__( self, *, blocklist_item_ids: List[str], - ): - ... + ) -> 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 ShieldPromptOptions(_model_base.Model): + """The request of analyzing potential direct or indirect injection attacks. + + :ivar user_prompt: The user prompt to be analyzed, which may contain direct injection attacks. + :vartype user_prompt: str + :ivar documents: The documents to be analyzed, which may contain direct or indirect injection + attacks. + :vartype documents: list[str] + """ + + user_prompt: Optional[str] = rest_field(name="userPrompt") + """The user prompt to be analyzed, which may contain direct injection attacks.""" + documents: Optional[List[str]] = rest_field() + """The documents to be analyzed, which may contain direct or indirect injection attacks.""" @overload - def __init__(self, mapping: Mapping[str, Any]): + def __init__( + self, + *, + user_prompt: Optional[str] = None, + documents: Optional[List[str]] = 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: # pylint: disable=useless-super-delegation + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + +class ShieldPromptResult(_model_base.Model): + """The combined analysis results of potential direct or indirect injection attacks. + + :ivar user_prompt_analysis: Direct injection attacks analysis result for the given user prompt. + :vartype user_prompt_analysis: ~azure.ai.contentsafety.models.UserPromptInjectionAnalysisResult + :ivar documents_analysis: Direct and indirect injection attacks analysis result for the given + documents. + :vartype documents_analysis: + list[~azure.ai.contentsafety.models.DocumentInjectionAnalysisResult] + """ + + user_prompt_analysis: Optional["_models.UserPromptInjectionAnalysisResult"] = rest_field(name="userPromptAnalysis") + """Direct injection attacks analysis result for the given user prompt.""" + documents_analysis: Optional[List["_models.DocumentInjectionAnalysisResult"]] = rest_field(name="documentsAnalysis") + """Direct and indirect injection attacks analysis result for the given documents.""" + + @overload + def __init__( + self, + *, + user_prompt_analysis: Optional["_models.UserPromptInjectionAnalysisResult"] = None, + documents_analysis: Optional[List["_models.DocumentInjectionAnalysisResult"]] = 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 TextBlocklist(_model_base.Model): """Text Blocklist. - All required parameters must be populated in order to send to server. :ivar blocklist_name: Text blocklist name. Required. :vartype blocklist_name: str @@ -385,17 +532,16 @@ def __init__( *, blocklist_name: str, description: Optional[str] = None, - ): - ... + ) -> None: ... @overload - def __init__(self, mapping: Mapping[str, Any]): + 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: # pylint: disable=useless-super-delegation + def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) @@ -404,15 +550,17 @@ class TextBlocklistItem(_model_base.Model): Readonly variables are only populated by the server, and will be ignored when sending a request. - All required parameters must be populated in order to send to server. :ivar blocklist_item_id: The service will generate a BlocklistItemId, which will be a UUID. Required. :vartype blocklist_item_id: str :ivar description: BlocklistItem description. :vartype description: str - :ivar text: BlocklistItem content. Required. + :ivar text: BlocklistItem content. The length is counted using Unicode code point. Required. :vartype text: str + :ivar is_regex: An optional properties indicating whether this item is to be matched as a + regular expression. + :vartype is_regex: bool """ blocklist_item_id: str = rest_field(name="blocklistItemId", visibility=["read"]) @@ -420,7 +568,9 @@ class TextBlocklistItem(_model_base.Model): description: Optional[str] = rest_field() """BlocklistItem description.""" text: str = rest_field() - """BlocklistItem content. Required.""" + """BlocklistItem content. The length is counted using Unicode code point. Required.""" + is_regex: Optional[bool] = rest_field(name="isRegex") + """An optional properties indicating whether this item is to be matched as a regular expression.""" @overload def __init__( @@ -428,24 +578,23 @@ def __init__( *, text: str, description: Optional[str] = None, - ): - ... + is_regex: Optional[bool] = None, + ) -> None: ... @overload - def __init__(self, mapping: Mapping[str, Any]): + 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: # pylint: disable=useless-super-delegation + def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) class TextBlocklistMatch(_model_base.Model): """The result of blocklist match. - All required parameters must be populated in order to send to server. :ivar blocklist_name: The name of the matched blocklist. Required. :vartype blocklist_name: str @@ -469,24 +618,22 @@ def __init__( blocklist_name: str, blocklist_item_id: str, blocklist_item_text: str, - ): - ... + ) -> None: ... @overload - def __init__(self, mapping: Mapping[str, Any]): + 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: # pylint: disable=useless-super-delegation + def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) class TextCategoriesAnalysis(_model_base.Model): """Text analysis result. - All required parameters must be populated in order to send to server. :ivar category: The text analysis category. Required. Known values are: "Hate", "SelfHarm", "Sexual", and "Violence". @@ -513,15 +660,72 @@ def __init__( *, category: Union[str, "_models.TextCategory"], severity: Optional[int] = 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 TextProtectedMaterialAnalysisResult(_model_base.Model): + """The individual detection result of potential protected material. + + + :ivar detected: Whether potential protected material is detected or not. Required. + :vartype detected: bool + """ + + detected: bool = rest_field() + """Whether potential protected material is detected or not. Required.""" + + @overload + def __init__( + self, + *, + detected: bool, + ) -> 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 UserPromptInjectionAnalysisResult(_model_base.Model): + """The individual analysis result of potential injection attacks in the given user prompt. + + + :ivar attack_detected: Whether a potential injection attack is detected or not. Required. + :vartype attack_detected: bool + """ + + attack_detected: bool = rest_field(name="attackDetected") + """Whether a potential injection attack is detected or not. Required.""" + + @overload + def __init__( + self, + *, + attack_detected: bool, + ) -> None: ... @overload - def __init__(self, mapping: Mapping[str, Any]): + 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: # pylint: disable=useless-super-delegation + def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) diff --git a/sdk/contentsafety/azure-ai-contentsafety/generated_samples/delete_text_blocklist.py b/sdk/contentsafety/azure-ai-contentsafety/generated_samples/delete_text_blocklist.py new file mode 100644 index 000000000000..e0eb35816510 --- /dev/null +++ b/sdk/contentsafety/azure-ai-contentsafety/generated_samples/delete_text_blocklist.py @@ -0,0 +1,32 @@ +# 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 azure.ai.contentsafety import ContentSafetyClient + +""" +# PREREQUISITES + pip install azure-ai-contentsafety +# USAGE + python delete_text_blocklist.py +""" + + +def main(): + client = ContentSafetyClient( + endpoint="ENDPOINT", + credential="CREDENTIAL", + ) + + client.delete_text_blocklist( + blocklist_name="TestBlocklist", + ) + + +# x-ms-original-file: 2024-09-01/DeleteTextBlocklist.json +if __name__ == "__main__": + main() diff --git a/sdk/contentsafety/azure-ai-contentsafety/generated_samples/get_text_blocklist.py b/sdk/contentsafety/azure-ai-contentsafety/generated_samples/get_text_blocklist.py new file mode 100644 index 000000000000..1171feb64678 --- /dev/null +++ b/sdk/contentsafety/azure-ai-contentsafety/generated_samples/get_text_blocklist.py @@ -0,0 +1,33 @@ +# 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 azure.ai.contentsafety import ContentSafetyClient + +""" +# PREREQUISITES + pip install azure-ai-contentsafety +# USAGE + python get_text_blocklist.py +""" + + +def main(): + client = ContentSafetyClient( + endpoint="ENDPOINT", + credential="CREDENTIAL", + ) + + response = client.get_text_blocklist( + blocklist_name="TestBlocklist", + ) + print(response) + + +# x-ms-original-file: 2024-09-01/GetTextBlocklist.json +if __name__ == "__main__": + main() diff --git a/sdk/contentsafety/azure-ai-contentsafety/generated_samples/get_text_blocklist_item.py b/sdk/contentsafety/azure-ai-contentsafety/generated_samples/get_text_blocklist_item.py new file mode 100644 index 000000000000..841a60f0a48a --- /dev/null +++ b/sdk/contentsafety/azure-ai-contentsafety/generated_samples/get_text_blocklist_item.py @@ -0,0 +1,34 @@ +# 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 azure.ai.contentsafety import ContentSafetyClient + +""" +# PREREQUISITES + pip install azure-ai-contentsafety +# USAGE + python get_text_blocklist_item.py +""" + + +def main(): + client = ContentSafetyClient( + endpoint="ENDPOINT", + credential="CREDENTIAL", + ) + + response = client.get_text_blocklist_item( + blocklist_name="TestBlocklist", + blocklist_item_id="9511969e-f1e3-4604-9127-05ee16c509ec", + ) + print(response) + + +# x-ms-original-file: 2024-09-01/GetTextBlocklistItem.json +if __name__ == "__main__": + main() diff --git a/sdk/contentsafety/azure-ai-contentsafety/generated_samples/list_text_blocklist_items.py b/sdk/contentsafety/azure-ai-contentsafety/generated_samples/list_text_blocklist_items.py new file mode 100644 index 000000000000..6e1406bab83f --- /dev/null +++ b/sdk/contentsafety/azure-ai-contentsafety/generated_samples/list_text_blocklist_items.py @@ -0,0 +1,34 @@ +# 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 azure.ai.contentsafety import ContentSafetyClient + +""" +# PREREQUISITES + pip install azure-ai-contentsafety +# USAGE + python list_text_blocklist_items.py +""" + + +def main(): + client = ContentSafetyClient( + endpoint="ENDPOINT", + credential="CREDENTIAL", + ) + + response = client.list_text_blocklist_items( + blocklist_name="TestBlocklist", + ) + for item in response: + print(item) + + +# x-ms-original-file: 2024-09-01/ListTextBlocklistItems.json +if __name__ == "__main__": + main() diff --git a/sdk/contentsafety/azure-ai-contentsafety/generated_samples/list_text_blocklists.py b/sdk/contentsafety/azure-ai-contentsafety/generated_samples/list_text_blocklists.py new file mode 100644 index 000000000000..0a6ae0f6b54d --- /dev/null +++ b/sdk/contentsafety/azure-ai-contentsafety/generated_samples/list_text_blocklists.py @@ -0,0 +1,32 @@ +# 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 azure.ai.contentsafety import ContentSafetyClient + +""" +# PREREQUISITES + pip install azure-ai-contentsafety +# USAGE + python list_text_blocklists.py +""" + + +def main(): + client = ContentSafetyClient( + endpoint="ENDPOINT", + credential="CREDENTIAL", + ) + + response = client.list_text_blocklists() + for item in response: + print(item) + + +# x-ms-original-file: 2024-09-01/ListTextBlocklists.json +if __name__ == "__main__": + main() diff --git a/sdk/contentsafety/azure-ai-contentsafety/generated_tests/conftest.py b/sdk/contentsafety/azure-ai-contentsafety/generated_tests/conftest.py new file mode 100644 index 000000000000..76b5ff5fdc4d --- /dev/null +++ b/sdk/contentsafety/azure-ai-contentsafety/generated_tests/conftest.py @@ -0,0 +1,46 @@ +# 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. +# -------------------------------------------------------------------------- +import os +import pytest +from dotenv import load_dotenv +from devtools_testutils import ( + test_proxy, + add_general_regex_sanitizer, + add_body_key_sanitizer, + add_header_regex_sanitizer, +) + +load_dotenv() + + +# For security, please avoid record sensitive identity information in recordings +@pytest.fixture(scope="session", autouse=True) +def add_sanitizers(test_proxy): + contentsafety_subscription_id = os.environ.get( + "CONTENTSAFETY_SUBSCRIPTION_ID", "00000000-0000-0000-0000-000000000000" + ) + contentsafety_tenant_id = os.environ.get("CONTENTSAFETY_TENANT_ID", "00000000-0000-0000-0000-000000000000") + contentsafety_client_id = os.environ.get("CONTENTSAFETY_CLIENT_ID", "00000000-0000-0000-0000-000000000000") + contentsafety_client_secret = os.environ.get("CONTENTSAFETY_CLIENT_SECRET", "00000000-0000-0000-0000-000000000000") + add_general_regex_sanitizer(regex=contentsafety_subscription_id, value="00000000-0000-0000-0000-000000000000") + add_general_regex_sanitizer(regex=contentsafety_tenant_id, value="00000000-0000-0000-0000-000000000000") + add_general_regex_sanitizer(regex=contentsafety_client_id, value="00000000-0000-0000-0000-000000000000") + add_general_regex_sanitizer(regex=contentsafety_client_secret, value="00000000-0000-0000-0000-000000000000") + + blocklist_subscription_id = os.environ.get("BLOCKLIST_SUBSCRIPTION_ID", "00000000-0000-0000-0000-000000000000") + blocklist_tenant_id = os.environ.get("BLOCKLIST_TENANT_ID", "00000000-0000-0000-0000-000000000000") + blocklist_client_id = os.environ.get("BLOCKLIST_CLIENT_ID", "00000000-0000-0000-0000-000000000000") + blocklist_client_secret = os.environ.get("BLOCKLIST_CLIENT_SECRET", "00000000-0000-0000-0000-000000000000") + add_general_regex_sanitizer(regex=blocklist_subscription_id, value="00000000-0000-0000-0000-000000000000") + add_general_regex_sanitizer(regex=blocklist_tenant_id, value="00000000-0000-0000-0000-000000000000") + add_general_regex_sanitizer(regex=blocklist_client_id, value="00000000-0000-0000-0000-000000000000") + add_general_regex_sanitizer(regex=blocklist_client_secret, value="00000000-0000-0000-0000-000000000000") + + add_header_regex_sanitizer(key="Set-Cookie", value="[set-cookie;]") + add_header_regex_sanitizer(key="Cookie", value="cookie;") + add_body_key_sanitizer(json_path="$..access_token", value="access_token") diff --git a/sdk/contentsafety/azure-ai-contentsafety/generated_tests/test_blocklist.py b/sdk/contentsafety/azure-ai-contentsafety/generated_tests/test_blocklist.py new file mode 100644 index 000000000000..83deae7cb51a --- /dev/null +++ b/sdk/contentsafety/azure-ai-contentsafety/generated_tests/test_blocklist.py @@ -0,0 +1,105 @@ +# 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. +# -------------------------------------------------------------------------- +import pytest +from devtools_testutils import recorded_by_proxy +from testpreparer import BlocklistClientTestBase, BlocklistPreparer + + +@pytest.mark.skip("you may need to update the auto-generated test case before run it") +class TestBlocklist(BlocklistClientTestBase): + @BlocklistPreparer() + @recorded_by_proxy + def test_add_or_update_blocklist_items(self, blocklist_endpoint): + client = self.create_client(endpoint=blocklist_endpoint) + response = client.add_or_update_blocklist_items( + blocklist_name="str", + options={ + "blocklistItems": [{"blocklistItemId": "str", "text": "str", "description": "str", "isRegex": bool}] + }, + ) + + # please add some check logic here by yourself + # ... + + @BlocklistPreparer() + @recorded_by_proxy + def test_create_or_update_text_blocklist(self, blocklist_endpoint): + client = self.create_client(endpoint=blocklist_endpoint) + response = client.create_or_update_text_blocklist( + blocklist_name="str", + options={"blocklistName": "str", "description": "str"}, + ) + + # please add some check logic here by yourself + # ... + + @BlocklistPreparer() + @recorded_by_proxy + def test_delete_text_blocklist(self, blocklist_endpoint): + client = self.create_client(endpoint=blocklist_endpoint) + response = client.delete_text_blocklist( + blocklist_name="str", + ) + + # please add some check logic here by yourself + # ... + + @BlocklistPreparer() + @recorded_by_proxy + def test_get_text_blocklist(self, blocklist_endpoint): + client = self.create_client(endpoint=blocklist_endpoint) + response = client.get_text_blocklist( + blocklist_name="str", + ) + + # please add some check logic here by yourself + # ... + + @BlocklistPreparer() + @recorded_by_proxy + def test_get_text_blocklist_item(self, blocklist_endpoint): + client = self.create_client(endpoint=blocklist_endpoint) + response = client.get_text_blocklist_item( + blocklist_name="str", + blocklist_item_id="str", + ) + + # please add some check logic here by yourself + # ... + + @BlocklistPreparer() + @recorded_by_proxy + def test_list_text_blocklist_items(self, blocklist_endpoint): + client = self.create_client(endpoint=blocklist_endpoint) + response = client.list_text_blocklist_items( + blocklist_name="str", + ) + result = [r for r in response] + # please add some check logic here by yourself + # ... + + @BlocklistPreparer() + @recorded_by_proxy + def test_list_text_blocklists(self, blocklist_endpoint): + client = self.create_client(endpoint=blocklist_endpoint) + response = client.list_text_blocklists() + result = [r for r in response] + # please add some check logic here by yourself + # ... + + @BlocklistPreparer() + @recorded_by_proxy + def test_remove_blocklist_items(self, blocklist_endpoint): + client = self.create_client(endpoint=blocklist_endpoint) + response = client.remove_blocklist_items( + blocklist_name="str", + options={"blocklistItemIds": ["str"]}, + ) + + # please add some check logic here by yourself + # ... diff --git a/sdk/contentsafety/azure-ai-contentsafety/generated_tests/test_blocklist_async.py b/sdk/contentsafety/azure-ai-contentsafety/generated_tests/test_blocklist_async.py new file mode 100644 index 000000000000..c38446aafb5c --- /dev/null +++ b/sdk/contentsafety/azure-ai-contentsafety/generated_tests/test_blocklist_async.py @@ -0,0 +1,106 @@ +# 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. +# -------------------------------------------------------------------------- +import pytest +from devtools_testutils.aio import recorded_by_proxy_async +from testpreparer import BlocklistPreparer +from testpreparer_async import BlocklistClientTestBaseAsync + + +@pytest.mark.skip("you may need to update the auto-generated test case before run it") +class TestBlocklistAsync(BlocklistClientTestBaseAsync): + @BlocklistPreparer() + @recorded_by_proxy_async + async def test_add_or_update_blocklist_items(self, blocklist_endpoint): + client = self.create_async_client(endpoint=blocklist_endpoint) + response = await client.add_or_update_blocklist_items( + blocklist_name="str", + options={ + "blocklistItems": [{"blocklistItemId": "str", "text": "str", "description": "str", "isRegex": bool}] + }, + ) + + # please add some check logic here by yourself + # ... + + @BlocklistPreparer() + @recorded_by_proxy_async + async def test_create_or_update_text_blocklist(self, blocklist_endpoint): + client = self.create_async_client(endpoint=blocklist_endpoint) + response = await client.create_or_update_text_blocklist( + blocklist_name="str", + options={"blocklistName": "str", "description": "str"}, + ) + + # please add some check logic here by yourself + # ... + + @BlocklistPreparer() + @recorded_by_proxy_async + async def test_delete_text_blocklist(self, blocklist_endpoint): + client = self.create_async_client(endpoint=blocklist_endpoint) + response = await client.delete_text_blocklist( + blocklist_name="str", + ) + + # please add some check logic here by yourself + # ... + + @BlocklistPreparer() + @recorded_by_proxy_async + async def test_get_text_blocklist(self, blocklist_endpoint): + client = self.create_async_client(endpoint=blocklist_endpoint) + response = await client.get_text_blocklist( + blocklist_name="str", + ) + + # please add some check logic here by yourself + # ... + + @BlocklistPreparer() + @recorded_by_proxy_async + async def test_get_text_blocklist_item(self, blocklist_endpoint): + client = self.create_async_client(endpoint=blocklist_endpoint) + response = await client.get_text_blocklist_item( + blocklist_name="str", + blocklist_item_id="str", + ) + + # please add some check logic here by yourself + # ... + + @BlocklistPreparer() + @recorded_by_proxy_async + async def test_list_text_blocklist_items(self, blocklist_endpoint): + client = self.create_async_client(endpoint=blocklist_endpoint) + response = client.list_text_blocklist_items( + blocklist_name="str", + ) + result = [r async for r in response] + # please add some check logic here by yourself + # ... + + @BlocklistPreparer() + @recorded_by_proxy_async + async def test_list_text_blocklists(self, blocklist_endpoint): + client = self.create_async_client(endpoint=blocklist_endpoint) + response = client.list_text_blocklists() + result = [r async for r in response] + # please add some check logic here by yourself + # ... + + @BlocklistPreparer() + @recorded_by_proxy_async + async def test_remove_blocklist_items(self, blocklist_endpoint): + client = self.create_async_client(endpoint=blocklist_endpoint) + response = await client.remove_blocklist_items( + blocklist_name="str", + options={"blocklistItemIds": ["str"]}, + ) + + # please add some check logic here by yourself + # ... diff --git a/sdk/contentsafety/azure-ai-contentsafety/generated_tests/test_content_safety.py b/sdk/contentsafety/azure-ai-contentsafety/generated_tests/test_content_safety.py new file mode 100644 index 000000000000..4e14070defa9 --- /dev/null +++ b/sdk/contentsafety/azure-ai-contentsafety/generated_tests/test_content_safety.py @@ -0,0 +1,67 @@ +# 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. +# -------------------------------------------------------------------------- +import pytest +from devtools_testutils import recorded_by_proxy +from testpreparer import ContentSafetyClientTestBase, ContentSafetyPreparer + + +@pytest.mark.skip("you may need to update the auto-generated test case before run it") +class TestContentSafety(ContentSafetyClientTestBase): + @ContentSafetyPreparer() + @recorded_by_proxy + def test_analyze_image(self, contentsafety_endpoint): + client = self.create_client(endpoint=contentsafety_endpoint) + response = client.analyze_image( + options={ + "image": {"blobUrl": "str", "content": bytes("bytes", encoding="utf-8")}, + "categories": ["str"], + "outputType": "str", + }, + ) + + # please add some check logic here by yourself + # ... + + @ContentSafetyPreparer() + @recorded_by_proxy + def test_analyze_text(self, contentsafety_endpoint): + client = self.create_client(endpoint=contentsafety_endpoint) + response = client.analyze_text( + options={ + "text2": "str", + "blocklistNames": ["str"], + "categories": ["str"], + "haltOnBlocklistHit": bool, + "outputType": "str", + }, + ) + + # please add some check logic here by yourself + # ... + + @ContentSafetyPreparer() + @recorded_by_proxy + def test_detect_text_protected_material(self, contentsafety_endpoint): + client = self.create_client(endpoint=contentsafety_endpoint) + response = client.detect_text_protected_material( + options={"text": "str"}, + ) + + # please add some check logic here by yourself + # ... + + @ContentSafetyPreparer() + @recorded_by_proxy + def test_shield_prompt(self, contentsafety_endpoint): + client = self.create_client(endpoint=contentsafety_endpoint) + response = client.shield_prompt( + options={"documents": ["str"], "userPrompt": "str"}, + ) + + # please add some check logic here by yourself + # ... diff --git a/sdk/contentsafety/azure-ai-contentsafety/generated_tests/test_content_safety_async.py b/sdk/contentsafety/azure-ai-contentsafety/generated_tests/test_content_safety_async.py new file mode 100644 index 000000000000..5f49357aa92c --- /dev/null +++ b/sdk/contentsafety/azure-ai-contentsafety/generated_tests/test_content_safety_async.py @@ -0,0 +1,68 @@ +# 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. +# -------------------------------------------------------------------------- +import pytest +from devtools_testutils.aio import recorded_by_proxy_async +from testpreparer import ContentSafetyPreparer +from testpreparer_async import ContentSafetyClientTestBaseAsync + + +@pytest.mark.skip("you may need to update the auto-generated test case before run it") +class TestContentSafetyAsync(ContentSafetyClientTestBaseAsync): + @ContentSafetyPreparer() + @recorded_by_proxy_async + async def test_analyze_image(self, contentsafety_endpoint): + client = self.create_async_client(endpoint=contentsafety_endpoint) + response = await client.analyze_image( + options={ + "image": {"blobUrl": "str", "content": bytes("bytes", encoding="utf-8")}, + "categories": ["str"], + "outputType": "str", + }, + ) + + # please add some check logic here by yourself + # ... + + @ContentSafetyPreparer() + @recorded_by_proxy_async + async def test_analyze_text(self, contentsafety_endpoint): + client = self.create_async_client(endpoint=contentsafety_endpoint) + response = await client.analyze_text( + options={ + "text2": "str", + "blocklistNames": ["str"], + "categories": ["str"], + "haltOnBlocklistHit": bool, + "outputType": "str", + }, + ) + + # please add some check logic here by yourself + # ... + + @ContentSafetyPreparer() + @recorded_by_proxy_async + async def test_detect_text_protected_material(self, contentsafety_endpoint): + client = self.create_async_client(endpoint=contentsafety_endpoint) + response = await client.detect_text_protected_material( + options={"text": "str"}, + ) + + # please add some check logic here by yourself + # ... + + @ContentSafetyPreparer() + @recorded_by_proxy_async + async def test_shield_prompt(self, contentsafety_endpoint): + client = self.create_async_client(endpoint=contentsafety_endpoint) + response = await client.shield_prompt( + options={"documents": ["str"], "userPrompt": "str"}, + ) + + # please add some check logic here by yourself + # ... diff --git a/sdk/contentsafety/azure-ai-contentsafety/generated_tests/testpreparer.py b/sdk/contentsafety/azure-ai-contentsafety/generated_tests/testpreparer.py new file mode 100644 index 000000000000..be1095e66878 --- /dev/null +++ b/sdk/contentsafety/azure-ai-contentsafety/generated_tests/testpreparer.py @@ -0,0 +1,42 @@ +# 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 azure.ai.contentsafety import BlocklistClient, ContentSafetyClient +from devtools_testutils import AzureRecordedTestCase, PowerShellPreparer +import functools + + +class ContentSafetyClientTestBase(AzureRecordedTestCase): + + def create_client(self, endpoint): + credential = self.get_credential(ContentSafetyClient) + return self.create_client_from_credential( + ContentSafetyClient, + credential=credential, + endpoint=endpoint, + ) + + +ContentSafetyPreparer = functools.partial( + PowerShellPreparer, "contentsafety", contentsafety_endpoint="https://fake_contentsafety_endpoint.com" +) + + +class BlocklistClientTestBase(AzureRecordedTestCase): + + def create_client(self, endpoint): + credential = self.get_credential(BlocklistClient) + return self.create_client_from_credential( + BlocklistClient, + credential=credential, + endpoint=endpoint, + ) + + +BlocklistPreparer = functools.partial( + PowerShellPreparer, "blocklist", blocklist_endpoint="https://fake_blocklist_endpoint.com" +) diff --git a/sdk/contentsafety/azure-ai-contentsafety/generated_tests/testpreparer_async.py b/sdk/contentsafety/azure-ai-contentsafety/generated_tests/testpreparer_async.py new file mode 100644 index 000000000000..0003142585ff --- /dev/null +++ b/sdk/contentsafety/azure-ai-contentsafety/generated_tests/testpreparer_async.py @@ -0,0 +1,31 @@ +# 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 azure.ai.contentsafety.aio import BlocklistClient, ContentSafetyClient +from devtools_testutils import AzureRecordedTestCase + + +class ContentSafetyClientTestBaseAsync(AzureRecordedTestCase): + + def create_async_client(self, endpoint): + credential = self.get_credential(ContentSafetyClient, is_async=True) + return self.create_client_from_credential( + ContentSafetyClient, + credential=credential, + endpoint=endpoint, + ) + + +class BlocklistClientTestBaseAsync(AzureRecordedTestCase): + + def create_async_client(self, endpoint): + credential = self.get_credential(BlocklistClient, is_async=True) + return self.create_client_from_credential( + BlocklistClient, + credential=credential, + endpoint=endpoint, + ) diff --git a/sdk/contentsafety/azure-ai-contentsafety/setup.py b/sdk/contentsafety/azure-ai-contentsafety/setup.py index b4e939ee2c66..bf8076d76021 100644 --- a/sdk/contentsafety/azure-ai-contentsafety/setup.py +++ b/sdk/contentsafety/azure-ai-contentsafety/setup.py @@ -42,11 +42,11 @@ "Programming Language :: Python", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "License :: OSI Approved :: MIT License", ], zip_safe=False, @@ -63,9 +63,9 @@ "azure.ai.contentsafety": ["py.typed"], }, install_requires=[ - "isodate<1.0.0,>=0.6.1", - "azure-core<2.0.0,>=1.28.0", - "typing-extensions>=4.3.0; python_version<'3.8.0'", + "isodate>=0.6.1", + "azure-core>=1.30.0", + "typing-extensions>=4.6.0", ], - python_requires=">=3.7", + python_requires=">=3.8", ) diff --git a/sdk/contentsafety/azure-ai-contentsafety/tests/conftest.py b/sdk/contentsafety/azure-ai-contentsafety/tests/conftest.py index eabe5360b66b..1ca14ff35bdf 100644 --- a/sdk/contentsafety/azure-ai-contentsafety/tests/conftest.py +++ b/sdk/contentsafety/azure-ai-contentsafety/tests/conftest.py @@ -9,6 +9,7 @@ from devtools_testutils import test_proxy, add_general_string_sanitizer import os + # autouse=True will trigger this fixture on each pytest run, even if it's not explicitly used by a test method @pytest.fixture(scope="session", autouse=True) def start_proxy(test_proxy): diff --git a/sdk/contentsafety/azure-ai-contentsafety/tsp-location.yaml b/sdk/contentsafety/azure-ai-contentsafety/tsp-location.yaml index 6b117195f5d3..f3c8dcbf77a7 100644 --- a/sdk/contentsafety/azure-ai-contentsafety/tsp-location.yaml +++ b/sdk/contentsafety/azure-ai-contentsafety/tsp-location.yaml @@ -1,4 +1,4 @@ directory: specification/cognitiveservices/ContentSafety -commit: fb39d9813934567c594ca253efff7c5c01a474e8 -repo: Azure/azure-rest-api-specs -additionalDirectories: [] \ No newline at end of file +commit: df98780fe1e53713c0f5c3785409c1e07529355b +repo: test-repo-billy/azure-rest-api-specs +additionalDirectories: