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
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)