From f0661e3c4a6e8baffba128546cbaf930618bbc6f Mon Sep 17 00:00:00 2001 From: annatisch Date: Thu, 20 May 2021 16:01:05 -0700 Subject: [PATCH] [Tables] Emulator tests and binary serialization (#18829) * Deserialize to binary * Updates for emulator support * Pylint * Changelog and tests --- sdk/tables/azure-data-tables/CHANGELOG.md | 9 ++ .../azure/data/tables/__init__.py | 3 +- .../azure/data/tables/_base_client.py | 14 ++-- .../azure/data/tables/_common_conversion.py | 8 +- .../azure/data/tables/_deserialize.py | 43 ++-------- .../azure/data/tables/_table_batch.py | 6 +- .../azure/data/tables/_table_client.py | 3 + .../data/tables/aio/_table_batch_async.py | 6 +- .../data/tables/aio/_table_client_async.py | 3 + .../sample_update_upsert_merge_entities.py | 6 +- .../tests/_shared/testcase.py | 4 +- .../azure-data-tables/tests/test_table.py | 10 --- .../tests/test_table_async.py | 10 --- .../tests/test_table_client.py | 79 +++++++++++++++++- .../tests/test_table_client_async.py | 82 ++++++++++++++++++- .../tests/test_table_client_cosmos.py | 46 ++++++++++- .../tests/test_table_client_cosmos_async.py | 46 ++++++++++- .../tests/test_table_entity.py | 16 ++-- .../tests/test_table_entity_async.py | 22 ++--- .../tests/test_table_entity_cosmos.py | 22 +++-- .../tests/test_table_entity_cosmos_async.py | 22 ++--- 21 files changed, 334 insertions(+), 126 deletions(-) diff --git a/sdk/tables/azure-data-tables/CHANGELOG.md b/sdk/tables/azure-data-tables/CHANGELOG.md index fd2d3521d0e5..d688c53cf88d 100644 --- a/sdk/tables/azure-data-tables/CHANGELOG.md +++ b/sdk/tables/azure-data-tables/CHANGELOG.md @@ -1,5 +1,14 @@ # Release History +## 12.0.0 (unreleased) +**Breaking** +* EdmType.Binary data in entities will now be deserialized as `bytes` in Python 3 and `str` in Python 2, rather than an `EdmProperty` instance. Likewise on serialization, `bytes` in Python 3 and `str` in Python 2 will be interpreted as binary (this is unchanged for Python 3, but breaking for Python 2, where `str` was previously serialized as EdmType.String) + +**Fixes** +* Fixed support for Cosmos emulator endpoint, via URL/credential or connection string. +* Fixed table name from URL parsing in `TableClient.from_table_url` classmethod. +* The `account_name` attribute on clients will now be pulled from an `AzureNamedKeyCredential` if used. + ## 12.0.0b7 (2021-05-11) **Breaking** * The `account_url` parameter in the client constructors has been renamed to `endpoint`. diff --git a/sdk/tables/azure-data-tables/azure/data/tables/__init__.py b/sdk/tables/azure-data-tables/azure/data/tables/__init__.py index 01fe558a40b1..5aaecd9643ff 100644 --- a/sdk/tables/azure-data-tables/azure/data/tables/__init__.py +++ b/sdk/tables/azure-data-tables/azure/data/tables/__init__.py @@ -6,7 +6,7 @@ from azure.data.tables._models import TableServiceStats from ._entity import TableEntity, EntityProperty, EdmType -from ._error import RequestTooLargeError, TableTransactionError +from ._error import RequestTooLargeError, TableTransactionError, TableErrorCode from ._table_shared_access_signature import generate_table_sas, generate_account_sas from ._table_client import TableClient from ._table_service_client import TableServiceClient @@ -26,7 +26,6 @@ TransactionOperation ) from ._version import VERSION -from ._deserialize import TableErrorCode __version__ = VERSION 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 038950f0aa35..8505b59a69a9 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 @@ -80,7 +80,6 @@ def __init__( account_url = "https://" + account_url except AttributeError: raise ValueError("Account URL must be a string.") - self._cosmos_endpoint = _is_cosmos_endpoint(account_url) parsed_url = urlparse(account_url.rstrip("/")) if not parsed_url.netloc: raise ValueError("Invalid URL: {}".format(account_url)) @@ -94,7 +93,7 @@ def __init__( self._location_mode = kwargs.get("location_mode", LocationMode.PRIMARY) self._hosts = kwargs.get("_hosts") self.scheme = parsed_url.scheme - self._cosmos_endpoint = _is_cosmos_endpoint(parsed_url.hostname) + self._cosmos_endpoint = _is_cosmos_endpoint(parsed_url) if ".core." in parsed_url.netloc or ".cosmos." in parsed_url.netloc: account = parsed_url.netloc.split(".table.core.") if "cosmos" in parsed_url.netloc: @@ -114,17 +113,19 @@ def __init__( self.credential = credential if self.scheme.lower() != "https" and hasattr(self.credential, "get_token"): raise ValueError("Token credential is only supported with HTTPS.") - if hasattr(self.credential, "account_name"): - self.account_name = self.credential.account_name + if hasattr(self.credential, "named_key"): + self.account_name = self.credential.named_key.name secondary_hostname = "{}-secondary.table.{}".format( - self.credential.account_name, SERVICE_HOST_BASE + self.credential.named_key.name, SERVICE_HOST_BASE ) if not self._hosts: if len(account) > 1: secondary_hostname = parsed_url.netloc.replace( account[0], account[0] + "-secondary" - ) + ) + parsed_url.path.replace( + account[0], account[0] + "-secondary" + ).rstrip("/") if kwargs.get("secondary_hostname"): secondary_hostname = kwargs["secondary_hostname"] primary_hostname = (parsed_url.netloc + parsed_url.path).rstrip("/") @@ -346,7 +347,6 @@ def parse_connection_str(conn_str, credential, keyword_args): credential = conn_settings.get("sharedaccesssignature") # if "sharedaccesssignature" in conn_settings: # credential = AzureSasCredential(conn_settings['sharedaccesssignature']) - primary = conn_settings.get("tableendpoint") secondary = conn_settings.get("tablesecondaryendpoint") if not primary: diff --git a/sdk/tables/azure-data-tables/azure/data/tables/_common_conversion.py b/sdk/tables/azure-data-tables/azure/data/tables/_common_conversion.py index 98791d7e345d..b11370952964 100644 --- a/sdk/tables/azure-data-tables/azure/data/tables/_common_conversion.py +++ b/sdk/tables/azure-data-tables/azure/data/tables/_common_conversion.py @@ -89,12 +89,12 @@ def _sign_string(key, string_to_sign, key_is_base64=True): def _is_cosmos_endpoint(url): - if ".table.cosmodb." in url: + if ".table.cosmodb." in url.hostname: return True - - if ".table.cosmos." in url: + if ".table.cosmos." in url.hostname: + return True + if url.hostname == "localhost" and url.port != 10002: return True - return False 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 ecb2d5e0d12a..a015867a1fb4 100644 --- a/sdk/tables/azure-data-tables/azure/data/tables/_deserialize.py +++ b/sdk/tables/azure-data-tables/azure/data/tables/_deserialize.py @@ -4,19 +4,14 @@ # license information. # -------------------------------------------------------------------------- -from typing import TYPE_CHECKING from uuid import UUID import logging import datetime -from azure.core.exceptions import ResourceExistsError +import six from ._entity import EntityProperty, EdmType, TableEntity from ._common_conversion import _decode_base64_to_bytes, TZ_UTC -from ._error import TableErrorCode - -if TYPE_CHECKING: - from azure.core.exceptions import AzureError _LOGGER = logging.getLogger(__name__) @@ -26,18 +21,6 @@ except ImportError: from urllib2 import quote # type: ignore -if TYPE_CHECKING: - from typing import ( # pylint: disable=ungrouped-imports - Union, - Optional, - Any, - Iterable, - Dict, - List, - Type, - Tuple, - ) - class TablesEntityDatetime(datetime.datetime): @@ -62,29 +45,14 @@ def get_enum_value(value): return value -def _deserialize_table_creation(response, _, headers): - if response.status_code == 204: - error_code = TableErrorCode.table_already_exists - error = ResourceExistsError( - message="Table already exists\nRequestId:{}\nTime:{}\nErrorCode:{}".format( - headers["x-ms-request-id"], headers["Date"], error_code - ), - response=response, - ) - error.error_code = error_code - error.additional_info = {} - raise error - return headers - - def _from_entity_binary(value): # type: (str) -> EntityProperty - return EntityProperty(_decode_base64_to_bytes(value), EdmType.BINARY) + return _decode_base64_to_bytes(value) def _from_entity_int32(value): # type: (str) -> EntityProperty - return EntityProperty(int(value), EdmType.INT32) + return int(value) def _from_entity_int64(value): @@ -129,8 +97,9 @@ def _from_entity_guid(value): def _from_entity_str(value): # type: (str) -> EntityProperty - return EntityProperty(value, EdmType.STRING) - + if isinstance(six.binary_type): + return value.decode('utf-8') + return value _EDM_TYPES = [ EdmType.BINARY, diff --git a/sdk/tables/azure-data-tables/azure/data/tables/_table_batch.py b/sdk/tables/azure-data-tables/azure/data/tables/_table_batch.py index e2f6506c4524..d342aeb5ea4a 100644 --- a/sdk/tables/azure-data-tables/azure/data/tables/_table_batch.py +++ b/sdk/tables/azure-data-tables/azure/data/tables/_table_batch.py @@ -12,7 +12,7 @@ Optional ) -from ._common_conversion import _is_cosmos_endpoint, _transform_patch_to_cosmos_post +from ._common_conversion import _transform_patch_to_cosmos_post from ._models import UpdateMode from ._serialize import _get_match_headers, _add_entity_properties from ._entity import TableEntity @@ -43,6 +43,7 @@ def __init__( deserializer, # type: msrest.Deserializer config, # type: AzureTableConfiguration table_name, # type: str + is_cosmos_endpoint=False, # type: bool **kwargs # type: Dict[str, Any] ): """Create TableClient from a Credential. @@ -66,6 +67,7 @@ def __init__( self._serialize = serializer self._deserialize = deserializer self._config = config + self._is_cosmos_endpoint = is_cosmos_endpoint self.table_name = table_name self._partition_key = kwargs.pop("partition_key", None) @@ -485,7 +487,7 @@ def _batch_merge_entity( request = self._client._client.patch( # pylint: disable=protected-access url, query_parameters, header_parameters, **body_content_kwargs ) - if _is_cosmos_endpoint(url): + if self._is_cosmos_endpoint: _transform_patch_to_cosmos_post(request) self.requests.append(request) 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 05574bfb2c28..36c01a5645c2 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 @@ -152,6 +152,8 @@ def from_table_url(cls, table_url, credential=None, **kwargs): parsed_url.query, ) table_name = unquote(table_path[-1]) + if table_name.lower().startswith("tables('"): + table_name = table_name[8:-2] if not table_name: raise ValueError( "Invalid URL. Please provide a URL with a valid table name" @@ -705,6 +707,7 @@ def submit_transaction( self._client._deserialize, # pylint: disable=protected-access self._client._config, # pylint: disable=protected-access self.table_name, + is_cosmos_endpoint=self._cosmos_endpoint, **kwargs ) for operation in operations: diff --git a/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_batch_async.py b/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_batch_async.py index 52034ebe5f31..6e22a616d3e1 100644 --- a/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_batch_async.py +++ b/sdk/tables/azure-data-tables/azure/data/tables/aio/_table_batch_async.py @@ -6,7 +6,7 @@ from typing import Dict, Any, Optional, Union, TYPE_CHECKING import msrest -from .._common_conversion import _is_cosmos_endpoint, _transform_patch_to_cosmos_post +from .._common_conversion import _transform_patch_to_cosmos_post from .._models import UpdateMode from .._entity import TableEntity from .._table_batch import EntityType @@ -41,12 +41,14 @@ def __init__( deserializer: msrest.Deserializer, config: AzureTableConfiguration, table_name: str, + is_cosmos_endpoint: bool = False, **kwargs: Dict[str, Any] ) -> None: self._client = client self._serialize = serializer self._deserialize = deserializer self._config = config + self._is_cosmos_endpoint = is_cosmos_endpoint self.table_name = table_name self._partition_key = kwargs.pop("partition_key", None) @@ -456,7 +458,7 @@ def _batch_merge_entity( request = self._client._client.patch( # pylint: disable=protected-access url, query_parameters, header_parameters, **body_content_kwargs ) - if _is_cosmos_endpoint(url): + if self._is_cosmos_endpoint: _transform_patch_to_cosmos_post(request) self.requests.append(request) 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 08f4705be0ca..d4a335c8cd9e 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 @@ -149,6 +149,8 @@ def from_table_url( parsed_url.query, ) table_name = unquote(table_path[-1]) + if table_name.lower().startswith("tables('"): + table_name = table_name[8:-2] if not table_name: raise ValueError( "Invalid URL. Please provide a URL with a valid table name" @@ -689,6 +691,7 @@ async def submit_transaction( self._client._deserialize, # pylint: disable=protected-access self._client._config, # pylint: disable=protected-access self.table_name, + is_cosmos_endpoint=self._cosmos_endpoint, **kwargs ) for operation in operations: diff --git a/sdk/tables/azure-data-tables/samples/sample_update_upsert_merge_entities.py b/sdk/tables/azure-data-tables/samples/sample_update_upsert_merge_entities.py index f49f96618290..7340d1542fc6 100644 --- a/sdk/tables/azure-data-tables/samples/sample_update_upsert_merge_entities.py +++ b/sdk/tables/azure-data-tables/samples/sample_update_upsert_merge_entities.py @@ -144,14 +144,14 @@ def update_entities(self): insert_entity = table.upsert_entity(mode=UpdateMode.REPLACE, entity=entity1) print("Inserted entity: {}".format(insert_entity)) - created["text"] = "NewMarker" + created[u"text"] = u"NewMarker" merged_entity = table.upsert_entity(mode=UpdateMode.MERGE, entity=entity) print("Merged entity: {}".format(merged_entity)) # [END upsert_entity] # [START update_entity] # Update the entity - created["text"] = "NewMarker" + created[u"text"] = u"NewMarker" table.update_entity(mode=UpdateMode.REPLACE, entity=created) # Get the replaced entity @@ -159,7 +159,7 @@ def update_entities(self): print("Replaced entity: {}".format(replaced)) # Merge the entity - replaced["color"] = "Blue" + replaced[u"color"] = u"Blue" table.update_entity(mode=UpdateMode.MERGE, entity=replaced) # Get the merged entity diff --git a/sdk/tables/azure-data-tables/tests/_shared/testcase.py b/sdk/tables/azure-data-tables/tests/_shared/testcase.py index 28be31dbce15..343eb224d602 100644 --- a/sdk/tables/azure-data-tables/tests/_shared/testcase.py +++ b/sdk/tables/azure-data-tables/tests/_shared/testcase.py @@ -176,7 +176,7 @@ def _assert_default_entity(self, entity): assert entity["large"] == 933311100 assert entity["Birthday"] == datetime(1973, 10, 4, tzinfo=tzutc()) assert entity["birthday"] == datetime(1970, 10, 4, tzinfo=tzutc()) - assert entity["binary"].value == b"binary" + assert entity["binary"] == b"binary" assert entity["other"] == 20 assert entity["clsid"] == uuid.UUID("c9da6455-213d-42c9-9a79-3e9149a57833") assert entity.metadata["etag"] @@ -197,7 +197,7 @@ def _assert_default_entity_json_full_metadata(self, entity, headers=None): assert entity["large"] == 933311100 assert entity["Birthday"] == datetime(1973, 10, 4, tzinfo=tzutc()) assert entity["birthday"] == datetime(1970, 10, 4, tzinfo=tzutc()) - assert entity["binary"].value == b"binary" + assert entity["binary"] == b"binary" assert entity["other"] == 20 assert entity["clsid"] == uuid.UUID("c9da6455-213d-42c9-9a79-3e9149a57833") assert entity.metadata["etag"] diff --git a/sdk/tables/azure-data-tables/tests/test_table.py b/sdk/tables/azure-data-tables/tests/test_table.py index f461937cb126..58fd219564fb 100644 --- a/sdk/tables/azure-data-tables/tests/test_table.py +++ b/sdk/tables/azure-data-tables/tests/test_table.py @@ -468,13 +468,3 @@ def test_delete_table_invalid_name(self): assert "Table names must be alphanumeric, cannot begin with a number, and must be between 3-63 characters long.""" in str( excinfo) - - def test_azurite_url(self): - account_url = "https://127.0.0.1:10002/my_account" - tsc = TableServiceClient(account_url, credential=self.credential) - - assert tsc.account_name == "my_account" - assert tsc.url == "https://127.0.0.1:10002/my_account" - assert tsc._location_mode == "primary" - assert tsc.credential.named_key.key == self.credential.named_key.key - assert tsc.credential.named_key.name == self.credential.named_key.name 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 e1e3e0d9251a..99da06b7b005 100644 --- a/sdk/tables/azure-data-tables/tests/test_table_async.py +++ b/sdk/tables/azure-data-tables/tests/test_table_async.py @@ -398,13 +398,3 @@ async def test_delete_table_invalid_name(self): assert "Table names must be alphanumeric, cannot begin with a number, and must be between 3-63 characters long.""" in str( excinfo) - - def test_azurite_url(self): - account_url = "https://127.0.0.1:10002/my_account" - tsc = TableServiceClient(account_url, credential=self.credential) - - assert tsc.account_name == "my_account" - assert tsc.url == "https://127.0.0.1:10002/my_account" - assert tsc._location_mode == "primary" - assert tsc.credential.named_key.key == self.credential.named_key.key - assert tsc.credential.named_key.name == self.credential.named_key.name diff --git a/sdk/tables/azure-data-tables/tests/test_table_client.py b/sdk/tables/azure-data-tables/tests/test_table_client.py index ac0d1df0880b..5b9c1d47934e 100644 --- a/sdk/tables/azure-data-tables/tests/test_table_client.py +++ b/sdk/tables/azure-data-tables/tests/test_table_client.py @@ -390,7 +390,7 @@ def test_create_service_with_custom_account_endpoint_path(self): service = service_type[0].from_connection_string(conn_string, table_name="foo") # Assert - assert service.account_name == "custom" + assert service.account_name == self.tables_storage_account_name assert service.credential.named_key.name == self.tables_storage_account_name assert service.credential.named_key.key == self.tables_primary_storage_account_key assert service._primary_hostname == 'local-machine:11002/custom/account/path' @@ -526,3 +526,80 @@ def test_create_client_with_api_version(self): with pytest.raises(ValueError): TableServiceClient(url, credential=self.credential, api_version="foo") + + def test_create_client_for_azurite(self): + azurite_credential = AzureNamedKeyCredential("myaccount", self.tables_primary_storage_account_key) + http_connstr = "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey={};TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;".format( + self.tables_primary_storage_account_key + ) + https_connstr = "DefaultEndpointsProtocol=https;AccountName=devstoreaccount1;AccountKey={};TableEndpoint=https://127.0.0.1:10002/devstoreaccount1;".format( + self.tables_primary_storage_account_key + ) + account_url = "https://127.0.0.1:10002/myaccount" + client = TableServiceClient(account_url, credential=azurite_credential) + assert client.account_name == "myaccount" + assert client.url == "https://127.0.0.1:10002/myaccount" + assert client._location_mode == "primary" + assert client._secondary_endpoint == "https://127.0.0.1:10002/myaccount-secondary" + assert client.credential.named_key.key == azurite_credential.named_key.key + assert client.credential.named_key.name == azurite_credential.named_key.name + assert not client._cosmos_endpoint + + client = TableServiceClient.from_connection_string(http_connstr) + assert client.account_name == "devstoreaccount1" + assert client.url == "http://127.0.0.1:10002/devstoreaccount1" + assert client._location_mode == "primary" + assert client._secondary_endpoint == "http://127.0.0.1:10002/devstoreaccount1-secondary" + assert client.credential.named_key.key == self.tables_primary_storage_account_key + assert client.credential.named_key.name == "devstoreaccount1" + assert not client._cosmos_endpoint + + client = TableServiceClient.from_connection_string(https_connstr) + assert client.account_name == "devstoreaccount1" + assert client.url == "https://127.0.0.1:10002/devstoreaccount1" + assert client._location_mode == "primary" + assert client._secondary_endpoint == "https://127.0.0.1:10002/devstoreaccount1-secondary" + assert client.credential.named_key.key == self.tables_primary_storage_account_key + assert client.credential.named_key.name == "devstoreaccount1" + assert not client._cosmos_endpoint + + table = TableClient(account_url, "tablename", credential=azurite_credential) + assert table.account_name == "myaccount" + assert table.table_name == "tablename" + assert table.url == "https://127.0.0.1:10002/myaccount" + assert table._location_mode == "primary" + assert table._secondary_endpoint == "https://127.0.0.1:10002/myaccount-secondary" + assert table.credential.named_key.key == azurite_credential.named_key.key + assert table.credential.named_key.name == azurite_credential.named_key.name + assert not table._cosmos_endpoint + + table = TableClient.from_connection_string(http_connstr, "tablename") + assert table.account_name == "devstoreaccount1" + assert table.table_name == "tablename" + assert table.url == "http://127.0.0.1:10002/devstoreaccount1" + assert table._location_mode == "primary" + assert table._secondary_endpoint == "http://127.0.0.1:10002/devstoreaccount1-secondary" + assert table.credential.named_key.key == self.tables_primary_storage_account_key + assert table.credential.named_key.name == "devstoreaccount1" + assert not table._cosmos_endpoint + + table = TableClient.from_connection_string(https_connstr, "tablename") + assert table.account_name == "devstoreaccount1" + assert table.table_name == "tablename" + assert table.url == "https://127.0.0.1:10002/devstoreaccount1" + assert table._location_mode == "primary" + assert table._secondary_endpoint == "https://127.0.0.1:10002/devstoreaccount1-secondary" + assert table.credential.named_key.key == self.tables_primary_storage_account_key + assert table.credential.named_key.name == "devstoreaccount1" + assert not table._cosmos_endpoint + + table_url = "https://127.0.0.1:10002/myaccount/Tables('tablename')" + table = TableClient.from_table_url(table_url, azurite_credential) + assert table.account_name == "myaccount" + assert table.table_name == "tablename" + assert table.url == "https://127.0.0.1:10002/myaccount" + assert table._location_mode == "primary" + assert table._secondary_endpoint == "https://127.0.0.1:10002/myaccount-secondary" + assert table.credential.named_key.key == azurite_credential.named_key.key + assert table.credential.named_key.name == azurite_credential.named_key.name + assert not table._cosmos_endpoint \ No newline at end of file diff --git a/sdk/tables/azure-data-tables/tests/test_table_client_async.py b/sdk/tables/azure-data-tables/tests/test_table_client_async.py index 91360d7d67a0..3a3ca94409b4 100644 --- a/sdk/tables/azure-data-tables/tests/test_table_client_async.py +++ b/sdk/tables/azure-data-tables/tests/test_table_client_async.py @@ -419,7 +419,7 @@ async def test_create_service_with_custom_account_endpoint_path_async(self): service = service_type[0].from_connection_string(conn_string, table_name="foo") # Assert - assert service.account_name == "custom" + assert service.account_name == self.tables_storage_account_name assert service.credential.named_key.name == self.tables_storage_account_name assert service.credential.named_key.key == self.tables_primary_storage_account_key assert service._primary_hostname == 'local-machine:11002/custom/account/path' @@ -529,4 +529,82 @@ async def test_create_client_with_api_version(self): assert table._client._config.version == "2019-07-07" with pytest.raises(ValueError): - TableServiceClient(url, credential=self.credential, api_version="foo") \ No newline at end of file + TableServiceClient(url, credential=self.credential, api_version="foo") + + @pytest.mark.asyncio + async def test_create_client_for_azurite(self): + azurite_credential = AzureNamedKeyCredential("myaccount", self.tables_primary_storage_account_key) + http_connstr = "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey={};TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;".format( + self.tables_primary_storage_account_key + ) + https_connstr = "DefaultEndpointsProtocol=https;AccountName=devstoreaccount1;AccountKey={};TableEndpoint=https://127.0.0.1:10002/devstoreaccount1;".format( + self.tables_primary_storage_account_key + ) + account_url = "https://127.0.0.1:10002/myaccount" + client = TableServiceClient(account_url, credential=azurite_credential) + assert client.account_name == "myaccount" + assert client.url == "https://127.0.0.1:10002/myaccount" + assert client._location_mode == "primary" + assert client._secondary_endpoint == "https://127.0.0.1:10002/myaccount-secondary" + assert client.credential.named_key.key == azurite_credential.named_key.key + assert client.credential.named_key.name == azurite_credential.named_key.name + assert not client._cosmos_endpoint + + client = TableServiceClient.from_connection_string(http_connstr) + assert client.account_name == "devstoreaccount1" + assert client.url == "http://127.0.0.1:10002/devstoreaccount1" + assert client._location_mode == "primary" + assert client._secondary_endpoint == "http://127.0.0.1:10002/devstoreaccount1-secondary" + assert client.credential.named_key.key == self.tables_primary_storage_account_key + assert client.credential.named_key.name == "devstoreaccount1" + assert not client._cosmos_endpoint + + client = TableServiceClient.from_connection_string(https_connstr) + assert client.account_name == "devstoreaccount1" + assert client.url == "https://127.0.0.1:10002/devstoreaccount1" + assert client._location_mode == "primary" + assert client._secondary_endpoint == "https://127.0.0.1:10002/devstoreaccount1-secondary" + assert client.credential.named_key.key == self.tables_primary_storage_account_key + assert client.credential.named_key.name == "devstoreaccount1" + assert not client._cosmos_endpoint + + table = TableClient(account_url, "tablename", credential=azurite_credential) + assert table.account_name == "myaccount" + assert table.table_name == "tablename" + assert table.url == "https://127.0.0.1:10002/myaccount" + assert table._location_mode == "primary" + assert table._secondary_endpoint == "https://127.0.0.1:10002/myaccount-secondary" + assert table.credential.named_key.key == azurite_credential.named_key.key + assert table.credential.named_key.name == azurite_credential.named_key.name + assert not table._cosmos_endpoint + + table = TableClient.from_connection_string(http_connstr, "tablename") + assert table.account_name == "devstoreaccount1" + assert table.table_name == "tablename" + assert table.url == "http://127.0.0.1:10002/devstoreaccount1" + assert table._location_mode == "primary" + assert table._secondary_endpoint == "http://127.0.0.1:10002/devstoreaccount1-secondary" + assert table.credential.named_key.key == self.tables_primary_storage_account_key + assert table.credential.named_key.name == "devstoreaccount1" + assert not table._cosmos_endpoint + + table = TableClient.from_connection_string(https_connstr, "tablename") + assert table.account_name == "devstoreaccount1" + assert table.table_name == "tablename" + assert table.url == "https://127.0.0.1:10002/devstoreaccount1" + assert table._location_mode == "primary" + assert table._secondary_endpoint == "https://127.0.0.1:10002/devstoreaccount1-secondary" + assert table.credential.named_key.key == self.tables_primary_storage_account_key + assert table.credential.named_key.name == "devstoreaccount1" + assert not table._cosmos_endpoint + + table_url = "https://127.0.0.1:10002/myaccount/Tables('tablename')" + table = TableClient.from_table_url(table_url, azurite_credential) + assert table.account_name == "myaccount" + assert table.table_name == "tablename" + assert table.url == "https://127.0.0.1:10002/myaccount" + assert table._location_mode == "primary" + assert table._secondary_endpoint == "https://127.0.0.1:10002/myaccount-secondary" + assert table.credential.named_key.key == azurite_credential.named_key.key + assert table.credential.named_key.name == azurite_credential.named_key.name + assert not table._cosmos_endpoint diff --git a/sdk/tables/azure-data-tables/tests/test_table_client_cosmos.py b/sdk/tables/azure-data-tables/tests/test_table_client_cosmos.py index 8d506d8a97cf..4fee653d0e81 100644 --- a/sdk/tables/azure-data-tables/tests/test_table_client_cosmos.py +++ b/sdk/tables/azure-data-tables/tests/test_table_client_cosmos.py @@ -430,7 +430,7 @@ def test_create_service_with_custom_account_endpoint_path(self): service = service_type[0].from_connection_string(conn_string, table_name="foo") # Assert - assert service.account_name == "custom" + assert service.account_name == self.tables_cosmos_account_name assert service.credential.named_key.name == self.tables_cosmos_account_name assert service.credential.named_key.key == self.tables_primary_cosmos_account_key assert service._primary_hostname == 'local-machine:11002/custom/account/path' @@ -508,6 +508,50 @@ def test_error_with_malformed_conn_str(self): elif conn_str in ("foobar=baz=foo"): assert str(e.value) == "Connection string missing required connection details." + def test_create_client_for_cosmos_emulator(self): + emulator_credential = AzureNamedKeyCredential('localhost', self.tables_primary_cosmos_account_key) + emulator_connstr = "DefaultEndpointsProtocol=http;AccountName=localhost;AccountKey={};TableEndpoint=http://localhost:8902/;".format( + self.tables_primary_cosmos_account_key + ) + + client = TableServiceClient.from_connection_string(emulator_connstr) + assert client.url == "http://localhost:8902" + assert client.account_name == 'localhost' + assert client.credential.named_key.name == 'localhost' + assert client.credential.named_key.key == self.tables_primary_cosmos_account_key + assert client._cosmos_endpoint + + client = TableServiceClient("http://localhost:8902/", emulator_credential) + assert client.url == "http://localhost:8902" + assert client.account_name == 'localhost' + assert client.credential.named_key.name == 'localhost' + assert client.credential.named_key.key == self.tables_primary_cosmos_account_key + assert client._cosmos_endpoint + + table = TableClient.from_connection_string(emulator_connstr, 'tablename') + assert table.url == "http://localhost:8902" + assert table.account_name == 'localhost' + assert table.table_name == 'tablename' + assert table.credential.named_key.name == 'localhost' + assert table.credential.named_key.key == self.tables_primary_cosmos_account_key + assert table._cosmos_endpoint + + table = TableClient("http://localhost:8902/", "tablename", emulator_credential) + assert table.url == "http://localhost:8902" + assert table.account_name == 'localhost' + assert table.table_name == 'tablename' + assert table.credential.named_key.name == 'localhost' + assert table.credential.named_key.key == self.tables_primary_cosmos_account_key + assert table._cosmos_endpoint + + table = TableClient.from_table_url("http://localhost:8902/Tables('tablename')", emulator_credential) + assert table.url == "http://localhost:8902" + assert table.account_name == 'localhost' + assert table.table_name == 'tablename' + assert table.credential.named_key.name == 'localhost' + assert table.credential.named_key.key == self.tables_primary_cosmos_account_key + assert table._cosmos_endpoint + def test_closing_pipeline_client(self): # Arrange for client, url in SERVICES.items(): diff --git a/sdk/tables/azure-data-tables/tests/test_table_client_cosmos_async.py b/sdk/tables/azure-data-tables/tests/test_table_client_cosmos_async.py index 8630f231f4d3..9276e3d3c2da 100644 --- a/sdk/tables/azure-data-tables/tests/test_table_client_cosmos_async.py +++ b/sdk/tables/azure-data-tables/tests/test_table_client_cosmos_async.py @@ -421,7 +421,7 @@ async def test_create_service_with_custom_account_endpoint_path_async(self): service = service_type[0].from_connection_string(conn_string, table_name="foo") # Assert - assert service.account_name == "custom" + assert service.account_name == self.tables_cosmos_account_name assert service.credential.named_key.name == self.tables_cosmos_account_name assert service.credential.named_key.key == self.tables_primary_cosmos_account_key assert service._primary_hostname == 'local-machine:11002/custom/account/path' @@ -496,6 +496,50 @@ async def test_error_with_malformed_conn_str_async(self): elif conn_str in ("foobar=baz=foo"): assert str(e.value) == "Connection string missing required connection details." + def test_create_client_for_cosmos_emulator(self): + emulator_credential = AzureNamedKeyCredential('localhost', self.tables_primary_cosmos_account_key) + emulator_connstr = "DefaultEndpointsProtocol=http;AccountName=localhost;AccountKey={};TableEndpoint=http://localhost:8902/;".format( + self.tables_primary_cosmos_account_key + ) + + client = TableServiceClient.from_connection_string(emulator_connstr) + assert client.url == "http://localhost:8902" + assert client.account_name == 'localhost' + assert client.credential.named_key.name == 'localhost' + assert client.credential.named_key.key == self.tables_primary_cosmos_account_key + assert client._cosmos_endpoint + + client = TableServiceClient("http://localhost:8902/", emulator_credential) + assert client.url == "http://localhost:8902" + assert client.account_name == 'localhost' + assert client.credential.named_key.name == 'localhost' + assert client.credential.named_key.key == self.tables_primary_cosmos_account_key + assert client._cosmos_endpoint + + table = TableClient.from_connection_string(emulator_connstr, 'tablename') + assert table.url == "http://localhost:8902" + assert table.account_name == 'localhost' + assert table.table_name == 'tablename' + assert table.credential.named_key.name == 'localhost' + assert table.credential.named_key.key == self.tables_primary_cosmos_account_key + assert table._cosmos_endpoint + + table = TableClient("http://localhost:8902/", "tablename", emulator_credential) + assert table.url == "http://localhost:8902" + assert table.account_name == 'localhost' + assert table.table_name == 'tablename' + assert table.credential.named_key.name == 'localhost' + assert table.credential.named_key.key == self.tables_primary_cosmos_account_key + assert table._cosmos_endpoint + + table = TableClient.from_table_url("http://localhost:8902/Tables('tablename')", emulator_credential) + assert table.url == "http://localhost:8902" + assert table.account_name == 'localhost' + assert table.table_name == 'tablename' + assert table.credential.named_key.name == 'localhost' + assert table.credential.named_key.key == self.tables_primary_cosmos_account_key + assert table._cosmos_endpoint + @pytest.mark.asyncio async def test_closing_pipeline_client_async(self): # Arrange diff --git a/sdk/tables/azure-data-tables/tests/test_table_entity.py b/sdk/tables/azure-data-tables/tests/test_table_entity.py index 160bc2f4a2ae..eb83a34345c4 100644 --- a/sdk/tables/azure-data-tables/tests/test_table_entity.py +++ b/sdk/tables/azure-data-tables/tests/test_table_entity.py @@ -480,7 +480,7 @@ def test_insert_entity_missing_pk(self, tables_storage_account_name, tables_prim # Arrange self._set_up(tables_storage_account_name, tables_primary_storage_account_key) try: - entity = {'RowKey': 'rk'} + entity = {'RowKey': u'rk'} # Act with pytest.raises(ValueError) as error: @@ -508,7 +508,7 @@ def test_insert_entity_missing_rk(self, tables_storage_account_name, tables_prim # Arrange self._set_up(tables_storage_account_name, tables_primary_storage_account_key) try: - entity = {'PartitionKey': 'pk'} + entity = {'PartitionKey': u'pk'} # Act with pytest.raises(ValueError) as error: @@ -1271,15 +1271,15 @@ def test_empty_and_spaces_property_value(self, tables_storage_account_name, tabl # Assert assert resp is not None - assert resp['EmptyByte'].value == b'' + assert resp['EmptyByte'] == b'' assert resp['EmptyUnicode'] == u'' - assert resp['SpacesOnlyByte'].value == b' ' + assert resp['SpacesOnlyByte'] == b' ' assert resp['SpacesOnlyUnicode'] == u' ' - assert resp['SpacesBeforeByte'].value == b' Text' + assert resp['SpacesBeforeByte'] == b' Text' assert resp['SpacesBeforeUnicode'] == u' Text' - assert resp['SpacesAfterByte'].value == b'Text ' + assert resp['SpacesAfterByte'] == b'Text ' assert resp['SpacesAfterUnicode'] == u'Text ' - assert resp['SpacesBeforeAndAfterByte'].value == b' Text ' + assert resp['SpacesBeforeAndAfterByte'] == b' Text ' assert resp['SpacesBeforeAndAfterUnicode'] == u' Text ' finally: self._tear_down() @@ -1317,7 +1317,7 @@ def test_binary_property_value(self, tables_storage_account_name, tables_primary # Assert assert resp is not None - assert resp['binary'].value == binary_data + assert resp['binary'] == binary_data finally: self._tear_down() diff --git a/sdk/tables/azure-data-tables/tests/test_table_entity_async.py b/sdk/tables/azure-data-tables/tests/test_table_entity_async.py index 8619a4a7d705..39aae73d0a24 100644 --- a/sdk/tables/azure-data-tables/tests/test_table_entity_async.py +++ b/sdk/tables/azure-data-tables/tests/test_table_entity_async.py @@ -993,15 +993,15 @@ async def test_empty_and_spaces_property_value(self, tables_storage_account_name try: entity = self._create_random_base_entity_dict() entity.update({ - 'EmptyByte': '', + 'EmptyByte': b'', 'EmptyUnicode': u'', - 'SpacesOnlyByte': ' ', + 'SpacesOnlyByte': b' ', 'SpacesOnlyUnicode': u' ', - 'SpacesBeforeByte': ' Text', + 'SpacesBeforeByte': b' Text', 'SpacesBeforeUnicode': u' Text', - 'SpacesAfterByte': 'Text ', + 'SpacesAfterByte': b'Text ', 'SpacesAfterUnicode': u'Text ', - 'SpacesBeforeAndAfterByte': ' Text ', + 'SpacesBeforeAndAfterByte': b' Text ', 'SpacesBeforeAndAfterUnicode': u' Text ', }) @@ -1011,15 +1011,15 @@ async def test_empty_and_spaces_property_value(self, tables_storage_account_name # Assert assert resp is not None - assert resp['EmptyByte'] == '' + assert resp['EmptyByte'] == b'' assert resp['EmptyUnicode'] == u'' - assert resp['SpacesOnlyByte'] == ' ' + assert resp['SpacesOnlyByte'] == b' ' assert resp['SpacesOnlyUnicode'] == u' ' - assert resp['SpacesBeforeByte'] == ' Text' + assert resp['SpacesBeforeByte'] == b' Text' assert resp['SpacesBeforeUnicode'] == u' Text' - assert resp['SpacesAfterByte'] == 'Text ' + assert resp['SpacesAfterByte'] == b'Text ' assert resp['SpacesAfterUnicode'] == u'Text ' - assert resp['SpacesBeforeAndAfterByte'] == ' Text ' + assert resp['SpacesBeforeAndAfterByte'] == b' Text ' assert resp['SpacesBeforeAndAfterUnicode'] == u' Text ' finally: await self._tear_down() @@ -1057,7 +1057,7 @@ async def test_binary_property_value(self, tables_storage_account_name, tables_p # Assert assert resp is not None - assert resp['binary'].value == binary_data + assert resp['binary'] == binary_data finally: await self._tear_down() diff --git a/sdk/tables/azure-data-tables/tests/test_table_entity_cosmos.py b/sdk/tables/azure-data-tables/tests/test_table_entity_cosmos.py index 55a368c2a736..0addcc35c9e7 100644 --- a/sdk/tables/azure-data-tables/tests/test_table_entity_cosmos.py +++ b/sdk/tables/azure-data-tables/tests/test_table_entity_cosmos.py @@ -474,7 +474,7 @@ def test_insert_entity_missing_pk(self, tables_cosmos_account_name, tables_prima # Arrange self._set_up(tables_cosmos_account_name, tables_primary_cosmos_account_key, url="cosmos") try: - entity = {'RowKey': 'rk'} + entity = {'RowKey': u'rk'} # Act with pytest.raises(ValueError) as error: @@ -488,7 +488,7 @@ def test_insert_entity_empty_string_pk(self, tables_cosmos_account_name, tables_ # Arrange self._set_up(tables_cosmos_account_name, tables_primary_cosmos_account_key, url="cosmos") try: - entity = {'RowKey': 'rk', 'PartitionKey': ''} + entity = {'RowKey': u'rk', 'PartitionKey': u''} # Act with pytest.raises(HttpResponseError): @@ -504,7 +504,7 @@ def test_insert_entity_missing_rk(self, tables_cosmos_account_name, tables_prima # Arrange self._set_up(tables_cosmos_account_name, tables_primary_cosmos_account_key, url="cosmos") try: - entity = {'PartitionKey': 'pk'} + entity = {'PartitionKey': u'pk'} # Act with pytest.raises(ValueError) as error: @@ -520,7 +520,7 @@ def test_insert_entity_empty_string_rk(self, tables_cosmos_account_name, tables_ # Arrange self._set_up(tables_cosmos_account_name, tables_primary_cosmos_account_key, url="cosmos") try: - entity = {'PartitionKey': 'pk', 'RowKey': ''} + entity = {'PartitionKey': u'pk', 'RowKey': u''} # Act with pytest.raises(HttpResponseError): @@ -557,12 +557,10 @@ def test_get_entity_with_select(self, tables_cosmos_account_name, tables_primary resp = self.table.get_entity(partition_key=entity['PartitionKey'], row_key=entity['RowKey'], select=['age', 'ratio']) - resp.pop('_metadata', None) assert resp == {'age': 39, 'ratio': 3.1} resp = self.table.get_entity(partition_key=entity['PartitionKey'], row_key=entity['RowKey'], select='age,ratio') - resp.pop('_metadata', None) assert resp == {'age': 39, 'ratio': 3.1} finally: self._tear_down() @@ -1206,15 +1204,15 @@ def test_empty_and_spaces_property_value(self, tables_cosmos_account_name, table resp = self.table.get_entity(entity['PartitionKey'], entity['RowKey']) # Assert - assert resp['EmptyByte'].value == b'' + assert resp['EmptyByte'] == b'' assert resp['EmptyUnicode'] == u'' - assert resp['SpacesOnlyByte'].value == b' ' + assert resp['SpacesOnlyByte'] == b' ' assert resp['SpacesOnlyUnicode'] == u' ' - assert resp['SpacesBeforeByte'].value == b' Text' + assert resp['SpacesBeforeByte'] == b' Text' assert resp['SpacesBeforeUnicode'] == u' Text' - assert resp['SpacesAfterByte'].value == b'Text ' + assert resp['SpacesAfterByte'] == b'Text ' assert resp['SpacesAfterUnicode'] == u'Text ' - assert resp['SpacesBeforeAndAfterByte'].value == b' Text ' + assert resp['SpacesBeforeAndAfterByte'] == b' Text ' assert resp['SpacesBeforeAndAfterUnicode'] == u' Text ' finally: self._tear_down() @@ -1252,7 +1250,7 @@ def test_binary_property_value(self, tables_cosmos_account_name, tables_primary_ # Assert assert resp is not None - assert resp['binary'].value == binary_data + assert resp['binary'] == binary_data finally: self._tear_down() diff --git a/sdk/tables/azure-data-tables/tests/test_table_entity_cosmos_async.py b/sdk/tables/azure-data-tables/tests/test_table_entity_cosmos_async.py index a13efe558cd6..8dbfc9566482 100644 --- a/sdk/tables/azure-data-tables/tests/test_table_entity_cosmos_async.py +++ b/sdk/tables/azure-data-tables/tests/test_table_entity_cosmos_async.py @@ -942,15 +942,15 @@ async def test_empty_and_spaces_property_value(self, tables_cosmos_account_name, try: entity = self._create_random_base_entity_dict() entity.update({ - 'EmptyByte': '', + 'EmptyByte': b'', 'EmptyUnicode': u'', - 'SpacesOnlyByte': ' ', + 'SpacesOnlyByte': b' ', 'SpacesOnlyUnicode': u' ', - 'SpacesBeforeByte': ' Text', + 'SpacesBeforeByte': b' Text', 'SpacesBeforeUnicode': u' Text', - 'SpacesAfterByte': 'Text ', + 'SpacesAfterByte': b'Text ', 'SpacesAfterUnicode': u'Text ', - 'SpacesBeforeAndAfterByte': ' Text ', + 'SpacesBeforeAndAfterByte': b' Text ', 'SpacesBeforeAndAfterUnicode': u' Text ', }) @@ -960,15 +960,15 @@ async def test_empty_and_spaces_property_value(self, tables_cosmos_account_name, # Assert assert resp is not None - assert resp['EmptyByte'] == '' + assert resp['EmptyByte'] == b'' assert resp['EmptyUnicode'] == u'' - assert resp['SpacesOnlyByte'] == ' ' + assert resp['SpacesOnlyByte'] == b' ' assert resp['SpacesOnlyUnicode'] == u' ' - assert resp['SpacesBeforeByte'] == ' Text' + assert resp['SpacesBeforeByte'] == b' Text' assert resp['SpacesBeforeUnicode'] == u' Text' - assert resp['SpacesAfterByte'] == 'Text ' + assert resp['SpacesAfterByte'] == b'Text ' assert resp['SpacesAfterUnicode'] == u'Text ' - assert resp['SpacesBeforeAndAfterByte'] == ' Text ' + assert resp['SpacesBeforeAndAfterByte'] == b' Text ' assert resp['SpacesBeforeAndAfterUnicode'] == u' Text ' finally: await self._tear_down() @@ -1006,7 +1006,7 @@ async def test_binary_property_value(self, tables_cosmos_account_name, tables_pr # Assert assert resp is not None - assert resp['binary'].value == binary_data + assert resp['binary'] == binary_data finally: await self._tear_down()