sqlacodegen 4.0.0rc3__tar.gz → 4.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/CHANGES.rst +24 -0
  2. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/PKG-INFO +1 -1
  3. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/src/sqlacodegen/generators.py +69 -59
  4. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/src/sqlacodegen.egg-info/PKG-INFO +1 -1
  5. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/tests/test_generator_declarative.py +87 -7
  6. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/tests/test_generator_sqlmodel.py +99 -2
  7. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/tests/test_generator_tables.py +80 -6
  8. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/.github/FUNDING.yml +0 -0
  9. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/.github/ISSUE_TEMPLATE/bug_report.yaml +0 -0
  10. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  11. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/.github/ISSUE_TEMPLATE/features_request.yaml +0 -0
  12. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/.github/dependabot.yml +0 -0
  13. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/.github/pull_request_template.md +0 -0
  14. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/.github/workflows/publish.yml +0 -0
  15. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/.github/workflows/test.yml +0 -0
  16. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/.gitignore +0 -0
  17. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/.pre-commit-config.yaml +0 -0
  18. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/CONTRIBUTING.rst +0 -0
  19. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/LICENSE +0 -0
  20. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/README.rst +0 -0
  21. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/pyproject.toml +0 -0
  22. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/setup.cfg +0 -0
  23. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/src/sqlacodegen/__init__.py +0 -0
  24. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/src/sqlacodegen/__main__.py +0 -0
  25. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/src/sqlacodegen/cli.py +0 -0
  26. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/src/sqlacodegen/models.py +0 -0
  27. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/src/sqlacodegen/py.typed +0 -0
  28. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/src/sqlacodegen/utils.py +0 -0
  29. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/src/sqlacodegen.egg-info/SOURCES.txt +0 -0
  30. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/src/sqlacodegen.egg-info/dependency_links.txt +0 -0
  31. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/src/sqlacodegen.egg-info/entry_points.txt +0 -0
  32. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/src/sqlacodegen.egg-info/requires.txt +0 -0
  33. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/src/sqlacodegen.egg-info/top_level.txt +0 -0
  34. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/tests/__init__.py +0 -0
  35. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/tests/conftest.py +0 -0
  36. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/tests/test_cli.py +0 -0
  37. {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/tests/test_generator_dataclass.py +0 -0
@@ -1,6 +1,28 @@
1
1
  Version history
2
2
  ===============
3
3
 
4
+ **4.0.1**
5
+
6
+ - Fix enum column definitions to explicitly include schema and name if reflected
7
+ via SQLAlchemy's Metadata (pr by @sheinbergon)
8
+
9
+ **4.0.0**
10
+
11
+ - **BACKWARD INCOMPATIBLE** API changes (for those who customize code generation by
12
+ subclassing the existing generators):
13
+
14
+ * Added new optional keyword argument, ``explicit_foreign_keys`` to
15
+ ``DeclarativeGenerator``, to force foreign keys to be rendered as
16
+ ``ClassName.attribute_name`` string references
17
+ * Removed the ``render_relationship_args()`` method from the SQLModel generator
18
+ * Added two new methods for customizing relationship rendering in
19
+ ``DeclarativeGenerator``:
20
+
21
+ * ``render_relationship_annotation()``: returns the appropriate type annotation
22
+ (without the ``Mapped`` wrapper) for the relationship
23
+ * ``render_relationship_arguments()``: returns a dictionary of keyword arguments to
24
+ ``sqlalchemy.orm.relationship()``
25
+
4
26
  **4.0.0rc3**
5
27
 
6
28
  - **BACKWARD INCOMPATIBLE** Relationship names changed when multiple FKs or junction tables
@@ -14,6 +36,8 @@ Version history
14
36
  ``students_enrollments``). Use ``--options nofknames`` to revert to old behavior. (PR by @sheinbergon)
15
37
  - Fixed ``Index`` kwargs (e.g. ``mysql_length``) being ignored during code generation
16
38
  (PR by @luliangce)
39
+ - Fixed the SQLModel generator not adding the ``foreign_keys`` parameters when
40
+ generating multiple relationships between the same two tables
17
41
 
18
42
  **4.0.0rc2**
19
43
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlacodegen
3
- Version: 4.0.0rc3
3
+ Version: 4.0.1
4
4
  Summary: Automatic model code generator for SQLAlchemy
