Skip to content

Commit 87f9315

Browse files
authored
Remove table backup/restore functionality. (#858)
1 parent d7e2f57 commit 87f9315

File tree

9 files changed

+47
-349
lines changed

9 files changed

+47
-349
lines changed

README.rst

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -213,25 +213,13 @@ the type of data you'd like to stream.
213213
name = UnicodeAttribute(range_key=True)
214214
id = UnicodeAttribute(hash_key=True)
215215
216-
Want to backup and restore a table? No problem.
217-
218-
.. code-block:: python
219-
220-
# Backup the table
221-
UserModel.dump("usermodel_backup.json")
222-
223-
# Restore the table
224-
UserModel.load("usermodel_backup.json")
225-
226-
227216
Features
228217
========
229218

230219
* Python >= 3.6 support
231220
* An ORM-like interface with query and scan filters
232221
* Compatible with DynamoDB Local
233222
* Supports the entire DynamoDB API
234-
* Full table backup/restore
235223
* Support for Unicode, Binary, JSON, Number, Set, and UTC Datetime attributes
236224
* Support for Global and Local Secondary Indexes
237225
* Provides iterators for working with queries, scans, that are automatically paginated

docs/backup_restore.rst

Lines changed: 0 additions & 66 deletions
This file was deleted.

docs/index.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ Topics
3737
optimistic_locking
3838
rate_limited_operations
3939
local
40-
backup_restore
4140
signals
4241
examples
4342
settings

docs/release_notes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,14 @@ The UTCDateTimeAttribute now strictly requires the date string format '%Y-%m-%dT
1919
PynamoDB has always written values with this format but previously would accept reading other formats.
2020
Items written using other formats must be rewritten before upgrading.
2121

22+
** Model Serialization **
23+
24+
The ``Model._serialize`` method has changed and now only returns a dictionary of the DynamoDB attribute values.
25+
2226
Other changes in this release:
2327

2428
* Python 2 is no longer supported. Python 3.6 or greater is now required.
29+
* Table backup functionality (``Model.dump[s]`` and ``Model.load[s]``) has been removed.
2530
* ``Model.query`` no longer demotes invalid range key conditions to be filter conditions to avoid surprising behaviors:
2631
where what's intended to be a cheap and fast condition ends up being expensive and slow. Since filter conditions
2732
cannot contain range keys, this had limited utility to begin with, and would sometimes cause confusing

examples/model.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -206,21 +206,8 @@ class Meta:
206206
# Print the size of the table
207207
print("Table size: {}".format(Thread.describe_table().get('ItemCount')))
208208

209-
# Dump the entire table to a file
210-
Thread.dump('thread.json')
211-
212209
# Optionally Delete all table items
213210
# Commented out for safety
214211
# for item in Thread.scan():
215212
# item.delete()
216213
print("Table size: {}".format(Thread.describe_table().get('ItemCount')))
217-
218-
# Restore table from a file
219-
Thread.load('thread.json')
220-
print("Table size: {}".format(Thread.describe_table().get('ItemCount')))
221-
222-
# Dump the entire table to a string
223-
serialized = Thread.dumps()
224-
225-
# Load the entire table from a string
226-
Thread.loads(serialized)

pynamodb/models.py

Lines changed: 28 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
"""
22
DynamoDB Models for PynamoDB
33
"""
4-
import json
54
import random
65
import time
76
import logging
@@ -13,7 +12,7 @@
1312
from pynamodb.expressions.update import Action
1413
from pynamodb.exceptions import DoesNotExist, TableDoesNotExist, TableError, InvalidStateError, PutError
1514
from pynamodb.attributes import (
16-
Attribute, AttributeContainer, AttributeContainerMeta, MapAttribute, TTLAttribute, VersionAttribute
15+
Attribute, AttributeContainer, AttributeContainerMeta, TTLAttribute, VersionAttribute
1716
)
1817
from pynamodb.connection.table import TableConnection
1918
from pynamodb.expressions.condition import Condition
@@ -119,10 +118,9 @@ def commit(self) -> None:
119118
log.debug("%s committing batch operation", self.model)
120119
put_items = []
121120
delete_items = []
122-
attrs_name = snake_to_camel_case(ATTRIBUTES)
123121
for item in self.pending_operations:
124122
if item['action'] == PUT:
125-
put_items.append(item['item']._serialize(attr_map=True)[attrs_name])
123+
put_items.append(item['item']._serialize())
126124
elif item['action'] == DELETE:
127125
delete_items.append(item['item']._get_keys())
128126
self.pending_operations = []
@@ -380,12 +378,11 @@ def batch_write(cls: Type[_T], auto_commit: bool = True) -> BatchWrite[_T]:
380378
return BatchWrite(cls, auto_commit=auto_commit)
381379

382380
def __repr__(self) -> str:
383-
table_name = self.Meta.table_name if self.Meta.table_name else 'unknown'
384-
serialized = self._serialize(null_check=False)
381+
hash_key, range_key = self._get_serialized_keys()
385382
if self._range_keyname:
386-
msg = "{}<{}, {}>".format(self.Meta.table_name, serialized.get(HASH), serialized.get(RANGE))
383+
msg = "{}<{}, {}>".format(self.Meta.table_name, hash_key, range_key)
387384
else:
388-
msg = "{}<{}>".format(self.Meta.table_name, serialized.get(HASH))
385+
msg = "{}<{}>".format(self.Meta.table_name, hash_key)
389386
return msg
390387

391388
def delete(self, condition: Optional[Condition] = None) -> Any:
@@ -824,59 +821,7 @@ def update_ttl(cls, ignore_update_ttl_errors: bool) -> None:
824821
else:
825822
raise
826823

827-
@classmethod
828-
def dumps(cls) -> Any:
829-
"""
830-
Returns a JSON representation of this model's table
831-
"""
832-
return json.dumps([item._get_json() for item in cls.scan()])
833-
834-
@classmethod
835-
def dump(cls, filename: str) -> None:
836-
"""
837-
Writes the contents of this model's table as JSON to the given filename
838-
"""
839-
with open(filename, 'w') as out:
840-
out.write(cls.dumps())
841-
842-
@classmethod
843-
def loads(cls, data: str) -> None:
844-
content = json.loads(data)
845-
with cls.batch_write() as batch:
846-
for item_data in content:
847-
item = cls._from_data(item_data)
848-
batch.save(item)
849-
850-
@classmethod
851-
def load(cls, filename: str) -> None:
852-
with open(filename, 'r') as inf:
853-
cls.loads(inf.read())
854-
855824
# Private API below
856-
@classmethod
857-
def _from_data(cls, data):
858-
"""
859-
Reconstructs a model object from JSON.
860-
"""
861-
hash_key, attrs = data
862-
range_key = attrs.pop('range_key', None)
863-
attributes = attrs.pop(snake_to_camel_case(ATTRIBUTES))
864-
hash_key_attribute = cls._hash_key_attribute()
865-
hash_keyname = hash_key_attribute.attr_name
866-
hash_keytype = hash_key_attribute.attr_type
867-
attributes[hash_keyname] = {
868-
hash_keytype: hash_key
869-
}
870-
if range_key is not None:
871-
range_key_attribute = cls._range_key_attribute()
872-
range_keyname = range_key_attribute.attr_name
873-
range_keytype = range_key_attribute.attr_type
874-
attributes[range_keyname] = {
875-
range_keytype: range_key
876-
}
877-
item = cls(_user_instantiated=False)
878-
item._deserialize(attributes)
879-
return item
880825

881826
@classmethod
882827
def _get_schema(cls):
@@ -943,19 +888,6 @@ def _get_indexes(cls):
943888
cls._indexes[snake_to_camel_case(LOCAL_SECONDARY_INDEXES)].append(idx)
944889
return cls._indexes
945890

946-
def _get_json(self):
947-
"""
948-
Returns a Python object suitable for serialization
949-
"""
950-
kwargs = {}
951-
serialized = self._serialize(null_check=False)
952-
hash_key = serialized.get(HASH)
953-
range_key = serialized.get(RANGE, None)
954-
if range_key is not None:
955-
kwargs[snake_to_camel_case(RANGE_KEY)] = range_key
956-
kwargs[snake_to_camel_case(ATTRIBUTES)] = serialized[snake_to_camel_case(ATTRIBUTES)]
957-
return hash_key, kwargs
958-
959891
def _get_save_args(self, attributes=True, null_check=True):
960892
"""
961893
Gets the proper *args, **kwargs for saving and retrieving this object
@@ -966,14 +898,18 @@ def _get_save_args(self, attributes=True, null_check=True):
966898
:param null_check: If True, then attributes are checked for null.
967899
"""
968900
kwargs = {}
969-
serialized = self._serialize(null_check=null_check)
970-
hash_key = serialized.get(HASH)
971-
range_key = serialized.get(RANGE, None)
901+
attribute_values = self._serialize(null_check)
902+
hash_key_attribute = self._hash_key_attribute()
903+
hash_key = attribute_values.pop(hash_key_attribute.attr_name, {}).get(hash_key_attribute.attr_type)
904+
range_key = None
905+
range_key_attribute = self._range_key_attribute()
906+
if range_key_attribute:
907+
range_key = attribute_values.pop(range_key_attribute.attr_name, {}).get(range_key_attribute.attr_type)
972908
args = (hash_key, )
973909
if range_key is not None:
974910
kwargs[snake_to_camel_case(RANGE_KEY)] = range_key
975911
if attributes:
976-
kwargs[snake_to_camel_case(ATTRIBUTES)] = serialized[snake_to_camel_case(ATTRIBUTES)]
912+
kwargs[snake_to_camel_case(ATTRIBUTES)] = attribute_values
977913
return args, kwargs
978914

979915
def _handle_version_attribute(self, serialized_attributes, actions=None):
@@ -1042,17 +978,21 @@ def _get_keys(self):
1042978
"""
1043979
Returns the proper arguments for deleting
1044980
"""
1045-
serialized = self._serialize(null_check=False)
1046-
hash_key = serialized.get(HASH)
1047-
range_key = serialized.get(RANGE, None)
1048-
attrs = {
1049-
self._hash_key_attribute().attr_name: hash_key,
1050-
}
1051-
if self._range_keyname is not None:
1052-
range_keyname = self._range_key_attribute().attr_name
1053-
attrs[range_keyname] = range_key
981+
hash_key, range_key = self._get_serialized_keys()
982+
hash_key_attribute = self._hash_key_attribute()
983+
range_key_attribute = self._range_key_attribute()
984+
attrs = {}
985+
if hash_key_attribute:
986+
attrs[hash_key_attribute.attr_name] = hash_key
987+
if range_key_attribute:
988+
attrs[range_key_attribute.attr_name] = range_key
1054989
return attrs
1055990

991+
def _get_serialized_keys(self) -> Tuple[_KeyType, _KeyType]:
992+
hash_key = getattr(self, self._hash_keyname) if self._hash_keyname else None
993+
range_key = getattr(self, self._range_keyname) if self._range_keyname else None
994+
return self._serialize_keys(hash_key, range_key)
995+
1056996
@classmethod
1057997
def _batch_get_page(cls, keys_to_get, consistent_read, attributes_to_get):
1058998
"""
@@ -1107,27 +1047,6 @@ def _get_connection(cls) -> TableConnection:
11071047
aws_session_token=cls.Meta.aws_session_token)
11081048
return cls._connection
11091049

1110-
def _serialize(self, null_check=True, attr_map=False) -> Dict[str, Dict[str, Any]]:
1111-
"""
1112-
Serializes all model attributes for use with DynamoDB
1113-
1114-
:param null_check: If True, then attributes are checked for null
1115-
:param attr_map: If True, then attributes are returned
1116-
"""
1117-
attributes = snake_to_camel_case(ATTRIBUTES)
1118-
attrs: Dict[str, Dict] = {attributes: super()._serialize(null_check)}
1119-
if not attr_map:
1120-
hash_key_attribute = self._hash_key_attribute()
1121-
hash_key_attribute_value = attrs[attributes].pop(hash_key_attribute.attr_name, None)
1122-
if hash_key_attribute_value is not None:
1123-
attrs[HASH] = hash_key_attribute_value[hash_key_attribute.attr_type]
1124-
range_key_attribute = self._range_key_attribute()
1125-
if range_key_attribute:
1126-
range_key_attribute_value = attrs[attributes].pop(range_key_attribute.attr_name, None)
1127-
if range_key_attribute_value is not None:
1128-
attrs[RANGE] = range_key_attribute_value[range_key_attribute.attr_type]
1129-
return attrs
1130-
11311050
@classmethod
11321051
def _serialize_value(cls, attr, value):
11331052
"""
@@ -1153,7 +1072,8 @@ def _serialize_keys(cls, hash_key, range_key=None) -> Tuple[_KeyType, _KeyType]:
11531072
:param hash_key: The hash key value
11541073
:param range_key: The range key value
11551074
"""
1156-
hash_key = cls._hash_key_attribute().serialize(hash_key)
1075+
if hash_key is not None:
1076+
hash_key = cls._hash_key_attribute().serialize(hash_key)
11571077
if range_key is not None:
11581078
range_key = cls._range_key_attribute().serialize(range_key)
11591079
return hash_key, range_key

0 commit comments

Comments
 (0)