Coverage for dibbler / models / Base.py: 83%
16 statements
« prev ^ index » next coverage.py v7.13.0, created at 2026-01-25 14:26 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2026-01-25 14:26 +0000
1from sqlalchemy import MetaData
2from sqlalchemy.orm import (
3 DeclarativeBase,
4 declared_attr,
5)
6from sqlalchemy.orm.collections import (
7 InstrumentedDict,
8 InstrumentedList,
9 InstrumentedSet,
10)
13def _pascal_case_to_snake_case(name: str) -> str:
14 return "".join(["_" + i.lower() if i.isupper() else i for i in name]).lstrip("_")
17class Base(DeclarativeBase):
18 metadata = MetaData(
19 naming_convention={
20 "ix": "ix__%(table_name)s__%(column_0_label)s",
21 "uq": "uq__%(table_name)s__%(column_0_name)s",
22 "ck": "ck__%(table_name)s__%(constraint_name)s",
23 "fk": "fk__%(table_name)s__%(column_0_name)s_%(referred_table_name)s",
24 "pk": "pk__%(table_name)s",
25 }
26 )
28 @declared_attr.directive
29 def __tablename__(cls) -> str:
30 if hasattr(cls, "__table_name__"): 30 ↛ 31line 30 didn't jump to line 31 because the condition on line 30 was never true
31 assert isinstance(cls.__table_name__, str)
32 return cls.__table_name__
33 return _pascal_case_to_snake_case(cls.__name__)
35 # NOTE: This is the default implementation of __repr__ for all tables,
36 # but it is preferable to override it for each table to get a nicer
37 # looking representation. This trades a bit of messiness for a complete
38 # output of all relevant fields.
39 def __repr__(self) -> str:
40 columns = ", ".join(
41 f"{k}={repr(v)}"
42 for k, v in self.__dict__.items()
43 if not any(
44 [
45 k.startswith("_"),
46 # Ensure that we don't try to print out the entire list of
47 # relationships, which could create an infinite loop
48 isinstance(v, Base),
49 isinstance(v, InstrumentedList),
50 isinstance(v, InstrumentedSet),
51 isinstance(v, InstrumentedDict),
52 ]
53 )
54 )
55 return f"<{self.__class__.__name__}({columns})>"