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.
Files changed (51) hide show
  1. as_aiopynamodb-1.0.1/LICENSE +21 -0
  2. as_aiopynamodb-1.0.1/MANIFEST.in +3 -0
  3. as_aiopynamodb-1.0.1/PKG-INFO +153 -0
  4. as_aiopynamodb-1.0.1/README.rst +112 -0
  5. as_aiopynamodb-1.0.1/aiopynamodb/__init__.py +11 -0
  6. as_aiopynamodb-1.0.1/aiopynamodb/_schema.py +51 -0
  7. as_aiopynamodb-1.0.1/aiopynamodb/_util.py +95 -0
  8. as_aiopynamodb-1.0.1/aiopynamodb/attributes.py +1421 -0
  9. as_aiopynamodb-1.0.1/aiopynamodb/connection/__init__.py +12 -0
  10. as_aiopynamodb-1.0.1/aiopynamodb/connection/_aiobotocore_patch.py +109 -0
  11. as_aiopynamodb-1.0.1/aiopynamodb/connection/_botocore_private.py +30 -0
  12. as_aiopynamodb-1.0.1/aiopynamodb/connection/base.py +1267 -0
  13. as_aiopynamodb-1.0.1/aiopynamodb/connection/table.py +329 -0
  14. as_aiopynamodb-1.0.1/aiopynamodb/constants.py +208 -0
  15. as_aiopynamodb-1.0.1/aiopynamodb/exceptions.py +247 -0
  16. as_aiopynamodb-1.0.1/aiopynamodb/expressions/__init__.py +0 -0
  17. as_aiopynamodb-1.0.1/aiopynamodb/expressions/condition.py +137 -0
  18. as_aiopynamodb-1.0.1/aiopynamodb/expressions/operand.py +366 -0
  19. as_aiopynamodb-1.0.1/aiopynamodb/expressions/projection.py +22 -0
  20. as_aiopynamodb-1.0.1/aiopynamodb/expressions/update.py +132 -0
  21. as_aiopynamodb-1.0.1/aiopynamodb/expressions/util.py +54 -0
  22. as_aiopynamodb-1.0.1/aiopynamodb/indexes.py +244 -0
  23. as_aiopynamodb-1.0.1/aiopynamodb/models.py +1161 -0
  24. as_aiopynamodb-1.0.1/aiopynamodb/pagination.py +194 -0
  25. as_aiopynamodb-1.0.1/aiopynamodb/py.typed +0 -0
  26. as_aiopynamodb-1.0.1/aiopynamodb/settings.py +53 -0
  27. as_aiopynamodb-1.0.1/aiopynamodb/signals.py +50 -0
  28. as_aiopynamodb-1.0.1/aiopynamodb/transactions.py +143 -0
  29. as_aiopynamodb-1.0.1/aiopynamodb/types.py +9 -0
  30. as_aiopynamodb-1.0.1/as_aiopynamodb.egg-info/PKG-INFO +153 -0
  31. as_aiopynamodb-1.0.1/as_aiopynamodb.egg-info/SOURCES.txt +50 -0
  32. as_aiopynamodb-1.0.1/as_aiopynamodb.egg-info/dependency_links.txt +1 -0
  33. as_aiopynamodb-1.0.1/as_aiopynamodb.egg-info/not-zip-safe +1 -0
  34. as_aiopynamodb-1.0.1/as_aiopynamodb.egg-info/requires.txt +7 -0
  35. as_aiopynamodb-1.0.1/as_aiopynamodb.egg-info/top_level.txt +1 -0
  36. as_aiopynamodb-1.0.1/mypy.ini +22 -0
  37. as_aiopynamodb-1.0.1/requirements-dev.txt +15 -0
  38. as_aiopynamodb-1.0.1/setup.cfg +10 -0
  39. as_aiopynamodb-1.0.1/setup.py +50 -0
  40. as_aiopynamodb-1.0.1/tests/test_attributes.py +1487 -0
  41. as_aiopynamodb-1.0.1/tests/test_base_connection.py +1726 -0
  42. as_aiopynamodb-1.0.1/tests/test_binary_legacy_encoding.py +16 -0
  43. as_aiopynamodb-1.0.1/tests/test_discriminator.py +196 -0
  44. as_aiopynamodb-1.0.1/tests/test_exceptions.py +49 -0
  45. as_aiopynamodb-1.0.1/tests/test_expressions.py +651 -0
  46. as_aiopynamodb-1.0.1/tests/test_model.py +3545 -0
  47. as_aiopynamodb-1.0.1/tests/test_pagination.py +94 -0
  48. as_aiopynamodb-1.0.1/tests/test_settings.py +22 -0
  49. as_aiopynamodb-1.0.1/tests/test_signals.py +101 -0
  50. as_aiopynamodb-1.0.1/tests/test_table_connection.py +572 -0
  51. 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,3 @@
1
+ include LICENSE
2
+ include mypy.ini
3
+ include requirements-*.txt
@@ -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,11 @@
1
+ """
2
+ AioPynamoDB Library
3
+ ^^^^^^^^^^^^^^^^
4
+
5
+ A simple async abstraction over DynamoDB
6
+
7
+ """
8
+
9
+ __author__ = "Jharrod LaFon"
10
+ __license__ = "MIT"
11
+ __version__ = "1.0.1"
@@ -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)