Repository navigation
Can you override what SQLModel does to convert from the Python type to the SQLAlchemy type? #2117
First Check
Example Codefrom ipaddress import IPv4Address, IPv6Address
from pydantic.networks import IPvAnyAddress
from sqlmodel import Field, SQLModel
from sqlmodel.sql.sqltypes import AutoString
# This model won't work as-is, you'll need to pick just one of the ip_ fields,
# and for this issue, you probably want ip_masquerade.
class ModelWithIpAddress(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
# this works - the IP address is converted to a string (addr.exploded)
# when you INSERT an instance of this model (session.add) into a database
ipv4_working: IPv4Address = Field()
# v6 should also work:
ipv6_working: IPv6Address = Field()
# neither of these work, for different reasons:
# "ValueError: <class 'pydantic.networks.IPvAnyAddress'> has no matching
# SQLAlchemy type"
ip_v_any: IPvAnyAddress = Field()
# "ValueError: Cannot have a (non-optional) union as a SQLAlchemy field"
ip_union: IPv4Address | IPv6Address = Field()
# now, if I override the sa_type to AutoString, the model can be created...
ip_masquerade: IPvAnyAddress = Field(sa_type=AutoString)
# ...but when I try to INSERT this into the database, I get the following:
#
# DBAPIError: <class 'asyncpg.exceptions.DataError'>: invalid input for
# query argument $6: IPv6Address('xxxx::xxxx:xxxx:xxxx:xxxx') (expected str,
# got IPv6Address)
#
# so, is it possible to override how a field is converted before being
# INSERTed into the database?DescriptionReplicating
QuestionIs it possible to override how a given field is converted to (for example) an SQLModel seems to already do this for However, if you use I can work around this by specifying the field type as Is this currently possible in SQLModel? Operating SystemmacOS Operating System DetailsmacOS Golden Gate 27.0.1, aarch64 Project VersionSQLModel 0.0.39 Python VersionPython 3.14.3 Additional ContextIf this is resolved in a newer version of SQLModel, I am able to update to a newer version. |
Replies: 1 comment 1 reply
|
Yes, with a SQLAlchemy A decorator that does the conversion in both directions: from ipaddress import IPv4Address, IPv6Address, ip_address
from pydantic.networks import IPvAnyAddress
from sqlalchemy.types import TypeDecorator
from sqlmodel import Field, SQLModel
from sqlmodel.sql.sqltypes import AutoString
class IPAddressString(TypeDecorator):
impl = AutoString
cache_ok = True
def process_bind_param(self, value, dialect):
return None if value is None else str(value)
def process_result_value(self, value, dialect):
return None if value is None else ip_address(value)
class Host(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
ip_any: IPvAnyAddress = Field(sa_type=IPAddressString)
ip_union: IPv4Address | IPv6Address = Field(sa_type=IPAddressString)
Run with sqlmodel 0.0.39, SQLAlchemy 2.0.55, pydantic 2.14.1, Python 3.12, SQLite in memory. Your The model above, one row built from objects and one through I did not run it against PostgreSQL with asyncpg. The bind step happens before the driver sees the value, so asyncpg should get a |
Yes, with a SQLAlchemy
TypeDecoratorpassed assa_type. SQLModel never converts the value itself. ForIPv4Address/IPv6Addressit only picksAutoStringas the column type (get_sqlalchemy_type), andAutoStringhas no bind step, so theipaddressobject goes to the driver as is. Whether that works then depends on the driver. On SQLite, a plainip: IPv4Addressfield fails the same way once the attribute holds anIPv4Addressobject:A decorator that does the conversion in both directions: