8.25. SQLAlchemy Schema Reflection — Python
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