diff --git a/sdk/confidentialledger/azure-confidentialledger/CHANGELOG.md b/sdk/confidentialledger/azure-confidentialledger/CHANGELOG.md index 645885090a54..685cb54ef7c3 100644 --- a/sdk/confidentialledger/azure-confidentialledger/CHANGELOG.md +++ b/sdk/confidentialledger/azure-confidentialledger/CHANGELOG.md @@ -1,5 +1,9 @@ # Release History +## 2.0.0 (2026-06-15) + +skip changelog generation for data-plane package and please add changelog manually. + ## 2.0.0b3 (2026-06-05) ### Features Added diff --git a/sdk/confidentialledger/azure-confidentialledger/_metadata.json b/sdk/confidentialledger/azure-confidentialledger/_metadata.json index 9b574d2c1b24..44deafa90967 100644 --- a/sdk/confidentialledger/azure-confidentialledger/_metadata.json +++ b/sdk/confidentialledger/azure-confidentialledger/_metadata.json @@ -1,3 +1,11 @@ { - "apiVersion": "2024-12-09-preview" + "apiVersion": "2026-02-23", + "apiVersions": { + "ConfidentialLedger": "2026-02-23" + }, + "commit": "8a3262d0e5c399e19b4e489368643a0e5b7f16e3", + "repository_url": "https://github.com/Azure/azure-rest-api-specs", + "typespec_src": "specification/confidentialledger/data-plane/ConfidentialLedger", + "emitterVersion": "0.63.1", + "httpClientPythonVersion": "^0.31.1" } \ No newline at end of file diff --git a/sdk/confidentialledger/azure-confidentialledger/apiview-properties.json b/sdk/confidentialledger/azure-confidentialledger/apiview-properties.json index afeb37d478db..51e247dd9ef8 100644 --- a/sdk/confidentialledger/azure-confidentialledger/apiview-properties.json +++ b/sdk/confidentialledger/azure-confidentialledger/apiview-properties.json @@ -27,7 +27,6 @@ "azure.confidentialledger.models.ReceiptElement": "ConfidentialLedger.ReceiptElement", "azure.confidentialledger.models.ReceiptLeafComponents": "ConfidentialLedger.ReceiptLeafComponents", "azure.confidentialledger.models.Role": "ConfidentialLedger.Role", - "azure.confidentialledger.models.Roles": "ConfidentialLedger.Roles", "azure.confidentialledger.models.TransactionReceipt": "ConfidentialLedger.TransactionReceipt", "azure.confidentialledger.models.TransactionStatus": "ConfidentialLedger.TransactionStatus", "azure.confidentialledger.models.UserDefinedFunction": "ConfidentialLedger.UserDefinedFunction", @@ -36,6 +35,8 @@ "azure.confidentialledger.models.UserDefinedFunctionExecutionResponse": "ConfidentialLedger.UserDefinedFunctionExecutionResponse", "azure.confidentialledger.models.UserDefinedFunctionExecutionResult": "ConfidentialLedger.UserDefinedFunctionExecutionResult", "azure.confidentialledger.models.UserDefinedFunctionHook": "ConfidentialLedger.UserDefinedFunctionHook", + "azure.confidentialledger.models.UserDefinedRole": "ConfidentialLedger.UserDefinedRole", + "azure.confidentialledger.models.UserDefinedRoles": "ConfidentialLedger.UserDefinedRoles", "azure.confidentialledger.models.ConfidentialLedgerQueryState": "ConfidentialLedger.ConfidentialLedgerQueryState", "azure.confidentialledger.models.ApplicationClaimProtocol": "ConfidentialLedger.ApplicationClaimProtocol", "azure.confidentialledger.models.ApplicationClaimKind": "ConfidentialLedger.ApplicationClaimKind", @@ -53,6 +54,8 @@ "azure.confidentialledger.aio.ConfidentialLedgerClient.get_enclave_quotes": "ConfidentialLedger.getEnclaveQuotes", "azure.confidentialledger.ConfidentialLedgerClient.list_collections": "ConfidentialLedger.listCollections", "azure.confidentialledger.aio.ConfidentialLedgerClient.list_collections": "ConfidentialLedger.listCollections", + "azure.confidentialledger.ConfidentialLedgerClient.list_tags": "ConfidentialLedger.listTags", + "azure.confidentialledger.aio.ConfidentialLedgerClient.list_tags": "ConfidentialLedger.listTags", "azure.confidentialledger.ConfidentialLedgerClient.list_ledger_entries": "ConfidentialLedger.listLedgerEntries", "azure.confidentialledger.aio.ConfidentialLedgerClient.list_ledger_entries": "ConfidentialLedger.listLedgerEntries", "azure.confidentialledger.ConfidentialLedgerClient.create_ledger_entry": "ConfidentialLedger.createLedgerEntry", @@ -87,8 +90,8 @@ "azure.confidentialledger.aio.ConfidentialLedgerClient.create_user_defined_endpoint": "ConfidentialLedger.createUserDefinedEndpoint", "azure.confidentialledger.ConfidentialLedgerClient.get_runtime_options": "ConfidentialLedger.getRuntimeOptions", "azure.confidentialledger.aio.ConfidentialLedgerClient.get_runtime_options": "ConfidentialLedger.getRuntimeOptions", - "azure.confidentialledger.ConfidentialLedgerClient.update_runtime_options": "ConfidentialLedger.updateRuntimeOptions", - "azure.confidentialledger.aio.ConfidentialLedgerClient.update_runtime_options": "ConfidentialLedger.updateRuntimeOptions", + "azure.confidentialledger.ConfidentialLedgerClient.update_runtime_options_stable": "ConfidentialLedger.updateRuntimeOptionsStable", + "azure.confidentialledger.aio.ConfidentialLedgerClient.update_runtime_options_stable": "ConfidentialLedger.updateRuntimeOptionsStable", "azure.confidentialledger.ConfidentialLedgerClient.get_user_defined_endpoints_module": "ConfidentialLedger.getUserDefinedEndpointsModule", "azure.confidentialledger.aio.ConfidentialLedgerClient.get_user_defined_endpoints_module": "ConfidentialLedger.getUserDefinedEndpointsModule", "azure.confidentialledger.ConfidentialLedgerClient.list_user_defined_functions": "ConfidentialLedger.listUserDefinedFunctions", @@ -103,11 +106,12 @@ "azure.confidentialledger.aio.ConfidentialLedgerClient.execute_user_defined_function": "ConfidentialLedger.executeUserDefinedFunction", "azure.confidentialledger.ConfidentialLedgerClient.get_user_defined_role": "ConfidentialLedger.getUserDefinedRole", "azure.confidentialledger.aio.ConfidentialLedgerClient.get_user_defined_role": "ConfidentialLedger.getUserDefinedRole", - "azure.confidentialledger.ConfidentialLedgerClient.create_user_defined_role": "ConfidentialLedger.createUserDefinedRole", - "azure.confidentialledger.aio.ConfidentialLedgerClient.create_user_defined_role": "ConfidentialLedger.createUserDefinedRole", - "azure.confidentialledger.ConfidentialLedgerClient.update_user_defined_role": "ConfidentialLedger.updateUserDefinedRole", - "azure.confidentialledger.aio.ConfidentialLedgerClient.update_user_defined_role": "ConfidentialLedger.updateUserDefinedRole", - "azure.confidentialledger.ConfidentialLedgerClient.delete_user_defined_role": "ConfidentialLedger.deleteUserDefinedRole", - "azure.confidentialledger.aio.ConfidentialLedgerClient.delete_user_defined_role": "ConfidentialLedger.deleteUserDefinedRole" - } + "azure.confidentialledger.ConfidentialLedgerClient.create_user_defined_role_stable": "ConfidentialLedger.createUserDefinedRoleStable", + "azure.confidentialledger.aio.ConfidentialLedgerClient.create_user_defined_role_stable": "ConfidentialLedger.createUserDefinedRoleStable", + "azure.confidentialledger.ConfidentialLedgerClient.update_user_defined_role_stable": "ConfidentialLedger.updateUserDefinedRoleStable", + "azure.confidentialledger.aio.ConfidentialLedgerClient.update_user_defined_role_stable": "ConfidentialLedger.updateUserDefinedRoleStable", + "azure.confidentialledger.ConfidentialLedgerClient.delete_user_defined_role_stable": "ConfidentialLedger.deleteUserDefinedRoleStable", + "azure.confidentialledger.aio.ConfidentialLedgerClient.delete_user_defined_role_stable": "ConfidentialLedger.deleteUserDefinedRoleStable" + }, + "CrossLanguageVersion": "4c5f007c40fa" } \ No newline at end of file diff --git a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_client.py b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_client.py index fa11e8b73f11..fee3a653c7f4 100644 --- a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_client.py +++ b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_client.py @@ -7,8 +7,8 @@ # -------------------------------------------------------------------------- from copy import deepcopy +import sys from typing import Any -from typing_extensions import Self from azure.core import PipelineClient from azure.core.pipeline import policies @@ -16,9 +16,13 @@ from ._configuration import ConfidentialLedgerClientConfiguration from ._operations import _ConfidentialLedgerClientOperationsMixin -from ._redirect_caching_policy import RedirectCachingPolicy from ._utils.serialization import Deserializer, Serializer +if sys.version_info >= (3, 11): + from typing import Self +else: + from typing_extensions import Self # type: ignore + class ConfidentialLedgerClient(_ConfidentialLedgerClientOperationsMixin): """Write and retrieve ledger entries against the Confidential Ledger service. @@ -27,9 +31,9 @@ class ConfidentialLedgerClient(_ConfidentialLedgerClientOperationsMixin): `https://contoso.confidentialledger.azure.com `_. Required. :type ledger_endpoint: str - :keyword api_version: The API version to use for this operation. Default value is - "2024-12-09-preview". Note that overriding this default value may result in unsupported - behavior. + :keyword api_version: The API version to use for this operation. Known values are "2026-02-23" + and None. Default value is None. If not set, the operation's default API version will be used. + Note that overriding this default value may result in unsupported behavior. :paramtype api_version: str """ @@ -47,19 +51,13 @@ def __init__( # pylint: disable=missing-client-constructor-parameter-credential self._config.user_agent_policy, self._config.proxy_policy, policies.ContentDecodePolicy(**kwargs), - kwargs.get("redirect_policy") or RedirectCachingPolicy(**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), - # Redirect cleanup is disabled to preserve authentication and ledger-specific headers - # on service-managed redirects. Confidential Ledger redirects are expected to stay within - # the same trusted ledger endpoint, so forwarding these sensitive headers is required - # for correct authentication behavior. - policies.SensitiveHeaderCleanupPolicy( - disable_redirect_cleanup=True, **kwargs - ) if self._config.redirect_policy else None, + 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) diff --git a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_configuration.py b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_configuration.py index 9042692d9795..936ca23d6a4e 100644 --- a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_configuration.py +++ b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_configuration.py @@ -23,14 +23,14 @@ class ConfidentialLedgerClientConfiguration: # pylint: disable=too-many-instanc `https://contoso.confidentialledger.azure.com `_. Required. :type ledger_endpoint: str - :keyword api_version: The API version to use for this operation. Default value is - "2024-12-09-preview". Note that overriding this default value may result in unsupported - behavior. + :keyword api_version: The API version to use for this operation. Known values are "2026-02-23" + and None. Default value is None. If not set, the operation's default API version will be used. + Note that overriding this default value may result in unsupported behavior. :paramtype api_version: str """ def __init__(self, ledger_endpoint: str, **kwargs: Any) -> None: - api_version: str = kwargs.pop("api_version", "2024-12-09-preview") + api_version: str = kwargs.pop("api_version", "2026-02-23") if ledger_endpoint is None: raise ValueError("Parameter 'ledger_endpoint' must not be None.") diff --git a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_operations/_operations.py b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_operations/_operations.py index 77da3c99ebaf..7ababd67fcad 100644 --- a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_operations/_operations.py +++ b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_operations/_operations.py @@ -34,6 +34,7 @@ from .._utils.model_base import SdkJSONEncoder, _deserialize, _failsafe_deserialize from .._utils.serialization import Serializer from .._utils.utils import ClientMixinABC +from .._validation import api_version_validation JSON = MutableMapping[str, Any] T = TypeVar("T") @@ -47,7 +48,7 @@ def build_confidential_ledger_get_constitution_request(**kwargs: Any) -> HttpReq _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", "2024-12-09-preview")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-02-23")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -68,7 +69,7 @@ def build_confidential_ledger_list_consortium_members_request( # pylint: disabl _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", "2024-12-09-preview")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-02-23")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -87,7 +88,7 @@ def build_confidential_ledger_get_enclave_quotes_request(**kwargs: Any) -> HttpR _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", "2024-12-09-preview")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-02-23")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -106,7 +107,7 @@ def build_confidential_ledger_list_collections_request(**kwargs: Any) -> HttpReq _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", "2024-12-09-preview")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-02-23")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -121,6 +122,29 @@ def build_confidential_ledger_list_collections_request(**kwargs: Any) -> HttpReq return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) +def build_confidential_ledger_list_tags_request( # pylint: disable=name-too-long + *, collection_id: Optional[str] = None, **kwargs: Any +) -> HttpRequest: + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) + + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-02-23")) + accept = _headers.pop("Accept", "application/json") + + # Construct URL + _url = "/app/collections/tags" + + # Construct parameters + _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") + if collection_id is not None: + _params["collectionId"] = _SERIALIZER.query("collection_id", collection_id, "str") + + # Construct headers + _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") + + return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) + + def build_confidential_ledger_list_ledger_entries_request( # pylint: disable=name-too-long *, collection_id: Optional[str] = None, @@ -132,7 +156,7 @@ def build_confidential_ledger_list_ledger_entries_request( # pylint: disable=na _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", "2024-12-09-preview")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-02-23")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -162,7 +186,7 @@ def build_confidential_ledger_create_ledger_entry_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", "2024-12-09-preview")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-02-23")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -189,7 +213,7 @@ def build_confidential_ledger_get_ledger_entry_request( # pylint: disable=name- _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", "2024-12-09-preview")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-02-23")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -217,7 +241,7 @@ def build_confidential_ledger_get_receipt_request( # pylint: disable=name-too-l _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", "2024-12-09-preview")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-02-23")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -243,7 +267,7 @@ def build_confidential_ledger_get_transaction_status_request( # pylint: disable _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", "2024-12-09-preview")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-02-23")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -269,7 +293,7 @@ def build_confidential_ledger_get_current_ledger_entry_request( # pylint: disab _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", "2024-12-09-preview")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-02-23")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -290,7 +314,7 @@ def build_confidential_ledger_list_users_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", "2024-12-09-preview")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-02-23")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -309,7 +333,7 @@ def build_confidential_ledger_list_ledger_users_request(**kwargs: Any) -> HttpRe _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", "2024-12-09-preview")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-02-23")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -329,7 +353,7 @@ def build_confidential_ledger_delete_user_request( # pylint: disable=name-too-l ) -> HttpRequest: _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) - api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2024-12-09-preview")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-02-23")) # Construct URL _url = "/app/users/{userId}" path_format_arguments = { @@ -350,7 +374,7 @@ def build_confidential_ledger_get_user_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", "2024-12-09-preview")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-02-23")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -377,7 +401,7 @@ def build_confidential_ledger_create_or_update_user_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", "2024-12-09-preview")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-02-23")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -404,7 +428,7 @@ def build_confidential_ledger_delete_ledger_user_request( # pylint: disable=nam ) -> HttpRequest: _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) - api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2024-12-09-preview")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-02-23")) # Construct URL _url = "/app/ledgerUsers/{userId}" path_format_arguments = { @@ -425,7 +449,7 @@ def build_confidential_ledger_get_ledger_user_request( # pylint: disable=name-t _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", "2024-12-09-preview")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-02-23")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -452,7 +476,7 @@ def build_confidential_ledger_create_or_update_ledger_user_request( # pylint: d _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-12-09-preview")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-02-23")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -480,7 +504,7 @@ def build_confidential_ledger_get_user_defined_endpoint_request( # pylint: disa _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", "2024-12-09-preview")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-02-23")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -502,7 +526,7 @@ def build_confidential_ledger_create_user_defined_endpoint_request( # pylint: d _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-12-09-preview")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-02-23")) # Construct URL _url = "/app/userDefinedEndpoints" @@ -522,7 +546,7 @@ def build_confidential_ledger_get_runtime_options_request( # pylint: disable=na _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", "2024-12-09-preview")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-02-23")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -537,14 +561,14 @@ def build_confidential_ledger_get_runtime_options_request( # pylint: disable=na return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) -def build_confidential_ledger_update_runtime_options_request( # pylint: disable=name-too-long +def build_confidential_ledger_update_runtime_options_stable_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", "2024-12-09-preview")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-02-23")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -567,7 +591,7 @@ def build_confidential_ledger_get_user_defined_endpoints_module_request( # pyli _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", "2024-12-09-preview")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-02-23")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -589,7 +613,7 @@ def build_confidential_ledger_list_user_defined_functions_request( # pylint: di _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", "2024-12-09-preview")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-02-23")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -609,7 +633,7 @@ def build_confidential_ledger_delete_user_defined_function_request( # pylint: d ) -> HttpRequest: _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) - api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2024-12-09-preview")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-02-23")) # Construct URL _url = "/app/userDefinedFunctions/{functionId}" path_format_arguments = { @@ -630,7 +654,7 @@ def build_confidential_ledger_get_user_defined_function_request( # pylint: disa _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", "2024-12-09-preview")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-02-23")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -657,7 +681,7 @@ def build_confidential_ledger_create_user_defined_function_request( # pylint: d _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-12-09-preview")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-02-23")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -686,7 +710,7 @@ def build_confidential_ledger_execute_user_defined_function_request( # pylint: _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-12-09-preview")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-02-23")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -714,7 +738,7 @@ def build_confidential_ledger_get_user_defined_role_request( # pylint: disable= _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", "2024-12-09-preview")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-02-23")) accept = _headers.pop("Accept", "application/json") # Construct URL @@ -730,14 +754,16 @@ def build_confidential_ledger_get_user_defined_role_request( # pylint: disable= return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) -def build_confidential_ledger_create_user_defined_role_request( # pylint: disable=name-too-long +def build_confidential_ledger_create_user_defined_role_stable_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", "2024-12-09-preview")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-02-23")) + accept = _headers.pop("Accept", "application/json") + # Construct URL _url = "/app/roles" @@ -747,18 +773,21 @@ def build_confidential_ledger_create_user_defined_role_request( # pylint: disab # 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="PUT", url=_url, params=_params, headers=_headers, **kwargs) -def build_confidential_ledger_update_user_defined_role_request( # pylint: disable=name-too-long +def build_confidential_ledger_update_user_defined_role_stable_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", "2024-12-09-preview")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-02-23")) + accept = _headers.pop("Accept", "application/json") + # Construct URL _url = "/app/roles" @@ -768,16 +797,17 @@ def build_confidential_ledger_update_user_defined_role_request( # pylint: disab # 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="PATCH", url=_url, params=_params, headers=_headers, **kwargs) -def build_confidential_ledger_delete_user_defined_role_request( # pylint: disable=name-too-long +def build_confidential_ledger_delete_user_defined_role_stable_request( # pylint: disable=name-too-long *, role_name: str, **kwargs: Any ) -> HttpRequest: _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) - api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2024-12-09-preview")) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "2026-02-23")) # Construct URL _url = "/app/roles" @@ -796,8 +826,7 @@ class _ConfidentialLedgerClientOperationsMixin( # pylint: disable=too-many-publ def get_constitution(self, **kwargs: Any) -> _models.Constitution: """Gets the constitution used for governance. - The constitution is a script that assesses and applies proposals from - consortium members. + The constitution is a script that assesses and applies proposals from consortium members. :return: Constitution. The Constitution is compatible with MutableMapping :rtype: ~azure.confidentialledger.models.Constitution @@ -828,6 +857,7 @@ def get_constitution(self, **kwargs: Any) -> _models.Constitution: } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -842,11 +872,14 @@ def get_constitution(self, **kwargs: Any) -> _models.Constitution: except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.Constitution, response.json()) @@ -904,7 +937,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + headers=_headers, + params=_next_request_params, ) path_format_arguments = { "ledgerEndpoint": self._serialize.url( @@ -917,7 +953,10 @@ def prepare_request(next_link=None): def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize(list[_models.ConsortiumMember], deserialized.get("members", [])) + list_of_elem = _deserialize( + list[_models.ConsortiumMember], + deserialized.get("members", []), + ) if cls: list_of_elem = cls(list_of_elem) # type: ignore return deserialized.get("nextLink") or None, iter(list_of_elem) @@ -933,7 +972,10 @@ def get_next(next_link=None): if response.status_code not in [200]: map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) return pipeline_response @@ -944,8 +986,8 @@ def get_next(next_link=None): def get_enclave_quotes(self, **kwargs: Any) -> _models.ConfidentialLedgerEnclaves: """Gets quotes for all nodes of the Confidential Ledger. - A quote is an SGX enclave measurement that can be used to verify the validity - of a node and its enclave. + A quote is an SGX enclave measurement that can be used to verify the validity of a node and its + enclave. :return: ConfidentialLedgerEnclaves. The ConfidentialLedgerEnclaves is compatible with MutableMapping @@ -977,6 +1019,7 @@ def get_enclave_quotes(self, **kwargs: Any) -> _models.ConfidentialLedgerEnclave } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -991,11 +1034,14 @@ def get_enclave_quotes(self, **kwargs: Any) -> _models.ConfidentialLedgerEnclave except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.ConfidentialLedgerEnclaves, response.json()) @@ -1053,7 +1099,112 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + headers=_headers, + params=_next_request_params, + ) + path_format_arguments = { + "ledgerEndpoint": self._serialize.url( + "self._config.ledger_endpoint", self._config.ledger_endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + return _request + + def extract_data(pipeline_response): + deserialized = pipeline_response.http_response.json() + list_of_elem = _deserialize( + list[_models.Collection], + deserialized.get("collections", []), + ) + if cls: + list_of_elem = cls(list_of_elem) # type: ignore + return deserialized.get("nextLink") or None, iter(list_of_elem) + + def get_next(next_link=None): + _request = prepare_request(next_link) + + _stream = False + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + response = pipeline_response.http_response + + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) + raise HttpResponseError(response=response, model=error) + + return pipeline_response + + return ItemPaged(get_next, extract_data) + + @distributed_trace + @api_version_validation( + method_added_on="2026-02-23", + params_added_on={"2026-02-23": ["api_version", "collection_id", "accept"]}, + api_versions_list=["2026-02-23"], + ) + def list_tags(self, *, collection_id: Optional[str] = None, **kwargs: Any) -> ItemPaged[str]: + """Gets a list of tags for a collection. + + Retrieves the tags associated with a collection. + + :keyword collection_id: The collection id. Default value is None. + :paramtype collection_id: str + :return: An iterator like instance of str + :rtype: ~azure.core.paging.ItemPaged[str] + :raises ~azure.core.exceptions.HttpResponseError: + """ + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[list[str]] = kwargs.pop("cls", None) + + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + def prepare_request(next_link=None): + if not next_link: + + _request = build_confidential_ledger_list_tags_request( + collection_id=collection_id, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "ledgerEndpoint": self._serialize.url( + "self._config.ledger_endpoint", self._config.ledger_endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + else: + # make call to next link with the client's api-version + _parsed_next_link = urllib.parse.urlparse(next_link) + _next_request_params = case_insensitive_dict( + { + key: [urllib.parse.quote(v) for v in value] + for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items() + } + ) + _next_request_params["api-version"] = self._config.api_version + _request = HttpRequest( + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + headers=_headers, + params=_next_request_params, ) path_format_arguments = { "ledgerEndpoint": self._serialize.url( @@ -1066,7 +1217,10 @@ def prepare_request(next_link=None): def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize(list[_models.Collection], deserialized.get("collections", [])) + list_of_elem = _deserialize( + list[str], + deserialized.get("tags", []), + ) if cls: list_of_elem = cls(list_of_elem) # type: ignore return deserialized.get("nextLink") or None, iter(list_of_elem) @@ -1082,7 +1236,10 @@ def get_next(next_link=None): if response.status_code not in [200]: map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) return pipeline_response @@ -1101,8 +1258,8 @@ def list_ledger_entries( ) -> ItemPaged["_models.LedgerEntry"]: """Gets ledger entries from a collection corresponding to a range. - A collection id may optionally be specified. Only entries in the specified (or - default) collection will be returned. + A collection id may optionally be specified. Only entries in the specified (or default) + collection will be returned. :keyword collection_id: The collection id. Default value is None. :paramtype collection_id: str @@ -1160,7 +1317,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + headers=_headers, + params=_next_request_params, ) path_format_arguments = { "ledgerEndpoint": self._serialize.url( @@ -1173,7 +1333,10 @@ def prepare_request(next_link=None): def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize(list[_models.LedgerEntry], deserialized.get("entries", [])) + list_of_elem = _deserialize( + list[_models.LedgerEntry], + deserialized.get("entries", []), + ) if cls: list_of_elem = cls(list_of_elem) # type: ignore return deserialized.get("nextLink") or None, iter(list_of_elem) @@ -1189,7 +1352,10 @@ def get_next(next_link=None): if response.status_code not in [200]: map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) return pipeline_response @@ -1341,6 +1507,7 @@ def create_ledger_entry( } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -1355,7 +1522,10 @@ def create_ledger_entry( except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) response_headers = {} @@ -1364,7 +1534,7 @@ def create_ledger_entry( ) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.LedgerWriteResult, response.json()) @@ -1377,14 +1547,12 @@ def create_ledger_entry( def get_ledger_entry( self, transaction_id: str, *, collection_id: Optional[str] = None, **kwargs: Any ) -> _models.LedgerQueryResult: - """Gets the ledger entry at the specified transaction id. A collection id may - optionally be specified to indicate the collection from which to fetch the - value. + """Gets the ledger entry at the specified transaction id. A collection id may optionally be + specified to indicate the collection from which to fetch the value. - To return older ledger entries, the relevant sections of the ledger must be - read from disk and validated. To prevent blocking within the enclave, the - response will indicate whether the entry is ready and part of the response, or - if the loading is still ongoing. + To return older ledger entries, the relevant sections of the ledger must be read from disk and + validated. To prevent blocking within the enclave, the response will indicate whether the entry + is ready and part of the response, or if the loading is still ongoing. :param transaction_id: Identifies a write transaction. Required. :type transaction_id: str @@ -1421,6 +1589,7 @@ def get_ledger_entry( } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -1435,11 +1604,14 @@ def get_ledger_entry( except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.LedgerQueryResult, response.json()) @@ -1486,6 +1658,7 @@ def get_receipt(self, transaction_id: str, **kwargs: Any) -> _models.Transaction } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -1500,11 +1673,14 @@ def get_receipt(self, transaction_id: str, **kwargs: Any) -> _models.Transaction except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.TransactionReceipt, response.json()) @@ -1551,6 +1727,7 @@ def get_transaction_status(self, transaction_id: str, **kwargs: Any) -> _models. } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -1565,11 +1742,14 @@ def get_transaction_status(self, transaction_id: str, **kwargs: Any) -> _models. except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.TransactionStatus, response.json()) @@ -1616,6 +1796,7 @@ def get_current_ledger_entry(self, *, collection_id: Optional[str] = None, **kwa } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -1630,11 +1811,14 @@ def get_current_ledger_entry(self, *, collection_id: Optional[str] = None, **kwa except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.LedgerEntry, response.json()) @@ -1692,7 +1876,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + headers=_headers, + params=_next_request_params, ) path_format_arguments = { "ledgerEndpoint": self._serialize.url( @@ -1705,7 +1892,10 @@ def prepare_request(next_link=None): def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize(list[_models.LedgerUser], deserialized.get("ledgerUsers", [])) + list_of_elem = _deserialize( + list[_models.LedgerUser], + deserialized.get("ledgerUsers", []), + ) if cls: list_of_elem = cls(list_of_elem) # type: ignore return deserialized.get("nextLink") or None, iter(list_of_elem) @@ -1721,7 +1911,10 @@ def get_next(next_link=None): if response.status_code not in [200]: map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) return pipeline_response @@ -1777,7 +1970,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + headers=_headers, + params=_next_request_params, ) path_format_arguments = { "ledgerEndpoint": self._serialize.url( @@ -1790,7 +1986,10 @@ def prepare_request(next_link=None): def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize(list[_models.LedgerUserMultipleRoles], deserialized.get("ledgerUsers", [])) + list_of_elem = _deserialize( + list[_models.LedgerUserMultipleRoles], + deserialized.get("ledgerUsers", []), + ) if cls: list_of_elem = cls(list_of_elem) # type: ignore return deserialized.get("nextLink") or None, iter(list_of_elem) @@ -1806,7 +2005,10 @@ def get_next(next_link=None): if response.status_code not in [200]: map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) return pipeline_response @@ -1860,7 +2062,10 @@ def delete_user(self, user_id: str, **kwargs: Any) -> None: # pylint: disable=i if response.status_code not in [204]: map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if cls: @@ -1904,6 +2109,7 @@ def get_user(self, user_id: str, **kwargs: Any) -> _models.LedgerUser: } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -1918,11 +2124,14 @@ def get_user(self, user_id: str, **kwargs: Any) -> _models.LedgerUser: except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.LedgerUser, response.json()) @@ -2054,6 +2263,7 @@ def create_or_update_user( } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -2068,11 +2278,14 @@ def create_or_update_user( except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.LedgerUser, response.json()) @@ -2128,7 +2341,10 @@ def delete_ledger_user(self, user_id: str, **kwargs: Any) -> None: # pylint: di if response.status_code not in [204]: map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if cls: @@ -2172,6 +2388,7 @@ def get_ledger_user(self, user_id: str, **kwargs: Any) -> _models.LedgerUserMult } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -2186,11 +2403,14 @@ def get_ledger_user(self, user_id: str, **kwargs: Any) -> _models.LedgerUserMult except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.LedgerUserMultipleRoles, response.json()) @@ -2331,6 +2551,7 @@ def create_or_update_ledger_user( } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -2345,11 +2566,14 @@ def create_or_update_ledger_user( except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.LedgerUserMultipleRoles, response.json()) @@ -2393,6 +2617,7 @@ def get_user_defined_endpoint(self, **kwargs: Any) -> _models.Bundle: } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -2407,11 +2632,14 @@ def get_user_defined_endpoint(self, **kwargs: Any) -> _models.Bundle: except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.Bundle, response.json()) @@ -2533,7 +2761,10 @@ def create_user_defined_endpoint( # pylint: disable=inconsistent-return-stateme if response.status_code not in [201]: map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if cls: @@ -2574,6 +2805,7 @@ def get_runtime_options(self, **kwargs: Any) -> _models.JsRuntimeOptions: } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -2588,11 +2820,14 @@ def get_runtime_options(self, **kwargs: Any) -> _models.JsRuntimeOptions: except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.JsRuntimeOptions, response.json()) @@ -2602,8 +2837,12 @@ def get_runtime_options(self, **kwargs: Any) -> _models.JsRuntimeOptions: return deserialized # type: ignore @overload - def update_runtime_options( - self, js_runtime_options: _models.JsRuntimeOptions, *, content_type: str = "application/json", **kwargs: Any + def update_runtime_options_stable( + self, + js_runtime_options: _models.JsRuntimeOptions, + *, + content_type: str = "application/merge-patch+json", + **kwargs: Any, ) -> _models.JsRuntimeOptions: """Runtime options for user defined endpoints. @@ -2612,7 +2851,7 @@ def update_runtime_options( :param js_runtime_options: JS Runtime options. Required. :type js_runtime_options: ~azure.confidentialledger.models.JsRuntimeOptions :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". + Default value is "application/merge-patch+json". :paramtype content_type: str :return: JsRuntimeOptions. The JsRuntimeOptions is compatible with MutableMapping :rtype: ~azure.confidentialledger.models.JsRuntimeOptions @@ -2620,8 +2859,8 @@ def update_runtime_options( """ @overload - def update_runtime_options( - self, js_runtime_options: JSON, *, content_type: str = "application/json", **kwargs: Any + def update_runtime_options_stable( + self, js_runtime_options: JSON, *, content_type: str = "application/merge-patch+json", **kwargs: Any ) -> _models.JsRuntimeOptions: """Runtime options for user defined endpoints. @@ -2630,7 +2869,7 @@ def update_runtime_options( :param js_runtime_options: JS Runtime options. Required. :type js_runtime_options: JSON :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". + Default value is "application/merge-patch+json". :paramtype content_type: str :return: JsRuntimeOptions. The JsRuntimeOptions is compatible with MutableMapping :rtype: ~azure.confidentialledger.models.JsRuntimeOptions @@ -2638,8 +2877,8 @@ def update_runtime_options( """ @overload - def update_runtime_options( - self, js_runtime_options: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + def update_runtime_options_stable( + self, js_runtime_options: IO[bytes], *, content_type: str = "application/merge-patch+json", **kwargs: Any ) -> _models.JsRuntimeOptions: """Runtime options for user defined endpoints. @@ -2648,7 +2887,7 @@ def update_runtime_options( :param js_runtime_options: JS Runtime options. Required. :type js_runtime_options: IO[bytes] :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". + Default value is "application/merge-patch+json". :paramtype content_type: str :return: JsRuntimeOptions. The JsRuntimeOptions is compatible with MutableMapping :rtype: ~azure.confidentialledger.models.JsRuntimeOptions @@ -2656,7 +2895,12 @@ def update_runtime_options( """ @distributed_trace - def update_runtime_options( + @api_version_validation( + method_added_on="2026-02-23", + params_added_on={"2026-02-23": ["api_version", "content_type", "accept"]}, + api_versions_list=["2026-02-23"], + ) + def update_runtime_options_stable( self, js_runtime_options: Union[_models.JsRuntimeOptions, JSON, IO[bytes]], **kwargs: Any ) -> _models.JsRuntimeOptions: """Runtime options for user defined endpoints. @@ -2685,14 +2929,14 @@ def update_runtime_options( content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) cls: ClsType[_models.JsRuntimeOptions] = kwargs.pop("cls", None) - content_type = content_type or "application/json" + content_type = content_type or "application/merge-patch+json" _content = None if isinstance(js_runtime_options, (IOBase, bytes)): _content = js_runtime_options else: _content = json.dumps(js_runtime_options, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_confidential_ledger_update_runtime_options_request( + _request = build_confidential_ledger_update_runtime_options_stable_request( content_type=content_type, api_version=self._config.api_version, content=_content, @@ -2706,6 +2950,7 @@ def update_runtime_options( } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -2720,11 +2965,14 @@ def update_runtime_options( except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.JsRuntimeOptions, response.json()) @@ -2771,6 +3019,7 @@ def get_user_defined_endpoints_module(self, *, module_name: str, **kwargs: Any) } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -2785,11 +3034,14 @@ def get_user_defined_endpoints_module(self, *, module_name: str, **kwargs: Any) except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.ModuleDef, response.json()) @@ -2847,7 +3099,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + headers=_headers, + params=_next_request_params, ) path_format_arguments = { "ledgerEndpoint": self._serialize.url( @@ -2860,7 +3115,10 @@ def prepare_request(next_link=None): def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize(list[_models.UserDefinedFunction], deserialized.get("functions", [])) + list_of_elem = _deserialize( + list[_models.UserDefinedFunction], + deserialized.get("functions", []), + ) if cls: list_of_elem = cls(list_of_elem) # type: ignore return deserialized.get("nextLink") or None, iter(list_of_elem) @@ -2876,7 +3134,10 @@ def get_next(next_link=None): if response.status_code not in [200]: map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) return pipeline_response @@ -2932,7 +3193,10 @@ def delete_user_defined_function( # pylint: disable=inconsistent-return-stateme if response.status_code not in [204]: map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if cls: @@ -2976,6 +3240,7 @@ def get_user_defined_function(self, function_id: str, **kwargs: Any) -> _models. } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -2990,11 +3255,14 @@ def get_user_defined_function(self, function_id: str, **kwargs: Any) -> _models. except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.UserDefinedFunction, response.json()) @@ -3133,6 +3401,7 @@ def create_user_defined_function( } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -3147,7 +3416,10 @@ def create_user_defined_function( except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) response_headers = {} @@ -3156,7 +3428,7 @@ def create_user_defined_function( ) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.UserDefinedFunction, response.json()) @@ -3312,6 +3584,7 @@ def execute_user_defined_function( } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -3326,11 +3599,14 @@ def execute_user_defined_function( except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.UserDefinedFunctionExecutionResponse, response.json()) @@ -3340,15 +3616,15 @@ def execute_user_defined_function( return deserialized # type: ignore @distributed_trace - def get_user_defined_role(self, *, role_name: str, **kwargs: Any) -> _models.Roles: + def get_user_defined_role(self, *, role_name: str, **kwargs: Any) -> _models.UserDefinedRole: """Gets role actions for user defined roles. user defined roles allow users to define and manage app specific AuthZ policy. :keyword role_name: user defined role name. Required. :paramtype role_name: str - :return: Roles. The Roles is compatible with MutableMapping - :rtype: ~azure.confidentialledger.models.Roles + :return: UserDefinedRole. The UserDefinedRole is compatible with MutableMapping + :rtype: ~azure.confidentialledger.models.UserDefinedRole :raises ~azure.core.exceptions.HttpResponseError: """ error_map: MutableMapping = { @@ -3362,7 +3638,7 @@ def get_user_defined_role(self, *, role_name: str, **kwargs: Any) -> _models.Rol _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[_models.Roles] = kwargs.pop("cls", None) + cls: ClsType[_models.UserDefinedRole] = kwargs.pop("cls", None) _request = build_confidential_ledger_get_user_defined_role_request( role_name=role_name, @@ -3377,6 +3653,7 @@ def get_user_defined_role(self, *, role_name: str, **kwargs: Any) -> _models.Rol } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -3391,13 +3668,16 @@ def get_user_defined_role(self, *, role_name: str, **kwargs: Any) -> _models.Rol except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.Roles, response.json()) + deserialized = _deserialize(_models.UserDefinedRole, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -3405,25 +3685,27 @@ def get_user_defined_role(self, *, role_name: str, **kwargs: Any) -> _models.Rol return deserialized # type: ignore @overload - def create_user_defined_role( - self, body: _models.Roles, *, content_type: str = "application/json", **kwargs: Any - ) -> None: + def create_user_defined_role_stable( + self, body: _models.UserDefinedRoles, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.UserDefinedRoles: """Creates new roles and their actions. User defined roles allow users to define and manage app specific AuthZ policy. :param body: Request body. Required. - :type body: ~azure.confidentialledger.models.Roles + :type body: ~azure.confidentialledger.models.UserDefinedRoles :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :return: None - :rtype: None + :return: UserDefinedRoles. The UserDefinedRoles is compatible with MutableMapping + :rtype: ~azure.confidentialledger.models.UserDefinedRoles :raises ~azure.core.exceptions.HttpResponseError: """ @overload - def create_user_defined_role(self, body: JSON, *, content_type: str = "application/json", **kwargs: Any) -> None: + def create_user_defined_role_stable( + self, body: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.UserDefinedRoles: """Creates new roles and their actions. User defined roles allow users to define and manage app specific AuthZ policy. @@ -3433,15 +3715,15 @@ def create_user_defined_role(self, body: JSON, *, content_type: str = "applicati :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :return: None - :rtype: None + :return: UserDefinedRoles. The UserDefinedRoles is compatible with MutableMapping + :rtype: ~azure.confidentialledger.models.UserDefinedRoles :raises ~azure.core.exceptions.HttpResponseError: """ @overload - def create_user_defined_role( + def create_user_defined_role_stable( self, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> None: + ) -> _models.UserDefinedRoles: """Creates new roles and their actions. User defined roles allow users to define and manage app specific AuthZ policy. @@ -3451,23 +3733,29 @@ def create_user_defined_role( :keyword content_type: Body Parameter content-type. Content type parameter for binary body. Default value is "application/json". :paramtype content_type: str - :return: None - :rtype: None + :return: UserDefinedRoles. The UserDefinedRoles is compatible with MutableMapping + :rtype: ~azure.confidentialledger.models.UserDefinedRoles :raises ~azure.core.exceptions.HttpResponseError: """ @distributed_trace - def create_user_defined_role( # pylint: disable=inconsistent-return-statements - self, body: Union[_models.Roles, JSON, IO[bytes]], **kwargs: Any - ) -> None: + @api_version_validation( + method_added_on="2026-02-23", + params_added_on={"2026-02-23": ["api_version", "content_type", "accept"]}, + api_versions_list=["2026-02-23"], + ) + def create_user_defined_role_stable( + self, body: Union[_models.UserDefinedRoles, JSON, IO[bytes]], **kwargs: Any + ) -> _models.UserDefinedRoles: """Creates new roles and their actions. User defined roles allow users to define and manage app specific AuthZ policy. - :param body: Request body. Is one of the following types: Roles, JSON, IO[bytes] Required. - :type body: ~azure.confidentialledger.models.Roles or JSON or IO[bytes] - :return: None - :rtype: None + :param body: Request body. Is one of the following types: UserDefinedRoles, JSON, IO[bytes] + Required. + :type body: ~azure.confidentialledger.models.UserDefinedRoles or JSON or IO[bytes] + :return: UserDefinedRoles. The UserDefinedRoles is compatible with MutableMapping + :rtype: ~azure.confidentialledger.models.UserDefinedRoles :raises ~azure.core.exceptions.HttpResponseError: """ error_map: MutableMapping = { @@ -3482,7 +3770,7 @@ def create_user_defined_role( # pylint: disable=inconsistent-return-statements _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[None] = kwargs.pop("cls", None) + cls: ClsType[_models.UserDefinedRoles] = kwargs.pop("cls", None) content_type = content_type or "application/json" _content = None @@ -3491,7 +3779,7 @@ def create_user_defined_role( # pylint: disable=inconsistent-return-statements else: _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_confidential_ledger_create_user_defined_role_request( + _request = build_confidential_ledger_create_user_defined_role_stable_request( content_type=content_type, api_version=self._config.api_version, content=_content, @@ -3505,7 +3793,8 @@ def create_user_defined_role( # pylint: disable=inconsistent-return-statements } _request.url = self._client.format_url(_request.url, **path_format_arguments) - _stream = False + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs ) @@ -3513,81 +3802,104 @@ def create_user_defined_role( # pylint: disable=inconsistent-return-statements 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) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.UserDefinedRoles, response.json()) + if cls: - return cls(pipeline_response, None, {}) # type: ignore + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore @overload - def update_user_defined_role( - self, body: _models.Roles, *, content_type: str = "application/json", **kwargs: Any - ) -> None: - """Patch replaces the allowed action on existing roles,if the desire is to remove - an existing action, the role must be deleted and recreated. + def update_user_defined_role_stable( + self, body: _models.UserDefinedRoles, *, content_type: str = "application/merge-patch+json", **kwargs: Any + ) -> _models.UserDefinedRoles: + """Patch replaces the allowed action on existing roles,if the desire is to remove an existing + action, the role must be deleted and recreated. User defined roles allow users to define and manage app specific AuthZ policy. :param body: Request body. Required. - :type body: ~azure.confidentialledger.models.Roles + :type body: ~azure.confidentialledger.models.UserDefinedRoles :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". + Default value is "application/merge-patch+json". :paramtype content_type: str - :return: None - :rtype: None + :return: UserDefinedRoles. The UserDefinedRoles is compatible with MutableMapping + :rtype: ~azure.confidentialledger.models.UserDefinedRoles :raises ~azure.core.exceptions.HttpResponseError: """ @overload - def update_user_defined_role(self, body: JSON, *, content_type: str = "application/json", **kwargs: Any) -> None: - """Patch replaces the allowed action on existing roles,if the desire is to remove - an existing action, the role must be deleted and recreated. + def update_user_defined_role_stable( + self, body: JSON, *, content_type: str = "application/merge-patch+json", **kwargs: Any + ) -> _models.UserDefinedRoles: + """Patch replaces the allowed action on existing roles,if the desire is to remove an existing + action, the role must be deleted and recreated. User defined roles allow users to define and manage app specific AuthZ policy. :param body: Request body. Required. :type body: JSON :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". + Default value is "application/merge-patch+json". :paramtype content_type: str - :return: None - :rtype: None + :return: UserDefinedRoles. The UserDefinedRoles is compatible with MutableMapping + :rtype: ~azure.confidentialledger.models.UserDefinedRoles :raises ~azure.core.exceptions.HttpResponseError: """ @overload - def update_user_defined_role( - self, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> None: - """Patch replaces the allowed action on existing roles,if the desire is to remove - an existing action, the role must be deleted and recreated. + def update_user_defined_role_stable( + self, body: IO[bytes], *, content_type: str = "application/merge-patch+json", **kwargs: Any + ) -> _models.UserDefinedRoles: + """Patch replaces the allowed action on existing roles,if the desire is to remove an existing + action, the role must be deleted and recreated. User defined roles allow users to define and manage app specific AuthZ policy. :param body: Request body. Required. :type body: IO[bytes] :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". + Default value is "application/merge-patch+json". :paramtype content_type: str - :return: None - :rtype: None + :return: UserDefinedRoles. The UserDefinedRoles is compatible with MutableMapping + :rtype: ~azure.confidentialledger.models.UserDefinedRoles :raises ~azure.core.exceptions.HttpResponseError: """ @distributed_trace - def update_user_defined_role( # pylint: disable=inconsistent-return-statements - self, body: Union[_models.Roles, JSON, IO[bytes]], **kwargs: Any - ) -> None: - """Patch replaces the allowed action on existing roles,if the desire is to remove - an existing action, the role must be deleted and recreated. + @api_version_validation( + method_added_on="2026-02-23", + params_added_on={"2026-02-23": ["api_version", "content_type", "accept"]}, + api_versions_list=["2026-02-23"], + ) + def update_user_defined_role_stable( + self, body: Union[_models.UserDefinedRoles, JSON, IO[bytes]], **kwargs: Any + ) -> _models.UserDefinedRoles: + """Patch replaces the allowed action on existing roles,if the desire is to remove an existing + action, the role must be deleted and recreated. User defined roles allow users to define and manage app specific AuthZ policy. - :param body: Request body. Is one of the following types: Roles, JSON, IO[bytes] Required. - :type body: ~azure.confidentialledger.models.Roles or JSON or IO[bytes] - :return: None - :rtype: None + :param body: Request body. Is one of the following types: UserDefinedRoles, JSON, IO[bytes] + Required. + :type body: ~azure.confidentialledger.models.UserDefinedRoles or JSON or IO[bytes] + :return: UserDefinedRoles. The UserDefinedRoles is compatible with MutableMapping + :rtype: ~azure.confidentialledger.models.UserDefinedRoles :raises ~azure.core.exceptions.HttpResponseError: """ error_map: MutableMapping = { @@ -3602,16 +3914,16 @@ def update_user_defined_role( # pylint: disable=inconsistent-return-statements _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[None] = kwargs.pop("cls", None) + cls: ClsType[_models.UserDefinedRoles] = kwargs.pop("cls", None) - content_type = content_type or "application/json" + content_type = content_type or "application/merge-patch+json" _content = None if isinstance(body, (IOBase, bytes)): _content = body else: _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_confidential_ledger_update_user_defined_role_request( + _request = build_confidential_ledger_update_user_defined_role_stable_request( content_type=content_type, api_version=self._config.api_version, content=_content, @@ -3625,7 +3937,8 @@ def update_user_defined_role( # pylint: disable=inconsistent-return-statements } _request.url = self._client.format_url(_request.url, **path_format_arguments) - _stream = False + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs ) @@ -3633,21 +3946,40 @@ def update_user_defined_role( # pylint: disable=inconsistent-return-statements 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) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.UserDefinedRoles, response.json()) + if cls: - return cls(pipeline_response, None, {}) # type: ignore + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore @distributed_trace - def delete_user_defined_role( # pylint: disable=inconsistent-return-statements + @api_version_validation( + method_added_on="2026-02-23", + params_added_on={"2026-02-23": ["api_version", "role_name"]}, + api_versions_list=["2026-02-23"], + ) + def delete_user_defined_role_stable( # pylint: disable=inconsistent-return-statements self, *, role_name: str, **kwargs: Any ) -> None: """Deletes user defined roles. - A user defined role allows the users to create and manage their own role - actions using the API. + A user defined role allows the users to create and manage their own role actions using the API. :keyword role_name: user defined role name. Required. :paramtype role_name: str @@ -3668,7 +4000,7 @@ def delete_user_defined_role( # pylint: disable=inconsistent-return-statements cls: ClsType[None] = kwargs.pop("cls", None) - _request = build_confidential_ledger_delete_user_defined_role_request( + _request = build_confidential_ledger_delete_user_defined_role_stable_request( role_name=role_name, api_version=self._config.api_version, headers=_headers, @@ -3688,9 +4020,12 @@ def delete_user_defined_role( # pylint: disable=inconsistent-return-statements response = pipeline_response.http_response - if response.status_code not in [200]: + if response.status_code not in [204]: map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if cls: diff --git a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_operations/_patch.py b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_operations/_patch.py index 77951c223179..87676c65a8f0 100644 --- a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_operations/_patch.py +++ b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_operations/_patch.py @@ -8,21 +8,8 @@ Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ -import time -from typing import Any, Callable, IO, List, Optional, Union, cast -from azure.core.exceptions import ResourceNotFoundError -from azure.core.polling import PollingMethod, LROPoller, NoPolling - -from azure.confidentialledger._operations._operations import ( - _ConfidentialLedgerClientOperationsMixin as GeneratedOperationsMixin, -) -from azure.confidentialledger._operations._operations import ClsType, JSON -import azure.confidentialledger.models as _models - -__all__: List[str] = [ - "_ConfidentialLedgerClientOperationsMixin" -] # Add all objects you want publicly available to users at this package level +__all__: list[str] = [] # Add all objects you want publicly available to users at this package level def patch_sdk(): @@ -32,314 +19,3 @@ def patch_sdk(): you can't accomplish using the techniques described in https://aka.ms/azsdk/python/dpcodegen/python/customize """ - - -class BaseStatePollingMethod: - """Base polling method for methods returning responses containing a 'state' field; the polling - completes when 'state' becomes a desired value. - """ - - def __init__( - self, - operation: Callable[[], Any], - desired_state: str, - polling_interval_s: float, - retry_not_found: bool, - ): - self._operation = operation - self._desired_state = desired_state - self._polling_interval_s = polling_interval_s - - self._deserialization_callback = None - self._status = "constructed" - self._latest_response: JSON = {} - - self._retry_not_found = retry_not_found - self._not_found_count = 0 - - def initialize(self, client, initial_response, deserialization_callback): # pylint: disable=unused-argument - self._evaluate_response(initial_response) - self._deserialization_callback = deserialization_callback - - def _evaluate_response(self, response: JSON) -> None: - self._status = "finished" if response.get("state", None) == self._desired_state else "polling" - self._latest_response = response - - def _give_up_not_found_error(self, exception: ResourceNotFoundError) -> bool: - if exception.error is not None and exception.error.code == "InvalidTransactionId": - return True - return False - - def status(self) -> str: - return self._status - - def finished(self) -> bool: - return self.status() in {"finished", "failed"} - - def resource(self): - if self._deserialization_callback: - return self._deserialization_callback(self._latest_response) - return self._latest_response - - -class StatePollingMethod(BaseStatePollingMethod, PollingMethod): - """Polling method for methods returning responses containing a 'state' field; the polling - completes when 'state' becomes a desired value. - """ - - def __init__( - self, - operation: Callable[[], JSON], - desired_state: str, - polling_interval_s: float, - retry_not_found: bool, - ): - super().__init__(operation, desired_state, polling_interval_s, retry_not_found) - - def run(self) -> None: - try: - while not self.finished(): - try: - response = self._operation() - self._evaluate_response(response) - except ResourceNotFoundError as not_found_exception: - # We'll allow some instances of resource not found to account for replication - # delay if session stickiness is lost. - - self._not_found_count += 1 - - not_retryable = not self._retry_not_found or self._give_up_not_found_error(not_found_exception) - - if not_retryable or self._not_found_count >= 3: - raise - if not self.finished(): - time.sleep(self._polling_interval_s) - except Exception: - self._status = "failed" - raise - - -class _ConfidentialLedgerClientOperationsMixin(GeneratedOperationsMixin): - def begin_get_ledger_entry( - self, transaction_id: str, *, collection_id: Optional[str] = None, **kwargs: Any - ) -> LROPoller[_models.LedgerQueryResult]: - """Returns a poller to fetch the ledger entry at the specified transaction id. - - A collection id may optionally be specified to indicate the collection from which to fetch - the value. - - To return older ledger entries, the relevant sections of the ledger must be - read from disk and validated. To prevent blocking within the enclave, the - response will indicate whether the entry is ready and part of the response, or - if the loading is still ongoing. - - :param transaction_id: Identifies a write transaction. Required. - :type transaction_id: str - :keyword collection_id: The collection id. Default value is None. - :paramtype collection_id: str - :return: An instance of LROPoller that returns a LedgerQueryResult object for the ledger entry. - :rtype: ~azure.core.polling.LROPoller[~azure.confidentialledger.models.LedgerQueryResult] - :raises ~azure.core.exceptions.HttpResponseError: - """ - polling = kwargs.pop("polling", True) # type: Union[bool, PollingMethod] - lro_delay = kwargs.pop("polling_interval", 0.5) - - def operation() -> JSON: - return super(_ConfidentialLedgerClientOperationsMixin, self).get_ledger_entry( - transaction_id, collection_id=collection_id, **kwargs - ) - - initial_response = operation() - - if polling is True: - polling_method = cast(PollingMethod, StatePollingMethod(operation, "Ready", lro_delay, False)) - elif polling is False: - polling_method = cast(PollingMethod, NoPolling()) - else: - polling_method = polling - return LROPoller(self._client, initial_response, lambda x: x, polling_method) - - def begin_get_receipt(self, transaction_id: str, **kwargs: Any) -> LROPoller[_models.TransactionReceipt]: - """Returns a poller for getting a receipt certifying ledger contents at a particular - transaction id. - - :param transaction_id: Identifies a write transaction. Required. - :type transaction_id: str - :return: An instance of LROPoller that returns a TransactionReceipt object for the receipt. - :rtype: ~azure.core.polling.LROPoller[~azure.confidentialledger.models.TransactionReceipt] - :raises ~azure.core.exceptions.HttpResponseError: - """ - polling = kwargs.pop("polling", True) # type: Union[bool, PollingMethod] - lro_delay = kwargs.pop("polling_interval", 0.5) - - def operation() -> JSON: - return super(_ConfidentialLedgerClientOperationsMixin, self).get_receipt( - transaction_id=transaction_id, **kwargs - ) - - initial_response = operation() - - if polling is True: - polling_method = cast(PollingMethod, StatePollingMethod(operation, "Ready", lro_delay, False)) - elif polling is False: - polling_method = cast(PollingMethod, NoPolling()) - else: - polling_method = polling - return LROPoller(self._client, initial_response, lambda x: x, polling_method) - - def begin_create_ledger_entry( - self, - entry: Union[_models.LedgerEntry, JSON, IO[bytes]], - *, - collection_id: Optional[str] = None, - **kwargs: Any, - ) -> LROPoller[_models.TransactionStatus]: - """Writes a ledger entry and returns a poller to wait for it to be durably committed. The - poller returns the result for the initial call to create the ledger entry. - - A collection id may optionally be specified. - - :param entry: Ledger entry. Is one of the following types: LedgerEntry, JSON, IO[bytes] - Required. - :type entry: ~azure.confidentialledger.models.LedgerEntry or JSON or IO[bytes] - :keyword collection_id: The collection id. Default value is None. - :paramtype collection_id: str - :return: TransactionStatus. The TransactionStatus is compatible with MutableMapping - :rtype: ~azure.core.polling.LROPoller[~azure.confidentialledger.models.TransactionStatus] - :raises ~azure.core.exceptions.HttpResponseError: - """ - - # Pop arguments that are unexpected in the pipeline. - - polling = kwargs.pop("polling", True) # type: Union[bool, PollingMethod] - lro_delay = kwargs.pop("polling_interval", 0.5) - - # Pop the custom deserializer, if any, so we know the format of the response and can - # retrieve the transactionId. Serialize the response later. - - cls = kwargs.pop("cls", None) # type: ClsType[JSON] - kwargs["cls"] = lambda pipeline_response, json_response, headers: ( - pipeline_response, - { - **json_response, - "transactionId": headers.get("x-ms-ccf-transaction-id") if headers else None, - }, - headers, - ) - - post_pipeline_response, post_result, post_headers = self.create_ledger_entry( - entry, collection_id=collection_id, **kwargs - ) - - # Delete the cls because it should only apply to the create_ledger_entry response, not the - # wait_for_commit call. - - del kwargs["cls"] - - transaction_id = post_result["transactionId"] # type: ignore - - kwargs["polling"] = polling - kwargs["polling_interval"] = lro_delay - - if cls: - kwargs["_create_ledger_entry_response"] = cls( - post_pipeline_response, cast(JSON, post_result), post_headers # type: ignore - ) - else: - kwargs["_create_ledger_entry_response"] = post_result - return self.begin_wait_for_commit(transaction_id, **kwargs) - - def begin_wait_for_commit( - self, - transaction_id, # type: str - **kwargs, # type: Any - ) -> LROPoller[_models.TransactionStatus]: - """Creates a poller that queries the state of the specified transaction until it is - Committed, a state that indicates the transaction is durably stored in the Confidential - Ledger. - - :param transaction_id: Identifies a write transaction. Required. - :type transaction_id: str - :return: An instance of LROPoller returning a TransactionStatus object describing the transaction status. - :rtype: ~azure.core.polling.LROPoller[~azure.confidentialledger.models.TransactionStatus] - :raises ~azure.core.exceptions.HttpResponseError: - """ - polling = kwargs.pop("polling", True) # type: Union[bool, PollingMethod] - lro_delay = kwargs.pop("polling_interval", 0.5) - - # If this poller was called from begin_create_ledger_entry, we should return the - # create_ledger_entry response, not the transaction status. - - post_result = kwargs.pop("_create_ledger_entry_response", None) - - def deserialization_callback(x): - return x if post_result is None else post_result - - def operation() -> JSON: - return super(_ConfidentialLedgerClientOperationsMixin, self).get_transaction_status( - transaction_id=transaction_id, **kwargs - ) - - try: - initial_response = operation() - except ResourceNotFoundError: - if polling is False or polling is None: - raise - # This method allows for temporary resource not found errors, which may occur if session - # stickiness is lost and there is replication lag. - - initial_response = {} - if polling is True: - polling_method = cast(PollingMethod, StatePollingMethod(operation, "Committed", lro_delay, True)) - elif polling is False: - polling_method = cast(PollingMethod, NoPolling()) - else: - polling_method = polling - return LROPoller(self._client, initial_response, deserialization_callback, polling_method) - - def create_ledger_entry( - self, - entry: Union[_models.LedgerEntry, JSON, IO[bytes]], - *, - collection_id: Optional[str] = None, - **kwargs: Any, - ) -> _models.LedgerWriteResult: - """Writes a ledger entry. - - The result is the expected JSON response with an additional field - 'transactionId' which represents the transaction identifier for this write operation. - - A collection id may optionally be specified. - - :param entry: Ledger entry. Is one of the following types: LedgerEntry, JSON, IO[bytes] - Required. - :type entry: ~azure.confidentialledger.models.LedgerEntry or JSON or IO[bytes] - :keyword collection_id: The collection id. Default value is None. - :paramtype collection_id: str - :return: LedgerWriteResult. The LedgerWriteResult is compatible with MutableMapping - :rtype: ~azure.confidentialledger.models.LedgerWriteResult - :raises ~azure.core.exceptions.HttpResponseError: - - Example: - .. code-block:: python - - # JSON input template you can fill out and use as your body input. - entry = { - "collectionId": { - "collectionId": "str" # Required. - }, - "contents": "str", # Required. Contents of the ledger entry. - "transactionId": "str" # Optional. A unique identifier for the state of the - ledger. If returned as part of a LedgerEntry, it indicates the state from which - the entry was read. - } - """ - - kwargs["cls"] = kwargs.get( - "cls", - lambda _, json_response, headers: { - **json_response, - "transactionId": headers.get("x-ms-ccf-transaction-id") if headers else None, - }, - ) - return super().create_ledger_entry(entry, collection_id=collection_id, **kwargs) diff --git a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_patch.py b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_patch.py index 59473e6e901b..87676c65a8f0 100644 --- a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_patch.py +++ b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_patch.py @@ -7,19 +7,9 @@ Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ -import os -from typing import Any, List, Union -from azure.core.credentials import TokenCredential -from azure.core.pipeline import policies -from azure.confidentialledger._client import ConfidentialLedgerClient as GeneratedClient -from azure.confidentialledger.certificate import ConfidentialLedgerCertificateClient # pylint: disable=import-error,no-name-in-module - -__all__: List[str] = [ - "ConfidentialLedgerCertificateCredential", - "ConfidentialLedgerClient", -] # Add all objects you want publicly available to users at this package level +__all__: list[str] = [] # Add all objects you want publicly available to users at this package level def patch_sdk(): @@ -29,92 +19,3 @@ def patch_sdk(): you can't accomplish using the techniques described in https://aka.ms/azsdk/python/dpcodegen/python/customize """ - - -class ConfidentialLedgerCertificateCredential: - """A certificate-based credential for the ConfidentialLedgerClient. - - :param certificate_path: Path to the PEM certificate to use for authentication. - :type certificate_path: Union[bytes, str, os.PathLike] - """ - - def __init__(self, certificate_path: Union[bytes, str, os.PathLike]): - self._certificate_path = certificate_path - - @property - def certificate_path(self) -> Union[bytes, str, os.PathLike]: - """The path to the certificate file for this credential. - - :return: The path to the certificate file for this credential. - :rtype: Union[bytes, str, os.PathLike]""" - - return self._certificate_path - - -class ConfidentialLedgerClient(GeneratedClient): - """The ConfidentialLedgerClient writes and retrieves ledger entries against the Confidential - Ledger service. - - :param endpoint: The Confidential Ledger URL, for example - https://contoso.confidentialledger.azure.com. - :type endpoint: str - :param credential: A credential object for authenticating with the Confidential Ledger. - :type credential: Union[ - ~azure.confidentialledger.ConfidentialLedgerCertificateCredential, - ~azure.core.credentials.TokenCredential] - :keyword ledger_certificate_path: The path to the Confidential Ledger's TLS certificate. If this - file does not exist yet, the Confidential Ledger's TLS certificate will be fetched and saved - to this file. - :paramtype ledger_certificate_path: Union[bytes, str, os.PathLike] - :keyword api_version: Api Version. Default value is "2022-05-13". Note that overriding this - default value may result in unsupported behavior. - :paramtype api_version: str - """ - - def __init__( - self, - endpoint: str, - credential: Union[ConfidentialLedgerCertificateCredential, TokenCredential], - *, - ledger_certificate_path: Union[bytes, str, os.PathLike], - **kwargs: Any, - ) -> None: - # Remove some kwargs first so that there aren't unexpected kwargs passed to - # get_ledger_identity. - - if isinstance(credential, ConfidentialLedgerCertificateCredential): - auth_policy = None - else: - credential_scopes = kwargs.pop("credential_scopes", ["https://confidential-ledger.azure.com/.default"]) - auth_policy = kwargs.pop( - "authentication_policy", - policies.BearerTokenCredentialPolicy(credential, *credential_scopes, **kwargs), - ) - if os.path.isfile(ledger_certificate_path) is False: - # We'll need to fetch the TLS certificate. - - identity_service_client = ConfidentialLedgerCertificateClient(**kwargs) - - # Ledger URIs are of the form https://.confidential-ledger.azure.com. - - ledger_id = endpoint.replace("https://", "").split(".")[0] - ledger_cert = identity_service_client.get_ledger_identity(ledger_id, **kwargs) - - with open(ledger_certificate_path, "w", encoding="utf-8") as outfile: - outfile.write(ledger_cert["ledgerTlsCertificate"]) - # For ConfidentialLedgerCertificateCredential, pass the path to the certificate down to the - # PipelineCLient. - - if isinstance(credential, ConfidentialLedgerCertificateCredential): - kwargs["connection_cert"] = kwargs.get("connection_cert", credential.certificate_path) - # The auto-generated client has authentication disabled so we can customize authentication. - # If the credential is the typical TokenCredential, then construct the authentication policy - # the normal way. - - else: - kwargs["authentication_policy"] = auth_policy - # Customize the underlying client to use a self-signed TLS certificate. - - kwargs["connection_verify"] = kwargs.get("connection_verify", ledger_certificate_path) - - super().__init__(endpoint, **kwargs) diff --git a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_redirect_caching_policy.py b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_redirect_caching_policy.py deleted file mode 100644 index 7fbf8c798fba..000000000000 --- a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_redirect_caching_policy.py +++ /dev/null @@ -1,373 +0,0 @@ -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ - -"""Custom redirect policies that cache the redirect target URL for write operations. - -Write requests (POST / PUT / PATCH / DELETE) that receive a 307 redirect from -the load-balancer are followed, and the target URL's base (scheme + host) is -cached so that subsequent writes skip the load-balancer entirely. - -Read requests (GET, HEAD, OPTIONS, …) **never** consult or populate the cache -and always go through the load-balancer. - -The cache is invalidated on 5xx responses or transport errors so that a -failover is respected on the next write. - -Redirect destination policy -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -A redirect is only followed when **all** of the following hold for the target: - -* the scheme is ``https`` (an ``https`` -> ``http`` downgrade is rejected so the - bearer token can never travel over cleartext); -* the effective port matches the original request's effective port (the scheme - default — 443 for ``https`` — is used when no port is given); and -* the host is the original request host or one of its subdomains (e.g. an - individual ledger node). - -Redirects to sibling ledgers, parent domains, unrelated hosts, look-alike suffix -domains, downgraded (``http``) schemes, or different ports are rejected and never -followed or cached. This prevents a misconfigured or malicious load-balancer from -redirecting requests (and their sensitive headers, including the ``Authorization`` -bearer token which is *not* stripped on redirect) to an unintended destination. - -Thread-safety -~~~~~~~~~~~~~ -* Reads of the cached value are lock-free (CPython GIL guarantees atomic - reference reads — *Volatile.Read* semantics). -* Writes are protected by a :class:`threading.Lock` (*Volatile.Write* - semantics) so that at most one thread mutates the cached reference at a - time. -""" - -import logging -import threading -from typing import FrozenSet, Optional -from urllib.parse import ParseResult, urlparse, urlunparse - -from azure.core.pipeline import PipelineRequest, PipelineResponse -from azure.core.pipeline.policies import AsyncHTTPPolicy, HTTPPolicy - -_LOGGER = logging.getLogger(__name__) - -_WRITE_METHODS: FrozenSet[str] = frozenset({"POST", "PUT", "PATCH", "DELETE"}) - -_REDIRECT_STATUS_CODES = frozenset({301, 302, 307, 308}) - - -class _RedirectUrlCache: - """Thread-safe cache for a redirect target base URL.""" - - def __init__(self) -> None: - self._lock = threading.Lock() - self._cached_base_url: Optional[str] = None - - # -- Volatile.Read -------------------------------------------------- - def get(self) -> Optional[str]: - """Return the cached base URL or *None* (lock-free).""" - return self._cached_base_url - - # -- Volatile.Write ------------------------------------------------- - def set(self, url: str) -> None: # pylint: disable=redefined-builtin - """Extract and cache the base URL (scheme + host) from *url*. - - :param str url: The full redirect target URL. - """ - parsed = urlparse(url) - base_url = f"{parsed.scheme}://{parsed.netloc}" - with self._lock: - self._cached_base_url = base_url - - def invalidate(self) -> None: - """Clear the cached value.""" - with self._lock: - self._cached_base_url = None - - -def _rewrite_url(original_url: str, cached_base_url: str) -> str: - """Replace the scheme + host of *original_url* with *cached_base_url*. - - :param str original_url: The original request URL. - :param str cached_base_url: The cached base URL (scheme + host) to use. - :return: The rewritten URL. - :rtype: str - """ - original = urlparse(original_url) - cached = urlparse(cached_base_url) - return urlunparse( - ( - cached.scheme, - cached.netloc, - original.path, - original.params, - original.query, - original.fragment, - ) - ) - - -def _is_redirect(status_code: int) -> bool: - return status_code in _REDIRECT_STATUS_CODES - - -def _effective_port(parsed: ParseResult) -> Optional[int]: - """Return the effective port for a parsed URL, applying the scheme default. - - :param parsed: A :func:`urllib.parse.urlparse` result. - :type parsed: ~urllib.parse.ParseResult - :return: The explicit port, or the scheme default (443 for ``https``, - 80 for ``http``), or ``None`` if it cannot be determined. - :rtype: Optional[int] - """ - try: - explicit = parsed.port - except ValueError: - # An invalid (out-of-range) port in the URL. - return None - if explicit is not None: - return explicit - scheme = (parsed.scheme or "").lower() - if scheme == "https": - return 443 - if scheme == "http": - return 80 - return None - - -def _is_allowed_redirect_target(original_url: str, target_url: str) -> bool: - """Return whether *target_url* is an allowed redirect destination. - - A redirect is permitted only when **all** of these hold: - - * the target scheme is ``https`` (no downgrade — the bearer token must never - travel over cleartext); - * the target's effective port equals the original request's effective port - (the scheme default is used when no port is present); and - * the target host is identical to the original request host or is a subdomain - of it. - - All other targets — sibling hosts, parent domains, unrelated hosts, look-alike - suffix domains, downgraded schemes, and different ports — are rejected. Host - comparison is case-insensitive. - - :param str original_url: The URL of the original request. - :param str target_url: The redirect target URL (e.g. from a ``Location`` header). - :return: True if the redirect target is permitted, otherwise False. - :rtype: bool - """ - original = urlparse(original_url) - target = urlparse(target_url) - - original_host = (original.hostname or "").lower() - target_host = (target.hostname or "").lower() - - # Fail safe: if either host cannot be determined, do not follow the redirect. - if not original_host or not target_host: - return False - - # Require HTTPS on the target so the bearer token is never sent over cleartext. - if (target.scheme or "").lower() != "https": - return False - - # Require the same effective port (scheme default applied when absent). - original_port = _effective_port(original) - target_port = _effective_port(target) - if original_port is None or target_port is None or original_port != target_port: - return False - - # Require the same host or a subdomain of the original host. - return target_host == original_host or target_host.endswith("." + original_host) - - -class RedirectCachingPolicy(HTTPPolicy): - """Synchronous redirect policy with write-URL caching. - - Replaces the default :class:`~azure.core.pipeline.policies.RedirectPolicy` - in the pipeline. See module docstring for caching semantics. - - :keyword bool permit_redirects: Whether redirects are followed at all. - Defaults to ``True``. - :keyword int redirect_max: Maximum number of redirects to follow per - request. Defaults to ``5``. - """ - - def __init__( - self, - *, - permit_redirects: bool = True, - redirect_max: int = 5, - **kwargs, # pylint: disable=unused-argument - ) -> None: - super().__init__() - self._permit_redirects = permit_redirects - self._max_redirects = redirect_max - self._cache = _RedirectUrlCache() - - def send(self, request: PipelineRequest) -> PipelineResponse: - method = request.http_request.method.upper() - is_write = method in _WRITE_METHODS - - # Capture the pristine request host (before any cache rewrite) so that - # redirect targets are validated against the original ledger endpoint. - original_url = request.http_request.url - - # For writes, rewrite the URL to the cached primary (if warm). - if is_write: - cached = self._cache.get() - if cached: - request.http_request.url = _rewrite_url( - request.http_request.url, cached - ) - _LOGGER.debug( - "Using cached redirect URL for %s: %s", - method, - request.http_request.url, - ) - - # Send the request downstream. - try: - response = self.next.send(request) - except Exception: - if is_write: - self._cache.invalidate() - _LOGGER.debug("Transport error on write; invalidated redirect cache") - raise - - if not self._permit_redirects: - return response - - # Follow redirect chain. - redirects_remaining = self._max_redirects - while ( - _is_redirect(response.http_response.status_code) - and redirects_remaining > 0 - ): - redirect_url = response.http_response.headers.get("Location") - if not redirect_url: - break - - # Enforce the redirect destination policy: only the original host or - # one of its subdomains may be followed. - if not _is_allowed_redirect_target(original_url, redirect_url): - _LOGGER.warning( - "Refusing to follow redirect to disallowed target: %s", - redirect_url, - ) - break - - # Only cache for write methods. - if is_write: - self._cache.set(redirect_url) - _LOGGER.debug("Cached redirect target for writes: %s", redirect_url) - - request.http_request.url = redirect_url - redirects_remaining -= 1 - - try: - response = self.next.send(request) - except Exception: - if is_write: - self._cache.invalidate() - _LOGGER.debug( - "Transport error following redirect; invalidated cache" - ) - raise - - # Invalidate cache on server errors for writes. - if is_write and response.http_response.status_code >= 500: - self._cache.invalidate() - _LOGGER.debug("5xx on write; invalidated redirect cache") - - return response - - -class AsyncRedirectCachingPolicy(AsyncHTTPPolicy): - """Asynchronous redirect policy with write-URL caching. - - Async counterpart of :class:`RedirectCachingPolicy`. - """ - - def __init__( - self, - *, - permit_redirects: bool = True, - redirect_max: int = 5, - **kwargs, # pylint: disable=unused-argument - ) -> None: - super().__init__() - self._permit_redirects = permit_redirects - self._max_redirects = redirect_max - self._cache = _RedirectUrlCache() - - async def send(self, request: PipelineRequest) -> PipelineResponse: - method = request.http_request.method.upper() - is_write = method in _WRITE_METHODS - - # Capture the pristine request host (before any cache rewrite) so that - # redirect targets are validated against the original ledger endpoint. - original_url = request.http_request.url - - if is_write: - cached = self._cache.get() - if cached: - request.http_request.url = _rewrite_url( - request.http_request.url, cached - ) - _LOGGER.debug( - "Using cached redirect URL for %s: %s", - method, - request.http_request.url, - ) - - try: - response = await self.next.send(request) - except Exception: - if is_write: - self._cache.invalidate() - _LOGGER.debug("Transport error on write; invalidated redirect cache") - raise - - if not self._permit_redirects: - return response - - redirects_remaining = self._max_redirects - while ( - _is_redirect(response.http_response.status_code) - and redirects_remaining > 0 - ): - redirect_url = response.http_response.headers.get("Location") - if not redirect_url: - break - - # Enforce the redirect destination policy: only the original host or - # one of its subdomains may be followed. - if not _is_allowed_redirect_target(original_url, redirect_url): - _LOGGER.warning( - "Refusing to follow redirect to disallowed target: %s", - redirect_url, - ) - break - - if is_write: - self._cache.set(redirect_url) - _LOGGER.debug("Cached redirect target for writes: %s", redirect_url) - - request.http_request.url = redirect_url - redirects_remaining -= 1 - - try: - response = await self.next.send(request) - except Exception: - if is_write: - self._cache.invalidate() - _LOGGER.debug( - "Transport error following redirect; invalidated cache" - ) - raise - - if is_write and response.http_response.status_code >= 500: - self._cache.invalidate() - _LOGGER.debug("5xx on write; invalidated redirect cache") - - return response diff --git a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_utils/model_base.py b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_utils/model_base.py index 3cd9cb9ceabf..bd5b9caf1022 100644 --- a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_utils/model_base.py +++ b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_utils/model_base.py @@ -23,20 +23,26 @@ from json import JSONEncoder import xml.etree.ElementTree as ET from collections.abc import MutableMapping -from typing_extensions import Self import isodate from azure.core.exceptions import DeserializationError from azure.core import CaseInsensitiveEnumMeta from azure.core.pipeline import PipelineResponse from azure.core.serialization import _Null + from azure.core.rest import HttpResponse +if sys.version_info >= (3, 11): + from typing import Self +else: + from typing_extensions import Self + _LOGGER = logging.getLogger(__name__) __all__ = ["SdkJSONEncoder", "Model", "rest_field", "rest_discriminator"] TZ_UTC = timezone.utc _T = typing.TypeVar("_T") +_NONE_TYPE = type(None) def _timedelta_as_isostr(td: timedelta) -> str: @@ -171,6 +177,21 @@ def default(self, o): # pylint: disable=too-many-return-statements r"(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s\d{4}\s\d{2}:\d{2}:\d{2}\sGMT" ) +_ARRAY_ENCODE_MAPPING = { + "pipeDelimited": "|", + "spaceDelimited": " ", + "commaDelimited": ",", + "newlineDelimited": "\n", +} + + +def _deserialize_array_encoded(delimit: str, attr): + if isinstance(attr, str): + if attr == "": + return [] + return attr.split(delimit) + return attr + def _deserialize_datetime(attr: typing.Union[str, datetime]) -> datetime: """Deserialize ISO-8601 formatted string into Datetime object. @@ -202,7 +223,7 @@ def _deserialize_datetime(attr: typing.Union[str, datetime]) -> datetime: test_utc = date_obj.utctimetuple() if test_utc.tm_year > 9999 or test_utc.tm_year < 1: raise OverflowError("Hit max or min date") - return date_obj + return date_obj # type: ignore[no-any-return] def _deserialize_datetime_rfc7231(attr: typing.Union[str, datetime]) -> datetime: @@ -256,7 +277,7 @@ def _deserialize_time(attr: typing.Union[str, time]) -> time: """ if isinstance(attr, time): return attr - return isodate.parse_time(attr) + return isodate.parse_time(attr) # type: ignore[no-any-return] def _deserialize_bytes(attr): @@ -315,6 +336,8 @@ def _deserialize_int_as_str(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 annotation is str and rf and rf._format in _ARRAY_ENCODE_MAPPING: + return functools.partial(_deserialize_array_encoded, _ARRAY_ENCODE_MAPPING[rf._format]) if rf and rf._format: return _DESERIALIZE_MAPPING_WITHFORMAT.get(rf._format) return _DESERIALIZE_MAPPING.get(annotation) # pyright: ignore @@ -353,9 +376,39 @@ def __contains__(self, key: typing.Any) -> bool: return key in self._data def __getitem__(self, key: str) -> typing.Any: + # If this key has been deserialized (for mutable types), we need to handle serialization + if hasattr(self, "_attr_to_rest_field"): + cache_attr = f"_deserialized_{key}" + if hasattr(self, cache_attr): + rf = _get_rest_field(getattr(self, "_attr_to_rest_field"), key) + if rf: + value = self._data.get(key) + if isinstance(value, (dict, list, set)): + # For mutable types, serialize and return + # But also update _data with serialized form and clear flag + # so mutations via this returned value affect _data + serialized = _serialize(value, rf._format) + # If serialized form is same type (no transformation needed), + # return _data directly so mutations work + if isinstance(serialized, type(value)) and serialized == value: + return self._data.get(key) + # Otherwise return serialized copy and clear flag + try: + object.__delattr__(self, cache_attr) + except AttributeError: + pass + # Store serialized form back + self._data[key] = serialized + return serialized return self._data.__getitem__(key) def __setitem__(self, key: str, value: typing.Any) -> None: + # Clear any cached deserialized value when setting through dictionary access + cache_attr = f"_deserialized_{key}" + try: + object.__delattr__(self, cache_attr) + except AttributeError: + pass self._data.__setitem__(key, value) def __delitem__(self, key: str) -> None: @@ -467,6 +520,8 @@ def setdefault(self, key: str, default: typing.Any = _UNSET) -> typing.Any: return self._data.setdefault(key, default) def __eq__(self, other: typing.Any) -> bool: + if isinstance(other, _MyMutableMapping): + return self._data == other._data try: other_model = self.__class__(other) except Exception: @@ -483,6 +538,8 @@ def _is_model(obj: typing.Any) -> bool: def _serialize(o, format: typing.Optional[str] = None): # pylint: disable=too-many-return-statements if isinstance(o, list): + if format in _ARRAY_ENCODE_MAPPING and all(isinstance(x, str) for x in o): + return _ARRAY_ENCODE_MAPPING[format].join(o) return [_serialize(x, format) for x in o] if isinstance(o, dict): return {k: _serialize(v, format) for k, v in o.items()} @@ -533,6 +590,239 @@ def _create_value(rf: typing.Optional["_RestField"], value: typing.Any) -> typin return _serialize(value, rf._format) +# ============================================================================ +# Fast-path scalar deserializer functions for rest_field(deserializer=...) +# These are referenced from rest_field declarations to bypass the generic +# _deserialize -> _deserialize_with_callable chain. +# Only simple/primitive types — no models or container types. +# ============================================================================ + + +def _xml_deser_str(value): + if isinstance(value, ET.Element): + return value.text or "" + return str(value) if value is not None else None + + +def _xml_deser_int(value): + if isinstance(value, ET.Element): + return int(value.text) if value.text else None + return int(value) if value is not None else None + + +def _xml_deser_float(value): + if isinstance(value, ET.Element): + return float(value.text) if value.text else None + return float(value) if value is not None else None + + +def _xml_deser_bool(value): + if isinstance(value, ET.Element): + text = value.text + else: + text = value + if text is None: + return None + if text in (True, False): + return text + return text.lower() == "true" + + +# pylint: disable=docstring-missing-param +def _xml_deser_bytes(value): + """Deserialize bytes from XML (base64).""" + if isinstance(value, ET.Element): + text = value.text + else: + text = value + if text is None: + return None + return _deserialize_bytes(text) + + +def _xml_deser_bytes_base64url(value): + """Deserialize bytes from XML (base64url).""" + if isinstance(value, ET.Element): + text = value.text + else: + text = value + if text is None: + return None + return _deserialize_bytes_base64(text) + + +def _xml_deser_datetime(value): + """Deserialize a datetime from XML (ISO 8601 / rfc3339).""" + if isinstance(value, ET.Element): + text = value.text + else: + text = value + if text is None: + return None + return _deserialize_datetime(text) + + +def _xml_deser_datetime_rfc7231(value): + """Deserialize a datetime from XML (RFC7231 format).""" + if isinstance(value, ET.Element): + text = value.text + else: + text = value + if text is None: + return None + return _deserialize_datetime_rfc7231(text) + + +def _xml_deser_datetime_unix_timestamp(value): + """Deserialize a datetime from XML (Unix timestamp).""" + if isinstance(value, ET.Element): + text = value.text + else: + text = value + if text is None: + return None + return _deserialize_datetime_unix_timestamp(float(text)) + + +def _xml_deser_date(value): + """Deserialize a date from XML (ISO 8601).""" + if isinstance(value, ET.Element): + text = value.text + else: + text = value + if text is None: + return None + return _deserialize_date(text) + + +def _xml_deser_time(value): + """Deserialize a time from XML (ISO 8601).""" + if isinstance(value, ET.Element): + text = value.text + else: + text = value + if text is None: + return None + return _deserialize_time(text) + + +def _xml_deser_duration(value): + """Deserialize a timedelta from XML (ISO 8601 duration).""" + if isinstance(value, ET.Element): + text = value.text + else: + text = value + if text is None: + return None + return _deserialize_duration(text) + + +def _xml_deser_decimal(value): + """Deserialize a Decimal from XML.""" + if isinstance(value, ET.Element): + text = value.text + else: + text = value + if text is None: + return None + return _deserialize_decimal(text) + + +def _xml_deser_enum_or_str(enum_cls, value): + """Deserialize a Union[EnumType, str] from XML.""" + text = value.text if isinstance(value, ET.Element) else value + if text is None: + return None + try: + return enum_cls(text) + except ValueError: + return text + + +def _extract_xml_model_type(rf_type): + """Extract the concrete Model class from a resolved rf._type partial chain. + + Unwraps ``Optional[Model]`` and ``_deserialize_model(Model, ...)`` + wrappers. Only handles Model and Optional[Model] — other composite + types (List, Dict, Union, etc.) return None and fall through to the + generic ``_deserialize`` path at runtime. + """ + if rf_type is None: + return None + if isinstance(rf_type, type) and _is_model(rf_type): + return rf_type + if not isinstance(rf_type, functools.partial): + return None + func = rf_type.func + args = rf_type.args + if func is _deserialize_with_optional and args: + return _extract_xml_model_type(args[0]) + if func is _deserialize_model and args: + cls = args[0] + return cls if isinstance(cls, type) and _is_model(cls) else None + return None + + +def _build_xml_field_plan( # pylint: disable=docstring-missing-return, docstring-missing-rtype, unused-variable + cls, attr_to_rest_field: dict +) -> list: + """Build a precomputed XML field plan for fast _init_from_xml iteration. + + Called once per model class in __new__. Returns a list of tuples: + (rest_name, xml_name, kind, deser, rf_type, is_optional, items_name) + + kind: 0=wrapped, 1=attribute, 2=unwrapped, 3=text + + For Model and Optional[Model] fields that lack a scalar + ``_deserializer``, this function precomputes the Model class as the + deserializer so ``_init_from_xml`` can call ``ModelClass(element)`` + directly instead of going through the expensive + ``_get_deserialize_callable_from_annotation`` chain at runtime. + """ + model_meta = getattr(cls, "_xml", {}) + model_ns = model_meta.get("ns") or model_meta.get("namespace") + plan = [] + + for rf in attr_to_rest_field.values(): + prop_meta = getattr(rf, "_xml", {}) + deser = rf._deserializer + + xml_name = prop_meta.get("name", rf._rest_name) + xml_ns = _resolve_xml_ns(prop_meta, model_meta) + if xml_ns: + xml_name = "{" + xml_ns + "}" + xml_name + + is_optional = rf._is_optional + + # For Model / Optional[Model] fields without a scalar deserializer, + # precompute the Model class as the deserializer. + if deser is None and rf._type is not None: + model_cls = _extract_xml_model_type(rf._type) + if model_cls is not None: + deser = model_cls + + if prop_meta.get("attribute", False): + plan.append((rf._rest_name, xml_name, 1, deser, rf._type, is_optional, None)) + elif prop_meta.get("unwrapped", False): + items_name = prop_meta.get("itemsName") + if items_name: + items_ns = prop_meta.get("itemsNs") + if items_ns is not None: + xml_ns = items_ns + if xml_ns: + items_name = "{" + xml_ns + "}" + items_name + else: + items_name = xml_name + plan.append((rf._rest_name, xml_name, 2, deser, rf._type, is_optional, items_name)) + elif prop_meta.get("text", False): + plan.append((rf._rest_name, xml_name, 3, deser, rf._type, is_optional, None)) + else: + plan.append((rf._rest_name, xml_name, 0, deser, rf._type, is_optional, None)) + + return plan + + +# pylint: enable=docstring-missing-param class Model(_MyMutableMapping): _is_model = True # label whether current class's _attr_to_rest_field has been calculated @@ -543,59 +833,10 @@ def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None: class_name = self.__class__.__name__ if len(args) > 1: raise TypeError(f"{class_name}.__init__() takes 2 positional arguments but {len(args) + 1} were given") - dict_to_pass = { - rest_field._rest_name: rest_field._default - for rest_field in self._attr_to_rest_field.values() - if rest_field._default is not _UNSET - } - if args: # pylint: disable=too-many-nested-blocks + dict_to_pass: dict[str, typing.Any] = {} + if args: if isinstance(args[0], ET.Element): - existed_attr_keys = [] - model_meta = getattr(self, "_xml", {}) - - for rf in self._attr_to_rest_field.values(): - prop_meta = getattr(rf, "_xml", {}) - xml_name = prop_meta.get("name", rf._rest_name) - xml_ns = prop_meta.get("ns", model_meta.get("ns", None)) - if xml_ns: - xml_name = "{" + xml_ns + "}" + xml_name - - # attribute - if prop_meta.get("attribute", False) and args[0].get(xml_name) is not None: - existed_attr_keys.append(xml_name) - dict_to_pass[rf._rest_name] = _deserialize(rf._type, args[0].get(xml_name)) - continue - - # unwrapped element is array - if prop_meta.get("unwrapped", False): - # unwrapped array could either use prop items meta/prop meta - if prop_meta.get("itemsName"): - xml_name = prop_meta.get("itemsName") - xml_ns = prop_meta.get("itemNs") - if xml_ns: - xml_name = "{" + xml_ns + "}" + xml_name - items = args[0].findall(xml_name) # pyright: ignore - if len(items) > 0: - existed_attr_keys.append(xml_name) - dict_to_pass[rf._rest_name] = _deserialize(rf._type, items) - continue - - # text element is primitive type - if prop_meta.get("text", False): - if args[0].text is not None: - dict_to_pass[rf._rest_name] = _deserialize(rf._type, args[0].text) - continue - - # wrapped element could be normal property or array, it should only have one element - item = args[0].find(xml_name) - if item is not None: - existed_attr_keys.append(xml_name) - dict_to_pass[rf._rest_name] = _deserialize(rf._type, item) - - # rest thing is additional properties - for e in args[0]: - if e.tag not in existed_attr_keys: - dict_to_pass[e.tag] = _convert_element(e) + dict_to_pass.update(self._init_from_xml(args[0])) else: dict_to_pass.update( {k: _create_value(_get_rest_field(self._attr_to_rest_field, k), v) for k, v in args[0].items()} @@ -612,8 +853,117 @@ def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None: if v is not None } ) + # Apply client default values for fields the caller didn't set so that + # defaults are part of `_data` and therefore included during serialization. + for rf in self._attr_to_rest_field.values(): + if rf._default is _UNSET: + continue + if rf._rest_name in dict_to_pass: + continue + dict_to_pass[rf._rest_name] = _create_value(rf, rf._default) super().__init__(dict_to_pass) + def _init_from_xml( # pylint: disable=too-many-branches, too-many-statements + self, element: ET.Element + ) -> dict[str, typing.Any]: + """Deserialize an XML element into a dict mapping rest field names to values. + + :param ET.Element element: The XML element to deserialize from. + :returns: A dictionary of rest_name to deserialized value pairs. + :rtype: dict + """ + result: dict[str, typing.Any] = {} + existed_attr_keys: list[str] = [] + + field_plan = getattr(self, "_xml_field_plan", None) + if field_plan: + for rest_name, xml_name, kind, deser, rf_type, is_optional, items_name in field_plan: + if kind == 0: # wrapped element (most common) + item = element.find(xml_name) + if item is not None: + existed_attr_keys.append(xml_name) + if deser: + result[rest_name] = deser(item) + else: + result[rest_name] = _deserialize(rf_type, item) + elif kind == 1: # attribute + attr_val = element.get(xml_name) + if attr_val is not None: + existed_attr_keys.append(xml_name) + if deser: + result[rest_name] = deser(attr_val) + else: + result[rest_name] = attr_val + elif kind == 2: # unwrapped array + items = element.findall(items_name) # pyright: ignore + if len(items) > 0: + existed_attr_keys.append(items_name) + if deser: + result[rest_name] = deser(items) + else: + result[rest_name] = _deserialize(rf_type, items) + elif not is_optional: + existed_attr_keys.append(items_name) + result[rest_name] = [] + elif kind == 3: # text + if element.text is not None: + if deser: + result[rest_name] = deser(element.text) + else: + result[rest_name] = element.text + else: + model_meta = getattr(self, "_xml", {}) + for rf in self._attr_to_rest_field.values(): + prop_meta = getattr(rf, "_xml", {}) + xml_name = prop_meta.get("name", rf._rest_name) + xml_ns = _resolve_xml_ns(prop_meta, model_meta) + if xml_ns: + xml_name = "{" + xml_ns + "}" + xml_name + + # attribute + if prop_meta.get("attribute", False) and element.get(xml_name) is not None: + existed_attr_keys.append(xml_name) + result[rf._rest_name] = _deserialize(rf._type, element.get(xml_name)) + continue + + # unwrapped element is array + if prop_meta.get("unwrapped", False): + _items_name = prop_meta.get("itemsName") + if _items_name: + xml_name = _items_name + _items_ns = prop_meta.get("itemsNs") + if _items_ns is not None: + xml_ns = _items_ns + if xml_ns: + xml_name = "{" + xml_ns + "}" + xml_name + items = element.findall(xml_name) # pyright: ignore + if len(items) > 0: + existed_attr_keys.append(xml_name) + result[rf._rest_name] = _deserialize(rf._type, items) + elif not rf._is_optional: + existed_attr_keys.append(xml_name) + result[rf._rest_name] = [] + continue + + # text element is primitive type + if prop_meta.get("text", False): + if element.text is not None: + result[rf._rest_name] = _deserialize(rf._type, element.text) + continue + + # wrapped element could be normal property or array + item = element.find(xml_name) + if item is not None: + existed_attr_keys.append(xml_name) + result[rf._rest_name] = _deserialize(rf._type, item) + + # rest thing is additional properties + for e in element: + if e.tag not in existed_attr_keys: + result[e.tag] = _convert_element(e) + + return result + def copy(self) -> "Model": return Model(self.__dict__) @@ -638,6 +988,13 @@ def __new__(cls, *args: typing.Any, **kwargs: typing.Any) -> Self: if not rf._rest_name_input: rf._rest_name_input = attr cls._attr_to_rest_field: dict[str, _RestField] = dict(attr_to_rest_field.items()) + cls._backcompat_attr_to_rest_field: dict[str, _RestField] = { + Model._get_backcompat_attribute_name(cls._attr_to_rest_field, attr): rf + for attr, rf in cls._attr_to_rest_field.items() + } + # Build XML field plan for fast _init_from_xml (only for XML models) + if getattr(cls, "_xml", None): + cls._xml_field_plan = _build_xml_field_plan(cls, attr_to_rest_field) cls._calculated.add(f"{cls.__module__}.{cls.__qualname__}") return super().__new__(cls) @@ -647,6 +1004,16 @@ def __init_subclass__(cls, discriminator: typing.Optional[str] = None) -> None: if hasattr(base, "__mapping__"): base.__mapping__[discriminator or cls.__name__] = cls # type: ignore + @classmethod + def _get_backcompat_attribute_name(cls, attr_to_rest_field: dict[str, "_RestField"], attr_name: str) -> str: + rest_field_obj = attr_to_rest_field.get(attr_name) # pylint: disable=protected-access + if rest_field_obj is None: + return attr_name + original_tsp_name = getattr(rest_field_obj, "_original_tsp_name", None) # pylint: disable=protected-access + if original_tsp_name: + return original_tsp_name + return attr_name + @classmethod def _get_discriminator(cls, exist_discriminators) -> typing.Optional["_RestField"]: for v in cls.__dict__.values(): @@ -666,7 +1033,7 @@ def _deserialize(cls, data, exist_discriminators): model_meta = getattr(cls, "_xml", {}) prop_meta = getattr(discriminator, "_xml", {}) xml_name = prop_meta.get("name", discriminator._rest_name) - xml_ns = prop_meta.get("ns", model_meta.get("ns", None)) + xml_ns = _resolve_xml_ns(prop_meta, model_meta) if xml_ns: xml_name = "{" + xml_ns + "}" + xml_name @@ -758,6 +1125,14 @@ def _deserialize_multiple_sequence( return type(obj)(_deserialize(deserializer, entry, module) for entry, deserializer in zip(obj, entry_deserializers)) +def _is_array_encoded_deserializer(deserializer: functools.partial) -> bool: + return ( + isinstance(deserializer, functools.partial) + and isinstance(deserializer.args[0], functools.partial) + and deserializer.args[0].func == _deserialize_array_encoded # pylint: disable=comparison-with-callable + ) + + def _deserialize_sequence( deserializer: typing.Optional[typing.Callable], module: typing.Optional[str], @@ -767,6 +1142,19 @@ def _deserialize_sequence( return obj if isinstance(obj, ET.Element): obj = list(obj) + + # encoded string may be deserialized to sequence + if isinstance(obj, str) and isinstance(deserializer, functools.partial): + # for list[str] + if _is_array_encoded_deserializer(deserializer): + return deserializer(obj) + + # for list[Union[...]] + if isinstance(deserializer.args[0], list): + for sub_deserializer in deserializer.args[0]: + if _is_array_encoded_deserializer(sub_deserializer): + return sub_deserializer(obj) + return type(obj)(_deserialize(deserializer, entry, module) for entry in obj) @@ -817,16 +1205,18 @@ def _get_deserialize_callable_from_annotation( # pylint: disable=too-many-retur # is it optional? try: - if any(a for a in annotation.__args__ if a == type(None)): # pyright: ignore # pylint: disable=unidiomatic-typecheck + if any(a is _NONE_TYPE for a in annotation.__args__): # pyright: ignore + if rf: + rf._is_optional = True if len(annotation.__args__) <= 2: # pyright: ignore if_obj_deserializer = _get_deserialize_callable_from_annotation( - next(a for a in annotation.__args__ if a != type(None)), module, rf # pyright: ignore # pylint: disable=unidiomatic-typecheck + next(a for a in annotation.__args__ if a is not _NONE_TYPE), 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 # pylint: disable=unidiomatic-typecheck + annotation_copy.__args__ = [a for a in annotation_copy.__args__ if a is not _NONE_TYPE] # pyright: ignore return _get_deserialize_callable_from_annotation(annotation_copy, module, rf) except AttributeError: pass @@ -910,16 +1300,20 @@ def _deserialize_with_callable( return float(value.text) if value.text else None if deserializer is bool: return value.text == "true" if value.text else None + if deserializer and deserializer in _DESERIALIZE_MAPPING.values(): + return deserializer(value.text) if value.text else None + if deserializer and deserializer in _DESERIALIZE_MAPPING_WITHFORMAT.values(): + return deserializer(value.text) if value.text else None if deserializer is None: return value if deserializer in [int, float, bool]: return deserializer(value) if isinstance(deserializer, CaseInsensitiveEnumMeta): try: - return deserializer(value) + return deserializer(value.text if isinstance(value, ET.Element) else value) except ValueError: # for unknown value, return raw value - return value + return value.text if isinstance(value, ET.Element) else value if isinstance(deserializer, type) and issubclass(deserializer, Model): return deserializer._deserialize(value, []) return typing.cast(typing.Callable[[typing.Any], typing.Any], deserializer)(value) @@ -952,7 +1346,7 @@ def _failsafe_deserialize( ) -> typing.Any: try: return _deserialize(deserializer, response.json(), module, rf, format) - except DeserializationError: + except Exception: # pylint: disable=broad-except _LOGGER.warning( "Ran into a deserialization error. Ignoring since this is failsafe deserialization", exc_info=True ) @@ -965,13 +1359,14 @@ def _failsafe_deserialize_xml( ) -> typing.Any: try: return _deserialize_xml(deserializer, response.text()) - except DeserializationError: + except Exception: # pylint: disable=broad-except _LOGGER.warning( "Ran into a deserialization error. Ignoring since this is failsafe deserialization", exc_info=True ) return None +# pylint: disable=too-many-instance-attributes class _RestField: def __init__( self, @@ -984,6 +1379,8 @@ def __init__( format: typing.Optional[str] = None, is_multipart_file_input: bool = False, xml: typing.Optional[dict[str, typing.Any]] = None, + deserializer: typing.Optional[typing.Callable] = None, + original_tsp_name: typing.Optional[str] = None, ): self._type = type self._rest_name_input = name @@ -991,14 +1388,21 @@ def __init__( self._is_discriminator = is_discriminator self._visibility = visibility self._is_model = False + self._is_optional = False self._default = default self._format = format self._is_multipart_file_input = is_multipart_file_input self._xml = xml if xml is not None else {} + self._deserializer = deserializer + self._original_tsp_name = original_tsp_name @property def _class_type(self) -> typing.Any: - return getattr(self._type, "args", [None])[0] + result = getattr(self._type, "args", [None])[0] + # type may be wrapped by nested functools.partial so we need to check for that + if isinstance(result, functools.partial): + return getattr(result, "args", [None])[0] + return result @property def _rest_name(self) -> str: @@ -1009,14 +1413,44 @@ def _rest_name(self) -> str: def __get__(self, obj: Model, type=None): # pylint: disable=redefined-builtin # by this point, type and rest_name will have a value bc we default # them in __new__ of the Model class - item = obj.get(self._rest_name) + # Use _data.get() directly to avoid triggering __getitem__ which clears the cache + item = obj._data.get(self._rest_name, _UNSET) + if item is _UNSET: + # Field not set by user; return the client default if one exists, otherwise None + return self._default if self._default is not _UNSET else None if item is None: return item if self._is_model: return item - return _deserialize(self._type, _serialize(item, self._format), rf=self) + + # For mutable types, we want mutations to directly affect _data + # Check if we've already deserialized this value + cache_attr = f"_deserialized_{self._rest_name}" + if hasattr(obj, cache_attr): + # Return the value from _data directly (it's been deserialized in place) + return obj._data.get(self._rest_name) + + # Fast path: use _deserializer directly (avoids _serialize/_deserialize chain) + if self._deserializer: + deserialized = self._deserializer(item) + else: + deserialized = _deserialize(self._type, _serialize(item, self._format), rf=self) + + # For mutable types, store the deserialized value back in _data + # so mutations directly affect _data + if isinstance(deserialized, (dict, list, set)): + obj._data[self._rest_name] = deserialized + object.__setattr__(obj, cache_attr, True) # Mark as deserialized + return deserialized + + return deserialized def __set__(self, obj: Model, value) -> None: + # Clear the cached deserialized object when setting a new value + cache_attr = f"_deserialized_{self._rest_name}" + if hasattr(obj, cache_attr): + object.__delattr__(obj, cache_attr) + if value is None: # we want to wipe out entries if users set attr to None try: @@ -1046,6 +1480,8 @@ def rest_field( format: typing.Optional[str] = None, is_multipart_file_input: bool = False, xml: typing.Optional[dict[str, typing.Any]] = None, + deserializer: typing.Optional[typing.Callable] = None, + original_tsp_name: typing.Optional[str] = None, ) -> typing.Any: return _RestField( name=name, @@ -1055,6 +1491,8 @@ def rest_field( format=format, is_multipart_file_input=is_multipart_file_input, xml=xml, + deserializer=deserializer, + original_tsp_name=original_tsp_name, ) @@ -1079,6 +1517,56 @@ def serialize_xml(model: Model, exclude_readonly: bool = False) -> str: return ET.tostring(_get_element(model, exclude_readonly), encoding="unicode") # type: ignore +def _get_xml_ns(meta: dict[str, typing.Any]) -> typing.Optional[str]: + """Return the XML namespace from a metadata dict, checking both 'ns' (old-style) and 'namespace' (DPG) keys. + + :param dict meta: The metadata dictionary to extract namespace from. + :returns: The namespace string if 'ns' or 'namespace' key is present, None otherwise. + :rtype: str or None + """ + ns = meta.get("ns") + if ns is None: + ns = meta.get("namespace") + return ns + + +def _resolve_xml_ns( + prop_meta: dict[str, typing.Any], model_meta: typing.Optional[dict[str, typing.Any]] = None +) -> typing.Optional[str]: + """Resolve XML namespace for a property, falling back to model namespace when appropriate. + + Checks the property metadata first; if no namespace is found and the model does not declare + an explicit prefix, falls back to the model-level namespace. + + :param dict prop_meta: The property metadata dictionary. + :param dict model_meta: The model metadata dictionary, used as fallback. + :returns: The resolved namespace string, or None. + :rtype: str or None + """ + ns = _get_xml_ns(prop_meta) + if ns is None and model_meta is not None and not model_meta.get("prefix"): + ns = _get_xml_ns(model_meta) + return ns + + +def _set_xml_attribute(element: ET.Element, name: str, value: typing.Any, prop_meta: dict[str, typing.Any]) -> None: + """Set an XML attribute on an element, handling namespace prefix registration. + + :param ET.Element element: The element to set the attribute on. + :param str name: The default attribute name (wire name). + :param any value: The attribute value. + :param dict prop_meta: The property metadata dictionary. + """ + xml_name = prop_meta.get("name", name) + _attr_ns = _get_xml_ns(prop_meta) + if _attr_ns: + _attr_prefix = prop_meta.get("prefix") + if _attr_prefix: + _safe_register_namespace(_attr_prefix, _attr_ns) + xml_name = "{" + _attr_ns + "}" + xml_name + element.set(xml_name, _get_primitive_type_value(value)) + + def _get_element( o: typing.Any, exclude_readonly: bool = False, @@ -1090,10 +1578,16 @@ def _get_element( # if prop is a model, then use the prop element directly, else generate a wrapper of model if wrapped_element is None: + # When serializing as an array item (parent_meta is set), check if the parent has an + # explicit itemsName. This ensures correct element names for unwrapped arrays (where + # the element tag is the property/items name, not the model type name). + _items_name = parent_meta.get("itemsName") if parent_meta is not None else None + element_name = _items_name if _items_name else (model_meta.get("name") or o.__class__.__name__) + _model_ns = _get_xml_ns(model_meta) wrapped_element = _create_xml_element( - model_meta.get("name", o.__class__.__name__), + element_name, model_meta.get("prefix"), - model_meta.get("ns"), + _model_ns, ) readonly_props = [] @@ -1115,7 +1609,9 @@ def _get_element( # additional properties will not have rest field, use the wire name as xml name prop_meta = {"name": k} - # if no ns for prop, use model's + # Propagate model namespace to properties only for old-style "ns"-keyed models. + # DPG-generated models use the "namespace" key and explicitly declare namespace on + # each property that needs it, so propagation is intentionally skipped for them. if prop_meta.get("ns") is None and model_meta.get("ns"): prop_meta["ns"] = model_meta.get("ns") prop_meta["prefix"] = model_meta.get("prefix") @@ -1127,12 +1623,7 @@ def _get_element( # text could only set on primitive type wrapped_element.text = _get_primitive_type_value(v) elif prop_meta.get("attribute", False): - xml_name = prop_meta.get("name", k) - if prop_meta.get("ns"): - ET.register_namespace(prop_meta.get("prefix"), prop_meta.get("ns")) # pyright: ignore - xml_name = "{" + prop_meta.get("ns") + "}" + xml_name # pyright: ignore - # attribute should be primitive type - wrapped_element.set(xml_name, _get_primitive_type_value(v)) + _set_xml_attribute(wrapped_element, k, v, prop_meta) else: # other wrapped prop element wrapped_element.append(_get_wrapped_element(v, exclude_readonly, prop_meta)) @@ -1141,6 +1632,7 @@ def _get_element( return [_get_element(x, exclude_readonly, parent_meta) for x in o] # type: ignore if isinstance(o, dict): result = [] + _dict_ns = _get_xml_ns(parent_meta) if parent_meta else None for k, v in o.items(): result.append( _get_wrapped_element( @@ -1148,7 +1640,7 @@ def _get_element( exclude_readonly, { "name": k, - "ns": parent_meta.get("ns") if parent_meta else None, + "ns": _dict_ns, "prefix": parent_meta.get("prefix") if parent_meta else None, }, ) @@ -1157,13 +1649,16 @@ def _get_element( # primitive case need to create element based on parent_meta if parent_meta: + _items_ns = parent_meta.get("itemsNs") + if _items_ns is None: + _items_ns = _get_xml_ns(parent_meta) return _get_wrapped_element( o, exclude_readonly, { "name": parent_meta.get("itemsName", parent_meta.get("name")), "prefix": parent_meta.get("itemsPrefix", parent_meta.get("prefix")), - "ns": parent_meta.get("itemsNs", parent_meta.get("ns")), + "ns": _items_ns, }, ) @@ -1175,8 +1670,9 @@ def _get_wrapped_element( exclude_readonly: bool, meta: typing.Optional[dict[str, typing.Any]], ) -> ET.Element: + _meta_ns = _get_xml_ns(meta) if meta else None wrapped_element = _create_xml_element( - meta.get("name") if meta else None, meta.get("prefix") if meta else None, meta.get("ns") if meta else None + meta.get("name") if meta else None, meta.get("prefix") if meta else None, _meta_ns ) if isinstance(v, (dict, list)): wrapped_element.extend(_get_element(v, exclude_readonly, meta)) @@ -1184,7 +1680,7 @@ def _get_wrapped_element( _get_element(v, exclude_readonly, meta, wrapped_element) else: wrapped_element.text = _get_primitive_type_value(v) - return wrapped_element + return wrapped_element # type: ignore[no-any-return] def _get_primitive_type_value(v) -> str: @@ -1197,9 +1693,29 @@ def _get_primitive_type_value(v) -> str: return str(v) -def _create_xml_element(tag, prefix=None, ns=None): - if prefix and ns: +def _safe_register_namespace(prefix: str, ns: str) -> None: + """Register an XML namespace prefix, handling reserved prefix patterns. + + Some prefixes (e.g. 'ns2') match Python's reserved 'ns\\d+' pattern used for + auto-generated prefixes, causing register_namespace to raise ValueError. + Falls back to directly registering in the internal namespace map. + + :param str prefix: The namespace prefix to register. + :param str ns: The namespace URI. + """ + try: ET.register_namespace(prefix, ns) + except ValueError: + _ns_map = getattr(ET, "_namespace_map", None) + if _ns_map is not None: + _ns_map[ns] = prefix + + +def _create_xml_element( + tag: typing.Any, prefix: typing.Optional[str] = None, ns: typing.Optional[str] = None +) -> ET.Element: + if prefix and ns: + _safe_register_namespace(prefix, ns) if ns: return ET.Element("{" + ns + "}" + tag) return ET.Element(tag) @@ -1210,6 +1726,8 @@ def _deserialize_xml( value: str, ) -> typing.Any: element = ET.fromstring(value) # nosec + if _is_model(deserializer): + return deserializer._deserialize(element, []) return _deserialize(deserializer, element) diff --git a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_utils/serialization.py b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_utils/serialization.py index 45a3e44e45cb..a088671e9c51 100644 --- a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_utils/serialization.py +++ b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_utils/serialization.py @@ -39,11 +39,15 @@ import xml.etree.ElementTree as ET import isodate # type: ignore -from typing_extensions import Self from azure.core.exceptions import DeserializationError, SerializationError from azure.core.serialization import NULL as CoreNull +if sys.version_info >= (3, 11): + from typing import Self +else: + from typing_extensions import Self + _BOM = codecs.BOM_UTF8.decode(encoding="utf-8") JSON = MutableMapping[str, Any] @@ -821,13 +825,20 @@ def serialize_basic(cls, data, data_type, **kwargs): :param str data_type: Type of object in the iterable. :rtype: str, int, float, bool :return: serialized object + :raises TypeError: raise if data_type is not one of str, int, float, bool. """ 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 # pylint: disable=eval-used + if data_type == "int": + return int(data) + if data_type == "float": + return float(data) + if data_type == "bool": + return bool(data) + raise TypeError("Unknown basic data type: {}".format(data_type)) @classmethod def serialize_unicode(cls, data): @@ -1394,7 +1405,7 @@ def __init__(self, classes: Optional[Mapping[str, type]] = None) -> None: # Otherwise, result are unexpected self.additional_properties_detection = True - def __call__(self, target_obj, response_data, content_type=None): + def __call__(self, target_obj, response_data, content_type=None): # pylint: disable=too-many-return-statements """Call the deserializer to process a REST response. :param str target_obj: Target data type to deserialize to. @@ -1404,6 +1415,27 @@ def __call__(self, target_obj, response_data, content_type=None): :return: Deserialized object. :rtype: object """ + # Fast path for header deserialization: response_data is a plain str or None + # and target_obj is a simple scalar type. This avoids the expensive + # _unpack_content → _deserialize → _classify_target → deserialize_data chain. + if response_data is None: + return None + if target_obj == "str" and isinstance(response_data, str): + return response_data + if isinstance(response_data, str): + if target_obj == "int": + return int(response_data) + if target_obj == "bool": + if response_data in ("true", "1", "True"): + return True + if response_data in ("false", "0", "False"): + return False + return bool(response_data) + if target_obj == "rfc-1123": + return Deserializer.deserialize_rfc(response_data) + if target_obj == "bytearray": + return Deserializer.deserialize_bytearray(response_data) + data = self._unpack_content(response_data, content_type) return self._deserialize(target_obj, data) @@ -1757,7 +1789,7 @@ def deserialize_basic(self, attr, data_type): # pylint: disable=too-many-return :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. + :raises TypeError: if string format is not valid or data_type is not one of str, int, float, bool. """ # If we're here, data is supposed to be a basic type. # If it's still an XML node, take the text @@ -1783,7 +1815,11 @@ def deserialize_basic(self, attr, data_type): # pylint: disable=too-many-return if data_type == "str": return self.deserialize_unicode(attr) - return eval(data_type)(attr) # nosec # pylint: disable=eval-used + if data_type == "int": + return int(attr) + if data_type == "float": + return float(attr) + raise TypeError("Unknown basic data type: {}".format(data_type)) @staticmethod def deserialize_unicode(data): diff --git a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_validation.py b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_validation.py new file mode 100644 index 000000000000..f5af3a4eb8a2 --- /dev/null +++ b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_validation.py @@ -0,0 +1,66 @@ +# -------------------------------------------------------------------------- +# 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", "") + api_versions_list = kwargs.pop("api_versions_list", []) + + def _index_with_default(value: str, default: int = -1) -> int: + """Get the index of value in lst, or return default if not found. + + :param value: The value to search for in the api_versions_list. + :type value: str + :param default: The default value to return if the value is not found. + :type default: int + :return: The index of the value in the list, or the default value if not found. + :rtype: int + """ + try: + return api_versions_list.index(value) + except ValueError: + return default + + 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 _index_with_default(method_added_on) > _index_with_default(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 _index_with_default(api_version) > _index_with_default(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/confidentialledger/azure-confidentialledger/azure/confidentialledger/_version.py b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_version.py index 2e7efc3e2e20..8f2350dd3b0c 100644 --- a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_version.py +++ b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/_version.py @@ -6,4 +6,4 @@ # Changes may cause incorrect behavior and will be lost if the code is regenerated. # -------------------------------------------------------------------------- -VERSION = "2.0.0b3" +VERSION = "2.0.0" diff --git a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/aio/_client.py b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/aio/_client.py index 7e465fb3869c..f0e9e03db253 100644 --- a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/aio/_client.py +++ b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/aio/_client.py @@ -7,18 +7,22 @@ # -------------------------------------------------------------------------- from copy import deepcopy +import sys from typing import Any, Awaitable -from typing_extensions import Self from azure.core import AsyncPipelineClient from azure.core.pipeline import policies from azure.core.rest import AsyncHttpResponse, HttpRequest -from .._redirect_caching_policy import AsyncRedirectCachingPolicy from .._utils.serialization import Deserializer, Serializer from ._configuration import ConfidentialLedgerClientConfiguration from ._operations import _ConfidentialLedgerClientOperationsMixin +if sys.version_info >= (3, 11): + from typing import Self +else: + from typing_extensions import Self # type: ignore + class ConfidentialLedgerClient(_ConfidentialLedgerClientOperationsMixin): """Write and retrieve ledger entries against the Confidential Ledger service. @@ -27,9 +31,9 @@ class ConfidentialLedgerClient(_ConfidentialLedgerClientOperationsMixin): `https://contoso.confidentialledger.azure.com `_. Required. :type ledger_endpoint: str - :keyword api_version: The API version to use for this operation. Default value is - "2024-12-09-preview". Note that overriding this default value may result in unsupported - behavior. + :keyword api_version: The API version to use for this operation. Known values are "2026-02-23" + and None. Default value is None. If not set, the operation's default API version will be used. + Note that overriding this default value may result in unsupported behavior. :paramtype api_version: str """ @@ -47,15 +51,13 @@ def __init__( # pylint: disable=missing-client-constructor-parameter-credential self._config.user_agent_policy, self._config.proxy_policy, policies.ContentDecodePolicy(**kwargs), - kwargs.get("redirect_policy") or AsyncRedirectCachingPolicy(**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( - disable_redirect_cleanup=True, **kwargs - ) if self._config.redirect_policy else None, + policies.SensitiveHeaderCleanupPolicy(**kwargs) if self._config.redirect_policy else None, self._config.http_logging_policy, ] self._client: AsyncPipelineClient = AsyncPipelineClient(base_url=_endpoint, policies=_policies, **kwargs) diff --git a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/aio/_configuration.py b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/aio/_configuration.py index 19fde80839b2..ceef61606532 100644 --- a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/aio/_configuration.py +++ b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/aio/_configuration.py @@ -23,14 +23,14 @@ class ConfidentialLedgerClientConfiguration: # pylint: disable=too-many-instanc `https://contoso.confidentialledger.azure.com `_. Required. :type ledger_endpoint: str - :keyword api_version: The API version to use for this operation. Default value is - "2024-12-09-preview". Note that overriding this default value may result in unsupported - behavior. + :keyword api_version: The API version to use for this operation. Known values are "2026-02-23" + and None. Default value is None. If not set, the operation's default API version will be used. + Note that overriding this default value may result in unsupported behavior. :paramtype api_version: str """ def __init__(self, ledger_endpoint: str, **kwargs: Any) -> None: - api_version: str = kwargs.pop("api_version", "2024-12-09-preview") + api_version: str = kwargs.pop("api_version", "2026-02-23") if ledger_endpoint is None: raise ValueError("Parameter 'ledger_endpoint' must not be None.") diff --git a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/aio/_operations/_operations.py b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/aio/_operations/_operations.py index 92a2e38e0703..50f2155730d9 100644 --- a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/aio/_operations/_operations.py +++ b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/aio/_operations/_operations.py @@ -37,10 +37,10 @@ build_confidential_ledger_create_or_update_user_request, build_confidential_ledger_create_user_defined_endpoint_request, build_confidential_ledger_create_user_defined_function_request, - build_confidential_ledger_create_user_defined_role_request, + build_confidential_ledger_create_user_defined_role_stable_request, build_confidential_ledger_delete_ledger_user_request, build_confidential_ledger_delete_user_defined_function_request, - build_confidential_ledger_delete_user_defined_role_request, + build_confidential_ledger_delete_user_defined_role_stable_request, build_confidential_ledger_delete_user_request, build_confidential_ledger_execute_user_defined_function_request, build_confidential_ledger_get_constitution_request, @@ -60,13 +60,15 @@ build_confidential_ledger_list_consortium_members_request, build_confidential_ledger_list_ledger_entries_request, build_confidential_ledger_list_ledger_users_request, + build_confidential_ledger_list_tags_request, build_confidential_ledger_list_user_defined_functions_request, build_confidential_ledger_list_users_request, - build_confidential_ledger_update_runtime_options_request, - build_confidential_ledger_update_user_defined_role_request, + build_confidential_ledger_update_runtime_options_stable_request, + build_confidential_ledger_update_user_defined_role_stable_request, ) from ..._utils.model_base import SdkJSONEncoder, _deserialize, _failsafe_deserialize from ..._utils.utils import ClientMixinABC +from ..._validation import api_version_validation from .._configuration import ConfidentialLedgerClientConfiguration JSON = MutableMapping[str, Any] @@ -82,8 +84,7 @@ class _ConfidentialLedgerClientOperationsMixin( # pylint: disable=too-many-publ async def get_constitution(self, **kwargs: Any) -> _models.Constitution: """Gets the constitution used for governance. - The constitution is a script that assesses and applies proposals from - consortium members. + The constitution is a script that assesses and applies proposals from consortium members. :return: Constitution. The Constitution is compatible with MutableMapping :rtype: ~azure.confidentialledger.models.Constitution @@ -114,6 +115,7 @@ async def get_constitution(self, **kwargs: Any) -> _models.Constitution: } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = await self._client._pipeline.run( # type: ignore # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -128,11 +130,14 @@ async def get_constitution(self, **kwargs: Any) -> _models.Constitution: except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.Constitution, response.json()) @@ -191,7 +196,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + headers=_headers, + params=_next_request_params, ) path_format_arguments = { "ledgerEndpoint": self._serialize.url( @@ -204,7 +212,10 @@ def prepare_request(next_link=None): async def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize(list[_models.ConsortiumMember], deserialized.get("members", [])) + list_of_elem = _deserialize( + list[_models.ConsortiumMember], + deserialized.get("members", []), + ) if cls: list_of_elem = cls(list_of_elem) # type: ignore return deserialized.get("nextLink") or None, AsyncList(list_of_elem) @@ -220,7 +231,10 @@ async def get_next(next_link=None): if response.status_code not in [200]: map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) return pipeline_response @@ -231,8 +245,8 @@ async def get_next(next_link=None): async def get_enclave_quotes(self, **kwargs: Any) -> _models.ConfidentialLedgerEnclaves: """Gets quotes for all nodes of the Confidential Ledger. - A quote is an SGX enclave measurement that can be used to verify the validity - of a node and its enclave. + A quote is an SGX enclave measurement that can be used to verify the validity of a node and its + enclave. :return: ConfidentialLedgerEnclaves. The ConfidentialLedgerEnclaves is compatible with MutableMapping @@ -264,6 +278,7 @@ async def get_enclave_quotes(self, **kwargs: Any) -> _models.ConfidentialLedgerE } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = await self._client._pipeline.run( # type: ignore # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -278,11 +293,14 @@ async def get_enclave_quotes(self, **kwargs: Any) -> _models.ConfidentialLedgerE except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.ConfidentialLedgerEnclaves, response.json()) @@ -340,7 +358,112 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + headers=_headers, + params=_next_request_params, + ) + path_format_arguments = { + "ledgerEndpoint": self._serialize.url( + "self._config.ledger_endpoint", self._config.ledger_endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + return _request + + async def extract_data(pipeline_response): + deserialized = pipeline_response.http_response.json() + list_of_elem = _deserialize( + list[_models.Collection], + deserialized.get("collections", []), + ) + if cls: + list_of_elem = cls(list_of_elem) # type: ignore + return deserialized.get("nextLink") or None, AsyncList(list_of_elem) + + async def get_next(next_link=None): + _request = prepare_request(next_link) + + _stream = False + pipeline_response: PipelineResponse = await self._client._pipeline.run( # type: ignore # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + response = pipeline_response.http_response + + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) + raise HttpResponseError(response=response, model=error) + + return pipeline_response + + return AsyncItemPaged(get_next, extract_data) + + @distributed_trace + @api_version_validation( + method_added_on="2026-02-23", + params_added_on={"2026-02-23": ["api_version", "collection_id", "accept"]}, + api_versions_list=["2026-02-23"], + ) + def list_tags(self, *, collection_id: Optional[str] = None, **kwargs: Any) -> AsyncItemPaged[str]: + """Gets a list of tags for a collection. + + Retrieves the tags associated with a collection. + + :keyword collection_id: The collection id. Default value is None. + :paramtype collection_id: str + :return: An iterator like instance of str + :rtype: ~azure.core.async_paging.AsyncItemPaged[str] + :raises ~azure.core.exceptions.HttpResponseError: + """ + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[list[str]] = kwargs.pop("cls", None) + + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + def prepare_request(next_link=None): + if not next_link: + + _request = build_confidential_ledger_list_tags_request( + collection_id=collection_id, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "ledgerEndpoint": self._serialize.url( + "self._config.ledger_endpoint", self._config.ledger_endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + else: + # make call to next link with the client's api-version + _parsed_next_link = urllib.parse.urlparse(next_link) + _next_request_params = case_insensitive_dict( + { + key: [urllib.parse.quote(v) for v in value] + for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items() + } + ) + _next_request_params["api-version"] = self._config.api_version + _request = HttpRequest( + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + headers=_headers, + params=_next_request_params, ) path_format_arguments = { "ledgerEndpoint": self._serialize.url( @@ -353,7 +476,10 @@ def prepare_request(next_link=None): async def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize(list[_models.Collection], deserialized.get("collections", [])) + list_of_elem = _deserialize( + list[str], + deserialized.get("tags", []), + ) if cls: list_of_elem = cls(list_of_elem) # type: ignore return deserialized.get("nextLink") or None, AsyncList(list_of_elem) @@ -369,7 +495,10 @@ async def get_next(next_link=None): if response.status_code not in [200]: map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) return pipeline_response @@ -388,8 +517,8 @@ def list_ledger_entries( ) -> AsyncItemPaged["_models.LedgerEntry"]: """Gets ledger entries from a collection corresponding to a range. - A collection id may optionally be specified. Only entries in the specified (or - default) collection will be returned. + A collection id may optionally be specified. Only entries in the specified (or default) + collection will be returned. :keyword collection_id: The collection id. Default value is None. :paramtype collection_id: str @@ -447,7 +576,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + headers=_headers, + params=_next_request_params, ) path_format_arguments = { "ledgerEndpoint": self._serialize.url( @@ -460,7 +592,10 @@ def prepare_request(next_link=None): async def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize(list[_models.LedgerEntry], deserialized.get("entries", [])) + list_of_elem = _deserialize( + list[_models.LedgerEntry], + deserialized.get("entries", []), + ) if cls: list_of_elem = cls(list_of_elem) # type: ignore return deserialized.get("nextLink") or None, AsyncList(list_of_elem) @@ -476,7 +611,10 @@ async def get_next(next_link=None): if response.status_code not in [200]: map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) return pipeline_response @@ -628,6 +766,7 @@ async def create_ledger_entry( } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = await self._client._pipeline.run( # type: ignore # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -642,7 +781,10 @@ async def create_ledger_entry( except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) response_headers = {} @@ -651,7 +793,7 @@ async def create_ledger_entry( ) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.LedgerWriteResult, response.json()) @@ -664,14 +806,12 @@ async def create_ledger_entry( async def get_ledger_entry( self, transaction_id: str, *, collection_id: Optional[str] = None, **kwargs: Any ) -> _models.LedgerQueryResult: - """Gets the ledger entry at the specified transaction id. A collection id may - optionally be specified to indicate the collection from which to fetch the - value. + """Gets the ledger entry at the specified transaction id. A collection id may optionally be + specified to indicate the collection from which to fetch the value. - To return older ledger entries, the relevant sections of the ledger must be - read from disk and validated. To prevent blocking within the enclave, the - response will indicate whether the entry is ready and part of the response, or - if the loading is still ongoing. + To return older ledger entries, the relevant sections of the ledger must be read from disk and + validated. To prevent blocking within the enclave, the response will indicate whether the entry + is ready and part of the response, or if the loading is still ongoing. :param transaction_id: Identifies a write transaction. Required. :type transaction_id: str @@ -708,6 +848,7 @@ async def get_ledger_entry( } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = await self._client._pipeline.run( # type: ignore # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -722,11 +863,14 @@ async def get_ledger_entry( except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.LedgerQueryResult, response.json()) @@ -773,6 +917,7 @@ async def get_receipt(self, transaction_id: str, **kwargs: Any) -> _models.Trans } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = await self._client._pipeline.run( # type: ignore # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -787,11 +932,14 @@ async def get_receipt(self, transaction_id: str, **kwargs: Any) -> _models.Trans except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.TransactionReceipt, response.json()) @@ -838,6 +986,7 @@ async def get_transaction_status(self, transaction_id: str, **kwargs: Any) -> _m } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = await self._client._pipeline.run( # type: ignore # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -852,11 +1001,14 @@ async def get_transaction_status(self, transaction_id: str, **kwargs: Any) -> _m except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.TransactionStatus, response.json()) @@ -905,6 +1057,7 @@ async def get_current_ledger_entry( } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = await self._client._pipeline.run( # type: ignore # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -919,11 +1072,14 @@ async def get_current_ledger_entry( except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.LedgerEntry, response.json()) @@ -981,7 +1137,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + headers=_headers, + params=_next_request_params, ) path_format_arguments = { "ledgerEndpoint": self._serialize.url( @@ -994,7 +1153,10 @@ def prepare_request(next_link=None): async def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize(list[_models.LedgerUser], deserialized.get("ledgerUsers", [])) + list_of_elem = _deserialize( + list[_models.LedgerUser], + deserialized.get("ledgerUsers", []), + ) if cls: list_of_elem = cls(list_of_elem) # type: ignore return deserialized.get("nextLink") or None, AsyncList(list_of_elem) @@ -1010,7 +1172,10 @@ async def get_next(next_link=None): if response.status_code not in [200]: map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) return pipeline_response @@ -1067,7 +1232,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + headers=_headers, + params=_next_request_params, ) path_format_arguments = { "ledgerEndpoint": self._serialize.url( @@ -1080,7 +1248,10 @@ def prepare_request(next_link=None): async def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize(list[_models.LedgerUserMultipleRoles], deserialized.get("ledgerUsers", [])) + list_of_elem = _deserialize( + list[_models.LedgerUserMultipleRoles], + deserialized.get("ledgerUsers", []), + ) if cls: list_of_elem = cls(list_of_elem) # type: ignore return deserialized.get("nextLink") or None, AsyncList(list_of_elem) @@ -1096,7 +1267,10 @@ async def get_next(next_link=None): if response.status_code not in [200]: map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) return pipeline_response @@ -1150,7 +1324,10 @@ async def delete_user(self, user_id: str, **kwargs: Any) -> None: if response.status_code not in [204]: map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if cls: @@ -1194,6 +1371,7 @@ async def get_user(self, user_id: str, **kwargs: Any) -> _models.LedgerUser: } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = await self._client._pipeline.run( # type: ignore # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -1208,11 +1386,14 @@ async def get_user(self, user_id: str, **kwargs: Any) -> _models.LedgerUser: except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.LedgerUser, response.json()) @@ -1344,6 +1525,7 @@ async def create_or_update_user( } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = await self._client._pipeline.run( # type: ignore # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -1358,11 +1540,14 @@ async def create_or_update_user( except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.LedgerUser, response.json()) @@ -1418,7 +1603,10 @@ async def delete_ledger_user(self, user_id: str, **kwargs: Any) -> None: if response.status_code not in [204]: map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if cls: @@ -1462,6 +1650,7 @@ async def get_ledger_user(self, user_id: str, **kwargs: Any) -> _models.LedgerUs } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = await self._client._pipeline.run( # type: ignore # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -1476,11 +1665,14 @@ async def get_ledger_user(self, user_id: str, **kwargs: Any) -> _models.LedgerUs except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.LedgerUserMultipleRoles, response.json()) @@ -1621,6 +1813,7 @@ async def create_or_update_ledger_user( } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = await self._client._pipeline.run( # type: ignore # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -1635,11 +1828,14 @@ async def create_or_update_ledger_user( except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.LedgerUserMultipleRoles, response.json()) @@ -1683,6 +1879,7 @@ async def get_user_defined_endpoint(self, **kwargs: Any) -> _models.Bundle: } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = await self._client._pipeline.run( # type: ignore # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -1697,11 +1894,14 @@ async def get_user_defined_endpoint(self, **kwargs: Any) -> _models.Bundle: except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.Bundle, response.json()) @@ -1821,7 +2021,10 @@ async def create_user_defined_endpoint(self, bundle: Union[_models.Bundle, JSON, if response.status_code not in [201]: map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if cls: @@ -1862,6 +2065,7 @@ async def get_runtime_options(self, **kwargs: Any) -> _models.JsRuntimeOptions: } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = await self._client._pipeline.run( # type: ignore # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -1876,11 +2080,14 @@ async def get_runtime_options(self, **kwargs: Any) -> _models.JsRuntimeOptions: except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.JsRuntimeOptions, response.json()) @@ -1890,8 +2097,12 @@ async def get_runtime_options(self, **kwargs: Any) -> _models.JsRuntimeOptions: return deserialized # type: ignore @overload - async def update_runtime_options( - self, js_runtime_options: _models.JsRuntimeOptions, *, content_type: str = "application/json", **kwargs: Any + async def update_runtime_options_stable( + self, + js_runtime_options: _models.JsRuntimeOptions, + *, + content_type: str = "application/merge-patch+json", + **kwargs: Any ) -> _models.JsRuntimeOptions: """Runtime options for user defined endpoints. @@ -1900,7 +2111,7 @@ async def update_runtime_options( :param js_runtime_options: JS Runtime options. Required. :type js_runtime_options: ~azure.confidentialledger.models.JsRuntimeOptions :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". + Default value is "application/merge-patch+json". :paramtype content_type: str :return: JsRuntimeOptions. The JsRuntimeOptions is compatible with MutableMapping :rtype: ~azure.confidentialledger.models.JsRuntimeOptions @@ -1908,8 +2119,8 @@ async def update_runtime_options( """ @overload - async def update_runtime_options( - self, js_runtime_options: JSON, *, content_type: str = "application/json", **kwargs: Any + async def update_runtime_options_stable( + self, js_runtime_options: JSON, *, content_type: str = "application/merge-patch+json", **kwargs: Any ) -> _models.JsRuntimeOptions: """Runtime options for user defined endpoints. @@ -1918,7 +2129,7 @@ async def update_runtime_options( :param js_runtime_options: JS Runtime options. Required. :type js_runtime_options: JSON :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". + Default value is "application/merge-patch+json". :paramtype content_type: str :return: JsRuntimeOptions. The JsRuntimeOptions is compatible with MutableMapping :rtype: ~azure.confidentialledger.models.JsRuntimeOptions @@ -1926,8 +2137,8 @@ async def update_runtime_options( """ @overload - async def update_runtime_options( - self, js_runtime_options: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + async def update_runtime_options_stable( + self, js_runtime_options: IO[bytes], *, content_type: str = "application/merge-patch+json", **kwargs: Any ) -> _models.JsRuntimeOptions: """Runtime options for user defined endpoints. @@ -1936,7 +2147,7 @@ async def update_runtime_options( :param js_runtime_options: JS Runtime options. Required. :type js_runtime_options: IO[bytes] :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". + Default value is "application/merge-patch+json". :paramtype content_type: str :return: JsRuntimeOptions. The JsRuntimeOptions is compatible with MutableMapping :rtype: ~azure.confidentialledger.models.JsRuntimeOptions @@ -1944,7 +2155,12 @@ async def update_runtime_options( """ @distributed_trace_async - async def update_runtime_options( + @api_version_validation( + method_added_on="2026-02-23", + params_added_on={"2026-02-23": ["api_version", "content_type", "accept"]}, + api_versions_list=["2026-02-23"], + ) + async def update_runtime_options_stable( self, js_runtime_options: Union[_models.JsRuntimeOptions, JSON, IO[bytes]], **kwargs: Any ) -> _models.JsRuntimeOptions: """Runtime options for user defined endpoints. @@ -1973,14 +2189,14 @@ async def update_runtime_options( content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) cls: ClsType[_models.JsRuntimeOptions] = kwargs.pop("cls", None) - content_type = content_type or "application/json" + content_type = content_type or "application/merge-patch+json" _content = None if isinstance(js_runtime_options, (IOBase, bytes)): _content = js_runtime_options else: _content = json.dumps(js_runtime_options, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_confidential_ledger_update_runtime_options_request( + _request = build_confidential_ledger_update_runtime_options_stable_request( content_type=content_type, api_version=self._config.api_version, content=_content, @@ -1994,6 +2210,7 @@ async def update_runtime_options( } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = await self._client._pipeline.run( # type: ignore # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -2008,11 +2225,14 @@ async def update_runtime_options( except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.JsRuntimeOptions, response.json()) @@ -2059,6 +2279,7 @@ async def get_user_defined_endpoints_module(self, *, module_name: str, **kwargs: } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = await self._client._pipeline.run( # type: ignore # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -2073,11 +2294,14 @@ async def get_user_defined_endpoints_module(self, *, module_name: str, **kwargs: except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.ModuleDef, response.json()) @@ -2136,7 +2360,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + headers=_headers, + params=_next_request_params, ) path_format_arguments = { "ledgerEndpoint": self._serialize.url( @@ -2149,7 +2376,10 @@ def prepare_request(next_link=None): async def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize(list[_models.UserDefinedFunction], deserialized.get("functions", [])) + list_of_elem = _deserialize( + list[_models.UserDefinedFunction], + deserialized.get("functions", []), + ) if cls: list_of_elem = cls(list_of_elem) # type: ignore return deserialized.get("nextLink") or None, AsyncList(list_of_elem) @@ -2165,7 +2395,10 @@ async def get_next(next_link=None): if response.status_code not in [200]: map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) return pipeline_response @@ -2219,7 +2452,10 @@ async def delete_user_defined_function(self, function_id: str, **kwargs: Any) -> if response.status_code not in [204]: map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if cls: @@ -2263,6 +2499,7 @@ async def get_user_defined_function(self, function_id: str, **kwargs: Any) -> _m } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = await self._client._pipeline.run( # type: ignore # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -2277,11 +2514,14 @@ async def get_user_defined_function(self, function_id: str, **kwargs: Any) -> _m except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.UserDefinedFunction, response.json()) @@ -2420,6 +2660,7 @@ async def create_user_defined_function( } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = await self._client._pipeline.run( # type: ignore # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -2434,7 +2675,10 @@ async def create_user_defined_function( except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) response_headers = {} @@ -2443,7 +2687,7 @@ async def create_user_defined_function( ) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.UserDefinedFunction, response.json()) @@ -2599,6 +2843,7 @@ async def execute_user_defined_function( } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = await self._client._pipeline.run( # type: ignore # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -2613,11 +2858,14 @@ async def execute_user_defined_function( except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: deserialized = _deserialize(_models.UserDefinedFunctionExecutionResponse, response.json()) @@ -2627,15 +2875,15 @@ async def execute_user_defined_function( return deserialized # type: ignore @distributed_trace_async - async def get_user_defined_role(self, *, role_name: str, **kwargs: Any) -> _models.Roles: + async def get_user_defined_role(self, *, role_name: str, **kwargs: Any) -> _models.UserDefinedRole: """Gets role actions for user defined roles. user defined roles allow users to define and manage app specific AuthZ policy. :keyword role_name: user defined role name. Required. :paramtype role_name: str - :return: Roles. The Roles is compatible with MutableMapping - :rtype: ~azure.confidentialledger.models.Roles + :return: UserDefinedRole. The UserDefinedRole is compatible with MutableMapping + :rtype: ~azure.confidentialledger.models.UserDefinedRole :raises ~azure.core.exceptions.HttpResponseError: """ error_map: MutableMapping = { @@ -2649,7 +2897,7 @@ async def get_user_defined_role(self, *, role_name: str, **kwargs: Any) -> _mode _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[_models.Roles] = kwargs.pop("cls", None) + cls: ClsType[_models.UserDefinedRole] = kwargs.pop("cls", None) _request = build_confidential_ledger_get_user_defined_role_request( role_name=role_name, @@ -2664,6 +2912,7 @@ async def get_user_defined_role(self, *, role_name: str, **kwargs: Any) -> _mode } _request.url = self._client.format_url(_request.url, **path_format_arguments) + _decompress = kwargs.pop("decompress", True) _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = await self._client._pipeline.run( # type: ignore # pylint: disable=protected-access _request, stream=_stream, **kwargs @@ -2678,13 +2927,16 @@ async def get_user_defined_role(self, *, role_name: str, **kwargs: Any) -> _mode except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if _stream: - deserialized = response.iter_bytes() + deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.Roles, response.json()) + deserialized = _deserialize(_models.UserDefinedRole, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -2692,27 +2944,27 @@ async def get_user_defined_role(self, *, role_name: str, **kwargs: Any) -> _mode return deserialized # type: ignore @overload - async def create_user_defined_role( - self, body: _models.Roles, *, content_type: str = "application/json", **kwargs: Any - ) -> None: + async def create_user_defined_role_stable( + self, body: _models.UserDefinedRoles, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.UserDefinedRoles: """Creates new roles and their actions. User defined roles allow users to define and manage app specific AuthZ policy. :param body: Request body. Required. - :type body: ~azure.confidentialledger.models.Roles + :type body: ~azure.confidentialledger.models.UserDefinedRoles :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :return: None - :rtype: None + :return: UserDefinedRoles. The UserDefinedRoles is compatible with MutableMapping + :rtype: ~azure.confidentialledger.models.UserDefinedRoles :raises ~azure.core.exceptions.HttpResponseError: """ @overload - async def create_user_defined_role( + async def create_user_defined_role_stable( self, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> None: + ) -> _models.UserDefinedRoles: """Creates new roles and their actions. User defined roles allow users to define and manage app specific AuthZ policy. @@ -2722,15 +2974,15 @@ async def create_user_defined_role( :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :return: None - :rtype: None + :return: UserDefinedRoles. The UserDefinedRoles is compatible with MutableMapping + :rtype: ~azure.confidentialledger.models.UserDefinedRoles :raises ~azure.core.exceptions.HttpResponseError: """ @overload - async def create_user_defined_role( + async def create_user_defined_role_stable( self, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> None: + ) -> _models.UserDefinedRoles: """Creates new roles and their actions. User defined roles allow users to define and manage app specific AuthZ policy. @@ -2740,21 +2992,29 @@ async def create_user_defined_role( :keyword content_type: Body Parameter content-type. Content type parameter for binary body. Default value is "application/json". :paramtype content_type: str - :return: None - :rtype: None + :return: UserDefinedRoles. The UserDefinedRoles is compatible with MutableMapping + :rtype: ~azure.confidentialledger.models.UserDefinedRoles :raises ~azure.core.exceptions.HttpResponseError: """ @distributed_trace_async - async def create_user_defined_role(self, body: Union[_models.Roles, JSON, IO[bytes]], **kwargs: Any) -> None: + @api_version_validation( + method_added_on="2026-02-23", + params_added_on={"2026-02-23": ["api_version", "content_type", "accept"]}, + api_versions_list=["2026-02-23"], + ) + async def create_user_defined_role_stable( + self, body: Union[_models.UserDefinedRoles, JSON, IO[bytes]], **kwargs: Any + ) -> _models.UserDefinedRoles: """Creates new roles and their actions. User defined roles allow users to define and manage app specific AuthZ policy. - :param body: Request body. Is one of the following types: Roles, JSON, IO[bytes] Required. - :type body: ~azure.confidentialledger.models.Roles or JSON or IO[bytes] - :return: None - :rtype: None + :param body: Request body. Is one of the following types: UserDefinedRoles, JSON, IO[bytes] + Required. + :type body: ~azure.confidentialledger.models.UserDefinedRoles or JSON or IO[bytes] + :return: UserDefinedRoles. The UserDefinedRoles is compatible with MutableMapping + :rtype: ~azure.confidentialledger.models.UserDefinedRoles :raises ~azure.core.exceptions.HttpResponseError: """ error_map: MutableMapping = { @@ -2769,7 +3029,7 @@ async def create_user_defined_role(self, body: Union[_models.Roles, JSON, IO[byt _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[None] = kwargs.pop("cls", None) + cls: ClsType[_models.UserDefinedRoles] = kwargs.pop("cls", None) content_type = content_type or "application/json" _content = None @@ -2778,7 +3038,7 @@ async def create_user_defined_role(self, body: Union[_models.Roles, JSON, IO[byt else: _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_confidential_ledger_create_user_defined_role_request( + _request = build_confidential_ledger_create_user_defined_role_stable_request( content_type=content_type, api_version=self._config.api_version, content=_content, @@ -2792,7 +3052,8 @@ async def create_user_defined_role(self, body: Union[_models.Roles, JSON, IO[byt } _request.url = self._client.format_url(_request.url, **path_format_arguments) - _stream = False + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = await self._client._pipeline.run( # type: ignore # pylint: disable=protected-access _request, stream=_stream, **kwargs ) @@ -2800,81 +3061,104 @@ async def create_user_defined_role(self, body: Union[_models.Roles, JSON, IO[byt 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) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.UserDefinedRoles, response.json()) + if cls: - return cls(pipeline_response, None, {}) # type: ignore + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore @overload - async def update_user_defined_role( - self, body: _models.Roles, *, content_type: str = "application/json", **kwargs: Any - ) -> None: - """Patch replaces the allowed action on existing roles,if the desire is to remove - an existing action, the role must be deleted and recreated. + async def update_user_defined_role_stable( + self, body: _models.UserDefinedRoles, *, content_type: str = "application/merge-patch+json", **kwargs: Any + ) -> _models.UserDefinedRoles: + """Patch replaces the allowed action on existing roles,if the desire is to remove an existing + action, the role must be deleted and recreated. User defined roles allow users to define and manage app specific AuthZ policy. :param body: Request body. Required. - :type body: ~azure.confidentialledger.models.Roles + :type body: ~azure.confidentialledger.models.UserDefinedRoles :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". + Default value is "application/merge-patch+json". :paramtype content_type: str - :return: None - :rtype: None + :return: UserDefinedRoles. The UserDefinedRoles is compatible with MutableMapping + :rtype: ~azure.confidentialledger.models.UserDefinedRoles :raises ~azure.core.exceptions.HttpResponseError: """ @overload - async def update_user_defined_role( - self, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> None: - """Patch replaces the allowed action on existing roles,if the desire is to remove - an existing action, the role must be deleted and recreated. + async def update_user_defined_role_stable( + self, body: JSON, *, content_type: str = "application/merge-patch+json", **kwargs: Any + ) -> _models.UserDefinedRoles: + """Patch replaces the allowed action on existing roles,if the desire is to remove an existing + action, the role must be deleted and recreated. User defined roles allow users to define and manage app specific AuthZ policy. :param body: Request body. Required. :type body: JSON :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". + Default value is "application/merge-patch+json". :paramtype content_type: str - :return: None - :rtype: None + :return: UserDefinedRoles. The UserDefinedRoles is compatible with MutableMapping + :rtype: ~azure.confidentialledger.models.UserDefinedRoles :raises ~azure.core.exceptions.HttpResponseError: """ @overload - async def update_user_defined_role( - self, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> None: - """Patch replaces the allowed action on existing roles,if the desire is to remove - an existing action, the role must be deleted and recreated. + async def update_user_defined_role_stable( + self, body: IO[bytes], *, content_type: str = "application/merge-patch+json", **kwargs: Any + ) -> _models.UserDefinedRoles: + """Patch replaces the allowed action on existing roles,if the desire is to remove an existing + action, the role must be deleted and recreated. User defined roles allow users to define and manage app specific AuthZ policy. :param body: Request body. Required. :type body: IO[bytes] :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". + Default value is "application/merge-patch+json". :paramtype content_type: str - :return: None - :rtype: None + :return: UserDefinedRoles. The UserDefinedRoles is compatible with MutableMapping + :rtype: ~azure.confidentialledger.models.UserDefinedRoles :raises ~azure.core.exceptions.HttpResponseError: """ @distributed_trace_async - async def update_user_defined_role(self, body: Union[_models.Roles, JSON, IO[bytes]], **kwargs: Any) -> None: - """Patch replaces the allowed action on existing roles,if the desire is to remove - an existing action, the role must be deleted and recreated. + @api_version_validation( + method_added_on="2026-02-23", + params_added_on={"2026-02-23": ["api_version", "content_type", "accept"]}, + api_versions_list=["2026-02-23"], + ) + async def update_user_defined_role_stable( + self, body: Union[_models.UserDefinedRoles, JSON, IO[bytes]], **kwargs: Any + ) -> _models.UserDefinedRoles: + """Patch replaces the allowed action on existing roles,if the desire is to remove an existing + action, the role must be deleted and recreated. User defined roles allow users to define and manage app specific AuthZ policy. - :param body: Request body. Is one of the following types: Roles, JSON, IO[bytes] Required. - :type body: ~azure.confidentialledger.models.Roles or JSON or IO[bytes] - :return: None - :rtype: None + :param body: Request body. Is one of the following types: UserDefinedRoles, JSON, IO[bytes] + Required. + :type body: ~azure.confidentialledger.models.UserDefinedRoles or JSON or IO[bytes] + :return: UserDefinedRoles. The UserDefinedRoles is compatible with MutableMapping + :rtype: ~azure.confidentialledger.models.UserDefinedRoles :raises ~azure.core.exceptions.HttpResponseError: """ error_map: MutableMapping = { @@ -2889,16 +3173,16 @@ async def update_user_defined_role(self, body: Union[_models.Roles, JSON, IO[byt _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[None] = kwargs.pop("cls", None) + cls: ClsType[_models.UserDefinedRoles] = kwargs.pop("cls", None) - content_type = content_type or "application/json" + content_type = content_type or "application/merge-patch+json" _content = None if isinstance(body, (IOBase, bytes)): _content = body else: _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_confidential_ledger_update_user_defined_role_request( + _request = build_confidential_ledger_update_user_defined_role_stable_request( content_type=content_type, api_version=self._config.api_version, content=_content, @@ -2912,7 +3196,8 @@ async def update_user_defined_role(self, body: Union[_models.Roles, JSON, IO[byt } _request.url = self._client.format_url(_request.url, **path_format_arguments) - _stream = False + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = await self._client._pipeline.run( # type: ignore # pylint: disable=protected-access _request, stream=_stream, **kwargs ) @@ -2920,19 +3205,38 @@ async def update_user_defined_role(self, body: Union[_models.Roles, JSON, IO[byt 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) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.UserDefinedRoles, response.json()) + if cls: - return cls(pipeline_response, None, {}) # type: ignore + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore @distributed_trace_async - async def delete_user_defined_role(self, *, role_name: str, **kwargs: Any) -> None: + @api_version_validation( + method_added_on="2026-02-23", + params_added_on={"2026-02-23": ["api_version", "role_name"]}, + api_versions_list=["2026-02-23"], + ) + async def delete_user_defined_role_stable(self, *, role_name: str, **kwargs: Any) -> None: """Deletes user defined roles. - A user defined role allows the users to create and manage their own role - actions using the API. + A user defined role allows the users to create and manage their own role actions using the API. :keyword role_name: user defined role name. Required. :paramtype role_name: str @@ -2953,7 +3257,7 @@ async def delete_user_defined_role(self, *, role_name: str, **kwargs: Any) -> No cls: ClsType[None] = kwargs.pop("cls", None) - _request = build_confidential_ledger_delete_user_defined_role_request( + _request = build_confidential_ledger_delete_user_defined_role_stable_request( role_name=role_name, api_version=self._config.api_version, headers=_headers, @@ -2973,9 +3277,12 @@ async def delete_user_defined_role(self, *, role_name: str, **kwargs: Any) -> No response = pipeline_response.http_response - if response.status_code not in [200]: + if response.status_code not in [204]: map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize(_models.ConfidentialLedgerError, response) + error = _failsafe_deserialize( + _models.ConfidentialLedgerError, + response, + ) raise HttpResponseError(response=response, model=error) if cls: diff --git a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/aio/_operations/_patch.py b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/aio/_operations/_patch.py index 1c934dcf36ec..87676c65a8f0 100644 --- a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/aio/_operations/_patch.py +++ b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/aio/_operations/_patch.py @@ -1,4 +1,3 @@ -# pylint: disable=line-too-long,useless-suppression # coding=utf-8 # -------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. @@ -9,22 +8,8 @@ Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ -import asyncio # pylint: disable=do-not-import-asyncio -from typing import Any, Callable, IO, Coroutine, List, Optional, Union, cast -from azure.core.exceptions import ResourceNotFoundError -from azure.core.polling import AsyncLROPoller, AsyncNoPolling, AsyncPollingMethod - -from azure.confidentialledger.aio._operations._operations import ( - _ConfidentialLedgerClientOperationsMixin as GeneratedOperationsMixin, -) -from azure.confidentialledger.aio._operations._operations import ClsType, JSON -from azure.confidentialledger._operations._patch import BaseStatePollingMethod -import azure.confidentialledger.models as _models - -__all__: List[str] = [ - "_ConfidentialLedgerClientOperationsMixin" -] # Add all objects you want publicly available to users at this package level +__all__: list[str] = [] # Add all objects you want publicly available to users at this package level def patch_sdk(): @@ -34,255 +19,3 @@ def patch_sdk(): you can't accomplish using the techniques described in https://aka.ms/azsdk/python/dpcodegen/python/customize """ - - -class AsyncStatePollingMethod(BaseStatePollingMethod, AsyncPollingMethod): - """Polling method for methods returning responses containing a 'state' field; the polling - completes when 'state' becomes a desired value. - """ - - def __init__( - self, - operation: Callable[[], Coroutine[Any, Any, JSON]], - desired_state: str, - polling_interval_s: float, - retry_not_found: bool, - ): - super().__init__(operation, desired_state, polling_interval_s, retry_not_found) - - async def run(self) -> None: - try: - while not self.finished(): - try: - response = await self._operation() - self._evaluate_response(response) - except ResourceNotFoundError as not_found_exception: - # We'll allow some instances of resource not found to account for replication - # delay if session stickiness is lost. - - self._not_found_count += 1 - - not_retryable = not self._retry_not_found or self._give_up_not_found_error(not_found_exception) - - if not_retryable or self._not_found_count >= 3: - raise - if not self.finished(): - await asyncio.sleep(self._polling_interval_s) - except Exception: - self._status = "failed" - raise - - -class _ConfidentialLedgerClientOperationsMixin(GeneratedOperationsMixin): - async def begin_get_ledger_entry( - self, transaction_id: str, *, collection_id: Optional[str] = None, **kwargs: Any - ) -> AsyncLROPoller[_models.LedgerQueryResult]: - """Returns a poller to fetch the ledger entry at the specified transaction id. - - A collection id may optionally be specified to indicate the collection from which to fetch - the value. - - To return older ledger entries, the relevant sections of the ledger must be - read from disk and validated. To prevent blocking within the enclave, the - response will indicate whether the entry is ready and part of the response, or - if the loading is still ongoing. - - :param transaction_id: Identifies a write transaction. Required. - :type transaction_id: str - :keyword collection_id: The collection id. Default value is None. - :paramtype collection_id: str - :return: An instance of AsyncLROPoller that returns a LedgerQueryResult for the ledger entry. - :rtype: ~azure.core.polling.AsyncLROPoller[~azure.confidentialledger.models.LedgerQueryResult] - :raises ~azure.core.exceptions.HttpResponseError: - """ - polling = kwargs.pop("polling", True) # type: Union[bool, AsyncPollingMethod] - lro_delay = kwargs.pop("polling_interval", 0.5) - - async def operation() -> JSON: - return await super(_ConfidentialLedgerClientOperationsMixin, self).get_ledger_entry( - transaction_id, collection_id=collection_id, **kwargs - ) - - initial_response = await operation() - - if polling is True: - polling_method = cast(AsyncPollingMethod, AsyncStatePollingMethod(operation, "Ready", lro_delay, False)) - elif polling is False: - polling_method = cast(AsyncPollingMethod, AsyncNoPolling()) - else: - polling_method = polling - return AsyncLROPoller(self._client, initial_response, lambda x: x, polling_method) - - async def begin_get_receipt(self, transaction_id: str, **kwargs: Any) -> AsyncLROPoller[_models.TransactionReceipt]: - """Returns a poller for getting a receipt certifying ledger contents at a particular - transaction id. - - :param transaction_id: Identifies a write transaction. Required. - :type transaction_id: str - :return: An instance of AsyncLROPoller that returns a TransactionReceipt for the receipt. - :rtype: ~azure.core.polling.AsyncLROPoller[~azure.confidentialledger.models.TransactionReceipt] - :raises ~azure.core.exceptions.HttpResponseError: - """ - polling = kwargs.pop("polling", True) # type: Union[bool, AsyncPollingMethod] - lro_delay = kwargs.pop("polling_interval", 0.5) - - async def operation() -> JSON: - return await super(_ConfidentialLedgerClientOperationsMixin, self).get_receipt( - transaction_id=transaction_id, **kwargs - ) - - initial_response = await operation() - - if polling is True: - polling_method = cast(AsyncPollingMethod, AsyncStatePollingMethod(operation, "Ready", lro_delay, False)) - elif polling is False: - polling_method = cast(AsyncPollingMethod, AsyncNoPolling()) - else: - polling_method = polling - return AsyncLROPoller(self._client, initial_response, lambda x: x, polling_method) - - async def begin_create_ledger_entry( - self, entry: Union[_models.LedgerEntry, JSON, IO[bytes]], *, collection_id: Optional[str] = None, **kwargs: Any - ) -> AsyncLROPoller[_models.TransactionStatus]: - """Writes a ledger entry and returns a poller to wait for it to be durably committed. The - poller returns the result for the initial call to create the ledger entry. - - A collection id may optionally be specified. - - :param entry: Ledger entry. Required. - :type entry: ~azure.confidentialledger.models.LedgerEntry or JSON or IO[bytes] - :keyword collection_id: The collection id. Default value is None. - :paramtype collection_id: str - :return: AsyncLROPoller[TransactionStatus]. The TransactionStatus is compatible with MutableMapping - :rtype: ~azure.core.polling.AsyncLROPoller[~azure.confidentialledger.models.TransactionStatus] - :raises ~azure.core.exceptions.HttpResponseError: - """ - - # Pop arguments that are unexpected in the pipeline. - - polling = kwargs.pop("polling", True) # type: Union[bool, AsyncPollingMethod] - lro_delay = kwargs.pop("polling_interval", 0.5) - - # Pop the custom deserializer, if any, so we know the format of the response and can - # retrieve the transactionId. Serialize the response later. - - cls = kwargs.pop("cls", None) # type: ClsType[JSON] - kwargs["cls"] = lambda pipeline_response, json_response, headers: ( - pipeline_response, - { - **json_response, - "transactionId": headers.get("x-ms-ccf-transaction-id") if headers else None, - }, - headers, - ) - - post_pipeline_response, post_result, post_headers = await self.create_ledger_entry( - entry, collection_id=collection_id, **kwargs - ) - - # Delete the cls because it should only apply to the create_ledger_entry response, not the - # wait_for_commit call. - - del kwargs["cls"] - - transaction_id = post_result["transactionId"] # type: ignore - - kwargs["polling"] = polling - kwargs["polling_interval"] = lro_delay - - if cls: - kwargs["_create_ledger_entry_response"] = cls( - post_pipeline_response, cast(JSON, post_result), post_headers # type: ignore - ) - else: - kwargs["_create_ledger_entry_response"] = post_result - return await self.begin_wait_for_commit(transaction_id, **kwargs) - - async def begin_wait_for_commit( - self, transaction_id: str, **kwargs: Any - ) -> AsyncLROPoller[_models.TransactionStatus]: - """Creates a poller that queries the state of the specified transaction until it is - Committed, a state that indicates the transaction is durably stored in the Confidential - Ledger. - - :param transaction_id: Identifies a write transaction. Required. - :type transaction_id: str - :return: An instance of AsyncLROPoller returning a TransactionStatus object describing the transaction status. - :rtype: ~azure.core.polling.AsyncLROPoller[~azure.confidentialledger.models.TransactionStatus] - :raises ~azure.core.exceptions.HttpResponseError: - """ - polling = kwargs.pop("polling", True) # type: Union[bool, AsyncPollingMethod] - lro_delay = kwargs.pop("polling_interval", 0.5) - - # If this poller was called from begin_create_ledger_entry, we should return the - # create_ledger_entry response, not the transaction status. - - post_result = kwargs.pop("_create_ledger_entry_response", None) - - def deserialization_callback(x): - return x if post_result is None else post_result - - async def operation() -> JSON: - return await super(_ConfidentialLedgerClientOperationsMixin, self).get_transaction_status( - transaction_id=transaction_id, **kwargs - ) - - try: - initial_response = await operation() - except ResourceNotFoundError: - if polling is False or polling is None: - raise - # This method allows for temporary resource not found errors, which may occur if session - # stickiness is lost and there is replication lag. - - initial_response = {} - if polling is True: - polling_method = cast( - AsyncPollingMethod, - AsyncStatePollingMethod(operation, "Committed", lro_delay, True), - ) - elif polling is False: - polling_method = cast(AsyncPollingMethod, AsyncNoPolling()) - else: - polling_method = polling - return AsyncLROPoller(self._client, initial_response, deserialization_callback, polling_method) - - async def create_ledger_entry( - self, entry: Union[_models.LedgerEntry, JSON, IO[bytes]], *, collection_id: Optional[str] = None, **kwargs: Any - ) -> _models.LedgerWriteResult: - """Writes a ledger entry. - - A collection id may optionally be specified. - - :param entry: Ledger entry. Is one of the following types: LedgerEntry, JSON, IO[bytes] - Required. - :type entry: ~azure.confidentialledger.models.LedgerEntry or JSON or IO[bytes] - :keyword collection_id: The collection id. Default value is None. - :paramtype collection_id: str - :return: LedgerWriteResult. The LedgerWriteResult is compatible with MutableMapping - :rtype: ~azure.confidentialledger.models.LedgerWriteResult - :raises ~azure.core.exceptions.HttpResponseError: - - Example: - .. code-block:: python - - # JSON input template you can fill out and use as your body input. - entry = { - "collectionId": { - "collectionId": "str" # Required. - }, - "contents": "str", # Required. Contents of the ledger entry. - "transactionId": "str" # Optional. A unique identifier for the state of the - ledger. If returned as part of a LedgerEntry, it indicates the state from which - the entry was read. - } - """ - - kwargs["cls"] = kwargs.get( - "cls", - lambda _, json_response, headers: { - **json_response, - "transactionId": headers.get("x-ms-ccf-transaction-id") if headers else None, - }, - ) - return await super().create_ledger_entry(entry, collection_id=collection_id, **kwargs) diff --git a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/aio/_patch.py b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/aio/_patch.py index 0f494322eb1d..87676c65a8f0 100644 --- a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/aio/_patch.py +++ b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/aio/_patch.py @@ -7,25 +7,9 @@ Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ -import os -from typing import Any, List, Union -from azure.core.credentials_async import AsyncTokenCredential -from azure.core.pipeline import policies -from azure.confidentialledger.aio._client import ( - ConfidentialLedgerClient as GeneratedClient, -) - -# Since we can't `await` in __init__, use the sync client for the Identity Service. - - -from azure.confidentialledger.certificate import ConfidentialLedgerCertificateClient # pylint: disable=import-error,no-name-in-module -from azure.confidentialledger._patch import ConfidentialLedgerCertificateCredential - -__all__: List[str] = [ - "ConfidentialLedgerClient", -] # Add all objects you want publicly available to users at this package level +__all__: list[str] = [] # Add all objects you want publicly available to users at this package level def patch_sdk(): @@ -35,84 +19,3 @@ def patch_sdk(): you can't accomplish using the techniques described in https://aka.ms/azsdk/python/dpcodegen/python/customize """ - - -class ConfidentialLedgerClient(GeneratedClient): - """The ConfidentialLedgerClient writes and retrieves ledger entries against the Confidential - Ledger service. - - :param endpoint: The Confidential Ledger URL, for example - https://contoso.confidentialledger.azure.com. - :type endpoint: str - :param credential: A credential object for authenticating with the Confidential Ledger. - :type credential: Union[ - ~azure.confidentialledger.ConfidentialLedgerCertificateCredential, - ~azure.core.credentials_async.AsyncTokenCredential] - :keyword ledger_certificate_path: The path to the Confidential Ledger's TLS certificate. If this - file does not exist yet, the Confidential Ledger's TLS certificate will be fetched and saved - to this file. - :paramtype ledger_certificate_path: Union[bytes, str, os.PathLike] - :keyword api_version: Api Version. Default value is "2022-05-13". Note that overriding this - default value may result in unsupported behavior. - :paramtype api_version: str - """ - - def __init__( - self, - endpoint: str, - credential: Union[ConfidentialLedgerCertificateCredential, AsyncTokenCredential], - *, - ledger_certificate_path: Union[bytes, str, os.PathLike], - **kwargs: Any, - ) -> None: - # Remove some kwargs first so that there aren't unexpected kwargs passed to - # get_ledger_identity. - - if isinstance(credential, ConfidentialLedgerCertificateCredential): - auth_policy = None - else: - credential_scopes = kwargs.pop("credential_scopes", ["https://confidential-ledger.azure.com/.default"]) - auth_policy = kwargs.pop( - "authentication_policy", - # Don't call AsyncBearerTokenCredentialPolicy here as an event loop may not be - # running if a non-async authentication policy is passed. - None, - ) - - if auth_policy is None: - auth_policy = policies.AsyncBearerTokenCredentialPolicy(credential, *credential_scopes, **kwargs) - if os.path.isfile(ledger_certificate_path) is False: - # We'll need to fetch the TLS certificate. - - identity_service_client = ConfidentialLedgerCertificateClient(**kwargs) - - # Ledger URIs are of the form https://.confidential-ledger.azure.com. - - ledger_id = endpoint.replace("https://", "").split(".")[0] - - # We use the sync client here because async __init__ is not allowed. - - ledger_cert = identity_service_client.get_ledger_identity(ledger_id, **kwargs) - - with open(ledger_certificate_path, "w", encoding="utf-8") as outfile: - outfile.write(ledger_cert["ledgerTlsCertificate"]) - # For ConfidentialLedgerCertificateCredential, pass the path to the certificate down to the - # PipelineCLient. - - if isinstance(credential, ConfidentialLedgerCertificateCredential): - # The async version of the client seems to expect a sequence of filenames. - # azure/core/pipeline/transport/_aiohttp.py:163 - # > ssl_ctx.load_cert_chain(*cert) - - kwargs["connection_cert"] = kwargs.get("connection_cert", (credential.certificate_path,)) - # The auto-generated client has authentication disabled so we can customize authentication. - # If the credential is the typical TokenCredential, then construct the authentication policy - # the normal way. - - else: - kwargs["authentication_policy"] = auth_policy - # Customize the underlying client to use a self-signed TLS certificate. - - kwargs["connection_verify"] = kwargs.get("connection_verify", ledger_certificate_path) - - super().__init__(endpoint, **kwargs) diff --git a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/models/__init__.py b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/models/__init__.py index 72c893bbc073..354dbbc29a03 100644 --- a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/models/__init__.py +++ b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/models/__init__.py @@ -40,7 +40,6 @@ ReceiptElement, ReceiptLeafComponents, Role, - Roles, TransactionReceipt, TransactionStatus, UserDefinedFunction, @@ -49,6 +48,8 @@ UserDefinedFunctionExecutionResponse, UserDefinedFunctionExecutionResult, UserDefinedFunctionHook, + UserDefinedRole, + UserDefinedRoles, ) from ._enums import ( # type: ignore @@ -93,7 +94,6 @@ "ReceiptElement", "ReceiptLeafComponents", "Role", - "Roles", "TransactionReceipt", "TransactionStatus", "UserDefinedFunction", @@ -102,6 +102,8 @@ "UserDefinedFunctionExecutionResponse", "UserDefinedFunctionExecutionResult", "UserDefinedFunctionHook", + "UserDefinedRole", + "UserDefinedRoles", "ApplicationClaimKind", "ApplicationClaimProtocol", "ConfidentialLedgerQueryState", diff --git a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/models/_enums.py b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/models/_enums.py index 5e977f918559..a4ed197c44b0 100644 --- a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/models/_enums.py +++ b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/models/_enums.py @@ -14,86 +14,84 @@ class ApplicationClaimKind(str, Enum, metaclass=CaseInsensitiveEnumMeta): """Represents the kind of an application claim.""" LEDGER_ENTRY = "LedgerEntry" - """Claim derived from a ledger entry""" + """Claim derived from a ledger entry.""" CLAIM_DIGEST = "ClaimDigest" - """Claim in digested form""" + """Claim in digested form.""" class ApplicationClaimProtocol(str, Enum, metaclass=CaseInsensitiveEnumMeta): - """Represents the protocol to be used to compute the digest of a claim from the - given claim data. - """ + """Represents the protocol to be used to compute the digest of a claim from the given claim data.""" LEDGER_ENTRY_V1 = "LedgerEntryV1" - """Ledger Entry V1 protocol""" + """Ledger Entry V1 protocol.""" class ConfidentialLedgerQueryState(str, Enum, metaclass=CaseInsensitiveEnumMeta): """State of a ledger query.""" LOADING = "Loading" - """The query is still loading""" + """The query is still loading.""" READY = "Ready" - """The query is ready""" + """The query is ready.""" class ConfidentialLedgerUserRoleName(str, Enum, metaclass=CaseInsensitiveEnumMeta): """Represents an assignable role.""" ADMINISTRATOR = "Administrator" - """Administrator role""" + """Administrator role.""" CONTRIBUTOR = "Contributor" - """Contributor role""" + """Contributor role.""" READER = "Reader" - """Reader role""" + """Reader role.""" class ForwardingRequired(str, Enum, metaclass=CaseInsensitiveEnumMeta): """Indicates whether request forwarding is required for this operation.""" SOMETIMES = "sometimes" - """Forwarding is required sometimes""" + """Forwarding is required sometimes.""" ALWAYS = "always" - """Forwarding is always required""" + """Forwarding is always required.""" NEVER = "never" - """Forwarding is never required""" + """Forwarding is never required.""" class Mode(str, Enum, metaclass=CaseInsensitiveEnumMeta): """Represents the operation mode of the endpoint.""" READWRITE = "readwrite" - """Read-write mode""" + """Read-write mode.""" READONLY = "readonly" - """Read-only mode""" + """Read-only mode.""" HISTORICAL = "historical" - """Historical mode""" + """Historical mode.""" class RedirectionStrategy(str, Enum, metaclass=CaseInsensitiveEnumMeta): """Represents the redirection strategy for the endpoint.""" NONE = "none" - """No redirection strategy""" + """No redirection strategy.""" TO_PRIMARY = "to_primary" - """Redirect to primary""" + """Redirect to primary.""" TO_BACKUP = "to_backup" - """Redirect to backup""" + """Redirect to backup.""" class TransactionState(str, Enum, metaclass=CaseInsensitiveEnumMeta): """Represents the state of the transaction.""" COMMITTED = "Committed" - """The transaction is committed""" + """The transaction is committed.""" PENDING = "Pending" - """The transaction is pending""" + """The transaction is pending.""" class UserDefinedFunctionExecutionStatus(str, Enum, metaclass=CaseInsensitiveEnumMeta): """Represents the status of a user defined function execution.""" SUCCEEDED = "Succeeded" - """The function execution completed successfully""" + """The function execution completed successfully.""" FAILED = "Failed" - """The function execution failed""" + """The function execution failed.""" diff --git a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/models/_models.py b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/models/_models.py index 3050c86fb133..851a38fe68fd 100644 --- a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/models/_models.py +++ b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/models/_models.py @@ -108,8 +108,8 @@ class ClaimDigest(_Model): protocol: Union[str, "_models.ApplicationClaimProtocol"] = rest_field( visibility=["read", "create", "update", "delete", "query"] ) - """Represents the protocol to be used to compute the digest of a claim from the - given claim data. Required. \"LedgerEntryV1\"""" + """Represents the protocol to be used to compute the digest of a claim from the given claim data. + Required. \"LedgerEntryV1\"""" @overload def __init__( @@ -511,18 +511,16 @@ class LedgerEntry(_Model): collection_id: Optional[str] = rest_field(name="collectionId", visibility=["read"]) """The collection identifier for this ledger entry.""" transaction_id: Optional[str] = rest_field(name="transactionId", visibility=["read"]) - """A unique identifier for the state of the ledger. If returned as part of a - LedgerEntry, it indicates the state from which the entry was read.""" + """A unique identifier for the state of the ledger. If returned as part of a LedgerEntry, it + indicates the state from which the entry was read.""" pre_hooks: Optional[list["_models.UserDefinedFunctionHook"]] = rest_field( name="preHooks", visibility=["read", "create", "update", "delete", "query"] ) - """List of user defined function hooks to be executed before the ledger entry is - written.""" + """List of user defined function hooks to be executed before the ledger entry is written.""" post_hooks: Optional[list["_models.UserDefinedFunctionHook"]] = rest_field( name="postHooks", visibility=["read", "create", "update", "delete", "query"] ) - """List of user defined function hooks to be executed after the ledger entry is - written.""" + """List of user defined function hooks to be executed after the ledger entry is written.""" @overload def __init__( @@ -569,8 +567,8 @@ class LedgerEntryClaim(_Model): protocol: Union[str, "_models.ApplicationClaimProtocol"] = rest_field( visibility=["read", "create", "update", "delete", "query"] ) - """Represents the protocol to be used to compute the digest of a claim from the - given claim data. Required. \"LedgerEntryV1\"""" + """Represents the protocol to be used to compute the digest of a claim from the given claim data. + Required. \"LedgerEntryV1\"""" @overload def __init__( @@ -594,8 +592,8 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: class LedgerQueryResult(_Model): - """The result of querying for a ledger entry from an older transaction id. The - ledger entry is available in the response only if the returned state is Ready. + """The result of querying for a ledger entry from an older transaction id. The ledger entry is + available in the response only if the returned state is Ready. :ivar state: State of a ledger query. Required. Known values are: "Loading" and "Ready". :vartype state: str or ~azure.confidentialledger.models.ConfidentialLedgerQueryState @@ -609,8 +607,8 @@ class LedgerQueryResult(_Model): ) """State of a ledger query. Required. Known values are: \"Loading\" and \"Ready\".""" entry: Optional["_models.LedgerEntry"] = rest_field(visibility=["read", "create", "update", "delete", "query"]) - """The ledger entry found as a result of the query. This is only available if the - query is in Ready state.""" + """The ledger entry found as a result of the query. This is only available if the query is in + Ready state.""" @overload def __init__( @@ -648,8 +646,7 @@ class LedgerUser(_Model): """Represents an assignable role. Required. Known values are: \"Administrator\", \"Contributor\", and \"Reader\".""" user_id: Optional[str] = rest_field(name="userId", visibility=["read"]) - """Identifier for the user. This must either be an AAD object id or a certificate - fingerprint.""" + """Identifier for the user. This must either be an AAD object id or a certificate fingerprint.""" @overload def __init__( @@ -685,8 +682,7 @@ class LedgerUserMultipleRoles(_Model): ) """Represents an assignable role. Required.""" user_id: Optional[str] = rest_field(name="userId", visibility=["read"]) - """Identifier for the user. This must either be an AAD object id or a certificate - fingerprint.""" + """Identifier for the user. This must either be an AAD object id or a certificate fingerprint.""" @overload def __init__( @@ -707,8 +703,8 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: class LedgerWriteResult(_Model): - """Returned as a result of a write to the Confidential Ledger, the transaction id - in the response indicates when the write will become durable. + """Returned as a result of a write to the Confidential Ledger, the transaction id in the response + indicates when the write will become durable. :ivar collection_id: The collection identifier of the ledger entry. Required. :vartype collection_id: str @@ -779,7 +775,7 @@ class MethodToEndpointProperties(_Model): """ get_property: Optional["_models.EndpointProperties"] = rest_field( - name="get", visibility=["read", "create", "update", "delete", "query"] + name="get", visibility=["read", "create", "update", "delete", "query"], original_tsp_name="get" ) """Properties for GET method endpoint.""" put: Optional["_models.EndpointProperties"] = rest_field(visibility=["read", "create", "update", "delete", "query"]) @@ -954,7 +950,7 @@ class ReceiptLeafComponents(_Model): :vartype claims_digest: str :ivar commit_evidence: Commit evidence. :vartype commit_evidence: str - :ivar write_set_digest: TWrite set digest. + :ivar write_set_digest: Write set digest. :vartype write_set_digest: str """ @@ -969,7 +965,7 @@ class ReceiptLeafComponents(_Model): write_set_digest: Optional[str] = rest_field( name="writeSetDigest", visibility=["read", "create", "update", "delete", "query"] ) - """TWrite set digest.""" + """Write set digest.""" @overload def __init__( @@ -1026,34 +1022,6 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) -class Roles(_Model): - """Roles. - - :ivar roles: Roles. Required. - :vartype roles: list[~azure.confidentialledger.models.Role] - """ - - roles: list["_models.Role"] = rest_field(visibility=["read", "create", "update", "delete", "query"]) - """Roles. Required.""" - - @overload - def __init__( - self, - *, - roles: list["_models.Role"], - ) -> 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 TransactionReceipt(_Model): """A receipt certifying the transaction at the specified id. @@ -1081,8 +1049,8 @@ class TransactionReceipt(_Model): ) """State of a ledger query. Required. Known values are: \"Loading\" and \"Ready\".""" transaction_id: str = rest_field(name="transactionId", visibility=["read", "create", "update", "delete", "query"]) - """A unique identifier for the state of the ledger. If returned as part of a - LedgerEntry, it indicates the state from which the entry was read. Required.""" + """A unique identifier for the state of the ledger. If returned as part of a LedgerEntry, it + indicates the state from which the entry was read. Required.""" @overload def __init__( @@ -1122,8 +1090,8 @@ class TransactionStatus(_Model): """Represents the state of the transaction. Required. Known values are: \"Committed\" and \"Pending\".""" transaction_id: str = rest_field(name="transactionId", visibility=["read", "create", "update", "delete", "query"]) - """A unique identifier for the state of the ledger. If returned as part of a - LedgerEntry, it indicates the state from which the entry was read. Required.""" + """A unique identifier for the state of the ledger. If returned as part of a LedgerEntry, it + indicates the state from which the entry was read. Required.""" @overload def __init__( @@ -1177,8 +1145,8 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: class UserDefinedFunctionExecutionError(_Model): - """The error object of a user defined function execution. This is returned only - when the user defined function execution throws an exception. + """The error object of a user defined function execution. This is returned only when the user + defined function execution throws an exception. :ivar message: Message indicating the error thrown when executing the function. :vartype message: str @@ -1211,8 +1179,7 @@ class UserDefinedFunctionExecutionProperties(_Model): :ivar arguments: Runtime arguments of the user defined function. Defaults to an empty list. :vartype arguments: list[str] :ivar exported_function_name: Name of the exported function to execute in the code of the user - defined - function. Defaults to main. + defined function. Defaults to main. :vartype exported_function_name: str :ivar runtime_options: JS runtime options for user defined endpoints and functions. :vartype runtime_options: ~azure.confidentialledger.models.JsRuntimeOptions @@ -1223,8 +1190,8 @@ class UserDefinedFunctionExecutionProperties(_Model): exported_function_name: Optional[str] = rest_field( name="exportedFunctionName", visibility=["read", "create", "update", "delete", "query"] ) - """Name of the exported function to execute in the code of the user defined - function. Defaults to main.""" + """Name of the exported function to execute in the code of the user defined function. Defaults to + main.""" runtime_options: Optional["_models.JsRuntimeOptions"] = rest_field( name="runtimeOptions", visibility=["read", "create", "update", "delete", "query"] ) @@ -1253,8 +1220,8 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: class UserDefinedFunctionExecutionResponse(_Model): """The result of a user defined function execution. - :ivar error: The error object of a user defined function execution. This is returned only - when the user defined function execution throws an exception. + :ivar error: The error object of a user defined function execution. This is returned only when + the user defined function execution throws an exception. :vartype error: ~azure.confidentialledger.models.UserDefinedFunctionExecutionError :ivar result: The result object of a user defined function execution. This is returned only when the user defined function executes successfully. @@ -1267,13 +1234,13 @@ class UserDefinedFunctionExecutionResponse(_Model): error: Optional["_models.UserDefinedFunctionExecutionError"] = rest_field( visibility=["read", "create", "update", "delete", "query"] ) - """The error object of a user defined function execution. This is returned only - when the user defined function execution throws an exception.""" + """The error object of a user defined function execution. This is returned only when the user + defined function execution throws an exception.""" result: Optional["_models.UserDefinedFunctionExecutionResult"] = rest_field( visibility=["read", "create", "update", "delete", "query"] ) - """The result object of a user defined function execution. This is returned only - when the user defined function executes successfully.""" + """The result object of a user defined function execution. This is returned only when the user + defined function executes successfully.""" status: Union[str, "_models.UserDefinedFunctionExecutionStatus"] = rest_field( visibility=["read", "create", "update", "delete", "query"] ) @@ -1301,20 +1268,19 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: class UserDefinedFunctionExecutionResult(_Model): - """The result object of a user defined function execution. This is returned only - when the user defined function executes successfully. + """The result object of a user defined function execution. This is returned only when the user + defined function executes successfully. :ivar return_value: String-encoded value returned by the user defined function execution. If - the - function does not return any value, this is set to an empty string. + the function does not return any value, this is set to an empty string. :vartype return_value: str """ return_value: Optional[str] = rest_field( name="returnValue", visibility=["read", "create", "update", "delete", "query"] ) - """String-encoded value returned by the user defined function execution. If the - function does not return any value, this is set to an empty string.""" + """String-encoded value returned by the user defined function execution. If the function does not + return any value, this is set to an empty string.""" @overload def __init__( @@ -1367,3 +1333,59 @@ def __init__(self, mapping: Mapping[str, Any]) -> None: def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) + + +class UserDefinedRole(_Model): + """User defined role. + + :ivar role: User defined role. Required. + :vartype role: list[~azure.confidentialledger.models.Role] + """ + + role: list["_models.Role"] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """User defined role. Required.""" + + @overload + def __init__( + self, + *, + role: list["_models.Role"], + ) -> 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 UserDefinedRoles(_Model): + """Roles. + + :ivar roles: Roles. Required. + :vartype roles: list[~azure.confidentialledger.models.Role] + """ + + roles: list["_models.Role"] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Roles. Required.""" + + @overload + def __init__( + self, + *, + roles: list["_models.Role"], + ) -> 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) diff --git a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/models/_patch.py b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/models/_patch.py index 8bcb627aa475..87676c65a8f0 100644 --- a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/models/_patch.py +++ b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/models/_patch.py @@ -7,9 +7,9 @@ Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ -from typing import List -__all__: List[str] = [] # Add all objects you want publicly available to users at this package level + +__all__: list[str] = [] # Add all objects you want publicly available to users at this package level def patch_sdk(): diff --git a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/receipt/__init__.py b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/receipt/__init__.py deleted file mode 100644 index 2b01f0c2d197..000000000000 --- a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/receipt/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# 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. -# -------------------------------------------------------------------------- - -from ._receipt_verification import verify_receipt -from ._claims_digest_computation import compute_claims_digest - -__all__ = ["verify_receipt", "compute_claims_digest"] diff --git a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/receipt/_claims_digest_computation.py b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/receipt/_claims_digest_computation.py deleted file mode 100644 index 88bf4496ef24..000000000000 --- a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/receipt/_claims_digest_computation.py +++ /dev/null @@ -1,216 +0,0 @@ -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Algorithm implementation for computing Azure Confidential Ledger application claims.""" - -from base64 import b64decode -from hashlib import sha256 -import hmac -from typing import Any, Dict, List, cast - -from azure.confidentialledger.receipt._claims_models import ( - ApplicationClaim, - LedgerEntryClaim, - ClaimDigest, -) - -LEDGER_ENTRY_CLAIM_TYPE = "LedgerEntry" -DIGEST_CLAIM_TYPE = "ClaimDigest" -LEDGER_ENTRY_V1_CLAIM_PROTOCOL = "LedgerEntryV1" - - -def compute_claims_digest(application_claims: List[Dict[str, Any]]) -> str: - """ - Compute the claims digest from a list of Azure Confidential Ledger application claims. - - :param application_claims: List of application claims to be verified against the receipt. - :type application_claims: List[Dict[str, Any]] - - :return: The claims digest of the application claims. - :rtype: str - :raises ValueError: If the claims digest computation has failed. - """ - - # The logic is structured in three distinct steps: - # 1. Claim dictionary validation - # 2. Claim conversion to object - # 3. Claims digest computation - # For every step, we iterate over the list of application claims to process them one by one. - # While it could be slightly inefficient to have three sets of iterations instead of a single one, - # the main idea is to have a clean separation of concerns between the different steps - # (e.g., not mix syntax/formatting errors with logical errors). - # From a performance perspective, it is not expected to have a large number of application - # claims in a single receipt (at the time of writing, we only support a single claim per receipt), - # so the overhead of having three iterations should be negligible. - - # Validate application claims provided by the user - _validate_application_claims(application_claims) - - # Convert application claims JSON objects to ApplicationClaim model - application_claims_obj = [] - for claim_dict in application_claims: - claim = ApplicationClaim.from_dict(claim_dict) - application_claims_obj.append(claim) - - # Compute claims digest from application claims - return _compute_claims_hexdigest(application_claims_obj) - - -def _validate_application_claims(application_claims: List[Dict[str, Any]]): - """Validate the application claims in a write transaction receipt. - - :param list[dict[str, any]] application_claims: List of application claims to be verified against the receipt. - """ - - assert isinstance(application_claims, list) - assert len(application_claims) > 0, "Application claims list cannot be empty" - - # Assert on each application claim object in the list - for application_claim_object in application_claims: - assert isinstance(application_claim_object, dict) - - # Assert on the kind of the claim - assert "kind" in application_claim_object - claim_kind = application_claim_object["kind"] - assert isinstance(claim_kind, str) - - # Assert on the ledger entry claim - if claim_kind == "LedgerEntry": - ledger_entry_claim = application_claim_object.get("ledgerEntry") - assert isinstance(ledger_entry_claim, dict) - - # Assert on the collection id - assert "collectionId" in ledger_entry_claim - assert isinstance(ledger_entry_claim["collectionId"], str) - - # Assert on the contents id - assert "contents" in ledger_entry_claim - assert isinstance(ledger_entry_claim["contents"], str) - - # Assert on the protocol - assert "protocol" in ledger_entry_claim - assert isinstance(ledger_entry_claim["protocol"], str) - - # Assert on the secret key - assert "secretKey" in ledger_entry_claim - assert isinstance(ledger_entry_claim["secretKey"], str) - - # Assert on the digest claim - elif claim_kind == "ClaimDigest": - assert "digest" in application_claim_object - digest_claim = application_claim_object["digest"] - assert isinstance(digest_claim, dict) - - # Assert on the digest value - assert "value" in digest_claim - assert isinstance(digest_claim["value"], str) - - # Assert on the protocol - assert "protocol" in digest_claim - assert isinstance(digest_claim["protocol"], str) - - else: - assert False, f"Unknown claim kind: {claim_kind}" - - -def _compute_ledger_entry_v1_claim_digest( - ledger_entry_claim: LedgerEntryClaim, -) -> bytes: - """Compute the digest of a LedgerEntryV1 claim. It returns the digest in bytes. - - :param LedgerEntryClaim ledger_entry_claim: LedgerEntry claim to be digested. - :return: The digest of the LedgerEntry claim. - :rtype: bytes - """ - - # Decode the secret key - secret_key = b64decode(ledger_entry_claim.secretKey, validate=True) - - # HMAC the collection ID with the secret key - collection_id_digest = hmac.new( - secret_key, - ledger_entry_claim.collectionId.encode(), - sha256, - ).digest() - - # HMAC the ledger contents with the secret key - contents_digest = hmac.new( - secret_key, - ledger_entry_claim.contents.encode(), - sha256, - ).digest() - - # Compute the SHA-256 of the concatenation of the collection ID and contents digests - return sha256(collection_id_digest + contents_digest).digest() - - -def _compute_ledger_entry_claim_digest(ledger_entry_claim: LedgerEntryClaim) -> bytes: - """Compute the digest of a LedgerEntry claim. It returns the digest in bytes. - - :param LedgerEntryClaim ledger_entry_claim: LedgerEntry claim to be digested. - :return: The digest of the LedgerEntry claim. - :rtype: bytes - """ - - claim_protocol = ledger_entry_claim.protocol - - # Compute the digest based on the specified protocol - if claim_protocol == LEDGER_ENTRY_V1_CLAIM_PROTOCOL: - # Compute the digest of the LedgerEntryV1 claim - ledger_entry_digest = _compute_ledger_entry_v1_claim_digest(ledger_entry_claim) - - else: - raise ValueError(f"Unsupported claim protocol: {claim_protocol}") - - # Compute the SHA-256 of the concatenation of the protocol and the ledger entry digest - return sha256(claim_protocol.encode() + ledger_entry_digest).digest() - - -def _compute_claim_digest_from_object(claim_digest_object: ClaimDigest) -> bytes: - # Compute the SHA-256 of the concatenation of the protocol and the digest value - return sha256(claim_digest_object.protocol.encode() + bytes.fromhex(claim_digest_object.value)).digest() - - -def _compute_claims_hexdigest(application_claims_list: List[ApplicationClaim]) -> str: - """Compute the CCF claims digest from the provided list of application claims objects. - It returns the hexdigest of the claims digest. - - :param list[ApplicationClaim] application_claims_list: List of application claims to be digested. - :return: The hexdigest of the claims digest. - :rtype: str - """ - - # Initialize the claims digest - claims_digests_concatenation = b"" - - # Iterate through all the application claims objects to compute their single digest. - # We assume that the order of the application objects is valid - # and the digests will be concatenated in the same order. - for application_claim_object in application_claims_list: - # Get the kind of the claim - claim_kind = application_claim_object.kind - - if claim_kind == LEDGER_ENTRY_CLAIM_TYPE: - # Compute the digest of the LedgerEntry claim - claim_digest = _compute_ledger_entry_claim_digest( - cast(LedgerEntryClaim, application_claim_object.ledgerEntry) - ) - - elif claim_kind == DIGEST_CLAIM_TYPE: - # Compute the digest of the ClaimDigest claim - claim_digest = _compute_claim_digest_from_object(cast(ClaimDigest, application_claim_object.digest)) - - else: - raise ValueError(f"Unsupported claim kind: {claim_kind}") - - # Append the computed digest to the result - claims_digests_concatenation += claim_digest - - # Prepend the size of application claims to the concatenation of the digests - claims_digests_concatenation = ( - len(application_claims_list).to_bytes(length=4, byteorder="little") + claims_digests_concatenation - ) - - # Hash the concatenation of application claims and return the digest in hexadecimal form - return sha256(claims_digests_concatenation).hexdigest() diff --git a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/receipt/_claims_models.py b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/receipt/_claims_models.py deleted file mode 100644 index 9859688f266b..000000000000 --- a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/receipt/_claims_models.py +++ /dev/null @@ -1,138 +0,0 @@ -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Models for application claims.""" - -from typing import Any, Dict, Optional, Union -from dataclasses import dataclass - - -@dataclass -class LedgerEntryClaim: - """ - LedgerEntryClaim represents an Application Claim derived from ledger entry data. - - :keyword protocol: The protocol used to compute the claim. - :paramtype protocol: str - - :keyword collectionId: The collection ID of the ledger entry. - :paramtype collectionId: str - - :keyword contents: The contents of the ledger entry. - :paramtype contents: str - - :keyword secretKey: The secret key used to compute the claim digest. - :paramtype secretKey: str - """ - - protocol: str - collectionId: str - contents: str - secretKey: str - - @classmethod - def from_dict(cls, ledger_entry_claim_dict: Dict[str, Any]): - """Create a new instance of this class from a dictionary. - - :param dict[str, any] ledger_entry_claim_dict: The dictionary representation of the ledger entry claim. - :return: A new instance of this class corresponding to the provided dictionary. - :rtype: LedgerEntryClaim - """ - - return cls(**ledger_entry_claim_dict) - - -@dataclass -class ClaimDigest: - """ - ClaimDigest represents an Application Claim in digested form. - - :keyword protocol: The protocol used to compute the claim. - :paramtype protocol: str - - :keyword value: The digest of the claim. - :paramtype value: str - """ - - protocol: str - value: str - - @classmethod - def from_dict(cls, ledger_entry_claim_dict: Dict[str, Any]): - """Create a new instance of this class from a dictionary. - - :param dict[str, any] ledger_entry_claim_dict: The dictionary representation of the claim digest. - :return: A new instance of this class corresponding to the provided dictionary. - :rtype: ClaimDigest - """ - - return cls(**ledger_entry_claim_dict) - - -@dataclass -class ApplicationClaim: - """ - ApplicationClaim represents a claim of a ledger application. - - :keyword kind: The kind of the claim. - :paramtype kind: str - - :keyword ledgerEntry: The ledger entry claim. - :paramtype ledgerEntry: Optional[Union[Dict[str, Any], LedgerEntryClaim]] - - :keyword digest: The claim digest object. - :paramtype digest: Optional[Union[Dict[str, Any], ClaimDigest]] - """ - - kind: str - ledgerEntry: Optional[LedgerEntryClaim] = None - digest: Optional[ClaimDigest] = None - - def __init__( - self, - kind: str, - ledgerEntry: Optional[Union[Dict[str, Any], LedgerEntryClaim]] = None, - digest: Optional[Union[Dict[str, Any], ClaimDigest]] = None, - **kwargs: Any - ): - """ - :keyword kind: The kind of the claim. - :paramtype kind: str - - :keyword ledgerEntry: The ledger entry claim. - :paramtype ledgerEntry: Optional[Union[Dict[str, Any], LedgerEntryClaim]] - - :keyword digest: The claim digest object. - :paramtype digest: Optional[Union[Dict[str, Any], ClaimDigest]] - """ - self.kind = kind - - if ledgerEntry: - if isinstance(ledgerEntry, LedgerEntryClaim): - self.ledgerEntry = ledgerEntry - else: - self.ledgerEntry = LedgerEntryClaim.from_dict(ledgerEntry) - else: - self.ledgerEntry = None - - if digest: - if isinstance(digest, ClaimDigest): - self.digest = digest - else: - self.digest = ClaimDigest.from_dict(digest) - else: - self.digest = None - - self.kwargs = kwargs - - @classmethod - def from_dict(cls, claim_dict: Dict[str, Any]): - """Create a new instance of this class from a dictionary. - - :param dict[str, any] claim_dict: The dictionary representation of the application claim. - :return: A new instance of this class corresponding to the provided dictionary. - :rtype: ApplicationClaim - """ - - return cls(**claim_dict) diff --git a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/receipt/_receipt_models.py b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/receipt/_receipt_models.py deleted file mode 100644 index a63d75b89f24..000000000000 --- a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/receipt/_receipt_models.py +++ /dev/null @@ -1,182 +0,0 @@ -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Models for receipt verification.""" - -from typing import Any, Dict, List, Optional, Union - -from dataclasses import dataclass - - -@dataclass -class ProofElement: - """ProofElement represents the object contained in the `proof` list field - of an Azure Confidential Ledger write transaction receipt. The `proof` list - contains the hashes of the nodes in the Merkle Tree and their relative - position with respect to the parent node (`left` or `right`); the given - information allow the re-computation of the root node from a given leaf - node. - - Each ProofElement should contain either the `left` or the `right` field, - but not both or none of them. - - :keyword left: Hash of a left node in the Merkle Tree, as an hexadecimal string. - :paramtype left: Optional[str] - - :keyword right: Hash of a right node in the Merkle Tree, as an hexadecimal string. - :paramtype right: Optional[str] - """ - - left: Optional[str] = None - right: Optional[str] = None - - @classmethod - def from_dict(cls, proof_dict: Dict[str, Any]): - """Create a new instance of this class from a dictionary. - - :param dict[str, any] proof_dict: The dictionary representation of the proof element. - :return: A new instance of this class corresponding to the provided dictionary. - :rtype: ProofElement - """ - return cls(**proof_dict) - - -@dataclass -class LeafComponents: - """LeafComponents represents the object contained in the `leafComponents` - field of an Azure Confidential Ledger write transaction receipt. The - `leafComponents` field contains the elements that are hashed to compute - the leaf node corresponding to the transaction associated to the given - receipt. - - :keyword claimsDigest: Hexadecimal string representing the digest of - the application claim attached by the Azure Confidential Ledger application. - :paramtype claimsDigest: str - - :keyword commitEvidence: A unique string that identifies a transaction / commit, - derived from the transaction ID and the secrets used by the Azure Confidential Ledger. - :paramtype commitEvidence: str - - :keyword writeSetDigest: Hexadecimal string representing the digest of the keys - and values written during a transaction, that captures the state of the - ledger at the time the transaction was committed. - :paramtype writeSetDigest: str - """ - - claimsDigest: str - commitEvidence: str - writeSetDigest: str - - @classmethod - def from_dict(cls, leaf_components_dict: Dict[str, Any]): - """Create a new instance of this class from a dictionary. - - :param dict[str, any] leaf_components_dict: The dictionary representation of the leaf components. - :return: A new instance of this class corresponding to the provided dictionary. - :rtype: LeafComponents - """ - return cls(**leaf_components_dict) - - -@dataclass -class Receipt: - """Receipt represents the object contained in the `receipt` field of an - Azure Confidential Ledger `get_receipt` response. A - receipt is a cryptographic proof that a transaction has been committed to - the ledger: it can be used to verify that the ledger entry associated to a - transaction has been appended to the ledger (thus, it can be used to - validate properties such as non-repudiation, integrity, and tamper- - proofing). A receipt contains all the information needed to verify - transaction inclusion and the verification can be done by applying an ad- - hoc algorithm. - - :keyword cert: PEM-encoded certificate string of the CCF node that signed the transaction. - :paramtype cert: str - - :keyword leafComponents: Components of the leaf node in the Merkle Tree associated to - the committed transaction. - :paramtype leafComponents: LeafComponents - - :keyword proof: List of nodes' hashes to be used to re-compute the root of the Merkle Tree, - together with the leaf node hash, by iteratively concatenating and hashing the given values. - :paramtype proof: List[ProofElement] - - :keyword signature: Base64 string representing the signature of the root of the Merkle Tree at - the given transaction. - :paramtype signature: str - - :keyword nodeId: Hexadecimal string representing the digest of the public key of the - CCF node that signed the transaction. - :paramtype nodeId: Optional[str] - - :keyword serviceEndorsements: List of PEM-encoded certificates strings representing - previous service identities. - :paramtype serviceEndorsements: Optional[List[str]] - """ - - cert: str - leafComponents: LeafComponents - proof: List[ProofElement] - signature: str - nodeId: Optional[str] = None - serviceEndorsements: Optional[List[str]] = None - - def __init__( # pylint: disable=dangerous-default-value - self, - cert: str, - leafComponents: Union[Dict[str, Any], LeafComponents], - proof: List[Union[Dict[str, Any], ProofElement]], - signature: str, - nodeId: Optional[str] = None, - serviceEndorsements: Optional[List[str]] = None, - **kwargs: Any - ): - """ - :keyword cert: PEM-encoded certificate string of the CCF node that signed the transaction. - :paramtype cert: str - - :keyword leafComponents: Components of the leaf node in the Merkle Tree associated to - the committed transaction. - :paramtype leafComponents: Dict[str, Any] - - :keyword proof: List of nodes' hashes to be used to re-compute the root of the Merkle Tree, - together with the leaf node hash, by iteratively concatenating and hashing the given values. - :paramtype proof: List[Dict[str, Any]] - - :keyword signature: Base64 string representing the signature of the root of the Merkle Tree at - the given transaction. - :paramtype signature: str - - :keyword nodeId: Hexadecimal string representing the digest of the public key of the - CCF node that signed the transaction. - :paramtype nodeId: Optional[str] - - :keyword serviceEndorsements: List of PEM-encoded certificates strings representing - previous service identities. - :paramtype serviceEndorsements: Optional[List[str]] - """ - self.cert = cert - self.nodeId = nodeId - self.serviceEndorsements = serviceEndorsements - self.signature = signature - - if isinstance(leafComponents, LeafComponents): - self.leafComponents = leafComponents - else: - self.leafComponents = LeafComponents.from_dict(leafComponents) - - self.proof = [elem if isinstance(elem, ProofElement) else ProofElement.from_dict(elem) for elem in proof] - - self.kwargs = kwargs - - @classmethod - def from_dict(cls, receipt_dict: Dict[str, Any]): - """Create a new instance of this class from a dictionary. - - :param dict[str, any] receipt_dict: The dictionary representation of the receipt. - :return: A new instance of this class corresponding to the provided dictionary. - :rtype: Receipt - """ - - return cls(**receipt_dict) diff --git a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/receipt/_receipt_verification.py b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/receipt/_receipt_verification.py deleted file mode 100644 index e1111e71b9d1..000000000000 --- a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/receipt/_receipt_verification.py +++ /dev/null @@ -1,386 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Algorithm implementation for verifying Azure Confidential Ledger write -transaction receipts.""" - -from base64 import b64decode -from hashlib import sha256 -from typing import Dict, List, Any, cast, Optional - -from cryptography.x509 import load_pem_x509_certificate, Certificate -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric import ec, utils -from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat - -from azure.confidentialledger.receipt._receipt_models import ( - LeafComponents, - ProofElement, - Receipt, -) - -from azure.confidentialledger.receipt._utils import ( - _convert_dict_to_camel_case, -) - -from azure.confidentialledger.receipt._claims_digest_computation import ( - compute_claims_digest, -) - - -def verify_receipt( - receipt: Dict[str, Any], - service_cert: str, - *, - application_claims: Optional[List[Dict[str, Any]]] = None, -) -> None: - """Verify that a given Azure Confidential Ledger write transaction receipt - is valid from its content and the Confidential Ledger service identity - certificate. - - :param receipt: Receipt dictionary containing the content of an Azure - Confidential Ledger write transaction receipt. - :type receipt: Dict[str, Any] - - :param service_cert: String containing the PEM-encoded - certificate of the Confidential Ledger service identity. - :type service_cert: str - - :keyword application_claims: List of application claims to be verified against the receipt. - :paramtype application_claims: Optional[List[Dict[str, Any]]] - - :raises ValueError: If the receipt verification has failed. - """ - - # Validate receipt content and convert it into a Receipt model - receipt_obj = _preprocess_input_receipt(receipt) - - # Validate application claims provided by the user, if any - if application_claims: - computed_claims_digest = compute_claims_digest(application_claims) - if computed_claims_digest != receipt_obj.leafComponents.claimsDigest: - raise ValueError( - "The computed claims digest from application claims does not match the receipt claims digest." - ) - - # Load node PEM certificate - node_cert = _load_and_verify_pem_certificate(receipt_obj.cert) - - # Verify node certificate is endorsed by the service certificate - # through endorsements certificates - _verify_node_cert_endorsed_by_service_cert( - node_cert, service_cert, cast(List[str], receipt_obj.serviceEndorsements) - ) - - # Compute hash of the leaf node in the Merkle Tree corresponding - # to the transaction associated to the given receipt - leaf_node_hash = _compute_leaf_node_hash(receipt_obj.leafComponents) - - # Compute root of the Merkle Tree at the time the transaction was committed - root_node_hash = _compute_root_node_hash(leaf_node_hash, receipt_obj.proof) - - # Verify signature of the signing node over the root of the tree with - # node certificate public key - _verify_signature_over_root_node_hash( - receipt_obj.signature, node_cert, cast(str, receipt_obj.nodeId), root_node_hash - ) - - -def _preprocess_input_receipt(receipt_dict: Dict[str, Any]) -> Receipt: - """Preprocess input receipt dictionary, validate its content, and returns a - valid Receipt object based on the vetted input data. - - :param dict[str, any] receipt_dict: Receipt dictionary - :return: Receipt object - :rtype: Receipt - """ - - # Convert any key in the receipt dictionary to camel case - # to match the model fields (we do this because customers may - # provide receipts with snake case keys since they were returned - # by older ACL instances) - receipt_dict = _convert_dict_to_camel_case(receipt_dict) - - _validate_receipt_content(receipt_dict) - - # Convert receipt JSON object to Receipt model - return Receipt.from_dict(receipt_dict) - - -def _validate_receipt_content(receipt: Dict[str, Any]): - """Validate the content of a write transaction receipt. - - :param dict[str, any] receipt: Receipt dictionary - """ - - def _check(condition: bool) -> None: - if not condition: - raise ValueError("The receipt content is invalid.") - - try: - _check("cert" in receipt) - _check(isinstance(receipt["cert"], str)) - - _check("leafComponents" in receipt) - _check(isinstance(receipt["leafComponents"], dict)) - - _check("claimsDigest" in receipt["leafComponents"]) - _check(isinstance(receipt["leafComponents"]["claimsDigest"], str)) - - _check("commitEvidence" in receipt["leafComponents"]) - _check(isinstance(receipt["leafComponents"]["commitEvidence"], str)) - - _check("writeSetDigest" in receipt["leafComponents"]) - _check(isinstance(receipt["leafComponents"]["writeSetDigest"], str)) - - _check("proof" in receipt) - _check(isinstance(receipt["proof"], list)) - - # Validate elements in proof - for elem in receipt["proof"]: - _check("left" in elem or "right" in elem) - if "left" in elem: - _check(isinstance(elem["left"], str)) - if "right" in elem: - _check(isinstance(elem["right"], str)) - - _check("signature" in receipt) - _check(isinstance(receipt["signature"], str)) - - # Validate nodeId, if present - if "nodeId" in receipt: - _check(isinstance(receipt["nodeId"], str)) - - # Validate serviceEndorsements, if present - if "serviceEndorsements" in receipt: - _check(isinstance(receipt["serviceEndorsements"], list)) - - # Validate elements in serviceEndorsements - for elem in receipt["serviceEndorsements"]: - _check(isinstance(elem, str)) - - except ValueError: - raise - except Exception as exception: - raise ValueError("The receipt content is invalid.") from exception - - -def _verify_signature_over_root_node_hash( - signature: str, node_cert: Certificate, node_id: str, root_node_hash: bytes -) -> None: - """Verify signature over root node hash of the Merkle Tree using node - certificate public key. - - :param str signature: Signature - :param Certificate node_cert: Node certificate - :param str node_id: Node ID - :param bytes root_node_hash: Root node hash - """ - - try: - # Verify public key contained in the node certificate is equal to the node_id - public_key_bytes = node_cert.public_key().public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo) - - if node_id is not None: - if sha256(public_key_bytes).digest() != bytes.fromhex(node_id): - raise ValueError( - "Node certificate public key hash does not match the receipt nodeId." - ) - - # Verify signature over root node hash using node certificate public key - _verify_ec_signature( - node_cert, - b64decode(signature, validate=True), - root_node_hash, - hashes.SHA256(), - ) - - except Exception as exception: - raise ValueError( - f"Encountered exception when verifying signature {signature} over root node hash." - ) from exception - - -def _compute_leaf_node_hash(leaf_components: LeafComponents) -> bytes: - """Compute the hash of the leaf node associated to a transaction given the - leaf components from a write transaction receipt. - - :param LeafComponents leaf_components: Leaf components - :return: Leaf node hash - :rtype: bytes - """ - - try: - # Digest commit evidence string - commit_evidence_digest = sha256(leaf_components.commitEvidence.encode()).digest() - - # Convert write set digest to bytes - write_set_digest = bytes.fromhex(leaf_components.writeSetDigest) - - # Convert claims digest to bytes - claims_digest = bytes.fromhex(leaf_components.claimsDigest) - - # Create leaf node hash by hashing the concatenation of its three components - # as bytes objects in the following order: - # 1. write_set_digest - # 2. commit_evidence_digest - # 3. claims_digest - return sha256(write_set_digest + commit_evidence_digest + claims_digest).digest() - - except Exception as exception: - raise ValueError( - f"Encountered exception when computing leaf node hash from leaf components {leaf_components}." - ) from exception - - -def _compute_root_node_hash(leaf_hash: bytes, proof: List[ProofElement]) -> bytes: - """Re-compute the hash of the root of the Merkle tree from a leaf node hash - and a receipt proof list containing the required nodes hashes for the - computation. - - :param bytes leaf_hash: Leaf node hash - :param list[ProofElement] proof: Receipt proof list - :return: Root node hash - :rtype: bytes - """ - - try: - # Initialize current hash to leaf hash - current_node_hash = leaf_hash - - # Iterate through all the elements in proof list - for element in proof: - # Check that the current element only contains either one left or right node hash - if ( - element is None - or (element.left is None and element.right is None) - or (element.left is not None and element.right is not None) - ): - raise ValueError( - "Invalid proof element in receipt: element must contain either one left or right node hash." - ) - - parent_node_hash = b"" - - # If the current element contains a left hash, concatenate the left hash and the current node hash - if element.left is not None: - parent_node_hash = bytes.fromhex(element.left) + current_node_hash - - # If the current element contains a right hash, concatenate the current node hash and the right hash - if element.right is not None: - parent_node_hash = current_node_hash + bytes.fromhex(element.right) - - # Hash the parent node hash - current_node_hash = sha256(parent_node_hash).digest() - - return current_node_hash - - except Exception as exception: - raise ValueError(f"Encountered exception when computing root node hash from proof list {proof}.") from exception - - -def _verify_certificate_endorsement(endorsee: Certificate, endorser: Certificate) -> None: - """Verify that the endorser certificate has endorsed endorsee - certificate using ECDSA. - - :param Certificate endorsee: Endorsee certificate - :param Certificate endorser: Endorser certificate - """ - - try: - # Extract TBS certificate hash from endorsee certificate - hash_algorithm = cast(hashes.HashAlgorithm, endorsee.signature_hash_algorithm) - digester = hashes.Hash(hash_algorithm) - digester.update(endorsee.tbs_certificate_bytes) - cert_digest = digester.finalize() - - # Verify endorser signature over endorsee certificate digest - _verify_ec_signature(endorser, endorsee.signature, cert_digest, hash_algorithm) - - except Exception as exception: - raise ValueError( - f"Encountered exception when verifying endorsement of certificate {endorsee} by certificate {endorser}." - ) from exception - - -def _verify_ec_signature( - certificate: Certificate, - signature: bytes, - data: bytes, - hash_algorithm: hashes.HashAlgorithm, -) -> None: - """Verify a signature over data using the certificate public key. - - :param Certificate certificate: Certificate - :param bytes signature: Signature - :param bytes data: Data - :param hashes.HashAlgorithm hash_algorithm: Hash algorithm - """ - - public_key = cast(ec.EllipticCurvePublicKey, certificate.public_key()) - - public_key.verify( - signature, - data, - ec.ECDSA(utils.Prehashed(hash_algorithm)), - ) - - -def _verify_node_cert_endorsed_by_service_cert( - node_cert: Certificate, service_cert_str: str, endorsements_certs: List[str] -) -> None: - """Check a node certificate is endorsed by a service certificate. - - If a list of endorsements certificates is not empty, check that the - node certificate is transitively endorsed by the service certificate - through the endorsement certificates in the list. - - :param Certificate node_cert: Node certificate - :param str service_cert_str: Service certificate string - :param list[str] endorsements_certs: Endorsements certificates list - """ - - current_cert = node_cert - - # Validate endorsement certificates list is present - if endorsements_certs is None: - endorsements_certs = [] - - # Add service certificate to the list of endorsements certificates - endorsements_certs.append(service_cert_str) - - # Iterate through all the endorsements certificates - for endorsement in endorsements_certs: - # Load endorsement PEM certificate - endorsement_cert = _load_and_verify_pem_certificate(endorsement) - - # Verify endorsement certificate has endorsed current certificate - _verify_certificate_endorsement(current_cert, endorsement_cert) - - # Set current certificate to endorsement certificate to continue the chain verification - current_cert = endorsement_cert - - -def _load_and_verify_pem_certificate(cert_str: str) -> Certificate: - """Load PEM certificate from a string representation and verify it is a - valid certificate with expected Elliptic Curve public key. - - :param str cert_str: PEM certificate string - :return: Certificate - :rtype: Certificate - """ - - try: - # Load certificate from string - cert = load_pem_x509_certificate(cert_str.encode()) - - # Verify public key is of the correct type - if not isinstance(cert.public_key(), ec.EllipticCurvePublicKey): - raise ValueError(f"PEM certificate {cert_str} does not contain an EC public key.") - - return cert - - except Exception as exception: - raise ValueError(f"PEM certificate {cert_str} is not valid.") from exception diff --git a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/receipt/_utils.py b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/receipt/_utils.py deleted file mode 100644 index bb83d1f22541..000000000000 --- a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/receipt/_utils.py +++ /dev/null @@ -1,55 +0,0 @@ -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Utils file for receipt verification.""" - -from typing import Dict, Any - - -def _to_camel_case(string: str) -> str: - """Convert a string to camel case. - - :param str string: The string to convert to camel case. - :return: The string converted to camel case. - :rtype: str - """ - - # Split the string by underscore - components = string.split("_") - - # Capitalize the first letter of each component except the first one - # with the 'title' method and join them together - return components[0] + "".join(elem.title() for elem in components[1:]) - - -def _convert_dict_to_camel_case(dictionary: Dict[str, Any]) -> Dict[str, Any]: - """Convert dictionary keys to camel case recursively. - - :param dict[str, any] dictionary: The dictionary to convert to camel case. - :return: The dictionary with keys converted to camel case. - :rtype: dict[str, any] - """ - - new_dictionary: Dict[str, Any] = {} - - # Iterate through all the keys in the dictionary - for key, value in dictionary.items(): - # Convert the key to camel case - camel_case_key = _to_camel_case(key) - - # If the value is a dictionary, apply algorithm recursively - if isinstance(value, dict): - new_dictionary[camel_case_key] = _convert_dict_to_camel_case(value) - - # If the value is a list, apply algorithm recursively to each element - elif isinstance(value, list): - new_dictionary[camel_case_key] = [ - _convert_dict_to_camel_case(elem) if isinstance(elem, dict) else elem for elem in value - ] - - # Otherwise, add the key and value to the new dictionary - else: - new_dictionary[camel_case_key] = value - - return new_dictionary diff --git a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/receipt/py.typed b/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/receipt/py.typed deleted file mode 100644 index 1242d4327701..000000000000 --- a/sdk/confidentialledger/azure-confidentialledger/azure/confidentialledger/receipt/py.typed +++ /dev/null @@ -1 +0,0 @@ -# Marker file for PEP 561. diff --git a/sdk/confidentialledger/azure-confidentialledger/pyproject.toml b/sdk/confidentialledger/azure-confidentialledger/pyproject.toml index 90bafd3f2522..01956b5c104b 100644 --- a/sdk/confidentialledger/azure-confidentialledger/pyproject.toml +++ b/sdk/confidentialledger/azure-confidentialledger/pyproject.toml @@ -1,18 +1,14 @@ -# -------------------------------------------------------------------------- -# 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. -# -------------------------------------------------------------------------- - [build-system] -requires = ["setuptools>=77.0.3", "wheel"] +requires = [ + "setuptools>=77.0.3", + "wheel", +] build-backend = "setuptools.build_meta" [project] name = "azure-confidentialledger" authors = [ - { name = "Microsoft Corporation", email = "azpysdkhelp@microsoft.com" }, + { name = "Microsoft Corporation", email = "azpysdkhelp@microsoft.com" }, ] description = "Microsoft Corporation Azure Confidential Ledger Client Library for Python" license = "MIT" @@ -21,48 +17,68 @@ classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", +] +requires-python = ">=3.10" +keywords = [ + "azure", + "azure sdk", ] -requires-python = ">=3.9" -keywords = ["azure", "azure sdk"] - dependencies = [ "isodate>=0.6.1", - "azure-core>=1.35.0", + "azure-core>=1.37.0", "typing-extensions>=4.6.0", "cryptography>=2.1.4", "azure-confidentialledger-certificate>=1.0.0b1", ] dynamic = [ -"version", "readme" + "version", + "readme", ] [project.urls] -Homepage = "https://github.com/Azure/azure-sdk-for-python" -"Bug Reports" = "https://github.com/Azure/azure-sdk-for-python/issues" -Source = "https://github.com/Azure/azure-sdk-for-python" +repository = "https://github.com/Azure/azure-sdk-for-python" -[tool.setuptools.dynamic] -version = {attr = "azure.confidentialledger._version.VERSION"} -readme = {file = ["README.md", "CHANGELOG.md"], content-type = "text/markdown"} +[tool.setuptools.dynamic.version] +attr = "azure.confidentialledger._version.VERSION" + +[tool.setuptools.dynamic.readme] +file = [ + "README.md", + "CHANGELOG.md", +] +content-type = "text/markdown" [tool.setuptools.packages.find] exclude = [ "tests*", + "generated_tests*", "samples*", + "generated_samples*", "doc*", "azure", ] [tool.setuptools.package-data] -pytyped = ["py.typed"] +pytyped = [ + "py.typed", +] [tool.azure-sdk-build] pyright = false [tool.azure-sdk-conda] in_bundle = false + +[packaging] +package_name = "azure-confidentialledger" +package_pprint_name = "Azure Confidential Ledger" +package_doc_id = "" +is_stable = false +is_arm = false +need_msrestazure = false +auto_update = false diff --git a/sdk/confidentialledger/azure-confidentialledger/sdk_packaging.toml b/sdk/confidentialledger/azure-confidentialledger/sdk_packaging.toml deleted file mode 100644 index 04b9b55da3e4..000000000000 --- a/sdk/confidentialledger/azure-confidentialledger/sdk_packaging.toml +++ /dev/null @@ -1,8 +0,0 @@ -[packaging] -package_name = "azure-confidentialledger" -package_pprint_name = "Azure Confidential Ledger" -package_doc_id = "" -is_stable = false -is_arm = false -need_msrestazure = false -auto_update = false \ No newline at end of file diff --git a/sdk/confidentialledger/azure-confidentialledger/tests/test_client_configuration.py b/sdk/confidentialledger/azure-confidentialledger/tests/test_client_configuration.py index 47459e6733dd..5766def0eb6c 100644 --- a/sdk/confidentialledger/azure-confidentialledger/tests/test_client_configuration.py +++ b/sdk/confidentialledger/azure-confidentialledger/tests/test_client_configuration.py @@ -28,9 +28,7 @@ def test_sensitive_header_cleanup_policy_disable_redirect_cleanup_enabled(self): # Create the generated client directly - this will trigger policy creation # The generated client only requires ledger_endpoint - client = GeneratedClient( - ledger_endpoint="https://test-ledger.confidentialledger.azure.com" - ) + client = GeneratedClient(ledger_endpoint="https://test-ledger.confidentialledger.azure.com") # Get the policies argument passed to PipelineClient call_args = mock_pipeline_client.call_args @@ -44,9 +42,9 @@ def test_sensitive_header_cleanup_policy_disable_redirect_cleanup_enabled(self): break # Assert the policy exists and has disable_redirect_cleanup=True - assert sensitive_header_policy is not None, ( - "SensitiveHeaderCleanupPolicy should be present in the client's policies" - ) + assert ( + sensitive_header_policy is not None + ), "SensitiveHeaderCleanupPolicy should be present in the client's policies" assert sensitive_header_policy._disable_redirect_cleanup is True, ( "SensitiveHeaderCleanupPolicy should have disable_redirect_cleanup=True " "to preserve authentication headers on Confidential Ledger redirects" @@ -63,9 +61,7 @@ def test_sensitive_header_cleanup_policy_is_in_correct_position(self): with patch("azure.confidentialledger._client.PipelineClient") as mock_pipeline_client: mock_pipeline_client.return_value = MagicMock() - client = GeneratedClient( - ledger_endpoint="https://test-ledger.confidentialledger.azure.com" - ) + client = GeneratedClient(ledger_endpoint="https://test-ledger.confidentialledger.azure.com") # Get the policies argument passed to PipelineClient call_args = mock_pipeline_client.call_args @@ -85,14 +81,10 @@ def test_sensitive_header_cleanup_policy_is_in_correct_position(self): distributed_tracing_idx = idx # SensitiveHeaderCleanupPolicy should come after DistributedTracingPolicy - assert sensitive_header_idx is not None, ( - "SensitiveHeaderCleanupPolicy should be present in the policies" - ) - assert distributed_tracing_idx is not None, ( - "DistributedTracingPolicy should be present in the policies" - ) - assert sensitive_header_idx > distributed_tracing_idx, ( - "SensitiveHeaderCleanupPolicy should be positioned after DistributedTracingPolicy" - ) + assert sensitive_header_idx is not None, "SensitiveHeaderCleanupPolicy should be present in the policies" + assert distributed_tracing_idx is not None, "DistributedTracingPolicy should be present in the policies" + assert ( + sensitive_header_idx > distributed_tracing_idx + ), "SensitiveHeaderCleanupPolicy should be positioned after DistributedTracingPolicy" client.close() diff --git a/sdk/confidentialledger/azure-confidentialledger/tests/test_redirect_caching_policy.py b/sdk/confidentialledger/azure-confidentialledger/tests/test_redirect_caching_policy.py index b62b6707980f..e4dad63b3b88 100644 --- a/sdk/confidentialledger/azure-confidentialledger/tests/test_redirect_caching_policy.py +++ b/sdk/confidentialledger/azure-confidentialledger/tests/test_redirect_caching_policy.py @@ -320,8 +320,7 @@ def test_disallowed_redirect_emits_warning_log(self, caplog): warnings = [r for r in caplog.records if r.levelno == logging.WARNING] assert any( - "Refusing to follow redirect to disallowed target" in r.message - and UNRELATED_URL in r.message + "Refusing to follow redirect to disallowed target" in r.message and UNRELATED_URL in r.message for r in warnings ), f"Expected a block warning for {UNRELATED_URL}, got: {[r.message for r in warnings]}" @@ -453,7 +452,6 @@ async def test_disallowed_redirect_emits_warning_log(self, caplog): warnings = [r for r in caplog.records if r.levelno == logging.WARNING] assert any( - "Refusing to follow redirect to disallowed target" in r.message - and UNRELATED_URL in r.message + "Refusing to follow redirect to disallowed target" in r.message and UNRELATED_URL in r.message for r in warnings ), f"Expected a block warning for {UNRELATED_URL}, got: {[r.message for r in warnings]}" diff --git a/sdk/confidentialledger/azure-confidentialledger/tsp-location.yaml b/sdk/confidentialledger/azure-confidentialledger/tsp-location.yaml index 1571c3088a86..85a542b9fdad 100644 --- a/sdk/confidentialledger/azure-confidentialledger/tsp-location.yaml +++ b/sdk/confidentialledger/azure-confidentialledger/tsp-location.yaml @@ -1,4 +1,4 @@ -directory: specification/confidentialledger/Microsoft.ConfidentialLedger/Ledger -commit: cccd9de9aa977d28bee3f8bfa028c8b34b3db78c +directory: specification/confidentialledger/data-plane/ConfidentialLedger +commit: 8a3262d0e5c399e19b4e489368643a0e5b7f16e3 repo: Azure/azure-rest-api-specs -additionalDirectories: [] +additionalDirectories: