Coverage for tests / test_tablename.py: 100%

64 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2026-06-10 09:40 +0000

1from pydantic.alias_generators import to_snake 1ijklmnop

2from sqlalchemy import inspect 1ijklmnop

3from sqlalchemy.orm import declared_attr 1ijklmnop

4from sqlmodel import Field, Session, SQLModel, create_engine, select 1ijklmnop

5from sqlmodel.pool import StaticPool 1ijklmnop

6 

7 

8def _engine(): 1ijklmnop

9 return create_engine( 1GaqHbrIcsJdtKeuLfvMgwNhx

10 "sqlite://", connect_args={"check_same_thread": False}, poolclass=StaticPool 

11 ) 

12 

13 

14def test_default_tablename() -> None: 1ijklmnop

15 """table=True models get __tablename__ = classname.lower() by default.""" 

16 

17 class Gadget(SQLModel, table=True): 1GHIJKLMN

18 id: int | None = Field(default=None, primary_key=True) 1GHIJKLMN

19 

20 assert Gadget.__tablename__ == "gadget" 1GHIJKLMN

21 

22 engine = _engine() 1GHIJKLMN

23 SQLModel.metadata.create_all(engine) 1GHIJKLMN

24 assert inspect(engine).has_table("gadget") 1GHIJKLMN

25 

26 

27def test_explicit_tablename() -> None: 1ijklmnop

28 """An explicit __tablename__ overrides the default.""" 

29 

30 class Widget(SQLModel, table=True): 1abcdefgh

31 __tablename__ = "custom_widgets" 1abcdefgh

32 id: int | None = Field(default=None, primary_key=True) 1abcdefgh

33 name: str 1abcdefgh

34 

35 assert Widget.__tablename__ == "custom_widgets" 1abcdefgh

36 

37 engine = _engine() 1abcdefgh

38 SQLModel.metadata.create_all(engine) 1abcdefgh

39 assert inspect(engine).has_table("custom_widgets") 1abcdefgh

40 assert not inspect(engine).has_table("widget") 1abcdefgh

41 

42 with Session(engine) as session: 1abcdefgh

43 session.add(Widget(name="sprocket")) 1abcdefgh

44 session.commit() 1abcdefgh

45 

46 with Session(engine) as session: 1abcdefgh

47 row = session.exec(select(Widget)).first() 1abcdefgh

48 assert row is not None 1abcdefgh

49 assert row.name == "sprocket" 1abcdefgh

50 

51 

52def test_tablename_inheritance_default() -> None: 1ijklmnop

53 """A subclass that is also a table gets its own default __tablename__.""" 

54 

55 class BaseThing(SQLModel, table=True): 1OPQRSTUV

56 id: int | None = Field(default=None, primary_key=True) 1OPQRSTUV

57 kind: str = "base" 1OPQRSTUV

58 

59 class SubThing(BaseThing, table=True): 1OPQRSTUV

60 extra: str | None = None 1OPQRSTUV

61 

62 assert BaseThing.__tablename__ == "basething" 1OPQRSTUV

63 assert SubThing.__tablename__ == "subthing" 1OPQRSTUV

64 

65 

66def test_tablename_inheritance_explicit_child() -> None: 1ijklmnop

67 """A subclass can set its own __tablename__, visible on the class.""" 

68 

69 class Vehicle(SQLModel, table=True): 1yzABCDEF

70 id: int | None = Field(default=None, primary_key=True) 1yzABCDEF

71 kind: str = "" 1yzABCDEF

72 

73 class Truck(Vehicle, table=True): 1yzABCDEF

74 __tablename__ = "trucks" 1yzABCDEF

75 payload: int | None = None 1yzABCDEF

76 

77 assert Vehicle.__tablename__ == "vehicle" 1yzABCDEF

78 assert Truck.__tablename__ == "trucks" 1yzABCDEF

79 

80 

81def test_tablename_default_on_plain_model() -> None: 1ijklmnop

82 """Non-table models also get a default __tablename__.""" 

83 

84 class Schema(SQLModel): 1WXYZ0123

85 name: str 1WXYZ0123

86 

87 assert Schema.__tablename__ == "schema" 1WXYZ0123

88 

89 

90def test_tablename_declared_attr_directive() -> None: 1ijklmnop

91 """A dynamically computed __tablename__ via @declared_attr.directive works.""" 

92 

93 class FirstWidget(SQLModel, table=True): 1qrstuvwx

94 id: int | None = Field(default=None, primary_key=True) 1qrstuvwx

95 name: str 1qrstuvwx

96 

97 @declared_attr.directive 1qrstuvwx

98 @classmethod 1qrstuvwx

99 def __tablename__(cls) -> str: 1qrstuvwx

100 return to_snake(cls.__name__) 1qrstuvwx

101 

102 assert FirstWidget.__tablename__ == "first_widget" 1qrstuvwx

103 

104 engine = _engine() 1qrstuvwx

105 SQLModel.metadata.create_all(engine) 1qrstuvwx

106 assert inspect(engine).has_table("first_widget") 1qrstuvwx