5
5
  Author-email: Alex Grönholm <alex.gronholm@nextday.fi>
6
6
  Maintainer-email: Idan Sheinberg <ishinberg0@gmail.com>
@@ -5,7 +5,7 @@ import re
5
5
  import sys
6
6
  from abc import ABCMeta, abstractmethod
7
7
  from collections import defaultdict
8
- from collections.abc import Collection, Iterable, Sequence
8
+ from collections.abc import Collection, Iterable, Mapping, Sequence
9
9
  from dataclasses import dataclass
10
10
  from importlib import import_module
11
11
  from inspect import Parameter
@@ -550,7 +550,14 @@ class TablesGenerator(CodeGenerator):
550
550
  ):
551
551
  # Import SQLAlchemy Enum (will be handled in collect_imports)
552
552
  self.add_import(Enum)
553
- return f"Enum({enum_class_name}, values_callable=lambda cls: [member.value for member in cls])"
553
+ extra_kwargs = ""
554
+ if column_type.name is not None:
555
+ extra_kwargs += f", name={column_type.name!r}"
556
+
557
+ if column_type.schema is not None:
558
+ extra_kwargs += f", schema={column_type.schema!r}"
559
+
560
+ return f"Enum({enum_class_name}, values_callable=lambda cls: [member.value for member in cls]{extra_kwargs})"
554
561
 
555
562
  args = []
556
563
  kwargs: dict[str, Any] = {}
@@ -562,7 +569,14 @@ class TablesGenerator(CodeGenerator):
562
569
  ):
563
570
  self.add_import(ARRAY)
564
571
  self.add_import(Enum)
565
- rendered_enum = f"Enum({enum_class_name}, values_callable=lambda cls: [member.value for member in cls])"
572
+ extra_kwargs = ""
573
+ if column_type.item_type.name is not None:
574
+ extra_kwargs += f", name={column_type.item_type.name!r}"
575
+
576
+ if column_type.item_type.schema is not None:
577
+ extra_kwargs += f", schema={column_type.item_type.schema!r}"
578
+
579
+ rendered_enum = f"Enum({enum_class_name}, values_callable=lambda cls: [member.value for member in cls]{extra_kwargs})"
566
580
  if column_type.dimensions is not None:
567
581
  kwargs["dimensions"] = repr(column_type.dimensions)
568
582
 
@@ -1001,10 +1015,12 @@ class DeclarativeGenerator(TablesGenerator):
1001
1015
  *,
1002
1016
  indentation: str = " ",
1003
1017
  base_class_name: str = "Base",
1018
+ explicit_foreign_keys: bool = False,
1004
1019
  ):
1005
1020
  super().__init__(metadata, bind, options, indentation=indentation)
1006
1021
  self.base_class_name: str = base_class_name
1007
1022
  self.inflect_engine = inflect.engine()
1023
+ self.explicit_foreign_keys = explicit_foreign_keys
1008
1024
 
1009
1025
  def generate_base(self) -> None:
