Core: MetaData and Table

SQLAlchemy · Essentials · 10 min

Table metadata is what lets SQLAlchemy generate correct SQL for any database instead of hand-written strings.

In Core you describe your schema as Python objects. A MetaData object is a collection of tables; each Table has a name and Columns with types such as Integer, String(50), DateTime or ForeignKey("users.id").

With the metadata in place, metadata.create_all(engine) issues CREATE TABLE for every table that doesn't exist yet. It's handy for tests and prototypes; real projects evolve the schema with migrations (Alembic), because create_all never alters an existing table.

The payoff is the SQL expression language. Instead of strings you build statements from table objects: insert(users).values(name="ada"), select(users).where(users.c.name == "ada"). Columns live under table.c, and comparing a column with a value produces a SQL expression, not a bool. SQLAlchemy turns these into dialect-specific SQL with bound parameters, so quoting, parameter style and dialect differences are handled for you.

  • print(stmt) shows the SQL a statement will produce, useful for debugging.
  • conn.execute(insert(users), [dict, dict]) performs an efficient executemany.
  • Results from select() are the same Row objects you get from text().

Many applications use only Core, especially for reporting or bulk data work. If you use the ORM, the declarative models in the next lesson create these same Table objects for you behind the scenes.

Example

from sqlalchemy import (MetaData, Table, Column, Integer, String,
                        create_engine, insert, select)

metadata = MetaData()
users = Table("users", metadata,
              Column("id", Integer, primary_key=True),
              Column("name", String(50), nullable=False))

engine = create_engine("sqlite://")
metadata.create_all(engine)

with engine.begin() as conn:
    conn.execute(insert(users), [{"name": "ada"}, {"name": "grace"}])

stmt = select(users.c.id, users.c.name).where(users.c.name == "grace")
print(stmt)
# SELECT users.id, users.name
# FROM users
# WHERE users.name = :name_1
with engine.connect() as conn:
    print(conn.execute(stmt).all())  # [(2, 'grace')]

Remember

  • MetaData holds Table objects; Columns carry types and constraints
  • Columns live under table.c and comparisons build SQL expressions
  • create_all creates missing tables but never alters existing ones
  • print(stmt) shows the generated SQL

Watch out for

  • Relying on create_all to apply schema changes to an existing database.

Check yourself

  1. What does `users.c.name == 'ada'` evaluate to in Python?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.sqlalchemy.org/en/20/tutorial/metadata.html