🚨 Latest Research:Tanstack npm Packages Compromised in Ongoing Mini Shai-Hulud Supply-Chain Attack.Learn More
Socket
Book a DemoSign in
Socket

sqlacodegen

Package Overview
Dependencies
Maintainers
2
Versions
33
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

sqlacodegen - pypi Package Compare versions

Comparing version
3.1.1
to
3.2.0
+1
-1
.github/workflows/test.yml

@@ -13,3 +13,3 @@ name: test suite

matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
runs-on: ubuntu-latest

@@ -16,0 +16,0 @@ steps:

@@ -19,3 +19,3 @@ # This is the configuration file for pre-commit (https://pre-commit.com/).

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.11
rev: v0.13.3
hooks:

@@ -27,3 +27,3 @@ - id: ruff

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.17.1
rev: v1.18.2
hooks:

@@ -41,1 +41,4 @@ - id: mypy

- id: rst-inline-touching-normal
ci:
autoupdate_schedule: quarterly
Version history
===============
**3.2.0**
- Dropped support for Python 3.9
- Fix Postgres ``DOMAIN`` adaptation regression introduced in SQLAlchemy 2.0.42 (PR by @sheinbergon)
- Support disabling special naming logic for single column many-to-one and one-to-one relationships
(PR by @Henkhogan, revised by @sheinbergon)
- Add ``include_dialect_options`` option to render ``Table`` and ``Column``
dialect-specific kwargs and ``info`` in generated code. (PR by @jaogoy)
- Add ``keep_dialect_types`` option to preserve dialect-specific column types instead of
adapting to generic SQLAlchemy types. (PR by @jaogoy)
**3.1.1**

@@ -5,0 +16,0 @@

Metadata-Version: 2.4
Name: sqlacodegen
Version: 3.1.1
Version: 3.2.0
Summary: Automatic model code generator for SQLAlchemy

@@ -18,3 +18,2 @@ Author-email: Alex Grönholm <alex.gronholm@nextday.fi>

Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10

@@ -24,9 +23,8 @@ Classifier: Programming Language :: Python :: 3.11

Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.9
Classifier: Programming Language :: Python :: 3.14
Requires-Python: >=3.10
Description-Content-Type: text/x-rst
License-File: LICENSE
Requires-Dist: SQLAlchemy<2.0.42,>=2.0.29
Requires-Dist: SQLAlchemy>=2.0.29
Requires-Dist: inflect>=4.0.0
Requires-Dist: importlib_metadata; python_version < "3.10"
Requires-Dist: stdlib-list; python_version < "3.10"
Provides-Extra: test

@@ -153,2 +151,6 @@ Requires-Dist: sqlacodegen[geoalchemy2,pgvector,sqlmodel]; extra == "test"

* ``noindexes``: ignore indexes
* ``noidsuffix``: prevent the special naming logic for single column many-to-one
and one-to-one relationships (see `Relationship naming logic`_ for details)
* ``include_dialect_options``: render a table' dialect options, such as ``starrocks_partition`` for StarRocks' specific options.
* ``keep_dialect_types``: preserve dialect-specific column types instead of adapting to generic SQLAlchemy types.

@@ -155,0 +157,0 @@ * ``declarative``

@@ -24,3 +24,2 @@ [build-system]

"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",

@@ -30,9 +29,8 @@ "Programming Language :: Python :: 3.11",

"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
]
requires-python = ">=3.9"
requires-python = ">=3.10"
dependencies = [
"SQLAlchemy >= 2.0.29,<2.0.42",
"SQLAlchemy >= 2.0.29",
"inflect >= 4.0.0",
"importlib_metadata; python_version < '3.10'",
"stdlib-list; python_version < '3.10'"
]

@@ -100,3 +98,3 @@ dynamic = ["version"]

[tool.tox]
env_list = ["py39", "py310", "py311", "py312", "py313"]
env_list = ["py310", "py311", "py312", "py313", "py314"]
skip_missing_interpreters = true

@@ -103,0 +101,0 @@

@@ -106,2 +106,6 @@ .. image:: https://github.com/agronholm/sqlacodegen/actions/workflows/test.yml/badge.svg

