sqlacodegen 3.1.1__tar.gz → 3.2.0__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 (35) hide show
  1. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/.github/workflows/test.yml +1 -1
  2. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/.pre-commit-config.yaml +5 -2
  3. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/CHANGES.rst +11 -0
  4. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/PKG-INFO +8 -6
  5. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/README.rst +4 -0
  6. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/pyproject.toml +4 -6
  7. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/src/sqlacodegen/cli.py +1 -5
  8. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/src/sqlacodegen/generators.py +93 -7
  9. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/src/sqlacodegen/models.py +2 -2
  10. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/src/sqlacodegen.egg-info/PKG-INFO +8 -6
  11. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/src/sqlacodegen.egg-info/requires.txt +1 -5
  12. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/tests/test_generator_declarative.py +230 -0
  13. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/tests/test_generator_tables.py +84 -1
  14. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/.github/ISSUE_TEMPLATE/bug_report.yaml +0 -0
  15. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  16. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/.github/ISSUE_TEMPLATE/features_request.yaml +0 -0
  17. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/.github/pull_request_template.md +0 -0
  18. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/.github/workflows/publish.yml +0 -0
  19. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/.gitignore +0 -0
  20. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/CONTRIBUTING.rst +0 -0
  21. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/LICENSE +0 -0
  22. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/setup.cfg +0 -0
  23. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/src/sqlacodegen/__init__.py +0 -0
  24. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/src/sqlacodegen/__main__.py +0 -0
  25. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/src/sqlacodegen/py.typed +0 -0
  26. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/src/sqlacodegen/utils.py +0 -0
  27. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/src/sqlacodegen.egg-info/SOURCES.txt +0 -0
  28. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/src/sqlacodegen.egg-info/dependency_links.txt +0 -0
  29. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/src/sqlacodegen.egg-info/entry_points.txt +0 -0
  30. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/src/sqlacodegen.egg-info/top_level.txt +0 -0
  31. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/tests/__init__.py +0 -0
  32. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/tests/conftest.py +0 -0
  33. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/tests/test_cli.py +0 -0
  34. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/tests/test_generator_dataclass.py +0 -0
  35. {sqlacodegen-3.1.1 → sqlacodegen-3.2.0}/tests/test_generator_sqlmodel.py +0 -0
@@ -10,7 +10,7 @@ jobs:
10
10
  strategy:
11
11
  fail-fast: false
12
12
  matrix:
13
- python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
13
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
14
14
  runs-on: ubuntu-latest
15
15
  steps:
16
16
  - uses: actions/checkout@v4
@@ -16,14 +16,14 @@ repos:
16
16
  - id: trailing-whitespace
17
17
 
18
18
  - repo: https://github.com/astral-sh/ruff-pre-commit
19
- rev: v0.12.11
19
+ rev: v0.13.3
20
20
  hooks:
21
21
  - id: ruff
22
22
  args: [--fix, --show-fixes]
23
23
  - id: ruff-format
24
24
 
25
25
  - repo: https://github.com/pre-commit/mirrors-mypy
26
- rev: v1.17.1
26
+ rev: v1.18.2
27
27
  hooks:
28
28
  - id: mypy
29
29
  additional_dependencies:
@@ -36,3 +36,6 @@ repos:
36
36
  - id: rst-backticks
37
37
  - id: rst-directive-colons
38
38
  - id: rst-inline-touching-normal
39
+
40
+ ci:
41
+ autoupdate_schedule: quarterly
@@ -1,6 +1,17 @@
1
1
  Version history
2
2
  ===============
3
3
 
4
+ **3.2.0**
5
+
6
+ - Dropped support for Python 3.9
7
+ - Fix Postgres ``DOMAIN`` adaptation regression introduced in SQLAlchemy 2.0.42 (PR by @sheinbergon)
8
+ - Support disabling special naming logic for single column many-to-one and one-to-one relationships
9
+ (PR by @Henkhogan, revised by @sheinbergon)
10
+ - Add ``include_dialect_options`` option to render ``Table`` and ``Column``
11
+ dialect-specific kwargs and ``info`` in generated code. (PR by @jaogoy)
12
+ - Add ``keep_dialect_types`` option to preserve dialect-specific column types instead of
13
+ adapting to generic SQLAlchemy types. (PR by @jaogoy)
14
+
4
15
  **3.1.1**
5
16
 
6
17
  - Fallback ``NotImplemented`` errors encountered when accessing ``python_type`` for
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlacodegen
3
- Version: 3.1.1
3
+ Version: 3.2.0
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>
@@ -15,18 +15,16 @@ Classifier: Topic :: Database
15
15
  Classifier: Topic :: Software Development :: Code Generators
16
16
  Classifier: Programming Language :: Python
17
17
  Classifier: Programming Language :: Python :: 3
18
- Classifier: Programming Language :: Python :: 3.9
19
18
  Classifier: Programming Language :: Python :: 3.10
20
19
  Classifier: Programming Language :: Python :: 3.11
21
20
  Classifier: Programming Language :: Python :: 3.12
22
21
  Classifier: Programming Language :: Python :: 3.13
23
- Requires-Python: >=3.9
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Requires-Python: >=3.10
24
24
  Description-Content-Type: text/x-rst
25
25
  License-File: LICENSE
26
- Requires-Dist: SQLAlchemy<2.0.42,>=2.0.29
26
+ Requires-Dist: SQLAlchemy>=2.0.29
27
27
  Requires-Dist: inflect>=4.0.0
28
- Requires-Dist: importlib_metadata; python_version < "3.10"
29
- Requires-Dist: stdlib-list; python_version < "3.10"
30
28
  Provides-Extra: test
31
29
  Requires-Dist: sqlacodegen[geoalchemy2,pgvector,sqlmodel]; extra == "test"
32
30
  Requires-Dist: pytest>=7.4; extra == "test"
@@ -148,6 +146,10 @@ values must be delimited by commas, e.g. ``--options noconstraints,nobidi``):
148
146
  * ``noconstraints``: ignore constraints (foreign key, unique etc.)
149
147
  * ``nocomments``: ignore table/column comments
150
148
  * ``noindexes``: ignore indexes
149
+ * ``noidsuffix``: prevent the special naming logic for single column many-to-one
150
+ and one-to-one relationships (see `Relationship naming logic`_ for details)
151
+ * ``include_dialect_options``: render a table' dialect options, such as ``starrocks_partition`` for StarRocks' specific options.
152
+ * ``keep_dialect_types``: preserve dialect-specific column types instead of adapting to generic SQLAlchemy types.
151
153
 
152
154
  * ``declarative``
153
155
 
@@ -103,6 +103,10 @@ values must be delimited by commas, e.g. ``--options noconstraints,nobidi``):
103
103
  * ``noconstraints``: ignore constraints (foreign key, unique etc.)
104
104
  * ``nocomments``: ignore table/column comments
105
105
  * ``noindexes``: ignore indexes
106
+ * ``noidsuffix``: prevent the special naming logic for single column many-to-one
107
+ and one-to-one relationships (see `Relationship naming logic`_ for details)
108
+ * ``include_dialect_options``: render a table' dialect options, such as ``starrocks_partition`` for StarRocks' specific options.
109
+ * ``keep_dialect_types``: preserve dialect-specific column types instead of adapting to generic SQLAlchemy types.
106
110
 
107
111
  * ``declarative``
108
112
 
@@ -21,18 +21,16 @@ classifiers = [
21
21
  "Topic :: Software Development :: Code Generators",
22
22
  "Programming Language :: Python",
23
23
  "Programming Language :: Python :: 3",
24
- "Programming Language :: Python :: 3.9",
25
24
  "Programming Language :: Python :: 3.10",
26
25
  "Programming Language :: Python :: 3.11",
27
26
  "Programming Language :: Python :: 3.12",
28
27
  "Programming Language :: Python :: 3.13",
28
+ "Programming Language :: Python :: 3.14",
29
29
  ]
30
- requires-python = ">=3.9"
30
+ requires-python = ">=3.10"
31
31
  dependencies = [
32
- "SQLAlchemy >= 2.0.29,<2.0.42",
32
+ "SQLAlchemy >= 2.0.29",
33
33
  "inflect >= 4.0.0",
34
- "importlib_metadata; python_version < '3.10'",
35
- "stdlib-list; python_version < '3.10'"
36
34
  ]
37
35
  dynamic = ["version"]
38
36
 
@@ -95,7 +93,7 @@ relative_files = true
95
93
  show_missing = true
