Skip to content

[Tables] Updating EntityProperty #18177

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
May 4, 2021
3 changes: 3 additions & 0 deletions sdk/tables/azure-data-tables/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
* Removed the `TableClient.create_batch` method along with the `TableBatchOperations` object. The transactional batching is now supported via a simple Python list of tuples.
* `TableClient.send_batch` has been renamed to `TableClient.submit_transaction`.
* Removed `BatchTransactionResult` object in favor of returning an iterable of batched entities with returned metadata.
* Removed Batching context-manager behavior
* Changed optional `value` and `type` arguments of `EntityProperty` to required.
* Renamed `EntityProperty.type` to `EntityProperty.edm_type`.
* `BatchErrorException` has been renamed to `TableTransactionError`.

**Fixes**
Expand Down
10 changes: 7 additions & 3 deletions sdk/tables/azure-data-tables/azure/data/tables/_deserialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,17 @@ def _deserialize_table_creation(response, _, headers):


def _from_entity_binary(value):
return EntityProperty(_decode_base64_to_bytes(value))
# type: (str) -> EntityProperty
return EntityProperty(_decode_base64_to_bytes(value), EdmType.BINARY)


def _from_entity_int32(value):
return EntityProperty(int(value))
# type: (str) -> EntityProperty
return EntityProperty(int(value), EdmType.INT32)


def _from_entity_int64(value):
# type: (str) -> EntityProperty
return EntityProperty(int(value), EdmType.INT64)


Expand Down Expand Up @@ -125,7 +128,8 @@ def _from_entity_guid(value):


def _from_entity_str(value):
return EntityProperty(value=value, type=EdmType.STRING)
# type: (str) -> EntityProperty
return EntityProperty(value, EdmType.STRING)


