Coverage for dibbler / models / Base.py: 100%
13 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-12 18:53 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-12 18:53 +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 return _pascal_case_to_snake_case(cls.__name__)
32 # NOTE: This is the default implementation of __repr__ for all tables,
33 # but it is preferable to override it for each table to get a nicer
34 # looking representation. This trades a bit of messiness for a complete
35 # output of all relevant fields.
36 def __repr__(self) -> str:
37 columns = ", ".join(
38 f"{k}={repr(v)}"
39 for k, v in self.__dict__.items()
40 if not any(
41 [
42 k.startswith("_"),
43 # Ensure that we don't try to print out the entire list of
44 # relationships, which could create an infinite loop
45 isinstance(v, Base),
46 isinstance(v, InstrumentedList),
47 isinstance(v, InstrumentedSet),
48 isinstance(v, InstrumentedDict),
49 ]
50 )
51 )
52 return f"<{self.__class__.__name__}({columns})>"