Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions sdk/storage/azure-storage-blob/azure/storage/blob/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def upload_blob_to_url(
:returns: Blob-updated property dict (Etag and last modified)
:rtype: dict(str, Any)
"""
with BlobClient(blob_url, credential=credential) as client:
with BlobClient.from_blob_url(blob_url, credential=credential) as client:
return client.upload_blob(
data=data,
blob_type=BlobType.BlockBlob,
Expand Down Expand Up @@ -164,7 +164,7 @@ def download_blob_from_url(
If the URL already has a SAS token, specifying an explicit credential will take priority.
:rtype: None
"""
with BlobClient(blob_url, credential=credential) as client:
with BlobClient.from_blob_url(blob_url, credential=credential) as client:
if hasattr(output, 'write'):
_download_to_stream(client, output, max_concurrency, **kwargs)
else:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,11 @@ class BlobClient(AsyncStorageAccountHostsMixin, BlobClientBase): # pylint: disa
:ivar str location_mode:
The location mode that the client is currently using. By default
this will be "primary". Options include "primary" and "secondary".
:param str blob_url: The full URI to the blob. This can also be a URL to the storage account
or container, in which case the blob and/or container must also be specified.
:param container: The container for the blob. If specified, this value will override
a container value specified in the blob URL.
:type container: str or ~azure.storage.blob.models.ContainerProperties
:param blob: The blob with which to interact. If specified, this value will override
a blob value specified in the blob URL.
:type blob: str or ~azure.storage.blob.models.BlobProperties
:param str account_url: The full URI to the account.
:param container_name: The container for the blob.
:type container_name: str
:param blob_name: The mame of the blob with which to interact.
:type blob_name: str
:param str snapshot:
The optional blob snapshot on which to operate.
:param credential:
Expand All @@ -96,9 +93,9 @@ class BlobClient(AsyncStorageAccountHostsMixin, BlobClientBase): # pylint: disa
:caption: Creating the BlobClient from a SAS URL to a blob.
"""
def __init__(
self, blob_url, # type: str
container=None, # type: Optional[Union[str, ContainerProperties]]
blob=None, # type: Optional[Union[str, BlobProperties]]
self, account_url, # type: str
container_name, # type: str
blob_name, # type: str
snapshot=None, # type: Optional[Union[str, Dict[str, Any]]]
credential=None, # type: Optional[Any]
loop=None, # type: Any
Expand All @@ -107,9 +104,9 @@ def __init__(
# type: (...) -> None
kwargs['retry_policy'] = kwargs.get('retry_policy') or ExponentialRetry(**kwargs)
super(BlobClient, self).__init__(
blob_url,
container=container,
blob=blob,
account_url,
container_name=container_name,
blob_name=blob_name,
snapshot=snapshot,
credential=credential,
loop=loop,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -465,9 +465,8 @@ def get_container_client(self, container):
The container need not already exist.

:param container:
The container. This can either be the name of the container,
or an instance of ContainerProperties.
:type container: str or ~azure.storage.blob.models.ContainerProperties
The container that the blob is in.
:type container: str or ~azure.storage.blob.ContainerProperties
:returns: A ContainerClient.
:rtype: ~azure.core.blob.aio.container_client_async.ContainerClient

Expand All @@ -479,8 +478,13 @@ def get_container_client(self, container):
:dedent: 8
:caption: Getting the container client to interact with a specific container.
"""
try:
container_name = container.name
except AttributeError:
container_name = container

return ContainerClient(
self.url, container=container,
self.url, container_name=container_name,
credential=self.credential, _configuration=self._config,
_pipeline=self._pipeline, _location_mode=self._location_mode, _hosts=self._hosts,
require_encryption=self.require_encryption, key_encryption_key=self.key_encryption_key,
Expand All @@ -497,13 +501,11 @@ def get_blob_client(
The blob need not already exist.

:param container:
The container that the blob is in. This can either be the name of the container,
or an instance of ContainerProperties.
:type container: str or ~azure.storage.blob.models.ContainerProperties
The container that the blob is in.
:type container: str or ~azure.storage.blob.ContainerProperties
:param blob:
The blob with which to interact. This can either be the name of the blob,
or an instance of BlobProperties.
:type blob: str or ~azure.storage.blob.models.BlobProperties
The blob with which to interact.
:type blob: str or ~azure.storage.blob.BlobProperties
:param snapshot:
The optional blob snapshot on which to operate. This can either be the ID of the snapshot,
or a dictionary output returned by
Expand All @@ -520,8 +522,18 @@ def get_blob_client(
:dedent: 12
:caption: Getting the blob client to interact with a specific blob.
"""
try:
container_name = container.name
except AttributeError:
container_name = container

try:
blob_name = blob.name
except AttributeError:
blob_name = blob

return BlobClient( # type: ignore
self.url, container=container, blob=blob, snapshot=snapshot,
self.url, container_name=container_name, blob_name=blob_name, snapshot=snapshot,
credential=self.credential, _configuration=self._config,
_pipeline=self._pipeline, _location_mode=self._location_mode, _hosts=self._hosts,
require_encryption=self.require_encryption, key_encryption_key=self.key_encryption_key,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ class ContainerClient(AsyncStorageAccountHostsMixin, ContainerClientBase):
:param str container_url:
The full URI to the container. This can also be a URL to the storage
account, in which case the blob container must also be specified.
:param container:
The container for the blob.
:type container: str or ~azure.storage.blob.models.ContainerProperties
:param container_name:
The name of the container for the blob.
:type container_name: str or ~azure.storage.blob.ContainerProperties
:param credential:
The credentials with which to authenticate. This is optional if the
account URL already has a SAS token. The value can be a SAS token string, and account
Expand All @@ -98,17 +98,17 @@ class ContainerClient(AsyncStorageAccountHostsMixin, ContainerClientBase):
:caption: Creating the container client directly.
"""
def __init__(
self, container_url, # type: str
container=None, # type: Optional[Union[ContainerProperties, str]]
self, account_url, # type: str
container_name=None, # type: str
credential=None, # type: Optional[Any]
loop=None, # type: Any
**kwargs # type: Any
):
# type: (...) -> None
kwargs['retry_policy'] = kwargs.get('retry_policy') or ExponentialRetry(**kwargs)
super(ContainerClient, self).__init__(
container_url,
container=container,
account_url,
container_name=container_name,
credential=credential,
loop=loop,
**kwargs)
Expand Down Expand Up @@ -776,7 +776,7 @@ async def delete_blob(
**kwargs)

def get_blob_client(
self, blob, # type: Union[str, BlobProperties]
self, blob, # type: Union[BlobProperties, str]
snapshot=None # type: str
):
# type: (...) -> BlobClient
Expand All @@ -785,9 +785,8 @@ def get_blob_client(
The blob need not already exist.

:param blob:
The blob with which to interact. If specified, this value will override
a blob value specified in the blob URL.
:type blob: str or ~azure.storage.blob.models.BlobProperties
The blob with which to interact.
:type blob: str or ~azure.storage.blob.BlobProperties
:param str snapshot:
The optional blob snapshot on which to operate.
:returns: A BlobClient.
Expand All @@ -801,8 +800,13 @@ def get_blob_client(
:dedent: 8
:caption: Get the blob client.
"""
try:
blob_name = blob.name
except AttributeError:
blob_name = blob

return BlobClient(
self.url, container=self.container_name, blob=blob, snapshot=snapshot,
self.url, container_name=self.container_name, blob_name=blob_name, snapshot=snapshot,
credential=self.credential, _configuration=self._config,
_pipeline=self._pipeline, _location_mode=self._location_mode, _hosts=self._hosts,
require_encryption=self.require_encryption, key_encryption_key=self.key_encryption_key,
Expand Down
116 changes: 76 additions & 40 deletions sdk/storage/azure-storage-blob/azure/storage/blob/blob_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,13 @@ class BlobClient(StorageAccountHostsMixin): # pylint: disable=too-many-public-m
:ivar str location_mode:
The location mode that the client is currently using. By default
this will be "primary". Options include "primary" and "secondary".
:param str blob_url: The full URI to the blob. This can also be a URL to the storage account
:param str account_url: The full URI to the account. This can also be a URL to the storage account
or container, in which case the blob and/or container must also be specified.
:param container: The container for the blob. If specified, this value will override
a container value specified in the blob URL.
:type container: str or ~azure.storage.blob.models.ContainerProperties
:param blob: The blob with which to interact. If specified, this value will override
:param container_name: The container for the blob.
:type container_name: str
:param blob_name: The blob with which to interact. If specified, this value will override
a blob value specified in the blob URL.
:type blob: str or ~azure.storage.blob.models.BlobProperties
:type blob_name: str
:param str snapshot:
The optional blob snapshot on which to operate.
:param credential:
Expand All @@ -122,54 +121,91 @@ class BlobClient(StorageAccountHostsMixin): # pylint: disable=too-many-public-m
:caption: Creating the BlobClient from a SAS URL to a blob.
"""
def __init__(
self, blob_url, # type: str
container=None, # type: Optional[Union[str, ContainerProperties]]
blob=None, # type: Optional[Union[str, BlobProperties]]
self, account_url, # type: str
container_name, # type: str
blob_name, # type: str
snapshot=None, # type: Optional[Union[str, Dict[str, Any]]]
credential=None, # type: Optional[Any]
**kwargs # type: Any
):
# type: (...) -> None
try:
if not blob_url.lower().startswith('http'):
blob_url = "https://" + blob_url
if not account_url.lower().startswith('http'):
account_url = "https://" + account_url
except AttributeError:
raise ValueError("Blob URL must be a string.")
parsed_url = urlparse(blob_url.rstrip('/'))
raise ValueError("Account URL must be a string.")
parsed_url = urlparse(account_url.rstrip('/'))

if not parsed_url.path and not (container and blob):
raise ValueError("Please specify a container and blob name.")
if not (container_name and blob_name):
raise ValueError("Please specify a container name and blob name.")
if not parsed_url.netloc:
raise ValueError("Invalid URL: {}".format(blob_url))
raise ValueError("Invalid URL: {}".format(account_url))

path_container = ""
path_blob = ""
path_snapshot = None
if parsed_url.path:
path_container, _, path_blob = parsed_url.path.lstrip('/').partition('/')
path_snapshot, sas_token = parse_query(parsed_url.query)

try:
self.container_name = container.name # type: ignore
except AttributeError:
self.container_name = container or unquote(path_container) # type: ignore
self.container_name = container_name
self.blob_name = blob_name
try:
self.snapshot = snapshot.snapshot # type: ignore
except AttributeError:
try:
self.snapshot = snapshot['snapshot'] # type: ignore
except TypeError:
self.snapshot = snapshot or path_snapshot
try:
self.blob_name = blob.name # type: ignore
if not snapshot:
self.snapshot = blob.snapshot # type: ignore
except AttributeError:
self.blob_name = blob or unquote(path_blob)

self._query_str, credential = self._format_query_string(sas_token, credential, snapshot=self.snapshot)
super(BlobClient, self).__init__(parsed_url, service='blob', credential=credential, **kwargs)
self._client = AzureBlobStorage(self.url, pipeline=self._pipeline)

@classmethod
def from_blob_url(cls, blob_url, credential=None, snapshot=None, **kwargs):
# type: (str, Optional[Any], Optional[Union[str, Dict[str, Any]]], Any) -> BlobClient
"""Create BlobClient from a blob url.

:param str blob_url:
The full endpoint URL to the Blob, including SAS token and snapshot if used. This could be
either the primary endpoint, or the secondary endpoint depending on the current `location_mode`.
:type blob_url: str
:param credential:
The credentials with which to authenticate. This is optional if the
account URL already has a SAS token, or the connection string already has shared
access key values. The value can be a SAS token string, and account shared access
key, or an instance of a TokenCredentials class from azure.identity.
Credentials provided here will take precedence over those in the connection string.
:param str snapshot:
The optional blob snapshot on which to operate. If specified, this will override the snapshot
in the url.
"""
try:
if not blob_url.lower().startswith('http'):
blob_url = "https://" + blob_url
except AttributeError:
raise ValueError("Blob URL must be a string.")
parsed_url = urlparse(blob_url.rstrip('/'))

if not parsed_url.netloc:
raise ValueError("Invalid URL: {}".format(blob_url))
account_url = parsed_url.netloc.rstrip('/') + "?" + parsed_url.query
path_blob = parsed_url.path.lstrip('/').partition('/')
container_name, blob_name = unquote(path_blob[0]), unquote(path_blob[2])
if not container_name or not blob_name:
raise ValueError("Invalid URL. Provide a blob_url with a valid blob and container name.")

path_snapshot, _ = parse_query(parsed_url.query)
if snapshot:
try:
path_snapshot = snapshot.snapshot # type: ignore
except AttributeError:
try:
path_snapshot = snapshot['snapshot'] # type: ignore
except TypeError:
path_snapshot = snapshot

return cls(
account_url, container_name=container_name, blob_name=blob_name,
snapshot=path_snapshot, credential=credential, **kwargs
)

def _format_url(self, hostname):
container_name = self.container_name
if isinstance(container_name, six.text_type):
Expand All @@ -184,8 +220,8 @@ def _format_url(self, hostname):
@classmethod
def from_connection_string(
cls, conn_str, # type: str
container, # type: Union[str, ContainerProperties]
blob, # type: Union[str, BlobProperties]
container_name, # type: str
blob_name, # type: str
snapshot=None, # type: Optional[str]
credential=None, # type: Optional[Any]
**kwargs # type: Any
Expand All @@ -195,12 +231,10 @@ def from_connection_string(

:param str conn_str:
A connection string to an Azure Storage account.
:param container: The container for the blob. This can either be the name of the container,
or an instance of ContainerProperties
:type container: str or ~azure.storage.blob.models.ContainerProperties
:param blob: The blob with which to interact. This can either be the name of the blob,
or an instance of BlobProperties.
:type blob: str or ~azure.storage.blob.models.BlobProperties
:param container_name: The container name for the blob.
:type container_name: str
:param blob_name: The name of the blob with which to interact/
:type blob_name: str
:param str snapshot:
The optional blob snapshot on which to operate.
:param credential:
Expand All @@ -222,7 +256,9 @@ def from_connection_string(
if 'secondary_hostname' not in kwargs:
kwargs['secondary_hostname'] = secondary
return cls(
account_url, container=container, blob=blob, snapshot=snapshot, credential=credential, **kwargs)
account_url, container_name=container_name, blob_name=blob_name,
snapshot=snapshot, credential=credential, **kwargs
)

def generate_shared_access_signature(
self, permission=None, # type: Optional[Union[BlobPermissions, str]]
Expand Down
Loading