1010
1026
  self.base = Base(
@@ -1626,6 +1642,33 @@ class DeclarativeGenerator(TablesGenerator):
1626
1642
  return f"{column_attr.name}: Mapped[{rendered_column_python_type}] = {rendered_column}"
1627
1643
 
1628
1644
  def render_relationship(self, relationship: RelationshipAttribute) -> str:
1645
+ kwargs = self.render_relationship_arguments(relationship)
1646
+ annotation = self.render_relationship_annotation(relationship)
1647
+ rendered_relationship = render_callable(
1648
+ "relationship", repr(relationship.target.name), kwargs=kwargs
1649
+ )
1650
+ return f"{relationship.name}: Mapped[{annotation}] = {rendered_relationship}"
1651
+
1652
+ def render_relationship_annotation(
1653
+ self, relationship: RelationshipAttribute
1654
+ ) -> str:
1655
+ match relationship.type:
1656
+ case RelationshipType.ONE_TO_MANY:
1657
+ return f"list[{relationship.target.name!r}]"
1658
+ case RelationshipType.ONE_TO_ONE | RelationshipType.MANY_TO_ONE:
1659
+ if relationship.constraint and any(
1660
+ col.nullable for col in relationship.constraint.columns
1661
+ ):
1662
+ self.add_literal_import("typing", "Optional")
1663
+ return f"Optional[{relationship.target.name!r}]"
1664
+ else:
1665
+ return f"'{relationship.target.name}'"
1666
+ case RelationshipType.MANY_TO_MANY:
1667
+ return f"list[{relationship.target.name!r}]"
1668
+
1669
+ def render_relationship_arguments(
1670
+ self, relationship: RelationshipAttribute
1671
+ ) -> Mapping[str, Any]:
1629
1672
  def render_column_attrs(column_attrs: list[ColumnAttribute]) -> str:
1630
1673
  rendered = []
1631
1674
  for attr in column_attrs:
@@ -1641,7 +1684,7 @@ class DeclarativeGenerator(TablesGenerator):
1641
1684
  render_as_string = False
1642
1685
  # Assume that column_attrs are all in relationship.source or none
1643
1686
  for attr in column_attrs:
1644
- if attr.model is relationship.source:
1687
+ if not self.explicit_foreign_keys and attr.model is relationship.source:
1645
1688
  rendered.append(attr.name)
1646
1689
  else:
1647
1690
  rendered.append(f"{attr.model.name}.{attr.name}")
@@ -1697,33 +1740,7 @@ class DeclarativeGenerator(TablesGenerator):
1697
1740
  if relationship.backref:
1698
1741
  kwargs["back_populates"] = repr(relationship.backref.name)
1699
1742
 
1700
- rendered_relationship = render_callable(
1701
- "relationship", repr(relationship.target.name), kwargs=kwargs
1702
- )
1703
-
1704
- relationship_type: str
1705
- if relationship.type == RelationshipType.ONE_TO_MANY:
1706
- relationship_type = f"list['{relationship.target.name}']"
1707
- elif relationship.type in (
1708
- RelationshipType.ONE_TO_ONE,
1709
- RelationshipType.MANY_TO_ONE,
1710
- ):
1711
- relationship_type = f"'{relationship.target.name}'"
1712
- if relationship.constraint and any(
1713
- col.nullable for col in relationship.constraint.columns
1714
- ):
1715
- self.add_literal_import("typing", "Optional")
1716
- relationship_type = f"Optional[{relationship_type}]"
1717
- elif relationship.type == RelationshipType.MANY_TO_MANY:
1718
- relationship_type = f"list['{relationship.target.name}']"
1719
- else:
1720
- self.add_literal_import("typing", "Any")
1721
- relationship_type = "Any"
1722
-
1723
- return (
1724
- f"{relationship.name}: Mapped[{relationship_type}] "
1725
- f"= {rendered_relationship}"
1726
- )
1743
+ return kwargs
1727
1744
 
1728
1745
 
1729
1746
  class DataclassGenerator(DeclarativeGenerator):
@@ -1778,6 +1795,7 @@ class SQLModelGenerator(DeclarativeGenerator):
1778
1795
  options,
1779
1796
  indentation=indentation,
1780
1797
  base_class_name=base_class_name,
1798
+ explicit_foreign_keys=True,
1781
1799
  )
1782
1800
 
1783
1801
  @property
@@ -1858,34 +1876,26 @@ class SQLModelGenerator(DeclarativeGenerator):
1858
1876
  return f"{column_attr.name}: {rendered_column_python_type} = {rendered_field}"
1859
1877
 
1860
1878
  def render_relationship(self, relationship: RelationshipAttribute) -> str:
1861
- rendered = super().render_relationship(relationship).partition(" = ")[2]
1862
- args = self.render_relationship_args(rendered)
1863
- kwargs: dict[str, Any] = {}
1864
- annotation = repr(relationship.target.name)
1879
+ kwargs = self.render_relationship_arguments(relationship)
1880
+ annotation = self.render_relationship_annotation(relationship)
1881
+
1882
+ native_kwargs: dict[str, Any] = {}
1883
+ non_native_kwargs: dict[str, Any] = {}
1884
+ for key, value in kwargs.items():
1885
+ # The following keyword arguments are natively supported in Relationship
1886
+ if key in ("back_populates", "cascade_delete", "passive_deletes"):
1887
+ native_kwargs[key] = value
1888
+ else:
1889
+ non_native_kwargs[key] = value
1865
1890
 
