From a7f51d7eebce941bf9fc9eb11bf7ec16039f91e3 Mon Sep 17 00:00:00 2001 From: seankane-msft Date: Wed, 5 Aug 2020 14:03:37 -0700 Subject: [PATCH 1/7] updated async code to be in line with sync methods, added connection_string --- .../data/tables/aio/_table_client_async.py | 69 +++++++++++++++++++ .../tables/aio/_table_service_client_async.py | 47 ++++++++----- .../tests/test_table_async.py | 2 +- 3 files changed, 100 insertions(+), 18 deletions(-) diff --git a/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_client_async.py b/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_client_async.py index 00c3acd2c5ec..ced909958793 100644 --- a/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_client_async.py +++ b/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_client_async.py @@ -9,6 +9,12 @@ Any, ) +try: + from urllib.parse import urlparse, unquote +except ImportError: + from urlparse import urlparse # type: ignore + from urllib2 import unquote # type: ignore + from azure.core.async_paging import AsyncItemPaged from azure.core.exceptions import ResourceNotFoundError, HttpResponseError from azure.core.tracing.decorator import distributed_trace @@ -28,6 +34,7 @@ from .._deserialize import _convert_to_entity from .._serialize import _add_entity_properties, _get_match_headers from .._shared._table_client_base import TableClientBase +from .._shared.base_client import parse_connection_str class TableClient(AsyncStorageAccountHostsMixin, TableClientBase): @@ -66,6 +73,68 @@ def __init__( self._client._config.version = kwargs.get('api_version', VERSION) # pylint: disable = W0212 self._loop = loop + @classmethod + async def from_connection_string( + cls, conn_str, # type: str + table_name, # type: str + **kwargs # type: Any + ): + # type: (...) -> TableClient + """Create TableClient from a Connection String. + + :param conn_str: + A connection string to an Azure Storage or Cosmos account. + :type conn_str: str + :param table_name: The table name. + :type table_name: str + :returns: A table client. + :rtype: ~azure.data.tables.TableClient + """ + account_url, secondary, credential = parse_connection_str( + conn_str=conn_str, credential=None, service='table') + if 'secondary_hostname' not in kwargs: + kwargs['secondary_hostname'] = secondary + return cls(account_url, table_name=table_name, credential=credential, **kwargs) # type: ignore + + @classmethod + async def from_table_url(cls, table_url, credential=None, **kwargs): + # type: (str, Optional[Any], Any) -> TableClient + """A client to interact with a specific Table. + + :param table_url: The full URI to the table, including SAS token if used. + :type table_url: str + :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, an account + shared access key. + :type credential: str + :returns: A table client. + :rtype: ~azure.data.tables.TableClient + """ + try: + if not table_url.lower().startswith('http'): + table_url = "https://" + table_url + except AttributeError: + raise ValueError("Table URL must be a string.") + parsed_url = urlparse(table_url.rstrip('/')) + + if not parsed_url.netloc: + raise ValueError("Invalid URL: {}".format(table_url)) + + table_path = parsed_url.path.lstrip('/').split('/') + account_path = "" + if len(table_path) > 1: + account_path = "/" + "/".join(table_path[:-1]) + account_url = "{}://{}{}?{}".format( + parsed_url.scheme, + parsed_url.netloc.rstrip('/'), + account_path, + parsed_url.query) + table_name = unquote(table_path[-1]) + if not table_name: + raise ValueError("Invalid URL. Please provide a URL with a valid table name") + return cls(account_url, table_name=table_name, credential=credential, **kwargs) + @distributed_trace_async async def get_table_access_policy( self, diff --git a/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_service_client_async.py b/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_service_client_async.py index 7c58d21a8a94..2309998ffdd8 100644 --- a/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_service_client_async.py +++ b/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_service_client_async.py @@ -23,8 +23,8 @@ from azure.data.tables._shared.policies_async import ExponentialRetry from azure.data.tables._shared.response_handlers import process_table_error from azure.data.tables.aio._table_client_async import TableClient +from .._shared.base_client import parse_connection_str from ._models import TablePropertiesPaged -from .._shared._error import _validate_table_name from .._shared._table_service_client_base import TableServiceClientBase from .._models import Table @@ -86,6 +86,25 @@ def __init__( self._client._config.version = kwargs.get('api_version', VERSION) # pylint: disable=protected-access self._loop = loop + @classmethod + async def from_connection_string( + cls, conn_str, # type: any + **kwargs # type: Any + ): # type: (...) -> TableServiceClient + """Create TableServiceClient from a Connection String. + + :param conn_str: + A connection string to an Azure Storage or Cosmos account. + :type conn_str: str + :returns: A Table service client. + :rtype: ~azure.data.tables.TableServiceClient + """ + account_url, secondary, credential = parse_connection_str( + conn_str=conn_str, credential=None, service='table') + if 'secondary_hostname' not in kwargs: + kwargs['secondary_hostname'] = secondary + return cls(account_url, credential=credential, **kwargs) + @distributed_trace_async async def get_service_stats(self, **kwargs): # type: (...) -> dict[str,object] @@ -175,11 +194,8 @@ async def create_table( :rtype: ~azure.data.tables.TableClient or None :raises: ~azure.core.exceptions.HttpResponseError """ - _validate_table_name(table_name) - - table_properties = TableProperties(table_name=table_name, **kwargs) - await self._client.table.create(table_properties=table_properties, **kwargs) - table = self.get_table_client(table=table_name) + table = self.get_table_client(table_name=table_name) + await table.create_table(**kwargs) return table @distributed_trace_async @@ -196,9 +212,8 @@ async def delete_table( :return: None :rtype: ~None """ - _validate_table_name(table_name) - - await self._client.table.delete(table=table_name, **kwargs) + table = self.get_table_client(table_name=table_name) + await table.delete_table(**kwargs) @distributed_trace def list_tables( @@ -231,8 +246,7 @@ def list_tables( @distributed_trace def query_tables( - self, - filter, # pylint: disable=W0622 + self, filter, # type: str pylint: disable=W0622 **kwargs # type: Any ): # type: (...) -> AsyncItemPaged[Table] @@ -262,8 +276,11 @@ def query_tables( page_iterator_class=TablePropertiesPaged ) - def get_table_client(self, table, **kwargs): - # type: (Union[TableProperties, str], Optional[Any]) -> TableClient + def get_table_client( + self, table_name, # type: Union[TableProperties, str] + **kwargs # type: Optional[Any] + ): + # type: (...) -> TableClient """Get a client to interact with the specified table. The table need not already exist. @@ -276,10 +293,6 @@ def get_table_client(self, table, **kwargs): :rtype: ~azure.data.tables.TableClient """ - try: - table_name = table.name - except AttributeError: - table_name = table _pipeline = AsyncPipeline( transport=AsyncTransportWrapper(self._pipeline._transport), # pylint: disable = protected-access diff --git a/sdk/tables/azure-data-tables/tests/test_table_async.py b/sdk/tables/azure-data-tables/tests/test_table_async.py index 5b6de7387b22..141932a35815 100644 --- a/sdk/tables/azure-data-tables/tests/test_table_async.py +++ b/sdk/tables/azure-data-tables/tests/test_table_async.py @@ -316,7 +316,7 @@ async def test_set_table_acl_with_signed_identifiers(self, resource_group, locat pytest.skip("Cosmos endpoint does not support this") ts = TableServiceClient(url, storage_account_key) table = await self._create_table(ts) - client = ts.get_table_client(table=table.table_name) + client = ts.get_table_client(table_name=table.table_name) # Act identifiers = dict() From db4fac250b8aa0e55137fe1e71ef79953e87fa72 Mon Sep 17 00:00:00 2001 From: seankane-msft Date: Mon, 10 Aug 2020 09:54:35 -0700 Subject: [PATCH 2/7] removing async from two methods --- .../azure/data/tables/aio/_table_client_async.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_client_async.py b/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_client_async.py index 140117feca84..a16c5e231d4e 100644 --- a/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_client_async.py +++ b/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_client_async.py @@ -74,7 +74,7 @@ def __init__( self._loop = loop @classmethod - async def from_connection_string( + def from_connection_string( cls, conn_str, # type: str table_name, # type: str **kwargs # type: Any @@ -97,7 +97,7 @@ async def from_connection_string( return cls(account_url, table_name=table_name, credential=credential, **kwargs) # type: ignore @classmethod - async def from_table_url(cls, table_url, credential=None, **kwargs): + def from_table_url(cls, table_url, credential=None, **kwargs): # type: (str, Optional[Any], Any) -> TableClient """A client to interact with a specific Table. From de74cb1203dcf736f4253b9a0a137377380a86b4 Mon Sep 17 00:00:00 2001 From: seankane-msft Date: Mon, 10 Aug 2020 10:00:02 -0700 Subject: [PATCH 3/7] added name to codeowners for tables --- .github/CODEOWNERS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 09bbe2f736b9..454087ee9e59 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -88,6 +88,9 @@ # PRLabel: %Cognitive - Form Recognizer /sdk/formrecognizer/ @kristapratico @iscai-msft @rakshith91 +# PRLabel: %Tables +/sdk/tables/ @seankane-msft + # Smoke Tests /common/smoketest/ @lmazuel @chlowell @annatisch @rakshith91 @shurd @southpolesteve From 189d4fb88d970110987a0562833826da9145c996 Mon Sep 17 00:00:00 2001 From: seankane-msft Date: Mon, 10 Aug 2020 12:30:11 -0700 Subject: [PATCH 4/7] removed async from table service client from conn str method --- .../azure/data/tables/aio/_table_service_client_async.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_service_client_async.py b/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_service_client_async.py index 338b28fbe405..f4747eb56212 100644 --- a/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_service_client_async.py +++ b/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_service_client_async.py @@ -87,7 +87,7 @@ def __init__( self._loop = loop @classmethod - async def from_connection_string( + def from_connection_string( cls, conn_str, # type: any **kwargs # type: Any ): # type: (...) -> TableServiceClient From aeecec940c9a25622e69832e63d16fa9ddadc7dc Mon Sep 17 00:00:00 2001 From: seankane-msft Date: Wed, 12 Aug 2020 14:01:15 -0700 Subject: [PATCH 5/7] linting fixes --- .../azure-data-tables/azure/data/tables/_deserialize.py | 1 - .../azure/data/tables/aio/_table_client_async.py | 1 + .../azure/data/tables/aio/_table_service_client_async.py | 5 +++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/sdk/tables/azure-data-tables/azure/data/tables/_deserialize.py b/sdk/tables/azure-data-tables/azure/data/tables/_deserialize.py index fa65406bcbde..ac82b854d53d 100644 --- a/sdk/tables/azure-data-tables/azure/data/tables/_deserialize.py +++ b/sdk/tables/azure-data-tables/azure/data/tables/_deserialize.py @@ -16,7 +16,6 @@ from ._entity import EntityProperty, EdmType, TableEntity from ._common_conversion import _decode_base64_to_bytes -from ._generated.models import TableProperties from ._error import TableErrorCode if TYPE_CHECKING: diff --git a/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_client_async.py b/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_client_async.py index a16c5e231d4e..98562eb2501b 100644 --- a/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_client_async.py +++ b/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_client_async.py @@ -21,6 +21,7 @@ from azure.core.tracing.decorator_async import distributed_trace_async from .. import VERSION +from .._base_client import parse_connection_str from .._entity import TableEntity from .._generated.aio import AzureTable from .._generated.models import SignedIdentifier, TableProperties, QueryOptions diff --git a/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_service_client_async.py b/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_service_client_async.py index f4747eb56212..913d84dd943f 100644 --- a/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_service_client_async.py +++ b/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_service_client_async.py @@ -17,10 +17,11 @@ from azure.core.tracing.decorator_async import distributed_trace_async from .. import VERSION, LocationMode +from .._base_client import parse_connection_str from .._generated.aio._azure_table_async import AzureTable from .._generated.models import TableServiceProperties, TableProperties, QueryOptions from .._models import service_stats_deserialize, service_properties_deserialize -from .._error import _validate_table_name, _process_table_error +from .._error import _process_table_error from .._table_service_client_base import TableServiceClientBase from .._models import Table from ._policies_async import ExponentialRetry @@ -277,7 +278,7 @@ def query_tables( ) def get_table_client( - self, table_name, # type: Union[TableProperties, str] + self, table_name, # type: str **kwargs # type: Optional[Any] ): # type: (...) -> TableClient From 2bc04796585f9d8818aa25fdf5e9a5bcf8e005ab Mon Sep 17 00:00:00 2001 From: seankane-msft Date: Thu, 13 Aug 2020 07:28:04 -0700 Subject: [PATCH 6/7] fixed weird indentation --- .../tables/aio/_table_service_client_async.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_service_client_async.py b/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_service_client_async.py index 913d84dd943f..6aa74e2ddbc6 100644 --- a/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_service_client_async.py +++ b/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_service_client_async.py @@ -284,16 +284,16 @@ def get_table_client( # type: (...) -> TableClient """Get a client to interact with the specified table. - The table need not already exist. + The table need not already exist. - :param table: - The queue. This can either be the name of the queue, - or an instance of QueueProperties. - :type table: str or ~azure.storage.table.TableProperties - :returns: A :class:`~azure.data.tables.TableClient` object. - :rtype: ~azure.data.tables.TableClient + :param table: + The queue. This can either be the name of the queue, + or an instance of QueueProperties. + :type table: str or ~azure.storage.table.TableProperties + :returns: A :class:`~azure.data.tables.TableClient` object. + :rtype: ~azure.data.tables.TableClient - """ + """ _pipeline = AsyncPipeline( transport=AsyncTransportWrapper(self._pipeline._transport), # pylint: disable = protected-access From 954b763e44756b836466a4edd1c6a5ed6f717ad9 Mon Sep 17 00:00:00 2001 From: seankane-msft Date: Thu, 13 Aug 2020 10:21:48 -0700 Subject: [PATCH 7/7] changed parse_connection_str method to remove some repeated code, other fixes from krista/issy --- .../azure-data-tables/azure/data/tables/_base_client.py | 8 ++++++-- .../azure-data-tables/azure/data/tables/_table_client.py | 8 +++----- .../azure/data/tables/_table_service_client.py | 6 ++---- .../azure/data/tables/aio/_table_client_async.py | 8 +++----- .../azure/data/tables/aio/_table_service_client_async.py | 6 ++---- 5 files changed, 16 insertions(+), 20 deletions(-) diff --git a/sdk/tables/azure-data-tables/azure/data/tables/_base_client.py b/sdk/tables/azure-data-tables/azure/data/tables/_base_client.py index af7a125d5082..62b1146acbd7 100644 --- a/sdk/tables/azure-data-tables/azure/data/tables/_base_client.py +++ b/sdk/tables/azure-data-tables/azure/data/tables/_base_client.py @@ -337,7 +337,7 @@ def format_shared_key_credential(account, credential): return credential -def parse_connection_str(conn_str, credential, service): +def parse_connection_str(conn_str, credential, service, keyword_args): conn_str = conn_str.rstrip(";") conn_settings = [s.split("=", 1) for s in conn_str.split(";")] if any(len(tup) != 2 for tup in conn_settings): @@ -378,7 +378,11 @@ def parse_connection_str(conn_str, credential, service): ) except KeyError: raise ValueError("Connection string missing required connection details.") - return primary, secondary, credential + + if 'secondary_hostname' not in keyword_args: + keyword_args['secondary_hostname'] = secondary + + return primary, credential def create_configuration(**kwargs): diff --git a/sdk/tables/azure-data-tables/azure/data/tables/_table_client.py b/sdk/tables/azure-data-tables/azure/data/tables/_table_client.py index a8ee3a33c212..543135352e2b 100644 --- a/sdk/tables/azure-data-tables/azure/data/tables/_table_client.py +++ b/sdk/tables/azure-data-tables/azure/data/tables/_table_client.py @@ -78,11 +78,9 @@ def from_connection_string( :returns: A table client. :rtype: ~azure.data.tables.TableClient """ - account_url, secondary, credential = parse_connection_str( - conn_str=conn_str, credential=None, service='table') - if 'secondary_hostname' not in kwargs: - kwargs['secondary_hostname'] = secondary - return cls(account_url, table_name=table_name, credential=credential, **kwargs) # type: ignore + account_url, credential = parse_connection_str( + conn_str=conn_str, credential=None, service='table', keyword_args=kwargs) + return cls(account_url, table_name=table_name, credential=credential, **kwargs) @classmethod def from_table_url(cls, table_url, credential=None, **kwargs): diff --git a/sdk/tables/azure-data-tables/azure/data/tables/_table_service_client.py b/sdk/tables/azure-data-tables/azure/data/tables/_table_service_client.py index 1c4c94303ef6..22656627e488 100644 --- a/sdk/tables/azure-data-tables/azure/data/tables/_table_service_client.py +++ b/sdk/tables/azure-data-tables/azure/data/tables/_table_service_client.py @@ -62,10 +62,8 @@ def from_connection_string( :returns: A Table service client. :rtype: ~azure.data.tables.TableServiceClient """ - account_url, secondary, credential = parse_connection_str( - conn_str=conn_str, credential=None, service='table') - if 'secondary_hostname' not in kwargs: - kwargs['secondary_hostname'] = secondary + account_url, credential = parse_connection_str( + conn_str=conn_str, credential=None, service='table', keyword_args=kwargs) return cls(account_url, credential=credential, **kwargs) @distributed_trace diff --git a/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_client_async.py b/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_client_async.py index 98562eb2501b..eb884bd47d5d 100644 --- a/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_client_async.py +++ b/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_client_async.py @@ -91,11 +91,9 @@ def from_connection_string( :returns: A table client. :rtype: ~azure.data.tables.TableClient """ - account_url, secondary, credential = parse_connection_str( - conn_str=conn_str, credential=None, service='table') - if 'secondary_hostname' not in kwargs: - kwargs['secondary_hostname'] = secondary - return cls(account_url, table_name=table_name, credential=credential, **kwargs) # type: ignore + account_url, credential = parse_connection_str( + conn_str=conn_str, credential=None, service='table', keyword_args=kwargs) + return cls(account_url, table_name=table_name, credential=credential, **kwargs) @classmethod def from_table_url(cls, table_url, credential=None, **kwargs): diff --git a/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_service_client_async.py b/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_service_client_async.py index 6aa74e2ddbc6..c5f07a5adccb 100644 --- a/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_service_client_async.py +++ b/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_service_client_async.py @@ -100,10 +100,8 @@ def from_connection_string( :returns: A Table service client. :rtype: ~azure.data.tables.TableServiceClient """ - account_url, secondary, credential = parse_connection_str( - conn_str=conn_str, credential=None, service='table') - if 'secondary_hostname' not in kwargs: - kwargs['secondary_hostname'] = secondary + account_url, credential = parse_connection_str( + conn_str=conn_str, credential=None, service='table', keyword_args=kwargs) return cls(account_url, credential=credential, **kwargs) @distributed_trace_async