Skip to content

Commit 72a20be

Browse files
Support self referential tables for SQLModels (#467)
* Support self referential tables for SQLModels * SQLModel self referential support * Update CHANGES.rst Co-authored-by: Alex Grönholm <alex.gronholm@nextday.fi> * Update src/sqlacodegen/generators.py Co-authored-by: Alex Grönholm <alex.gronholm@nextday.fi> * Apply suggestion from @agronholm --------- Co-authored-by: Alex Grönholm <alex.gronholm@nextday.fi>
1 parent 4dd547e commit 72a20be

File tree

3 files changed

+89
-4
lines changed

3 files changed

+89
-4
lines changed

CHANGES.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Version history
77
``**kwargs`` in their initializers (such as MySQL ``CHAR`` with ``collation``) while
88
preserving existing ``*args`` rendering behavior (PR by @hyoj0942)
99
- Fixed missing metadata argument when rendering plain tables with the SQLModel
10-
generator
10+
- Added support for self-referential tables in the SQLModel generator (PR by @sheinbergon)
1111

1212
**4.0.1**
1313

src/sqlacodegen/generators.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1737,13 +1737,16 @@ def render_relationship_arguments(
17371737
) -> Mapping[str, Any]:
17381738
def render_column_attrs(column_attrs: list[ColumnAttribute]) -> str:
17391739
rendered = []
1740+
render_as_string = False
17401741
for attr in column_attrs:
1741-
if attr.model is relationship.source:
1742+
if not self.explicit_foreign_keys and attr.model is relationship.source:
17421743
rendered.append(attr.name)
17431744
else:
1744-
rendered.append(repr(f"{attr.model.name}.{attr.name}"))
1745+
rendered.append(f"{attr.model.name}.{attr.name}")
1746+
render_as_string = True
17451747

1746-
return "[" + ", ".join(rendered) + "]"
1748+
joined = "[" + ", ".join(rendered) + "]"
1749+
return repr(joined) if render_as_string else joined
17471750

17481751
def render_foreign_keys(column_attrs: list[ColumnAttribute]) -> str:
17491752
rendered = []

tests/test_generator_sqlmodel.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,3 +387,85 @@ def test_fallback_table(generator: CodeGenerator) -> None:
387387
)
388388
""",
389389
)
390+
391+
392+
def test_onetomany_selfref(generator: CodeGenerator) -> None:
393+
Table(
394+
"simple_items",
395+
generator.metadata,
396+
Column("id", INTEGER, primary_key=True),
397+
Column("parent_item_id", INTEGER),
398+
ForeignKeyConstraint(["parent_item_id"], ["simple_items.id"]),
399+
)
400+
401+
validate_code(
402+
generator.generate(),
403+
"""\
404+
from typing import Optional
405+
406+
from sqlalchemy import Column, ForeignKey, Integer
407+
from sqlmodel import Field, Relationship, SQLModel
408+
409+
class SimpleItems(SQLModel, table=True):
410+
__tablename__ = 'simple_items'
411+
412+
id: int = Field(sa_column=Column('id', Integer, primary_key=True))
413+
parent_item_id: Optional[int] = Field(default=None, sa_column=Column(\
414+
'parent_item_id', ForeignKey('simple_items.id')))
415+
416+
parent_item: Optional['SimpleItems'] = Relationship(\
417+
back_populates='parent_item_reverse', sa_relationship_kwargs={\
418+
'remote_side': '[SimpleItems.id]'})
419+
parent_item_reverse: list['SimpleItems'] = Relationship(\
420+
back_populates='parent_item', sa_relationship_kwargs={\
421+
'remote_side': '[SimpleItems.parent_item_id]'})
422+
""",
423+
)
424+
425+
426+
def test_onetomany_selfref_multi(generator: CodeGenerator) -> None:
427+
Table(
428+
"simple_items_selfref",
429+
generator.metadata,
430+
Column("id", INTEGER, primary_key=True),
431+
Column("parent_item_id", INTEGER),
432+
Column("top_item_id", INTEGER),
433+
ForeignKeyConstraint(["parent_item_id"], ["simple_items_selfref.id"]),
434+
ForeignKeyConstraint(["top_item_id"], ["simple_items_selfref.id"]),
435+
)
436+
437+
validate_code(
438+
generator.generate(),
439+
"""\
440+
from typing import Optional
441+
442+
from sqlalchemy import Column, ForeignKey, Integer
443+
from sqlmodel import Field, Relationship, SQLModel
444+
445+
class SimpleItemsSelfref(SQLModel, table=True):
446+
__tablename__ = 'simple_items_selfref'
447+
448+
id: int = Field(sa_column=Column('id', Integer, primary_key=True))
449+
parent_item_id: Optional[int] = Field(default=None, sa_column=Column(\
450+
'parent_item_id', ForeignKey('simple_items_selfref.id')))
451+
top_item_id: Optional[int] = Field(default=None, sa_column=Column(\
452+
'top_item_id', ForeignKey('simple_items_selfref.id')))
453+
454+
parent_item: Optional['SimpleItemsSelfref'] = Relationship(\
455+
back_populates='parent_item_reverse', sa_relationship_kwargs={\
456+
'remote_side': '[SimpleItemsSelfref.id]', \
457+
'foreign_keys': '[SimpleItemsSelfref.parent_item_id]'})
458+
parent_item_reverse: list['SimpleItemsSelfref'] = Relationship(\
459+
back_populates='parent_item', sa_relationship_kwargs={\
460+
'remote_side': '[SimpleItemsSelfref.parent_item_id]', \
461+
'foreign_keys': '[SimpleItemsSelfref.parent_item_id]'})
462+
top_item: Optional['SimpleItemsSelfref'] = Relationship(\
463+
back_populates='top_item_reverse', sa_relationship_kwargs={\
464+
'remote_side': '[SimpleItemsSelfref.id]', \
465+
'foreign_keys': '[SimpleItemsSelfref.top_item_id]'})
466+
top_item_reverse: list['SimpleItemsSelfref'] = Relationship(\
467+
back_populates='top_item', sa_relationship_kwargs={\
468+
'remote_side': '[SimpleItemsSelfref.top_item_id]', \
469+
'foreign_keys': '[SimpleItemsSelfref.top_item_id]'})
470+
""",
471+
)

0 commit comments

Comments
 (0)