Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Azure AI Face SDK beta2 #37044

Merged
merged 31 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2979994
codegen update
Han-msft Aug 27, 2024
269e116
Update client _patch
Han-msft Aug 27, 2024
6f89e05
Update version
Han-msft Aug 27, 2024
ed8876b
Update assets
Han-msft Aug 27, 2024
4a19c27
Update codegen
Han-msft Aug 28, 2024
492b67b
Sample for large face list
Han-msft Aug 28, 2024
0fc87db
Add LPG sample
Han-msft Aug 28, 2024
ae592ed
Update codegen
Han-msft Sep 2, 2024
d7a0393
Remove unused testcase
Han-msft Sep 2, 2024
99837e6
Add findsimilar test
Han-msft Sep 2, 2024
9315435
Add identify test
Han-msft Sep 3, 2024
453f812
Remove FL and PG
Han-msft Sep 6, 2024
2cf5745
Fix test
Han-msft Sep 6, 2024
d29b753
Update codegen
Han-msft Sep 9, 2024
c5d7ed8
Remove _operations
Han-msft Sep 9, 2024
a1d365e
Add changelog
Han-msft Sep 9, 2024
0ec1f1e
Codegen
Han-msft Sep 9, 2024
b96d74a
Add new content to README
Han-msft Sep 10, 2024
b0f98e5
Merge remote-tracking branch 'origin/main' into hachiang/face-v1.1
Han-msft Sep 13, 2024
3c2367c
Regen
Han-msft Sep 13, 2024
5fd08b6
Update model enum
Han-msft Sep 19, 2024
3b4be0b
Update typo in generated test
Han-msft Sep 19, 2024
8c39cc2
Add enum breaking change schangelog
Han-msft Sep 24, 2024
af693ed
Merge remote-tracking branch 'origin/main' into hachiang/face-v1.1
Han-msft Oct 9, 2024
e2cb5ac
update codegen
Han-msft Oct 9, 2024
6900326
Fix customization
Han-msft Oct 9, 2024
efb967b
Suppress verifytypes
Han-msft Oct 9, 2024
3d21d99
Update to latest tsp
Han-msft Oct 9, 2024
fc3fb85
Pause verifytypes
Han-msft Oct 9, 2024
7d58902
Update release date
Han-msft Oct 22, 2024
4fa8355
Update date
Han-msft Oct 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions sdk/face/azure-ai-vision-face/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,25 @@

### Features Added

- Added support for the Large Face List and Large Person Group:
- Added operation groups `LargeFaceListOperations` and `LargePersonGroupOperations` to `FaceAdministrationClient`.
- Added operations `find_similar_from_large_face_list`, `identify_from_large_person_group` and `verify_from_large_person_group` to `FaceClient`.
msyyc marked this conversation as resolved.
Show resolved Hide resolved
- Added models for supporting Large Face List and Large Person Group.
- Added support for latest Detect Liveness Session API:
- Added operations `get_session_image` and `detect_from_session_image` to `FaceSessionClient`.
- Added properties `enable_session_image` and `liveness_single_modal_model` to model `CreateLivenessSessionContent`.
- Added model `CreateLivenessWithVerifySessionContent`.

### Breaking Changes

- Changed the parameter of `create_liveness_with_verify_session` from model `CreateLivenessSessionContent` to `CreateLivenessWithVerifySessionContent`.

### Bugs Fixed

### Other Changes

- Change the default service API version to `v1.2-preview.1`.

## 1.0.0b1 (2024-05-28)