96
94
 
97
95
  [tool.tox]
98
- env_list = ["py39", "py310", "py311", "py312", "py313"]
96
+ env_list = ["py310", "py311", "py312", "py313", "py314"]
99
97
  skip_missing_interpreters = true
100
98
 
101
99
  [tool.tox.env_run_base]
@@ -4,6 +4,7 @@ import argparse
4
4
  import ast
5
5
  import sys
6
6
  from contextlib import ExitStack
7
+ from importlib.metadata import entry_points, version
7
8
  from typing import Any, TextIO
8
9
 
9
10
  from sqlalchemy.engine import create_engine
@@ -24,11 +25,6 @@ try:
24
25
  except ImportError:
25
26
  pgvector = None
26
27
 
27
- if sys.version_info < (3, 10):
28
- from importlib_metadata import entry_points, version
29
- else:
30
- from importlib.metadata import entry_points, version
31
-
32
28
 
33
29
  def _parse_engine_arg(arg_str: str) -> tuple[str, Any]:
34
30
  if "=" not in arg_str:
@@ -119,7 +119,13 @@ class CodeGenerator(metaclass=ABCMeta):
119
119
 
120
120
  @dataclass(eq=False)
121
121
  class TablesGenerator(CodeGenerator):
122
- valid_options: ClassVar[set[str]] = {"noindexes", "noconstraints", "nocomments"}
122
+ valid_options: ClassVar[set[str]] = {
123
+ "noindexes",
124
+ "noconstraints",
125
+ "nocomments",
126
+ "include_dialect_options",
127
+ "keep_dialect_types",
128
+ }
123
129
  stdlib_module_names: ClassVar[set[str]] = get_stdlib_module_names()
124
130
 
