Skip to content

Commit c9ac11b

Browse files
author
Yalin Li
authored
Add encoder samples and update CHANGELOG.md (#35816)
1 parent ea090e9 commit c9ac11b

17 files changed

+1233
-491
lines changed

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
# Release History
22

3-
## 12.5.1 (Unreleased)
3+
## 12.6.0b1 (Unreleased)
44

55
### Features Added
6-
7-
### Breaking Changes
6+
* Added to support custom encoder in entity CRUD operations.
7+
* Added to support custom Entity type.
8+
* Added to support Entity property in Tuple and Enum types.
89

910
### Bugs Fixed
11+
* Fixed a bug in encoder when Entity property has "@odata.type" provided.
12+
* Fixed a bug in encoder that int32 and int64 are mapped to int32 when no "@odata.type" provided.
1013

1114
### Other Changes
15+
* Removed value range validation for Entity property in int32 and int64.
1216

1317
## 12.5.0 (2024-01-10)
1418

sdk/tables/azure-data-tables/assets.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"AssetsRepo": "Azure/azure-sdk-assets",
33
"AssetsRepoPrefixPath": "python",
44
"TagPrefix": "python/tables/azure-data-tables",
5-
"Tag": "python/tables/azure-data-tables_7ddb8a1cfc"
5+
"Tag": "python/tables/azure-data-tables_1fb1a4af1a"
66
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,8 @@
1717
NEXT_TABLE_NAME = "x-ms-continuation-NextTableName"
1818
NEXT_PARTITION_KEY = "x-ms-continuation-NextPartitionKey"
1919
NEXT_ROW_KEY = "x-ms-continuation-NextRowKey"
20+
21+
MAX_INT32 = (2**31) - 1 # 2147483647
22+
MIN_INT32 = -(2**31) # -2147483648
23+
MAX_INT64 = (2**63) - 1 # 9223372036854775807
24+
MIN_INT64 = -(2**63) # -9223372036854775808

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

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
from math import isnan
1212

1313
from ._entity import EdmType, TableEntity
14-
from ._deserialize import _convert_to_entity
1514
from ._common_conversion import _encode_base64, _to_utc_datetime
15+
from ._constants import MAX_INT32, MIN_INT32, MAX_INT64, MIN_INT64
1616

1717
_ODATA_SUFFIX = "@odata.type"
1818
T = TypeVar("T")
@@ -50,7 +50,11 @@ def prepare_value( # pylint: disable=too-many-return-statements
5050
if isinstance(value, str):
5151
return None, value
5252
if isinstance(value, int):
53-
return None, value # TODO: Test what happens if the supplied value exceeds int32.
53+
if MIN_INT32 <= value <= MAX_INT32:
54+
return None, value
55+
if MIN_INT64 <= value <= MAX_INT64:
56+
return EdmType.INT64, str(value)
57+
return None, value
5458
if isinstance(value, float):
5559
if isnan(value):
5660
return EdmType.DOUBLE, "NaN"
@@ -133,9 +137,6 @@ def encode_entity(self, entity: T) -> Dict[str, Union[str, int, float, bool]]:
133137
:rtype: dict
134138
"""
135139

136-
@abc.abstractmethod
137-
def decode_entity(self, entity: Dict[str, Union[str, int, float, bool]]) -> T: ...
138-
139140

140141
class TableEntityEncoder(TableEntityEncoderABC[Union[TableEntity, Mapping[str, Any]]]):
141142
def encode_entity(self, entity: Union[TableEntity, Mapping[str, Any]]) -> Dict[str, Union[str, int, float, bool]]:
@@ -168,18 +169,16 @@ def encode_entity(self, entity: Union[TableEntity, Mapping[str, Any]]) -> Dict[s
168169
for key, value in entity.items():
169170
edm_type, value = self.prepare_value(key, value)
170171
try:
171-
if _ODATA_SUFFIX in key or key + _ODATA_SUFFIX in entity:
172+
odata = f"{key}{_ODATA_SUFFIX}"
173+
if _ODATA_SUFFIX in key or odata in entity:
172174
encoded[key] = value
173175
continue
174176
# The edm type is decided by value
175177
# For example, when value=EntityProperty(str(uuid.uuid4), "Edm.Guid"),
176178
# the type is string instead of Guid after encoded
177179
if edm_type:
178-
encoded[key + _ODATA_SUFFIX] = edm_type.value if hasattr(edm_type, "value") else edm_type
180+
encoded[odata] = edm_type.value if hasattr(edm_type, "value") else edm_type
179181
except TypeError:
180182
pass
181183
encoded[key] = value
182184
return encoded
183-
184-
def decode_entity(self, entity: Dict[str, Union[str, int, float, bool]]) -> TableEntity:
185-
return _convert_to_entity(entity)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
# license information.
55
# --------------------------------------------------------------------------
66

7-
VERSION = "12.5.1"
7+
VERSION = "12.6.0b1"

sdk/tables/azure-data-tables/samples/async_samples/sample_conditional_update_async.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
2) TABLES_STORAGE_ACCOUNT_NAME - the Tables storage account name
2121
3) TABLES_PRIMARY_STORAGE_ACCOUNT_KEY - the Tables storage account access key
2222
"""
23-
import sys
2423
import asyncio
2524
import os
2625
from datetime import datetime

sdk/tables/azure-data-tables/samples/async_samples/sample_copy_table_async.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
4) STORAGE_ACCOUNT_NAME - the blob storage account name
2323
5) STORAGE_ACCOUNT_KEY - the blob storage account key
2424
"""
25-
import sys
2625
import asyncio
2726
import json
2827
import os
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
# coding: utf-8
2+
3+
# -------------------------------------------------------------------------
4+
# Copyright (c) Microsoft Corporation. All rights reserved.
5+
# Licensed under the MIT License. See License.txt in the project root for
6+
# license information.
7+
# --------------------------------------------------------------------------
8+
9+
"""
10+
FILE: sample_custom_encoder_dataclass_async.py
11+
12+
DESCRIPTION:
13+
These samples demonstrate the following: inserting entities into a table
14+
and deleting entities from a table.
15+
16+
USAGE:
17+
python sample_custom_encoder_dataclass_async.py
18+
19+
Set the environment variables with your own values before running the sample:
20+
1) TABLES_STORAGE_ENDPOINT_SUFFIX - the Table service account URL suffix
21+
2) TABLES_STORAGE_ACCOUNT_NAME - the name of the storage account
22+
3) TABLES_PRIMARY_STORAGE_ACCOUNT_KEY - the storage account access key
23+
"""
24+
import os
25+
import asyncio
26+
from datetime import datetime, timezone
27+
from uuid import uuid4, UUID
28+
from dotenv import find_dotenv, load_dotenv
29+
from dataclasses import dataclass, asdict
30+
from typing import Dict, Union, Optional
31+
from azure.data.tables import TableEntityEncoderABC, UpdateMode
32+
from azure.data.tables.aio import TableClient
33+
34+
35+
@dataclass
36+
class Car:
37+
partition_key: str
38+
row_key: UUID
39+
price: Optional[float] = None
40+
last_updated: Optional[datetime] = None
41+
product_id: Optional[UUID] = None
42+
inventory_count: Optional[int] = None
43+
barcode: Optional[bytes] = None
44+
color: Optional[str] = None
45+
maker: Optional[str] = None
46+
model: Optional[str] = None
47+
production_date: Optional[datetime] = None
48+
mileage: Optional[int] = None
49+
is_second_hand: Optional[bool] = None
50+
51+
52+
class MyEncoder(TableEntityEncoderABC[Car]):
53+
def prepare_key(self, key: UUID) -> str: # type: ignore[override]
54+
return super().prepare_key(str(key))
55+
56+
def encode_entity(self, entity: Car) -> Dict[str, Union[str, int, float, bool]]:
57+
encoded = {}
58+
for key, value in asdict(entity).items():
59+
if key == "partition_key":
60+
encoded["PartitionKey"] = value # this property should be "PartitionKey" in encoded result
61+
continue
62+
if key == "row_key":
63+
encoded["RowKey"] = str(value) # this property should be "RowKey" in encoded result
64+
continue
65+
edm_type, value = self.prepare_value(key, value)
66+
if edm_type:
67+
encoded[f"{key}@odata.type"] = edm_type.value if hasattr(edm_type, "value") else edm_type
68+
encoded[key] = value
69+
return encoded
70+
71+
72+
class InsertUpdateDeleteEntity(object):
73+
def __init__(self):
74+
load_dotenv(find_dotenv())
75+
self.access_key = os.environ["TABLES_PRIMARY_STORAGE_ACCOUNT_KEY"]
76+
self.endpoint_suffix = os.environ["TABLES_STORAGE_ENDPOINT_SUFFIX"]
77+
self.account_name = os.environ["TABLES_STORAGE_ACCOUNT_NAME"]
78+
self.endpoint = f"{self.account_name}.table.{self.endpoint_suffix}"
79+
self.connection_string = f"DefaultEndpointsProtocol=https;AccountName={self.account_name};AccountKey={self.access_key};EndpointSuffix={self.endpoint_suffix}"
80+
self.table_name = "CustomEncoderDataClassAsync"
81+
82+
self.entity = Car(
83+
partition_key="PK",
84+
row_key=uuid4(),
85+
price=4.99,
86+
last_updated=datetime.today(),
87+
product_id=uuid4(),
88+
inventory_count=42,
89+
barcode=b"135aefg8oj0ld58", # cspell:disable-line
90+
color="white",
91+
maker="maker",
92+
model="model",
93+
production_date=datetime(year=2014, month=4, day=1, hour=9, minute=30, second=45, tzinfo=timezone.utc),
94+
mileage=2**31, # an int64 integer
95+
is_second_hand=True,
96+
)
97+
98+
async def create_delete_entity(self):
99+
table_client = TableClient.from_connection_string(self.connection_string, self.table_name)
100+
async with table_client:
101+
await table_client.create_table()
102+
103+
result = await table_client.create_entity(entity=self.entity, encoder=MyEncoder())
104+
print(f"Created entity: {result}")
105+
106+
result = await table_client.get_entity(
107+
self.entity.partition_key,
108+
self.entity.row_key, # type: ignore[arg-type] # intend to pass a non-string RowKey
109+
encoder=MyEncoder(),
110+
)
111+
print(f"Get entity result: {result}")
112+
113+
await table_client.delete_entity(
114+
partition_key=self.entity.partition_key,
115+
row_key=self.entity.row_key, # type: ignore[call-overload] # intend to pass a non-string RowKey
116+
encoder=MyEncoder(),
117+
)
118+
print("Successfully deleted!")
119+
120+
await table_client.delete_table()
121+
print("Cleaned up")
122+
123+
async def upsert_update_entities(self):
124+
table_client = TableClient.from_connection_string(
125+
self.connection_string, table_name=f"{self.table_name}UpsertUpdate"
126+
)
127+
128+
async with table_client:
129+
await table_client.create_table()
130+
131+
entity1 = Car(
132+
partition_key="PK",
133+
row_key=uuid4(),
134+
price=4.99,
135+
last_updated=datetime.today(),
136+
product_id=uuid4(),
137+
inventory_count=42,
138+
barcode=b"135aefg8oj0ld58", # cspell:disable-line
139+
)
140+
entity2 = Car(
141+
partition_key=entity1.partition_key,
142+
row_key=entity1.row_key,
143+
color="red",
144+
maker="maker2",
145+
model="model2",
146+
production_date=datetime(year=2014, month=4, day=1, hour=9, minute=30, second=45, tzinfo=timezone.utc),
147+
mileage=2**31, # an int64 integer
148+
is_second_hand=True,
149+
)
150+
151+
await table_client.upsert_entity(mode=UpdateMode.REPLACE, entity=entity2, encoder=MyEncoder())
152+
inserted_entity = await table_client.get_entity(
153+
entity2.partition_key,
154+
entity2.row_key, # type: ignore[arg-type] # intend to pass a non-string RowKey
155+
encoder=MyEncoder(),
156+
)
157+
print(f"Inserted entity: {inserted_entity}")
158+
159+
await table_client.upsert_entity(mode=UpdateMode.MERGE, entity=entity1, encoder=MyEncoder())
160+
merged_entity = await table_client.get_entity(
161+
entity1.partition_key,
162+
entity1.row_key, # type: ignore[arg-type] # intend to pass a non-string RowKey
163+
encoder=MyEncoder(),
164+
)
165+
print(f"Merged entity: {merged_entity}")
166+
167+
entity3 = Car(
168+
partition_key=entity1.partition_key,
169+
row_key=entity2.row_key,
170+
color="white",
171+
)
172+
await table_client.update_entity(mode=UpdateMode.REPLACE, entity=entity3, encoder=MyEncoder())
173+
replaced_entity = await table_client.get_entity(
174+
entity3.partition_key,
175+
entity3.row_key, # type: ignore[arg-type] # intend to pass a non-string RowKey
176+
encoder=MyEncoder(),
177+
)
178+
print(f"Replaced entity: {replaced_entity}")
179+
180+
await table_client.update_entity(mode=UpdateMode.REPLACE, entity=entity2, encoder=MyEncoder())
181+
merged_entity = await table_client.get_entity(
182+
entity2.partition_key,
183+
entity2.row_key, # type: ignore[arg-type] # intend to pass a non-string RowKey
184+
encoder=MyEncoder(),
185+
)
186+
print(f"Merged entity: {merged_entity}")
187+
188+
await table_client.delete_table()
189+
print("Cleaned up")
190+
191+
192+
async def main():
193+
ide = InsertUpdateDeleteEntity()
194+
await ide.create_delete_entity()
195+
await ide.upsert_update_entities()
196+
197+
198+
if __name__ == "__main__":
199+
asyncio.run(main())

sdk/tables/azure-data-tables/samples/async_samples/sample_insert_delete_entities_async.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
1212
DESCRIPTION:
1313
These samples demonstrate the following: inserting entities into a table
14-
and deleting tables from a table.
14+
and deleting entities from a table.
1515
1616
USAGE:
1717
python sample_insert_delete_entities_async.py

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
2) TABLES_STORAGE_ACCOUNT_NAME - the Tables storage account name
2121
3) TABLES_PRIMARY_STORAGE_ACCOUNT_KEY - the Tables storage account access key
2222
"""
23-
import sys
2423
import os
2524
from datetime import datetime
2625
from dotenv import find_dotenv, load_dotenv

0 commit comments

Comments
 (0)