1866
- if relationship.type in (
1867
- RelationshipType.ONE_TO_MANY,
1868
- RelationshipType.MANY_TO_MANY,
1869
- ):
1870
- annotation = f"list[{annotation}]"
1871
- else:
1872
- self.add_literal_import("typing", "Optional")
1873
- annotation = f"Optional[{annotation}]"
1891
+ if non_native_kwargs:
1892
+ native_kwargs["sa_relationship_kwargs"] = (
1893
+ "{"
1894
+ + ", ".join(
1895
+ f"{key!r}: {value}" for key, value in non_native_kwargs.items()
1896
+ )
1897
+ + "}"
1898
+ )
1874
1899
 
1875
- rendered_field = render_callable("Relationship", *args, kwargs=kwargs)
1900
+ rendered_field = render_callable("Relationship", kwargs=native_kwargs)
1876
1901
  return f"{relationship.name}: {annotation} = {rendered_field}"
1877
-
1878
- def render_relationship_args(self, arguments: str) -> list[str]:
1879
- argument_list = arguments.split(",")
1880
- # delete ')' and ' ' from args
1881
- argument_list[-1] = argument_list[-1][:-1]
1882
- argument_list = [argument[1:] for argument in argument_list]
1883
-
1884
- rendered_args: list[str] = []
1885
- for arg in argument_list:
1886
- if "back_populates" in arg:
1887
- rendered_args.append(arg)
1888
- if "uselist=False" in arg:
1889
- rendered_args.append("sa_relationship_kwargs={'uselist': False}")
1890
-
1891
- return rendered_args
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlacodegen
3
- Version: 4.0.0rc3
3
+ Version: 4.0.1
4
4
  Summary: Automatic model code generator for SQLAlchemy
5
5
  Author-email: Alex Grönholm <alex.gronholm@nextday.fi>
6
6
  Maintainer-email: Idan Sheinberg <ishinberg0@gmail.com>
@@ -2508,14 +2508,14 @@ def test_enum_shared_values(generator: CodeGenerator) -> None:
2508
2508
  __tablename__ = 'accounts'
2509
2509
 
2510
2510
  id: Mapped[int] = mapped_column(Integer, primary_key=True)
2511
- status: Mapped[StatusEnum] = mapped_column(Enum(StatusEnum, values_callable=lambda cls: [member.value for member in cls]), nullable=False)
2511
+ status: Mapped[StatusEnum] = mapped_column(Enum(StatusEnum, values_callable=lambda cls: [member.value for member in cls], name='status_enum'), nullable=False)
2512
2512
 
2513
2513
 
2514
2514
  class Users(Base):
2515
2515
  __tablename__ = 'users'
2516
2516
 
2517
2517
  id: Mapped[int] = mapped_column(Integer, primary_key=True)
2518
- status: Mapped[StatusEnum] = mapped_column(Enum(StatusEnum, values_callable=lambda cls: [member.value for member in cls]), nullable=False)
2518
+ status: Mapped[StatusEnum] = mapped_column(Enum(StatusEnum, values_callable=lambda cls: [member.value for member in cls], name='status_enum'), nullable=False)
2519
2519
  """,
2520
2520
  )
2521
2521
 
@@ -2851,7 +2851,7 @@ def test_array_enum_named(generator: CodeGenerator) -> None:
2851
2851
  __tablename__ = 'users'
2852
2852
 
2853
2853
  id: Mapped[int] = mapped_column(Integer, primary_key=True)
2854
- roles: Mapped[list[RoleEnum]] = mapped_column(ARRAY(Enum(RoleEnum, values_callable=lambda cls: [member.value for member in cls])), nullable=False)
2854
+ roles: Mapped[list[RoleEnum]] = mapped_column(ARRAY(Enum(RoleEnum, values_callable=lambda cls: [member.value for member in cls], name='role_enum')), nullable=False)
2855
2855
  """,
2856
2856
  )
2857
2857
 
@@ -2927,7 +2927,7 @@ def test_array_enum_nullable(generator: CodeGenerator) -> None:
2927
2927
  __tablename__ = 'users'
2928
2928
 
2929
2929
  id: Mapped[int] = mapped_column(Integer, primary_key=True)
