Releases: ormar-orm/ormar
Fix critical vulnerability, drop Python 3.9, bugfixes
0.23.0
‼️ 🚨 Critical vulnerability fixed – please upgrade ASAP
- In this version of ormar the critical vulnerability (
CVE-2026-26198) in aggregate functions was patched - thanks @AAtomical
for reporting. The vulnerability was caused by the way ormar generated SQL queries for aggregate functions, allowing arbitrary SQL execution through user input. - Affected versions:
0.9.9 - 0.12.20.20.0b1 - 0.22.0 (latest)
✨ Breaking changes
- Drop support for Python 3.9
🐛 Fixes
- Fix selecting data with nested models with json fields #1530
- Fix prefetching JSON list field throwing TypeError - thanks @jannyware-inc #1402
Drop databases package and use sqlalchemy async instead
0.22.0
🐛 Breaking changes
-
Migration from
databaseslibrary to native async SQLAlchemyVersion 0.22.0 migrates from the
databaseslibrary to native async SQLAlchemy using ormar'sDatabaseConnectionwrapper. This provides better integration with SQLAlchemy's async ecosystem and improved transaction handling and avoid dependency on archiveddatabaseslibrary. -
Import changes
Replace
databasesimport withDatabaseConnectionfrom ormar:# ormar < 0.22 import databases database = databases.Database("sqlite:///db.sqlite") # ormar >= 0.22 from ormar import DatabaseConnection database = DatabaseConnection("sqlite+aiosqlite:///db.sqlite")
-
Database URLs require async drivers
Database connection strings must now use async-compatible drivers:
- SQLite:
sqlite+aiosqlite://(notsqlite://) - PostgreSQL:
postgresql+asyncpg://(notpostgresql://) - MySQL:
mysql+aiomysql://(notmysql://)
# ormar < 0.22 database = databases.Database("sqlite:///db.sqlite") # ormar >= 0.22 database = DatabaseConnection("sqlite+aiosqlite:///db.sqlite")
- SQLite:
-
Engine parameter removed from OrmarConfig
The
engineparameter is no longer needed inOrmarConfig- it's created internally byDatabaseConnection:# ormar < 0.22 import databases import sqlalchemy database = databases.Database("sqlite:///db.sqlite") engine = sqlalchemy.create_engine("sqlite:///db.sqlite") base_ormar_config = ormar.OrmarConfig( database=database, metadata=sqlalchemy.MetaData(), engine=engine, # <- No longer needed ) # ormar >= 0.22 from ormar import DatabaseConnection import sqlalchemy database = DatabaseConnection("sqlite+aiosqlite:///db.sqlite") base_ormar_config = ormar.OrmarConfig( database=database, metadata=sqlalchemy.MetaData(), # engine is created internally )
-
Table creation requires sync engine
When using
metadata.create_all(), you must create a separate sync engine:# ormar >= 0.22 import sqlalchemy from ormar import DatabaseConnection DATABASE_URL = "sqlite+aiosqlite:///db.sqlite" database = DatabaseConnection(DATABASE_URL) metadata = sqlalchemy.MetaData() # Create a sync engine for table creation sync_engine = sqlalchemy.create_engine( DATABASE_URL.replace('+aiosqlite', '') ) metadata.create_all(sync_engine)
✨ Features
-
Improved transaction handling
Transactions now use context variables and SQLAlchemy savepoints for better nested transaction support:
# Nested transactions with automatic savepoints async with database.transaction(): await Model1.objects.create(...) async with database.transaction(): # Uses savepoint await Model2.objects.create(...)
-
Enhanced testing support
The
force_rollbackparameter for transactions makes testing easier.
Like in databasesforce_rollbackcan also be used with DatabaseConnection directly to use one global transaction.async with database.transaction(force_rollback=True): # Your test code - will rollback even on success await Model.objects.create(...)
💬 Other
- Improved pyright support - thanks @MaximSrour - #1491
Add support for sqlalchemy 2.0 and pydantic 2.11
0.21.0
🐛 Breaking changes
- Drop support for Python 3.8
- Remove the possibility to exclude parents' fields in children models (discouraged as bad practice anyway)
- Add support for Sqlalchemy 2.0 and drop for 1.4
💬 Other
- Bump dependencies to newer versions: among others pydantic, databases and fastapi
- Move setuptools to dev dependencies
- Solve vulnerabilities in dependencies
Bug fixes and bump pydantic & python supported versions
0.20.2
🐛 Fixes
- Fix mutable default argument in translate list to dict - thanks @cadlagtrader #1382
- Fix fastapi docs - thanks @inktrap #1362
- Fix clashing many to many fields names #1407
💬 Other
- Add official support for python 3.12 - thanks @ChristopherMacGown #1395
- Unpin pydantic allowing pydantic versions <2.9.0 - thanks @camillol #1388
Fixes for prefetch related and saving related
0.20.1
✨ Breaking changes
- Note that this is the first non-beta release of ormar with support for Pydantic v2. Check release notes for 0.20.0 and https://collerek.github.io/ormar/0.20.0b1/migration/
🐛 Fixes
- Fix merging same target models when using
select_relatedwithprefetch_related#906 - Fix saving related with pk only models #812
- Fix adding the same relation multiple times corrupting relation cache #1335
✨ Features
💬 Other
- Some docs fixes by @Chaoyingz, thanks!
BETA - Support for pydantic v2
BETA
0.20.0
You can find updated documentation at: https://collerek.github.io/ormar/0.20.0b1/
✨ Breaking changes
-
ormarModel configurationInstead of defining a
Metaclass now each of the ormar models require an ormar_config parameter that is an instance of theOrmarConfigclass.
Note that the attribute must be namedormar_configand be an instance of the config class.import databases import ormar import sqlalchemy database = databases.Database("sqlite:///db.sqlite") metadata = sqlalchemy.MetaData() # ormar < 0.20 class Album(ormar.Model): class Meta: database = database metadata = metadata tablename = "albums" id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=100) favorite: bool = ormar.Boolean(default=False) # ormar >= 0.20 class AlbumV20(ormar.Model): ormar_config = ormar.OrmarConfig( database=database, metadata=metadata, tablename="albums_v20" ) id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=100) favorite: bool = ormar.Boolean(default=False)
-
OrmarConfigapi/ parametersThe
ormar_configexpose the same set of settings asMetaclass used to provide.
That means that you can use any of the following parameters initializing the config:metadata: Optional[sqlalchemy.MetaData] database: Optional[databases.Database] engine: Optional[sqlalchemy.engine.Engine] tablename: Optional[str] order_by: Optional[List[str]] abstract: bool exclude_parent_fields: Optional[List[str]] queryset_class: Type[QuerySet] extra: Extra constraints: Optional[List[ColumnCollectionConstraint]]
-
BaseMetaequivalent - best practiceNote that to reduce the duplication of code and ease of development it's still recommended to create a base config and provide each of the models with a copy.
OrmarConfig provides a convenientcopymethod for that purpose.The
copymethod accepts the same parameters asOrmarConfiginit, so you can overwrite if needed, but by default it will return already existing attributes, except for:tablename,order_byandconstraintswhich by default are cleared.import databases import ormar import sqlalchemy base_ormar_config = ormar.OrmarConfig( database=databases.Database("sqlite:///db.sqlite"), metadata=sqlalchemy.MetaData() ) class AlbumV20(ormar.Model): ormar_config = base_ormar_config.copy( tablename="albums_v20" ) id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=100) class TrackV20(ormar.Model): ormar_config = base_ormar_config.copy( tablename="tracks_v20" ) id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=100)
-
choicesField parameter is no longer supported.Before version 0.20 you could provide
choicesparameter to any existing ormar Field to limit the accepted values.
This functionality was dropped, and you should useormar.Enumfield that was designed for this purpose.
If you want to keep the database field type (i.e. an Integer field) you can always write a custom validator.import databases import ormar import sqlalchemy database = databases.Database("sqlite:///db.sqlite") metadata = sqlalchemy.MetaData() # ormar < 0.20 class Artist(ormar.Model): class Meta: database = database metadata = metadata id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=100) country: str = ormar.String(default=False, max_length=50, choices=["UK", "US", "Vietnam", "Colombia"]) # ormar >= 0.20 from enum import Enum class Country(str, Enum): UK = "UK" US = "US" VIETNAM = "Vietnam" COLOMBIA = "Colombia" class ArtistV20(ormar.Model): ormar_config = ormar.OrmarConfig( database=database, metadata=metadata, tablename="artists_v20" ) id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=100) country: Country = ormar.Enum(enum_class=Country)
-
pydantic_onlyField parameter is no longer supportedpydantic_onlyfields were already deprecated and are removed in v 0.20. Ormar allows defining pydantic fields as in ordinary pydantic model.import databases import ormar import sqlalchemy database = databases.Database("sqlite:///db.sqlite") metadata = sqlalchemy.MetaData() # ormar < 0.20 class Dish(ormar.Model): class Meta: database = database metadata = metadata tablename = "dishes" id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=100) cook: str = ormar.String(max_length=40, pydantic_only=True, default="sam") # ormar >= 0.20 class DishV20(ormar.Model): ormar_config = ormar.OrmarConfig( database=database, metadata=metadata, tablename="dishes_v20" ) id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=100) cook: str = "sam" # this is normal pydantic field
-
property_fielddecorator is no longer supportedproperty_fielddecorator was used to provide a way to pass calculated fields that were included in dictionary/ serialized json representation of the model.
Version 2.X of pydantic introduced such a possibility, so you should now switch to the one native to the pydantic.import databases import ormar import sqlalchemy import pydantic database = databases.Database("sqlite:///db.sqlite") metadata = sqlalchemy.MetaData() # ormar < 0.20 class Employee(ormar.Model): class Meta: database = database metadata = metadata id: int = ormar.Integer(primary_key=True) first_name: str = ormar.String(max_length=100) last_name: str = ormar.String(max_length=100) @ormar.property_field() def full_name(self) -> str: return f"{self.first_name} {self.last_name}" # ormar >= 0.20 class EmployeeV20(ormar.Model): ormar_config = ormar.OrmarConfig( database=database, metadata=metadata, ) id: int = ormar.Integer(primary_key=True) first_name: str = ormar.String(max_length=100) last_name: str = ormar.String(max_length=100) @pydantic.computed_field() def full_name(self) -> str: return f"{self.first_name} {self.last_name}"
-
Deprecated methods
All methods listed below are deprecated and will be removed in version 0.30 of
ormar.dict()becomes themodel_dump()
import databases import ormar import sqlalchemy database = databases.Database("sqlite:///db.sqlite") metadata = sqlalchemy.MetaData() class Album(ormar.Model): ormar_config = ormar.OrmarConfig( database=database, metadata=metadata, tablename="albums" ) id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=100) favorite: bool = ormar.Boolean(default=False) album = Album(name="Dark Side of the Moon") # ormar < 0.20 album_dict = album.dict() # ormar >= 0.20 new_album_dict = album.model_dump()
Note that parameters remain the same i.e.
include,excludeetc.-
json()becomes themodel_dump_json()import databases import ormar import sqlalchemy database = databases.Database("sqlite:///db.sqlite") metadata = sqlalchemy.MetaData() class Album(ormar.Model): ormar_config = ormar.OrmarConfig( database=database, metadata=metadata, tablename="albums" ) id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=100) favorite: bool = ormar.Boolean(default=False) album = Album(name="Dark Side of the Moon") # ormar < 0.20 album_json= album.json() # ormar >= 0.20 new_album_dict = album.model_dump_json()
Note that parameters remain the same i.e.
include,excludeetc. -
construct()becomes themodel_construct()
import databases import ormar import sqlalchemy database = databases.Database("sqlite:///db.sqlite") metadata = sqlalchemy.MetaData() class Album(ormar.Model): ormar_config = ormar.OrmarConfig( database=database, metadata=metadata, tablename="albums" ) id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=100) favorite: bool = ormar.Boolean(default=False) params = { "n...
Bump fastapi and python
Performance improvements
0.12.1
✨ Features
- Massive performance improvements in the area of loading the models due to recursive loads and caching of the models and related models. (by @erichaydel - thanks!) #853
💬 Internals
- Benchmarks for comparing the performance effect of implemented changes in regard of trends (again, by @erichaydel - thanks!) #853
Force save in upset and bug fixes
0.12.0
✨ Breaking Changes
Queryset.bulk_createwill now raiseModelListEmptyErroron empty list of models (by @ponytailer - thanks!) #853
✨ Features
Model.upsert()now handles a flag__force_save__:boolthat allow upserting the models regardless of the fact if they have primary key set or not.
Note that setting this flag will cause two queries for each upserted model ->getto check if model exists and laterupdate/insertaccordingly. #889
🐛 Fixes
- Fix for empty relations breaking
constructmethod (by @Abdeldjalil-H - thanks!) #870 - Fix save related not saving models with already set pks (including uuid) #885
- Fix for wrong relations exclusions depending on the order of exclusions #779
- Fix
property_fieldsnot being inherited properly #774
CheckColumn constraint, ReferentialAction and bug fixes
0.11.3
✨ Features
- Document
onupdateandondeletereferential actions inForeignKeyand provideReferentialActionenum to specify the behavior of the relationship (by @SepehrBazyar - thanks!) #724 - Add
CheckColumnto supported constraints in models Meta (by @SepehrBazyar - thanks!) #729
🐛 Fixes
- Fix limiting query result to 0 should return empty list (by @SepehrBazyar - thanks!) #766
💬 Other
- Add dark mode to docs (by @SepehrBazyar - thanks!) #717
- Update aiomysql dependency #778