def test_meta_set_update_many(historian: mincepy.Historian): car1 = Car() car2 = Car() car1id, car2id = historian.save(car1, car2) historian.archive.meta_set_many({ car1id: { 'reg': 'car1' }, car2id: { 'reg': 'car2' } }) results = historian.archive.meta_get_many((car1id, car2id)) assert results == {car1id: {'reg': 'car1'}, car2id: {'reg': 'car2'}} historian.archive.meta_update_many({ car1id: { 'colour': 'red' }, car2id: { 'reg': 'car2updated' } }) metas = historian.archive.meta_get_many((car1id, car2id)) assert metas == { car1id: { 'reg': 'car1', 'colour': 'red' }, car2id: { 'reg': 'car2updated' } }
def test_get_obj_graph_current(historian: mincepy.Historian): """Test that when changing a reference the reference graph is returned correctly""" car = Car() garage = Garage(mincepy.ObjRef(car)) gid = garage.save() garage_graph = historian.archive.get_obj_ref_graph(gid) assert len(garage_graph.edges) == 1 assert (gid, car.obj_id) in garage_graph.edges # Now, modify the garage car2 = Car() garage.car = mincepy.ObjRef(car2) garage.save() # Check that the reference graph is correct garage_graph = historian.archive.get_obj_ref_graph(gid) assert len(garage_graph.edges) == 1 assert (gid, car2.obj_id) in garage_graph.edges # Finally, set the reference to None garage.car = mincepy.ObjRef() garage.save() # Check that the reference graph is correct garage_graph = historian.archive.get_obj_ref_graph(gid) assert len(garage_graph.edges) == 0 assert len(garage_graph.nodes) == 1
def test_meta_set_update_many(historian: mincepy.Historian): archive = historian.archive # Use the historian to simplify saving of some data car1 = Car() car2 = Car() car1id, car2id = historian.save(car1, car2) archive.meta_set_many({car1id: {'reg': 'car1'}, car2id: {'reg': 'car2'}}) results = archive.meta_get_many((car1id, car2id)) assert results == {car1id: {'reg': 'car1'}, car2id: {'reg': 'car2'}} archive.meta_update_many({ car1id: { 'colour': 'red' }, car2id: { 'reg': 'car2updated' } }) metas = archive.meta_get_many((car1id, car2id)) assert metas == { car1id: { 'reg': 'car1', 'colour': 'red' }, car2id: { 'reg': 'car2updated' } }
def test_nested_references(historian: mincepy.Historian): car = Car() garage = Garage(car) car_id = historian.save(car) garage_id = historian.save(garage) # Now change the car car.make = 'fiat' car.colour = 'white' historian.save(car) # Try loading while the object is still alive loaded_garage = historian.load(garage_id) assert loaded_garage is garage # Now delete and load del garage del car loaded_garage2 = historian.load(garage_id) # Should be the last version fo the car assert loaded_garage2.car.make == 'fiat' assert len(historian.history(car_id)) == 2 # The following may seem counter intuitive that we only have one history # entry for garage. But above we only saved it once. It's just that when # we load the garage again we get the 'latest' version it's contents i.e. # the newer version of the car assert len(historian.history(garage_id)) == 1
def test_replace_simple(historian: mincepy.Historian): def paint_shop(car, colour): """An imaginary function that modifies an object but returns a copy rather than an in place modification""" return Car(car.make, colour) honda = Car('honda', 'yellow') honda_id = historian.save(honda) # Now paint the honda new_honda = paint_shop(honda, 'green') assert historian.get_obj_id(honda) == honda_id # Now we know that this is a 'continuation' of the history of the original honda, so replace historian.replace(honda, new_honda) assert historian.get_obj_id(honda) is None assert historian.get_obj_id(new_honda) == honda_id historian.save(new_honda) del honda, new_honda loaded = historian.load(honda_id) assert loaded.make == 'honda' assert loaded.colour == 'green' with pytest.raises(RuntimeError): # Check that we can't replace in a transaction with historian.transaction(): historian.replace(loaded, Car())
def test_distinct(historian): colours = {'red', 'green', 'blue'} for colour in colours: Car('ferrari', colour=colour).save() Car('skoda', colour=colour).save() assert set(historian.records.distinct(Car.colour)) == colours
def test_sync(historian: mincepy.Historian, archive_uri): historian2 = mincepy.connect(archive_uri) historian2.register_types(mincepy.testing.HISTORIAN_TYPES) car = Car('ferrari', 'red') historian.save_one(car) # Simulate saving the car from another connection same_car = historian2.load(car.obj_id) same_car.make = 'honda' same_car.colour = 'black' same_car.save() honda_record = historian2.get_current_record(same_car) del same_car # Now update and check the state historian.sync(car) assert car.make == 'honda' assert car.colour == 'black' # Also, check the cached record car_record = historian.get_current_record(car) assert car_record.snapshot_hash == honda_record.snapshot_hash assert car_record.state == honda_record.state # Check that syncing an unsaved object returns False (because there's nothing to do) assert historian.sync(Car()) is False # Test syncing an object that is deleted historian2.delete(car.obj_id) with pytest.raises(mincepy.NotFound): car.sync()
def test_history(historian: mincepy.Historian): rainbow = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'] car = Car() car_id = None for colour in rainbow: car.colour = colour car_id = historian.save(car) car_history = historian.history(car_id) assert len(car_history) == len(rainbow) for i, entry in enumerate(car_history): assert entry[1].colour == rainbow[i] # Test loading directly from snapshot id assert historian.load_snapshot(car_history[2].ref) == car_history[2].obj # Test slicing assert historian.history(car_id, -1)[0].obj.colour == rainbow[-1] # Try changing history old_version = car_history[2].obj old_version.colour = 'black' with pytest.raises(mincepy.ModificationError): historian.save(old_version)
def test_meta_on_delete(historian: mincepy.Historian): """Test that metadata gets deleted when the object does""" car = Car() car_id = car.save(meta={'reg': '1234'}) assert historian.meta.get(car_id) == {'reg': '1234'} historian.delete(car_id) assert historian.meta.get(car_id) is None
def test_metadata_find_objects(historian: mincepy.Historian): honda = Car('honda', 'white') honda2 = Car('honda', 'white') historian.save_one(honda, meta={'reg': 'H123', 'vin': 1234}) historian.save(honda2) results = list(historian.find(Car, meta={'reg': 'H123'})) assert len(results) == 1
def test_metadata_multiple(historian: mincepy.Historian): honda = Car('honda', 'white') zonda = Car('zonda', 'yellow') historian.save((honda, {'reg': 'H123'}), (zonda, {'reg': 'Z456'})) assert historian.meta.get(honda) == {'reg': 'H123'} assert historian.meta.get(zonda) == {'reg': 'Z456'}
def test_meta_find(historian: mincepy.Historian): car1 = Car() car2 = Car() car1id, _ = historian.save(car1, car2) historian.archive.meta_set(car1id, {'reg': 'car1'}) results = dict(historian.archive.meta_find({}, (car1id, ))) assert results == {car1id: {'reg': 'car1'}}
def test_meta_unique_index(historian: mincepy.Historian): historian.meta.create_index('reg', True) car = Car() car.save(meta={'reg': 'VD495'}) historian.archive.meta_find(dict(reg='VD495')) with pytest.raises(mincepy.DuplicateKeyError): Car().save(meta={'reg': 'VD495'})
def test_to_obj_id(historian: mincepy.Historian): car = Car() car_id = car.save() assert historian.to_obj_id(car_id) is car_id assert historian.to_obj_id(car) == car_id assert historian.to_obj_id(str(car_id)) == car_id assert historian.to_obj_id('carrot') is None assert historian.to_obj_id(historian.get_snapshot_id(car)) == car_id
def test_tuple_helper(historian: mincepy.Historian): historian.register_type(mincepy.common_helpers.TupleHelper()) container = mincepy.builtins.List() container.append((Car('ferrari'), )) container_id = historian.save(container) del container loaded = historian.load(container_id) assert loaded[0][0] == Car('ferrari')
def test_meta_stick_copy(historian: mincepy.Historian): """Test that sticky meta is applied to objects that are copied. Issue #13: https://github.com/muhrin/mincepy/issues/13""" car = Car() historian.meta.sticky.update({'owner': 'martin'}) car.save() car_copy = mincepy.copy(car) car_copy.save() assert historian.meta.get(car) == {'owner': 'martin'} assert historian.meta.get(car_copy) == {'owner': 'martin'}
def test_count(historian: mincepy.Historian): archive = historian.archive car1 = Car('ferrari') car2 = Car('skoda') car1id, car2id = historian.save(car1, car2) archive.meta_set_many({car1id: {'reg': 'car1'}, car2id: {'reg': 'car2'}}) assert archive.count() == 2 assert archive.count(state=dict(make='ferrari')) == 1 assert archive.count(meta=dict(reg='car1')) == 1
def test_meta_update_many(historian: mincepy.Historian): archive = historian.archive car1 = Car() car2 = Car() car1id, car2id = historian.save(car1, car2) archive.meta_set_many({car1id: {'reg': 'car1'}, car2id: {'reg': 'car2'}}) results = archive.meta_get_many((car1id, car2id)) assert results[car1id] == {'reg': 'car1'} assert results[car2id] == {'reg': 'car2'}
def test_meta_set(historian: mincepy.Historian): archive = historian.archive # Use the historian to simplify saving of some data car1 = Car() car1.save() archive.meta_set(car1.obj_id, {'some': 'meta'}) assert archive.meta_get(car1.obj_id) == {'some': 'meta'} # Try settings invalid id with pytest.raises(mincepy.NotFound): archive.meta_set(bson.ObjectId(), {'some': 'meta'})
def test_meta_transaction(historian: mincepy.Historian): """Check that metadata respects transaction boundaries""" car1 = Car() with historian.transaction(): car1.save() with historian.transaction() as trans: historian.meta.set(car1.obj_id, {'spurious': True}) assert historian.meta.get(car1.obj_id) == {'spurious': True} trans.rollback() assert not historian.meta.get(car1) assert not historian.meta.get(car1)
def test_metadata_using_object_instance(historian: mincepy.Historian): car = Car() historian.save((car, {'reg': 'VD395'})) # Check that we get back what we just set assert historian.meta.get(car) == {'reg': 'VD395'} car.make = 'fiat' historian.save(car) # Check that the metadata is shared assert historian.meta.get(car) == {'reg': 'VD395'} historian.meta.set(car, {'reg': 'N317'}) assert historian.meta.get(car) == {'reg': 'N317'}
def test_meta_delete(historian: mincepy.Historian): """Test that when an object is delete so is its metadata""" car = Car() car_id = car.save(meta={'reg': '1234'}) results = dict(historian.meta.get(car)) assert results == {'reg': '1234'} historian.delete(car) assert historian.meta.get(car_id) is None results = tuple(historian.meta.find({}, obj_id=car_id)) assert len(results) == 0
def test_meta_find(historian: mincepy.Historian): archive = historian.archive car1 = Car() car2 = Car() car1id, _ = historian.save(car1, car2) archive.meta_set(car1id, {'reg': 'car1'}) results = dict(archive.meta_find({}, (car1id, ))) assert results == {car1id: {'reg': 'car1'}} # No metadata on car2 assert not list(archive.meta_find(obj_id=car2.obj_id))
def test_find_cars(historian: mincepy.Historian, benchmark, num): """Test finding a car as a function of the number of entries in the database""" # Put in the one we want to find historian.save(Car('honda', 'green')) # Put in the correct number of random other entries for _ in range(num): historian.save(Car(utils.random_str(10), utils.random_str(5))) result = benchmark(find, historian, state=dict(make='honda', colour='green')) assert len(result) == 1
def test_loading_snapshot(historian: mincepy.Historian): honda = Car('honda', 'white') historian.save(honda) white_honda_sid = historian.get_snapshot_id(honda) honda.colour = 'red' historian.save(honda) del honda with historian.transaction(): white_honda = historian.load_snapshot(white_honda_sid) assert white_honda.colour == 'white' # Make sure that if we load it again we get a different object instance assert white_honda is not historian.load_snapshot(white_honda_sid)
def test_purge(historian: mincepy.Historian): car = Car() garage = testing.Garage(mincepy.ObjRef(car)) car_id, _ = historian.save(car, garage) # Now update the car car.colour = 'blue' car.save() records_count = historian.records.find().count() # This should not perge anything as v0 of the car has a reference from the garage, while # v1 is the current, live, version res = historian.purge(dry_run=False) assert not res.deleted_purged assert not res.unreferenced_purged assert records_count == historian.records.find().count() # Now mutate the car, save and purge again car.colour = 'white' car.save() # This time, version 1 is unreferenced by anything and so can be safely deleted res = historian.purge(dry_run=False) assert not res.deleted_purged assert res.unreferenced_purged == {mincepy.SnapshotId(car_id, 1)} assert records_count == historian.records.find().count()
def test_meta_sticky_children(historian: mincepy.Historian): """Catch bug where metadata was not being set on being references by other objects""" garage = mincepy.RefList() garage.append(Car()) garage.append(Car()) historian.meta.sticky['owner'] = 'martin' garage_id = garage.save() car0_id = garage[0].save(meta={'for_sale': True}) car1_id = garage[1].save(meta={'owner': 'james'}) del garage assert historian.meta.get(garage_id) == {'owner': 'martin'} assert historian.meta.get(car0_id) == {'owner': 'martin', 'for_sale': True} assert historian.meta.get(car1_id) == {'owner': 'james'}
def test_metadata_simple(historian: mincepy.Historian): car = Car() ferrari_id = historian.save((car, {'reg': 'VD395'})) # Check that we get back what we just set assert historian.meta.get(ferrari_id) == {'reg': 'VD395'} car.make = 'fiat' red_fiat_id = historian.save(car) # Check that the metadata is shared assert historian.meta.get(red_fiat_id) == {'reg': 'VD395'} historian.meta.set(ferrari_id, {'reg': 'N317'}) # Check that this saves the metadata on the object level i.e. both are changed assert historian.meta.get(ferrari_id) == {'reg': 'N317'} assert historian.meta.get(red_fiat_id) == {'reg': 'N317'}
def test_storing_internal_object(historian: mincepy.Historian): class Person(mincepy.SavableObject): TYPE_ID = uuid.UUID('f6f83595-6375-4bc4-89f2-d8f31a1286b0') def __init__(self, car): super().__init__() self.car = car # This person 'owns' the car def __eq__(self, other): return self.car == other.car def yield_hashables(self, hasher): yield from hasher.yield_hashables(self.car) def save_instance_state(self, _depositor): return {'car': self.car} def load_instance_state(self, saved_state, _depositor): self.car = saved_state['car'] ferrari = Car('ferrari') mike = Person(ferrari) mike_id = historian.save(mike) del mike loaded_mike = historian.load(mike_id) assert loaded_mike.car.make == 'ferrari' # Default is to save by value so two cars should not be the same assert loaded_mike.car is not ferrari assert loaded_mike.car == ferrari # But values should match
def test_encode_nested(historian: mincepy.Historian): class CarDelegate(mincepy.SimpleSavable): TYPE_ID = uuid.UUID('c0148a43-c0c0-4d2b-9262-ed1c8c6ab2fc') ATTRS = ('car',) def __init__(self, car): super().__init__() self.car = car def save_instance_state(self, saver): return {'car': self.car} def load_instance_state(self, saved_state, _loader): self.car = saved_state['car'] historian.register_type(CarDelegate) car = Car() delegate = CarDelegate(car) garage = Garage(delegate) garage_id = historian.save(garage) del garage historian.load(garage_id)