sqlalchemy-utils review
SQLAlchemy-Utils 0.42.1 is a collection of add-ons for SQLAlchemy rather than another ORM. It supplies custom column types for choices, emails, URLs, phones, passwords, encryption, colors, ranges, UUIDs, locales, and search vectors; database create and inspect helpers; mapper inspection; coercion listeners; timestamp and repr mixins; aggregate attributes; generic relationships; and database views. Version 0.42.1 fixes an AttributeError in the instant-defaults listener when a column uses a Sequence default. Our Python 3.12 install imported successfully, but it took 2.09 seconds and the distribution does not publish py.typed.
SQLAlchemy-Utils 0.42.1 installed in 0.4 seconds as 4 packages using 17 MB, imported in 2.09 seconds, and had 0 audit findings in our sandbox. Install it when several of its types and ORM helpers earn their maintenance cost; write one local SQLAlchemy helper when that is all you need.
We installed it
| Install | ✓ · 0.4s | 4 packages on disk · 17 MB |
| Import | ✓ | import sqlalchemy_utils in 2.09s · pure Python · requires Python >=3.9 |
| Known vulns | 0 | (pip-audit) |
Answers from our run
Does sqlalchemy-utils install cleanly?
Yes. In a fresh container with an empty cache, pip install sqlalchemy-utils finished in 0.4s, leaving 4 packages and 17 MB on disk. pip-audit reported no known vulnerabilities.
What does sqlalchemy-utils need to run?
Python >=3.9, and nothing compiled: it is pure Python. In our run import sqlalchemy_utils succeeded in 2.09s.
sqlalchemy-utils or sqlalchemy: which should you use?
sqlalchemy: Choose built-ins plus a local TypeDecorator when only one small utility is missing. SQLAlchemy-Utils 0.42.1 installed in 0.4 seconds as 4 packages using 17 MB, imported in 2.09 seconds, and had 0 audit findings in our sandbox.
When should you not use sqlalchemy-utils?
Only 1 helper is needed. Depending on a wide ORM utility layer can cost more upgrade testing than a small local SQLAlchemy function.
Use it if
- Several models need the package's value-object types rather than one local TypeDecorator.
- Test provisioning needs database_exists, create_database, and drop_database across supported SQL backends.
- Mapper inspection, change detection, ordering, aggregates, generic relationships, or view helpers would remove repeated internal code.
- The team accepts explicit optional extras and understands process-wide SQLAlchemy event listeners.
- Only 1 helper is needed. Depending on a wide ORM utility layer can cost more upgrade testing than a small local SQLAlchemy function.
- The application is async-only. Database administration helpers create synchronous engines, and many listeners and examples assume the synchronous ORM.
- Database constraints must enforce every domain rule. Several custom types serialize to ordinary text or binary columns and rely on Python-side coercion.
- Static typing requires a py.typed distribution. Our package inspection found no marker, so strict consumers may need stubs or targeted ignores.
- You plan to copy an old EncryptedType example. Current docs deprecate it in favor of StringEncryptedType, which still requires deliberate key storage and rotation.
Setup reality
We installed SQLAlchemy-Utils 0.42.1 in a fresh Python 3.12 Bookworm sandbox. Installation took 0.4 seconds, left 4 packages, and used 17 MB. The pure-Python distribution reports 51 direct dependency entries across base and extras, requires Python 3.9 or newer, and imported in 2.09 seconds. pip-audit found 0 known vulnerabilities. The package does not ship py.typed, and its published license metadata is unknown.
The base dependency is SQLAlchemy 1.4 or newer; richer types need named extras. PasswordType needs passlib, URLType needs furl, PhoneNumberType needs phonenumbers, ColorType needs colour, encrypted strings need cryptography, and Arrow, Pendulum, Babel, intervals, and timezone helpers each add their own package. Declare the exact extras in application dependencies. Otherwise a model may import but fail when a type is constructed or return a plain value where code expected a richer object.
Automatic coercion is opt-in through force_auto_coercion and must be registered before mapper configuration. Without a mapper argument it installs listeners for every SQLAlchemy mapper in the process, which affects unrelated model sets and tests. force_instant_defaults has a similar event-driven shape; 0.42.1 specifically repairs Sequence default handling in that listener. Call setup once during application initialization, then test ordinary assignments, bulk operations, and mapper startup under the pinned SQLAlchemy version.
Custom types commonly compile to String, Unicode, Text, or binary storage, so review Alembic output and add database constraints where another writer can bypass Python validation. Password hash upgrades can occur during comparison and need a scheme policy. StringEncryptedType needs durable keys and does not make encrypted values searchable. create_database and drop_database open synchronous administrative connections; restrict them to provisioning code, verify the exact URL, and never expose drop_database on an application request path.
Patterns
Check for an existing database check-database
from sqlalchemy_utils import database_exists
if not database_exists(database_url):
raise RuntimeError('database is missing')The helper creates a synchronous engine and needs the target driver plus metadata access; keep credential-bearing URLs out of logs.
Provision an isolated test database create-database
from sqlalchemy_utils import create_database, database_exists
if not database_exists(test_url):
create_database(test_url, encoding='utf8')Administrative permission is required, and concurrent workers can race the existence check; use unique names or serialize provisioning.
Remove a disposable test database drop-database
from sqlalchemy_utils import database_exists, drop_database
assert test_url.database.startswith('test_')
if database_exists(test_url):
drop_database(test_url)drop_database is destructive and backend-specific. Validate the resolved database name before the call and keep it outside request code.
Map an Enum through ChoiceType store-choice
from enum import Enum
import sqlalchemy as sa
from sqlalchemy_utils import ChoiceType
class Role(Enum):
admin = 'admin'
member = 'member'
role = sa.Column(ChoiceType(Role, impl=sa.String(20)), nullable=False)ChoiceType stores the Enum value through String(20); add a database CHECK or native enum when other writers must obey the same choices.
Use the package email type normalize-email
import sqlalchemy as sa
from sqlalchemy_utils import EmailType
email = sa.Column(EmailType, nullable=False, unique=True)EmailType lowercases values but does not confirm deliverability; database collation still decides how the unique index compares text.
Expose URL values through furl coerce-url
import sqlalchemy as sa
from sqlalchemy_utils import URLType, force_auto_coercion
force_auto_coercion()
website = sa.Column(URLType)Install sqlalchemy-utils[url] and register coercion before mappers are configured; the base package alone does not provide furl objects.
Hash and verify an assigned password hash-password
import sqlalchemy as sa
from sqlalchemy_utils import PasswordType, force_auto_coercion
force_auto_coercion()
password = sa.Column(PasswordType(schemes=['pbkdf2_sha512']), nullable=False)
user.password = plaintext
assert user.password == plaintextInstall sqlalchemy-utils[password]. Scheme choice, work factor, migration, and secret logging still belong to the application's password policy.
Register assignment coercion once enable-coercion
from sqlalchemy_utils import force_auto_coercion
force_auto_coercion()
# Import mapped models after listener registration.With no mapper argument the listener applies to every mapper in the process, so register it during startup rather than per request.
Add ORM-managed timestamps add-timestamps
import sqlalchemy as sa
from sqlalchemy_utils import Timestamp
class Article(Timestamp, Base):
__tablename__ = 'article'
id = sa.Column(sa.Integer, primary_key=True)Timestamp updates through ORM events; bulk SQL and external writers can bypass those events and need database-side timestamp policy.
Treat user wildcards as literal text escape-like
from sqlalchemy import select
from sqlalchemy_utils import escape_like
needle = escape_like(user_text)
stmt = select(User).where(User.name.ilike(f'%{needle}%', escape='*'))Use the same escape character in the LIKE expression that escape_like expects, or the `%` and `_` characters regain wildcard meaning.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| sqlalchemy | PyPI | Choose built-ins plus a local TypeDecorator when only one small utility is missing. |
| sqlmodel | PyPI | Choose it when Pydantic-style typed models should sit directly on SQLAlchemy. |
| sqlalchemy-mixins | PyPI | Choose it when active-record CRUD, serialization, smart queries, and model mixins are the main requirement. |
More data guides
numpy · fsspec · pandas · sqlalchemy · pyarrow · lxml · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

