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.
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/CHANGES.rst +24 -0
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/PKG-INFO +1 -1
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/src/sqlacodegen/generators.py +69 -59
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/src/sqlacodegen.egg-info/PKG-INFO +1 -1
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/tests/test_generator_declarative.py +87 -7
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/tests/test_generator_sqlmodel.py +99 -2
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/tests/test_generator_tables.py +80 -6
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/.github/FUNDING.yml +0 -0
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/.github/ISSUE_TEMPLATE/bug_report.yaml +0 -0
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/.github/ISSUE_TEMPLATE/features_request.yaml +0 -0
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/.github/dependabot.yml +0 -0
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/.github/pull_request_template.md +0 -0
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/.github/workflows/publish.yml +0 -0
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/.github/workflows/test.yml +0 -0
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/.gitignore +0 -0
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/.pre-commit-config.yaml +0 -0
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/CONTRIBUTING.rst +0 -0
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/LICENSE +0 -0
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/README.rst +0 -0
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/pyproject.toml +0 -0
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/setup.cfg +0 -0
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/src/sqlacodegen/__init__.py +0 -0
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/src/sqlacodegen/__main__.py +0 -0
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/src/sqlacodegen/cli.py +0 -0
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/src/sqlacodegen/models.py +0 -0
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/src/sqlacodegen/py.typed +0 -0
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/src/sqlacodegen/utils.py +0 -0
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/src/sqlacodegen.egg-info/SOURCES.txt +0 -0
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/src/sqlacodegen.egg-info/dependency_links.txt +0 -0
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/src/sqlacodegen.egg-info/entry_points.txt +0 -0
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/src/sqlacodegen.egg-info/requires.txt +0 -0
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/src/sqlacodegen.egg-info/top_level.txt +0 -0
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/tests/__init__.py +0 -0
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/tests/conftest.py +0 -0
- {sqlacodegen-4.0.0rc3 → sqlacodegen-4.0.1}/tests/test_cli.py +0 -0
- {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
|
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
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
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
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",
|
|
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
|
|
@@ -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}
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|