Ronaldo Nascimento
Ronaldo Nascimento

Reputation: 1571

Get sqlalchemy table Model and Field objects from strings?

Very simple trying to run a query in Python 3 sqlalchemy to delete some records given string names of table and field to query against.

  1. How do you get the table object from a string?
  2. Given 1. how do you run a query via ORM with just a string of the field name?

I would assume all ORM's have an internal array or method like get with the name.



json_config = [
    {"table": "tableA",
     "field": "modified_on"
     "expires": 30},
    {"table": "tableB",
     "field": "event_on"
     "expires": 30}
]

for table_conf_item in self.json_config:
    table_name = table_conf_item["table"]
    field_name = table_conf_item["field"]
    expire_after = table_conf_item["expires"]
    table_obj = self.orm_session.TABLES[table_name]
    field_obj = self.orm_session.TABLES[table_name].FIELDS[field_name]
    result = self.orm_session.delete(table_obj).where(field_obj < expire_after)
    self.orm_session.commit()
    print(f"{table_name}: removed {result.row_count} objects")

Upvotes: 0

Views: 1623

Answers (1)

snakecharmerb
snakecharmerb

Reputation: 55943

Given the table's name, you can use reflection to get a Table object. Using SQLAlchemy's core layer, this is reasonably straightforward:

import sqlalchemy as sa

engine = sa.create_engine(...)

tbl = sa.Table(name_of_table, metadata, autoload_with=engine)

If you want to work with multiple tables, it may be more efficient to store them a Metadata instance for later access:

metadata = sa.MetaData()
metadata.reflect(engine, only=list_of_table_names)
tbl = metadata.tables[name_of_table]

Once you have a Table object you can reference columns by name like this: tbl.c[name_of_field].

Full example:

import sqlalchemy as sa

# Setup
engine = sa.create_engine('sqlite://', echo=True, future=True)

tbl = sa.Table(
    't',
    sa.MetaData(),
    sa.Column('id', sa.Integer, primary_key=True),
    sa.Column('foo', sa.Integer),
)

tbl.create(engine)

with engine.begin() as conn:
    vals = [42, 43, 42, 43, 56, 87, 89]
    conn.execute(tbl.insert(), [{'foo': v} for v in vals])

del tbl

# Reflect the table.
metadata = sa.MetaData()
metadata.reflect(engine, only=['t'])
tbl = metadata.tables['t']

# Define some statements.
q1 = sa.select(tbl).where(tbl.c['foo'] == 42)
q2 = sa.select(tbl.c['id'], tbl.c['foo']).where(tbl.c['foo'] == 43)
q3 = sa.delete(tbl).where(tbl.c['foo'] != 42)

# Execute the statements.
with engine.connect() as conn:
    rows = conn.execute(q1)
    for row in rows:
        print(row)
    print()
    rows = conn.execute(q2)
    for row in rows:
        print(row)
    print()

with engine.begin() as conn:
    conn.execute(q3)


with engine.connect() as conn:
    rows = conn.execute(q1)
    for row in rows:
        print(row)
    print()

Doing the same through the ORM layer is more complicated, as table and column names must be mapped to ORM entity classes (models) and their attributes. This replicates the previous example for a simple mapping (it assumes the same initial data as above).

import sqlalchemy as sa
from sqlalchemy import orm

Base = orm.declarative_base()


class Thing(Base):
    __tablename__ = 't'

    id = sa.Column(sa.Integer, primary_key=True)
    thing_foo = sa.Column('foo', sa.Integer)


engine = sa.create_engine(...)
Base.metadata.create_all(engine)
Session = orm.sessionmaker(engine, future=True)

tablename = 't'
columnname = 'foo'

with Session.begin() as s:
    # Get the mappers for the Base class.
    mappers = Base.registry.mappers
    # Get the mapper for our table.
    mapper = next(m for m in mappers if m.entity.__tablename__ == tablename)
    # Get the entity class (Thing).
    entity = mapper.entity
    # Get the column from the Table.
    table_column = mapper.selectable.c[columnname]
    # Get the mapper property that corresponds to the column
    # (the entity attribute may have a different name to the 
    # column in the database).
    mapper_property = mapper.get_property_by_column(table_column)
    # Get the queryable entity attribute (Thing.thing_foo).
    attr = mapper.all_orm_descriptors[mapper_property.key]
    q = sa.select(entity).where(attr != 42)
    entities = s.scalars(q)
    for entity in entities:
        s.delete(entity)

with Session() as s:
    for thing in s.scalars(sa.select(Thing)):
        print(thing.id, thing.thing_foo)

Upvotes: 2

Related Questions