Warm tip: This article is reproduced from stackoverflow.com, please click
class flask python flask-sqlalchemy

How to create a child SQL-Alchemy instance from a parent instance?

发布于 2020-03-29 20:58:51

I have two models, Signal and Trade. My Trade object inherits its attributes from Signal, as almost all of the attributes are the same, however there are some key functions and attributes which make it different. I'm writing a method on Signal called as_trade(), I want to create a Trade instance from all the data/attributes on the Signal instance so I can save it to the database. I was using deepcopy - and this had been working, but in actual fact I don't think this is the correct/best way to do it. Is there a tried/tested way of performing an action like this?

class Signal(db.Model):
    __tablename__ = 'signal'
    id = db.Column(db.Integer, primary_key=True)
    class_type = db.Column(db.String())

    __mapper_args__ = {
        'polymorphic_identity': 'signal',
        'polymorphic_on': class_type,
    }

    # MAIN FIELDS
    type = db.Column(db.String(20))

    def as_trade(self):
       signal_dict = self.__dict__
       for key in ['id', 'user_id', '_sa_instance_state', 'class_type']:
           signal_dict.pop(key)
       trade = Trade(**signal_dict)
       return trade

    ...

class Trade(Signal):
    __tablename__ = 'trade'
    id = db.Column(db.Integer, db.ForeignKey('signal.id'), primary_key=True)

    __mapper_args__ = {
        'polymorphic_identity': 'trade',
    }
    ....

I should add, the issue I'm getting here is that when as_trade() is called, it throws a key error. If I call self.id in PDB, I get a value (1). However, if I call self.__dict__ I get only an InstanceState, with no attributes. Hence, it throws a keyError. But sometimes, by calling self_dict and self.dict whilst in PDB, I can get the dictionary to actually appear and have values - this is the part I don't understand. Left alone, the program fails here as it throws a keyError given that self.dict gives only InstanceState object. But poking around in PDB and calling self.dict a step ahead in the process allows the for loop to complete because the dictionary appears with all the attributes required...?

{'_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x000002E694983B88>
}

UPDATE: The reason its acting funky is because by popping _sa_instance_state from the dictionary, the state is acting up. If I leave _sa_instance_state in the dictionary object and pass it to Trade(), I get an error TypeError: '_sa_instance_state' is an invalid keyword argument for Trade

Questioner
phil0s0pher
Viewed
123
SuperShoot 2020-01-31 19:25

If both the Signal class and the Trade class share the same database columns and the only way they differ is in the methods defined on the classes, then it makes sense to use a single table inheritance model.

As Flask-SQLAlchemy magically defines table names if you don't define them, you'll need to explicitly set the __tablename__ attribute of the child class to None to signal you intention to use the single table construct.

Then, if the objective is to copy the values of the columns of the Signal object over to a new Trade object, then you can iterate over the columns of Signal.__table__ in order to get the values of the column from Signal and write them to the new instance of Trade.

The loop would look something like this:

signal = db.session.query(Signal).one()
new_trade = Trade()
for col in Signal.__table__.c:
    if col.key not in ("id", "type"):
        setattr(new_trade, col.key, getattr(signal, col.key))

Here's a full working example:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///test.db"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy(app)


class Signal(db.Model):
    __tablename__ = "signal"
    id = db.Column(db.Integer, primary_key=True)

    # MAIN FIELDS
    type = db.Column(db.String(20))
    col1 = db.Column(db.Integer)
    col2 = db.Column(db.Integer)

    __mapper_args__ = {"polymorphic_on": "type", "polymorphic_identity": "signal"}


class Trade(Signal):

    __tablename__ = None

    __mapper_args__ = {"polymorphic_identity": "trade"}


if __name__ == "__main__":
    db.drop_all()
    db.create_all()
    db.session.add(Signal(col1=1, col2=2))
    db.session.commit()
    signal = db.session.query(Signal).one()
    new_trade = Trade()
    for col in Signal.__table__.c:
        if col.key not in ("id", "type"):
            setattr(new_trade, col.key, getattr(signal, col.key))
    db.session.add(new_trade)
    db.session.commit()
    db.session.expire_all()
    trade = db.session.query(Trade).first()
    print(trade.col1, trade.col2)