as-aiopynamodb 1.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- as_aiopynamodb-1.0.1/LICENSE +21 -0
- as_aiopynamodb-1.0.1/MANIFEST.in +3 -0
- as_aiopynamodb-1.0.1/PKG-INFO +153 -0
- as_aiopynamodb-1.0.1/README.rst +112 -0
- as_aiopynamodb-1.0.1/aiopynamodb/__init__.py +11 -0
- as_aiopynamodb-1.0.1/aiopynamodb/_schema.py +51 -0
- as_aiopynamodb-1.0.1/aiopynamodb/_util.py +95 -0
- as_aiopynamodb-1.0.1/aiopynamodb/attributes.py +1421 -0
- as_aiopynamodb-1.0.1/aiopynamodb/connection/__init__.py +12 -0
- as_aiopynamodb-1.0.1/aiopynamodb/connection/_aiobotocore_patch.py +109 -0
- as_aiopynamodb-1.0.1/aiopynamodb/connection/_botocore_private.py +30 -0
- as_aiopynamodb-1.0.1/aiopynamodb/connection/base.py +1267 -0
- as_aiopynamodb-1.0.1/aiopynamodb/connection/table.py +329 -0
- as_aiopynamodb-1.0.1/aiopynamodb/constants.py +208 -0
- as_aiopynamodb-1.0.1/aiopynamodb/exceptions.py +247 -0
- as_aiopynamodb-1.0.1/aiopynamodb/expressions/__init__.py +0 -0
- as_aiopynamodb-1.0.1/aiopynamodb/expressions/condition.py +137 -0
- as_aiopynamodb-1.0.1/aiopynamodb/expressions/operand.py +366 -0
- as_aiopynamodb-1.0.1/aiopynamodb/expressions/projection.py +22 -0
- as_aiopynamodb-1.0.1/aiopynamodb/expressions/update.py +132 -0
- as_aiopynamodb-1.0.1/aiopynamodb/expressions/util.py +54 -0
- as_aiopynamodb-1.0.1/aiopynamodb/indexes.py +244 -0
- as_aiopynamodb-1.0.1/aiopynamodb/models.py +1161 -0
- as_aiopynamodb-1.0.1/aiopynamodb/pagination.py +194 -0
- as_aiopynamodb-1.0.1/aiopynamodb/py.typed +0 -0
- as_aiopynamodb-1.0.1/aiopynamodb/settings.py +53 -0
- as_aiopynamodb-1.0.1/aiopynamodb/signals.py +50 -0
- as_aiopynamodb-1.0.1/aiopynamodb/transactions.py +143 -0
- as_aiopynamodb-1.0.1/aiopynamodb/types.py +9 -0
- as_aiopynamodb-1.0.1/as_aiopynamodb.egg-info/PKG-INFO +153 -0
- as_aiopynamodb-1.0.1/as_aiopynamodb.egg-info/SOURCES.txt +50 -0
- as_aiopynamodb-1.0.1/as_aiopynamodb.egg-info/dependency_links.txt +1 -0
- as_aiopynamodb-1.0.1/as_aiopynamodb.egg-info/not-zip-safe +1 -0
- as_aiopynamodb-1.0.1/as_aiopynamodb.egg-info/requires.txt +7 -0
- as_aiopynamodb-1.0.1/as_aiopynamodb.egg-info/top_level.txt +1 -0
- as_aiopynamodb-1.0.1/mypy.ini +22 -0
- as_aiopynamodb-1.0.1/requirements-dev.txt +15 -0
- as_aiopynamodb-1.0.1/setup.cfg +10 -0
- as_aiopynamodb-1.0.1/setup.py +50 -0
- as_aiopynamodb-1.0.1/tests/test_attributes.py +1487 -0
- as_aiopynamodb-1.0.1/tests/test_base_connection.py +1726 -0
- as_aiopynamodb-1.0.1/tests/test_binary_legacy_encoding.py +16 -0
- as_aiopynamodb-1.0.1/tests/test_discriminator.py +196 -0
- as_aiopynamodb-1.0.1/tests/test_exceptions.py +49 -0
- as_aiopynamodb-1.0.1/tests/test_expressions.py +651 -0
- as_aiopynamodb-1.0.1/tests/test_model.py +3545 -0
- as_aiopynamodb-1.0.1/tests/test_pagination.py +94 -0
- as_aiopynamodb-1.0.1/tests/test_settings.py +22 -0
- as_aiopynamodb-1.0.1/tests/test_signals.py +101 -0
- as_aiopynamodb-1.0.1/tests/test_table_connection.py +572 -0
- as_aiopynamodb-1.0.1/tests/test_transaction.py +127 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2014 Jharrod LaFon
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: as-aiopynamodb
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: An Async Pythonic Interface to DynamoDB
|
|
5
|
+
Home-page: http://jlafon.io/pynamodb.html
|
|
6
|
+
Author: Jharrod LaFon
|
|
7
|
+
Author-email: jlafon@eyesopen.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Project-URL: Fork, https://github.com/AppSolves/AioPynamoDB
|
|
10
|
+
Project-URL: Original, https://github.com/brunobelloni/AioPynamoDB
|
|
11
|
+
Keywords: python dynamodb amazon async asyncio aiobotocore
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/x-rst
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: aiobotocore>=2.24.1
|
|
24
|
+
Requires-Dist: typing-extensions>=4; python_version < "3.11"
|
|
25
|
+
Provides-Extra: signals
|
|
26
|
+
Requires-Dist: blinker<2.0,>=1.3; extra == "signals"
|
|
27
|
+
Dynamic: author
|
|
28
|
+
Dynamic: author-email
|
|
29
|
+
Dynamic: classifier
|
|
30
|
+
Dynamic: description
|
|
31
|
+
Dynamic: description-content-type
|
|
32
|
+
Dynamic: home-page
|
|
33
|
+
Dynamic: keywords
|
|
34
|
+
Dynamic: license
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
Dynamic: project-url
|
|
37
|
+
Dynamic: provides-extra
|
|
38
|
+
Dynamic: requires-dist
|
|
39
|
+
Dynamic: requires-python
|
|
40
|
+
Dynamic: summary
|
|
41
|
+
|
|
42
|
+
===========
|
|
43
|
+
AioPynamoDB
|
|
44
|
+
===========
|
|
45
|
+
Work in progress. Careful in production.
|
|
46
|
+
|
|
47
|
+
This library is as fork of `PynamoDB <https://github.com/pynamodb/PynamoDB>`_ to add async support.
|
|
48
|
+
|
|
49
|
+
Basic functionality is working, help to improve it is welcome.
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
** Known Issues **
|
|
53
|
+
- Python type hints needs migration. MyPy testing implementation is pending and contributions in this area are welcome.
|
|
54
|
+
|
|
55
|
+
Installation
|
|
56
|
+
============
|
|
57
|
+
From GitHub::
|
|
58
|
+
|
|
59
|
+
$ pip install git+https://github.com/brunobelloni/AioPynamoDB#egg=aiopynamodb
|
|
60
|
+
|
|
61
|
+
Basic Usage
|
|
62
|
+
===========
|
|
63
|
+
|
|
64
|
+
Create a model that describes your DynamoDB table.
|
|
65
|
+
|
|
66
|
+
.. code-block:: python
|
|
67
|
+
|
|
68
|
+
from aiopynamodb.models import Model
|
|
69
|
+
from aiopynamodb.attributes import UnicodeAttribute
|
|
70
|
+
|
|
71
|
+
class UserModel(Model):
|
|
72
|
+
"""
|
|
73
|
+
A DynamoDB User
|
|
74
|
+
"""
|
|
75
|
+
class Meta:
|
|
76
|
+
table_name = "dynamodb-user"
|
|
77
|
+
email = UnicodeAttribute(null=True)
|
|
78
|
+
first_name = UnicodeAttribute(range_key=True)
|
|
79
|
+
last_name = UnicodeAttribute(hash_key=True)
|
|
80
|
+
|
|
81
|
+
PynamoDB allows you to create the table if needed (it must exist before you can use it!):
|
|
82
|
+
|
|
83
|
+
.. code-block:: python
|
|
84
|
+
|
|
85
|
+
await UserModel.create_table(read_capacity_units=1, write_capacity_units=1)
|
|
86
|
+
|
|
87
|
+
Create a new user:
|
|
88
|
+
|
|
89
|
+
.. code-block:: python
|
|
90
|
+
|
|
91
|
+
user = UserModel("John", "Denver")
|
|
92
|
+
user.email = "djohn@company.org"
|
|
93
|
+
await user.save()
|
|
94
|
+
|
|
95
|
+
Now, search your table for all users with a last name of 'Denver' and whose
|
|
96
|
+
first name begins with 'J':
|
|
97
|
+
|
|
98
|
+
.. code-block:: python
|
|
99
|
+
|
|
100
|
+
async for user in UserModel.query("Denver", UserModel.first_name.startswith("J")):
|
|
101
|
+
print(user.first_name)
|
|
102
|
+
|
|
103
|
+
Examples of ways to query your table with filter conditions:
|
|
104
|
+
|
|
105
|
+
.. code-block:: python
|
|
106
|
+
|
|
107
|
+
async for user in UserModel.query("Denver", UserModel.email=="djohn@company.org"):
|
|
108
|
+
print(user.first_name)
|
|
109
|
+
|
|
110
|
+
Retrieve an existing user:
|
|
111
|
+
|
|
112
|
+
.. code-block:: python
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
user = await UserModel.get("John", "Denver")
|
|
116
|
+
print(user)
|
|
117
|
+
except UserModel.DoesNotExist:
|
|
118
|
+
print("User does not exist")
|
|
119
|
+
|
|
120
|
+
Advanced Usage
|
|
121
|
+
==============
|
|
122
|
+
|
|
123
|
+
Want to use indexes? No problem:
|
|
124
|
+
|
|
125
|
+
.. code-block:: python
|
|
126
|
+
|
|
127
|
+
from aiopynamodb.models import Model
|
|
128
|
+
from aiopynamodb.indexes import GlobalSecondaryIndex, AllProjection
|
|
129
|
+
from aiopynamodb.attributes import NumberAttribute, UnicodeAttribute
|
|
130
|
+
|
|
131
|
+
class ViewIndex(GlobalSecondaryIndex):
|
|
132
|
+
class Meta:
|
|
133
|
+
read_capacity_units = 2
|
|
134
|
+
write_capacity_units = 1
|
|
135
|
+
projection = AllProjection()
|
|
136
|
+
view = NumberAttribute(default=0, hash_key=True)
|
|
137
|
+
|
|
138
|
+
class TestModel(Model):
|
|
139
|
+
class Meta:
|
|
140
|
+
table_name = "TestModel"
|
|
141
|
+
forum = UnicodeAttribute(hash_key=True)
|
|
142
|
+
thread = UnicodeAttribute(range_key=True)
|
|
143
|
+
view = NumberAttribute(default=0)
|
|
144
|
+
view_index = ViewIndex()
|
|
145
|
+
|
|
146
|
+
Now query the index for all items with 0 views:
|
|
147
|
+
|
|
148
|
+
.. code-block:: python
|
|
149
|
+
|
|
150
|
+
async for item in TestModel.view_index.query(0):
|
|
151
|
+
print("Item queried from index: {0}".format(item))
|
|
152
|
+
|
|
153
|
+
It's really that simple.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
===========
|
|
2
|
+
AioPynamoDB
|
|
3
|
+
===========
|
|
4
|
+
Work in progress. Careful in production.
|
|
5
|
+
|
|
6
|
+
This library is as fork of `PynamoDB <https://github.com/pynamodb/PynamoDB>`_ to add async support.
|
|
7
|
+
|
|
8
|
+
Basic functionality is working, help to improve it is welcome.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
** Known Issues **
|
|
12
|
+
- Python type hints needs migration. MyPy testing implementation is pending and contributions in this area are welcome.
|
|
13
|
+
|
|
14
|
+
Installation
|
|
15
|
+
============
|
|
16
|
+
From GitHub::
|
|
17
|
+
|
|
18
|
+
$ pip install git+https://github.com/brunobelloni/AioPynamoDB#egg=aiopynamodb
|
|
19
|
+
|
|
20
|
+
Basic Usage
|
|
21
|
+
===========
|
|
22
|
+
|
|
23
|
+
Create a model that describes your DynamoDB table.
|
|
24
|
+
|
|
25
|
+
.. code-block:: python
|
|
26
|
+
|
|
27
|
+
from aiopynamodb.models import Model
|
|
28
|
+
from aiopynamodb.attributes import UnicodeAttribute
|
|
29
|
+
|
|
30
|
+
class UserModel(Model):
|
|
31
|
+
"""
|
|
32
|
+
A DynamoDB User
|
|
33
|
+
"""
|
|
34
|
+
class Meta:
|
|
35
|
+
table_name = "dynamodb-user"
|
|
36
|
+
email = UnicodeAttribute(null=True)
|
|
37
|
+
first_name = UnicodeAttribute(range_key=True)
|
|
38
|
+
last_name = UnicodeAttribute(hash_key=True)
|
|
39
|
+
|
|
40
|
+
PynamoDB allows you to create the table if needed (it must exist before you can use it!):
|
|
41
|
+
|
|
42
|
+
.. code-block:: python
|
|
43
|
+
|
|
44
|
+
await UserModel.create_table(read_capacity_units=1, write_capacity_units=1)
|
|
45
|
+
|
|
46
|
+
Create a new user:
|
|
47
|
+
|
|
48
|
+
.. code-block:: python
|
|
49
|
+
|
|
50
|
+
user = UserModel("John", "Denver")
|
|
51
|
+
user.email = "djohn@company.org"
|
|
52
|
+
await user.save()
|
|
53
|
+
|
|
54
|
+
Now, search your table for all users with a last name of 'Denver' and whose
|
|
55
|
+
first name begins with 'J':
|
|
56
|
+
|
|
57
|
+
.. code-block:: python
|
|
58
|
+
|
|
59
|
+
async for user in UserModel.query("Denver", UserModel.first_name.startswith("J")):
|
|
60
|
+
print(user.first_name)
|
|
61
|
+
|
|
62
|
+
Examples of ways to query your table with filter conditions:
|
|
63
|
+
|
|
64
|
+
.. code-block:: python
|
|
65
|
+
|
|
66
|
+
async for user in UserModel.query("Denver", UserModel.email=="djohn@company.org"):
|
|
67
|
+
print(user.first_name)
|
|
68
|
+
|
|
69
|
+
Retrieve an existing user:
|
|
70
|
+
|
|
71
|
+
.. code-block:: python
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
user = await UserModel.get("John", "Denver")
|
|
75
|
+
print(user)
|
|
76
|
+
except UserModel.DoesNotExist:
|
|
77
|
+
print("User does not exist")
|
|
78
|
+
|
|
79
|
+
Advanced Usage
|
|
80
|
+
==============
|
|
81
|
+
|
|
82
|
+
Want to use indexes? No problem:
|
|
83
|
+
|
|
84
|
+
.. code-block:: python
|
|
85
|
+
|
|
86
|
+
from aiopynamodb.models import Model
|
|
87
|
+
from aiopynamodb.indexes import GlobalSecondaryIndex, AllProjection
|
|
88
|
+
from aiopynamodb.attributes import NumberAttribute, UnicodeAttribute
|
|
89
|
+
|
|
90
|
+
class ViewIndex(GlobalSecondaryIndex):
|
|
91
|
+
class Meta:
|
|
92
|
+
read_capacity_units = 2
|
|
93
|
+
write_capacity_units = 1
|
|
94
|
+
projection = AllProjection()
|
|
95
|
+
view = NumberAttribute(default=0, hash_key=True)
|
|
96
|
+
|
|
97
|
+
class TestModel(Model):
|
|
98
|
+
class Meta:
|
|
99
|
+
table_name = "TestModel"
|
|
100
|
+
forum = UnicodeAttribute(hash_key=True)
|
|
101
|
+
thread = UnicodeAttribute(range_key=True)
|
|
102
|
+
view = NumberAttribute(default=0)
|
|
103
|
+
view_index = ViewIndex()
|
|
104
|
+
|
|
105
|
+
Now query the index for all items with 0 views:
|
|
106
|
+
|
|
107
|
+
.. code-block:: python
|
|
108
|
+
|
|
109
|
+
async for item in TestModel.view_index.query(0):
|
|
110
|
+
print("Item queried from index: {0}".format(item))
|
|
111
|
+
|
|
112
|
+
It's really that simple.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from typing import Dict
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
if sys.version_info >= (3, 8):
|
|
6
|
+
from typing import TypedDict
|
|
7
|
+
else:
|
|
8
|
+
from typing_extensions import TypedDict
|
|
9
|
+
|
|
10
|
+
if sys.version_info >= (3, 11):
|
|
11
|
+
from typing import NotRequired
|
|
12
|
+
else:
|
|
13
|
+
from typing_extensions import NotRequired
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SchemaAttrDefinition(TypedDict):
|
|
17
|
+
AttributeName: str
|
|
18
|
+
AttributeType: str
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class KeySchema(TypedDict):
|
|
22
|
+
AttributeName: str
|
|
23
|
+
KeyType: str
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Projection(TypedDict):
|
|
27
|
+
ProjectionType: str
|
|
28
|
+
NonKeyAttributes: NotRequired[List[str]]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class IndexSchema(TypedDict):
|
|
32
|
+
index_name: str
|
|
33
|
+
key_schema: List[Dict[str, str]]
|
|
34
|
+
projection: Dict[str, str]
|
|
35
|
+
attribute_definitions: List[SchemaAttrDefinition]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ProvisionedThroughput(TypedDict, total=False):
|
|
39
|
+
ReadCapacityUnits: int
|
|
40
|
+
WriteCapacityUnits: int
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class GlobalSecondaryIndexSchema(IndexSchema):
|
|
44
|
+
provisioned_throughput: ProvisionedThroughput
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ModelSchema(TypedDict):
|
|
48
|
+
attribute_definitions: List[SchemaAttrDefinition]
|
|
49
|
+
key_schema: List[KeySchema]
|
|
50
|
+
global_secondary_indexes: List[GlobalSecondaryIndexSchema]
|
|
51
|
+
local_secondary_indexes: List[IndexSchema]
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from base64 import b64decode
|
|
3
|
+
from base64 import b64encode
|
|
4
|
+
from typing import Any
|
|
5
|
+
from typing import Dict
|
|
6
|
+
|
|
7
|
+
from aiopynamodb.constants import BINARY
|
|
8
|
+
from aiopynamodb.constants import BINARY_SET
|
|
9
|
+
from aiopynamodb.constants import BOOLEAN
|
|
10
|
+
from aiopynamodb.constants import LIST
|
|
11
|
+
from aiopynamodb.constants import MAP
|
|
12
|
+
from aiopynamodb.constants import NULL
|
|
13
|
+
from aiopynamodb.constants import NUMBER
|
|
14
|
+
from aiopynamodb.constants import NUMBER_SET
|
|
15
|
+
from aiopynamodb.constants import STRING
|
|
16
|
+
from aiopynamodb.constants import STRING_SET
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def attr_value_to_simple_dict(attribute_value: Dict[str, Any], force: bool) -> Any:
|
|
20
|
+
attr_type, attr_value = next(iter(attribute_value.items()))
|
|
21
|
+
if attr_type == LIST:
|
|
22
|
+
return [attr_value_to_simple_dict(v, force) for v in attr_value]
|
|
23
|
+
if attr_type == MAP:
|
|
24
|
+
return {k: attr_value_to_simple_dict(v, force) for k, v in attr_value.items()}
|
|
25
|
+
if attr_type == NULL:
|
|
26
|
+
return None
|
|
27
|
+
if attr_type == BOOLEAN:
|
|
28
|
+
return attr_value
|
|
29
|
+
if attr_type == STRING:
|
|
30
|
+
return attr_value
|
|
31
|
+
if attr_type == NUMBER:
|
|
32
|
+
return json.loads(attr_value)
|
|
33
|
+
if attr_type == BINARY:
|
|
34
|
+
if force:
|
|
35
|
+
return b64encode(attr_value).decode()
|
|
36
|
+
raise ValueError("Binary attributes are not supported")
|
|
37
|
+
if attr_type == BINARY_SET:
|
|
38
|
+
if force:
|
|
39
|
+
return [b64encode(v).decode() for v in attr_value]
|
|
40
|
+
raise ValueError("Binary set attributes are not supported")
|
|
41
|
+
if attr_type == STRING_SET:
|
|
42
|
+
if force:
|
|
43
|
+
return attr_value
|
|
44
|
+
raise ValueError("String set attributes are not supported")
|
|
45
|
+
if attr_type == NUMBER_SET:
|
|
46
|
+
if force:
|
|
47
|
+
return [json.loads(v) for v in attr_value]
|
|
48
|
+
raise ValueError("Number set attributes are not supported")
|
|
49
|
+
raise ValueError("Unknown attribute type: {}".format(attr_type))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def simple_dict_to_attr_value(value: Any) -> Dict[str, Any]:
|
|
53
|
+
if value is None:
|
|
54
|
+
return {NULL: True}
|
|
55
|
+
if value is True or value is False:
|
|
56
|
+
return {BOOLEAN: value}
|
|
57
|
+
if isinstance(value, (int, float)):
|
|
58
|
+
return {NUMBER: json.dumps(value)}
|
|
59
|
+
if isinstance(value, str):
|
|
60
|
+
return {STRING: value}
|
|
61
|
+
if isinstance(value, list):
|
|
62
|
+
return {LIST: [simple_dict_to_attr_value(v) for v in value]}
|
|
63
|
+
if isinstance(value, dict):
|
|
64
|
+
return {MAP: {k: simple_dict_to_attr_value(v) for k, v in value.items()}}
|
|
65
|
+
raise ValueError("Unknown value type: {}".format(type(value).__name__))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _b64encode(b: bytes) -> str:
|
|
69
|
+
return b64encode(b).decode()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def bin_encode_attr(attr: Dict[str, Any]) -> None:
|
|
73
|
+
if BINARY in attr:
|
|
74
|
+
attr[BINARY] = _b64encode(attr[BINARY])
|
|
75
|
+
elif BINARY_SET in attr:
|
|
76
|
+
attr[BINARY_SET] = [_b64encode(v) for v in attr[BINARY_SET]]
|
|
77
|
+
elif MAP in attr:
|
|
78
|
+
for sub_attr in attr[MAP].values():
|
|
79
|
+
bin_encode_attr(sub_attr)
|
|
80
|
+
elif LIST in attr:
|
|
81
|
+
for sub_attr in attr[LIST]:
|
|
82
|
+
bin_encode_attr(sub_attr)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def bin_decode_attr(attr: Dict[str, Any]) -> None:
|
|
86
|
+
if BINARY in attr:
|
|
87
|
+
attr[BINARY] = b64decode(attr[BINARY])
|
|
88
|
+
elif BINARY_SET in attr:
|
|
89
|
+
attr[BINARY_SET] = [b64decode(v) for v in attr[BINARY_SET]]
|
|
90
|
+
elif MAP in attr:
|
|
91
|
+
for sub_attr in attr[MAP].values():
|
|
92
|
+
bin_decode_attr(sub_attr)
|
|
93
|
+
elif LIST in attr:
|
|
94
|
+
for sub_attr in attr[LIST]:
|
|
95
|
+
bin_decode_attr(sub_attr)
|