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

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) 

11 

12 

13def _pascal_case_to_snake_case(name: str) -> str: 

14 return "".join(["_" + i.lower() if i.isupper() else i for i in name]).lstrip("_") 

15 

16 

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 ) 

27 

28 @declared_attr.directive 

29 def __tablename__(cls) -> str: 

30 return _pascal_case_to_snake_case(cls.__name__) 

31 

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})>"