Skip to content
GitHub

application.yaml

lexigram-sql ships three database backends. All implement DatabaseProviderProtocol from lexigram-contracts and expose the same repository, unit-of-work, and migration APIs.

BackendExtraDriverProduction-readyBest for
Postgreslexigram-sql[postgres]asyncpg>=0.29.0YesPrimary production database
MySQLlexigram-sql[mysql]aiomysql>=0.1.0YesExisting MySQL/MariaDB deployments
SQLitelexigram-sql[sqlite]aiosqlite>=0.22.1Development / testLocal dev, CI, single-user apps

The production-grade backend. Connection pooling, prepared statements, advisory locks, and full-text search (PostgresFTSQuery) are all supported.

  • Strengths: Best-in-class reliability, advisory locks for distributed coordination, rich data types, row-level security support (RowLevelSecurityPolicy).
  • Weaknesses: Requires an external Postgres server; connection overhead makes it unsuitable for ephemeral/CI workflows.
  • When to choose: Any production deployment, especially multi-service, multi-tenant, or data-intensive apps.
application.yaml
sql:
backend:
url: postgresql://user:pass@localhost:5432/mydb
pool:
min_size: 2
max_size: 20

Full MySQL/MariaDB support via aiomysql. Includes MySQLFTSQuery for full-text search.

  • Strengths: Drop-in for existing MySQL infrastructure; mature replication tooling.
  • Weaknesses: Slightly narrower feature set than Postgres (no advisory locks, no partial indexes in older versions).
  • When to choose: You already run MySQL or need MariaDB compatibility.
sql:
backend:
url: mysql://user:pass@localhost:3306/mydb

Zero-config file-based backend via aiosqlite. SQLite is the default when no URL is provided.

  • Strengths: No server process, no install — just a file path. Perfect for tests and single-user tools.
  • Weaknesses: No concurrency (WAL mode helps but doesn’t replace a real server); fewer SQL features.
  • When to choose: Unit tests (DatabaseModule.stub()), local development, embedded or single-process apps.
sql:
backend:
url: sqlite:///./dev.db # file-based

Or in code:

config = DatabaseConfig.from_url("sqlite:///:memory:")
  • I’m deploying to productionpostgres (with asyncpg). Use the postgres extra.
  • My org already uses MySQLmysql (with aiomysql). Use the mysql extra.
  • I need a test databasesqlite (with aiosqlite). No extra needed. Use DatabaseModule.stub().
  • I’m building a CLI toolsqlite. Zero install, zero config.
  • I need multiple databases → Use the backends: list (see below).

When your app talks to more than one database, configure them as named backends:

sql:
backends:
- name: primary
backend:
url: postgresql://user:pass@primary-host:5432/app
primary: true
- name: analytics
backend:
url: postgresql://user:pass@analytics-host:5432/olap

Resolve by name:

from typing import Annotated
from lexigram.contracts.data import DatabaseProviderProtocol
from lexigram.di.markers import Named
# Injected by name
class ReportService:
def __init__(
self,
primary: DatabaseProviderProtocol,
analytics: Annotated[DatabaseProviderProtocol, Named("analytics")],
):
...

Backends without primary: true must be resolved via Named(name). The primary backend also receives the unnamed binding for backward compatibility.

Use the in-memory SQLite backend for tests — no server, no file I/O:

from lexigram.sql import DatabaseModule, DatabaseConfig
stub = DatabaseModule.stub(DatabaseConfig.from_url("sqlite:///:memory:"))

Or set it via config:

config = DatabaseConfig(
backend=DatabaseBackendConfig(url="sqlite:///:memory:"),
)

All three backends are async-only via the async driver. There is no synchronous fallback. Every repository method, migration, and query runs through asyncio.

To call from sync code, use run_in_threadpool_with_context:

from lexigram.sql.context import run_in_threadpool_with_context
result = await run_in_threadpool_with_context(sync_function, arg)