_EDM_TYPES = [
Expand Down
87 changes: 20 additions & 67 deletions sdk/tables/azure-data-tables/azure/data/tables/_entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@
# license information.
# --------------------------------------------------------------------------
from enum import Enum
from datetime import datetime
from uuid import UUID
import six
from typing import Any, Dict, Union, NamedTuple

from ._error import _ERROR_ATTRIBUTE_MISSING, _ERROR_VALUE_TOO_LARGE
from ._error import _ERROR_ATTRIBUTE_MISSING


class TableEntity(dict):
Expand Down Expand Up @@ -68,69 +66,6 @@ def __dir__(self):
return dir({}) + list(self.keys())


class EntityProperty(object):
"""
An entity property. Used to explicitly set :class:`~EdmType` when necessary.

Values which require explicit typing are GUID, INT64, and BINARY. Other EdmTypes
may be explicitly create as EntityProperty objects but need not be. For example,
the below with both create STRING typed properties on the entity::
entity = TableEntity()
entity.a = 'b'
entity.x = EntityProperty('y', EdmType.STRING)
"""

def __init__(
self,
value=None, # type: Any
type=None, # type: Union[str,EdmType] pylint: disable=redefined-builtin
):
"""
Represents an Azure Table. Returned by list_tables.

:param type: The type of the property.
:type type: str or EdmType
:param Any value: The value of the property.
"""
self.value = value
if type is not None:
self.type = type
elif isinstance(value, six.text_type):
try:
self.value = UUID(value)
self.type = EdmType.GUID
except ValueError:
self.type = EdmType.STRING
elif isinstance(value, six.binary_type):
self.type = EdmType.BINARY
elif isinstance(value, bool):
self.type = EdmType.BOOLEAN
elif isinstance(value, six.integer_types):
if value.bit_length() <= 32:
self.type = EdmType.INT32
else:
raise TypeError(
_ERROR_VALUE_TOO_LARGE.format(str(value), EdmType.INT32)
)
elif isinstance(value, datetime):
self.type = EdmType.DATETIME
elif isinstance(value, float):
self.type = EdmType.DOUBLE
else:
raise ValueError(
"""Type of {} could not be inferred. Acceptable types are bytes, int, uuid.UUID,
datetime, string, int32, int64, float, and boolean. Refer to
azure.data.tables.EdmType for more information.
""".format(
value
)
)

def __eq__(self, other):
# type: (TableEntity) -> bool
return self.value == other.value and self.type == other.type


class EdmType(str, Enum):
"""
Used by :class:`~.EntityProperty` to represent the type of the entity property
Expand Down Expand Up @@ -160,3 +95,21 @@ class EdmType(str, Enum):

BOOLEAN = "Edm.Boolean"
""" Represents a boolean. This type will be inferred for Python bools. """


EntityProperty = NamedTuple("EntityProperty", [("value", Any), ("edm_type", Union[str, EdmType])])
"""
An entity property. Used to explicitly set :class:`~EdmType` when necessary.

Values which require explicit typing are GUID, INT64, and BINARY. Other EdmTypes
may be explicitly create as EntityProperty objects but need not be. For example,
the below with both create STRING typed properties on the entity::
entity = TableEntity()
entity.a = 'b'
entity.x = EntityProperty('y', EdmType.STRING)

:param value:
:type value: Any
:param edm_type: Type of the value
:type edm_type: str or :class:`~azure.data.tables.EdmType`
"""
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ def _add_entity_properties(source):
elif isinstance(value, datetime):
mtype, value = _to_entity_datetime(value)
elif isinstance(value, EntityProperty):
conv = _EDM_TO_ENTITY_CONVERSIONS.get(value.type)
conv = _EDM_TO_ENTITY_CONVERSIONS.get(value.edm_type)
Copy link
Member

Choose a reason for hiding this comment

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

We should remove the isinstance check, and instead treat this as a generic tuple....

elif isinstance(value, tuple):  # ideally we wouldn't do the instance check at all here.... 
    conv = _EDM_TO_ENTITY_CONVERSIONS.get(value[1])
    ...
    mtype, value = conv(value[0])

if conv is None:
raise TypeError(_ERROR_TYPE_NOT_SUPPORTED.format(value.type))
mtype, value = conv(value.value)
Expand Down
76 changes: 23 additions & 53 deletions sdk/tables/azure-data-tables/tests/test_table_batch.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def _create_random_entity_dict(self, pk=None, rk=None):
'Birthday': datetime(1973, 10, 4, tzinfo=tzutc()),
'birthday': datetime(1970, 10, 4, tzinfo=tzutc()),
'binary': b'binary',
'other': EntityProperty(value=20, type=EdmType.INT32),
'other': EntityProperty(20, EdmType.INT32),
'clsid': uuid.UUID('c9da6455-213d-42c9-9a79-3e9149a57833')
}
return TableEntity(**properties)
Expand Down Expand Up @@ -183,10 +183,10 @@ def test_batch_single_insert(self, tables_storage_account_name, tables_primary_s
entity = TableEntity()
entity.PartitionKey = '001'
entity.RowKey = 'batch_insert'
entity.test = EntityProperty(True)
entity.test = EntityProperty(True, EdmType.BOOLEAN)
entity.test2 = 'value'
entity.test3 = 3
entity.test4 = EntityProperty(1234567890)
entity.test4 = EntityProperty(1234567890, EdmType.INT32)
entity.test5 = datetime.utcnow()

batch = [('create', entity)]
Expand Down Expand Up @@ -214,10 +214,10 @@ def test_batch_single_update(self, tables_storage_account_name, tables_primary_s
entity = TableEntity()
entity.PartitionKey = '001'
entity.RowKey = 'batch_insert'
entity.test = EntityProperty(True)
entity.test = EntityProperty(True, EdmType.BOOLEAN)
entity.test2 = 'value'
entity.test3 = 3
entity.test4 = EntityProperty(1234567890)
entity.test4 = EntityProperty(1234567890, EdmType.INT32)
entity.test5 = datetime.utcnow()

resp = self.table.create_entity(entity)
Expand Down Expand Up @@ -249,10 +249,10 @@ def test_batch_update(self, tables_storage_account_name, tables_primary_storage_
entity = TableEntity()
entity.PartitionKey = u'001'
entity.RowKey = u'batch_update'
entity.test = EntityProperty(True)
entity.test = EntityProperty(True, EdmType.BOOLEAN)
entity.test2 = u'value'
entity.test3 = 3
entity.test4 = EntityProperty(1234567890)
entity.test4 = EntityProperty(1234567890, EdmType.INT32)
Copy link
Member

Choose a reason for hiding this comment

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

Would be good to either add a test, or modify one of the existing ones to just use naked tuples:
entity.test4 = (1234567890, 'Edm.Int32')

entity.test5 = datetime.utcnow()
self.table.create_entity(entity)

Expand Down Expand Up @@ -285,10 +285,10 @@ def test_batch_merge(self, tables_storage_account_name, tables_primary_storage_a
entity = TableEntity()
entity.PartitionKey = u'001'
entity.RowKey = u'batch_merge'
entity.test = EntityProperty(True)
entity.test = EntityProperty(True, EdmType.BOOLEAN)
entity.test2 = u'value'
entity.test3 = 3
entity.test4 = EntityProperty(1234567890)
entity.test4 = EntityProperty(1234567890, EdmType.INT32)
entity.test5 = datetime.utcnow()
self.table.create_entity(entity)

Expand Down Expand Up @@ -377,10 +377,10 @@ def test_batch_single_op_if_doesnt_match(self, tables_storage_account_name, tabl
# Act
entity = TableEntity()
entity.PartitionKey = 'batch_inserts'
entity.test = EntityProperty(True)
entity.test = EntityProperty(True, EdmType.BOOLEAN)
entity.test2 = 'value'
entity.test3 = 3
entity.test4 = EntityProperty(1234567890)
entity.test4 = EntityProperty(1234567890, EdmType.INT32)

batch = []
transaction_count = 0
Expand Down Expand Up @@ -423,7 +423,7 @@ def test_batch_insert_replace(self, tables_storage_account_name, tables_primary_
entity.test = True
entity.test2 = 'value'
entity.test3 = 3
entity.test4 = EntityProperty(1234567890)
entity.test4 = EntityProperty(1234567890, EdmType.INT32)
entity.test5 = datetime.utcnow()

batch = [('upsert', entity, {'mode': UpdateMode.REPLACE})]
Expand Down Expand Up @@ -453,7 +453,7 @@ def test_batch_insert_merge(self, tables_storage_account_name, tables_primary_st
entity.test = True
entity.test2 = 'value'
entity.test3 = 3
entity.test4 = EntityProperty(1234567890)
entity.test4 = EntityProperty(1234567890, EdmType.INT32)
entity.test5 = datetime.utcnow()

batch = [('upsert', entity, {'mode': UpdateMode.MERGE})]
Expand All @@ -480,10 +480,10 @@ def test_batch_delete(self, tables_storage_account_name, tables_primary_storage_
entity = TableEntity()
entity.PartitionKey = u'001'
entity.RowKey = u'batch_delete'
entity.test = EntityProperty(True)
entity.test = EntityProperty(True, EdmType.BOOLEAN)
entity.test2 = u'value'
entity.test3 = 3
entity.test4 = EntityProperty(1234567890)
entity.test4 = EntityProperty(1234567890, EdmType.INT32)
entity.test5 = datetime.utcnow()
self.table.create_entity(entity)

Expand Down Expand Up @@ -511,10 +511,10 @@ def test_batch_inserts(self, tables_storage_account_name, tables_primary_storage
# Act
entity = TableEntity()
entity.PartitionKey = 'batch_inserts'
entity.test = EntityProperty(True)
entity.test = EntityProperty(True, EdmType.BOOLEAN)
entity.test2 = 'value'
entity.test3 = 3
entity.test4 = EntityProperty(1234567890)
entity.test4 = EntityProperty(1234567890, EdmType.INT32)

transaction_count = 0
batch = []
Expand Down Expand Up @@ -547,10 +547,10 @@ def test_batch_all_operations_together(self, tables_storage_account_name, tables
entity = TableEntity()
entity.PartitionKey = '003'
entity.RowKey = 'batch_all_operations_together-1'
entity.test = EntityProperty(True)
entity.test = EntityProperty(True, EdmType.BOOLEAN)
entity.test2 = 'value'
entity.test3 = 3
entity.test4 = EntityProperty(1234567890)
entity.test4 = EntityProperty(1234567890, EdmType.INT32)
entity.test5 = datetime.utcnow()
self.table.create_entity(entity)
entity.RowKey = 'batch_all_operations_together-2'
Expand Down Expand Up @@ -619,10 +619,10 @@ def test_batch_reuse(self, tables_storage_account_name, tables_primary_storage_a
entity = TableEntity()
entity.PartitionKey = '003'
entity.RowKey = 'batch_all_operations_together-1'
entity.test = EntityProperty(True)
entity.test = EntityProperty(True, EdmType.BOOLEAN)
entity.test2 = 'value'
entity.test3 = 3
entity.test4 = EntityProperty(1234567890)
entity.test4 = EntityProperty(1234567890, EdmType.INT32)
entity.test5 = datetime.utcnow()

batch = []
Expand Down Expand Up @@ -814,10 +814,10 @@ def test_batch_sas_auth(self, tables_storage_account_name, tables_primary_storag

entity = TableEntity()
entity.PartitionKey = 'batch_inserts'
entity.test = EntityProperty(True)
entity.test = EntityProperty(True, EdmType.BOOLEAN)
entity.test2 = 'value'
entity.test3 = 3
entity.test4 = EntityProperty(1234567890)
entity.test4 = EntityProperty(1234567890, EdmType.INT32)

batch = []
transaction_count = 0
Expand Down Expand Up @@ -861,33 +861,3 @@ def test_batch_request_too_large(self, tables_storage_account_name, tables_prima

finally:
self._tear_down()



class TestTableUnitTest(TableTestCase):

#--Test cases for batch ---------------------------------------------
def test_inferred_types(self):
# Arrange
# Act
entity = TableEntity()
entity.PartitionKey = '003'
entity.RowKey = 'batch_all_operations_together-1'
entity.test = EntityProperty(True)
entity.test2 = EntityProperty(b'abcdef')
entity.test3 = EntityProperty(u'c9da6455-213d-42c9-9a79-3e9149a57833')
entity.test4 = EntityProperty(datetime(1973, 10, 4, tzinfo=tzutc()))
entity.test5 = EntityProperty(u"stringystring")
entity.test6 = EntityProperty(3.14159)
entity.test7 = EntityProperty(100)
entity.test8 = EntityProperty(2 ** 33, EdmType.INT64)

# Assert
assert entity.test.type == EdmType.BOOLEAN
assert entity.test2.type == EdmType.BINARY
assert entity.test3.type == EdmType.GUID
assert entity.test4.type == EdmType.DATETIME
assert entity.test5.type == EdmType.STRING
assert entity.test6.type == EdmType.DOUBLE
assert entity.test7.type == EdmType.INT32
assert entity.test8.type == EdmType.INT64
Loading