Of course! Using an ORM (Object-Relational Mapper) with SQLite in Python is a fantastic way to work with databases. It allows you to interact with your database using Python objects and methods instead of writing raw SQL queries, which makes your code more readable, maintainable, and less error-prone.

Here's a comprehensive guide covering the most popular Python SQLite ORMs, from the built-in standard library to powerful third-party options.
The Standard Library: sqlite3 (No ORM, but the Foundation)
Before we dive into ORMs, it's important to know that Python has a built-in module for SQLite. It's not an ORM, but it's the foundation upon which all other tools are built. You interact with it by writing raw SQL.
Why use it?
- It's built-in, no installation needed.
- Excellent for simple scripts, small projects, or when you need maximum performance and control over the exact SQL being executed.
Example:

import sqlite3
# 1. Connect to a database (it will be created if it doesn't exist)
conn = sqlite3.connect('example.db')
cursor = conn.cursor()
# 2. Create a table
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE
)
''')
# 3. Insert data
cursor.execute("INSERT INTO users (name, email) VALUES (?, ?)", ('Alice', 'alice@example.com'))
# Use ? placeholders to prevent SQL injection
# 4. Query data
cursor.execute("SELECT * FROM users")
users = cursor.fetchall()
for user in users:
print(user)
# 5. Commit the changes and close the connection
conn.commit()
conn.close()
The Modern Favorite: SQLAlchemy
SQLAlchemy is the most popular and powerful ORM for Python. It's not just an ORM; it's a complete SQL toolkit and Object-Relational Mapping system. It provides a high-level abstraction over the database while still allowing you to fall back to raw SQL when needed.
Why use it?
- Powerful & Flexible: Maps complex object models to database schemas.
- Database Agnostic: Write your code once, and it can work with SQLite, PostgreSQL, MySQL, etc., with minimal changes.
- Two Styles:
- ORM (The "Object" way): Define Python classes that map to tables. You interact with objects.
- Core (The "SQL Expression" way): A Pythonic way to build SQL queries programmatically without writing raw strings.
- Ecosystem: Huge community, extensive documentation, and integrates with many other frameworks (like FastAPI and Flask).
Installation
pip install SQLAlchemy
Example: SQLAlchemy ORM
This is the most common way to use SQLAlchemy.
import sqlalchemy
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
# 1. Setup
# The engine is the core interface to the database.
# 'sqlite:///filename.db' is the connection string for SQLite.
engine = create_engine('sqlite:///users_orm.db', echo=True) # echo=True prints generated SQL
# The Base is a class our mapped classes will inherit from.
Base = declarative_base()
# 2. Define the Mapped Class (our "ORM Model")
class User(Base):
__tablename__ = 'users' # The name of the table in the database
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
email = Column(String, unique=True, nullable=False)
def __repr__(self):
return f"<User(name='{self.name}', email='{self.email}')>"
# 3. Create the table in the database (if it doesn't exist)
Base.metadata.create_all(engine)
# 4. Create a Session
# A Session manages all conversations with the database.
Session = sessionmaker(bind=engine)
session = Session()
# 5. Use the ORM to interact with the data
# CREATE - Add new users
user1 = User(name='Bob', email='bob@example.com')
user2 = User(name='Charlie', email='charlie@example.com')
session.add(user1)
session.add(user2)
session.commit() # Commit the transaction
# READ - Query for users
print("\n--- All Users ---")
all_users = session.query(User).all()
for user in all_users:
print(user)
# READ - Query a specific user
print("\n--- User with ID 1 ---")
bob = session.query(User).filter_by(name='Bob').first()
print(bob)
# UPDATE - Change a user's email
charlie = session.query(User).filter_by(name='Charlie').first()
if charlie:
charlie.email = 'charlie.new@example.com'
session.commit() # Commit the change
# DELETE - Remove a user
user_to_delete = session.query(User).filter_by(name='Alice').first() # Assuming Alice was there
if user_to_delete:
session.delete(user_to_delete)
session.commit()
# Clean up
session.close()
The Simpler Alternative: Peewee
Peewee is a lightweight and simple ORM. It's much easier to get started with than SQLAlchemy and has a very clean, intuitive API. It's a great choice for smaller projects or applications where you don't need the full power of a massive toolkit.

Why use it?
- Simple & Easy to Learn: The API is straightforward and feels very "Pythonic".
- Lightweight: It has fewer dependencies and a smaller footprint.
- Fast: Good performance for most use cases.
Installation
pip install peewee
Example: Peewee ORM
import peewee as pw
from playhouse.sqlite_ext import SqliteExtDatabase # Use for advanced SQLite features
# 1. Setup
# Define the database connection
db = SqliteExtDatabase('users_peewee.db')
# 2. Define the Model (our "ORM Model")
class User(pw.Model):
name = pw.CharField() # CharField is for strings
email = pw.CharField(unique=True)
class Meta:
database = db # This model uses the 'db' database
# 3. Create the table (and the database file)
db.connect()
db.create_tables([User])
# 4. Use the ORM to interact with the data
# CREATE - Add new users
user1 = User.create(name='David', email='david@example.com')
user2 = User.create(name='Eve', email='eve@example.com')
print(f"Created user with ID: {user1.id}")
# READ - Query for users
print("\n--- All Users ---")
all_users = User.select()
for user in all_users:
print(user.name, user.email)
# READ - Query a specific user
print("\n--- User with name 'David' ---")
david = User.get(User.name == 'David')
print(david)
# UPDATE - Change a user's email
eve = User.get(User.name == 'Eve')
eve.email = 'eve.new@example.com'
eve.save() # Save the changes
# DELETE - Remove a user
david.delete_instance() # Deletes the user from the database
# Clean up
db.close()
The Async-Focused Option: Tortoise ORM
Tortoise ORM is a modern, async-first ORM. If you are building an asynchronous application using a framework like FastAPI, Sanic, or Starlette, Tortoise ORM is the perfect choice. It uses async/await syntax throughout.
Why use it?
- Asynchronous: Designed from the ground up for
asyncio. - Modern API: Clean and intuitive, similar to Peewee.
- Auto-Migrations: Can automatically generate and run schema migrations.
Installation
pip install tortoise-orm
Example: Tortoise ORM
import asyncio
from tortoise import Tortoise, fields
from tortoise.models import Model
from tortoise import run_async
# 1. Define the Model
class User(Model):
id = fields.IntField(pk=True) # pk=True makes it the primary key
name = fields.CharField(max_length=255)
email = fields.CharField(max_length=255, unique=True)
class Meta:
table = "users_async" # The table name in the database
def __str__(self):
return self.name
# 2. Initialize the database connection
async def init_db():
await Tortoise.init(
db_url='sqlite://users_tortoise.db',
modules={'models': ['__main__']} # __main__ because the model is defined here
)
# Generate the schema
await Tortoise.generate_schemas()
# 3. Main async function to run our logic
async def main():
await init_db()
# CREATE - Add new users
await User.create(name='Frank', email='frank@example.com')
await User.create(name='Grace', email='grace@example.com')
# READ - Query for users
print("\n--- All Users ---")
users = await User.all()
for user in users:
print(user)
# READ - Query a specific user
print("\n--- User with name 'Frank' ---")
frank = await User.get(name='Frank')
print(frank)
# UPDATE - Change a user's email
grace = await User.get(name='Grace')
grace.email = 'grace.new@example.com'
await grace.save()
# DELETE - Remove a user
await frank.delete()
# Close the connections
await Tortoise.close_connections()
# Run the async main function
run_async(main())
Summary & Comparison
| Feature | sqlite3 (Standard) |
SQLAlchemy | Peewee | Tortoise ORM |
|---|---|---|---|---|
| Type | DB-API Driver | Full SQL Toolkit & ORM | Lightweight ORM | Async ORM |
| Ease of Use | Low (requires SQL knowledge) | Medium to High | High | High |
| Learning Curve | Low (for SQL) | Steep | Gentle | Gentle (for async) |
| Best For | Simple scripts, direct SQL control, maximum performance. | Complex applications, database-agnostic code, large projects. | Smaller projects, quick prototypes, developers who prefer simplicity. | Asynchronous web applications (FastAPI, etc.). |
| Async Support | No (sync-only) | Yes (with asyncio engine) |
Yes (with aiopg/aiosqlite) |
Yes (Built-in & First-Class) |
| Schema Mgmt. | Manual | Manual or with Alembic | Manual | Built-in generate_schemas |
Recommendation
- For Beginners or Simple Scripts: Start with
sqlite3. It's the foundation and good for learning SQL. - For Most Projects: SQLAlchemy is the industry standard. It's powerful, flexible, and will serve you well as your application grows.
- For Simplicity and Speed: Peewee is an excellent choice. If you find SQLAlchemy overwhelming, Peewee will feel like a breath of fresh air.
- For Async Web Apps: Tortoise ORM is the clear winner. It's designed for
asyncioand integrates perfectly with modern async frameworks.