125
131
  def __init__(
@@ -135,6 +141,13 @@ class TablesGenerator(CodeGenerator):
135
141
  self.imports: dict[str, set[str]] = defaultdict(set)
136
142
  self.module_imports: set[str] = set()
137
143
 
144
+ # Render SchemaItem.info and dialect kwargs (Table/Column) into output
145
+ self.include_dialect_options_and_info: bool = (
146
+ "include_dialect_options" in self.options
147
+ )
148
+ # Keep dialect-specific types instead of adapting to generic SQLAlchemy types
149
+ self.keep_dialect_types: bool = "keep_dialect_types" in self.options
150
+
138
151
  @property
139
152
  def views_supported(self) -> bool:
140
153
  return True
@@ -393,6 +406,10 @@ class TablesGenerator(CodeGenerator):
393
406
  if table_comment:
394
407
  kwargs["comment"] = repr(table.comment)
395
408
 
409
+ # add info + dialect kwargs for callable context (opt-in)
410
+ if self.include_dialect_options_and_info:
411
+ self._add_dialect_kwargs_and_info(table, kwargs, values_for_dict=False)
412
+
396
413
  return render_callable("Table", *args, kwargs=kwargs, indentation=" ")
397
414
 
398
415
  def render_index(self, index: Index) -> str:
@@ -498,6 +515,10 @@ class TablesGenerator(CodeGenerator):
498
515
  if comment:
499
516
  kwargs["comment"] = repr(comment)
500
517
 
518
+ # add column info + dialect kwargs for callable context (opt-in)
519
+ if self.include_dialect_options_and_info:
520
+ self._add_dialect_kwargs_and_info(column, kwargs, values_for_dict=False)
521
+
501
522
  return self.render_column_callable(is_table, *args, **kwargs)
502
523
 
503
524
  def render_column_callable(self, is_table: bool, *args: Any, **kwargs: Any) -> str:
@@ -615,6 +636,51 @@ class TablesGenerator(CodeGenerator):
615
636
 
616
637
  return render_callable(constraint.__class__.__name__, *args, kwargs=kwargs)
617
638
 
639
+ def _add_dialect_kwargs_and_info(
640
+ self, obj: Any, target_kwargs: dict[str, object], *, values_for_dict: bool
641
+ ) -> None:
642
+ """
643
+ Merge SchemaItem-like object's .info and .dialect_kwargs into target_kwargs.
644
+ - values_for_dict=True: keep raw values so pretty-printer emits repr() (for __table_args__ dict)
645
+ - values_for_dict=False: set values to repr() strings (for callable kwargs)
646
+ """
647
+ info_dict = getattr(obj, "info", None)
648
+ if info_dict:
649
+ target_kwargs["info"] = info_dict if values_for_dict else repr(info_dict)
650
+
651
+ dialect_keys: list[str]
652
+ try:
653
+ dialect_keys = sorted(getattr(obj, "dialect_kwargs"))
654
+ except Exception:
655
+ return
656
+
657
+ dialect_kwargs = getattr(obj, "dialect_kwargs", {})
658
+ for key in dialect_keys:
659
+ try:
660
+ value = dialect_kwargs[key]
661
+ except Exception:
662
+ continue
663
+
664
+ # Render values:
665
+ # - callable context (values_for_dict=False): produce a string expression.
666
+ # primitives use repr(value); custom objects stringify then repr().
667
+ # - dict context (values_for_dict=True): pass raw primitives / str;
668
+ # custom objects become str(value) so pformat quotes them.
669
+ if values_for_dict:
670
+ if isinstance(value, type(None) | bool | int | float):
671
+ target_kwargs[key] = value
672
+ elif isinstance(value, str | dict | list):
673
+ target_kwargs[key] = value
674
+ else:
675
+ target_kwargs[key] = str(value)
676
+ else:
677
+ if isinstance(
678
+ value, type(None) | bool | int | float | str | dict | list
679
+ ):
680
+ target_kwargs[key] = repr(value)
681
+ else:
682
+ target_kwargs[key] = repr(str(value))
683
+
618
684
  def should_ignore_table(self, table: Table) -> bool:
619
685
  # Support for Alembic and sqlalchemy-migrate -- never expose the schema version
620
686
  # tables
@@ -680,10 +746,11 @@ class TablesGenerator(CodeGenerator):
680
746
  continue
681
747
 
682
748
  for column in table.c:
683
- try:
684
- column.type = self.get_adapted_type(column.type)
685
- except CompileError:
686
- pass
749
+ if not self.keep_dialect_types:
750
+ try:
751
+ column.type = self.get_adapted_type(column.type)
752
+ except CompileError:
753
+ continue
687
754
 
688
755
  # PostgreSQL specific fix: detect sequences from server_default
689
756
  if column.server_default and self.bind.dialect.name == "postgresql":
@@ -718,6 +785,20 @@ class TablesGenerator(CodeGenerator):
718
785
  if coltype.schema:
719
786
  kw["schema"] = coltype.schema
720
787
 
788
+ # Hack to fix Postgres DOMAIN type adaptation, broken as of SQLAlchemy 2.0.42
789
+ # For additional information - https://github.com/agronholm/sqlacodegen/issues/416#issuecomment-3417480599
790
+ if supercls is DOMAIN:
791
+ if coltype.default:
792
+ kw["default"] = coltype.default
793
+ if coltype.constraint_name is not None:
794
+ kw["constraint_name"] = coltype.constraint_name
795
+ if coltype.not_null:
796
+ kw["not_null"] = coltype.not_null
797
+ if coltype.check is not None:
798
+ kw["check"] = coltype.check
799
+ if coltype.create_type:
800
+ kw["create_type"] = coltype.create_type
801
+
721
802
  try:
722
803
  new_coltype = coltype.adapt(supercls)
723
804
  except TypeError:
@@ -752,6 +833,7 @@ class DeclarativeGenerator(TablesGenerator):
752
833
  "use_inflect",
753
834
  "nojoined",
754
835
  "nobidi",
836
+ "noidsuffix",
755
837
  }
756
838
 
