diff --git a/entropylab_qpudb/_qpudatabase.py b/entropylab_qpudb/_qpudatabase.py index 9cc2b1f..8c3e587 100644 --- a/entropylab_qpudb/_qpudatabase.py +++ b/entropylab_qpudb/_qpudatabase.py @@ -101,6 +101,10 @@ def create_new_qpu_database(dbname, initial_data_dict, force_create=False, path= db_hist.close() +class ReadOnlyError(Exception): + pass + + class QpuDatabaseConnectionBase(Instrument): def setup_driver(self): pass @@ -143,13 +147,16 @@ def __init__(self, dbname, history_index=None, path=None): self._con_hist.transaction_manager.begin() hist_entries = self._con_hist.root()["entries"] if self._history_index is not None: + self._readonly = True message_index = self._history_index at = self._con_hist.root()["entries"][self._history_index]["timestamp"] else: + self._readonly = False message_index = len(hist_entries) - 1 at = None db = ZODB.DB(dbfilename) self._con = db.open(transaction_manager=transaction.TransactionManager(), at=at) + assert self._con.isReadOnly() == self._readonly, "internal error: Inconsistent readonly state" self._con.transaction_manager.begin() print( f"opening qpu database {dbname} from " @@ -159,6 +166,10 @@ def __init__(self, dbname, history_index=None, path=None): def __enter__(self): return self + @property + def readonly(self): + return self._readonly + def close(self): print(f"closing qpu database {self._dbname}") self._con._db.close() @@ -182,11 +193,11 @@ def set(self, element, attribute, value, new_cal_state=None): root["elements"][element][attribute].cal_state = new_cal_state def add_attribute( - self, - element: str, - attribute: str, - value: Any = None, - new_cal_state: Optional[CalState] = None, + self, + element: str, + attribute: str, + value: Any = None, + new_cal_state: Optional[CalState] = None, ) -> None: """ Adds an attribute to an existing element. @@ -235,6 +246,8 @@ def commit(self, message: Optional[str] = None) -> None: Permanently store the existing state to the DB and add a new commit to the history list :param message: an optional message for the commit """ + if self._readonly: + raise ReadOnlyError("Attempting to commit to a DB in a readonly state") self._con.transaction_manager.commit() hist_root = self._con_hist.root() hist_entries = hist_root["entries"] diff --git a/entropylab_qpudb/tests/test_qpudatabase.py b/entropylab_qpudb/tests/test_qpudatabase.py index 5a32bbb..a0f71b7 100644 --- a/entropylab_qpudb/tests/test_qpudatabase.py +++ b/entropylab_qpudb/tests/test_qpudatabase.py @@ -4,6 +4,7 @@ from time import sleep import pytest +from persistent.timestamp import _parseRaw from quaentropy.instruments.lab_topology import LabResources, ExperimentResources from quaentropy.results_backend.sqlalchemy.db import SqlAlchemyDB @@ -11,7 +12,7 @@ from entropylab_qpudb._qpudatabase import ( QpuDatabaseConnectionBase, create_new_qpu_database, - QpuParameter, + QpuParameter, ReadOnlyError, ) @@ -164,6 +165,29 @@ def test_set_with_commit_history(testdb): print(db.get_history()) +def test_fail_on_commit_to_readonly(testdb): + with QpuDatabaseConnection(testdb, simp_resolver) as db: + print() + db.set("q2", "p1", 11.0) + print(db.get("q2", "p1")) + db.commit("my first commit") + + sleep(0.5) + + # open historical connection + + # should fail when DB is modified + with QpuDatabaseConnection(testdb, simp_resolver, history_index=0) as db: + db.set('q1', 'p1', 444) + with pytest.raises(ReadOnlyError): + db.commit('trying') + + # should fail when DB is not modified + with QpuDatabaseConnection(testdb, simp_resolver, history_index=0) as db: + with pytest.raises(ReadOnlyError): + db.commit('trying') + + def test_print(testdb): with QpuDatabaseConnectionBase(testdb) as db: print()