# Working with Database Metadata
SQLAlchemy Core and ORM were created to allow Python objects to be used like tables and columns in a database. These can be used as database metadata.
Metadata describes data that describes other data. Here, metadata refers to configured tables, columns, constraints, and other object information.
# Creating a table object and add it to your metadata
In relational databases, tables are created via queries, but in SQLAlchemy, tables can be created through Python objects.
To start with SQLAlchemy Expression Language, you need to create a Table
object for the database table you want to use.
>>> from sqlalchemy import MetaData
>>> # An object that will hold the meta information for the tables.
>>> metadata = MetaData()
>>>
>>> from sqlalchemy import Table, Column, Integer, String
>>> user_table = Table(
... # The name of the table that will be stored in the database.
... 'user_account',
... metadata,
...
... # The columns that will go into this table.
... Column('id', Integer, primary_key=True),
... Column('name', String(30)),
... Column('fullname', String),
... )
You can create database tables using the
Table
object.Columns of the table are implemented using
Column
.- By default, it defines like
Column(column name, data type)
.
- By default, it defines like
After creating a
Table
instance, you can know the created column information as follows:
>>> user_table.c.name
Column('name', String(length=30), table=<user_account>)
>>> user_table.c.keys()
['id', 'name', 'fullname']
# Declaring Simple Constraints
We saw the Column('id', Integer, primary_key=True)
statement in the code that creates the user table above. This declares the id column as the primary key.
The primary key is implicitly declared as a structure in the PrimaryKeyConstraint
object. This can be confirmed as follows.
>>> user_table.primary_key
PrimaryKeyConstraint(Column('id', Integer(), table=<user_account>, primary_key=True, nullable=False))
Along with the primary key, foreign keys can also be declared as follows.
>>> from sqlalchemy import ForeignKey
>>> address_table = Table(
... "address",
... metadata,
... Column('id', Integer, primary_key=True),
... # Declaring Foreign Key as `ForeignKey` object.
... Column('user_id', ForeignKey('user_account.id'), nullable=False),
... Column('email_address', String, nullable=False)
... )
- You can declare a foreign key column in the form of
ForeignKey('table_name.foreign_key')
.- In this case, you can omit the data type of the
Column
object. The data type is automatically inferred by locating the column corresponding to the foreign key.
- In this case, you can omit the data type of the
- You can also declare a
NOT NULL
constraint on a column by passing thenullable=False
parameter and value.
# Applying to your database
We have declared database tables using SQLAlchemy so far. Now, let's make these declared tables actually get created in the database.
Execute metadata.create_all()
as follows.
>>> metadata.create_all(engine)
# The above code creates all tables recorded in the metadata instance.
# As a result, it executes the following queries.
BEGIN (implicit)
PRAGMA main.table_...info("user_account")
...
PRAGMA main.table_...info("address")
...
CREATE TABLE user_account (
id INTEGER NOT NULL,
name VARCHAR(30),
fullname VARCHAR,
PRIMARY KEY (id)
)
...
CREATE TABLE address (
id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
email_address VARCHAR NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY(user_id) REFERENCES user_account (id)
)
...
COMMIT
# Defining table metadata the ORM way
We will create the same database structure and use the same constraints as above, but this time we will proceed using the ORM approach.
# Setting the registry
object.
First of all, create a registry
object as follows.
>>> from sqlalchemy.orm import registry
>>> mapper_registry = registry()
The registry
object contains a MetaData
object.
>>> mapper_registry.metadata
MetaData()
Now we can execute the following code.
>>> Base = mapper_registry.generate_base()
The above process can be simplified using
declarative_base
as follows.>>> from sqlalchemy.orm import declarative_base >>> Base = declarative_base()
# Declaring the ORM object
By defining a subclass that inherits from the Base
object, you can declare tables in the database using the ORM approach.
>>> from sqlalchemy.orm import relationship
>>> class User(Base):
... # A name of the table to be used in the database.
... __tablename__ = 'user_account'
...
... id = Column(Integer, primary_key=True)
... name = Column(String(30))
... fullname = Column(String)
...
... addresses = relationship("Address", back_populates="user")
...
... def __repr__(self):
... return f"User(id={self.id!r}, name={self.name!r}, fullname={self.fullname!r})"
>>> class Address(Base):
... __tablename__ = 'address'
...
... id = Column(Integer, primary_key=True)
... email_address = Column(String, nullable=False)
... user_id = Column(Integer, ForeignKey('user_account.id'))
...
... user = relationship("User", back_populates="addresses")
...
... def __repr__(self):
... return f"Address(id={self.id!r}, email_address={self.email_address!r})"
The User
and Address
objects include a Table
object.
You can check this through the __table__
attribute as follows.
>>> User.__table__
Table('user_account', MetaData(),
Column('id', Integer(), table=<user_account>, primary_key=True, nullable=False),
Column('name', String(length=30), table=<user_account>),
Column('fullname', String(), table=<user_account>), schema=None)
# Creating an ORM object
After defining the table, you can create an ORM object as follows.
>>> sandy = User(name="sandy", fullname="Sandy Cheeks")
>>> sandy
User(id=None, name='sandy', fullname='Sandy Cheeks')
# Applying to your database
Now, you can apply the tables declared with ORM to the actual database as follows.
>>> mapper_registry.metadata.create_all(engine)
>>> Base.metadata.create_all(engine)
# Importing tables from an existing database into an ORM object
Aside from the above methods, there is a way to retrieve tables from the database without declaring them directly.
>>> some_table = Table("some_table", metadata, autoload_with=engine)
BEGIN (implicit)
PRAGMA main.table_...info("some_table")
[raw sql] ()
SELECT sql FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE name = ? AND type = 'table'
[raw sql] ('some_table',)
PRAGMA main.foreign_key_list("some_table")
...
PRAGMA main.index_list("some_table")
...
ROLLBACK
Now it can be used as follows:
>>> some_table
Table('some_table', MetaData(),
Column('x', INTEGER(), table=<some_table>),
Column('y', INTEGER(), table=<some_table>),
schema=None)