Skip to content

Commit f0661e3

Browse files
authored
[Tables] Emulator tests and binary serialization (Azure#18829)
* Deserialize to binary * Updates for emulator support * Pylint * Changelog and tests
1 parent a107e55 commit f0661e3

21 files changed

+334
-126
lines changed

sdk/tables/azure-data-tables/CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Release History
22

3+
## 12.0.0 (unreleased)
4+
**Breaking**
5+
* 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)
6+
7+
**Fixes**
8+
* Fixed support for Cosmos emulator endpoint, via URL/credential or connection string.
9+
* Fixed table name from URL parsing in `TableClient.from_table_url` classmethod.
10+
* The `account_name` attribute on clients will now be pulled from an `AzureNamedKeyCredential` if used.
11+
312
## 12.0.0b7 (2021-05-11)
413
**Breaking**
514
* The `account_url` parameter in the client constructors has been renamed to `endpoint`.

sdk/tables/azure-data-tables/azure/data/tables/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from azure.data.tables._models import TableServiceStats
77

88
from ._entity import TableEntity, EntityProperty, EdmType
9-
from ._error import RequestTooLargeError, TableTransactionError
9+
from ._error import RequestTooLargeError, TableTransactionError, TableErrorCode
1010
from ._table_shared_access_signature import generate_table_sas, generate_account_sas
1111
from ._table_client import TableClient
1212
from ._table_service_client import TableServiceClient
@@ -26,7 +26,6 @@
2626
TransactionOperation
2727
)
2828
from ._version import VERSION
29-
from ._deserialize import TableErrorCode
3029

3130
__version__ = VERSION
3231

