Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 19 additions & 8 deletions src/sqlacodegen/generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
from sqlalchemy.exc import CompileError
from sqlalchemy.sql.elements import TextClause
from sqlalchemy.sql.type_api import UserDefinedType
from sqlalchemy.types import TypeEngine

from .models import (
ColumnAttribute,
Expand Down Expand Up @@ -1201,18 +1202,28 @@ def render_column_attribute(self, column_attr: ColumnAttribute) -> str:
column = column_attr.column
rendered_column = self.render_column(column, column_attr.name != column.name)

try:
python_type = column.type.python_type
def recursively_get_col_type(column_type: TypeEngine[Any]) -> str:
Comment thread
sheinbergon marked this conversation as resolved.
Outdated
if column_type.python_type == list:
Comment thread
sheinbergon marked this conversation as resolved.
Outdated
self.add_literal_import("typing", "List")
# Dimensions is how postgres handles matrices
Comment thread
sheinbergon marked this conversation as resolved.
Outdated
# See: https://docs.sqlalchemy.org/en/13/dialects/postgresql.html#sqlalchemy.dialects.postgresql.ARRAY
dim = getattr(column_type, "dimensions", None) or 1

return f"{'List[' * dim}{recursively_get_col_type(column_type.item_type)}{']' * dim}"
Comment thread
sheinbergon marked this conversation as resolved.
Outdated

python_type = column_type.python_type
python_type_name = python_type.__name__
if python_type.__module__ == "builtins":
column_python_type = python_type_name
else:
return python_type_name
try:
python_type_module = python_type.__module__
column_python_type = f"{python_type_module}.{python_type_name}"
self.add_module_import(python_type_module)
except NotImplementedError:
self.add_literal_import("typing", "Any")
column_python_type = "Any"
return f"{python_type_module}.{python_type_name}"
except NotImplementedError:
self.add_literal_import("typing", "Any")
return "Any"

column_python_type = recursively_get_col_type(column.type)

if column.nullable:
self.add_literal_import("typing", "Optional")
Expand Down
38 changes: 37 additions & 1 deletion tests/test_generator_declarative.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import pytest
from _pytest.fixtures import FixtureRequest
from sqlalchemy import PrimaryKeyConstraint
from sqlalchemy.dialects import postgresql
from sqlalchemy.engine import Engine
from sqlalchemy.schema import (
CheckConstraint,
Expand All @@ -15,7 +16,7 @@
UniqueConstraint,
)
from sqlalchemy.sql.expression import text
from sqlalchemy.types import INTEGER, VARCHAR, Text
from sqlalchemy.types import ARRAY, INTEGER, VARCHAR, Text

from sqlacodegen.generators import CodeGenerator, DeclarativeGenerator

Expand Down Expand Up @@ -1509,3 +1510,38 @@ class Simple(Base):
server_default=text("'test'"))
""",
)


def test_table_with_arrays(generator: CodeGenerator) -> None:
_ = Table(
Comment thread
agronholm marked this conversation as resolved.
Outdated
"with_items",
generator.metadata,
# Should still-handle run of the mill attributes
Comment thread
sheinbergon marked this conversation as resolved.
Outdated
Column("id", INTEGER, primary_key=True),
Column("int_items_not_optional", ARRAY(INTEGER()), nullable=False),
# Should handle postgresql matrices
Comment thread
sheinbergon marked this conversation as resolved.
Outdated
# TODO: How do the others handle multi-dimensional arrays?
Column("str_matrix", postgresql.ARRAY(VARCHAR(), dimensions=2)),
)

validate_code(
generator.generate(),
"""\
from typing import List, Optional

from sqlalchemy import ARRAY, INTEGER, Integer, VARCHAR
Comment thread
sheinbergon marked this conversation as resolved.
from sqlalchemy.dialects.postgresql import ARRAY
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column

class Base(DeclarativeBase):
pass


class WithItems(Base):
__tablename__ = 'with_items'

id: Mapped[int] = mapped_column(Integer, primary_key=True)
int_items_not_optional: Mapped[List[int]] = mapped_column(ARRAY(INTEGER()))
str_matrix: Mapped[Optional[List[List[str]]]] = mapped_column(ARRAY(VARCHAR(), dimensions=2))
""",
)