statikk 0.0.9__py3-none-any.whl → 0.0.11__py3-none-any.whl
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.
- statikk/engine.py +47 -10
- statikk/models.py +7 -3
- {statikk-0.0.9.dist-info → statikk-0.0.11.dist-info}/METADATA +3 -2
- statikk-0.0.11.dist-info/RECORD +10 -0
- {statikk-0.0.9.dist-info → statikk-0.0.11.dist-info}/WHEEL +1 -1
- statikk-0.0.9.dist-info/RECORD +0 -10
- {statikk-0.0.9.dist-info → statikk-0.0.11.dist-info}/LICENSE.txt +0 -0
- {statikk-0.0.9.dist-info → statikk-0.0.11.dist-info}/top_level.txt +0 -0
statikk/engine.py
CHANGED
@@ -6,7 +6,7 @@ import boto3
|
|
6
6
|
from botocore.config import Config
|
7
7
|
from pydantic.fields import FieldInfo
|
8
8
|
from boto3.dynamodb.conditions import ComparisonCondition, Key
|
9
|
-
from boto3.dynamodb.types import TypeDeserializer
|
9
|
+
from boto3.dynamodb.types import TypeDeserializer, Decimal
|
10
10
|
|
11
11
|
from statikk.conditions import Condition, Equals, BeginsWith
|
12
12
|
from statikk.expressions import UpdateExpressionBuilder
|
@@ -19,6 +19,10 @@ from statikk.models import (
|
|
19
19
|
KeySchema,
|
20
20
|
)
|
21
21
|
|
22
|
+
from aws_xray_sdk.core import xray_recorder
|
23
|
+
from aws_xray_sdk.core import patch_all
|
24
|
+
|
25
|
+
patch_all()
|
22
26
|
|
23
27
|
class InvalidIndexNameError(Exception):
|
24
28
|
pass
|
@@ -54,19 +58,30 @@ class Table:
|
|
54
58
|
model.set_table_ref(self)
|
55
59
|
if "type" not in model.model_fields:
|
56
60
|
model.model_fields["type"] = FieldInfo(annotation=str, default=model.model_type(), required=False)
|
61
|
+
self._client = None
|
62
|
+
self._dynamodb_table = None
|
57
63
|
|
58
64
|
def _dynamodb_client(self):
|
59
|
-
|
65
|
+
if self._client:
|
66
|
+
return self._client
|
67
|
+
|
68
|
+
self._client = boto3.client(
|
60
69
|
"dynamodb",
|
61
70
|
config=Config(region_name=os.environ.get("AWS_DEFAULT_REGION", "eu-west-1")),
|
62
71
|
)
|
63
72
|
|
73
|
+
return self._client
|
74
|
+
|
64
75
|
def _get_dynamodb_table(self):
|
76
|
+
if self._dynamodb_table:
|
77
|
+
return self._dynamodb_table
|
78
|
+
|
65
79
|
dynamodb = boto3.resource(
|
66
80
|
"dynamodb",
|
67
81
|
config=Config(region_name=os.environ.get("AWS_DEFAULT_REGION", "eu-west-1")),
|
68
82
|
)
|
69
|
-
|
83
|
+
self._dynamodb_table = dynamodb.Table(self.name)
|
84
|
+
return self._dynamodb_table
|
70
85
|
|
71
86
|
def _to_dynamodb_type(self, type: Any):
|
72
87
|
if type is str:
|
@@ -157,7 +172,13 @@ class Table:
|
|
157
172
|
key = {self.key_schema.hash_key: id}
|
158
173
|
self._get_dynamodb_table().delete_item(Key=key)
|
159
174
|
|
160
|
-
def get_item(
|
175
|
+
def get_item(
|
176
|
+
self,
|
177
|
+
id: str,
|
178
|
+
model_class: Type[DatabaseModel],
|
179
|
+
sort_key: Optional[Any] = None,
|
180
|
+
consistent_read: bool = False,
|
181
|
+
):
|
161
182
|
"""
|
162
183
|
Returns an item from the database by id, using the partition key of the table.
|
163
184
|
:param id: The id of the item to retrieve.
|
@@ -167,11 +188,10 @@ class Table:
|
|
167
188
|
key = {self.key_schema.hash_key: id}
|
168
189
|
if sort_key:
|
169
190
|
key[self.key_schema.sort_key] = self._serialize_value(sort_key)
|
170
|
-
raw_data = self._get_dynamodb_table().get_item(Key=key)
|
191
|
+
raw_data = self._get_dynamodb_table().get_item(Key=key, ConsistentRead=consistent_read)
|
171
192
|
if "Item" not in raw_data:
|
172
193
|
raise ItemNotFoundError(f"{model_class} with id '{id}' not found.")
|
173
194
|
data = raw_data["Item"]
|
174
|
-
del data["type"]
|
175
195
|
for key, value in data.items():
|
176
196
|
data[key] = self._deserialize_value(value, model_class.model_fields[key])
|
177
197
|
return model_class(**data)
|
@@ -207,7 +227,6 @@ class Table:
|
|
207
227
|
"""
|
208
228
|
data = self._serialize_item(model)
|
209
229
|
self._get_dynamodb_table().put_item(Item=data)
|
210
|
-
del data["type"]
|
211
230
|
for key, value in data.items():
|
212
231
|
data[key] = self._deserialize_value(value, model.model_fields[key])
|
213
232
|
return type(model)(**data)
|
@@ -231,6 +250,7 @@ class Table:
|
|
231
250
|
def _find_changed_indexes():
|
232
251
|
changed_index_values = set()
|
233
252
|
for prefixed_attribute, value in expression_attribute_values.items():
|
253
|
+
expression_attribute_values[prefixed_attribute] = self._serialize_value(value)
|
234
254
|
attribute = prefixed_attribute.replace(":", "")
|
235
255
|
if model.model_fields[attribute].annotation is IndexSecondaryKeyField:
|
236
256
|
idx_field = getattr(model, attribute)
|
@@ -313,13 +333,14 @@ class Table:
|
|
313
333
|
|
314
334
|
while last_evaluated_key:
|
315
335
|
items = self._get_dynamodb_table().query(**query_params)
|
316
|
-
yield from [
|
336
|
+
yield from [self._deserialize_item(item, model_class=model_class) for item in items["Items"]]
|
317
337
|
last_evaluated_key = items.get("LastEvaluatedKey", False)
|
318
338
|
|
319
339
|
def scan(
|
320
340
|
self,
|
321
341
|
model_class: Type[DatabaseModel],
|
322
342
|
filter_condition: Optional[ComparisonCondition] = None,
|
343
|
+
consistent_read: bool = False,
|
323
344
|
):
|
324
345
|
"""
|
325
346
|
Scans the database for items matching the provided filter condition. The method returns a list of items matching
|
@@ -328,7 +349,9 @@ class Table:
|
|
328
349
|
:param model_class: The model class to use to deserialize the items.
|
329
350
|
:param filter_condition: An optional filter condition to use for the query. See boto3.dynamodb.conditions.ComparisonCondition for more information.
|
330
351
|
"""
|
331
|
-
query_params = {
|
352
|
+
query_params = {
|
353
|
+
"ConsistentRead": consistent_read,
|
354
|
+
}
|
332
355
|
if filter_condition:
|
333
356
|
query_params["FilterExpression"] = filter_condition
|
334
357
|
last_evaluated_key = True
|
@@ -394,17 +417,29 @@ class Table:
|
|
394
417
|
data = self._prepare_model_data(item, self.indexes)
|
395
418
|
for key, value in data.items():
|
396
419
|
data[key] = self._serialize_value(value)
|
397
|
-
data["type"] = item.model_type()
|
398
420
|
return data
|
399
421
|
|
422
|
+
def _deserialize_item(self, item: Dict[str, Any], model_class: Type[DatabaseModel]):
|
423
|
+
for key, value in item.items():
|
424
|
+
item[key] = self._deserialize_value(value, model_class.model_fields[key])
|
425
|
+
return model_class(**item)
|
426
|
+
|
400
427
|
def _deserialize_value(self, value: Any, annotation: Any):
|
401
428
|
if annotation is datetime or "datetime" in str(annotation):
|
402
429
|
return datetime.fromtimestamp(int(value))
|
430
|
+
if annotation is float:
|
431
|
+
return float(value)
|
403
432
|
return value
|
404
433
|
|
405
434
|
def _serialize_value(self, value: Any):
|
406
435
|
if isinstance(value, datetime):
|
407
436
|
return int(value.timestamp())
|
437
|
+
if isinstance(value, float):
|
438
|
+
return Decimal(value)
|
439
|
+
if isinstance(value, list):
|
440
|
+
return [self._serialize_value(item) for item in value]
|
441
|
+
if isinstance(value, dict):
|
442
|
+
return {key: self._serialize_value(item) for key, item in value.items() if item is not None}
|
408
443
|
return value
|
409
444
|
|
410
445
|
def _set_index_fields(self, model: DatabaseModel | Type[DatabaseModel], idx: GSI):
|
@@ -422,6 +457,8 @@ class Table:
|
|
422
457
|
if field_info.annotation is IndexSecondaryKeyField and idx.name in getattr(model, field_name).index_names
|
423
458
|
]
|
424
459
|
|
460
|
+
if len(sort_key_fields_unordered) == idx.sort_key is not None:
|
461
|
+
raise IncorrectSortKeyError(f"Model {model.__class__} does not have a sort key defined.")
|
425
462
|
if sort_key_fields_unordered[0][1] is not None:
|
426
463
|
sort_key_fields_unordered.sort(key=lambda x: x[1])
|
427
464
|
|
statikk/models.py
CHANGED
@@ -111,15 +111,19 @@ class DatabaseModel(BaseModel):
|
|
111
111
|
)
|
112
112
|
|
113
113
|
@classmethod
|
114
|
-
def get(cls, id: str, sort_key: Optional[str] = None):
|
115
|
-
return cls._table.get_item(id=id, model_class=cls, sort_key=sort_key)
|
114
|
+
def get(cls, id: str, sort_key: Optional[str] = None, consistent_read: bool = False):
|
115
|
+
return cls._table.get_item(id=id, model_class=cls, sort_key=sort_key, consistent_read=consistent_read)
|
116
116
|
|
117
117
|
@classmethod
|
118
118
|
def batch_get(cls, ids: List[str], batch_size: int = 100):
|
119
119
|
return cls._table.batch_get_items(ids=ids, model_class=cls, batch_size=batch_size)
|
120
120
|
|
121
121
|
@classmethod
|
122
|
-
def scan(
|
122
|
+
def scan(
|
123
|
+
cls,
|
124
|
+
filter_condition: Optional[ComparisonCondition] = None,
|
125
|
+
consistent_read: bool = False,
|
126
|
+
):
|
123
127
|
return cls._table.scan(model_class=cls, filter_condition=filter_condition)
|
124
128
|
|
125
129
|
@staticmethod
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: statikk
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.11
|
4
4
|
Summary: statikk is a single table application (STA) library for DynamoDb.
|
5
5
|
Home-page: https://github.com/terinia/statikk
|
6
6
|
Author: Balint Biro
|
@@ -13,8 +13,9 @@ Classifier: Programming Language :: Python
|
|
13
13
|
Requires-Python: >=3.8
|
14
14
|
Description-Content-Type: text/x-rst; charset=UTF-8
|
15
15
|
License-File: LICENSE.txt
|
16
|
-
Requires-Dist: pydantic ==2.
|
16
|
+
Requires-Dist: pydantic ==2.7.4
|
17
17
|
Requires-Dist: boto3 ==1.28.43
|
18
|
+
Requires-Dist: aws-xray-sdk ==2.14.0
|
18
19
|
Requires-Dist: importlib-metadata ; python_version < "3.8"
|
19
20
|
Provides-Extra: testing
|
20
21
|
Requires-Dist: setuptools ; extra == 'testing'
|
@@ -0,0 +1,10 @@
|
|
1
|
+
statikk/__init__.py,sha256=pH5i4Fj1tbXLqLtTVIdoojiplZssQn0nnud8-HXodRE,577
|
2
|
+
statikk/conditions.py,sha256=66kJ-2YWqlV-dzEkiAe6c985dMQ-lpaoZ8aaoWhSs_M,2220
|
3
|
+
statikk/engine.py,sha256=wjorLdbirezwymmqyEgSdAVrJCizQZW6RSn00eXsvvY,22149
|
4
|
+
statikk/expressions.py,sha256=mF6Hmj3Kmj6KKXTymeTHSepVA7rhiSINpFgSAPeBTRY,12210
|
5
|
+
statikk/models.py,sha256=q-wY2bQRgaH0NTnR4NGnQ22eO3rUshK-aAykt7NVFO4,6172
|
6
|
+
statikk-0.0.11.dist-info/LICENSE.txt,sha256=uSH_2Hpb2Bigy5_HhBliN2fZbBU64G3ERM5zzhKPUEE,1078
|
7
|
+
statikk-0.0.11.dist-info/METADATA,sha256=wzxKaL43IN7GtHmpfU-7cHuuTW-Jr2PGO7SOh1fV014,3330
|
8
|
+
statikk-0.0.11.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
9
|
+
statikk-0.0.11.dist-info/top_level.txt,sha256=etKmBbjzIlLpSefXoiOfhWGEgvqUEALaFwCjFDBD9YI,8
|
10
|
+
statikk-0.0.11.dist-info/RECORD,,
|
statikk-0.0.9.dist-info/RECORD
DELETED
@@ -1,10 +0,0 @@
|
|
1
|
-
statikk/__init__.py,sha256=pH5i4Fj1tbXLqLtTVIdoojiplZssQn0nnud8-HXodRE,577
|
2
|
-
statikk/conditions.py,sha256=66kJ-2YWqlV-dzEkiAe6c985dMQ-lpaoZ8aaoWhSs_M,2220
|
3
|
-
statikk/engine.py,sha256=ArbYY4RU1kuRajpxF92lDPWpHYQ4kTMCFlianVePaYI,20712
|
4
|
-
statikk/expressions.py,sha256=mF6Hmj3Kmj6KKXTymeTHSepVA7rhiSINpFgSAPeBTRY,12210
|
5
|
-
statikk/models.py,sha256=XBJv-SJYMQ64kPsVat9VXFvK8_egaA2Z9SjuDSVn7AQ,6046
|
6
|
-
statikk-0.0.9.dist-info/LICENSE.txt,sha256=uSH_2Hpb2Bigy5_HhBliN2fZbBU64G3ERM5zzhKPUEE,1078
|
7
|
-
statikk-0.0.9.dist-info/METADATA,sha256=1XaS5o7SHNX7ALpLQ6999HNvwWDhYRE4wxunSzqTNVY,3292
|
8
|
-
statikk-0.0.9.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
|
9
|
-
statikk-0.0.9.dist-info/top_level.txt,sha256=etKmBbjzIlLpSefXoiOfhWGEgvqUEALaFwCjFDBD9YI,8
|
10
|
-
statikk-0.0.9.dist-info/RECORD,,
|
File without changes
|
File without changes
|