statikk 0.0.9__tar.gz → 0.0.12__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.
- {statikk-0.0.9 → statikk-0.0.12}/PKG-INFO +4 -3
- {statikk-0.0.9 → statikk-0.0.12}/setup.cfg +3 -2
- {statikk-0.0.9 → statikk-0.0.12}/src/statikk/engine.py +57 -20
- {statikk-0.0.9 → statikk-0.0.12}/src/statikk/models.py +7 -3
- {statikk-0.0.9 → statikk-0.0.12}/src/statikk.egg-info/PKG-INFO +4 -3
- {statikk-0.0.9 → statikk-0.0.12}/src/statikk.egg-info/requires.txt +3 -2
- {statikk-0.0.9 → statikk-0.0.12}/tests/test_engine.py +80 -4
- {statikk-0.0.9 → statikk-0.0.12}/.coveragerc +0 -0
- {statikk-0.0.9 → statikk-0.0.12}/.gitignore +0 -0
- {statikk-0.0.9 → statikk-0.0.12}/.readthedocs.yml +0 -0
- {statikk-0.0.9 → statikk-0.0.12}/AUTHORS.rst +0 -0
- {statikk-0.0.9 → statikk-0.0.12}/CHANGELOG.rst +0 -0
- {statikk-0.0.9 → statikk-0.0.12}/CONTRIBUTING.rst +0 -0
- {statikk-0.0.9 → statikk-0.0.12}/LICENSE.txt +0 -0
- {statikk-0.0.9 → statikk-0.0.12}/README.rst +0 -0
- {statikk-0.0.9 → statikk-0.0.12}/assets/favicon.png +0 -0
- {statikk-0.0.9 → statikk-0.0.12}/assets/logo.png +0 -0
- {statikk-0.0.9 → statikk-0.0.12}/docs/Makefile +0 -0
- {statikk-0.0.9 → statikk-0.0.12}/docs/_static/.gitignore +0 -0
- {statikk-0.0.9 → statikk-0.0.12}/docs/authors.rst +0 -0
- {statikk-0.0.9 → statikk-0.0.12}/docs/changelog.rst +0 -0
- {statikk-0.0.9 → statikk-0.0.12}/docs/conf.py +0 -0
- {statikk-0.0.9 → statikk-0.0.12}/docs/contributing.rst +0 -0
- {statikk-0.0.9 → statikk-0.0.12}/docs/index.rst +0 -0
- {statikk-0.0.9 → statikk-0.0.12}/docs/license.rst +0 -0
- {statikk-0.0.9 → statikk-0.0.12}/docs/readme.rst +0 -0
- {statikk-0.0.9 → statikk-0.0.12}/docs/requirements.txt +0 -0
- {statikk-0.0.9 → statikk-0.0.12}/docs/usage.rst +0 -0
- {statikk-0.0.9 → statikk-0.0.12}/pyproject.toml +0 -0
- {statikk-0.0.9 → statikk-0.0.12}/setup.py +0 -0
- {statikk-0.0.9 → statikk-0.0.12}/src/statikk/__init__.py +0 -0
- {statikk-0.0.9 → statikk-0.0.12}/src/statikk/conditions.py +0 -0
- {statikk-0.0.9 → statikk-0.0.12}/src/statikk/expressions.py +0 -0
- {statikk-0.0.9 → statikk-0.0.12}/src/statikk.egg-info/SOURCES.txt +0 -0
- {statikk-0.0.9 → statikk-0.0.12}/src/statikk.egg-info/dependency_links.txt +0 -0
- {statikk-0.0.9 → statikk-0.0.12}/src/statikk.egg-info/not-zip-safe +0 -0
- {statikk-0.0.9 → statikk-0.0.12}/src/statikk.egg-info/top_level.txt +0 -0
- {statikk-0.0.9 → statikk-0.0.12}/tests/conftest.py +0 -0
- {statikk-0.0.9 → statikk-0.0.12}/tests/test_expressions.py +0 -0
- {statikk-0.0.9 → statikk-0.0.12}/tests/test_models.py +0 -0
- {statikk-0.0.9 → statikk-0.0.12}/tox.ini +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: statikk
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.12
|
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
|
@@ -14,13 +14,14 @@ Requires-Python: >=3.8
|
|
14
14
|
Description-Content-Type: text/x-rst; charset=UTF-8
|
15
15
|
License-File: LICENSE.txt
|
16
16
|
Requires-Dist: importlib-metadata; python_version < "3.8"
|
17
|
-
Requires-Dist: pydantic==2.
|
17
|
+
Requires-Dist: pydantic==2.7.4
|
18
18
|
Requires-Dist: boto3==1.28.43
|
19
|
+
Requires-Dist: aws-xray-sdk==2.14.0
|
19
20
|
Provides-Extra: testing
|
20
21
|
Requires-Dist: setuptools; extra == "testing"
|
21
22
|
Requires-Dist: pytest; extra == "testing"
|
22
23
|
Requires-Dist: pytest-cov; extra == "testing"
|
23
|
-
Requires-Dist: moto[dynamodb]; extra == "testing"
|
24
|
+
Requires-Dist: moto[dynamodb]==4.2.14; extra == "testing"
|
24
25
|
|
25
26
|
.. image:: ./assets/logo.png
|
26
27
|
:alt: Statikk
|
@@ -24,8 +24,9 @@ package_dir =
|
|
24
24
|
python_requires = >=3.8
|
25
25
|
install_requires =
|
26
26
|
importlib-metadata; python_version<"3.8"
|
27
|
-
pydantic==2.
|
27
|
+
pydantic==2.7.4
|
28
28
|
boto3==1.28.43
|
29
|
+
aws-xray-sdk==2.14.0
|
29
30
|
|
30
31
|
[options.packages.find]
|
31
32
|
where = src
|
@@ -37,7 +38,7 @@ testing =
|
|
37
38
|
setuptools
|
38
39
|
pytest
|
39
40
|
pytest-cov
|
40
|
-
moto[dynamodb]
|
41
|
+
moto[dynamodb]==4.2.14
|
41
42
|
|
42
43
|
[options.entry_points]
|
43
44
|
|
@@ -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:
|
@@ -149,15 +164,13 @@ class Table:
|
|
149
164
|
"""Deletes the DynamoDB table."""
|
150
165
|
self._dynamodb_client().delete_table(TableName=self.name)
|
151
166
|
|
152
|
-
def
|
153
|
-
|
154
|
-
|
155
|
-
:
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
def get_item(self, id: str, model_class: Type[DatabaseModel], sort_key: Optional[Any] = None):
|
167
|
+
def get_item(
|
168
|
+
self,
|
169
|
+
id: str,
|
170
|
+
model_class: Type[DatabaseModel],
|
171
|
+
sort_key: Optional[Any] = None,
|
172
|
+
consistent_read: bool = False,
|
173
|
+
):
|
161
174
|
"""
|
162
175
|
Returns an item from the database by id, using the partition key of the table.
|
163
176
|
:param id: The id of the item to retrieve.
|
@@ -167,15 +180,22 @@ class Table:
|
|
167
180
|
key = {self.key_schema.hash_key: id}
|
168
181
|
if sort_key:
|
169
182
|
key[self.key_schema.sort_key] = self._serialize_value(sort_key)
|
170
|
-
raw_data = self._get_dynamodb_table().get_item(Key=key)
|
183
|
+
raw_data = self._get_dynamodb_table().get_item(Key=key, ConsistentRead=consistent_read)
|
171
184
|
if "Item" not in raw_data:
|
172
185
|
raise ItemNotFoundError(f"{model_class} with id '{id}' not found.")
|
173
186
|
data = raw_data["Item"]
|
174
|
-
del data["type"]
|
175
187
|
for key, value in data.items():
|
176
188
|
data[key] = self._deserialize_value(value, model_class.model_fields[key])
|
177
189
|
return model_class(**data)
|
178
190
|
|
191
|
+
def delete_item(self, id: str):
|
192
|
+
"""
|
193
|
+
Deletes an item from the database by id, using the partition key of the table.
|
194
|
+
:param id: The id of the item to delete.
|
195
|
+
"""
|
196
|
+
key = {self.key_schema.hash_key: id}
|
197
|
+
self._get_dynamodb_table().delete_item(Key=key)
|
198
|
+
|
179
199
|
def put_item(self, model: DatabaseModel) -> DatabaseModel:
|
180
200
|
"""
|
181
201
|
Puts an item into the database.
|
@@ -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
|
@@ -366,7 +389,7 @@ class Table:
|
|
366
389
|
else:
|
367
390
|
results.extend(
|
368
391
|
[
|
369
|
-
|
392
|
+
self._deserialize_item(self._convert_dynamodb_to_python(item), model_class)
|
370
393
|
for item in response["Responses"][self.name]
|
371
394
|
]
|
372
395
|
)
|
@@ -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
|
-
if annotation is datetime or "datetime" in str(annotation):
|
428
|
+
if annotation is datetime or "datetime" in str(annotation) and value is not None:
|
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
|
|
@@ -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.12
|
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
|
@@ -14,13 +14,14 @@ Requires-Python: >=3.8
|
|
14
14
|
Description-Content-Type: text/x-rst; charset=UTF-8
|
15
15
|
License-File: LICENSE.txt
|
16
16
|
Requires-Dist: importlib-metadata; python_version < "3.8"
|
17
|
-
Requires-Dist: pydantic==2.
|
17
|
+
Requires-Dist: pydantic==2.7.4
|
18
18
|
Requires-Dist: boto3==1.28.43
|
19
|
+
Requires-Dist: aws-xray-sdk==2.14.0
|
19
20
|
Provides-Extra: testing
|
20
21
|
Requires-Dist: setuptools; extra == "testing"
|
21
22
|
Requires-Dist: pytest; extra == "testing"
|
22
23
|
Requires-Dist: pytest-cov; extra == "testing"
|
23
|
-
Requires-Dist: moto[dynamodb]; extra == "testing"
|
24
|
+
Requires-Dist: moto[dynamodb]==4.2.14; extra == "testing"
|
24
25
|
|
25
26
|
.. image:: ./assets/logo.png
|
26
27
|
:alt: Statikk
|
@@ -1,6 +1,7 @@
|
|
1
|
+
from _decimal import Decimal
|
1
2
|
from datetime import datetime, timezone
|
2
|
-
from typing import List
|
3
|
-
|
3
|
+
from typing import List, Optional
|
4
|
+
from pydantic import BaseModel
|
4
5
|
import pytest
|
5
6
|
from boto3.dynamodb.conditions import Attr
|
6
7
|
from moto import mock_dynamodb
|
@@ -28,7 +29,8 @@ class MyAwesomeModel(DatabaseModel):
|
|
28
29
|
name: str = "Foo"
|
29
30
|
values: set = {1, 2, 3, 4}
|
30
31
|
cost: int = 4
|
31
|
-
|
32
|
+
probability: float = 0.5
|
33
|
+
created_at: Optional[datetime] = None
|
32
34
|
|
33
35
|
class SimpleModel(DatabaseModel):
|
34
36
|
player_id: IndexPrimaryKeyField
|
@@ -89,6 +91,8 @@ def test_create_my_awesome_model():
|
|
89
91
|
"values": {1, 2, 3, 4},
|
90
92
|
"cost": 4,
|
91
93
|
"type": "MyAwesomeModel",
|
94
|
+
"probability": 0.5,
|
95
|
+
"created_at": None
|
92
96
|
}
|
93
97
|
model_2 = MyAwesomeModel(id="foo-2", player_id="123", tier="EPIC", name="FooFoo")
|
94
98
|
table.put_item(model_2)
|
@@ -102,6 +106,8 @@ def test_create_my_awesome_model():
|
|
102
106
|
"values": {1, 2, 3, 4},
|
103
107
|
"cost": 4,
|
104
108
|
"type": "MyAwesomeModel",
|
109
|
+
"probability": 0.5,
|
110
|
+
"created_at": None
|
105
111
|
}
|
106
112
|
mock_dynamodb().stop()
|
107
113
|
|
@@ -340,7 +346,7 @@ def test_batch_get_items():
|
|
340
346
|
models=[MyAwesomeModel],
|
341
347
|
)
|
342
348
|
_create_dynamodb_table(table)
|
343
|
-
model = MyAwesomeModel(id="foo", player_id="123", tier="LEGENDARY")
|
349
|
+
model = MyAwesomeModel(id="foo", player_id="123", tier="LEGENDARY", created_at=datetime(2024, 7, 9))
|
344
350
|
model_2 = MyAwesomeModel(id="foo-2", player_id="123", tier="LEGENDARY")
|
345
351
|
table.put_item(model)
|
346
352
|
table.put_item(model_2)
|
@@ -349,9 +355,11 @@ def test_batch_get_items():
|
|
349
355
|
assert models[0].id == model.id
|
350
356
|
assert models[0].model_type == model.model_type
|
351
357
|
assert models[0].tier == model.tier
|
358
|
+
assert models[0].created_at == datetime(2024, 7, 9)
|
352
359
|
assert models[1].id == model_2.id
|
353
360
|
assert models[1].model_type == model_2.model_type
|
354
361
|
assert models[1].tier == model_2.tier
|
362
|
+
assert models[1].created_at is None
|
355
363
|
mock_dynamodb().stop()
|
356
364
|
|
357
365
|
|
@@ -744,3 +752,71 @@ def test_index_field_order_is_respected():
|
|
744
752
|
model.save()
|
745
753
|
item = table.get_item("123", ModelWithIndexOrdersDefined)
|
746
754
|
assert item.gsi_sk == "ModelWithIndexOrdersDefined|EPIC|Mage"
|
755
|
+
|
756
|
+
|
757
|
+
def test_nested_models():
|
758
|
+
class InnerInnerModel(BaseModel):
|
759
|
+
baz: str
|
760
|
+
|
761
|
+
class InnerModel(BaseModel):
|
762
|
+
foo: str
|
763
|
+
values: List[datetime] = [
|
764
|
+
datetime(2023, 9, 9, 12, 0, 0),
|
765
|
+
datetime(2023, 9, 9, 13, 0, 0),
|
766
|
+
]
|
767
|
+
cost: int = 5
|
768
|
+
inner_inner: InnerInnerModel
|
769
|
+
|
770
|
+
class NestedModel(DatabaseModel):
|
771
|
+
player_id: IndexPrimaryKeyField
|
772
|
+
unit_class: IndexSecondaryKeyField = IndexSecondaryKeyField(order=2)
|
773
|
+
tier: IndexSecondaryKeyField = IndexSecondaryKeyField(order=1)
|
774
|
+
name: str = "Foo"
|
775
|
+
values: set = {1, 2, 3, 4}
|
776
|
+
cost: int = 4
|
777
|
+
inner_model: InnerModel
|
778
|
+
|
779
|
+
mock_dynamodb().start()
|
780
|
+
table = Table(
|
781
|
+
name="my-dynamodb-table",
|
782
|
+
key_schema=KeySchema(hash_key="id"),
|
783
|
+
indexes=[
|
784
|
+
GSI(
|
785
|
+
name="main-index",
|
786
|
+
hash_key=Key(name="gsi_pk"),
|
787
|
+
sort_key=Key(name="gsi_sk"),
|
788
|
+
)
|
789
|
+
],
|
790
|
+
models=[NestedModel],
|
791
|
+
)
|
792
|
+
_create_dynamodb_table(table)
|
793
|
+
model = NestedModel(
|
794
|
+
id="123",
|
795
|
+
player_id="456",
|
796
|
+
unit_class="Mage",
|
797
|
+
tier="EPIC",
|
798
|
+
inner_model=InnerModel(foo="bar", inner_inner=InnerInnerModel(baz="baz")),
|
799
|
+
)
|
800
|
+
model.save()
|
801
|
+
item = table.get_item("123", NestedModel)
|
802
|
+
assert item.model_dump() == {
|
803
|
+
"id": "123",
|
804
|
+
"player_id": "456",
|
805
|
+
"unit_class": "Mage",
|
806
|
+
"tier": "EPIC",
|
807
|
+
"name": "Foo",
|
808
|
+
"values": {Decimal("1"), Decimal("2"), Decimal("3"), Decimal("4")},
|
809
|
+
"cost": 4,
|
810
|
+
"inner_model": {
|
811
|
+
"foo": "bar",
|
812
|
+
"values": [
|
813
|
+
datetime(2023, 9, 9, 10, 0, tzinfo=timezone.utc),
|
814
|
+
datetime(2023, 9, 9, 11, 0, tzinfo=timezone.utc),
|
815
|
+
],
|
816
|
+
"cost": 5,
|
817
|
+
"inner_inner": {"baz": "baz"},
|
818
|
+
},
|
819
|
+
"gsi_pk": "456",
|
820
|
+
"gsi_sk": "NestedModel|EPIC|Mage",
|
821
|
+
"type": "NestedModel",
|
822
|
+
}
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|