This is the first preview of the `azure-ai-vision-face` client library that follows the [Azure Python SDK Design Guidelines](https://azure.github.io/azure-sdk/python_design.html).
Expand Down
2 changes: 1 addition & 1 deletion sdk/face/azure-ai-vision-face/assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "python",
"TagPrefix": "python/face/azure-ai-vision-face",
"Tag": "python/face/azure-ai-vision-face_f787b7aa30"
"Tag": "python/face/azure-ai-vision-face_0b4013000f"
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
# --------------------------------------------------------------------------

from ._client import FaceAdministrationClient
from ._patch import FaceClient
from ._patch import FaceSessionClient
from ._version import VERSION
Expand All @@ -16,6 +17,7 @@
from ._patch import patch_sdk as _patch_sdk

__all__ = [
"FaceAdministrationClient",
Han-msft marked this conversation as resolved.
Show resolved Hide resolved
"FaceClient",
"FaceSessionClient",
]
Expand Down
115 changes: 107 additions & 8 deletions sdk/face/azure-ai-vision-face/azure/ai/vision/face/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,120 @@

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
from azure.core.pipeline import policies
from azure.core.rest import HttpRequest, HttpResponse

from ._configuration import FaceClientConfiguration, FaceSessionClientConfiguration
from ._operations import FaceClientOperationsMixin, FaceSessionClientOperationsMixin
from ._configuration import (
FaceAdministrationClientConfiguration,
FaceClientConfiguration,
FaceSessionClientConfiguration,
)
from ._serialization import Deserializer, Serializer
from .operations import (
FaceClientOperationsMixin,
FaceSessionClientOperationsMixin,
LargeFaceListOperations,
LargePersonGroupOperations,
)

if TYPE_CHECKING:
# pylint: disable=unused-import,ungrouped-imports
from azure.core.credentials import TokenCredential


class FaceAdministrationClient: # pylint: disable=client-accepts-api-version-keyword
"""FaceAdministrationClient.

:ivar large_face_list: LargeFaceListOperations operations
:vartype large_face_list: azure.ai.vision.face.operations.LargeFaceListOperations
:ivar large_person_group: LargePersonGroupOperations operations
:vartype large_person_group: azure.ai.vision.face.operations.LargePersonGroupOperations
:param endpoint: Supported Cognitive Services endpoints (protocol and hostname, for example:
https://{resource-name}.cognitiveservices.azure.com). Required.
:type endpoint: str
: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: API Version. Known values are "v1.2-preview.1" and None. Default value is
"v1.2-preview.1". Note that overriding this default value may result in unsupported behavior.
:paramtype api_version: str or ~azure.ai.vision.face.models.Versions
:keyword int polling_interval: Default waiting time between two polls for LRO operations if no
Retry-After header is present.
"""

def __init__(self, endpoint: str, credential: Union[AzureKeyCredential, "TokenCredential"], **kwargs: Any) -> None:
_endpoint = "{endpoint}/face/{apiVersion}"
self._config = FaceAdministrationClientConfiguration(endpoint=endpoint, credential=credential, **kwargs)
_policies = kwargs.pop("policies", None)
if _policies is None:
_policies = [
policies.RequestIdPolicy(**kwargs),
self._config.headers_policy,
self._config.user_agent_policy,
self._config.proxy_policy,
policies.ContentDecodePolicy(**kwargs),
self._config.redirect_policy,
self._config.retry_policy,
self._config.authentication_policy,
self._config.custom_hook_policy,
self._config.logging_policy,
policies.DistributedTracingPolicy(**kwargs),
policies.SensitiveHeaderCleanupPolicy(**kwargs) if self._config.redirect_policy else None,
self._config.http_logging_policy,
]
self._client: PipelineClient = PipelineClient(base_url=_endpoint, policies=_policies, **kwargs)

self._serialize = Serializer()
self._deserialize = Deserializer()
self._serialize.client_side_validation = False
self.large_face_list = LargeFaceListOperations(self._client, self._config, self._serialize, self._deserialize)
self.large_person_group = LargePersonGroupOperations(
self._client, self._config, self._serialize, self._deserialize
)

def send_request(self, request: HttpRequest, *, stream: bool = False, **kwargs: Any) -> HttpResponse:
"""Runs the network request through the client's chained policies.

>>> from azure.core.rest import HttpRequest
>>> request = HttpRequest("GET", "https://www.example.org/")
<HttpRequest [GET], url: 'https://www.example.org/'>
>>> response = client.send_request(request)
<HttpResponse: 200 OK>

For more information on this code flow, see https://aka.ms/azsdk/dpcodegen/python/send_request

:param request: The network request you want to make. Required.
:type request: ~azure.core.rest.HttpRequest
:keyword bool stream: Whether the response payload will be streamed. Defaults to False.
:return: The response of your network call. Does not do error handling on your response.
:rtype: ~azure.core.rest.HttpResponse
"""

request_copy = deepcopy(request)
path_format_arguments = {
"endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True),
"apiVersion": self._serialize.url("self._config.api_version", self._config.api_version, "str"),
}

request_copy.url = self._client.format_url(request_copy.url, **path_format_arguments)
return self._client.send_request(request_copy, stream=stream, **kwargs) # type: ignore

def close(self) -> None:
self._client.close()

def __enter__(self) -> Self:
self._client.__enter__()
return self

def __exit__(self, *exc_details: Any) -> None:
self._client.__exit__(*exc_details)


class FaceClient(FaceClientOperationsMixin): # pylint: disable=client-accepts-api-version-keyword
"""FaceClient.

Expand All @@ -33,8 +132,8 @@ class FaceClient(FaceClientOperationsMixin): # pylint: disable=client-accepts-a
AzureKeyCredential type or a TokenCredential type. Required.
:type credential: ~azure.core.credentials.AzureKeyCredential or
~azure.core.credentials.TokenCredential
:keyword api_version: API Version. Default value is "v1.1-preview.1". Note that overriding this
default value may result in unsupported behavior.
:keyword api_version: API Version. Known values are "v1.2-preview.1" and None. Default value is
"v1.2-preview.1". Note that overriding this default value may result in unsupported behavior.
:paramtype api_version: str or ~azure.ai.vision.face.models.Versions
"""

Expand Down Expand Up @@ -94,7 +193,7 @@ def send_request(self, request: HttpRequest, *, stream: bool = False, **kwargs:
def close(self) -> None:
self._client.close()

def __enter__(self) -> "FaceClient":
def __enter__(self) -> Self:
self._client.__enter__()
return self

Expand All @@ -112,8 +211,8 @@ class FaceSessionClient(FaceSessionClientOperationsMixin): # pylint: disable=cl
AzureKeyCredential type or a TokenCredential type. Required.
:type credential: ~azure.core.credentials.AzureKeyCredential or
~azure.core.credentials.TokenCredential
:keyword api_version: API Version. Default value is "v1.1-preview.1". Note that overriding this
default value may result in unsupported behavior.
:keyword api_version: API Version. Known values are "v1.2-preview.1" and None. Default value is
"v1.2-preview.1". Note that overriding this default value may result in unsupported behavior.
:paramtype api_version: str or ~azure.ai.vision.face.models.Versions
"""

Expand Down Expand Up @@ -173,7 +272,7 @@ def send_request(self, request: HttpRequest, *, stream: bool = False, **kwargs:
def close(self) -> None:
self._client.close()

def __enter__(self) -> "FaceSessionClient":
def __enter__(self) -> Self:
self._client.__enter__()
return self

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,61 @@
from azure.core.credentials import TokenCredential


class FaceAdministrationClientConfiguration: # pylint: disable=too-many-instance-attributes,name-too-long
"""Configuration for FaceAdministrationClient.

Note that all parameters used to create this instance are saved as instance
attributes.

:param endpoint: Supported Cognitive Services endpoints (protocol and hostname, for example:
https://{resource-name}.cognitiveservices.azure.com). Required.
:type endpoint: str
: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: API Version. Known values are "v1.2-preview.1" and None. Default value is
"v1.2-preview.1". Note that overriding this default value may result in unsupported behavior.
:paramtype api_version: str or ~azure.ai.vision.face.models.Versions
"""

def __init__(self, endpoint: str, credential: Union[AzureKeyCredential, "TokenCredential"], **kwargs: Any) -> None:
api_version: str = kwargs.pop("api_version", "v1.2-preview.1")

if endpoint is None:
raise ValueError("Parameter 'endpoint' must not be None.")
if credential is None:
raise ValueError("Parameter 'credential' must not be None.")

self.endpoint = endpoint
self.credential = credential
self.api_version = api_version
self.credential_scopes = kwargs.pop("credential_scopes", ["https://cognitiveservices.azure.com/.default"])
kwargs.setdefault("sdk_moniker", "ai-vision-face/{}".format(VERSION))
self.polling_interval = kwargs.get("polling_interval", 30)
self._configure(**kwargs)

def _infer_policy(self, **kwargs):
if isinstance(self.credential, AzureKeyCredential):
return policies.AzureKeyCredentialPolicy(self.credential, "Ocp-Apim-Subscription-Key", **kwargs)
if hasattr(self.credential, "get_token"):
return policies.BearerTokenCredentialPolicy(self.credential, *self.credential_scopes, **kwargs)
raise TypeError(f"Unsupported credential: {self.credential}")

def _configure(self, **kwargs: Any) -> None:
self.user_agent_policy = kwargs.get("user_agent_policy") or policies.UserAgentPolicy(**kwargs)
self.headers_policy = kwargs.get("headers_policy") or policies.HeadersPolicy(**kwargs)
self.proxy_policy = kwargs.get("proxy_policy") or policies.ProxyPolicy(**kwargs)
self.logging_policy = kwargs.get("logging_policy") or policies.NetworkTraceLoggingPolicy(**kwargs)
self.http_logging_policy = kwargs.get("http_logging_policy") or policies.HttpLoggingPolicy(**kwargs)
self.custom_hook_policy = kwargs.get("custom_hook_policy") or policies.CustomHookPolicy(**kwargs)
self.redirect_policy = kwargs.get("redirect_policy") or policies.RedirectPolicy(**kwargs)
self.retry_policy = kwargs.get("retry_policy") or policies.RetryPolicy(**kwargs)
self.authentication_policy = kwargs.get("authentication_policy")
if self.credential and not self.authentication_policy:
self.authentication_policy = self._infer_policy(**kwargs)


class FaceClientConfiguration: # pylint: disable=too-many-instance-attributes
"""Configuration for FaceClient.

Expand All @@ -31,13 +86,13 @@ class FaceClientConfiguration: # pylint: disable=too-many-instance-attributes
AzureKeyCredential type or a TokenCredential type. Required.
:type credential: ~azure.core.credentials.AzureKeyCredential or
~azure.core.credentials.TokenCredential
:keyword api_version: API Version. Default value is "v1.1-preview.1". Note that overriding this
default value may result in unsupported behavior.
:keyword api_version: API Version. Known values are "v1.2-preview.1" and None. Default value is
"v1.2-preview.1". Note that overriding this default value may result in unsupported behavior.
:paramtype api_version: str or ~azure.ai.vision.face.models.Versions
"""

def __init__(self, endpoint: str, credential: Union[AzureKeyCredential, "TokenCredential"], **kwargs: Any) -> None:
api_version: str = kwargs.pop("api_version", "v1.1-preview.1")
api_version: str = kwargs.pop("api_version", "v1.2-preview.1")

if endpoint is None:
raise ValueError("Parameter 'endpoint' must not be None.")
Expand Down Expand Up @@ -86,13 +141,13 @@ class FaceSessionClientConfiguration: # pylint: disable=too-many-instance-attri
AzureKeyCredential type or a TokenCredential type. Required.
:type credential: ~azure.core.credentials.AzureKeyCredential or
~azure.core.credentials.TokenCredential
:keyword api_version: API Version. Default value is "v1.1-preview.1". Note that overriding this
default value may result in unsupported behavior.
:keyword api_version: API Version. Known values are "v1.2-preview.1" and None. Default value is
"v1.2-preview.1". Note that overriding this default value may result in unsupported behavior.
:paramtype api_version: str or ~azure.ai.vision.face.models.Versions
"""

def __init__(self, endpoint: str, credential: Union[AzureKeyCredential, "TokenCredential"], **kwargs: Any) -> None:
api_version: str = kwargs.pop("api_version", "v1.1-preview.1")
api_version: str = kwargs.pop("api_version", "v1.2-preview.1")

if endpoint is None:
raise ValueError("Parameter 'endpoint' must not be None.")
Expand Down
46 changes: 27 additions & 19 deletions sdk/face/azure-ai-vision-face/azure/ai/vision/face/_model_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,9 @@ def _create_value(rf: typing.Optional["_RestField"], value: typing.Any) -> typin

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__
Expand Down Expand Up @@ -508,24 +511,27 @@ def copy(self) -> "Model":
return Model(self.__dict__)

def __new__(cls, *args: typing.Any, **kwargs: typing.Any) -> Self: # 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())
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__") # 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())
cls._calculated.add(f"{cls.__module__}.{cls.__qualname__}")

return super().__new__(cls) # pylint: disable=no-value-for-parameter

Expand Down Expand Up @@ -563,6 +569,7 @@ 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():
Expand Down Expand Up @@ -883,5 +890,6 @@ 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,
) -> typing.Any:
return _RestField(name=name, type=type, is_discriminator=True)
return _RestField(name=name, type=type, is_discriminator=True, visibility=visibility)
Loading
Loading