757
839
  def __init__(
@@ -1088,7 +1170,7 @@ class DeclarativeGenerator(TablesGenerator):
1088
1170
 
1089
1171
  # If there's a constraint with a single column that ends with "_id", use the
1090
1172
  # preceding part as the relationship name
1091
- if relationship.constraint:
1173
+ if relationship.constraint and "noidsuffix" not in self.options:
1092
1174
  is_source = relationship.source.table is relationship.constraint.table
1093
1175
  if is_source or relationship.type not in (
1094
1176
  RelationshipType.ONE_TO_ONE,
@@ -1178,7 +1260,7 @@ class DeclarativeGenerator(TablesGenerator):
1178
1260
 
1179
1261
  def render_table_args(self, table: Table) -> str:
1180
1262
  args: list[str] = []
1181
- kwargs: dict[str, str] = {}
1263
+ kwargs: dict[str, object] = {}
1182
1264
 
1183
1265
  # Render constraints
1184
1266
  for constraint in sorted(table.constraints, key=get_constraint_sort_key):
@@ -1204,6 +1286,10 @@ class DeclarativeGenerator(TablesGenerator):
1204
1286
  if table.comment:
1205
1287
  kwargs["comment"] = table.comment
1206
1288
 
1289
+ # add info + dialect kwargs for dict context (__table_args__) (opt-in)
1290
+ if self.include_dialect_options_and_info:
1291
+ self._add_dialect_kwargs_and_info(table, kwargs, values_for_dict=True)
1292
+
1207
1293
  if kwargs:
1208
1294
  formatted_kwargs = pformat(kwargs)
1209
1295
  if not args:
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
4
  from enum import Enum, auto
5
- from typing import Any, Union
5
+ from typing import Any
6
6
 
7
7
  from sqlalchemy.sql.schema import Column, ForeignKeyConstraint, Table
8
8
 
@@ -52,7 +52,7 @@ class ColumnAttribute:
52
52
  return self.name
53
53
 
54
54
 
55
- JoinType = tuple[Model, Union[ColumnAttribute, str], Model, Union[ColumnAttribute, str]]
55
+ JoinType = tuple[Model, ColumnAttribute | str, Model, ColumnAttribute | str]
56
56
 
57
57
 
58
58
  @dataclass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlacodegen
3
- Version: 3.1.1
3
+ Version: 3.2.0
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>
@@ -15,18 +15,16 @@ Classifier: Topic :: Database
15
15
  Classifier: Topic :: Software Development :: Code Generators
16
16
  Classifier: Programming Language :: Python
17
17
  Classifier: Programming Language :: Python :: 3
18
- Classifier: Programming Language :: Python :: 3.9
19
18
  Classifier: Programming Language :: Python :: 3.10
20
19
  Classifier: Programming Language :: Python :: 3.11
21
20
  Classifier: Programming Language :: Python :: 3.12
22
21
  Classifier: Programming Language :: Python :: 3.13
23
- Requires-Python: >=3.9
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Requires-Python: >=3.10
24
24
  Description-Content-Type: text/x-rst
25
25
  License-File: LICENSE
26
- Requires-Dist: SQLAlchemy<2.0.42,>=2.0.29
26
+ Requires-Dist: SQLAlchemy>=2.0.29
27
27
  Requires-Dist: inflect>=4.0.0
28
- Requires-Dist: importlib_metadata; python_version < "3.10"
29
- Requires-Dist: stdlib-list; python_version < "3.10"
30
28
  Provides-Extra: test
31
29
  Requires-Dist: sqlacodegen[geoalchemy2,pgvector,sqlmodel]; extra == "test"
32
30
  Requires-Dist: pytest>=7.4; extra == "test"
@@ -148,6 +146,10 @@ values must be delimited by commas, e.g. ``--options noconstraints,nobidi``):
148
146
  * ``noconstraints``: ignore constraints (foreign key, unique etc.)
149
147
  * ``nocomments``: ignore table/column comments
150
148
  * ``noindexes``: ignore indexes
149
+ * ``noidsuffix``: prevent the special naming logic for single column many-to-one
150
+ and one-to-one relationships (see `Relationship naming logic`_ for details)
151
+ * ``include_dialect_options``: render a table' dialect options, such as ``starrocks_partition`` for StarRocks' specific options.
152
+ * ``keep_dialect_types``: preserve dialect-specific column types instead of adapting to generic SQLAlchemy types.
151
153
 
152
154
  * ``declarative``
153
155
 
@@ -1,10 +1,6 @@
1
- SQLAlchemy<2.0.42,>=2.0.29
1
+ SQLAlchemy>=2.0.29
2
2
  inflect>=4.0.0
3
3
 
4
- [:python_version < "3.10"]
5
- importlib_metadata
6
- stdlib-list
7
-
8
4
  [citext]
9
5
  sqlalchemy-citext>=1.7.0
10
6
 
@@ -111,6 +111,180 @@ class SimpleItems(Base):
111
111
  )
112
112
 
113
113
 
