You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
SQLModel fails to create DB constraints when using Annotated types after Pydantic 2.12.0 #1623
Originally posted by MarishLakshmanan October 8, 2025
First Check
I added a very descriptive title here.
I used the GitHub search to find a similar question and didn't find it.
I searched the SQLModel documentation, with the integrated search.
I already searched in Google "How to X in SQLModel" and didn't find any information.
I already read and followed all the tutorial in the docs and didn't find an answer.
I already checked if it is not related to SQLModel but to Pydantic.
I already checked if it is not related to SQLModel but to SQLAlchemy.
Commit to Help
I commit to help with one of those options 👆
Example Code
Environment:
Pydantic==2.12.0
Code:
frompydanticimportStringConstraintsNonEmptyString=Annotated[str, StringConstraints(strip_whitespace=True, min_length=1)]
ClassTest(SQLModel):
id: NonEmptyString=Field(primary_key=True) # this was working fine on pydantic v2.11.9
Description
Description
After upgrading to Pydantic 2.12.0, SQLModel no longer creates database constraints (like primary keys, unqiue keys ) for fields defined using Annotated types.
I think I just ran into this issue too, but in relation to timestamps with timezone:
fromdatetimeimportdatetimeasdtfromdatetimeimporttimezoneastzfromtyping_extensionsimportAnnotatedfromsqlmodelimportTIMESTAMP, Field, SQLModelfrompydanticimportBeforeValidatorTIMESTAMP_WITH_TZ: type=TIMESTAMP(timezone=True)
IntDatetime=Annotated[
dt,
BeforeValidator(lambdax: xifisinstance(x, dt) elsedt.fromtimestamp(int(x) /1000, tz=tz.utc)),
]
# This model works with both 2.11.10 and 2.12.0classMessage(SQLModel, table=True):
time: dt=Field(sa_type=TIMESTAMP_WITH_TZ)
# This model works with <=2.11.10, but 2.12.0 generates an error# `can't subtract offset-naive and offset-aware datetimes`# As the SQL generated is $1::TIMESTAMP WITHOUT TIME ZONEclassMessage(SQLModel, table=True):
time: IntDatetime=Field(sa_type=TIMESTAMP_WITH_TZ)
I wanted to share this example which can also be used as a test.
With Pydantic 2.12 it prints an empty set while it should print: {ForeignKey('vehicle.id')}
frompydanticimportPositiveIntfromsqlmodelimportField, Session, SQLModel, col, selectclassVehicle(SQLModel, table=True):
name: strid: PositiveInt|None=Field(default=None, primary_key=True)
classDrivers_Logbook(SQLModel, table=True):
# To make this example succeed, replace the next line with either# vehicle_id: int = Field(foreign_key='vehicle.id')# vehicle_id: PositiveInt | None = Field(foreign_key='vehicle.id', default=None)vehicle_id: PositiveInt=Field(foreign_key='vehicle.id')
id: PositiveInt|None=Field(default=None, primary_key=True)
drivers_logbook=SQLModel.metadata.tables['drivers_logbook']
print(drivers_logbook.foreign_keys)
I also have a version using pure SQLAlchemy and couldn't reproduce the issue there.
Ran into this. The fundamental issue appears to be that the subclass sqlmodel.main.FieldInfo is no longer respected in Pydantic 2.12+. The object being passed to get_column_from_field is of type pydantic.fields.FieldInfo, whereas in previous versions it was the sqlmodel subclass. #1603 seems like a reasonable resolution as it restores the original sqlmodel.main.FieldInfo object to pass into everything else.
I just found out I had the same problem: no issue with pydantic==2.11.9, but trouble after that. For reference, in both cases I have the same version of sqlalchemy==2.0.44 and sqlmodel==0.0.27 in a locked container.
Traceback (most recent call last):
File "/app/test.py", line 4, in <module>
class User(SQLModel, table=True):
File "/opt/venv/lib/python3.11/site-packages/sqlmodel/main.py", line 644, in __init__
DeclarativeMeta.__init__(cls, classname, bases, dict_, **kw)
File "/opt/venv/lib/python3.11/site-packages/sqlalchemy/orm/decl_api.py", line 199, in __init__
_as_declarative(reg, cls, dict_)
File "/opt/venv/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py", line 245, in _as_declarative
return _MapperConfig.setup_mapping(registry, cls, dict_, None, {})
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/venv/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py", line 326, in setup_mapping
return _ClassScanMapperConfig(
^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/venv/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py", line 581, in __init__
self._early_mapping(mapper_kw)
File "/opt/venv/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py", line 367, in _early_mapping
self.map(mapper_kw)
File "/opt/venv/lib/python3.11/site-packages/sqlalchemy/orm/decl_base.py", line 1995, in map
mapper_cls(self.cls, self.local_table, **self.mapper_args),
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<string>", line 2, in __init__
File "/opt/venv/lib/python3.11/site-packages/sqlalchemy/util/deprecations.py", line 281, in warned
return fn(*args, **kwargs) # type: ignore[no-any-return]
^^^^^^^^^^^^^^^^^^^
File "/opt/venv/lib/python3.11/site-packages/sqlalchemy/orm/mapper.py", line 866, in __init__
self._configure_pks()
File "/opt/venv/lib/python3.11/site-packages/sqlalchemy/orm/mapper.py", line 1652, in _configure_pks
raise sa_exc.ArgumentError(
sqlalchemy.exc.ArgumentError: Mapper Mapper[User(users)] could not assemble any primary key columns for mapped table 'users'
You didn't use Annotated types (unless you rebound the built in types), so this is a different issue.
It may be related to your usage of extend_existing or you're missing a default for the id column, but it doesn't belong here. You could open a new discussion.
However I hope the issue here will be resolved soon as it blocks upgrading to Python 3.14.
I receive the same error after upgrading python to 3.14 and pydantic to the new version.
pydantic 2.12.5
pydantic-core 2.41.5
pydantic-extra-types 2.11.0
pydantic-settings 2.12.0
This is the definition:
class MyModel(SQLModel, table=True):
id: UUID4 = Field(default_factory=uuid4, primary_key=True, index=True, nullable=False)
description: str = Field(default="Default Description", nullable=False)
...
I think the issue is that you mixed field and type metadata. As mentioned in the Tip box in the section of the Pydantic docs you linked, you annotated the int type with sa_type and it doesn't have any effect there.
You need to put that on the field foo, as done in your second example. I'm not sure it could be written differently with the same effect.
@KrilleGH In both cases the sa_type is applied to the FieldInfo definition. But in the first case it is injected into the model via a 'default' value of the model field (which is a little bit of a hack/abuse) and the other via metadata on the model field type. Pydantic understands both approaches, but it appears that SQLModel doesn't (yet?) understand the latter. Since SQLModel has other issues with type metadata introduced via Annotated (the discussion above) I wondered if these two issues were actually the same thing.
this fix change the FieldInfo copy logic, don't use copy.copy() to clone the FieldInfo
if type(self) is FieldInfo: # Fast-path if the instance isn't a subclass (copy.copy()relies on pickling which is slower): copied = FieldInfo.__new__(FieldInfo) for attr_name in FieldInfo.__slots__: setattr(copied, attr_name, getattr(self, attr_name
but when i check the commit date i realize it still not release
Activity
YuriiMotov commentedon Oct 27, 2025
Test taken from #1603
test_declaration_syntax_2andtest_declaration_syntax_3work well with Pydantic 2.11.10, but fail with Pydantic 2.12PRs:
Annotatedfields with Pydantic 2.12+ #1603Annotatedfields with Pydantic 2.12+ #1607Samreay commentedon Nov 5, 2025
I think I just ran into this issue too, but in relation to timestamps with timezone:
KrilleGH commentedon Nov 12, 2025
I wanted to share this example which can also be used as a test.
With Pydantic 2.12 it prints an empty set while it should print:
{ForeignKey('vehicle.id')}I also have a version using pure SQLAlchemy and couldn't reproduce the issue there.
dwreeves commentedon Nov 18, 2025
Ran into this. The fundamental issue appears to be that the subclass
sqlmodel.main.FieldInfois no longer respected in Pydantic 2.12+. The object being passed toget_column_from_fieldis of typepydantic.fields.FieldInfo, whereas in previous versions it was the sqlmodel subclass. #1603 seems like a reasonable resolution as it restores the originalsqlmodel.main.FieldInfoobject to pass into everything else.chore: 暂时锁定pydantic版本至2.11以规避sqlmodel库的bug
frgfm commentedon Nov 25, 2025
I just found out I had the same problem: no issue with
pydantic==2.11.9, but trouble after that. For reference, in both cases I have the same version ofsqlalchemy==2.0.44andsqlmodel==0.0.27in a locked container.My snippet for the regression:
the error with
pydantic==2.12.4:raffaelemancuso commentedon Dec 13, 2025
I have pinned pydantic to 2.11.9 in my
pyproject.toml:and
uv syncbut I still get this error:Any idea why?
KrilleGH commentedon Dec 14, 2025
You didn't use Annotated types (unless you rebound the built in types), so this is a different issue.
It may be related to your usage of
extend_existingor you're missing adefaultfor theidcolumn, but it doesn't belong here. You could open a new discussion.However I hope the issue here will be resolved soon as it blocks upgrading to Python 3.14.
varadinov commentedon Jan 10, 2026
I receive the same error after upgrading python to 3.14 and pydantic to the new version.
pydantic 2.12.5
pydantic-core 2.41.5
pydantic-extra-types 2.11.0
pydantic-settings 2.12.0
This is the definition:
class MyModel(SQLModel, table=True):
id: UUID4 = Field(default_factory=uuid4, primary_key=True, index=True, nullable=False)
description: str = Field(default="Default Description", nullable=False)
...
iangoldby commentedon Jan 16, 2026
Is this the same issue that prevents the 'new style' Pydantic Annotated pattern from working?
For example, if I define a field in a table model as:
then it ignores the sa_type and the column is created with a regular INT type. But if I use
then it works as expected.
KrilleGH commentedon Jan 22, 2026
I think the issue is that you mixed field and type metadata. As mentioned in the Tip box in the section of the Pydantic docs you linked, you annotated the
inttype withsa_typeand it doesn't have any effect there.You need to put that on the field
foo, as done in your second example. I'm not sure it could be written differently with the same effect.iangoldby commentedon Jan 22, 2026
@KrilleGH In both cases the sa_type is applied to the FieldInfo definition. But in the first case it is injected into the model via a 'default' value of the model field (which is a little bit of a hack/abuse) and the other via metadata on the model field type. Pydantic understands both approaches, but it appears that SQLModel doesn't (yet?) understand the latter. Since SQLModel has other issues with type metadata introduced via Annotated (the discussion above) I wondered if these two issues were actually the same thing.
FelixYe0509 commentedon Jan 28, 2026
Just use pydantic<=2.11.9
KrilleGH commentedon Jan 29, 2026
I thoght the previous text with the detailed description that was here before is much more interesting. Could you bring it back?
FelixYe0509 commentedon Jan 29, 2026
At the beginning, i thought its' because pydantic/pydantic@d3a57eb
this fix change the FieldInfo copy logic, don't use copy.copy() to clone the FieldInfo
if type(self) is FieldInfo: # Fast-path if the instance isn't a subclass (copy.copy()relies on pickling which is slower): copied = FieldInfo.__new__(FieldInfo) for attr_name in FieldInfo.__slots__: setattr(copied, attr_name, getattr(self, attr_namebut when i check the commit date i realize it still not release
so i delete what i wrote