* ``noindexes``: ignore indexes
* ``noidsuffix``: prevent the special naming logic for single column many-to-one
and one-to-one relationships (see `Relationship naming logic`_ for details)
* ``include_dialect_options``: render a table' dialect options, such as ``starrocks_partition`` for StarRocks' specific options.
* ``keep_dialect_types``: preserve dialect-specific column types instead of adapting to generic SQLAlchemy types.

@@ -108,0 +112,0 @@ * ``declarative``

Metadata-Version: 2.4
Name: sqlacodegen
Version: 3.1.1
Version: 3.2.0
Summary: Automatic model code generator for SQLAlchemy

@@ -18,3 +18,2 @@ Author-email: Alex Grönholm <alex.gronholm@nextday.fi>

Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10

@@ -24,9 +23,8 @@ Classifier: Programming Language :: Python :: 3.11

Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.9
Classifier: Programming Language :: Python :: 3.14
Requires-Python: >=3.10
Description-Content-Type: text/x-rst
License-File: LICENSE
Requires-Dist: SQLAlchemy<2.0.42,>=2.0.29
Requires-Dist: SQLAlchemy>=2.0.29
Requires-Dist: inflect>=4.0.0
Requires-Dist: importlib_metadata; python_version < "3.10"
Requires-Dist: stdlib-list; python_version < "3.10"
Provides-Extra: test

@@ -153,2 +151,6 @@ Requires-Dist: sqlacodegen[geoalchemy2,pgvector,sqlmodel]; extra == "test"

* ``noindexes``: ignore indexes
* ``noidsuffix``: prevent the special naming logic for single column many-to-one
and one-to-one relationships (see `Relationship naming logic`_ for details)
* ``include_dialect_options``: render a table' dialect options, such as ``starrocks_partition`` for StarRocks' specific options.
* ``keep_dialect_types``: preserve dialect-specific column types instead of adapting to generic SQLAlchemy types.

@@ -155,0 +157,0 @@ * ``declarative``

@@ -1,8 +0,4 @@

SQLAlchemy<2.0.42,>=2.0.29
SQLAlchemy>=2.0.29
inflect>=4.0.0
[:python_version < "3.10"]
importlib_metadata
stdlib-list
[citext]

@@ -9,0 +5,0 @@ sqlalchemy-citext>=1.7.0

@@ -7,2 +7,3 @@ from __future__ import annotations

from contextlib import ExitStack
from importlib.metadata import entry_points, version
from typing import Any, TextIO

@@ -28,8 +29,3 @@

if sys.version_info < (3, 10):
from importlib_metadata import entry_points, version
else:
from importlib.metadata import entry_points, version
def _parse_engine_arg(arg_str: str) -> tuple[str, Any]:

@@ -36,0 +32,0 @@ if "=" not in arg_str:

@@ -122,3 +122,9 @@ from __future__ import annotations

class TablesGenerator(CodeGenerator):
valid_options: ClassVar[set[str]] = {"noindexes", "noconstraints", "nocomments"}
valid_options: ClassVar[set[str]] = {
"noindexes",
"noconstraints",
"nocomments",
"include_dialect_options",
"keep_dialect_types",
}
stdlib_module_names: ClassVar[set[str]] = get_stdlib_module_names()

@@ -139,2 +145,9 @@

# Render SchemaItem.info and dialect kwargs (Table/Column) into output
self.include_dialect_options_and_info: bool = (
"include_dialect_options" in self.options
)
# Keep dialect-specific types instead of adapting to generic SQLAlchemy types
self.keep_dialect_types: bool = "keep_dialect_types" in self.options
@property

@@ -398,2 +411,6 @@ def views_supported(self) -> bool:

# add info + dialect kwargs for callable context (opt-in)
if self.include_dialect_options_and_info:
self._add_dialect_kwargs_and_info(table, kwargs, values_for_dict=False)
return render_callable("Table", *args, kwargs=kwargs, indentation=" ")

@@ -504,2 +521,6 @@

# add column info + dialect kwargs for callable context (opt-in)
if self.include_dialect_options_and_info:
self._add_dialect_kwargs_and_info(column, kwargs, values_for_dict=False)
return self.render_column_callable(is_table, *args, **kwargs)

@@ -622,2 +643,47 @@

