statikk 0.1.0__py3-none-any.whl → 0.1.2__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
@@ -8,6 +8,7 @@ from pydantic.fields import FieldInfo
8
8
  from boto3.dynamodb.conditions import ComparisonCondition, Key
9
9
  from boto3.dynamodb.types import TypeDeserializer, Decimal
10
10
 
11
+ from statikk.typing import T
11
12
  from statikk.conditions import Condition, Equals, BeginsWith
12
13
  from statikk.expressions import UpdateExpressionBuilder
13
14
  from statikk.models import (
@@ -170,10 +171,10 @@ class Table:
170
171
  def get_item(
171
172
  self,
172
173
  id: str,
173
- model_class: Type[DatabaseModel],
174
+ model_class: Type[T],
174
175
  sort_key: Optional[Any] = None,
175
176
  consistent_read: bool = False,
176
- ):
177
+ ) -> T:
177
178
  """
178
179
  Returns an item from the database by id, using the partition key of the table.
179
180
  :param id: The id of the item to retrieve.
@@ -331,11 +332,11 @@ class Table:
331
332
  def query_index(
332
333
  self,
333
334
  hash_key: Union[Condition | str],
334
- model_class: Type[DatabaseModel],
335
+ model_class: Type[T],
335
336
  range_key: Optional[Condition] = None,
336
337
  filter_condition: Optional[ComparisonCondition] = None,
337
338
  index_name: Optional[str] = None,
338
- ):
339
+ ) -> list[T]:
339
340
  """
340
341
  Queries the database using the provided hash key and range key conditions. A filter condition can also be provided
341
342
  using the filter_condition parameter. The method returns a list of items matching the query, deserialized into the
@@ -397,7 +398,7 @@ class Table:
397
398
  self,
398
399
  filter_condition: Optional[ComparisonCondition] = None,
399
400
  consistent_read: bool = False,
400
- ):
401
+ ) -> list[DatabaseModel]:
401
402
  """
402
403
  Scans the database for items matching the provided filter condition. The method returns a list of items matching
403
404
  the query, deserialized into the provided model_class parameter.
@@ -421,9 +422,7 @@ class Table:
421
422
  deserializer = TypeDeserializer()
422
423
  return {k: deserializer.deserialize(v) for k, v in item.items()}
423
424
 
424
- def batch_get_items(
425
- self, ids: List[str], model_class: Type[DatabaseModel], batch_size: int = 100
426
- ) -> List[DatabaseModel]:
425
+ def batch_get_items(self, ids: List[str], model_class: Type[T], batch_size: int = 100) -> list[T]:
427
426
  dynamodb = self._dynamodb_client()
428
427
 
429
428
  id_batches = [ids[i : i + batch_size] for i in range(0, len(ids), batch_size)]
@@ -548,7 +547,7 @@ class Table:
548
547
  sort_key_values.append(model.type())
549
548
  for sort_key_field in sort_key_fields:
550
549
  if sort_key_field in model.model_fields.keys():
551
- sort_key_values.append(getattr(model, sort_key_field))
550
+ sort_key_values.append(self._serialize_value(getattr(model, sort_key_field)))
552
551
 
553
552
  return self.delimiter.join(sort_key_values)
554
553
 
statikk/models.py CHANGED
@@ -4,6 +4,7 @@ import typing
4
4
  import logging
5
5
  from uuid import uuid4
6
6
  from typing import Optional, List, Any, Set, Type
7
+ from statikk.typing import T
7
8
 
8
9
  from boto3.dynamodb.conditions import ComparisonCondition
9
10
  from pydantic import BaseModel, model_serializer, model_validator, Field, Extra
@@ -55,6 +56,10 @@ class IndexFieldConfig(BaseModel):
55
56
  class TrackingMixin:
56
57
  _original_hash: int = Field(exclude=True)
57
58
 
59
+ @classmethod
60
+ def should_track(cls) -> bool:
61
+ return True
62
+
58
63
  def __init__(self):
59
64
  self._original_hash = self._recursive_hash()
60
65
 
@@ -99,7 +104,9 @@ class TrackingMixin:
99
104
  if contains_model:
100
105
  continue
101
106
 
102
- values.append(self._make_hashable(value))
107
+ hashed_value = self._make_hashable(value)
108
+ if hashed_value is not None:
109
+ values.append(hashed_value)
103
110
 
104
111
  return hash(tuple(values))
105
112
 
@@ -110,20 +117,24 @@ class TrackingMixin:
110
117
  return tuple(self._make_hashable(item) for item in value)
111
118
  elif isinstance(value, dict):
112
119
  return tuple((self._make_hashable(k), self._make_hashable(v)) for k, v in sorted(value.items()))
113
- elif isinstance(value, BaseModel):
114
- return value._recursive_hash() if hasattr(value, "_recursive_hash") else hash(value)
120
+ elif isinstance(value, BaseModel) and hasattr(value, "_recursive_hash"):
121
+ return value._recursive_hash()
115
122
  else:
116
123
  try:
117
124
  return hash(value)
118
- except Exception:
125
+ except TypeError:
119
126
  logger.warning(
120
127
  f"{type(value)} is unhashable, tracking will not work. Consider implementing the TrackingMixin for this type."
121
128
  )
129
+ return None
122
130
  return value
123
131
 
124
132
  @property
125
133
  def was_modified(self) -> bool:
126
- return self._recursive_hash() != self._original_hash
134
+ if self.should_track():
135
+ return self._recursive_hash() != self._original_hash
136
+
137
+ return True
127
138
 
128
139
 
129
140
  class DatabaseModel(BaseModel, TrackingMixin, extra=Extra.allow):
@@ -157,12 +168,12 @@ class DatabaseModel(BaseModel, TrackingMixin, extra=Extra.allow):
157
168
 
158
169
  @classmethod
159
170
  def query(
160
- cls,
171
+ cls: Type[T],
161
172
  hash_key: Condition,
162
173
  range_key: Optional[Condition] = None,
163
174
  filter_condition: Optional[ComparisonCondition] = None,
164
175
  index_name: Optional[str] = None,
165
- ):
176
+ ) -> T:
166
177
  return cls._table.query_index(
167
178
  hash_key=hash_key,
168
179
  model_class=cls,
@@ -173,12 +184,12 @@ class DatabaseModel(BaseModel, TrackingMixin, extra=Extra.allow):
173
184
 
174
185
  @classmethod
175
186
  def query_hierarchy(
176
- cls,
187
+ cls: Type[T],
177
188
  hash_key: Union[Condition | str],
178
189
  range_key: Optional[Condition] = None,
179
190
  filter_condition: Optional[ComparisonCondition] = None,
180
191
  index_name: Optional[str] = None,
181
- ) -> DatabaseModel:
192
+ ) -> T:
182
193
  return cls._table.query_hierarchy(
183
194
  hash_key=hash_key,
184
195
  model_class=cls,
@@ -212,11 +223,11 @@ class DatabaseModel(BaseModel, TrackingMixin, extra=Extra.allow):
212
223
  )
213
224
 
214
225
  @classmethod
215
- def get(cls, id: str, sort_key: Optional[str] = None, consistent_read: bool = False):
226
+ def get(cls: Type[T], id: str, sort_key: Optional[str] = None, consistent_read: bool = False) -> T:
216
227
  return cls._table.get_item(id=id, model_class=cls, sort_key=sort_key, consistent_read=consistent_read)
217
228
 
218
229
  @classmethod
219
- def batch_get(cls, ids: List[str], batch_size: int = 100):
230
+ def batch_get(cls: Type[T], ids: List[str], batch_size: int = 100) -> list[T]:
220
231
  return cls._table.batch_get_items(ids=ids, model_class=cls, batch_size=batch_size)
221
232
 
222
233
  def should_write_to_database(self) -> bool:
@@ -229,7 +240,7 @@ class DatabaseModel(BaseModel, TrackingMixin, extra=Extra.allow):
229
240
  cls,
230
241
  filter_condition: Optional[ComparisonCondition] = None,
231
242
  consistent_read: bool = False,
232
- ):
243
+ ) -> list[DatabaseModel]:
233
244
  return cls._table.scan(filter_condition=filter_condition)
234
245
 
235
246
  @model_serializer(mode="wrap")
statikk/typing.py ADDED
@@ -0,0 +1,3 @@
1
+ from typing import TypeVar
2
+
3
+ T = TypeVar("T", bound="DatabaseModel")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: statikk
3
- Version: 0.1.0
3
+ Version: 0.1.2
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
@@ -0,0 +1,12 @@
1
+ statikk/__init__.py,sha256=pH5i4Fj1tbXLqLtTVIdoojiplZssQn0nnud8-HXodRE,577
2
+ statikk/conditions.py,sha256=63FYMR-UUaE-ZJEb_8CU721CQTwhajq39-BbokmKeMA,2166
3
+ statikk/engine.py,sha256=hMA6V_aneWpJlCa4mogu6rE3-T7qwFuRGAkK-0nREj8,30884
4
+ statikk/expressions.py,sha256=mF6Hmj3Kmj6KKXTymeTHSepVA7rhiSINpFgSAPeBTRY,12210
5
+ statikk/fields.py,sha256=LkMP5NnX7WS0HSLxI3Q-dMOrfaJ0SD7SayZxJU5Acgg,86
6
+ statikk/models.py,sha256=rekTKPnN-I3oBOTzqkAy4FmSnaXI-woQNyG3rG85en8,13155
7
+ statikk/typing.py,sha256=qfpegORcdODuILK3gvuD4SdcZA1a7Myn0yvscOLPHOM,68
8
+ statikk-0.1.2.dist-info/LICENSE.txt,sha256=uSH_2Hpb2Bigy5_HhBliN2fZbBU64G3ERM5zzhKPUEE,1078
9
+ statikk-0.1.2.dist-info/METADATA,sha256=7IOtn9UQIVO-Iy0jJKooQrtzbtSJRq90Q_Z0wKg05-Y,3160
10
+ statikk-0.1.2.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
11
+ statikk-0.1.2.dist-info/top_level.txt,sha256=etKmBbjzIlLpSefXoiOfhWGEgvqUEALaFwCjFDBD9YI,8
12
+ statikk-0.1.2.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- statikk/__init__.py,sha256=pH5i4Fj1tbXLqLtTVIdoojiplZssQn0nnud8-HXodRE,577
2
- statikk/conditions.py,sha256=63FYMR-UUaE-ZJEb_8CU721CQTwhajq39-BbokmKeMA,2166
3
- statikk/engine.py,sha256=GCcqowgJa041Oo22sarSqDUQRWS4GbwHSNz_WGmcpNM,30855
4
- statikk/expressions.py,sha256=mF6Hmj3Kmj6KKXTymeTHSepVA7rhiSINpFgSAPeBTRY,12210
5
- statikk/fields.py,sha256=LkMP5NnX7WS0HSLxI3Q-dMOrfaJ0SD7SayZxJU5Acgg,86
6
- statikk/models.py,sha256=S5KdjNH0ufGn7iW4ZTVMXv1e177U95C28mbUlwDax_k,12831
7
- statikk-0.1.0.dist-info/LICENSE.txt,sha256=uSH_2Hpb2Bigy5_HhBliN2fZbBU64G3ERM5zzhKPUEE,1078
8
- statikk-0.1.0.dist-info/METADATA,sha256=Aqf0Od-2YmGbkQRqX36IhwPPzx-r5Wjr2ipe5dBcj7o,3160
9
- statikk-0.1.0.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
10
- statikk-0.1.0.dist-info/top_level.txt,sha256=etKmBbjzIlLpSefXoiOfhWGEgvqUEALaFwCjFDBD9YI,8
11
- statikk-0.1.0.dist-info/RECORD,,