114
+ @pytest.mark.parametrize("generator", [["include_dialect_options"]], indirect=True)
115
+ def test_include_dialect_options_and_info_table_and_column(
116
+ generator: CodeGenerator,
117
+ ) -> None:
118
+ from .test_generator_tables import _PartitionInfo
119
+
120
+ Table(
121
+ "t_opts",
122
+ generator.metadata,
123
+ Column("id", INTEGER, primary_key=True, starrocks_is_agg_key=True),
124
+ Column("name", VARCHAR, starrocks_agg_type="REPLACE"),
125
+ starrocks_aggregate_key="id",
126
+ starrocks_partition_by=_PartitionInfo("RANGE(id)"),
127
+ starrocks_security="DEFINER",
128
+ starrocks_PROPERTIES={"replication_num": "3", "storage_medium": "SSD"},
129
+ info={
130
+ "table_kind": "MATERIALIZED VIEW",
131
+ "definition": "SELECT id, name FROM t_opts_base_table",
132
+ },
133
+ )
134
+
135
+ validate_code(
136
+ generator.generate(),
137
+ """\
138
+ from typing import Optional
139
+
140
+ from sqlalchemy import Integer, String
141
+ from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
142
+
143
+ class Base(DeclarativeBase):
144
+ pass
145
+
146
+
147
+ class TOpts(Base):
148
+ __tablename__ = 't_opts'
149
+ __table_args__ = {'info': {'definition': 'SELECT id, name FROM t_opts_base_table',
150
+ 'table_kind': 'MATERIALIZED VIEW'},
151
+ 'starrocks_PROPERTIES': {'replication_num': '3', 'storage_medium': 'SSD'},
152
+ 'starrocks_aggregate_key': 'id',
153
+ 'starrocks_partition_by': 'RANGE(id)',
154
+ 'starrocks_security': 'DEFINER'}
155
+
156
+ id: Mapped[int] = mapped_column(Integer, primary_key=True, starrocks_is_agg_key=True)
157
+ name: Mapped[Optional[str]] = mapped_column(String, starrocks_agg_type='REPLACE')
158
+ """,
159
+ )
160
+
161
+
162
+ @pytest.mark.parametrize("generator", [["include_dialect_options"]], indirect=True)
163
+ def test_include_dialect_options_and_info_with_hyphen(generator: CodeGenerator) -> None:
164
+ Table(
165
+ "t_opts2",
166
+ generator.metadata,
167
+ Column("id", INTEGER, primary_key=True),
168
+ mysql_engine="InnoDB",
169
+ info={"table_kind": "View"},
170
+ )
171
+
172
+ validate_code(
173
+ generator.generate(),
174
+ """\
175
+ from sqlalchemy import Integer
176
+ from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
177
+
178
+ class Base(DeclarativeBase):
179
+ pass
180
+
181
+
182
+ class TOpts2(Base):
183
+ __tablename__ = 't_opts2'
184
+ __table_args__ = {'info': {'table_kind': 'View'}, 'mysql_engine': 'InnoDB'}
185
+
186
+ id: Mapped[int] = mapped_column(Integer, primary_key=True)
187
+ """,
188
+ )
189
+
190
+
191
+ def test_include_dialect_options_not_enabled_skips(generator: CodeGenerator) -> None:
192
+ from .test_generator_tables import _PartitionInfo
193
+
194
+ Table(
195
+ "t_plain",
196
+ generator.metadata,
197
+ Column(
198
+ "id",
199
+ INTEGER,
200
+ primary_key=True,
201
+ info={"abc": True},
202
+ starrocks_is_agg_key=True,
203
+ ),
204
+ starrocks_engine="OLAP",
205
+ starrocks_partition_by=_PartitionInfo("RANGE(id)"),
206
+ )
207
+
208
+ validate_code(
209
+ generator.generate(),
210
+ """\
211
+ from sqlalchemy import Integer
212
+ from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
213
+
214
+ class Base(DeclarativeBase):
215
+ pass
216
+
217
+
218
+ class TPlain(Base):
219
+ __tablename__ = 't_plain'
220
+
221
+ id: Mapped[int] = mapped_column(Integer, primary_key=True)
222
+ """,
223
+ )
224
+
225
+
226
+ def test_keep_dialect_types_adapts_mysql_integer_default(
227
+ generator: CodeGenerator,
228
+ ) -> None:
229
+ from sqlalchemy.dialects.mysql import INTEGER as MYSQL_INTEGER
230
+
231
+ Table(
232
+ "num",
233
+ generator.metadata,
234
+ Column("id", INTEGER, primary_key=True),
235
+ Column("val", MYSQL_INTEGER(), nullable=False),
236
+ )
237
+
238
+ validate_code(
239
+ generator.generate(),
240
+ """\
241
+ from sqlalchemy import Integer
242
+ from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
243
+
244
+ class Base(DeclarativeBase):
245
+ pass
246
+
247
+
248
+ class Num(Base):
249
+ __tablename__ = 'num'
250
+
251
+ id: Mapped[int] = mapped_column(Integer, primary_key=True)
252
+ val: Mapped[int] = mapped_column(Integer, nullable=False)
253
+ """,
254
+ )
255
+
256
+
257
+ @pytest.mark.parametrize("generator", [["keep_dialect_types"]], indirect=True)
258
+ def test_keep_dialect_types_keeps_mysql_integer(generator: CodeGenerator) -> None:
259
+ from sqlalchemy.dialects.mysql import INTEGER as MYSQL_INTEGER
260
+
261
+ Table(
262
+ "num2",
263
+ generator.metadata,
264
+ Column("id", INTEGER, primary_key=True),
265
+ Column("val", MYSQL_INTEGER(), nullable=False),
266
+ )
267
+
268
+ validate_code(
269
+ generator.generate(),
270
+ """\
271
+ from sqlalchemy import INTEGER
272
+ from sqlalchemy.dialects.mysql import INTEGER
273
+ from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
274
+
275
+ class Base(DeclarativeBase):
276
+ pass
277
+
278
+
279
+ class Num2(Base):
280
+ __tablename__ = 'num2'
281
+
282
+ id: Mapped[int] = mapped_column(INTEGER, primary_key=True)
283
+ val: Mapped[int] = mapped_column(INTEGER, nullable=False)
284
+ """,
285
+ )
286
+
287
+
114
288
  def test_onetomany(generator: CodeGenerator) -> None:
