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