def _add_dialect_kwargs_and_info(
self, obj: Any, target_kwargs: dict[str, object], *, values_for_dict: bool
) -> None:
"""
Merge SchemaItem-like object's .info and .dialect_kwargs into target_kwargs.
- values_for_dict=True: keep raw values so pretty-printer emits repr() (for __table_args__ dict)
- values_for_dict=False: set values to repr() strings (for callable kwargs)
"""
info_dict = getattr(obj, "info", None)
if info_dict:
target_kwargs["info"] = info_dict if values_for_dict else repr(info_dict)
dialect_keys: list[str]
try:
dialect_keys = sorted(getattr(obj, "dialect_kwargs"))
except Exception:
return
dialect_kwargs = getattr(obj, "dialect_kwargs", {})
for key in dialect_keys:
try:
value = dialect_kwargs[key]
except Exception:
continue
# Render values:
# - callable context (values_for_dict=False): produce a string expression.
# primitives use repr(value); custom objects stringify then repr().
# - dict context (values_for_dict=True): pass raw primitives / str;
# custom objects become str(value) so pformat quotes them.
if values_for_dict:
if isinstance(value, type(None) | bool | int | float):
target_kwargs[key] = value
elif isinstance(value, str | dict | list):
target_kwargs[key] = value
else:
target_kwargs[key] = str(value)
else:
if isinstance(
value, type(None) | bool | int | float | str | dict | list
):
target_kwargs[key] = repr(value)
else:
target_kwargs[key] = repr(str(value))
def should_ignore_table(self, table: Table) -> bool:

@@ -688,6 +754,7 @@ # Support for Alembic and sqlalchemy-migrate -- never expose the schema version

for column in table.c:
try:
column.type = self.get_adapted_type(column.type)
except CompileError:
pass
if not self.keep_dialect_types:
try:
column.type = self.get_adapted_type(column.type)
except CompileError:
continue

@@ -727,2 +794,16 @@ # PostgreSQL specific fix: detect sequences from server_default

# Hack to fix Postgres DOMAIN type adaptation, broken as of SQLAlchemy 2.0.42
# For additional information - https://github.com/agronholm/sqlacodegen/issues/416#issuecomment-3417480599
if supercls is DOMAIN:
if coltype.default:
kw["default"] = coltype.default
if coltype.constraint_name is not None:
kw["constraint_name"] = coltype.constraint_name
if coltype.not_null:
kw["not_null"] = coltype.not_null
if coltype.check is not None:
kw["check"] = coltype.check
if coltype.create_type:
kw["create_type"] = coltype.create_type
try:

@@ -762,2 +843,3 @@ new_coltype = coltype.adapt(supercls)

"nobidi",
"noidsuffix",
}

@@ -1099,3 +1181,3 @@

# preceding part as the relationship name
if relationship.constraint:
if relationship.constraint and "noidsuffix" not in self.options:
is_source = relationship.source.table is relationship.constraint.table