2930
- roles: Mapped[Optional[list[RoleEnum]]] = mapped_column(ARRAY(Enum(RoleEnum, values_callable=lambda cls: [member.value for member in cls])))
2930
+ roles: Mapped[Optional[list[RoleEnum]]] = mapped_column(ARRAY(Enum(RoleEnum, values_callable=lambda cls: [member.value for member in cls], name='role_enum')))
2931
2931
  """,
2932
2932
  )
2933
2933
 
@@ -2965,7 +2965,7 @@ def test_array_enum_with_dimensions(generator: CodeGenerator) -> None:
2965
2965
  __tablename__ = 'items'
2966
2966
 
2967
2967
  id: Mapped[int] = mapped_column(Integer, primary_key=True)
2968
- tag_matrix: Mapped[list[list[TagEnum]]] = mapped_column(ARRAY(Enum(TagEnum, values_callable=lambda cls: [member.value for member in cls]), dimensions=2), nullable=False)
2968
+ tag_matrix: Mapped[list[list[TagEnum]]] = mapped_column(ARRAY(Enum(TagEnum, values_callable=lambda cls: [member.value for member in cls], name='tag_enum'), dimensions=2), nullable=False)
2969
2969
  """,
2970
2970
  )
2971
2971
 
@@ -3043,7 +3043,87 @@ def test_array_enum_shared_with_regular_enum(generator: CodeGenerator) -> None:
3043
3043
  __tablename__ = 'users'
3044
3044
 
3045
3045
  id: Mapped[int] = mapped_column(Integer, primary_key=True)
