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
« 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
8def _engine(): 1ijklmnop
9 return create_engine( 1GaqHbrIcsJdtKeuLfvMgwNhx
10 "sqlite://", connect_args={"check_same_thread": False}, poolclass=StaticPool
11 )
14def test_default_tablename() -> None: 1ijklmnop
15 """table=True models get __tablename__ = classname.lower() by default."""
17 class Gadget(SQLModel, table=True): 1GHIJKLMN
18 id: int | None = Field(default=None, primary_key=True) 1GHIJKLMN
20 assert Gadget.__tablename__ == "gadget" 1GHIJKLMN
22 engine = _engine() 1GHIJKLMN
23 SQLModel.metadata.create_all(engine) 1GHIJKLMN
24 assert inspect(engine).has_table("gadget") 1GHIJKLMN
27def test_explicit_tablename() -> None: 1ijklmnop
28 """An explicit __tablename__ overrides the default."""
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
35 assert Widget.__tablename__ == "custom_widgets" 1abcdefgh
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
42 with Session(engine) as session: 1abcdefgh
43 session.add(Widget(name="sprocket")) 1abcdefgh
44 session.commit() 1abcdefgh
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
52def test_tablename_inheritance_default() -> None: 1ijklmnop
53 """A subclass that is also a table gets its own default __tablename__."""
55 class BaseThing(SQLModel, table=True): 1OPQRSTUV
56 id: int | None = Field(default=None, primary_key=True) 1OPQRSTUV
57 kind: str = "base" 1OPQRSTUV
59 class SubThing(BaseThing, table=True): 1OPQRSTUV
60 extra: str | None = None 1OPQRSTUV
62 assert BaseThing.__tablename__ == "basething" 1OPQRSTUV
63 assert SubThing.__tablename__ == "subthing" 1OPQRSTUV
66def test_tablename_inheritance_explicit_child() -> None: 1ijklmnop
67 """A subclass can set its own __tablename__, visible on the class."""
69 class Vehicle(SQLModel, table=True): 1yzABCDEF
70 id: int | None = Field(default=None, primary_key=True) 1yzABCDEF
71 kind: str = "" 1yzABCDEF
73 class Truck(Vehicle, table=True): 1yzABCDEF
74 __tablename__ = "trucks" 1yzABCDEF
75 payload: int | None = None 1yzABCDEF
77 assert Vehicle.__tablename__ == "vehicle" 1yzABCDEF
78 assert Truck.__tablename__ == "trucks" 1yzABCDEF
81def test_tablename_default_on_plain_model() -> None: 1ijklmnop
82 """Non-table models also get a default __tablename__."""
84 class Schema(SQLModel): 1WXYZ0123
85 name: str 1WXYZ0123
87 assert Schema.__tablename__ == "schema" 1WXYZ0123
90def test_tablename_declared_attr_directive() -> None: 1ijklmnop
91 """A dynamically computed __tablename__ via @declared_attr.directive works."""
93 class FirstWidget(SQLModel, table=True): 1qrstuvwx
94 id: int | None = Field(default=None, primary_key=True) 1qrstuvwx
95 name: str 1qrstuvwx
97 @declared_attr.directive 1qrstuvwx
98 @classmethod 1qrstuvwx
99 def __tablename__(cls) -> str: 1qrstuvwx
100 return to_snake(cls.__name__) 1qrstuvwx
102 assert FirstWidget.__tablename__ == "first_widget" 1qrstuvwx
104 engine = _engine() 1qrstuvwx
105 SQLModel.metadata.create_all(engine) 1qrstuvwx
106 assert inspect(engine).has_table("first_widget") 1qrstuvwx