8.25. Schema Reflection

  • SQLAlchemy allows for database reflection

SQLAlchemy tool Automap does the reflection of database and figures out how to do the mapping dynamically.

There is third-party tool sqlacodegen that generates the files (Python code) for you based on introspected tables. This is more robust solution.

Reflection

Loading Table objects based on reading from an existing database.

8.25.1. Reflection

"Reflection" refers to loading Table objects based on reading from an existing database. In order to create a reflection, first create empty metadata object:

>>> metadata2 = MetaData()

And then use it to introspect the database table:

>>> with engine.connect() as db:
...     astronauts = Table('astronauts', metadata2, autoload_with=db)

The reflected object is filled in with all the columns and constraints and is ready to use.

>>> print(astronauts.c)  
<sqlalchemy.sql.base.ImmutableColumnCollection object at 0x...>
>>> print(astronauts.primary_key)
PrimaryKeyConstraint(Column('id', INTEGER(), table=<astronaut>, primary_key=True, nullable=False))
>>> print(select(astronauts))
SELECT astronaut.id, astronaut.firstname, astronaut.lastname, astronaut.birthdate, astronaut.height, astronaut.weight, astronaut.agency
FROM astronaut

8.25.2. Inspection

Information about a database at a more specific level is available using the Inspector object. Inspector will work with an engine or a connection. [1]

First import the inspector:

>>> from sqlalchemy import inspect

Attach it to the engine:

>>> inspector = inspect(engine)

You can query the database to get all tables:

>>> inspector.get_table_names()
['astronaut']

Or get information about columns:

>>> inspector.get_columns('astronaut')  
[{'name': 'id', 'type': INTEGER(), 'nullable': False, 'default': None, 'autoincrement': 'auto', 'primary_key': 1},
 {'name': 'firstname', 'type': VARCHAR(length=50), 'nullable': False, 'default': None, 'autoincrement': 'auto', 'primary_key': 0},
 {'name': 'lastname', 'type': VARCHAR(length=50), 'nullable': False, 'default': None, 'autoincrement': 'auto', 'primary_key': 0},
 {'name': 'birthdate', 'type': DATE(), 'nullable': True, 'default': None, 'autoincrement': 'auto', 'primary_key': 0},
 {'name': 'height', 'type': INTEGER(), 'nullable': True, 'default': None, 'autoincrement': 'auto', 'primary_key': 0},
 {'name': 'weight', 'type': NUMERIC(precision=3, scale=2), 'nullable': True, 'default': None, 'autoincrement': 'auto', 'primary_key': 0},
 {'name': 'agency', 'type': VARCHAR(length=9), 'nullable': True, 'default': None, 'autoincrement': 'auto', 'primary_key': 0}]

Or constraints:

>>> inspector.get_foreign_keys('astronaut')
[]

Currently supported constraints:

  • FOREIGNKEY

  • UNIQUE

  • CHECK

Currently not supported:

  • Functional Indexes (PostgreSQL)

  • EXCLUDE (PostgreSQL)

8.25.3. Reflecting an Entire Schema

The MetaData object also includes a feature that will reflect all the tables in particular schema at once. [1]

>>> metadata3 = MetaData()
>>>
>>> with engine.connect() as db:
...     metadata3.reflect(db)

Note, that this will produce a lot of database queries. The Tables objects are then in the metadata.tables collection:

>>> metadata3.tables  
FacadeDict({
    'published': Table('published', MetaData(),
                    Column('pub_id', INTEGER(), table=<published>, primary_key=True, nullable=False),
                    Column('pub_timestamp', Date(), table=<published>),
                    Column('story_id', INTEGER(), ForeignKey('story.story_id'), table=<published>),
                    Column('version_id', INTEGER(), ForeignKey('story.version_id'), table=<published>), schema=None),
    'story': Table('story', MetaData(),
                    Column('story_id', INTEGER(), table=<story>, primary_key=True, nullable=False),
                    Column('version_id', INTEGER(), table=<story>, primary_key=True, nullable=False),
                    Column('headline', VARCHAR(length=100), table=<story>, nullable=False),
                    Column('body', TEXT(), table=<story>), schema=None),
    'users': Table('users', MetaData(),
                    Column('uid', INTEGER(), table=<users>, primary_key=True, nullable=False),
                    Column('firstname', VARCHAR(), table=<users>, nullable=False),
                    Column('lastname', VARCHAR(), table=<users>, nullable=False), schema=None)})
>>> story = metadata3.tables['story']
>>> published = metadata3.tables['published']
>>> story  
Table('story', MetaData(),
      Column('story_id', INTEGER(), table=<story>, primary_key=True, nullable=False),
      Column('version_id', INTEGER(), table=<story>, primary_key=True, nullable=False),
      Column('headline', VARCHAR(length=100), table=<story>, nullable=False),
      Column('body', TEXT(), table=<story>), schema=None)
>>> published  
Table('published', MetaData(),
      Column('pub_id', INTEGER(), table=<published>, primary_key=True, nullable=False),
      Column('pub_timestamp', DATE(), table=<published>),
      Column('story_id', INTEGER(), ForeignKey('story.story_id'), table=<published>),
      Column('version_id', INTEGER(), ForeignKey('story.version_id'), table=<published>), schema=None)

This is useful if you have an existing database and you want to write queries against it.

>>> query = select(story).join(published)
>>> print(query)
SELECT story.story_id, story.version_id, story.headline, story.body
FROM story JOIN published ON story.story_id = published.story_id AND story.version_id = published.version_id

8.25.4. References