1# -------------------------------------------------------------------------
2# Copyright (c) Microsoft Corporation. All rights reserved.
3# Licensed under the MIT License. See License.txt in the project root for
4# license information.
5# --------------------------------------------------------------------------
6
7import functools
8from typing import (  # pylint: disable=unused-import
9    Union, Optional, Any, Iterable, Dict, List,
10    TYPE_CHECKING
11)
12
13from azure.core.tracing.decorator import distributed_trace
14from azure.core.pipeline import AsyncPipeline
15from azure.core.tracing.decorator_async import distributed_trace_async
16from azure.core.async_paging import AsyncItemPaged
17
18from .._shared.models import LocationMode
19from .._shared.policies_async import ExponentialRetry
20from .._shared.base_client_async import AsyncStorageAccountHostsMixin, AsyncTransportWrapper
21from .._shared.response_handlers import return_response_headers, process_storage_error
22from .._shared.parser import _to_utc_datetime
23from .._shared.response_handlers import parse_to_internal_user_delegation_key
24from .._generated.aio import AzureBlobStorage
25from .._generated.models import StorageErrorException, StorageServiceProperties, KeyInfo
26from .._blob_service_client import BlobServiceClient as BlobServiceClientBase
27from ._container_client_async import ContainerClient
28from ._blob_client_async import BlobClient
29from .._models import (
30    ContainerProperties,
31    service_stats_deserialize,
32    service_properties_deserialize,
33)
34from ._models import ContainerPropertiesPaged
35
36if TYPE_CHECKING:
37    from datetime import datetime
38    from azure.core.pipeline.transport import HttpTransport
39    from azure.core.pipeline.policies import HTTPPolicy
40    from .._shared.models import AccountSasPermissions, ResourceTypes, UserDelegationKey
41    from ._lease_async import BlobLeaseClient
42    from .._models import (
43        BlobProperties,
44        PublicAccess,
45        BlobAnalyticsLogging,
46        Metrics,
47        CorsRule,
48        RetentionPolicy,
49        StaticWebsite,
50    )
51
52
53class BlobServiceClient(AsyncStorageAccountHostsMixin, BlobServiceClientBase):
54    """A client to interact with the Blob Service at the account level.
55
56    This client provides operations to retrieve and configure the account properties
57    as well as list, create and delete containers within the account.
58    For operations relating to a specific container or blob, clients for those entities
59    can also be retrieved using the `get_client` functions.
60
61    :param str account_url:
62        The URL to the blob storage account. Any other entities included
63        in the URL path (e.g. container or blob) will be discarded. This URL can be optionally
64        authenticated with a SAS token.
65    :param credential:
66        The credentials with which to authenticate. This is optional if the
67        account URL already has a SAS token. The value can be a SAS token string, an account
68        shared access key, or an instance of a TokenCredentials class from azure.identity.
69        If the URL already has a SAS token, specifying an explicit credential will take priority.
70    :keyword str secondary_hostname:
71        The hostname of the secondary endpoint.
72    :keyword int max_block_size: The maximum chunk size for uploading a block blob in chunks.
73        Defaults to 4*1024*1024, or 4MB.
74    :keyword int max_single_put_size: If the blob size is less than max_single_put_size, then the blob will be
75        uploaded with only one http PUT request. If the blob size is larger than max_single_put_size,
76        the blob will be uploaded in chunks. Defaults to 64*1024*1024, or 64MB.
77    :keyword int min_large_block_upload_threshold: The minimum chunk size required to use the memory efficient
78        algorithm when uploading a block blob. Defaults to 4*1024*1024+1.
79    :keyword bool use_byte_buffer: Use a byte buffer for block blob uploads. Defaults to False.
80    :keyword int max_page_size: The maximum chunk size for uploading a page blob. Defaults to 4*1024*1024, or 4MB.
81    :keyword int max_single_get_size: The maximum size for a blob to be downloaded in a single call,
82        the exceeded part will be downloaded in chunks (could be parallel). Defaults to 32*1024*1024, or 32MB.
83    :keyword int max_chunk_get_size: The maximum chunk size used for downloading a blob. Defaults to 4*1024*1024,
84        or 4MB.
85
86    .. admonition:: Example:
87
88        .. literalinclude:: ../samples/blob_samples_authentication_async.py
89            :start-after: [START create_blob_service_client]
90            :end-before: [END create_blob_service_client]
91            :language: python
92            :dedent: 8
93            :caption: Creating the BlobServiceClient with account url and credential.
94
95        .. literalinclude:: ../samples/blob_samples_authentication_async.py
96            :start-after: [START create_blob_service_client_oauth]
97            :end-before: [END create_blob_service_client_oauth]
98            :language: python
99            :dedent: 8
100            :caption: Creating the BlobServiceClient with Azure Identity credentials.
101    """
102
103    def __init__(
104            self, account_url,  # type: str
105            credential=None,  # type: Optional[Any]
106            **kwargs  # type: Any
107        ):
108        # type: (...) -> None
109        kwargs['retry_policy'] = kwargs.get('retry_policy') or ExponentialRetry(**kwargs)
110        super(BlobServiceClient, self).__init__(
111            account_url,
112            credential=credential,
113            **kwargs)
114        self._client = AzureBlobStorage(url=self.url, pipeline=self._pipeline)
115        self._loop = kwargs.get('loop', None)
116
117    @distributed_trace_async
118    async def get_user_delegation_key(self, key_start_time,  # type: datetime
119                                      key_expiry_time,  # type: datetime
120                                      **kwargs  # type: Any
121                                      ):
122        # type: (...) -> UserDelegationKey
123        """
124        Obtain a user delegation key for the purpose of signing SAS tokens.
125        A token credential must be present on the service object for this request to succeed.
126
127        :param ~datetime.datetime key_start_time:
128            A DateTime value. Indicates when the key becomes valid.
129        :param ~datetime.datetime key_expiry_time:
130            A DateTime value. Indicates when the key stops being valid.
131        :keyword int timeout:
132            The timeout parameter is expressed in seconds.
133        :return: The user delegation key.
134        :rtype: ~azure.storage.blob.UserDelegationKey
135        """
136        key_info = KeyInfo(start=_to_utc_datetime(key_start_time), expiry=_to_utc_datetime(key_expiry_time))
137        timeout = kwargs.pop('timeout', None)
138        try:
139            user_delegation_key = await self._client.service.get_user_delegation_key(key_info=key_info,
140                                                                                     timeout=timeout,
141                                                                                     **kwargs)  # type: ignore
142        except StorageErrorException as error:
143            process_storage_error(error)
144
145        return parse_to_internal_user_delegation_key(user_delegation_key)  # type: ignore
146
147    @distributed_trace_async
148    async def get_account_information(self, **kwargs):
149        # type: (Any) -> Dict[str, str]
150        """Gets information related to the storage account.
151
152        The information can also be retrieved if the user has a SAS to a container or blob.
153        The keys in the returned dictionary include 'sku_name' and 'account_kind'.
154
155        :returns: A dict of account information (SKU and account type).
156        :rtype: dict(str, str)
157
158        .. admonition:: Example:
159
160            .. literalinclude:: ../samples/blob_samples_service_async.py
161                :start-after: [START get_blob_service_account_info]
162                :end-before: [END get_blob_service_account_info]
163                :language: python
164                :dedent: 12
165                :caption: Getting account information for the blob service.
166        """
167        try:
168            return await self._client.service.get_account_info(cls=return_response_headers, **kwargs) # type: ignore
169        except StorageErrorException as error:
170            process_storage_error(error)
171
172    @distributed_trace_async
173    async def get_service_stats(self, **kwargs):
174        # type: (Any) -> Dict[str, Any]
175        """Retrieves statistics related to replication for the Blob service.
176
177        It is only available when read-access geo-redundant replication is enabled for
178        the storage account.
179
180        With geo-redundant replication, Azure Storage maintains your data durable
181        in two locations. In both locations, Azure Storage constantly maintains
182        multiple healthy replicas of your data. The location where you read,
183        create, update, or delete data is the primary storage account location.
184        The primary location exists in the region you choose at the time you
185        create an account via the Azure Management Azure classic portal, for
186        example, North Central US. The location to which your data is replicated
187        is the secondary location. The secondary location is automatically
188        determined based on the location of the primary; it is in a second data
189        center that resides in the same region as the primary location. Read-only
190        access is available from the secondary location, if read-access geo-redundant
191        replication is enabled for your storage account.
192
193        :keyword int timeout:
194            The timeout parameter is expressed in seconds.
195        :return: The blob service stats.
196        :rtype: Dict[str, Any]
197
198        .. admonition:: Example:
199
200            .. literalinclude:: ../samples/blob_samples_service_async.py
201                :start-after: [START get_blob_service_stats]
202                :end-before: [END get_blob_service_stats]
203                :language: python
204                :dedent: 12
205                :caption: Getting service stats for the blob service.
206        """
207        timeout = kwargs.pop('timeout', None)
208        try:
209            stats = await self._client.service.get_statistics( # type: ignore
210                timeout=timeout, use_location=LocationMode.SECONDARY, **kwargs)
211            return service_stats_deserialize(stats)
212        except StorageErrorException as error:
213            process_storage_error(error)
214
215    @distributed_trace_async
216    async def get_service_properties(self, **kwargs):
217        # type: (Any) -> Dict[str, Any]
218        """Gets the properties of a storage account's Blob service, including
219        Azure Storage Analytics.
220
221        :keyword int timeout:
222            The timeout parameter is expressed in seconds.
223        :returns: An object containing blob service properties such as
224            analytics logging, hour/minute metrics, cors rules, etc.
225        :rtype: Dict[str, Any]
226
227        .. admonition:: Example:
228
229            .. literalinclude:: ../samples/blob_samples_service_async.py
230                :start-after: [START get_blob_service_properties]
231                :end-before: [END get_blob_service_properties]
232                :language: python
233                :dedent: 12
234                :caption: Getting service properties for the blob service.
235        """
236        timeout = kwargs.pop('timeout', None)
237        try:
238            service_props = await self._client.service.get_properties(timeout=timeout, **kwargs)
239            return service_properties_deserialize(service_props)
240        except StorageErrorException as error:
241            process_storage_error(error)
242
243    @distributed_trace_async
244    async def set_service_properties(
245            self, analytics_logging=None,  # type: Optional[BlobAnalyticsLogging]
246            hour_metrics=None,  # type: Optional[Metrics]
247            minute_metrics=None,  # type: Optional[Metrics]
248            cors=None,  # type: Optional[List[CorsRule]]
249            target_version=None,  # type: Optional[str]
250            delete_retention_policy=None,  # type: Optional[RetentionPolicy]
251            static_website=None,  # type: Optional[StaticWebsite]
252            **kwargs
253        ):
254        # type: (...) -> None
255        """Sets the properties of a storage account's Blob service, including
256        Azure Storage Analytics.
257
258        If an element (e.g. analytics_logging) is left as None, the
259        existing settings on the service for that functionality are preserved.
260
261        :param analytics_logging:
262            Groups the Azure Analytics Logging settings.
263        :type analytics_logging: ~azure.storage.blob.BlobAnalyticsLogging
264        :param hour_metrics:
265            The hour metrics settings provide a summary of request
266            statistics grouped by API in hourly aggregates for blobs.
267        :type hour_metrics: ~azure.storage.blob.Metrics
268        :param minute_metrics:
269            The minute metrics settings provide request statistics
270            for each minute for blobs.
271        :type minute_metrics: ~azure.storage.blob.Metrics
272        :param cors:
273            You can include up to five CorsRule elements in the
274            list. If an empty list is specified, all CORS rules will be deleted,
275            and CORS will be disabled for the service.
276        :type cors: list[~azure.storage.blob.CorsRule]
277        :param str target_version:
278            Indicates the default version to use for requests if an incoming
279            request's version is not specified.
280        :param delete_retention_policy:
281            The delete retention policy specifies whether to retain deleted blobs.
282            It also specifies the number of days and versions of blob to keep.
283        :type delete_retention_policy: ~azure.storage.blob.RetentionPolicy
284        :param static_website:
285            Specifies whether the static website feature is enabled,
286            and if yes, indicates the index document and 404 error document to use.
287        :type static_website: ~azure.storage.blob.StaticWebsite
288        :keyword int timeout:
289            The timeout parameter is expressed in seconds.
290        :rtype: None
291
292        .. admonition:: Example:
293
294            .. literalinclude:: ../samples/blob_samples_service_async.py
295                :start-after: [START set_blob_service_properties]
296                :end-before: [END set_blob_service_properties]
297                :language: python
298                :dedent: 12
299                :caption: Setting service properties for the blob service.
300        """
301        props = StorageServiceProperties(
302            logging=analytics_logging,
303            hour_metrics=hour_metrics,
304            minute_metrics=minute_metrics,
305            cors=cors,
306            default_service_version=target_version,
307            delete_retention_policy=delete_retention_policy,
308            static_website=static_website
309        )
310        timeout = kwargs.pop('timeout', None)
311        try:
312            await self._client.service.set_properties(props, timeout=timeout, **kwargs)
313        except StorageErrorException as error:
314            process_storage_error(error)
315
316    @distributed_trace
317    def list_containers(
318            self, name_starts_with=None,  # type: Optional[str]
319            include_metadata=False,  # type: Optional[bool]
320            **kwargs
321        ):
322        # type: (...) -> AsyncItemPaged[ContainerProperties]
323        """Returns a generator to list the containers under the specified account.
324
325        The generator will lazily follow the continuation tokens returned by
326        the service and stop when all containers have been returned.
327
328        :param str name_starts_with:
329            Filters the results to return only containers whose names
330            begin with the specified prefix.
331        :param bool include_metadata:
332            Specifies that container metadata to be returned in the response.
333            The default value is `False`.
334        :keyword int results_per_page:
335            The maximum number of container names to retrieve per API
336            call. If the request does not specify the server will return up to 5,000 items.
337        :keyword int timeout:
338            The timeout parameter is expressed in seconds.
339        :returns: An iterable (auto-paging) of ContainerProperties.
340        :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.storage.blob.ContainerProperties]
341
342        .. admonition:: Example:
343
344            .. literalinclude:: ../samples/blob_samples_service_async.py
345                :start-after: [START bsc_list_containers]
346                :end-before: [END bsc_list_containers]
347                :language: python
348                :dedent: 16
349                :caption: Listing the containers in the blob service.
350        """
351        include = 'metadata' if include_metadata else None
352        timeout = kwargs.pop('timeout', None)
353        results_per_page = kwargs.pop('results_per_page', None)
354        command = functools.partial(
355            self._client.service.list_containers_segment,
356            prefix=name_starts_with,
357            include=include,
358            timeout=timeout,
359            **kwargs)
360        return AsyncItemPaged(
361            command,
362            prefix=name_starts_with,
363            results_per_page=results_per_page,
364            page_iterator_class=ContainerPropertiesPaged
365        )
366
367    @distributed_trace_async
368    async def create_container(
369            self, name,  # type: str
370            metadata=None,  # type: Optional[Dict[str, str]]
371            public_access=None,  # type: Optional[Union[PublicAccess, str]]
372            **kwargs
373        ):
374        # type: (...) -> ContainerClient
375        """Creates a new container under the specified account.
376
377        If the container with the same name already exists, a ResourceExistsError will
378        be raised. This method returns a client with which to interact with the newly
379        created container.
380
381        :param str name: The name of the container to create.
382        :param metadata:
383            A dict with name-value pairs to associate with the
384            container as metadata. Example: `{'Category':'test'}`
385        :type metadata: dict(str, str)
386        :param public_access:
387            Possible values include: 'container', 'blob'.
388        :type public_access: str or ~azure.storage.blob.PublicAccess
389        :keyword int timeout:
390            The timeout parameter is expressed in seconds.
391        :rtype: ~azure.storage.blob.aio.ContainerClient
392
393        .. admonition:: Example:
394
395            .. literalinclude:: ../samples/blob_samples_service_async.py
396                :start-after: [START bsc_create_container]
397                :end-before: [END bsc_create_container]
398                :language: python
399                :dedent: 16
400                :caption: Creating a container in the blob service.
401        """
402        container = self.get_container_client(name)
403        timeout = kwargs.pop('timeout', None)
404        kwargs.setdefault('merge_span', True)
405        await container.create_container(
406            metadata=metadata, public_access=public_access, timeout=timeout, **kwargs)
407        return container
408
409    @distributed_trace_async
410    async def delete_container(
411            self, container,  # type: Union[ContainerProperties, str]
412            lease=None,  # type: Optional[Union[BlobLeaseClient, str]]
413            **kwargs
414        ):
415        # type: (...) -> None
416        """Marks the specified container for deletion.
417
418        The container and any blobs contained within it are later deleted during garbage collection.
419        If the container is not found, a ResourceNotFoundError will be raised.
420
421        :param container:
422            The container to delete. This can either be the name of the container,
423            or an instance of ContainerProperties.
424        :type container: str or ~azure.storage.blob.ContainerProperties
425        :param lease:
426            If specified, delete_container only succeeds if the
427            container's lease is active and matches this ID.
428            Required if the container has an active lease.
429        :paramtype lease: ~azure.storage.blob.aio.BlobLeaseClient or str
430        :keyword ~datetime.datetime if_modified_since:
431            A DateTime value. Azure expects the date value passed in to be UTC.
432            If timezone is included, any non-UTC datetimes will be converted to UTC.
433            If a date is passed in without timezone info, it is assumed to be UTC.
434            Specify this header to perform the operation only
435            if the resource has been modified since the specified time.
436        :keyword ~datetime.datetime if_unmodified_since:
437            A DateTime value. Azure expects the date value passed in to be UTC.
438            If timezone is included, any non-UTC datetimes will be converted to UTC.
439            If a date is passed in without timezone info, it is assumed to be UTC.
440            Specify this header to perform the operation only if
441            the resource has not been modified since the specified date/time.
442        :keyword str etag:
443            An ETag value, or the wildcard character (*). Used to check if the resource has changed,
444            and act according to the condition specified by the `match_condition` parameter.
445        :keyword ~azure.core.MatchConditions match_condition:
446            The match condition to use upon the etag.
447        :keyword int timeout:
448            The timeout parameter is expressed in seconds.
449        :rtype: None
450
451        .. admonition:: Example:
452
453            .. literalinclude:: ../samples/blob_samples_service_async.py
454                :start-after: [START bsc_delete_container]
455                :end-before: [END bsc_delete_container]
456                :language: python
457                :dedent: 16
458                :caption: Deleting a container in the blob service.
459        """
460        container = self.get_container_client(container) # type: ignore
461        kwargs.setdefault('merge_span', True)
462        timeout = kwargs.pop('timeout', None)
463        await container.delete_container( # type: ignore
464            lease=lease,
465            timeout=timeout,
466            **kwargs)
467
468    def get_container_client(self, container):
469        # type: (Union[ContainerProperties, str]) -> ContainerClient
470        """Get a client to interact with the specified container.
471
472        The container need not already exist.
473
474        :param container:
475            The container. This can either be the name of the container,
476            or an instance of ContainerProperties.
477        :type container: str or ~azure.storage.blob.ContainerProperties
478        :returns: A ContainerClient.
479        :rtype: ~azure.storage.blob.aio.ContainerClient
480
481        .. admonition:: Example:
482
483            .. literalinclude:: ../samples/blob_samples_service_async.py
484                :start-after: [START bsc_get_container_client]
485                :end-before: [END bsc_get_container_client]
486                :language: python
487                :dedent: 12
488                :caption: Getting the container client to interact with a specific container.
489        """
490        try:
491            container_name = container.name
492        except AttributeError:
493            container_name = container
494        _pipeline = AsyncPipeline(
495            transport=AsyncTransportWrapper(self._pipeline._transport), # pylint: disable = protected-access
496            policies=self._pipeline._impl_policies # pylint: disable = protected-access
497        )
498        return ContainerClient(
499            self.url, container_name=container_name,
500            credential=self.credential, _configuration=self._config,
501            _pipeline=_pipeline, _location_mode=self._location_mode, _hosts=self._hosts,
502            require_encryption=self.require_encryption, key_encryption_key=self.key_encryption_key,
503            key_resolver_function=self.key_resolver_function, loop=self._loop)
504
505    def get_blob_client(
506            self, container,  # type: Union[ContainerProperties, str]
507            blob,  # type: Union[BlobProperties, str]
508            snapshot=None  # type: Optional[Union[Dict[str, Any], str]]
509        ):
510        # type: (...) -> BlobClient
511        """Get a client to interact with the specified blob.
512
513        The blob need not already exist.
514
515        :param container:
516            The container that the blob is in. This can either be the name of the container,
517            or an instance of ContainerProperties.
518        :type container: str or ~azure.storage.blob.ContainerProperties
519        :param blob:
520            The blob with which to interact. This can either be the name of the blob,
521            or an instance of BlobProperties.
522        :type blob: str or ~azure.storage.blob.BlobProperties
523        :param snapshot:
524            The optional blob snapshot on which to operate. This can either be the ID of the snapshot,
525            or a dictionary output returned by
526            :func:`~azure.storage.blob.aio.BlobClient.create_snapshot()`.
527        :type snapshot: str or dict(str, Any)
528        :returns: A BlobClient.
529        :rtype: ~azure.storage.blob.aio.BlobClient
530
531        .. admonition:: Example:
532
533            .. literalinclude:: ../samples/blob_samples_service_async.py
534                :start-after: [START bsc_get_blob_client]
535                :end-before: [END bsc_get_blob_client]
536                :language: python
537                :dedent: 16
538                :caption: Getting the blob client to interact with a specific blob.
539        """
540        try:
541            container_name = container.name
542        except AttributeError:
543            container_name = container
544
545        try:
546            blob_name = blob.name
547        except AttributeError:
548            blob_name = blob
549        _pipeline = AsyncPipeline(
550            transport=AsyncTransportWrapper(self._pipeline._transport), # pylint: disable = protected-access
551            policies=self._pipeline._impl_policies # pylint: disable = protected-access
552        )
553        return BlobClient( # type: ignore
554            self.url, container_name=container_name, blob_name=blob_name, snapshot=snapshot,
555            credential=self.credential, _configuration=self._config,
556            _pipeline=_pipeline, _location_mode=self._location_mode, _hosts=self._hosts,
557            require_encryption=self.require_encryption, key_encryption_key=self.key_encryption_key,
558            key_resolver_function=self.key_resolver_function, loop=self._loop)
559