115
289
  Table(
116
290
  "simple_items",
@@ -1563,6 +1737,62 @@ back_populates='simple_items')
1563
1737
  )
1564
1738
 
1565
1739
 
1740
+ @pytest.mark.parametrize("generator", [["noidsuffix"]], indirect=True)
1741
+ def test_named_foreign_key_constraints_with_noidsuffix(
1742
+ generator: CodeGenerator,
1743
+ ) -> None:
1744
+ Table(
1745
+ "simple_items",
1746
+ generator.metadata,
1747
+ Column("id", INTEGER, primary_key=True),
1748
+ Column("container_id", INTEGER),
1749
+ ForeignKeyConstraint(
1750
+ ["container_id"], ["simple_containers.id"], name="foreignkeytest"
1751
+ ),
1752
+ )
1753
+ Table(
1754
+ "simple_containers",
1755
+ generator.metadata,
1756
+ Column("id", INTEGER, primary_key=True),
1757
+ )
1758
+
1759
+ validate_code(
1760
+ generator.generate(),
1761
+ """\
1762
+ from typing import Optional
1763
+
1764
+ from sqlalchemy import ForeignKeyConstraint, Integer
1765
+ from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
1766
+
1767
+ class Base(DeclarativeBase):
1768
+ pass
1769
+
1770
+
1771
+ class SimpleContainers(Base):
1772
+ __tablename__ = 'simple_containers'
1773
+
1774
+ id: Mapped[int] = mapped_column(Integer, primary_key=True)
1775
+
1776
+ simple_items: Mapped[list['SimpleItems']] = relationship('SimpleItems', \
1777
+ back_populates='simple_containers')
1778
+
1779
+
1780
+ class SimpleItems(Base):
1781
+ __tablename__ = 'simple_items'
1782
+ __table_args__ = (
1783
+ ForeignKeyConstraint(['container_id'], ['simple_containers.id'], \
1784
+ name='foreignkeytest'),
1785
+ )
1786
+
1787
+ id: Mapped[int] = mapped_column(Integer, primary_key=True)
1788
+ container_id: Mapped[Optional[int]] = mapped_column(Integer)
1789
+
1790
+ simple_containers: Mapped[Optional['SimpleContainers']] = relationship('SimpleContainers', \
1791
+ back_populates='simple_items')
1792
+ """,
1793
+ )
1794
+
1795
+
1566
1796
  # @pytest.mark.xfail(strict=True)