sdk/tables/azure-data-tables/azure/data/tables/_base_client.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ def __init__(
8080
account_url = "https://" + account_url
8181
except AttributeError:
8282
raise ValueError("Account URL must be a string.")
83-
self._cosmos_endpoint = _is_cosmos_endpoint(account_url)
8483
parsed_url = urlparse(account_url.rstrip("/"))
8584
if not parsed_url.netloc:
8685
raise ValueError("Invalid URL: {}".format(account_url))
@@ -94,7 +93,7 @@ def __init__(
9493
self._location_mode = kwargs.get("location_mode", LocationMode.PRIMARY)
9594
self._hosts = kwargs.get("_hosts")
9695
self.scheme = parsed_url.scheme
97-
self._cosmos_endpoint = _is_cosmos_endpoint(parsed_url.hostname)
96+
self._cosmos_endpoint = _is_cosmos_endpoint(parsed_url)
9897
if ".core." in parsed_url.netloc or ".cosmos." in parsed_url.netloc:
9998
account = parsed_url.netloc.split(".table.core.")
10099
if "cosmos" in parsed_url.netloc:
@@ -114,17 +113,19 @@ def __init__(
114113
self.credential = credential
115114
if self.scheme.lower() != "https" and hasattr(self.credential, "get_token"):
116115
raise ValueError("Token credential is only supported with HTTPS.")
117-
if hasattr(self.credential, "account_name"):
118-
self.account_name = self.credential.account_name
116+
if hasattr(self.credential, "named_key"):
117+
self.account_name = self.credential.named_key.name
119118
secondary_hostname = "{}-secondary.table.{}".format(
120-
self.credential.account_name, SERVICE_HOST_BASE
119+
self.credential.named_key.name, SERVICE_HOST_BASE
121120
)
122121

123122
if not self._hosts:
124123
if len(account) > 1:
125124
secondary_hostname = parsed_url.netloc.replace(
126125
account[0], account[0] + "-secondary"
127-
)
126+
) + parsed_url.path.replace(
127+
account[0], account[0] + "-secondary"
128+
).rstrip("/")
128129
if kwargs.get("secondary_hostname"):
129130
secondary_hostname = kwargs["secondary_hostname"]
130131
primary_hostname = (parsed_url.netloc + parsed_url.path).rstrip("/")
@@ -346,7 +347,6 @@ def parse_connection_str(conn_str, credential, keyword_args):
346347
credential = conn_settings.get("sharedaccesssignature")
347348
# if "sharedaccesssignature" in conn_settings:
348349
# credential = AzureSasCredential(conn_settings['sharedaccesssignature'])
349-
350350
primary = conn_settings.get("tableendpoint")
351351
secondary = conn_settings.get("tablesecondaryendpoint")
352352
if not primary:

sdk/tables/azure-data-tables/azure/data/tables/_common_conversion.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,12 @@ def _sign_string(key, string_to_sign, key_is_base64=True):
8989

9090

9191
def _is_cosmos_endpoint(url):
92-
if ".table.cosmodb." in url:
92+
if ".table.cosmodb." in url.hostname:
9393
return True
94-
95-
if ".table.cosmos." in url:
94+
if ".table.cosmos." in url.hostname:
95+
return True
96+
if url.hostname == "localhost" and url.port != 10002:
9697
return True
97-
9898
return False
9999

100100

sdk/tables/azure-data-tables/azure/data/tables/_deserialize.py

Lines changed: 6 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,14 @@
44
# license information.
55
# --------------------------------------------------------------------------
66

7-
from typing import TYPE_CHECKING
87
from uuid import UUID
98
import logging
109
import datetime
1110

12-
from azure.core.exceptions import ResourceExistsError
11+
import six
1312

1413
from ._entity import EntityProperty, EdmType, TableEntity
1514
from ._common_conversion import _decode_base64_to_bytes, TZ_UTC
16-
from ._error import TableErrorCode
17-
18-
if TYPE_CHECKING:
19-
from azure.core.exceptions import AzureError
2015

2116

2217
_LOGGER = logging.getLogger(__name__)
@@ -26,18 +21,6 @@
2621
except ImportError:
2722
from urllib2 import quote # type: ignore
2823

29-
if TYPE_CHECKING:
30-
from typing import ( # pylint: disable=ungrouped-imports
31-
Union,
32-
Optional,
33-
Any,
34-
Iterable,
35-
Dict,
36-
List,
37-
Type,
38-
Tuple,
39-
)
40-
4124

4225
class TablesEntityDatetime(datetime.datetime):
4326

@@ -62,29 +45,14 @@ def get_enum_value(value):
6245
return value
6346

6447

65-
def _deserialize_table_creation(response, _, headers):
66-
if response.status_code == 204:
67-
error_code = TableErrorCode.table_already_exists
68-
error = ResourceExistsError(
69-
message="Table already exists\nRequestId:{}\nTime:{}\nErrorCode:{}".format(
70-
headers["x-ms-request-id"], headers["Date"], error_code
71-
),
72-
response=response,
73-
)
74-
error.error_code = error_code
75-
error.additional_info = {}
76-
raise error
77-
return headers
78-
79-
8048
def _from_entity_binary(value):
8149
# type: (str) -> EntityProperty
82-
return EntityProperty(_decode_base64_to_bytes(value), EdmType.BINARY)
50+
return _decode_base64_to_bytes(value)
8351

8452

8553
def _from_entity_int32(value):
8654
# type: (str) -> EntityProperty
87-
return EntityProperty(int(value), EdmType.INT32)
55+
return int(value)
8856

8957

9058
def _from_entity_int64(value):
@@ -129,8 +97,9 @@ def _from_entity_guid(value):
12997

13098
def _from_entity_str(value):
13199
# type: (str) -> EntityProperty
132-
return EntityProperty(value, EdmType.STRING)
133-
100+
if isinstance(six.binary_type):
101+
return value.decode('utf-8')
102+
return value
134103

135104
_EDM_TYPES = [
136105
EdmType.BINARY,

sdk/tables/azure-data-tables/azure/data/tables/_table_batch.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
Optional
1313
)
1414

15-
from ._common_conversion import _is_cosmos_endpoint, _transform_patch_to_cosmos_post
15+
from ._common_conversion import _transform_patch_to_cosmos_post
1616
from ._models import UpdateMode
1717
from ._serialize import _get_match_headers, _add_entity_properties
1818
from ._entity import TableEntity
@@ -43,6 +43,7 @@ def __init__(
4343
deserializer, # type: msrest.Deserializer
4444
config, # type: AzureTableConfiguration
4545
table_name, # type: str
46+
is_cosmos_endpoint=False, # type: bool
4647
**kwargs # type: Dict[str, Any]
4748
):
4849
"""Create TableClient from a Credential.
@@ -66,6 +67,7 @@ def __init__(
6667
self._serialize = serializer
6768
self._deserialize = deserializer
6869
self._config = config
70+
self._is_cosmos_endpoint = is_cosmos_endpoint
6971
self.table_name = table_name
7072

7173
self._partition_key = kwargs.pop("partition_key", None)
@@ -485,7 +487,7 @@ def _batch_merge_entity(
485487
request = self._client._client.patch( # pylint: disable=protected-access
486488
url, query_parameters, header_parameters, **body_content_kwargs
487489
)
488-
if _is_cosmos_endpoint(url):
490+
if self._is_cosmos_endpoint:
489491
_transform_patch_to_cosmos_post(request)
490492
self.requests.append(request)
491493

sdk/tables/azure-data-tables/azure/data/tables/_table_client.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,8 @@ def from_table_url(cls, table_url, credential=None, **kwargs):
152152
parsed_url.query,
153153
)
154154
table_name = unquote(table_path[-1])
155+
if table_name.lower().startswith("tables('"):
156+
table_name = table_name[8:-2]
155157
if not table_name:
156158
raise ValueError(
157159
"Invalid URL. Please provide a URL with a valid table name"
@@ -705,6 +707,7 @@ def submit_transaction(
705707
self._client._deserialize, # pylint: disable=protected-access
706708
self._client._config, # pylint: disable=protected-access
707709
self.table_name,
710+
is_cosmos_endpoint=self._cosmos_endpoint,
708711
**kwargs
709712
)
710713
for operation in operations:

sdk/tables/azure-data-tables/azure/data/tables/aio/_table_batch_async.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from typing import Dict, Any, Optional, Union, TYPE_CHECKING
77
import msrest
88

9-
from .._common_conversion import _is_cosmos_endpoint, _transform_patch_to_cosmos_post
9+
from .._common_conversion import _transform_patch_to_cosmos_post
1010
from .._models import UpdateMode
1111
from .._entity import TableEntity
1212
from .._table_batch import EntityType
@@ -41,12 +41,14 @@ def __init__(
4141
deserializer: msrest.Deserializer,
4242
config: AzureTableConfiguration,
4343
table_name: str,
44+
is_cosmos_endpoint: bool = False,
4445
**kwargs: Dict[str, Any]
4546
) -> None:
4647
self._client = client
4748
self._serialize = serializer
4849
self._deserialize = deserializer
4950
self._config = config
51+
self._is_cosmos_endpoint = is_cosmos_endpoint
5052
self.table_name = table_name
5153

5254
self._partition_key = kwargs.pop("partition_key", None)
@@ -456,7 +458,7 @@ def _batch_merge_entity(
456458
request = self._client._client.patch( # pylint: disable=protected-access
457459
url, query_parameters, header_parameters, **body_content_kwargs
458460
)
459-
if _is_cosmos_endpoint(url):
461+
if self._is_cosmos_endpoint:
460462
_transform_patch_to_cosmos_post(request)
461463
self.requests.append(request)
462464

sdk/tables/azure-data-tables/azure/data/tables/aio/_table_client_async.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ def from_table_url(
149149
parsed_url.query,
150150
)
151151
table_name = unquote(table_path[-1])
152+
if table_name.lower().startswith("tables('"):
153+
table_name = table_name[8:-2]
152154
if not table_name:
153155
raise ValueError(
154156
"Invalid URL. Please provide a URL with a valid table name"
@@ -689,6 +691,7 @@ async def submit_transaction(
689691
self._client._deserialize, # pylint: disable=protected-access
690692
self._client._config, # pylint: disable=protected-access
691693
self.table_name,
694+
is_cosmos_endpoint=self._cosmos_endpoint,
692695
**kwargs
693696
)
694697
for operation in operations:

sdk/tables/azure-data-tables/samples/sample_update_upsert_merge_entities.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,22 +144,22 @@ def update_entities(self):
144144
insert_entity = table.upsert_entity(mode=UpdateMode.REPLACE, entity=entity1)
145145
print("Inserted entity: {}".format(insert_entity))
146146

147-
created["text"] = "NewMarker"
147+
created[u"text"] = u"NewMarker"
148148
merged_entity = table.upsert_entity(mode=UpdateMode.MERGE, entity=entity)
149149
print("Merged entity: {}".format(merged_entity))
150150
# [END upsert_entity]
151151

152152
# [START update_entity]
153153
# Update the entity
154-
created["text"] = "NewMarker"
154+
created[u"text"] = u"NewMarker"
155155
table.update_entity(mode=UpdateMode.REPLACE, entity=created)
156156

157157
# Get the replaced entity
158158
replaced = table.get_entity(partition_key=created["PartitionKey"], row_key=created["RowKey"])
159159
print("Replaced entity: {}".format(replaced))
160160

161161
# Merge the entity
162-
replaced["color"] = "Blue"
162+
replaced[u"color"] = u"Blue"
163163
table.update_entity(mode=UpdateMode.MERGE, entity=replaced)
164164

165165
# Get the merged entity

sdk/tables/azure-data-tables/tests/_shared/testcase.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ def _assert_default_entity(self, entity):
176176
assert entity["large"] == 933311100
177177
assert entity["Birthday"] == datetime(1973, 10, 4, tzinfo=tzutc())
178178
assert entity["birthday"] == datetime(1970, 10, 4, tzinfo=tzutc())
179-
assert entity["binary"].value == b"binary"
179+
assert entity["binary"] == b"binary"
180180
assert entity["other"] == 20
181181
assert entity["clsid"] == uuid.UUID("c9da6455-213d-42c9-9a79-3e9149a57833")
182182
assert entity.metadata["etag"]
@@ -197,7 +197,7 @@ def _assert_default_entity_json_full_metadata(self, entity, headers=None):
197197
assert entity["large"] == 933311100
198198
assert entity["Birthday"] == datetime(1973, 10, 4, tzinfo=tzutc())
199199
assert entity["birthday"] == datetime(1970, 10, 4, tzinfo=tzutc())
200-
assert entity["binary"].value == b"binary"
200+
assert entity["binary"] == b"binary"
201201
assert entity["other"] == 20
202202
assert entity["clsid"] == uuid.UUID("c9da6455-213d-42c9-9a79-3e9149a57833")
203203
assert entity.metadata["etag"]

sdk/tables/azure-data-tables/tests/test_table.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -468,13 +468,3 @@ def test_delete_table_invalid_name(self):
468468

469469
assert "Table names must be alphanumeric, cannot begin with a number, and must be between 3-63 characters long.""" in str(
470470
excinfo)
471-
472-
def test_azurite_url(self):
473-
account_url = "https://127.0.0.1:10002/my_account"
474-
tsc = TableServiceClient(account_url, credential=self.credential)
475-
476-
assert tsc.account_name == "my_account"
477-
assert tsc.url == "https://127.0.0.1:10002/my_account"
478-
assert tsc._location_mode == "primary"
479-
assert tsc.credential.named_key.key == self.credential.named_key.key
480-
assert tsc.credential.named_key.name == self.credential.named_key.name

sdk/tables/azure-data-tables/tests/test_table_async.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -398,13 +398,3 @@ async def test_delete_table_invalid_name(self):
398398

399399
assert "Table names must be alphanumeric, cannot begin with a number, and must be between 3-63 characters long.""" in str(
400400
excinfo)
401-
402-
def test_azurite_url(self):
403-
account_url = "https://127.0.0.1:10002/my_account"
404-
tsc = TableServiceClient(account_url, credential=self.credential)
405-
406-
assert tsc.account_name == "my_account"
407-
assert tsc.url == "https://127.0.0.1:10002/my_account"
408-
assert tsc._location_mode == "primary"
409-
assert tsc.credential.named_key.key == self.credential.named_key.key
410-
assert tsc.credential.named_key.name == self.credential.named_key.name

0 commit comments

Comments
 (0)