Skip to content

Commit c5b1c3a

Browse files
committed
Fixed invalid code generation for identical table names
Fixes #169.
1 parent b2a5255 commit c5b1c3a

File tree

4 files changed

+58
-7
lines changed

4 files changed

+58
-7
lines changed

CHANGES.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ Version history
88
named, single column foreign key constraints (PR by Leonardus Chen)
99
. Fixed ``KeyError`` when rendering an index without any columns
1010
- Fixed improper handling of schema prefixes in sequence names in server defaults
11+
- Fixed identically named tables from different schemas resulting in invalid generated
12+
code
1113
- Worked around PostgreSQL UUID columns getting ``Any`` as the type annotation
1214

1315
**3.0.0b3**

src/sqlacodegen/generators.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
get_common_fk_constraints,
5757
get_compiled_expression,
5858
get_constraint_sort_key,
59+
qualified_table_name,
5960
render_callable,
6061
uses_default_name,
6162
)
@@ -692,6 +693,8 @@ def generate_models(self) -> list[Model]:
692693
# Pick association tables from the metadata into their own set, don't process them normally
693694
links: defaultdict[str, list[Model]] = defaultdict(lambda: [])
694695
for table in self.metadata.sorted_tables:
696+
qualified_name = qualified_table_name(table)
697+
695698
# Link tables have exactly two foreign key constraints and all columns are involved in
696699
# them
697700
fk_constraints = sorted(
@@ -700,18 +703,18 @@ def generate_models(self) -> list[Model]:
700703
if len(fk_constraints) == 2 and all(
701704
col.foreign_keys for col in table.columns
702705
):
703-
model = models_by_table_name[table.name] = Model(table)
706+
model = models_by_table_name[qualified_name] = Model(table)
704707
tablename = fk_constraints[0].elements[0].column.table.name
705708
links[tablename].append(model)
706709
continue
707710

708711
# Only form model classes for tables that have a primary key and are not association
709712
# tables
710713
if not table.primary_key:
711-
models_by_table_name[table.name] = Model(table)
714+
models_by_table_name[qualified_name] = Model(table)
712715
else:
713716
model = ModelClass(table)
714-
models_by_table_name[table.name] = model
717+
models_by_table_name[qualified_name] = model
715718

716719
# Fill in the columns
717720
for column in table.c:
@@ -735,7 +738,7 @@ def generate_models(self) -> list[Model]:
735738
for constraint in model.table.foreign_key_constraints:
736739
if set(get_column_names(constraint)) == pk_column_names:
737740
target = models_by_table_name[
738-
constraint.elements[0].column.table.name
741+
qualified_table_name(constraint.elements[0].column.table)
739742
]
740743
if isinstance(target, ModelClass):
741744
model.parent_class = target
@@ -750,6 +753,7 @@ def generate_models(self) -> list[Model]:
750753
}
751754
for model in models_by_table_name.values():
752755
self.generate_model_name(model, global_names)
756+
global_names.add(model.name)
753757

754758
return list(models_by_table_name.values())
755759

@@ -767,12 +771,14 @@ def generate_relationships(
767771
for constraint in sorted(
768772
source.table.foreign_key_constraints, key=get_constraint_sort_key
769773
):
770-
target = models_by_table_name[constraint.elements[0].column.table.name]
774+
target = models_by_table_name[
775+
qualified_table_name(constraint.elements[0].column.table)
776+
]
771777
if isinstance(target, ModelClass):
772778
if "nojoined" not in self.options:
773779
if set(get_column_names(constraint)) == pk_column_names:
774780
parent = models_by_table_name[
775-
constraint.elements[0].column.table.name
781+
qualified_table_name(constraint.elements[0].column.table)
776782
]
777783
if isinstance(parent, ModelClass):
778784
source.parent_class = parent
@@ -841,7 +847,7 @@ def generate_relationships(
841847
key=get_constraint_sort_key,
842848
)
843849
target = models_by_table_name[
844-
fk_constraints[1].elements[0].column.table.name
850+
qualified_table_name(fk_constraints[1].elements[0].column.table)
845851
]
846852
if isinstance(target, ModelClass):
847853
relationship = RelationshipAttribute(

src/sqlacodegen/utils.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,10 @@ def render_callable(
161161

162162
rendered_args = delimiter.join(str(arg) for arg in args)
163163
return f"{name}({prefix}{rendered_args}{suffix})"
164+
165+
166+
def qualified_table_name(table: Table) -> str:
167+
if table.schema:
168+
return f"{table.schema}.{table.name}"
169+
else:
170+
return str(table.name)

tests/test_generators.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1728,6 +1728,42 @@ class SimpleSubItems(SimpleItems):
17281728
""",
17291729
)
17301730

1731+
def test_joined_inheritance_same_table_name(self, generator: CodeGenerator) -> None:
1732+
Table(
1733+
"simple",
1734+
generator.metadata,
1735+
Column("id", INTEGER, primary_key=True),
1736+
)
1737+
Table(
1738+
"simple",
1739+
generator.metadata,
1740+
Column("id", INTEGER, ForeignKey("simple.id"), primary_key=True),
1741+
schema="altschema",
1742+
)
1743+
1744+
validate_code(
1745+
generator.generate(),
1746+
"""\
1747+
from sqlalchemy import Column, ForeignKey, Integer
1748+
from sqlalchemy.orm import declarative_base
1749+
1750+
Base = declarative_base()
1751+
1752+
1753+
class Simple(Base):
1754+
__tablename__ = 'simple'
1755+
1756+
id = Column(Integer, primary_key=True)
1757+
1758+
1759+
class Simple_(Simple):
1760+
__tablename__ = 'simple'
1761+
__table_args__ = {'schema': 'altschema'}
1762+
1763+
id = Column(ForeignKey('simple.id'), primary_key=True)
1764+
""",
1765+
)
1766+
17311767
@pytest.mark.parametrize("generator", [["use_inflect"]], indirect=True)
17321768
def test_use_inflect(self, generator: CodeGenerator) -> None:
17331769
Table(

0 commit comments

Comments
 (0)