create database and engine
from sqlalchemy import create_engine, MetaData
from passlib.context import CryptContext
import databases
database = databases.Database("sqlite:///example.db")
metadata = MetaData()
...
engine = create_engine("sqlite:///example.db")
metadata.create_all(engine)
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
...
@app.on_event("startup")
async def startup():
await database.connect()
@app.on_event("shutdown")
async def shutdown():
await database.disconnect()
Table Construction
- SQLAlchemy Core: A SQL expression language toolkit. You work more directly with schema constructs like
Table,Column, and functions likeselect,insert,update,delete. This feels closer to SQL but provides Pythonic expression building and database abstraction. Methods are often associated with theTableobject or are functions that operate onTableobjects. - SQLAlchemy ORM (Object Relational Mapper): Builds on Core. You define Python classes (models) that map to
database tables. You interact with tables primarily by manipulating instances of these classes and using a
Sessionobject to coordinate database operations.
SQLAlchemy Core: Working with Table Objects
In Core, you typically define or load a Table object first.
from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String, select, insert, update, delete
# Define metadata
metadata_obj = MetaData()
# Define a Table object (or load it using autoload_with=engine)
users_table = Table(
"users",
metadata_obj,
Column("id", Integer, primary_key=True),
Column("name", String(50)),
Column("email", String(100)),
)
# Connect to a database (example using SQLite in-memory)
engine = create_engine("sqlite:///:memory:")
metadata_obj.create_all(engine) # Create the table in the database
Now, let's look at the primary ways to build SQL statements using this Table object, replacing raw SQL:
1. SELECT Queries
- SQL:
SELECT id, name FROM users WHERE name = 'Alice' ORDER BY id LIMIT 1; -
SQLAlchemy Core (using
select()function):stmt = select(users_table.c.id, users_table.c.name)\ .where(users_table.c.name == 'Alice')\ .order_by(users_table.c.id)\ .limit(1) # To execute: with engine.connect() as connection: result = connection.execute(stmt) for row in result: print(row) # Access columns like row.id, row.name or by index row[0] # Example Output: (1, 'Alice') if found- Key Constructs:
select(...): Creates a SELECT statement object. You pass the columns (users_table.c.column_name) you want.select(users_table)selects all columns (SELECT *)..where(...): Adds a WHERE clause. You use standard Python comparison operators (==,!=,>,<, etc.) withTable.c.column_name. Combine conditions withand_(),or_(),not_()..order_by(...): Adds an ORDER BY clause. Usecolumn.asc()orcolumn.desc()..limit(...): Adds a LIMIT clause..offset(...): Adds an OFFSET clause..group_by(...): Adds a GROUP BY clause..join(...): Adds a JOIN clause (e.g.,users_table.join(addresses_table, users_table.c.id == addresses_table.c.user_id)).
- Key Constructs:
2. INSERT Statements
- SQL:
INSERT INTO users (name, email) VALUES ('Bob', 'bob@example.com'); -
SQLAlchemy Core (using
insert()function orTable.insert()method):# Using the insert() function stmt = insert(users_table).values(name='Bob', email='bob@example.com') # Or using the Table.insert() method (less common in modern SQLAlchemy) # stmt = users_table.insert().values(name='Bob', email='bob@example.com') # To execute: with engine.connect() as connection: result = connection.execute(stmt) connection.commit() # Important: commit changes for INSERT/UPDATE/DELETE print(f"Inserted PK: {result.inserted_primary_key}") # Get the new primary key- Key Constructs:
insert(table): Creates an INSERT statement object..values(...): Specifies the data to insert, using keyword arguments (column names) or a list of dictionaries for multiple rows.
- Key Constructs:
3. UPDATE Statements
- SQL:
UPDATE users SET email = 'robert@example.com' WHERE name = 'Bob'; -
SQLAlchemy Core (using
update()function orTable.update()method):# Using the update() function stmt = update(users_table)\ .where(users_table.c.name == 'Bob')\ .values(email='robert@example.com') # Or using the Table.update() method # stmt = users_table.update()\ # .where(users_table.c.name == 'Bob')\ # .values(email='robert@example.com') # To execute: with engine.connect() as connection: result = connection.execute(stmt) connection.commit() # Commit changes print(f"Rows updated: {result.rowcount}")- Key Constructs:
update(table): Creates an UPDATE statement object..where(...): Specifies which rows to update (like SELECT). If omitted, all rows will be updated (usually not desired!)..values(...): Specifies the column(s) and their new value(s).
- Key Constructs:
4. DELETE Statements
- SQL:
DELETE FROM users WHERE name = 'Bob'; -
SQLAlchemy Core (using
delete()function orTable.delete()method):# Using the delete() function stmt = delete(users_table).where(users_table.c.name == 'Bob') # Or using the Table.delete() method # stmt = users_table.delete().where(users_table.c.name == 'Bob') # To execute: with engine.connect() as connection: result = connection.execute(stmt) connection.commit() # Commit changes print(f"Rows deleted: {result.rowcount}")- Key Constructs:
delete(table): Creates a DELETE statement object..where(...): Specifies which rows to delete. If omitted, all rows will be deleted (use with extreme caution!).
- Key Constructs:
SQLAlchemy ORM: Working with Mapped Classes and Sessions
In the ORM, you interact with Python objects, and the Session translates these actions into SQL.
from sqlalchemy.orm import sessionmaker, declarative_base
from sqlalchemy import create_engine, Column, Integer, String, select, update, delete # Need select, update, delete for 2.0 style ORM queries
# Define a Base class
Base = declarative_base()
# Define a mapped class (Model)
class User(Base):
__tablename__ = 'users_orm' # Optional if class name matches table name convention
id = Column(Integer, primary_key=True)
name = Column(String(50))
email = Column(String(100))
def __repr__(self):
return f"<User(id={self.id}, name='{self.name}', email='{self.email}')>"
# Engine and create table
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
# Create a Session factory
Session = sessionmaker(bind=engine)
1. SELECT Queries (Fetching Objects)
- SQL:
SELECT id, name, email FROM users_orm WHERE name = 'Charlie'; -
SQLAlchemy ORM:
with Session() as session: # --- Modern (2.0) Style --- stmt = select(User).where(User.name == 'Charlie') results = session.scalars(stmt).all() # scalars() gets first column (the User object) # Or: results = session.execute(stmt).scalars().all() # --- Older (1.x) Style (still works but 2.0 is preferred) --- # results = session.query(User).filter(User.name == 'Charlie').all() for user in results: print(user) # Example Output: <User(id=1, name='Charlie', email='charlie@example.com')>
2. INSERT Statements (Adding Objects)
- SQL:
INSERT INTO users_orm (name, email) VALUES ('David', 'david@example.com'); - SQLAlchemy ORM:
3. UPDATE Statements (Modifying Objects)
- SQL:
UPDATE users_orm SET email = 'dave@example.com' WHERE name = 'David'; -
SQLAlchemy ORM:
with Session() as session: # --- Option 1: Fetch, Modify, Commit --- user_to_update = session.scalars(select(User).where(User.name == 'David')).first() # Or: user_to_update = session.query(User).filter(User.name == 'David').first() if user_to_update: user_to_update.email = 'dave@example.com' session.commit() # The Session tracks the change and issues an UPDATE print("User updated via object modification.") else: print("User not found.") # --- Option 2: Bulk Update (Modern 2.0 Style) --- # Efficient for updating multiple rows without loading them into memory stmt = update(User)\ .where(User.name == 'David')\ .values(email='dave.new@example.com') result = session.execute(stmt) session.commit() print(f"Rows updated via bulk statement: {result.rowcount}")
4. DELETE Statements (Deleting Objects)
- SQL:
DELETE FROM users_orm WHERE name = 'David'; -
SQLAlchemy ORM:
with Session() as session: # --- Option 1: Fetch, Delete, Commit --- user_to_delete = session.scalars(select(User).where(User.name == 'David')).first() # Or: user_to_delete = session.query(User).filter(User.name == 'David').first() if user_to_delete: session.delete(user_to_delete) session.commit() # Issues a DELETE statement print("User deleted via object.") else: print("User not found.") # --- Option 2: Bulk Delete (Modern 2.0 Style) --- # Efficient for deleting multiple rows without loading them stmt = delete(User).where(User.name == 'SomeOtherUser') result = session.execute(stmt) session.commit() print(f"Rows deleted via bulk statement: {result.rowcount}")
In summary, SQLAlchemy (both Core and ORM) provides Pythonic ways to construct and execute SQL statements. You replace raw SQL strings with Python functions, methods, and object interactions, gaining benefits like:
- Database Agnosticism: Write code once, run against different SQL databases (PostgreSQL, MySQL, SQLite, etc.) with minimal changes.
- Type Safety & Autocompletion: Work with Python objects and methods, catching errors earlier.
- Security: Automatically handles parameter escaping, helping prevent SQL injection vulnerabilities when used correctly.
- Composability: Build complex queries step-by-step.
- ORM Features: (With ORM) Relationship management, unit of work pattern (session), lazy loading, etc.