data-science

SQLAlchemy 1.4 Async: A Practical Guide for Python Developers

If you've built a Python web application that talks to a database, you've likely used SQLAlchemy. For years, it ran everything synchronously—your code waited, doing nothing, until the database responded. The workaround was ThreadPoolExecutor: hand off queries to a thread pool, let them run in parallel, continue with your main code. It worked, but added complexity.

SQLAlchemy 1.4 changes this. The release adds first-class asyncio support, meaning you can now write database code that pauses while waiting for results, then picks up exactly where it left off. No threads required.

When to Use Expression Language vs. ORM

Both approaches now work with async. Here's how to choose:

Scenario Expression Language ORM with session.run_sync
Raw SQL performance needed ✓ Direct control —
Session cache benefits — ✓ Object tracking
Complex object graphs — ✓ Relationship handling
Simple CRUD operations ✓ Minimal overhead ✓
Learning curve Lower Higher

The clever part: your model class can declare `__table__` metadata, letting the same class work in both worlds. Need raw speed? Use the expression language. Want ORM conveniences? Use the model. The choice becomes situational rather than architectural.

How It Works

With the expression language, you write queries that resemble SQL—joins, where clauses, order_by. The new Session is context-aware: it opens when you enter the context and closes automatically when you leave. This makes resource management cleaner than manual cleanup.

With the ORM approach, your model classes know about their supported views. The query syntax is more compact. The catch: you need to handle select options for loading strategies, and there's no `await` keyword in your code. Instead, SQLAlchemy 1.4 uses `session.run_sync`, a greenlet abstraction that lets you treat async models as if they were synchronous.

The sheer effort to unify these two paradigms is noteworthy—the library achieves something impressive under the hood.

Testing Async Code

Testing requires pytest.asyncio. Add this to your setup.cfg:

“`

[tool:pytest]

Asyncio_mode = strict

“`

Without this configuration, pytest warns about deprecated behavior. For database migrations, Alembic has an async template that works with the new session factory. In production, configuration typically comes from environment variables; in tests, it often comes from setup.cfg.

Why It Matters

The wins:** Simpler concurrent code without ThreadPoolExecutor overhead. Cleaner resource management. The ability to mix approaches within the same codebase.

The trade-offs:** The documentation remains fragmented—developers report difficulty navigating async-specific features. If you're using the ORM path, greenlet behavior can be confusing. Configuration scattered across multiple files (setup.cfg, pyproject.toml, alembic.ini) adds setup complexity.

Who benefits most:** New projects starting with Python 3.7+ can build async-first from day one. Existing projects can migrate gradually, keeping synchronous code intact while adding async components.

What to watch:** Some backends (the reference mentions MySQL and SQLite) don't support `RETURNING` clauses, requiring separate insert-and-select patterns. Monitor whether documentation improves in subsequent releases—the underlying implementation works, but the learning curve remains steep.

Key takeaways

  • SQLAlchemy 1.4 eliminates ThreadPoolExecutor for async database operations
  • Expression language and ORM now coexist in the same model via `__table__` metadata
  • Context-aware Sessions handle resource cleanup automatically
  • Testing requires pytest.asyncio with `asyncio_mode = strict` configured
  • Documentation gaps persist, but the implementation is solid

Frequently asked questions

What is asyncio in Python?

Asyncio is Python's standard library for writing concurrent code using async/await syntax. It allows code to pause and resume, handling multiple operations without using multiple threads.

Does SQLAlchemy 1.4 support both async and sync code?

Yes. The release lets you write async queries while maintaining compatibility with traditional synchronous code. You can choose based on your application's needs.

Do I need to rewrite existing SQLAlchemy code to use 1.4?

No. Existing synchronous code continues to work. The async features are additive, so you can adopt them gradually or not at all.

What databases support SQLAlchemy 1.4 async?

Any database with an asyncio-compatible driver should work. MySQL and SQLite have been tested with this approach.

Is the documentation complete for SQLAlchemy 1.4 async features?

The documentation remains somewhat fragmented and difficult to navigate for async-specific features, though the core implementation works well. Expect a learning curve.

Related