Skip to content

Commit a1f22fc

Browse files
Support external python-types (#428)
* Support external python-types * Update CHANGES.rst * Fix dependencies * Workflow fixes * Test fixes * Exclude python 3.9 tests from geoalchemy2 tests and bump minimal version * Version fix * Update CHANGES.rst Co-authored-by: Alex Grönholm <alex.gronholm@nextday.fi> * PR Fixes * PR Fixes --------- Co-authored-by: Alex Grönholm <alex.gronholm@nextday.fi>
1 parent 9c7477b commit a1f22fc

4 files changed

Lines changed: 62 additions & 12 deletions

File tree

CHANGES.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
Version history
22
===============
33

4+
**3.1.1**
5+
6+
- Fallback ``NotImplemented`` errors encountered when accessing ``python_type`` for
7+
non-native types to ``typing.Any``
8+
(PR by @sheinbergon, based on work by @danplischke)
9+
410
**3.1.0**
511

612
- Type annotations for ARRAY column attributes now include the Python type of

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,15 @@ dynamic = ["version"]
4242

4343
[project.optional-dependencies]
4444
test = [
45-
"sqlacodegen[sqlmodel,pgvector]",
45+
"sqlacodegen[sqlmodel,pgvector,geoalchemy2]",
4646
"pytest >= 7.4",
4747
"coverage >= 7",
4848
"psycopg[binary]",
4949
"mysql-connector-python",
5050
]
5151
sqlmodel = ["sqlmodel >= 0.0.22"]
5252
citext = ["sqlalchemy-citext >= 1.7.0"]
53-
geoalchemy2 = ["geoalchemy2 >= 0.11.1"]
53+
geoalchemy2 = ["geoalchemy2 >= 0.17.0"]
5454
pgvector = ["pgvector >= 0.2.4"]
5555

5656
[project.entry-points."sqlacodegen.generators"]

src/sqlacodegen/generators.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1241,22 +1241,22 @@ def get_type_qualifiers() -> tuple[str, TypeEngine[Any], str]:
12411241

12421242
def render_python_type(column_type: TypeEngine[Any]) -> str:
12431243
if isinstance(column_type, DOMAIN):
1244-
python_type = column_type.data_type.python_type
1245-
else:
1246-
python_type = column_type.python_type
1247-
1248-
python_type_name = python_type.__name__
1249-
python_type_module = python_type.__module__
1250-
if python_type_module == "builtins":
1251-
return python_type_name
1244+
column_type = column_type.data_type
12521245

12531246
try:
1254-
self.add_module_import(python_type_module)
1255-
return f"{python_type_module}.{python_type_name}"
1247+
python_type = column_type.python_type
1248+
python_type_module = python_type.__module__
1249+
python_type_name = python_type.__name__
12561250
except NotImplementedError:
12571251
self.add_literal_import("typing", "Any")
12581252
return "Any"
12591253

1254+
if python_type_module == "builtins":
1255+
return python_type_name
1256+
1257+
self.add_module_import(python_type_module)
1258+
return f"{python_type_module}.{python_type_name}"
1259+
12601260
pre, col_type, post = get_type_qualifiers()
12611261
column_python_type = f"{pre}{render_python_type(col_type)}{post}"
12621262
return column_python_type

tests/test_generator_declarative.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
from __future__ import annotations
22

3+
import sys
4+
35
import pytest
46
from _pytest.fixtures import FixtureRequest
7+
from geoalchemy2 import Geography, Geometry
58
from sqlalchemy import BIGINT, PrimaryKeyConstraint
69
from sqlalchemy.dialects import postgresql
710
from sqlalchemy.dialects.postgresql import JSON, JSONB
@@ -1706,3 +1709,44 @@ class TestDomainJson(Base):
17061709
foo: Mapped[Optional[dict]] = mapped_column(DOMAIN('domain_json', {domain_type.__name__}(astext_type=Text(length=128)), not_null=False))
17071710
""",
17081711
)
1712+
1713+
1714+
@pytest.mark.skipif(
1715+
sys.version_info < (3, 10),
1716+
reason="This test assumes GeoAlchemy2 0.18.x and above, which does not support python 3.9",
1717+
)
1718+
@pytest.mark.parametrize("engine", ["postgresql"], indirect=["engine"])
1719+
def test_geoalchemy2_types(generator: CodeGenerator) -> None:
1720+
Table(
1721+
"spatial_table",
1722+
generator.metadata,
1723+
Column("id", INTEGER, primary_key=True),
1724+
Column("geom", Geometry("POINT", srid=4326, dimension=2), nullable=False),
1725+
Column("geog", Geography("POLYGON", dimension=2)),
1726+
)
1727+
1728+
validate_code(
1729+
generator.generate(),
1730+
"""\
1731+
from typing import Any, Optional
1732+
1733+
from geoalchemy2.types import Geography, Geometry
1734+
from sqlalchemy import Index, Integer
1735+
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
1736+
1737+
class Base(DeclarativeBase):
1738+
pass
1739+
1740+
1741+
class SpatialTable(Base):
1742+
__tablename__ = 'spatial_table'
1743+
__table_args__ = (
1744+
Index('idx_spatial_table_geog', 'geog'),
1745+
Index('idx_spatial_table_geom', 'geom')
1746+
)
1747+
1748+
id: Mapped[int] = mapped_column(Integer, primary_key=True)
1749+
geom: Mapped[Any] = mapped_column(Geometry('POINT', 4326, 2, from_text='ST_GeomFromEWKT', name='geometry', nullable=False), nullable=False)
1750+
geog: Mapped[Optional[Any]] = mapped_column(Geography('POLYGON', dimension=2, from_text='ST_GeogFromText', name='geography'))
1751+
""",
1752+
)

0 commit comments

Comments
 (0)