3046
- primary_role: Mapped[RoleEnum] = mapped_column(Enum(RoleEnum, values_callable=lambda cls: [member.value for member in cls]), nullable=False)
3047
- all_roles: Mapped[list[RoleEnum]] = mapped_column(ARRAY(Enum(RoleEnum, values_callable=lambda cls: [member.value for member in cls])), nullable=False)
3046
+ primary_role: Mapped[RoleEnum] = mapped_column(Enum(RoleEnum, values_callable=lambda cls: [member.value for member in cls], name='role_enum'), nullable=False)
3047
+ all_roles: Mapped[list[RoleEnum]] = mapped_column(ARRAY(Enum(RoleEnum, values_callable=lambda cls: [member.value for member in cls], name='role_enum')), nullable=False)
3048
+ """,
3049
+ )
3050
+
3051
+
3052
+ def test_enum_named_with_schema(generator: CodeGenerator) -> None:
3053
+ Table(
3054
+ "my_table",
3055
+ generator.metadata,
3056
+ Column("id", INTEGER, primary_key=True),
3057
+ Column(
3058
+ "status",
3059
+ SAEnum("active", "inactive", name="status_enum", schema="custom_schema"),
3060
+ nullable=False,
3061
+ ),
3062
+ schema="custom_schema",
3063
+ )
3064
+
3065
+ validate_code(
3066
+ generator.generate(),
3067
+ """\
3068
+ import enum
3069
+
3070
+ from sqlalchemy import Enum, Integer
3071
+ from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
3072
+
3073
+ class Base(DeclarativeBase):
3074
+ pass
3075
+
3076
+
3077
+ class StatusEnum(str, enum.Enum):
3078
+ ACTIVE = 'active'
3079
+ INACTIVE = 'inactive'
3080
+
3081
+
3082
+ class MyTable(Base):
3083
+ __tablename__ = 'my_table'
3084
+ __table_args__ = {'schema': 'custom_schema'}
3085
+
3086
+ id: Mapped[int] = mapped_column(Integer, primary_key=True)
3087
+ status: Mapped[StatusEnum] = mapped_column(Enum(StatusEnum, values_callable=lambda cls: [member.value for member in cls], name='status_enum', schema='custom_schema'), nullable=False)
3088
+ """,
3089
+ )
3090
+
3091
+
3092
+ def test_array_enum_named_with_schema(generator: CodeGenerator) -> None:
3093
+ Table(
3094
+ "my_table",
3095
+ generator.metadata,
3096
+ Column("id", INTEGER, primary_key=True),
3097
+ Column(
3098
+ "tags",
3099
+ ARRAY(SAEnum("a", "b", name="tag_enum", schema="custom_schema")),
3100
+ nullable=False,
3101
+ ),
3102
+ schema="custom_schema",
3103
+ )
3104
+
3105
+ validate_code(
3106
+ generator.generate(),
3107
+ """\
3108
+ import enum
3109
+
3110
+ from sqlalchemy import ARRAY, Enum, Integer
3111
+ from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
3112
+
3113
+ class Base(DeclarativeBase):
3114
+ pass
3115
+
3116
+
3117
+ class TagEnum(str, enum.Enum):
3118
+ A = 'a'
3119
+ B = 'b'
3120
+
3121
+
3122
+ class MyTable(Base):
3123
+ __tablename__ = 'my_table'
3124
+ __table_args__ = {'schema': 'custom_schema'}
3125
+
3126
+ id: Mapped[int] = mapped_column(Integer, primary_key=True)
3127
+ tags: Mapped[list[TagEnum]] = mapped_column(ARRAY(Enum(TagEnum, values_callable=lambda cls: [member.value for member in cls], name='tag_enum', schema='custom_schema')), nullable=False)
3048
3128
  """,
3049
3129
  )
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import pytest
4
4
  from _pytest.fixtures import FixtureRequest
5
+ from sqlalchemy import Enum as SAEnum
5
6
  from sqlalchemy import Uuid
6
7
  from sqlalchemy.engine import Engine
7
8
  from sqlalchemy.schema import (
@@ -13,7 +14,7 @@ from sqlalchemy.schema import (
13
14
  Table,
14
15
  UniqueConstraint,
15
16
  )
16
- from sqlalchemy.types import INTEGER, VARCHAR
17
+ from sqlalchemy.types import ARRAY, INTEGER, VARCHAR
17
18
 
18
19
  from sqlacodegen.generators import CodeGenerator, SQLModelGenerator
19
20
 
@@ -142,6 +143,66 @@ back_populates='simple_goods')
142
143
  )
143
144
 
144
145
 
146
+ def test_onetomany_multiref(generator: CodeGenerator) -> None:
147
+ Table(
148
+ "simple_items_multiref",
149
+ generator.metadata,
150
+ Column("id", INTEGER, primary_key=True),
151
+ Column("parent_container_id", INTEGER),
152
+ Column("top_container_id", INTEGER, nullable=False),
153
+ ForeignKeyConstraint(
154
+ ["parent_container_id"], ["simple_containers_multiref.id"]
155
+ ),
156
+ ForeignKeyConstraint(["top_container_id"], ["simple_containers_multiref.id"]),
157
+ )
158
+ Table(
159
+ "simple_containers_multiref",
160
+ generator.metadata,
161
+ Column("id", INTEGER, primary_key=True),
162
+ )
163
+
164
+ validate_code(
165
+ generator.generate(),
166
+ """\
167
+ from typing import Optional
168
+
169
+ from sqlalchemy import Column, ForeignKey, Integer
170
+ from sqlmodel import Field, Relationship, SQLModel
171
+
172
+ class SimpleContainersMultiref(SQLModel, table=True):
173
+ __tablename__ = 'simple_containers_multiref'
174
+
175
+ id: int = Field(sa_column=Column('id', Integer, primary_key=True))
176
+
177
+ simple_items_multiref_parent_container: list['SimpleItemsMultiref'] = \
178
+ Relationship(back_populates='parent_container', sa_relationship_kwargs={\
179
+ 'foreign_keys': '[SimpleItemsMultiref.parent_container_id]'})
180
+ simple_items_multiref_top_container: list['SimpleItemsMultiref'] = \
181
+ Relationship(back_populates='top_container', sa_relationship_kwargs={'foreign_keys': \
182
+ '[SimpleItemsMultiref.top_container_id]'})
183
+
184
+
185
+ class SimpleItemsMultiref(SQLModel, table=True):
186
+ __tablename__ = 'simple_items_multiref'
187
+
188
+ id: int = Field(sa_column=Column('id', Integer, primary_key=True))
189
+ top_container_id: int = \
190
+ Field(sa_column=Column('top_container_id', \
191
+ ForeignKey('simple_containers_multiref.id'), nullable=False))
192
+ parent_container_id: Optional[int] = \
193
+ Field(default=None, sa_column=Column('parent_container_id', \
194
+ ForeignKey('simple_containers_multiref.id')))
195
+
196
+ parent_container: Optional['SimpleContainersMultiref'] = Relationship(\
197
+ back_populates='simple_items_multiref_parent_container', sa_relationship_kwargs={\
198
+ 'foreign_keys': '[SimpleItemsMultiref.parent_container_id]'})
199
+ top_container: 'SimpleContainersMultiref' = Relationship(\
200
+ back_populates='simple_items_multiref_top_container', sa_relationship_kwargs={\
201
+ 'foreign_keys': '[SimpleItemsMultiref.top_container_id]'})
202
+ """,
203
+ )
204
+
205
+
145
206
  def test_onetoone(generator: CodeGenerator) -> None:
146
207
  Table(
147
208
  "simple_onetoone",
@@ -167,7 +228,7 @@ def test_onetoone(generator: CodeGenerator) -> None:
167
228
  id: int = Field(sa_column=Column('id', Integer, primary_key=True))
168
229
 
169
230
  simple_onetoone: Optional['SimpleOnetoone'] = Relationship(\
170
- sa_relationship_kwargs={'uselist': False}, back_populates='other_item')
231
+ back_populates='other_item', sa_relationship_kwargs={'uselist': False})
171
232
 
172
233
 
173
234
  class SimpleOnetoone(SQLModel, table=True):
@@ -269,3 +330,39 @@ def test_synthetic_enum_generation(generator: CodeGenerator) -> None:
269
330
  status: AccountsStatus = Field(sa_column=Column('status', Enum(AccountsStatus, values_callable=lambda cls: [member.value for member in cls]), nullable=False))
270
331
  """,
