def test_disable_allow_auto(): db = SqliteDb(":memory:") with patch.object(Cars, "allow_auto", False): mgr = MyOmen(db) mgr.cars = Cars(mgr) with pytest.raises(OmenNoPkError): mgr.cars.add(Car(gas_level=0, color="green"))
def test_thread_locked_writer_only(): db = SqliteDb(":memory:") mgr = MyOmen(db) mgr.cars = Cars(mgr) car = mgr.cars.add(Car(gas_level=0, color=str(0))) num_t = 15 num_w = 3 pool = ThreadPool(num_t) # to reproduce the problem with this, you need to catch python while switching contexts # in between setattr calls in a non-atomic "apply" function # this is basically impossible without sticking a time sleep in there # even with 100 attributes and 5000 threads it never failed # so this test case only tests if the atomic apply is totally/deeply broken def update_stuff(i): if i < num_w: with car: car.gas_level += 1 car.color = str(car.gas_level) time.sleep(0.1) else: with pytest.raises(OmenUseWithError): car.gas_level += 1 # lots of threads can update stuff pool.map(update_stuff, range(num_t)) assert car.gas_level == num_w
def test_threaded_reads(): db = SqliteDb(":memory:") mgr = MyOmen(db) mgr.cars = Cars(mgr) car = mgr.cars.add(Car(gas_level=0, color=str(0))) pool = ThreadPool(50) # to reproduce the problem with this, you need to catch python while switching contexts # in between setattr calls in a non-atomic "apply" function # this is basically impossible without sticking a time sleep in there # even with 100 attributes and 5000 threads it never failed # so this test case only tests if the atomic apply is totally/deeply broken def update_stuff(_i): time.sleep(0.00001) assert car.color == str(car.gas_level) with car: car.gas_level += 1 time.sleep(0.00001) car.color = str(car.gas_level) assert car.color == str(car.gas_level) time.sleep(0.00001) assert car.color == str(car.gas_level) # lots of threads can update stuff num_t = 10 pool.map(update_stuff, range(num_t)) assert car.gas_level == num_t
def test_type_checking(): db = SqliteDb(":memory:") mgr = MyOmen(db) Car._type_check = True mgr.cars = Cars(mgr) car = mgr.cars.add(Car(gas_level=0, color="green")) with pytest.raises(TypeError): with car: car.color = None with pytest.raises(TypeError): with car: car.color = 4 with pytest.raises(TypeError): with car: car.gas_level = "hello" with pytest.raises(TypeError): with car: car.color = b"ggh" car._type_check = False # notanorm integrity error because color is a required field with pytest.raises(notanorm.errors.IntegrityError): with car: car.color = None # sqlite allows this, so we do too, since type checking is off with car: car.color = b"ggh"
def test_readme(tmp_path): fname = str(tmp_path / "test.txt") db = SqliteDb(fname) mgr = MyOmen(db) # cars pk is autoincrement assert Cars.allow_auto mgr.cars = Cars(mgr) # by default, you can always iterate on tables assert mgr.cars.count() == 0 car = Car() # creates a default car (black, full tank) car.color = "red" car.gas_level = 0.5 car.doors.add(gen_objs.doors_row(type="a")) car.doors.add(gen_objs.doors_row(type="b")) car.doors.add(gen_objs.doors_row(type="c")) car.doors.add(gen_objs.doors_row(type="d")) assert not car.id mgr.cars.add(car) # cars have ids, generated by the db assert car.id with pytest.raises(AttributeError, match=r".*gas.*"): mgr.cars.add(Car(color="red", gas=0.3)) mgr.cars.add( Car( color="red", gas_level=0.3, doors=[gen_objs.doors_row(type=str(i)) for i in range(4)], )) assert sum(1 for _ in mgr.cars.select(color="red")) == 2 # 2 log.info("cars: %s", list(mgr.cars.select(color="red"))) car = mgr.cars.select_one(color="red", gas_level=0.3) assert not car._is_new with car: with pytest.raises(AttributeError, match=r".*gas.*"): car.gas = 0.9 car.gas_level = 0.9 types = set() for door in car.doors: types.add(int(door.type)) assert types == set(range(4)) # doors are inserted assert len(car.doors) == 4
def test_sync_on_getattr(): db = SqliteDb(":memory:") mgr = MyOmen(db) mgr.cars = Cars(mgr) car = Car(id=44, gas_level=0, color="green") mgr.cars.add(car) car._sync_on_getattr = True assert car.color == "green" db.update("cars", id=car.id, color="blue") assert car.color == "blue"
def test_underlying_delete(): db = SqliteDb(":memory:") mgr = MyOmen(db) mgr.cars = Cars(mgr) car = Car(id=44, gas_level=0, color="green") mgr.cars.add(car) assert db.select_one("cars", id=car.id) db.delete("cars", id=car.id) car2 = Car(id=44, gas_level=0, color="green") mgr.cars.add(car2) assert car2 is not car assert mgr.cars.get(id=44) is car2
def test_unbound_add(): db = SqliteDb(":memory:") mgr = MyOmen(db) mgr.cars = Cars(mgr) car = Car(id=44, gas_level=0, color="green") door = gen_objs.doors_row(type="a") car.doors.add(door) assert door.carid == car.id assert door.carid assert door in car.doors assert car.doors.select_one(type="a") mgr.cars.add(car) assert db.select_one("doors", carid=car.id, type="a")
def test_reload_from_disk(): db = SqliteDb(":memory:") mgr = MyOmen(db) mgr.cars = Cars(mgr) car = mgr.cars.add(Car(gas_level=0, color="green")) assert db.select_one("cars", id=1).color == "green" db.update("cars", id=1, color="blue") # doesn't notice db change because we have a weakref-cache assert car.color == "green" car2 = mgr.cars.select_one(id=1) # weakref cache assert car2 is car # db fills to cache on select assert car.color == "blue"
def test_threaded(): db = SqliteDb(":memory:") mgr = MyOmen(db) mgr.cars = Cars(mgr) car = mgr.cars.add(Car(gas_level=0)) pool = ThreadPool(10) def update_stuff(_i): with car: car.gas_level += 1 # lots of threads can update stuff num_t = 10 pool.map(update_stuff, range(num_t)) assert car.gas_level == num_t # written to db assert mgr.db.select_one("cars", id=car.id).gas_level == num_t
def test_cache(): db = SqliteDb(":memory:") mgr = MyOmen(db, cars=Cars) mgr.cars = Cars(mgr) orig = mgr.cars cars = ObjCache(mgr.cars) # replace the table with a cache mgr.cars = cars mgr.cars.add(Car(gas_level=2, color="green")) mgr.cars.add(Car(gas_level=3, color="green")) assert cars.select_one(id=1) assert cars.count() == 2 assert orig._cache mgr.db.insert("cars", id=99, gas_level=99, color="green") mgr.db.insert("cars", id=98, gas_level=98, color="green") # this won't hit the db # noinspection PyUnresolvedReferences assert not any(car.id == 99 for car in mgr.cars.select()) # this won't hit the db assert not cars.select_one(id=99) # this will assert cars.table.select(id=99) # now the cache is full assert cars.select(id=99) # but still missing others assert not cars.select_one(id=98) assert cars.reload() == 4 log.debug(orig._cache) # until now assert cars.select_one(id=98)
def test_weak_cache(caplog): # important to disable log capturing/reporting otherwise refs to car() could be out there! caplog.clear() caplog.set_level("ERROR") db = SqliteDb(":memory:") mgr = MyOmen(db) mgr.cars = Cars(mgr) car = mgr.cars.add(Car(gas_level=0)) # cache works car2 = mgr.cars.select_one(gas_level=0) assert id(car2) == id(car) assert len(mgr.cars._cache.data) == 1 # weak dict cleans up del car del car2 gc.collect() assert not mgr.cars._cache.data
def test_cache_sharing(): db = SqliteDb(":memory:") mgr = MyOmen(db) mgr.cars = Cars(mgr) cache = ObjCache(mgr.cars) car = Car(id=44, gas_level=0, color="green") cache.add(car) assert car in mgr.cars assert car in cache db.insert("cars", id=45, gas_level=45, color="blue") db.insert("cars", id=46, gas_level=46, color="red") assert cache.get(id=44) assert not cache.select_one(id=45) assert not cache.select_one(id=46) assert mgr.cars.select_one(id=45) assert cache.select_one(id=45) assert mgr.cars.select_one(id=46) assert cache.select_one(id=46) db.delete("cars", id=44) # still cached assert cache.get(id=44) assert cache.select_one(id=44) # still cached, select with a different filter does not clear cache assert mgr.cars.select_one(id=45) assert cache.select_one(id=44) # cache updated by select with matching filter assert not mgr.cars.select_one(id=44) assert not cache.select_one(id=44) # select() with no filter clears cache db.delete("cars", id=46) assert len(list(mgr.cars.select())) == 1 assert not cache.select_one(id=46) # reload() clears cache assert cache.get(id=45) db.delete("cars", id=45) cache.reload() assert not cache.select_one(id=45)
def test_cache_sharing_threaded(): db = SqliteDb(":memory:") mgr = MyOmen(db) mgr.cars = Cars(mgr) cache = ObjCache(mgr.cars) db.insert("cars", id=12, gas_level=0, color="green") assert mgr.cars.select_one(id=12) assert cache.select_one(id=12) # all threads update gas_level of cached car, only the first thread reloads the cache def update_stuff(_i): if _i == 0: cache.reload() # if the cache was cleared in in another thread, this returns None (behavior we want to avoid) c = cache.select_one(id=12) assert c with c: c.gas_level += 1 num_t = 10 pool = ThreadPool(num_t) pool.map(update_stuff, range(num_t)) assert cache.select_one(id=12).gas_level == num_t