sqlacodegen 4.0.1__tar.gz → 4.0.2__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.1 → sqlacodegen-4.0.2}/CHANGES.rst +10 -0
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/PKG-INFO +1 -1
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/src/sqlacodegen/generators.py +104 -23
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/src/sqlacodegen.egg-info/PKG-INFO +1 -1
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/tests/test_generator_declarative.py +106 -0
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/tests/test_generator_sqlmodel.py +103 -0
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/tests/test_generator_tables.py +28 -0
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/.github/FUNDING.yml +0 -0
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/.github/ISSUE_TEMPLATE/bug_report.yaml +0 -0
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/.github/ISSUE_TEMPLATE/features_request.yaml +0 -0
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/.github/dependabot.yml +0 -0
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/.github/pull_request_template.md +0 -0
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/.github/workflows/publish.yml +0 -0
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/.github/workflows/test.yml +0 -0
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/.gitignore +0 -0
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/.pre-commit-config.yaml +0 -0
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/CONTRIBUTING.rst +0 -0
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/LICENSE +0 -0
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/README.rst +0 -0
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/pyproject.toml +0 -0
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/setup.cfg +0 -0
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/src/sqlacodegen/__init__.py +0 -0
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/src/sqlacodegen/__main__.py +0 -0
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/src/sqlacodegen/cli.py +0 -0
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/src/sqlacodegen/models.py +0 -0
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/src/sqlacodegen/py.typed +0 -0
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/src/sqlacodegen/utils.py +0 -0
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/src/sqlacodegen.egg-info/SOURCES.txt +0 -0
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/src/sqlacodegen.egg-info/dependency_links.txt +0 -0
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/src/sqlacodegen.egg-info/entry_points.txt +0 -0
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/src/sqlacodegen.egg-info/requires.txt +0 -0
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/src/sqlacodegen.egg-info/top_level.txt +0 -0
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/tests/__init__.py +0 -0
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/tests/conftest.py +0 -0
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/tests/test_cli.py +0 -0
- {sqlacodegen-4.0.1 → sqlacodegen-4.0.2}/tests/test_generator_dataclass.py +0 -0
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
Version history
|
|
2
2
|
===============
|
|
3
3
|
|
|
4
|
+
**4.0.2**
|
|
5
|
+
|
|
6
|
+
- Fixed rendering of inherited keyword arguments for dialect-specific types that use
|
|
7
|
+
``**kwargs`` in their initializers (such as MySQL ``CHAR`` with ``collation``) while
|
|
8
|
+
preserving existing ``*args`` rendering behavior (PR by @hyoj0942)
|
|
9
|
+
- Fixed missing metadata argument when rendering plain tables with the SQLModel
|
|
10
|
+
- Added support for self-referential tables in the SQLModel generator (PR by @sheinbergon)
|
|
11
|
+
- Fixed empty dialect kwargs (e.g. ``postgresql_include=[]``) being included in
|
|
12
|
+
rendered indexes, tables, and columns (PR by @sheinbergon)
|
|
13
|
+
|
|
4
14
|
**4.0.1**
|
|
5
15
|
|
|
6
16
|
- Fix enum column definitions to explicitly include schema and name if reflected
|
|
@@ -427,6 +427,7 @@ class TablesGenerator(CodeGenerator):
|
|
|
427
427
|
kwargs = {
|
|
428
428
|
key: repr(value) if isinstance(value, str) else value
|
|
429
429
|
for key, value in sorted(index.kwargs.items(), key=lambda item: item[0])
|
|
430
|
+
if value not in ([], {})
|
|
430
431
|
}
|
|
431
432
|
if index.unique:
|
|
432
433
|
kwargs["unique"] = True
|
|
@@ -541,6 +542,77 @@ class TablesGenerator(CodeGenerator):
|
|
|
541
542
|
else:
|
|
542
543
|
return render_callable("mapped_column", *args, kwargs=kwargs)
|
|
543
544
|
|
|
545
|
+
def _render_column_type_value(self, value: Any) -> str:
|
|
546
|
+
if isinstance(value, (JSONB, JSON)):
|
|
547
|
+
# Remove astext_type if it's the default
|
|
548
|
+
if isinstance(value.astext_type, Text) and value.astext_type.length is None:
|
|
549
|
+
value.astext_type = None # type: ignore[assignment]
|
|
550
|
+
else:
|
|
551
|
+
self.add_import(Text)
|
|
552
|
+
|
|
553
|
+
if isinstance(value, TextClause):
|
|
554
|
+
self.add_literal_import("sqlalchemy", "text")
|
|
555
|
+
return render_callable("text", repr(value.text))
|
|
556
|
+
|
|
557
|
+
return repr(value)
|
|
558
|
+
|
|
559
|
+
def _collect_inherited_init_kwargs(
|
|
560
|
+
self,
|
|
561
|
+
column_type: Any,
|
|
562
|
+
init_sig: inspect.Signature,
|
|
563
|
+
seen_param_names: set[str],
|
|
564
|
+
missing: object,
|
|
565
|
+
) -> dict[str, str]:
|
|
566
|
+
has_var_keyword = any(
|
|
567
|
+
param.kind is Parameter.VAR_KEYWORD
|
|
568
|
+
for param in init_sig.parameters.values()
|
|
569
|
+
)
|
|
570
|
+
has_var_positional = any(
|
|
571
|
+
param.kind is Parameter.VAR_POSITIONAL
|
|
572
|
+
for param in init_sig.parameters.values()
|
|
573
|
+
)
|
|
574
|
+
if not has_var_keyword or has_var_positional:
|
|
575
|
+
return {}
|
|
576
|
+
|
|
577
|
+
inherited_kwargs: dict[str, str] = {}
|
|
578
|
+
for supercls in column_type.__class__.__mro__[1:]:
|
|
579
|
+
if supercls is object:
|
|
580
|
+
break
|
|
581
|
+
|
|
582
|
+
try:
|
|
583
|
+
super_sig = inspect.signature(supercls.__init__)
|
|
584
|
+
except (TypeError, ValueError):
|
|
585
|
+
continue
|
|
586
|
+
|
|
587
|
+
for super_param in list(super_sig.parameters.values())[1:]:
|
|
588
|
+
if super_param.name.startswith("_"):
|
|
589
|
+
continue
|
|
590
|
+
|
|
591
|
+
if super_param.kind in (
|
|
592
|
+
Parameter.POSITIONAL_ONLY,
|
|
593
|
+
Parameter.VAR_POSITIONAL,
|
|
594
|
+
Parameter.VAR_KEYWORD,
|
|
595
|
+
):
|
|
596
|
+
continue
|
|
597
|
+
|
|
598
|
+
if super_param.name in seen_param_names:
|
|
599
|
+
continue
|
|
600
|
+
|
|
601
|
+
seen_param_names.add(super_param.name)
|
|
602
|
+
value = getattr(column_type, super_param.name, missing)
|
|
603
|
+
if value is missing:
|
|
604
|
+
continue
|
|
605
|
+
|
|
606
|
+
default = super_param.default
|
|
607
|
+
if default is not Parameter.empty and value == default:
|
|
608
|
+
continue
|
|
609
|
+
|
|
610
|
+
inherited_kwargs[super_param.name] = self._render_column_type_value(
|
|
611
|
+
value
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
return inherited_kwargs
|
|
615
|
+
|
|
544
616
|
def render_column_type(self, column: Column[Any]) -> str:
|
|
545
617
|
column_type = column.type
|
|
546
618
|
# Check if this is an enum column with a Python enum class
|
|
@@ -586,6 +658,8 @@ class TablesGenerator(CodeGenerator):
|
|
|
586
658
|
defaults = {param.name: param.default for param in sig.parameters.values()}
|
|
587
659
|
missing = object()
|
|
588
660
|
use_kwargs = False
|
|
661
|
+
seen_param_names: set[str] = set()
|
|
662
|
+
|
|
589
663
|
for param in list(sig.parameters.values())[1:]:
|
|
590
664
|
# Remove annoyances like _warn_on_bytestring
|
|
591
665
|
if param.name.startswith("_"):
|
|
@@ -594,32 +668,25 @@ class TablesGenerator(CodeGenerator):
|
|
|
594
668
|
use_kwargs = True
|
|
595
669
|
continue
|
|
596
670
|
|
|
671
|
+
seen_param_names.add(param.name)
|
|
597
672
|
value = getattr(column_type, param.name, missing)
|
|
598
|
-
|
|
599
|
-
if isinstance(value, (JSONB, JSON)):
|
|
600
|
-
# Remove astext_type if it's the default
|
|
601
|
-
if (
|
|
602
|
-
isinstance(value.astext_type, Text)
|
|
603
|
-
and value.astext_type.length is None
|
|
604
|
-
):
|
|
605
|
-
value.astext_type = None # type: ignore[assignment]
|
|
606
|
-
else:
|
|
607
|
-
self.add_import(Text)
|
|
608
|
-
|
|
609
673
|
default = defaults.get(param.name, missing)
|
|
610
|
-
if isinstance(value, TextClause):
|
|
611
|
-
self.add_literal_import("sqlalchemy", "text")
|
|
612
|
-
rendered_value = render_callable("text", repr(value.text))
|
|
613
|
-
else:
|
|
614
|
-
rendered_value = repr(value)
|
|
615
|
-
|
|
616
674
|
if value is missing or value == default:
|
|
617
675
|
use_kwargs = True
|
|
618
|
-
|
|
676
|
+
continue
|
|
677
|
+
|
|
678
|
+
rendered_value = self._render_column_type_value(value)
|
|
679
|
+
if use_kwargs:
|
|
619
680
|
kwargs[param.name] = rendered_value
|
|
620
681
|
else:
|
|
621
682
|
args.append(rendered_value)
|
|
622
683
|
|
|
684
|
+
kwargs.update(
|
|
685
|
+
self._collect_inherited_init_kwargs(
|
|
686
|
+
column_type, sig, seen_param_names, missing
|
|
687
|
+
)
|
|
688
|
+
)
|
|
689
|
+
|
|
623
690
|
vararg = next(
|
|
624
691
|
(
|
|
625
692
|
param.name
|
|
@@ -712,6 +779,9 @@ class TablesGenerator(CodeGenerator):
|
|
|
712
779
|
except Exception:
|
|
713
780
|
continue
|
|
714
781
|
|
|
782
|
+
if isinstance(value, list | dict) and not value:
|
|
783
|
+
continue
|
|
784
|
+
|
|
715
785
|
# Render values:
|
|
716
786
|
# - callable context (values_for_dict=False): produce a string expression.
|
|
717
787
|
# primitives use repr(value); custom objects stringify then repr().
|
|
@@ -1671,13 +1741,16 @@ class DeclarativeGenerator(TablesGenerator):
|
|
|
1671
1741
|
) -> Mapping[str, Any]:
|
|
1672
1742
|
def render_column_attrs(column_attrs: list[ColumnAttribute]) -> str:
|
|
1673
1743
|
rendered = []
|
|
1744
|
+
render_as_string = False
|
|
1674
1745
|
for attr in column_attrs:
|
|
1675
|
-
if attr.model is relationship.source:
|
|
1746
|
+
if not self.explicit_foreign_keys and attr.model is relationship.source:
|
|
1676
1747
|
rendered.append(attr.name)
|
|
1677
1748
|
else:
|
|
1678
|
-
rendered.append(
|
|
1749
|
+
rendered.append(f"{attr.model.name}.{attr.name}")
|
|
1750
|
+
render_as_string = True
|
|
1679
1751
|
|
|
1680
|
-
|
|
1752
|
+
joined = "[" + ", ".join(rendered) + "]"
|
|
1753
|
+
return repr(joined) if render_as_string else joined
|
|
1681
1754
|
|
|
1682
1755
|
def render_foreign_keys(column_attrs: list[ColumnAttribute]) -> str:
|
|
1683
1756
|
rendered = []
|
|
@@ -1806,19 +1879,27 @@ class SQLModelGenerator(DeclarativeGenerator):
|
|
|
1806
1879
|
self.add_import(Column)
|
|
1807
1880
|
return render_callable("Column", *args, kwargs=kwargs)
|
|
1808
1881
|
|
|
1882
|
+
def render_table(self, table: Table) -> str:
|
|
1883
|
+
# Hack to fix #465 without breaking backwards compatibility
|
|
1884
|
+
self.base.metadata_ref = "SQLModel.metadata"
|
|
1885
|
+
|
|
1886
|
+
return super().render_table(table)
|
|
1887
|
+
|
|
1809
1888
|
def generate_base(self) -> None:
|
|
1810
1889
|
self.base = Base(
|
|
1811
1890
|
literal_imports=[],
|
|
1812
1891
|
declarations=[],
|
|
1813
|
-
metadata_ref="",
|
|
1892
|
+
metadata_ref="SQLModel.metadata",
|
|
1814
1893
|
)
|
|
1815
1894
|
|
|
1816
1895
|
def collect_imports(self, models: Iterable[Model]) -> None:
|
|
1817
1896
|
super(DeclarativeGenerator, self).collect_imports(models)
|
|
1818
1897
|
if any(isinstance(model, ModelClass) for model in models):
|
|
1898
|
+
self.add_literal_import("sqlmodel", "Field")
|
|
1899
|
+
|
|
1900
|
+
if models:
|
|
1819
1901
|
self.remove_literal_import("sqlalchemy", "MetaData")
|
|
1820
1902
|
self.add_literal_import("sqlmodel", "SQLModel")
|
|
1821
|
-
self.add_literal_import("sqlmodel", "Field")
|
|
1822
1903
|
|
|
1823
1904
|
def collect_imports_for_model(self, model: Model) -> None:
|
|
1824
1905
|
super(DeclarativeGenerator, self).collect_imports_for_model(model)
|
|
@@ -321,6 +321,40 @@ class Num2(Base):
|
|
|
321
321
|
)
|
|
322
322
|
|
|
323
323
|
|
|
324
|
+
@pytest.mark.parametrize("engine", ["mysql"], indirect=["engine"])
|
|
325
|
+
@pytest.mark.parametrize("generator", [["keep_dialect_types"]], indirect=True)
|
|
326
|
+
def test_keep_dialect_types_keeps_mysql_char_collation(
|
|
327
|
+
generator: CodeGenerator,
|
|
328
|
+
) -> None:
|
|
329
|
+
from sqlalchemy.dialects.mysql import CHAR as MYSQL_CHAR
|
|
330
|
+
from sqlalchemy.dialects.mysql import INTEGER as MYSQL_INTEGER
|
|
331
|
+
|
|
332
|
+
Table(
|
|
333
|
+
"result_logs",
|
|
334
|
+
generator.metadata,
|
|
335
|
+
Column("id", MYSQL_INTEGER, primary_key=True),
|
|
336
|
+
Column("result_code", MYSQL_CHAR(1, collation="utf8mb3_bin"), nullable=False),
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
validate_code(
|
|
340
|
+
generator.generate(),
|
|
341
|
+
"""\
|
|
342
|
+
from sqlalchemy.dialects.mysql import CHAR, INTEGER
|
|
343
|
+
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
|
344
|
+
|
|
345
|
+
class Base(DeclarativeBase):
|
|
346
|
+
pass
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
class ResultLogs(Base):
|
|
350
|
+
__tablename__ = 'result_logs'
|
|
351
|
+
|
|
352
|
+
id: Mapped[int] = mapped_column(INTEGER, primary_key=True)
|
|
353
|
+
result_code: Mapped[str] = mapped_column(CHAR(1, collation='utf8mb3_bin'), nullable=False)
|
|
354
|
+
""",
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
|
|
324
358
|
def test_onetomany(generator: CodeGenerator) -> None:
|
|
325
359
|
Table(
|
|
326
360
|
"simple_items",
|
|
@@ -3127,3 +3161,75 @@ def test_array_enum_named_with_schema(generator: CodeGenerator) -> None:
|
|
|
3127
3161
|
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)
|
|
3128
3162
|
""",
|
|
3129
3163
|
)
|
|
3164
|
+
|
|
3165
|
+
|
|
3166
|
+
def test_index_with_empty_kwargs(generator: CodeGenerator) -> None:
|
|
3167
|
+
simple_items = Table(
|
|
3168
|
+
"simple_items",
|
|
3169
|
+
generator.metadata,
|
|
3170
|
+
Column("id", INTEGER, primary_key=True),
|
|
3171
|
+
Column("name", VARCHAR),
|
|
3172
|
+
)
|
|
3173
|
+
simple_items.indexes.add(
|
|
3174
|
+
Index(
|
|
3175
|
+
"idx_name",
|
|
3176
|
+
simple_items.c.name,
|
|
3177
|
+
postgresql_using="gist",
|
|
3178
|
+
postgresql_include=[],
|
|
3179
|
+
)
|
|
3180
|
+
)
|
|
3181
|
+
|
|
3182
|
+
validate_code(
|
|
3183
|
+
generator.generate(),
|
|
3184
|
+
"""\
|
|
3185
|
+
from typing import Optional
|
|
3186
|
+
|
|
3187
|
+
from sqlalchemy import Index, Integer, String
|
|
3188
|
+
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
|
3189
|
+
|
|
3190
|
+
class Base(DeclarativeBase):
|
|
3191
|
+
pass
|
|
3192
|
+
|
|
3193
|
+
|
|
3194
|
+
class SimpleItems(Base):
|
|
3195
|
+
__tablename__ = 'simple_items'
|
|
3196
|
+
__table_args__ = (
|
|
3197
|
+
Index('idx_name', 'name', postgresql_using='gist'),
|
|
3198
|
+
)
|
|
3199
|
+
|
|
3200
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
3201
|
+
name: Mapped[Optional[str]] = mapped_column(String)
|
|
3202
|
+
""",
|
|
3203
|
+
)
|
|
3204
|
+
|
|
3205
|
+
|
|
3206
|
+
@pytest.mark.parametrize("generator", [["include_dialect_options"]], indirect=True)
|
|
3207
|
+
def test_include_dialect_options_empty_values_skipped(
|
|
3208
|
+
generator: CodeGenerator,
|
|
3209
|
+
) -> None:
|
|
3210
|
+
Table(
|
|
3211
|
+
"t_opts3",
|
|
3212
|
+
generator.metadata,
|
|
3213
|
+
Column("id", INTEGER, primary_key=True),
|
|
3214
|
+
mysql_engine="InnoDB",
|
|
3215
|
+
mysql_partition_by=[],
|
|
3216
|
+
mysql_PROPERTIES={},
|
|
3217
|
+
)
|
|
3218
|
+
|
|
3219
|
+
validate_code(
|
|
3220
|
+
generator.generate(),
|
|
3221
|
+
"""\
|
|
3222
|
+
from sqlalchemy import Integer
|
|
3223
|
+
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
|
3224
|
+
|
|
3225
|
+
class Base(DeclarativeBase):
|
|
3226
|
+
pass
|
|
3227
|
+
|
|
3228
|
+
|
|
3229
|
+
class TOpts3(Base):
|
|
3230
|
+
__tablename__ = 't_opts3'
|
|
3231
|
+
__table_args__ = {'mysql_engine': 'InnoDB'}
|
|
3232
|
+
|
|
3233
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
3234
|
+
""",
|
|
3235
|
+
)
|
|
@@ -366,3 +366,106 @@ def test_array_enum_named_with_schema(generator: CodeGenerator) -> None:
|
|
|
366
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
367
|
""",
|
|
368
368
|
)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def test_fallback_table(generator: CodeGenerator) -> None:
|
|
372
|
+
Table(
|
|
373
|
+
"simple_fallback",
|
|
374
|
+
generator.metadata,
|
|
375
|
+
Column("field", VARCHAR(20), nullable=False),
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
validate_code(
|
|
379
|
+
generator.generate(),
|
|
380
|
+
"""\
|
|
381
|
+
from sqlalchemy import Column, String, Table
|
|
382
|
+
from sqlmodel import SQLModel
|
|
383
|
+
|
|
384
|
+
t_simple_fallback = Table(
|
|
385
|
+
'simple_fallback', SQLModel.metadata,
|
|
386
|
+
Column('field', String(20), nullable=False)
|
|
387
|
+
)
|
|
388
|
+
""",
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def test_onetomany_selfref(generator: CodeGenerator) -> None:
|
|
393
|
+
Table(
|
|
394
|
+
"simple_items",
|
|
395
|
+
generator.metadata,
|
|
396
|
+
Column("id", INTEGER, primary_key=True),
|
|
397
|
+
Column("parent_item_id", INTEGER),
|
|
398
|
+
ForeignKeyConstraint(["parent_item_id"], ["simple_items.id"]),
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
validate_code(
|
|
402
|
+
generator.generate(),
|
|
403
|
+
"""\
|
|
404
|
+
from typing import Optional
|
|
405
|
+
|
|
406
|
+
from sqlalchemy import Column, ForeignKey, Integer
|
|
407
|
+
from sqlmodel import Field, Relationship, SQLModel
|
|
408
|
+
|
|
409
|
+
class SimpleItems(SQLModel, table=True):
|
|
410
|
+
__tablename__ = 'simple_items'
|
|
411
|
+
|
|
412
|
+
id: int = Field(sa_column=Column('id', Integer, primary_key=True))
|
|
413
|
+
parent_item_id: Optional[int] = Field(default=None, sa_column=Column(\
|
|
414
|
+
'parent_item_id', ForeignKey('simple_items.id')))
|
|
415
|
+
|
|
416
|
+
parent_item: Optional['SimpleItems'] = Relationship(\
|
|
417
|
+
back_populates='parent_item_reverse', sa_relationship_kwargs={\
|
|
418
|
+
'remote_side': '[SimpleItems.id]'})
|
|
419
|
+
parent_item_reverse: list['SimpleItems'] = Relationship(\
|
|
420
|
+
back_populates='parent_item', sa_relationship_kwargs={\
|
|
421
|
+
'remote_side': '[SimpleItems.parent_item_id]'})
|
|
422
|
+
""",
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def test_onetomany_selfref_multi(generator: CodeGenerator) -> None:
|
|
427
|
+
Table(
|
|
428
|
+
"simple_items_selfref",
|
|
429
|
+
generator.metadata,
|
|
430
|
+
Column("id", INTEGER, primary_key=True),
|
|
431
|
+
Column("parent_item_id", INTEGER),
|
|
432
|
+
Column("top_item_id", INTEGER),
|
|
433
|
+
ForeignKeyConstraint(["parent_item_id"], ["simple_items_selfref.id"]),
|
|
434
|
+
ForeignKeyConstraint(["top_item_id"], ["simple_items_selfref.id"]),
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
validate_code(
|
|
438
|
+
generator.generate(),
|
|
439
|
+
"""\
|
|
440
|
+
from typing import Optional
|
|
441
|
+
|
|
442
|
+
from sqlalchemy import Column, ForeignKey, Integer
|
|
443
|
+
from sqlmodel import Field, Relationship, SQLModel
|
|
444
|
+
|
|
445
|
+
class SimpleItemsSelfref(SQLModel, table=True):
|
|
446
|
+
__tablename__ = 'simple_items_selfref'
|
|
447
|
+
|
|
448
|
+
id: int = Field(sa_column=Column('id', Integer, primary_key=True))
|
|
449
|
+
parent_item_id: Optional[int] = Field(default=None, sa_column=Column(\
|
|
450
|
+
'parent_item_id', ForeignKey('simple_items_selfref.id')))
|
|
451
|
+
top_item_id: Optional[int] = Field(default=None, sa_column=Column(\
|
|
452
|
+
'top_item_id', ForeignKey('simple_items_selfref.id')))
|
|
453
|
+
|
|
454
|
+
parent_item: Optional['SimpleItemsSelfref'] = Relationship(\
|
|
455
|
+
back_populates='parent_item_reverse', sa_relationship_kwargs={\
|
|
456
|
+
'remote_side': '[SimpleItemsSelfref.id]', \
|
|
457
|
+
'foreign_keys': '[SimpleItemsSelfref.parent_item_id]'})
|
|
458
|
+
parent_item_reverse: list['SimpleItemsSelfref'] = Relationship(\
|
|
459
|
+
back_populates='parent_item', sa_relationship_kwargs={\
|
|
460
|
+
'remote_side': '[SimpleItemsSelfref.parent_item_id]', \
|
|
461
|
+
'foreign_keys': '[SimpleItemsSelfref.parent_item_id]'})
|
|
462
|
+
top_item: Optional['SimpleItemsSelfref'] = Relationship(\
|
|
463
|
+
back_populates='top_item_reverse', sa_relationship_kwargs={\
|
|
464
|
+
'remote_side': '[SimpleItemsSelfref.id]', \
|
|
465
|
+
'foreign_keys': '[SimpleItemsSelfref.top_item_id]'})
|
|
466
|
+
top_item_reverse: list['SimpleItemsSelfref'] = Relationship(\
|
|
467
|
+
back_populates='top_item', sa_relationship_kwargs={\
|
|
468
|
+
'remote_side': '[SimpleItemsSelfref.top_item_id]', \
|
|
469
|
+
'foreign_keys': '[SimpleItemsSelfref.top_item_id]'})
|
|
470
|
+
""",
|
|
471
|
+
)
|
|
@@ -603,6 +603,34 @@ def test_mysql_column_types(generator: CodeGenerator) -> None:
|
|
|
603
603
|
)
|
|
604
604
|
|
|
605
605
|
|
|
606
|
+
@pytest.mark.parametrize("engine", ["mysql"], indirect=["engine"])
|
|
607
|
+
@pytest.mark.parametrize("generator", [["keep_dialect_types"]], indirect=True)
|
|
608
|
+
def test_mysql_char_collation_keep_dialect_types(generator: CodeGenerator) -> None:
|
|
609
|
+
Table(
|
|
610
|
+
"simple_items",
|
|
611
|
+
generator.metadata,
|
|
612
|
+
Column("id", mysql.INTEGER, primary_key=True),
|
|
613
|
+
Column("result_code", mysql.CHAR(1, collation="utf8mb3_bin"), nullable=False),
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
validate_code(
|
|
617
|
+
generator.generate(),
|
|
618
|
+
"""\
|
|
619
|
+
from sqlalchemy import Column, MetaData, Table
|
|
620
|
+
from sqlalchemy.dialects.mysql import CHAR, INTEGER
|
|
621
|
+
|
|
622
|
+
metadata = MetaData()
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
t_simple_items = Table(
|
|
626
|
+
'simple_items', metadata,
|
|
627
|
+
Column('id', INTEGER, primary_key=True),
|
|
628
|
+
Column('result_code', CHAR(1, collation='utf8mb3_bin'), nullable=False)
|
|
629
|
+
)
|
|
630
|
+
""",
|
|
631
|
+
)
|
|
632
|
+
|
|
633
|
+
|
|
606
634
|
def test_constraints(generator: CodeGenerator) -> None:
|
|
607
635
|
Table(
|
|
608
636
|
"simple_items",
|
|
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
|