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 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
- return boto3.client(
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
- return dynamodb.Table(self.name)
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(self, id: str, model_class: Type[DatabaseModel], sort_key: Optional[Any] = None):
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 [model_class(**item) for item in items["Items"]]
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(cls, filter_condition: Optional[ComparisonCondition] = None):
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.9
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.3.0
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.41.2)
2
+ Generator: bdist_wheel (0.43.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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,,