Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# ------------------------------------
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# ------------------------------------
import functools
from typing import TYPE_CHECKING

from azure.core.exceptions import DecodeError, ResourceExistsError, ResourceNotFoundError
from azure.core.pipeline.policies import ContentDecodePolicy

if TYPE_CHECKING:
# pylint:disable=unused-import,ungrouped-imports
from typing import Type
from azure.core.exceptions import AzureError
from azure.core.pipeline.transport import HttpResponse


def get_exception_for_key_vault_error(cls, response):
# type: (Type[AzureError], HttpResponse) -> AzureError
try:
body = ContentDecodePolicy.deserialize_from_http_generics(response)
message = "({}) {}".format(body["error"]["code"], body["error"]["message"])
except (DecodeError, KeyError):
# Key Vault error response bodies have the expected shape and should be deserializable.
# If we somehow land here, we'll take HttpResponse's default message.
message = None

return cls(message=message, response=response)


_code_to_core_error = {404: ResourceNotFoundError, 409: ResourceExistsError}

# map status codes to callables returning appropriate azure-core errors
error_map = {
status_code: functools.partial(get_exception_for_key_vault_error, cls)
for status_code, cls in _code_to_core_error.items()
}
72 changes: 47 additions & 25 deletions sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/aio/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,21 @@
from datetime import datetime
from typing import Any, AsyncIterable, Optional, Dict, List, Union

from azure.core.exceptions import ResourceExistsError, ResourceNotFoundError
from azure.core.tracing.decorator import distributed_trace
from azure.core.tracing.decorator_async import distributed_trace_async
from azure.keyvault.keys.models import DeletedKey, JsonWebKey, Key, KeyBase
from azure.keyvault.keys._shared import AsyncKeyVaultClientBase

from .._shared.exceptions import error_map
from ..crypto.aio import CryptographyClient


class KeyClient(AsyncKeyVaultClientBase):
"""A high-level asynchronous interface for managing a vault's keys.

:param str vault_url: URL of the vault the client will access
:param credential: An object which can provide an access token for the vault, such as a credential from
:mod:`azure.identity.aio`
:param str vault_url: URL of the vault the client will access

Example:
.. literalinclude:: ../tests/test_samples_keys_async.py
Expand All @@ -33,6 +33,18 @@ class KeyClient(AsyncKeyVaultClientBase):
# pylint:disable=protected-access

def get_cryptography_client(self, key: Union[Key, str], **kwargs: Any) -> CryptographyClient:
"""
Get a :class:`~azure.keyvault.keys.crypto.aio.CryptographyClient` capable of performing cryptographic operations
with a key.

:param key:
Either a :class:`~azure.keyvault.keys.Key` instance as returned by
:func:`~azure.keyvault.keys.aio.KeyClient.get_key`, or a string. If a string, the value must be the full
identifier of an Azure Key Vault key with a version.
:type key: str or :class:`~azure.keyvault.keys.Key`
:rtype: :class:`~azure.keyvault.keys.crypto.aio.CryptographyClient`
"""

# the initializer requires a credential but won't actually use it in this case because we pass in this
# KeyClient's generated client, whose pipeline (and auth policy) is fully configured
credential = object()
Expand Down Expand Up @@ -69,6 +81,7 @@ async def create_key(
:type curve: ~azure.keyvault.keys.enums.KeyCurveName or str
:returns: The created key
:rtype: ~azure.keyvault.keys.models.Key
:raises: :class:`~azure.core.exceptions.HttpResponseError`

Example:
.. literalinclude:: ../tests/test_samples_keys_async.py
Expand Down Expand Up @@ -123,6 +136,7 @@ async def create_rsa_key(
:param dict tags: (optional) Application specific metadata in the form of key-value pairs
:returns: The created key
:rtype: ~azure.keyvault.keys.models.Key
:raises: :class:`~azure.core.exceptions.HttpResponseError`

Example:
.. literalinclude:: ../tests/test_samples_keys_async.py
Expand Down Expand Up @@ -174,6 +188,7 @@ async def create_ec_key(
:param dict tags: (optional) Application specific metadata in the form of key-value pairs
:returns: The created key
:rtype: ~azure.keyvault.keys.models.Key
:raises: :class:`~azure.core.exceptions.HttpResponseError`

Example:
.. literalinclude:: ../tests/test_samples_keys_async.py
Expand Down Expand Up @@ -204,7 +219,9 @@ async def delete_key(self, name: str, **kwargs: "**Any") -> DeletedKey:
:param str name: The name of the key to delete.
:returns: The deleted key
:rtype: ~azure.keyvault.keys.models.DeletedKey
:raises: :class:`~azure.core.exceptions.ResourceNotFoundError` if the key doesn't exist
:raises:
:class:`~azure.core.exceptions.ResourceNotFoundError` if the key doesn't exist,
:class:`~azure.core.exceptions.HttpResponseError` for other errors

Example:
.. literalinclude:: ../tests/test_samples_keys_async.py
Expand All @@ -214,7 +231,7 @@ async def delete_key(self, name: str, **kwargs: "**Any") -> DeletedKey:
:caption: Delete a key
:dedent: 8
"""
bundle = await self._client.delete_key(self.vault_url, name, error_map={404: ResourceNotFoundError}, **kwargs)
bundle = await self._client.delete_key(self.vault_url, name, error_map=error_map, **kwargs)
return DeletedKey._from_deleted_key_bundle(bundle)