271
332
  )
333
+
334
+
335
+ def test_array_enum_named_with_schema(generator: CodeGenerator) -> None:
336
+ Table(
337
+ "my_table",
338
+ generator.metadata,
339
+ Column("id", INTEGER, primary_key=True),
340
+ Column(
341
+ "tags",
342
+ ARRAY(SAEnum("a", "b", name="tag_enum", schema="custom_schema")),
343
+ nullable=False,
344
+ ),
345
+ schema="custom_schema",
346
+ )
347
+
348
+ validate_code(
349
+ generator.generate(),
350
+ """\
351
+ import enum
352
+
353
+ from sqlalchemy import ARRAY, Column, Enum, Integer
354
+ from sqlmodel import Field, SQLModel
355
+
356
+ class TagEnum(str, enum.Enum):
357
+ A = 'a'
358
+ B = 'b'
359
+
360
+
361
+ class MyTable(SQLModel, table=True):
362
+ __tablename__ = 'my_table'
363
+ __table_args__ = {'schema': 'custom_schema'}
364
+
365
+ id: int = Field(sa_column=Column('id', Integer, primary_key=True))
366
+ tags: list[TagEnum] = Field(sa_column=Column('tags', ARRAY(Enum(TagEnum, values_callable=lambda cls: [member.value for member in cls], name='tag_enum', schema='custom_schema')), nullable=False))
367
+ """,
368
+ )
@@ -76,7 +76,7 @@ def test_fancy_coltypes(generator: CodeGenerator) -> None:
76
76
 
77
77
  t_simple_items = Table(
78
78
  'simple_items', metadata,
79
- Column('enum', Enum(Blah, values_callable=lambda cls: [member.value for member in cls])),
79
+ Column('enum', Enum(Blah, values_callable=lambda cls: [member.value for member in cls], name='blah', schema='someschema')),
80
80
  Column('bool', Boolean),
81
81
  Column('vector', VECTOR(3)),
82
82
  Column('number', Numeric(10, asdecimal=False)),
@@ -309,13 +309,13 @@ def test_enum_shared_values(generator: CodeGenerator) -> None:
309
309
  t_accounts = Table(
310
310
  'accounts', metadata,
311
311
  Column('id', Integer, primary_key=True),
312
- Column('status', Enum(StatusEnum, values_callable=lambda cls: [member.value for member in cls]))
312
+ Column('status', Enum(StatusEnum, values_callable=lambda cls: [member.value for member in cls], name='status_enum'))
313
313
  )
314
314
 
315
315
  t_users = Table(
316
316
  'users', metadata,
317
317
  Column('id', Integer, primary_key=True),
318
- Column('status', Enum(StatusEnum, values_callable=lambda cls: [member.value for member in cls]))
318
+ Column('status', Enum(StatusEnum, values_callable=lambda cls: [member.value for member in cls], name='status_enum'))
319
319
  )
320
320
  """,
321
321
  )
@@ -348,7 +348,7 @@ def test_array_enum_named(generator: CodeGenerator) -> None:
348
348
  t_users = Table(
349
349
  'users', metadata,
350
350
  Column('id', Integer, primary_key=True),
351
- Column('roles', ARRAY(Enum(RoleEnum, values_callable=lambda cls: [member.value for member in cls])))
351
+ Column('roles', ARRAY(Enum(RoleEnum, values_callable=lambda cls: [member.value for member in cls], name='role_enum')))
352
352
  )
353
353
  """,
354
354
  )
