return [row[0] for row in
self.exec(f'SELECT {column} FROM {table_name}')]
- def delete_where(self, table_name: str, key: str, target: int) -> None:
+ def delete_where(self, table_name: str, key: str,
+ target: int | str) -> None:
"""Delete from table where key == target."""
self.exec(f'DELETE FROM {table_name} WHERE {key} = ?', (target,))
raise HandledException(msg)
self.id_ = id_
+ def __eq__(self, other: object) -> bool:
+ if not isinstance(other, self.__class__):
+ return False
+ to_hash_me = tuple([self.id_] +
+ [getattr(self, name) for name in self.to_save])
+ to_hash_other = tuple([other.id_] +
+ [getattr(other, name) for name in other.to_save])
+ return hash(to_hash_me) == hash(to_hash_other)
+
+ def __lt__(self, other: Any) -> bool:
+ if not isinstance(other, self.__class__):
+ msg = 'cannot compare to object of different class'
+ raise HandledException(msg)
+ assert isinstance(self.id_, int)
+ assert isinstance(other.id_, int)
+ return self.id_ < other.id_
+
@classmethod
def get_cached(cls: type[BaseModelInstance],
id_: BaseModelId) -> BaseModelInstance | None:
obj.cache()
return obj
- @classmethod
- def _by_id(cls, db_conn: DatabaseConnection,
- id_: BaseModelId) -> Self | None:
- """Return instance found by ID, or None, and if from cache or not."""
- obj = cls.get_cached(id_)
- if not obj:
- for row in db_conn.row_where(cls.table_name, 'id', id_):
- obj = cls.from_table_row(db_conn, row)
- obj.cache()
- break
- return obj
-
@classmethod
def by_id(cls, db_conn: DatabaseConnection,
id_: BaseModelId | None,
# pylint: disable=unused-argument
create: bool = False) -> Self:
- """Retrieve by id_, on failure throw NotFoundException."""
+ """Retrieve by id_, on failure throw NotFoundException.
+
+ First try to get from cls.cache_, only then check DB; if found,
+ put into cache.
+
+ If create=True, make anew (but do not cache yet).
+ """
obj = None
if id_ is not None:
- obj = cls._by_id(db_conn, id_)
+ obj = cls.get_cached(id_)
+ if not obj:
+ for row in db_conn.row_where(cls.table_name, 'id', id_):
+ obj = cls.from_table_row(db_conn, row)
+ obj.cache()
+ break
if obj:
return obj
if create:
@classmethod
def all(cls: type[BaseModelInstance],
db_conn: DatabaseConnection) -> list[BaseModelInstance]:
- """Collect all objects of class."""
+ """Collect all objects of class into list.
+
+ Note that this primarily returns the contents of the cache, and only
+ _expands_ that by additional findings in the DB. This assumes the
+ cache is always instantly cleaned of any items that would be removed
+ from the DB.
+ """
items: dict[BaseModelId, BaseModelInstance] = {}
for k, v in cls.get_cache().items():
assert isinstance(v, cls)
items[item.id_] = item
return list(items.values())
- def save_core(self, db_conn: DatabaseConnection,
- update_with_lastrowid: bool = True) -> None:
- """Write bare-bones self (sans connected items), ensuring self.id_."""
+ def save_core(self, db_conn: DatabaseConnection) -> None:
+ """Write bare-bones self (sans connected items), ensuring self.id_.
+
+ Write both to DB, and to cache. To DB, write .id_ and attributes
+ listed in cls.to_save.
+
+ Ensure self.id_ by setting it to what the DB command returns as the
+ last saved row's ID (cursor.lastrowid), EXCEPT if self.id_ already
+ exists as a 'str', which implies we do our own ID creation (so far
+ only the case with the Day class, where it's to be a date string.
+ """
values = tuple([self.id_] + [getattr(self, key)
for key in self.to_save])
q_marks = DatabaseConnection.q_marks_from_values(values)
table_name = self.table_name
cursor = db_conn.exec(f'REPLACE INTO {table_name} VALUES {q_marks}',
values)
- if update_with_lastrowid:
+ if not isinstance(self.id_, str):
self.id_ = cursor.lastrowid # type: ignore[assignment]
self.cache()
+
+ def remove(self, db_conn: DatabaseConnection) -> None:
+ """Remove from DB and cache."""
+ if self.id_ is None or self.__class__.get_cached(self.id_) is None:
+ raise HandledException('cannot remove unsaved item')
+ self.uncache()
+ db_conn.delete_where(self.table_name, 'id', self.id_)