Skip to content

Commit f6d73b7

Browse files
Merge pull request pandas-dev#18 from mstampfer/master
Snapshot specific symbol versions
2 parents 38b9667 + 8b1640b commit f6d73b7

File tree

3 files changed

+73
-9
lines changed

3 files changed

+73
-9
lines changed

arctic/store/version_store.py

+11-8
Original file line numberDiff line numberDiff line change
@@ -666,9 +666,9 @@ def _write_audit(self, user, message, changed_version):
666666
# Create the audit entry
667667
mongo_retry(self._audit.insert_one)(audit)
668668

669-
def snapshot(self, snap_name, metadata=None, skip_symbols=None):
669+
def snapshot(self, snap_name, metadata=None, skip_symbols=None, versions=None):
670670
"""
671-
Snapshot the current versions of symbols in the library. Can be used like:
671+
Snapshot versions of symbols in the library. Can be used like:
672672
673673
Parameters
674674
----------
@@ -678,6 +678,8 @@ def snapshot(self, snap_name, metadata=None, skip_symbols=None):
678678
an optional dictionary of metadata to persist along with the symbol.
679679
skip_symbols : `collections.Iterable`
680680
optional symbols to be excluded from the snapshot
681+
versions: `dict`
682+
an optional dictionary of versions of the symbols to be snapshot
681683
"""
682684
# Ensure the user doesn't insert duplicates
683685
snapshot = self._snapshots.find_one({'name': snap_name})
@@ -688,22 +690,23 @@ def snapshot(self, snap_name, metadata=None, skip_symbols=None):
688690
snapshot = {'_id': bson.ObjectId()}
689691
snapshot['name'] = snap_name
690692
snapshot['metadata'] = metadata
693+
694+
skip_symbols = set() if skip_symbols is None else set(skip_symbols)
691695

692-
if skip_symbols is None:
693-
skip_symbols = set()
694-
else:
695-
skip_symbols = set(skip_symbols)
696+
if versions is None:
697+
versions = {sym: None for sym in set(self.list_symbols()) - skip_symbols}
696698

697699
# Loop over, and snapshot all versions except those we've been asked to skip
698-
for sym in set(self.list_symbols()) - skip_symbols:
700+
for sym in versions:
699701
try:
700-
sym = self._read_metadata(sym, read_preference=ReadPreference.PRIMARY)
702+
sym = self._read_metadata(sym, read_preference=ReadPreference.PRIMARY, as_of=versions[sym])
701703
# Update the parents field of the version document
702704
mongo_retry(self._versions.update_one)({'_id': sym['_id']},
703705
{'$addToSet': {'parent': snapshot['_id']}})
704706
except NoDataFoundException:
705707
# Version has been deleted, not included in the snapshot
706708
pass
709+
707710
mongo_retry(self._snapshots.insert_one)(snapshot)
708711

709712
def delete_snapshot(self, snap_name):

tests/integration/store/test_version_store.py

+40
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,46 @@ def test_snapshot(library):
491491
assert versions[2]['snapshots'] == ['current']
492492

493493

494+
def test_snapshot_with_versions(library):
495+
""" Test snapshot of write versions consistency """
496+
library.write(symbol, ts1)
497+
library.write(symbol, ts2)
498+
499+
# ensure snapshot of previous version is taken
500+
library.snapshot('previous', versions={symbol: 1})
501+
versions = library.list_versions(symbol)
502+
assert versions[0]['snapshots'] == []
503+
assert versions[1]['snapshots'] == ['previous']
504+
assert_frame_equal(library.read(symbol, as_of='previous').data, ts1)
505+
506+
# ensure new snapshots are ordered after previous ones
507+
library.snapshot('new')
508+
versions = library.list_versions(symbol)
509+
assert versions[0]['snapshots'] == ['new']
510+
assert versions[0]['version'] == 2
511+
assert_frame_equal(library.read(symbol, as_of='new').data, ts2)
512+
513+
assert versions[1]['snapshots'] == ['previous']
514+
assert versions[1]['version'] == 1
515+
assert_frame_equal(library.read(symbol, as_of='previous').data, ts1)
516+
517+
# ensure snapshot of previous version doesn't overwrite current version
518+
library.write(symbol, ts1, prune_previous_version=True)
519+
library.snapshot('another', versions={symbol: 1})
520+
versions = library.list_versions(symbol)
521+
522+
assert versions[0]['snapshots'] == []
523+
assert versions[0]['version'] == 3
524+
assert_frame_equal(library.read(symbol).data, ts1)
525+
526+
assert versions[1]['snapshots'] == ['new']
527+
assert versions[1]['version'] == 2
528+
529+
assert versions[2]['snapshots'] == ['previous', 'another']
530+
assert versions[2]['version'] == 1
531+
assert_frame_equal(library.read(symbol, as_of='another').data, ts1)
532+
533+
494534
def test_snapshot_exclusion(library):
495535
library.write(symbol, ts1)
496536
library.snapshot('current', skip_symbols=[symbol])

tests/unit/store/test_version_store.py

+22-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from arctic.store import version_store
1212
from arctic.store.version_store import VersionStore, VersionedItem
1313
from arctic.arctic import ArcticLibraryBinding, Arctic
14-
from arctic.exceptions import ConcurrentModificationException
14+
from arctic.exceptions import ConcurrentModificationException, DuplicateSnapshotException
1515
from pymongo.errors import OperationFailure
1616
from pymongo.collection import Collection
1717

@@ -202,3 +202,24 @@ def test_read_reports_random_errors():
202202
VersionStore.read(self, sentinel.symbol, sentinel.as_of, sentinel.from_version)
203203
assert 'bad' in str(e)
204204
assert le.call_count == 1
205+
206+
207+
def test_snapshot():
208+
vs = create_autospec(VersionStore, _snapshots=Mock(),
209+
_collection=Mock(),
210+
_versions=Mock())
211+
vs._snapshots.find_one.return_value = False
212+
vs._versions.update_one.__name__ = 'name'
213+
vs._snapshots.insert_one.__name__ = 'name'
214+
vs.list_symbols.return_value = ['foo', 'bar']
215+
VersionStore.snapshot(vs, "symbol")
216+
assert vs._read_metadata.call_args_list == [call('foo', as_of=None, read_preference=ReadPreference.PRIMARY),
217+
call('bar', as_of=None, read_preference=ReadPreference.PRIMARY)]
218+
219+
220+
def test_snapshot_duplicate_raises_exception():
221+
vs = create_autospec(VersionStore, _snapshots=Mock())
222+
with pytest.raises(DuplicateSnapshotException) as e:
223+
vs._snapshots.find_one.return_value = True
224+
VersionStore.snapshot(vs, 'symbol')
225+
assert "Snapshot 'symbol' already exists" in str(e.value)

0 commit comments

Comments
 (0)