Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Add a from_blob_url method
  • Loading branch information
rakshith91 committed Oct 3, 2019
commit b962d3bb2f134221cf393c470bea1fae690374cb
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 @@ -458,16 +458,15 @@ async def delete_container(
timeout=timeout,
**kwargs)

def get_container_client(self, container):
# type: (Union[ContainerProperties, str]) -> ContainerClient
def get_container_client(self, container_name):
# type: (str) -> ContainerClient
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we were leaving the get_client behaviour unchanged?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The behavior is unchanged, but we only accept strings now since the client init only accept strings.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what I meant - I thought we planned to support properties as well as strings for these functions? And do the try/except name extraction here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed it

"""Get a client to interact with the specified 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
:param container_name:
The name of the container
:type container_name: str
:returns: A ContainerClient.
:rtype: ~azure.core.blob.aio.container_client_async.ContainerClient

Expand All @@ -480,30 +479,28 @@ def get_container_client(self, container):
:caption: Getting the container client to interact with a specific 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,
key_resolver_function=self.key_resolver_function, loop=self._loop)

def get_blob_client(
self, container, # type: Union[ContainerProperties, str]
blob, # type: Union[BlobProperties, str]
self, container_name, # type: str
blob_name, # type: str
snapshot=None # type: Optional[Union[Dict[str, Any], str]]
):
# type: (...) -> BlobClient
"""Get a client to interact with the specified blob.

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
: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 name of the container that the blob is in.
:type container_name: str
:param blob_name:
The blob with which to interact.
:type blob_name: str
: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 @@ -521,7 +518,7 @@ def get_blob_client(
:caption: Getting the blob client to interact with a specific 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.models.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 @@ -99,7 +99,7 @@ class ContainerClient(AsyncStorageAccountHostsMixin, ContainerClientBase):
"""
def __init__(
self, container_url, # type: str
container=None, # type: Optional[Union[ContainerProperties, str]]
container_name=None, # type: str
credential=None, # type: Optional[Any]
loop=None, # type: Any
**kwargs # type: Any
Expand All @@ -108,7 +108,7 @@ def __init__(
kwargs['retry_policy'] = kwargs.get('retry_policy') or ExponentialRetry(**kwargs)
super(ContainerClient, self).__init__(
container_url,
container=container,
container_name=container_name,
credential=credential,
loop=loop,
**kwargs)
Expand Down Expand Up @@ -776,18 +776,17 @@ async def delete_blob(
**kwargs)

def get_blob_client(
self, blob, # type: Union[str, BlobProperties]
self, blob_name, # type: str
snapshot=None # type: str
):
# type: (...) -> BlobClient
"""Get a client to interact with the specified blob.

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
:param blob_name:
The blob with which to interact.
:type blob_name: str
:param str snapshot:
The optional blob snapshot on which to operate.
:returns: A BlobClient.
Expand All @@ -802,7 +801,7 @@ def get_blob_client(
:caption: Get the blob client.
"""
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
94 changes: 54 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,71 @@ 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):
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
container_name, _, blob_name = parsed_url.path.lstrip('/').partition('/')

path_snapshot, sas_token = parse_query(parsed_url.query)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sas_token appears to be discarded, so you can rename to _

if snapshot is not None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The snapshot value can't be an empty string, or false value, so in this case you can just make this: 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 +200,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 +211,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 +236,7 @@ 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