@distributed_trace_async
Expand All @@ -225,7 +242,9 @@ async def get_key(self, name: str, version: Optional[str] = None, **kwargs: "**A
:param str version: (optional) A specific version of the key to get. If not specified, gets the latest version
of the key.
:rtype: ~azure.keyvault.keys.models.Key
:raises: :class:`~azure.core.exceptions.ResourceNotFoundError` if the key doesn't exist
:raises:
:class:`~azure.core.exceptions.ResourceNotFoundError` if the key doesn't exist,
:class:`~azure.core.exceptions.HttpResponseError` for other errors

Example:
.. literalinclude:: ../tests/test_samples_keys_async.py
Expand All @@ -238,9 +257,7 @@ async def get_key(self, name: str, version: Optional[str] = None, **kwargs: "**A
if version is None:
version = ""

bundle = await self._client.get_key(
self.vault_url, name, version, error_map={404: ResourceNotFoundError}, **kwargs
)
bundle = await self._client.get_key(self.vault_url, name, version, error_map=error_map, **kwargs)
return Key._from_key_bundle(bundle)

@distributed_trace_async
Expand All @@ -251,6 +268,9 @@ async def get_deleted_key(self, name: str, **kwargs: "**Any") -> DeletedKey:
:param str name: The name of the key
:returns: The deleted key
:rtype: ~azure.keyvault.keys.models.DeletedKey
:raises:
:class:`~azure.core.exceptions.ResourceNotFoundError` if the key doesn't exist,
:class:`~azure.core.exceptions.HttpResponseError` for other errors

Example:
.. literalinclude:: ../tests/test_samples_keys_async.py
Expand All @@ -260,9 +280,7 @@ async def get_deleted_key(self, name: str, **kwargs: "**Any") -> DeletedKey:
:caption: Get a deleted key
:dedent: 8
"""
bundle = await self._client.get_deleted_key(
self.vault_url, name, error_map={404: ResourceNotFoundError}, **kwargs
)
bundle = await self._client.get_deleted_key(self.vault_url, name, error_map=error_map, **kwargs)
return DeletedKey._from_deleted_key_bundle(bundle)

@distributed_trace
Expand All @@ -286,7 +304,7 @@ def list_deleted_keys(self, **kwargs: "**Any") -> AsyncIterable[DeletedKey]:
self.vault_url,
maxresults=max_results,
cls=lambda objs: [DeletedKey._from_deleted_key_item(x) for x in objs],
**kwargs
**kwargs,
)

@distributed_trace
Expand All @@ -306,10 +324,7 @@ def list_keys(self, **kwargs: "**Any") -> AsyncIterable[KeyBase]:
"""
max_results = kwargs.get("max_page_size")
return self._client.get_keys(
self.vault_url,
maxresults=max_results,
cls=lambda objs: [KeyBase._from_key_item(x) for x in objs],
**kwargs
self.vault_url, maxresults=max_results, cls=lambda objs: [KeyBase._from_key_item(x) for x in objs], **kwargs
)

@distributed_trace
Expand All @@ -334,7 +349,7 @@ def list_key_versions(self, name: str, **kwargs: "**Any") -> AsyncIterable[KeyBa
name,
maxresults=max_results,
cls=lambda objs: [KeyBase._from_key_item(x) for x in objs],
**kwargs
**kwargs,
)

@distributed_trace_async
Expand All @@ -346,6 +361,7 @@ async def purge_deleted_key(self, name: str, **kwargs: "**Any") -> None:

:param str name: The name of the key
:returns: None
:raises: :class:`~azure.core.exceptions.HttpResponseError`

Example:
.. code-block:: python
Expand All @@ -368,6 +384,7 @@ async def recover_deleted_key(self, name: str, **kwargs: "**Any") -> Key:
:param str name: The name of the deleted key
:returns: The recovered key
:rtype: ~azure.keyvault.keys.models.Key
:raises: :class:`~azure.core.exceptions.HttpResponseError`

Example:
.. literalinclude:: ../tests/test_samples_keys_async.py
Expand Down Expand Up @@ -405,7 +422,9 @@ async def update_key(
:param dict tags: (optional) Application specific metadata in the form of key-value pairs
:returns: The updated key
:rtype: ~azure.keyvault.keys.models.Key
:raises: :class:`~azure.core.exceptions.ResourceNotFoundError` if the key doesn't exist
:raises:
:class:`~azure.core.exceptions.ResourceNotFoundError` if the key doesn't exist,
:class:`~azure.core.exceptions.HttpResponseError` for other errors

Example:
.. literalinclude:: ../tests/test_samples_keys_async.py
Expand All @@ -427,7 +446,7 @@ async def update_key(
key_ops=key_operations,
tags=tags,
key_attributes=attributes,
error_map={404: ResourceNotFoundError},
error_map=error_map,
**kwargs,
)
return Key._from_key_bundle(bundle)
Expand All @@ -443,7 +462,9 @@ async def backup_key(self, name: str, **kwargs: "**Any") -> bytes:
:param str name: The name of the key
:returns: The raw bytes of the key backup
:rtype: bytes
:raises: :class:`~azure.core.exceptions.ResourceNotFoundError` if the key doesn't exist
:raises:
:class:`~azure.core.exceptions.ResourceNotFoundError` if the key doesn't exist,
:class:`~azure.core.exceptions.HttpResponseError` for other errors

Example:
.. literalinclude:: ../tests/test_samples_keys_async.py
Expand All @@ -453,9 +474,7 @@ async def backup_key(self, name: str, **kwargs: "**Any") -> bytes:
:caption: Get a key backup
:dedent: 8
"""
backup_result = await self._client.backup_key(
self.vault_url, name, error_map={404: ResourceNotFoundError}, **kwargs
)
backup_result = await self._client.backup_key(self.vault_url, name, error_map=error_map, **kwargs)
return backup_result.value

@distributed_trace_async
Expand All @@ -469,7 +488,9 @@ async def restore_key(self, backup: bytes, **kwargs: "**Any") -> Key:
:param bytes backup: The raw bytes of the key backup
:returns: The restored key
:rtype: ~azure.keyvault.keys.models.Key
:raises: :class:`~azure.core.exceptions.ResourceExistsError` if the backed up key's name is already in use
:raises:
:class:`~azure.core.exceptions.ResourceExistsError` if the backed up key's name is already in use,
:class:`~azure.core.exceptions.HttpResponseError` for other errors

Example:
.. literalinclude:: ../tests/test_samples_keys_async.py
Expand All @@ -479,7 +500,7 @@ async def restore_key(self, backup: bytes, **kwargs: "**Any") -> Key:
:caption: Restore a key backup
:dedent: 8
"""
bundle = await self._client.restore_key(self.vault_url, backup, error_map={409: ResourceExistsError}, **kwargs)
bundle = await self._client.restore_key(self.vault_url, backup, error_map=error_map, **kwargs)
return Key._from_key_bundle(bundle)

@distributed_trace_async
Expand Down Expand Up @@ -507,6 +528,7 @@ async def import_key(
:param dict tags: (optional) Application specific metadata in the form of key-value pairs
:returns: The imported key
:rtype: ~azure.keyvault.keys.models.Key
:raises: :class:`~azure.core.exceptions.HttpResponseError`

"""
if enabled is not None or not_before is not None or expires is not None:
Expand Down
Loading