@@ -1190,3 +1272,3 @@ if is_source or relationship.type not in (

args: list[str] = []
kwargs: dict[str, str] = {}
kwargs: dict[str, object] = {}

@@ -1217,2 +1299,6 @@ # Render constraints

# add info + dialect kwargs for dict context (__table_args__) (opt-in)
if self.include_dialect_options_and_info:
self._add_dialect_kwargs_and_info(table, kwargs, values_for_dict=True)
if kwargs:

@@ -1219,0 +1305,0 @@ formatted_kwargs = pformat(kwargs)

@@ -5,3 +5,3 @@ from __future__ import annotations

from enum import Enum, auto
from typing import Any, Union
from typing import Any

@@ -56,3 +56,3 @@ from sqlalchemy.sql.schema import Column, ForeignKeyConstraint, Table

JoinType = tuple[Model, Union[ColumnAttribute, str], Model, Union[ColumnAttribute, str]]
JoinType = tuple[Model, ColumnAttribute | str, Model, ColumnAttribute | str]

@@ -59,0 +59,0 @@

@@ -114,2 +114,176 @@ from __future__ import annotations

@pytest.mark.parametrize("generator", [["include_dialect_options"]], indirect=True)
def test_include_dialect_options_and_info_table_and_column(
generator: CodeGenerator,
) -> None:
from .test_generator_tables import _PartitionInfo
Table(
"t_opts",
generator.metadata,
Column("id", INTEGER, primary_key=True, starrocks_is_agg_key=True),
Column("name", VARCHAR, starrocks_agg_type="REPLACE"),
starrocks_aggregate_key="id",
starrocks_partition_by=_PartitionInfo("RANGE(id)"),
starrocks_security="DEFINER",
starrocks_PROPERTIES={"replication_num": "3", "storage_medium": "SSD"},
info={
"table_kind": "MATERIALIZED VIEW",
"definition": "SELECT id, name FROM t_opts_base_table",
},
)
validate_code(
generator.generate(),
"""\
from typing import Optional
from sqlalchemy import Integer, String
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase):
pass
class TOpts(Base):
__tablename__ = 't_opts'
__table_args__ = {'info': {'definition': 'SELECT id, name FROM t_opts_base_table',
'table_kind': 'MATERIALIZED VIEW'},
'starrocks_PROPERTIES': {'replication_num': '3', 'storage_medium': 'SSD'},
'starrocks_aggregate_key': 'id',
'starrocks_partition_by': 'RANGE(id)',
'starrocks_security': 'DEFINER'}
id: Mapped[int] = mapped_column(Integer, primary_key=True, starrocks_is_agg_key=True)
name: Mapped[Optional[str]] = mapped_column(String, starrocks_agg_type='REPLACE')
""",
)
@pytest.mark.parametrize("generator", [["include_dialect_options"]], indirect=True)
def test_include_dialect_options_and_info_with_hyphen(generator: CodeGenerator) -> None:
Table(
"t_opts2",
generator.metadata,
Column("id", INTEGER, primary_key=True),
mysql_engine="InnoDB",
info={"table_kind": "View"},
)
validate_code(
generator.generate(),
"""\
from sqlalchemy import Integer
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase):
pass
class TOpts2(Base):
__tablename__ = 't_opts2'
__table_args__ = {'info': {'table_kind': 'View'}, 'mysql_engine': 'InnoDB'}
id: Mapped[int] = mapped_column(Integer, primary_key=True)
""",
)
def test_include_dialect_options_not_enabled_skips(generator: CodeGenerator) -> None:
from .test_generator_tables import _PartitionInfo
Table(
"t_plain",
generator.metadata,
Column(
"id",
INTEGER,
primary_key=True,
info={"abc": True},
starrocks_is_agg_key=True,
),
starrocks_engine="OLAP",
starrocks_partition_by=_PartitionInfo("RANGE(id)"),
)
validate_code(
generator.generate(),
"""\
from sqlalchemy import Integer
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase):
pass
class TPlain(Base):
__tablename__ = 't_plain'
id: Mapped[int] = mapped_column(Integer, primary_key=True)
""",
)
def test_keep_dialect_types_adapts_mysql_integer_default(
generator: CodeGenerator,
) -> None:
from sqlalchemy.dialects.mysql import INTEGER as MYSQL_INTEGER
Table(
"num",
generator.metadata,
Column("id", INTEGER, primary_key=True),
Column("val", MYSQL_INTEGER(), nullable=False),
)
validate_code(
generator.generate(),
"""\
from sqlalchemy import Integer
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase):
pass
class Num(Base):
__tablename__ = 'num'
id: Mapped[int] = mapped_column(Integer, primary_key=True)
val: Mapped[int] = mapped_column(Integer, nullable=False)
""",
)
@pytest.mark.parametrize("generator", [["keep_dialect_types"]], indirect=True)
def test_keep_dialect_types_keeps_mysql_integer(generator: CodeGenerator) -> None:
from sqlalchemy.dialects.mysql import INTEGER as MYSQL_INTEGER
Table(
"num2",
generator.metadata,
Column("id", INTEGER, primary_key=True),
Column("val", MYSQL_INTEGER(), nullable=False),
)
validate_code(
generator.generate(),
"""\
from sqlalchemy import INTEGER
from sqlalchemy.dialects.mysql import INTEGER
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase):
pass
class Num2(Base):
__tablename__ = 'num2'
id: Mapped[int] = mapped_column(INTEGER, primary_key=True)
val: Mapped[int] = mapped_column(INTEGER, nullable=False)
""",
)
def test_onetomany(generator: CodeGenerator) -> None:

@@ -1567,2 +1741,58 @@ Table(

@pytest.mark.parametrize("generator", [["noidsuffix"]], indirect=True)
def test_named_foreign_key_constraints_with_noidsuffix(
generator: CodeGenerator,
) -> None:
Table(
"simple_items",
generator.metadata,
Column("id", INTEGER, primary_key=True),
Column("container_id", INTEGER),
ForeignKeyConstraint(
["container_id"], ["simple_containers.id"], name="foreignkeytest"
),
)
Table(
"simple_containers",
generator.metadata,
Column("id", INTEGER, primary_key=True),
)
validate_code(
generator.generate(),
"""\
from typing import Optional
from sqlalchemy import ForeignKeyConstraint, Integer
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
class Base(DeclarativeBase):
pass
class SimpleContainers(Base):
__tablename__ = 'simple_containers'
id: Mapped[int] = mapped_column(Integer, primary_key=True)
simple_items: Mapped[list['SimpleItems']] = relationship('SimpleItems', \
back_populates='simple_containers')
class SimpleItems(Base):
__tablename__ = 'simple_items'
__table_args__ = (
ForeignKeyConstraint(['container_id'], ['simple_containers.id'], \
name='foreignkeytest'),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True)
container_id: Mapped[Optional[int]] = mapped_column(Integer)
simple_containers: Mapped[Optional['SimpleContainers']] = relationship('SimpleContainers', \
back_populates='simple_items')
""",
)
# @pytest.mark.xfail(strict=True)

@@ -1569,0 +1799,0 @@ def test_colname_import_conflict(generator: CodeGenerator) -> None:

@@ -8,3 +8,4 @@ from __future__ import annotations

from sqlalchemy import TypeDecorator
from sqlalchemy.dialects import mysql, postgresql
from sqlalchemy.dialects import mysql, postgresql, registry
from sqlalchemy.dialects.mysql.pymysql import MySQLDialect_pymysql
from sqlalchemy.engine import Engine

@@ -1094,1 +1095,83 @@ from sqlalchemy.schema import (

)
class MockStarRocksDialect(MySQLDialect_pymysql):
name = "starrocks"
construct_arguments = [
(
Column,
{
"is_agg_key": None,
"agg_type": None,
"IS_AGG_KEY": None,
"AGG_TYPE": None,
},
),
(
Table,
{
"primary_key": None,
"aggregate_key": None,
"unique_key": None,
"duplicate_key": None,
"engine": "OLAP",
"partition_by": None,
"order_by": None,
"security": None,
"properties": {},
"ENGINE": "OLAP",
"PARTITION_BY": None,
"ORDER_BY": None,
"SECURITY": None,
"PROPERTIES": {},
},
),
]
# Register StarRocksDialect
registry.register("starrocks", __name__, "MockStarRocksDialect")
class _PartitionInfo:
def __init__(self, partition_by: str) -> None:
self.partition_by = partition_by
def __str__(self) -> str:
return self.partition_by
def __repr__(self) -> str:
return repr(self.partition_by)
@pytest.mark.parametrize("generator", [["include_dialect_options"]], indirect=True)
def test_include_dialect_options_starrocks_tables(generator: CodeGenerator) -> None:
Table(
"t_starrocks",
generator.metadata,
Column("id", INTEGER, primary_key=True, starrocks_is_agg_key=True),
starrocks_ENGINE="OLAP",
starrocks_PARTITION_BY=_PartitionInfo("RANGE(id)"),
starrocks_ORDER_BY="id, name",
starrocks_PROPERTIES={"replication_num": "3", "storage_medium": "SSD"},
).info = {"table_kind": "TABLE"}
validate_code(
generator.generate(),
"""\
from sqlalchemy import Column, Integer, MetaData, Table
metadata = MetaData()
t_t_starrocks = Table(
't_starrocks', metadata,
Column('id', Integer, primary_key=True, starrocks_is_agg_key=True),
info={'table_kind': 'TABLE'},
starrocks_ENGINE='OLAP',
starrocks_ORDER_BY='id, name',
starrocks_PARTITION_BY='RANGE(id)',
starrocks_PROPERTIES={'replication_num': '3', 'storage_medium': 'SSD'}
)
""",
)