Skip to content

Commit 0524407

Browse files
committed
refactor: improve STI detection logic in CommonTableAttributes and clean up test fixture
1 parent 02488f8 commit 0524407

File tree

2 files changed

+27
-18
lines changed

2 files changed

+27
-18
lines changed

advanced_alchemy/base.py

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -220,25 +220,24 @@ def __tablename__(cls) -> Optional[str]:
220220
Returns:
221221
Optional[str]: Snake-case table name derived from class name, or None for STI child classes.
222222
"""
223-
# Check if __tablename__ was explicitly defined on this class (not inherited from parent)
224-
explicitly_defined_tablename = cls.__dict__.get("__tablename__")
225-
if explicitly_defined_tablename:
226-
for parent_cls in cls.__mro__[1:]:
227-
if not hasattr(parent_cls, "__dict__"):
228-
continue
229-
parent_tablename = parent_cls.__dict__.get("__tablename__")
230-
if parent_tablename == explicitly_defined_tablename:
231-
explicitly_defined_tablename = None
232-
break
233-
234-
if explicitly_defined_tablename:
235-
return explicitly_defined_tablename
223+
# STI pattern detection: only return None if this is truly an STI child
224+
# An STI child has ALL of these characteristics:
225+
# 1. Inherits from a mapped parent (has_inherited_table)
226+
# 2. Parent has polymorphic_on defined (indicating STI hierarchy)
227+
# 3. Not marked as concrete=True (which would be CTI)
228+
# 4. Did NOT explicitly define __tablename__ in its class body
236229

237-
# STI pattern: child inherits parent's table (unless concrete=True for CTI)
238230
is_concrete_table_inheritance = getattr(cls, "__mapper_args__", {}).get("concrete", False)
239-
if has_inherited_table(cls) and not is_concrete_table_inheritance:
240-
return None
241231

232+
if has_inherited_table(cls) and not is_concrete_table_inheritance:
233+
# Check if any parent has polymorphic_on to confirm this is truly STI
234+
for parent in cls.__mro__[1:]:
235+
parent_mapper_args = getattr(parent, "__mapper_args__", {})
236+
if "polymorphic_on" in parent_mapper_args:
237+
# This is STI - return None to use parent's table
238+
return None
239+
240+
# Not STI - auto-generate tablename from class name
242241
return table_name_regexp.sub(r"_\1", cls.__name__).lower()
243242

244243
@classmethod
@@ -270,7 +269,18 @@ def __table_cls__(cls, name: str, metadata: MetaData, *args: Any, **kwargs: Any)
270269
return Table(name, metadata, *args, **kwargs)
271270

272271
# No own PK - check if this is STI (inheriting from a mapped parent)
273-
if has_inherited_table(cls) and not getattr(cls, "__mapper_args__", {}).get("concrete", False):
272+
is_concrete = getattr(cls, "__mapper_args__", {}).get("concrete", False)
273+
274+
is_sti_child = False
275+
if has_inherited_table(cls) and not is_concrete:
276+
# Check if any parent has polymorphic_on to confirm this is truly STI
277+
for parent in cls.__mro__[1:]:
278+
parent_mapper_args = getattr(parent, "__mapper_args__", {})
279+
if "polymorphic_on" in parent_mapper_args:
280+
is_sti_child = True
281+
break
282+
283+
if is_sti_child:
274284
# This is STI - don't create a table, use parent's
275285
# Delete the inherited tablename so SQLAlchemy uses parent table
276286
if hasattr(cls, "__tablename__"):

tests/conftest.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ def _clear_sqlalchemy_mappers() -> Generator[None, None, None]:
3535
"""
3636
from advanced_alchemy.base import orm_registry
3737

38-
orm_registry.metadata.clear()
3938
yield
4039
orm_registry.dispose()
4140
orm_registry.metadata.clear()

0 commit comments

Comments
 (0)