1567
1797
  def test_colname_import_conflict(generator: CodeGenerator) -> None:
1568
1798
  Table(
@@ -5,7 +5,8 @@ from textwrap import dedent
5
5
  import pytest
6
6
  from _pytest.fixtures import FixtureRequest
7
7
  from sqlalchemy import TypeDecorator
8
- from sqlalchemy.dialects import mysql, postgresql
8
+ from sqlalchemy.dialects import mysql, postgresql, registry
9
+ from sqlalchemy.dialects.mysql.pymysql import MySQLDialect_pymysql
9
10
  from sqlalchemy.engine import Engine
10
11
  from sqlalchemy.schema import (
11
12
  CheckConstraint,
@@ -1090,3 +1091,85 @@ schema='{expected_schema}'), primary_key=True),
1090
1091
  )
1091
1092
  """,
1092
1093
  )
1094
+
1095
+
1096
+ class MockStarRocksDialect(MySQLDialect_pymysql):
1097
+ name = "starrocks"
1098
+ construct_arguments = [
1099
+ (
1100
+ Column,
1101
+ {
1102
+ "is_agg_key": None,
1103
+ "agg_type": None,
1104
+ "IS_AGG_KEY": None,
1105
+ "AGG_TYPE": None,
1106
+ },
1107
+ ),
1108
+ (
1109
+ Table,
1110
+ {
1111
+ "primary_key": None,
1112
+ "aggregate_key": None,
1113
+ "unique_key": None,
1114
+ "duplicate_key": None,
1115
+ "engine": "OLAP",
1116
+ "partition_by": None,
1117
+ "order_by": None,
1118
+ "security": None,
1119
+ "properties": {},
1120
+ "ENGINE": "OLAP",
1121
+ "PARTITION_BY": None,
1122
+ "ORDER_BY": None,
1123
+ "SECURITY": None,
1124
+ "PROPERTIES": {},
1125
+ },
1126
+ ),
1127
+ ]
1128
+
1129
+
1130
+ # Register StarRocksDialect
1131
+ registry.register("starrocks", __name__, "MockStarRocksDialect")
1132
+
1133
+
1134
+ class _PartitionInfo:
1135
+ def __init__(self, partition_by: str) -> None:
1136
+ self.partition_by = partition_by
1137
+
1138
+ def __str__(self) -> str:
1139
+ return self.partition_by
1140
+
1141
+ def __repr__(self) -> str:
1142
+ return repr(self.partition_by)
1143
+
1144
+
1145
+ @pytest.mark.parametrize("generator", [["include_dialect_options"]], indirect=True)
1146
+ def test_include_dialect_options_starrocks_tables(generator: CodeGenerator) -> None:
1147
+ Table(
1148
+ "t_starrocks",
1149
+ generator.metadata,
1150
+ Column("id", INTEGER, primary_key=True, starrocks_is_agg_key=True),
1151
+ starrocks_ENGINE="OLAP",
1152
+ starrocks_PARTITION_BY=_PartitionInfo("RANGE(id)"),
1153
+ starrocks_ORDER_BY="id, name",
1154
+ starrocks_PROPERTIES={"replication_num": "3", "storage_medium": "SSD"},
1155
+ ).info = {"table_kind": "TABLE"}
1156
+
1157
+ validate_code(
1158
+ generator.generate(),
1159
+ """\
1160
+ from sqlalchemy import Column, Integer, MetaData, Table
1161
+
1162
+ metadata = MetaData()
1163
+
1164
+
1165
+ t_t_starrocks = Table(
1166
+ 't_starrocks', metadata,
1167
+ Column('id', Integer, primary_key=True, starrocks_is_agg_key=True),
1168
+ info={'table_kind': 'TABLE'},
1169
+ starrocks_ENGINE='OLAP',
1170
+ starrocks_ORDER_BY='id, name',
1171
+ starrocks_PARTITION_BY='RANGE(id)',
1172
+ starrocks_PROPERTIES={'replication_num': '3', 'storage_medium': 'SSD'}
1173
+ )
1174
+ """,
1175
+ )
File without changes
File without changes
File without changes