I am using a class with property and setter decorators. I'm wondering why I must reference the private variable name _child instead of child in the example below. Shouldn't I indirectly access _child through the setter/getter when accessing child
Minimal example below:
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
Base = declarative_base()
class Parent(Base):
__tablename__ = 'parents'
id = Column(Integer, primary_key=True)
child = relationship('Child', back_populates='parent')
child_id = Column(Integer,ForeignKey('child.id'))
def __init__(self):
self._child= Child()
#property
def child(self):
return self._child
#child.setter
def child(self, child):
print('child set')
self._child= child
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent = relationship('Parent', back_populates='child')
def __init__(self):
pass
if __name__ == '__main__':
child = Child()
parent = Parent()
parent.child = child
This throws the following error:
sqlalchemy.exc.InvalidRequestError: Mapper 'Mapper|Parent|parents' has no property 'child'
If I replace
child = relationship('Child', back_populates='parent')
# and
parent = relationship('Parent', back_populates='child')
with
_child = relationship('Child', back_populates='parent')
# and
parent = relationship('Parent', back_populates='_child')
everything works
You're shadowing the relationship child with a property of the same name. It's the same as if you didn't have the relationship at all, so back_populates="child" cannot find a relationship called child, because it doesn't exist.
Judging by your example alone, you don't need the property at all.
Related
I have a flask-sqlalchemy polymorphic table structure like so
class Parent(db.Model):
id = db.Column(db.Integer, primary_key=True)
polytype = db.Column(db.String(32), nullable=False)
value = db.Column(db.String(32))
__mapper_args__ = {'polymorphic_identity': 'parent',
'polymorphic_on': polytype}
class Child(Parent):
id = db.Column(db.Integer,db.ForeignKey('parent.id'),
primary_key=True)
#validates('value')
def validate_value(self, key, val):
# [validation code]
return value
__mapper_args__ = {'polymorphic_identity': 'child'}
and I want to validate the value field. However, the validator for Child.value, the column inherited from Parent, never runs.
What is the correct way to validate an inherited column?
There's an old open issue about it.
In that issue, it is suggested that using an event listener can work, for example:
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import event
db = SQLAlchemy()
class Parent(db.Model):
id = db.Column(db.Integer, primary_key=True)
polytype = db.Column(db.String(32), nullable=False)
value = db.Column(db.String(32))
__mapper_args__ = {'polymorphic_identity': 'parent',
'polymorphic_on': polytype}
class Child(Parent):
id = db.Column(db.Integer,db.ForeignKey('parent.id'),
primary_key=True)
__mapper_args__ = {'polymorphic_identity': 'child'}
#event.listens_for(Parent.value, "set", propagate=True)
def validate_value(inst, val, *args):
print(f"checking value for {inst}")
assert val == "spam"
Parent(value="spam")
Child(value="spam")
If you don't want the listener to fire on Parent instances, decorate your listener func with event.listens_for(Child.value, ...).
Another workaround for this known open issue is creating an instance method to be called inside the #validate method, for example:
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import validates
from sqlalchemy import event
db = SQLAlchemy()
class Parent(db.Model):
id = db.Column(db.Integer, primary_key=True)
polytype = db.Column(db.String(32), nullable=False)
value = db.Column(db.String(32))
__mapper_args__ = {'polymorphic_identity': 'parent',
'polymorphic_on': polytype}
#validates('value')
def validate_value(self, key, new_value):
self._validate_value(new_value)
def _validate_value(self, new_value):
print(f"checking value for {self}")
assert new_value == "spam"
class Child(Parent):
id = db.Column(db.Integer,db.ForeignKey('parent.id'),
primary_key=True)
__mapper_args__ = {'polymorphic_identity': 'child'}
def _validate_value(self, new_value):
print(f"checking value for {self}")
assert new_value == "child"
Parent(value="spam")
Child(value="child")
In this case, you are able to validate the value having different behaviors on the Childs overwriting the private method _validate_value
I've had success polymorphically loading items via an AbstractConcreteBase parent. Is it possible to also filter on hybrid_property definitions of descendants? I'm also wanting to filter on different columns per child class.
Queries
# Fetching all ItemA and ItemB works well with...
ItemBase.query.all()
# Given the models below is it possible to filter on the children's
# item_id hybrid_property to fetch all ItemB with item_b_id of 1
# Result is []
ItemBase.query.filter(ItemBase.item_id == 'B1')
# This also doesn't work
# Result is everything unfiltered
ItemBase.query.filter(ItemB.item_id == 'B1')
Models:
from sqlalchemy.sql.expression import cast
from sqlalchemy.ext.declarative import AbstractConcreteBase
from sqlalchemy.ext.hybrid import hybrid_property
class ItemBase(AbstractConcreteBase, Base):
__tablename__ = None
#hybrid_property
def item_id(self): pass
#activity_id.expression
def item_id(cls):
pass
class ItemA(ItemBase):
__tablename__ = 'item_a'
__mapper_args__ = {
'polymorphic_identity': 'item_a',
'concrete':True
}
item_a_id = db.Column(db.Integer, primary_key=True)
#hybrid_property
def item_id(self):
return 'A' + str(self.item_a_id)
#activity_id.expression
def item_id(cls):
return 'A' + str(self.item_a_id)
class ItemB(ItemBase):
__tablename__ = 'item_b'
__mapper_args__ = {
'polymorphic_identity': 'item_b',
'concrete':True
}
item_b_id = db.Column(db.Integer, primary_key=True)
#hybrid_property
def item_id(self):
return 'B' + str(self.item_b_id)
#activity_id.expression
def item_id(cls):
return 'B' + str(self.item_b_id)
I'm stuck with the table structure for now. Any help is greatly appreciated.
Below is a form that populates an empty parent object and creates its children. It was necessary to manually invoke a ModelFormField, which was a minor annoyance. It works great. However, when I use the form to do an update, only the object is updated -- the children are created fresh.
What is the correct way to propogate an update to the children in this framework? Effectively, I'd like to the two print statements below to print the same thing. I'd be especially grateful if the form would create (delete) children if the formdata had extra (was missing) data for the children.
from multidict import MultiDict
from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
from wtforms_alchemy import ModelFieldList, ModelForm, ModelFormField
Base = declarative_base()
class Child(Base):
__tablename__ = "child"
id = Column(Integer, primary_key=True)
name = Column(String)
parent_id = Column(Integer, ForeignKey("parent.id"))
class Parent(Base):
__tablename__ = "parent"
id = Column(Integer, primary_key=True)
name = Column(String)
children = relationship(Child)
class ChildForm(ModelForm):
class Meta:
model = Child
class ParentForm(ModelForm):
class Meta:
model = Parent
children = ModelFieldList(ModelFormField(ChildForm)) # annoyed!
engine = create_engine("sqlite://")
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
# Form data
formdata = MultiDict({"name": "Foo", "children-0-name": "hotdog"})
# Create initial object
question = Parent()
form = ParentForm(formdata, obj=question)
form.populate_obj(question)
if not form.validate():
raise RuntimeError(form.errors)
session.add(question)
session.commit()
# prints: "(1, 1)"
print((question.id, question.children[0].id))
# Retrieve object and update with same data
question_get = session.query(Parent).get(question.id)
form = ParentForm(formdata, obj=question_get)
form.populate_obj(question_get)
if not form.validate():
raise RuntimeError(form.errors)
session.add(question_get)
session.commit()
# prints: "(1, 2)", want it to print the same as above
print((question_get.id, question_get.children[0].id))
In digging through the code, it looks like object ids are expected for updates. Therefore, one must use a form on update which includes the id and pass around the ids.
--- orig.py 2018-05-01 20:51:09.000000000 -0700
+++ fixed.py 2018-05-01 20:56:16.000000000 -0700
## -4,6 +4,7 ##
from sqlalchemy.orm import relationship
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
+from wtforms import IntegerField
from wtforms_alchemy import ModelFieldList, ModelForm, ModelFormField
## -24,6 +25,7 ##
class ChildForm(ModelForm):
class Meta:
model = Child
+ id = IntegerField() # Optional field used in update operations.
class ParentForm(ModelForm):
class Meta:
## -53,7 +55,11 ##
# Retrieve object and update with same data
question_get = session.query(Parent).get(question.id)
-form = ParentForm(formdata, obj=question_get)
+child_id = question_get.children[0].id
+formdata_update = MultiDict({"name": "Foo",
+ "children-0-id": child_id,
+ "children-0-name": "pizza"})
+form = ParentForm(formdata_update, obj=question_get)
form.populate_obj(question_get)
if not form.validate():
raise RuntimeError(form.errors)
Nice sleuthing. Still this seems rather cumbersome. Have you subsequently found a easier way to do an update on a form with a one to many relationship? WTForms are supposed to save developer time. But this seems like more overhead than doing it without WTForms.
I have the following ORM class:
class Video(Base):
...
public_tag_entries = relationship("VideoTagEntry")
tags = association_proxy("public_tag_entries", "value")
Furthermore i have associated an event on append :
def video_tag_added(target, value, initiator):
print "tag added"
event.listen(Video.public_tag_entries, 'append', video_tag_added)
when I append to the public_tag_entries, the event is emitted
video.public_tag_entries.append(VideoTagEntry(value = "foo"))
However when i add using:
video.tags.append("foo")
the event is not emitted.
I tried to register an event on the video.tags association proxy, but that seems not to work.
Question: is this expected and correct behavior, or is this a bug? And is there a work around, or am i simply doing something wrong.
I would expect the association proxy to trigger orm events to the underlying attribute.
Thanks,
Jacco
can't reproduce (using 0.7.9):
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import event
Base = declarative_base()
class VideoTagEntry(Base):
__tablename__ = 'vte'
id = Column(Integer, primary_key=True)
video_id = Column(Integer, ForeignKey('video.id'))
value = Column(String)
def __init__(self, value):
self.value = value
class Video(Base):
__tablename__ = 'video'
id = Column(Integer, primary_key=True)
public_tag_entries = relationship("VideoTagEntry")
tags = association_proxy("public_tag_entries", "value")
canary = []
def video_tag_added(target, value, initiator):
print "tag added"
canary.append(value)
event.listen(Video.public_tag_entries, 'append', video_tag_added)
video = Video()
video.public_tag_entries.append(VideoTagEntry(value="foo"))
video.tags.append("foo")
assert len(canary) == 2
output:
tag added
tag added
So you need to alter this test case to look more like your code to see what the difference is.
I have two databases that I'm working with in with Python using SQLAlchemy, the databases share table names and therefore I'm getting an error message when running the code.
The error message is :
sqlalchemy.exc.InvalidRequestError: Table 'wo' is already defined for this MetaData instance. Specify 'extend_existing=True' to redefine options and columns on an existing Table object.
The simplified code is below:
from sqlalchemy import create_engine, Column, Integer, String, DateTime, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship, backref
from mysql.connector.connection import MySQLConnection
Base = declarative_base()
def get_characterset_info(self):
return self.get_charset()
MySQLConnection.get_characterset_info = MySQLConnection.get_charset
mysqlengine = create_engine('mysql+mysqlconnector://......../mp2', echo=True)
MYSQLSession = sessionmaker(bind=mysqlengine)
mysqlsession= MYSQLSession()
MP2engine = create_engine('mssql+pyodbc://......../mp2', echo=True)
MP2Session = sessionmaker(bind=MP2engine)
mp2session= MP2Session()
class MYSQLWo(Base):
__tablename__= 'wo'
wonum = Column(String, primary_key=True)
taskdesc = Column(String)
comments = relationship("MYSQLWocom", order_by="MYSQLWocom.wonum", backref='wo')
class MYSQLWocom (Base):
__tablename__='wocom'
wonum = Column(String, ForeignKey('wo.wonum'), primary_key=True)
comments = Column(String, primary_key=True)
class MP2Wo(Base):
__tablename__= 'wo'
wonum = Column(String, primary_key=True)
taskdesc = Column(String)
comments = relationship("MP2Wocom", order_by="MP2Wocom.wonum", backref='wo')
class MP2Wocom (Base):
__tablename__='woc'
wonum = Column(String, ForeignKey('wo.wonum'), primary_key=True)
location = Column(String)
sublocation1 = Column(String)
texts = Column(String, primary_key=True)
How do I deal with databases having the same table structure? I'm guessing it has something to do with the MetaData instance, but the SQLAlchemy documentation gets a little confusing when talking about the difference in the class declarative and classical usage..
Since in reality the tables had different structures, the solution was to simply create a separate declarative base. If the tables indeed had the same structure, I would have only needed one class for both tables.
Base = declarative_base()
Base2 = declarative_base() #this is all I needed
class MYSQLWo(Base):
....
class MYSQLWocom(Base):
....
class MP2Wo(Base2):
....
class MP2Wocom(Base2)
http://groups.google.com/group/sqlalchemy/browse_thread/thread/afe09d6387a4dc69?hl=en
You can use one db instance with two Model to bypass this problem.
And this can also be used to implement a master/slave use case in Flask-SQLAlchemy.
Just like this:
app = Flask(__name__)
app.config['SQLALCHEMY_BINDS'] = {'rw': 'rw', 'r': 'r'}
db = SQLAlchemy(app)
db.Model_RW = db.make_declarative_base()
class A(db.Model):
__tablename__ = 'common'
class B(db.Model_RW):
__tablename__ = 'common'