Skip to content

Commit 0bc271d

Browse files
Sas batching (#17133)
Fixes customer reported #17093 I added two more tests, fixed sas tests to wrap with `AzureSasCredential`
1 parent 8e8a378 commit 0bc271d

File tree

5 files changed

+372
-19
lines changed

5 files changed

+372
-19
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
interactions:
2+
- request:
3+
body: '{"TableName": "uttabled4f0e8f"}'
4+
headers:
5+
Accept:
6+
- application/json;odata=minimalmetadata
7+
Accept-Encoding:
8+
- gzip, deflate
9+
Connection:
10+
- keep-alive
11+
Content-Length:
12+
- '31'
13+
Content-Type:
14+
- application/json;odata=nometadata
15+
DataServiceVersion:
16+
- '3.0'
17+
Date:
18+
- Fri, 05 Mar 2021 16:54:43 GMT
19+
User-Agent:
20+
- azsdk-python-data-tables/12.0.0b5 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0)
21+
x-ms-date:
22+
- Fri, 05 Mar 2021 16:54:43 GMT
23+
x-ms-version:
24+
- '2019-02-02'
25+
method: POST
26+
uri: https://fake_table_account.table.core.windows.net/Tables
27+
response:
28+
body:
29+
string: '{"odata.error":{"code":"TableAlreadyExists","message":{"lang":"en-US","value":"The
30+
table specified already exists.\nRequestId:7fd90d5f-e002-003b-41e0-119f80000000\nTime:2021-03-05T16:54:43.1645315Z"}}}'
31+
headers:
32+
cache-control:
33+
- no-cache
34+
content-type:
35+
- application/json;odata=minimalmetadata;streaming=true;charset=utf-8
36+
date:
37+
- Fri, 05 Mar 2021 16:54:42 GMT
38+
server:
39+
- Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0
40+
transfer-encoding:
41+
- chunked
42+
x-content-type-options:
43+
- nosniff
44+
x-ms-version:
45+
- '2019-02-02'
46+
status:
47+
code: 409
48+
message: Conflict
49+
- request:
50+
body: "--batch_8e6535d9-ecdb-4f9f-8547-dfe348188540\r\nContent-Type: multipart/mixed;\
51+
\ boundary=changeset_070b7ea2-9157-4ee4-8ce1-38b81ab0d4f6\r\n\r\n--changeset_070b7ea2-9157-4ee4-8ce1-38b81ab0d4f6\r\
52+
\nContent-Type: application/http\r\nContent-Transfer-Encoding: binary\r\nContent-ID:\
53+
\ 0\r\n\r\nPOST https://seankaneprim.table.core.windows.net/uttabled4f0e8f?st=2021-03-05T16%3A53%3A44Z&se=2021-03-05T17%3A54%3A44Z&sp=a&sv=2019-02-02&tn=uttabled4f0e8f&sig=TStgoQP%2FZDFaSw7JFX8lYcHHSbNXkWMaIejc8v7EGso%3D\
54+
\ HTTP/1.1\r\nx-ms-version: 2019-02-02\r\nDataServiceVersion: 3.0\r\nPrefer:\
55+
\ return-no-content\r\nContent-Type: application/json;odata=nometadata\r\nAccept:\
56+
\ application/json;odata=minimalmetadata\r\nContent-Length: 223\r\nx-ms-date:\
57+
\ Fri, 05 Mar 2021 16:54:44 GMT\r\nDate: Fri, 05 Mar 2021 16:54:44 GMT\r\nx-ms-client-request-id:\
58+
\ 7c64afbf-7dd3-11eb-ab63-002b67128e4c\r\n\r\n{\"PartitionKey\": \"batch_inserts\"\
59+
, \"[email protected]\": \"Edm.String\", \"test\": true, \"test2\": \"\
60+
value\", \"[email protected]\": \"Edm.String\", \"test3\": 3, \"test4\": 1234567890,\
61+
\ \"RowKey\": \"0\", \"[email protected]\": \"Edm.String\"}\r\n--changeset_070b7ea2-9157-4ee4-8ce1-38b81ab0d4f6\r\
62+
\nContent-Type: application/http\r\nContent-Transfer-Encoding: binary\r\nContent-ID:\
63+
\ 1\r\n\r\nPOST https://seankaneprim.table.core.windows.net/uttabled4f0e8f?st=2021-03-05T16%3A53%3A44Z&se=2021-03-05T17%3A54%3A44Z&sp=a&sv=2019-02-02&tn=uttabled4f0e8f&sig=TStgoQP%2FZDFaSw7JFX8lYcHHSbNXkWMaIejc8v7EGso%3D\
64+
\ HTTP/1.1\r\nx-ms-version: 2019-02-02\r\nDataServiceVersion: 3.0\r\nPrefer:\
65+
\ return-no-content\r\nContent-Type: application/json;odata=nometadata\r\nAccept:\
66+
\ application/json;odata=minimalmetadata\r\nContent-Length: 223\r\nx-ms-date:\
67+
\ Fri, 05 Mar 2021 16:54:44 GMT\r\nDate: Fri, 05 Mar 2021 16:54:44 GMT\r\nx-ms-client-request-id:\
68+
\ 7c64afc0-7dd3-11eb-8299-002b67128e4c\r\n\r\n{\"PartitionKey\": \"batch_inserts\"\
69+
, \"[email protected]\": \"Edm.String\", \"test\": true, \"test2\": \"\
70+
value\", \"[email protected]\": \"Edm.String\", \"test3\": 3, \"test4\": 1234567890,\
71+
\ \"RowKey\": \"1\", \"[email protected]\": \"Edm.String\"}\r\n--changeset_070b7ea2-9157-4ee4-8ce1-38b81ab0d4f6\r\
72+
\nContent-Type: application/http\r\nContent-Transfer-Encoding: binary\r\nContent-ID:\
73+
\ 2\r\n\r\nPOST https://seankaneprim.table.core.windows.net/uttabled4f0e8f?st=2021-03-05T16%3A53%3A44Z&se=2021-03-05T17%3A54%3A44Z&sp=a&sv=2019-02-02&tn=uttabled4f0e8f&sig=TStgoQP%2FZDFaSw7JFX8lYcHHSbNXkWMaIejc8v7EGso%3D\
74+
\ HTTP/1.1\r\nx-ms-version: 2019-02-02\r\nDataServiceVersion: 3.0\r\nPrefer:\
75+
\ return-no-content\r\nContent-Type: application/json;odata=nometadata\r\nAccept:\
76+
\ application/json;odata=minimalmetadata\r\nContent-Length: 223\r\nx-ms-date:\
77+
\ Fri, 05 Mar 2021 16:54:44 GMT\r\nDate: Fri, 05 Mar 2021 16:54:44 GMT\r\nx-ms-client-request-id:\
78+
\ 7c64d743-7dd3-11eb-a56b-002b67128e4c\r\n\r\n{\"PartitionKey\": \"batch_inserts\"\
79+
, \"[email protected]\": \"Edm.String\", \"test\": true, \"test2\": \"\
80+
value\", \"[email protected]\": \"Edm.String\", \"test3\": 3, \"test4\": 1234567890,\
81+
\ \"RowKey\": \"2\", \"[email protected]\": \"Edm.String\"}\r\n--changeset_070b7ea2-9157-4ee4-8ce1-38b81ab0d4f6\r\
82+
\nContent-Type: application/http\r\nContent-Transfer-Encoding: binary\r\nContent-ID:\
83+
\ 3\r\n\r\nPOST https://seankaneprim.table.core.windows.net/uttabled4f0e8f?st=2021-03-05T16%3A53%3A44Z&se=2021-03-05T17%3A54%3A44Z&sp=a&sv=2019-02-02&tn=uttabled4f0e8f&sig=TStgoQP%2FZDFaSw7JFX8lYcHHSbNXkWMaIejc8v7EGso%3D\
84+
\ HTTP/1.1\r\nx-ms-version: 2019-02-02\r\nDataServiceVersion: 3.0\r\nPrefer:\
85+
\ return-no-content\r\nContent-Type: application/json;odata=nometadata\r\nAccept:\
86+
\ application/json;odata=minimalmetadata\r\nContent-Length: 223\r\nx-ms-date:\
87+
\ Fri, 05 Mar 2021 16:54:44 GMT\r\nDate: Fri, 05 Mar 2021 16:54:44 GMT\r\nx-ms-client-request-id:\
88+
\ 7c65083f-7dd3-11eb-88e4-002b67128e4c\r\n\r\n{\"PartitionKey\": \"batch_inserts\"\
89+
, \"[email protected]\": \"Edm.String\", \"test\": true, \"test2\": \"\
90+
value\", \"[email protected]\": \"Edm.String\", \"test3\": 3, \"test4\": 1234567890,\
91+
\ \"RowKey\": \"3\", \"[email protected]\": \"Edm.String\"}\r\n--changeset_070b7ea2-9157-4ee4-8ce1-38b81ab0d4f6\r\
92+
\nContent-Type: application/http\r\nContent-Transfer-Encoding: binary\r\nContent-ID:\
93+
\ 4\r\n\r\nPOST https://seankaneprim.table.core.windows.net/uttabled4f0e8f?st=2021-03-05T16%3A53%3A44Z&se=2021-03-05T17%3A54%3A44Z&sp=a&sv=2019-02-02&tn=uttabled4f0e8f&sig=TStgoQP%2FZDFaSw7JFX8lYcHHSbNXkWMaIejc8v7EGso%3D\
94+
\ HTTP/1.1\r\nx-ms-version: 2019-02-02\r\nDataServiceVersion: 3.0\r\nPrefer:\
95+
\ return-no-content\r\nContent-Type: application/json;odata=nometadata\r\nAccept:\
96+
\ application/json;odata=minimalmetadata\r\nContent-Length: 223\r\nx-ms-date:\
97+
\ Fri, 05 Mar 2021 16:54:44 GMT\r\nDate: Fri, 05 Mar 2021 16:54:44 GMT\r\nx-ms-client-request-id:\
98+
\ 7c650840-7dd3-11eb-8f93-002b67128e4c\r\n\r\n{\"PartitionKey\": \"batch_inserts\"\
99+
, \"[email protected]\": \"Edm.String\", \"test\": true, \"test2\": \"\
100+
value\", \"[email protected]\": \"Edm.String\", \"test3\": 3, \"test4\": 1234567890,\
101+
\ \"RowKey\": \"4\", \"[email protected]\": \"Edm.String\"}\r\n--changeset_070b7ea2-9157-4ee4-8ce1-38b81ab0d4f6\r\
102+
\nContent-Type: application/http\r\nContent-Transfer-Encoding: binary\r\nContent-ID:\
103+
\ 5\r\n\r\nPOST https://seankaneprim.table.core.windows.net/uttabled4f0e8f?st=2021-03-05T16%3A53%3A44Z&se=2021-03-05T17%3A54%3A44Z&sp=a&sv=2019-02-02&tn=uttabled4f0e8f&sig=TStgoQP%2FZDFaSw7JFX8lYcHHSbNXkWMaIejc8v7EGso%3D\
104+
\ HTTP/1.1\r\nx-ms-version: 2019-02-02\r\nDataServiceVersion: 3.0\r\nPrefer:\
105+
\ return-no-content\r\nContent-Type: application/json;odata=nometadata\r\nAccept:\
106+
\ application/json;odata=minimalmetadata\r\nContent-Length: 223\r\nx-ms-date:\
107+
\ Fri, 05 Mar 2021 16:54:44 GMT\r\nDate: Fri, 05 Mar 2021 16:54:44 GMT\r\nx-ms-client-request-id:\
108+
\ 7c650841-7dd3-11eb-a3d0-002b67128e4c\r\n\r\n{\"PartitionKey\": \"batch_inserts\"\
109+
, \"[email protected]\": \"Edm.String\", \"test\": true, \"test2\": \"\
110+
value\", \"[email protected]\": \"Edm.String\", \"test3\": 3, \"test4\": 1234567890,\
111+
\ \"RowKey\": \"5\", \"[email protected]\": \"Edm.String\"}\r\n--changeset_070b7ea2-9157-4ee4-8ce1-38b81ab0d4f6\r\
112+
\nContent-Type: application/http\r\nContent-Transfer-Encoding: binary\r\nContent-ID:\
113+
\ 6\r\n\r\nPOST https://seankaneprim.table.core.windows.net/uttabled4f0e8f?st=2021-03-05T16%3A53%3A44Z&se=2021-03-05T17%3A54%3A44Z&sp=a&sv=2019-02-02&tn=uttabled4f0e8f&sig=TStgoQP%2FZDFaSw7JFX8lYcHHSbNXkWMaIejc8v7EGso%3D\
114+
\ HTTP/1.1\r\nx-ms-version: 2019-02-02\r\nDataServiceVersion: 3.0\r\nPrefer:\
115+
\ return-no-content\r\nContent-Type: application/json;odata=nometadata\r\nAccept:\
116+
\ application/json;odata=minimalmetadata\r\nContent-Length: 223\r\nx-ms-date:\
117+
\ Fri, 05 Mar 2021 16:54:44 GMT\r\nDate: Fri, 05 Mar 2021 16:54:44 GMT\r\nx-ms-client-request-id:\
118+
\ 7c653e76-7dd3-11eb-9358-002b67128e4c\r\n\r\n{\"PartitionKey\": \"batch_inserts\"\
119+
, \"[email protected]\": \"Edm.String\", \"test\": true, \"test2\": \"\
120+
value\", \"[email protected]\": \"Edm.String\", \"test3\": 3, \"test4\": 1234567890,\
121+
\ \"RowKey\": \"6\", \"[email protected]\": \"Edm.String\"}\r\n--changeset_070b7ea2-9157-4ee4-8ce1-38b81ab0d4f6\r\
122+
\nContent-Type: application/http\r\nContent-Transfer-Encoding: binary\r\nContent-ID:\
123+
\ 7\r\n\r\nPOST https://seankaneprim.table.core.windows.net/uttabled4f0e8f?st=2021-03-05T16%3A53%3A44Z&se=2021-03-05T17%3A54%3A44Z&sp=a&sv=2019-02-02&tn=uttabled4f0e8f&sig=TStgoQP%2FZDFaSw7JFX8lYcHHSbNXkWMaIejc8v7EGso%3D\
124+
\ HTTP/1.1\r\nx-ms-version: 2019-02-02\r\nDataServiceVersion: 3.0\r\nPrefer:\
125+
\ return-no-content\r\nContent-Type: application/json;odata=nometadata\r\nAccept:\
126+
\ application/json;odata=minimalmetadata\r\nContent-Length: 223\r\nx-ms-date:\
127+
\ Fri, 05 Mar 2021 16:54:44 GMT\r\nDate: Fri, 05 Mar 2021 16:54:44 GMT\r\nx-ms-client-request-id:\
128+
\ 7c653e77-7dd3-11eb-a991-002b67128e4c\r\n\r\n{\"PartitionKey\": \"batch_inserts\"\
129+
, \"[email protected]\": \"Edm.String\", \"test\": true, \"test2\": \"\
130+
value\", \"[email protected]\": \"Edm.String\", \"test3\": 3, \"test4\": 1234567890,\
131+
\ \"RowKey\": \"7\", \"[email protected]\": \"Edm.String\"}\r\n--changeset_070b7ea2-9157-4ee4-8ce1-38b81ab0d4f6\r\
132+
\nContent-Type: application/http\r\nContent-Transfer-Encoding: binary\r\nContent-ID:\
133+
\ 8\r\n\r\nPOST https://seankaneprim.table.core.windows.net/uttabled4f0e8f?st=2021-03-05T16%3A53%3A44Z&se=2021-03-05T17%3A54%3A44Z&sp=a&sv=2019-02-02&tn=uttabled4f0e8f&sig=TStgoQP%2FZDFaSw7JFX8lYcHHSbNXkWMaIejc8v7EGso%3D\
134+
\ HTTP/1.1\r\nx-ms-version: 2019-02-02\r\nDataServiceVersion: 3.0\r\nPrefer:\
135+
\ return-no-content\r\nContent-Type: application/json;odata=nometadata\r\nAccept:\
136+
\ application/json;odata=minimalmetadata\r\nContent-Length: 223\r\nx-ms-date:\
137+
\ Fri, 05 Mar 2021 16:54:44 GMT\r\nDate: Fri, 05 Mar 2021 16:54:44 GMT\r\nx-ms-client-request-id:\
138+
\ 7c653e78-7dd3-11eb-9906-002b67128e4c\r\n\r\n{\"PartitionKey\": \"batch_inserts\"\
139+
, \"[email protected]\": \"Edm.String\", \"test\": true, \"test2\": \"\
140+
value\", \"[email protected]\": \"Edm.String\", \"test3\": 3, \"test4\": 1234567890,\
141+
\ \"RowKey\": \"8\", \"[email protected]\": \"Edm.String\"}\r\n--changeset_070b7ea2-9157-4ee4-8ce1-38b81ab0d4f6\r\
142+
\nContent-Type: application/http\r\nContent-Transfer-Encoding: binary\r\nContent-ID:\
143+
\ 9\r\n\r\nPOST https://seankaneprim.table.core.windows.net/uttabled4f0e8f?st=2021-03-05T16%3A53%3A44Z&se=2021-03-05T17%3A54%3A44Z&sp=a&sv=2019-02-02&tn=uttabled4f0e8f&sig=TStgoQP%2FZDFaSw7JFX8lYcHHSbNXkWMaIejc8v7EGso%3D\
144+
\ HTTP/1.1\r\nx-ms-version: 2019-02-02\r\nDataServiceVersion: 3.0\r\nPrefer:\
145+
\ return-no-content\r\nContent-Type: application/json;odata=nometadata\r\nAccept:\
146+
\ application/json;odata=minimalmetadata\r\nContent-Length: 223\r\nx-ms-date:\
147+
\ Fri, 05 Mar 2021 16:54:44 GMT\r\nDate: Fri, 05 Mar 2021 16:54:44 GMT\r\nx-ms-client-request-id:\
148+
\ 7c653e79-7dd3-11eb-81dd-002b67128e4c\r\n\r\n{\"PartitionKey\": \"batch_inserts\"\
149+
, \"[email protected]\": \"Edm.String\", \"test\": true, \"test2\": \"\
150+
value\", \"[email protected]\": \"Edm.String\", \"test3\": 3, \"test4\": 1234567890,\
151+
\ \"RowKey\": \"9\", \"[email protected]\": \"Edm.String\"}\r\n--changeset_070b7ea2-9157-4ee4-8ce1-38b81ab0d4f6--\r\
152+
\n\r\n--batch_8e6535d9-ecdb-4f9f-8547-dfe348188540--\r\n"
153+
headers:
154+
Accept:
155+
- '*/*'
156+
Accept-Encoding:
157+
- gzip, deflate
158+
Connection:
159+
- keep-alive
160+
Content-Length:
161+
- '9418'
162+
Content-Type:
163+
- multipart/mixed; boundary=batch_8e6535d9-ecdb-4f9f-8547-dfe348188540
164+
DataServiceVersion:
165+
- '3.0'
166+
Date:
167+
- Fri, 05 Mar 2021 16:54:44 GMT
168+
MaxDataServiceVersion:
169+
- 3.0;NetFx
170+
User-Agent:
171+
- azsdk-python-data-tables/12.0.0b5 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0)
172+
x-ms-date:
173+
- Fri, 05 Mar 2021 16:54:44 GMT
174+
x-ms-version:
175+
- '2019-02-02'
176+
method: POST
177+
uri: https://fake_table_account.table.core.windows.net/$batch
178+
response:
179+
body:
180+
string: '<?xml version="1.0" encoding="utf-8"?><m:error xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"><m:code>ResourceNotFound</m:code><m:message
181+
xml:lang="en-US">The specified resource does not exist.
182+
183+
RequestId:b6ba23cc-3002-005a-7ce0-11bcc3000000
184+
185+
Time:2021-03-05T16:54:44.0091854Z</m:message></m:error>'
186+
headers:
187+
content-length:
188+
- '322'
189+
content-type:
190+
- application/xml
191+
date:
192+
- Fri, 05 Mar 2021 16:54:43 GMT
193+
server:
194+
- Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0
195+
x-ms-error-code:
196+
- ResourceNotFound
197+
x-ms-version:
198+
- '2019-02-02'
199+
status:
200+
code: 404
201+
message: The specified resource does not exist.
202+
- request:
203+
body: null
204+
headers:
205+
Accept:
206+
- application/json
207+
Accept-Encoding:
208+
- gzip, deflate
209+
Connection:
210+
- keep-alive
211+
Content-Length:
212+
- '0'
213+
Date:
214+
- Fri, 05 Mar 2021 16:54:45 GMT
215+
User-Agent:
216+
- azsdk-python-data-tables/12.0.0b5 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0)
217+
x-ms-date:
218+
- Fri, 05 Mar 2021 16:54:45 GMT
219+
x-ms-version:
220+
- '2019-02-02'
221+
method: DELETE
222+
uri: https://fake_table_account.table.core.windows.net/Tables('uttabled4f0e8f')
223+
response:
224+
body:
225+
string: ''
226+
headers:
227+
cache-control:
228+
- no-cache
229+
content-length:
230+
- '0'
231+
date:
232+
- Fri, 05 Mar 2021 16:54:43 GMT
233+
server:
234+
- Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0
235+
x-content-type-options:
236+
- nosniff
237+
x-ms-version:
238+
- '2019-02-02'
239+
status:
240+
code: 204
241+
message: No Content
242+
version: 1

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

+57-1
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@
88

99
import pytest
1010

11-
from datetime import datetime
11+
from datetime import datetime, timedelta
1212
from dateutil.tz import tzutc
1313
import sys
1414
import uuid
1515

1616
from devtools_testutils import AzureTestCase
1717

1818
from azure.core import MatchConditions
19+
from azure.core.credentials import AzureSasCredential
1920
from azure.core.exceptions import (
2021
ResourceExistsError,
2122
ResourceNotFoundError,
@@ -32,6 +33,9 @@
3233
TableServiceClient,
3334
TableEntity,
3435
UpdateMode,
36+
generate_table_sas,
37+
TableSasPermissions,
38+
TableClient
3539
)
3640

3741
from _shared.testcase import TableTestCase
@@ -851,6 +855,58 @@ def test_new_delete_nonexistent_entity(self, tables_storage_account_name, tables
851855
finally:
852856
self._tear_down()
853857

858+
@pytest.mark.skipif(sys.version_info < (3, 0), reason="requires Python3")
859+
@pytest.mark.live_test_only
860+
@TablesPreparer()
861+
def test_batch_sas_auth(self, tables_storage_account_name, tables_primary_storage_account_key):
862+
# Arrange
863+
self._set_up(tables_storage_account_name, tables_primary_storage_account_key)
864+
try:
865+
866+
token = generate_table_sas(
867+
tables_storage_account_name,
868+
tables_primary_storage_account_key,
869+
self.table_name,
870+
permission=TableSasPermissions(add=True, read=True, update=True, delete=True),
871+
expiry=datetime.utcnow() + timedelta(hours=1),
872+
start=datetime.utcnow() - timedelta(minutes=1),
873+
)
874+
token = AzureSasCredential(token)
875+
876+
# Act
877+
service = TableServiceClient(
878+
self.account_url(tables_storage_account_name, "table"),
879+
credential=token,
880+
)
881+
table = service.get_table_client(self.table_name)
882+
883+
entity = TableEntity()
884+
entity.PartitionKey = 'batch_inserts'
885+
entity.test = EntityProperty(True)
886+
entity.test2 = 'value'
887+
entity.test3 = 3
888+
entity.test4 = EntityProperty(1234567890)
889+
890+
batch = table.create_batch()
891+
transaction_count = 0
892+
for i in range(10):
893+
entity.RowKey = str(i)
894+
batch.create_entity(entity)
895+
transaction_count += 1
896+
transaction_result = table.send_batch(batch)
897+
898+
assert transaction_result is not None
899+
900+
total_entities = 0
901+
for e in table.list_entities():
902+
total_entities += 1
903+
904+
assert total_entities == transaction_count
905+
finally:
906+
self._tear_down()
907+
908+
909+
854910
class TestTableUnitTest(TableTestCase):
855911

856912
#--Test cases for batch ---------------------------------------------

0 commit comments

Comments
 (0)