@@ -386,13 +386,87 @@ def test_array_enum_shared(generator: CodeGenerator) -> None:
386
386
  t_groups = Table(
387
387
  'groups', metadata,
388
388
  Column('id', Integer, primary_key=True),
389
- Column('allowed_roles', ARRAY(Enum(RoleEnum, values_callable=lambda cls: [member.value for member in cls])))
389
+ Column('allowed_roles', ARRAY(Enum(RoleEnum, values_callable=lambda cls: [member.value for member in cls], name='role_enum')))
390
390
  )
391
391
 
392
392
  t_users = Table(
393
393
  'users', metadata,
394
394
  Column('id', Integer, primary_key=True),
395
- Column('roles', ARRAY(Enum(RoleEnum, values_callable=lambda cls: [member.value for member in cls])))
395
+ Column('roles', ARRAY(Enum(RoleEnum, values_callable=lambda cls: [member.value for member in cls], name='role_enum')))
396
+ )
397
+ """,
398
+ )
399
+
400
+
401
+ def test_enum_named_with_schema(generator: CodeGenerator) -> None:
402
+ Table(
403
+ "my_table",
404
+ generator.metadata,
405
+ Column("id", INTEGER, primary_key=True),
406
+ Column(
407
+ "status",
408
+ SAEnum("active", "inactive", name="status_enum", schema="custom_schema"),
409
+ ),
410
+ schema="custom_schema",
411
+ )
412
+
413
+ validate_code(
414
+ generator.generate(),
415
+ """\
416
+ import enum
417
+
418
+ from sqlalchemy import Column, Enum, Integer, MetaData, Table
419
+
420
+ metadata = MetaData()
421
+
422
+
423
+ class StatusEnum(str, enum.Enum):
424
+ ACTIVE = 'active'
425
+ INACTIVE = 'inactive'
426
+
427
+
428
+ t_my_table = Table(
429
+ 'my_table', metadata,
430
+ Column('id', Integer, primary_key=True),
431
+ Column('status', Enum(StatusEnum, values_callable=lambda cls: [member.value for member in cls], name='status_enum', schema='custom_schema')),
432
+ schema='custom_schema'
433
+ )
434
+ """,
435
+ )
436
+
437
+
438
+ def test_array_enum_named_with_schema(generator: CodeGenerator) -> None:
439
+ Table(
440
+ "my_table",
441
+ generator.metadata,
442
+ Column("id", INTEGER, primary_key=True),
443
+ Column(
444
+ "tags",
445
+ ARRAY(SAEnum("a", "b", name="tag_enum", schema="custom_schema")),
446
+ ),
447
+ schema="custom_schema",
448
+ )
449
+
450
+ validate_code(
451
+ generator.generate(),
452
+ """\
453
+ import enum
454
+
455
+ from sqlalchemy import ARRAY, Column, Enum, Integer, MetaData, Table
456
+
457
+ metadata = MetaData()
458
+
459
+
460
+ class TagEnum(str, enum.Enum):
461
+ A = 'a'
462
+ B = 'b'
463
+
464
+
465
+ t_my_table = Table(
466
+ 'my_table', metadata,
467
+ Column('id', Integer, primary_key=True),
468
+ Column('tags', ARRAY(Enum(TagEnum, values_callable=lambda cls: [member.value for member in cls], name='tag_enum', schema='custom_schema'))),
469
+ schema='custom_schema'
396
470
  )
397
471
  """,
398
472
  )
File without changes
File without changes
File without changes
File without changes