Two arithmetical operations in primaryjoin and_ causes failure - python

I have a versioning system of annotations
class Annotation(db.Model):
id = db.Column(db.Integer, primary_key=True)
class AnnotationVersion(db.Model):
id = db.Column(db.Integer, primary_key=True)
book_id = db.Column(db.Integer, db.ForeignKey("book.id"))
previous_id = db.Column(db.Integer, db.ForeignKey("post_version.id"), default=None)
pointer_id = db.Column(db.Integer, db.ForeignKey("annotation.id"))
current = db.Column(db.Boolean, index=True)
first_line_num = db.Column(db.Integer)
last_line_num = db.Column(db.Integer)
class Line(db.Model):
id = db.Column(db.Integer, primary_key=True)
book_id = db.Column(db.Integer, db.ForeignKey("book.id")
line = db.Column(db.String(255))
I have the following two relationships on the Annotation class:
lines = db.relationship("Line", secondary="annotation_version",
primaryjoin="and_(Annotation.id==AnnotationVersion.pointer_id,"
"AnnotationVersion.current==True)",
secondaryjoin="and_(Line.l_num>=AnnotationVersion.first_line_num,"
"Line.l_num<=AnnotationVersion.last_line_num,"
"Line.book_id==AnnotationVersion.book_id)",
viewonly=True, uselist=True)
context = db.relationship("Line", secondary="annotation_version",
primaryjoin="and_(Annotation.id==AnnotationVersion.pointer_id,"
"AnnotationVersion.current==True)",
secondaryjoin="and_(Line.l_num>=AnnotationVersion.first_line_num-5,"
"Line.l_num<=AnnotationVersion.last_line_num+5,"
"Line.book_id==AnnotationVersion.book_id)",
viewonly=True, uselist=True)
As you can see, the context is simply the first_line_num-5 and last_line_num+5; in other words, the context of the annotation is simply the the prior five and next five lines to the actual body of the text of the annotation.
I am trying to define the same context relationship on the actual AnnotationVersion:
context = db.relationship("Line",
primaryjoin="and_(Line.l_num>=AnnotationVersion.first_line_num-5,"
"Line.l_num<=AnnotationVersion.last_line_num+5,"
"Line.book_id==AnnotationVersion.book_id)",
viewonly=True, uselist=True)
But this exact definition always returns a failure of
sqlalchemy.exc.ArgumentError: Could not locate any relevant foreign
key columns for primary join condition 'line.l_num >=
annotation_version.first_line_num - :first_line_num_1 AND line.l_num
<= annotation_version.last_line_num + :last_line_num_1 AND
line.book_id = annotation_version.book_id' on relationship
AnnotationVersion.context. Ensure that referencing columns are
associated with a ForeignKey or ForeignKeyConstraint, or are annotated
in the join condition with the foreign() annotation.
If I remove either the +5 or the -5 it works. But as soon as I define both, I get that error.
What on earth could cause this particular failure? As you can see it only happens when defined in the primaryjoin condition, because it works perfectly as a secondaryjoin condition.

Ilja Everila's reference to the documentation helped solve it. All I had to do was specify the foreign_key param:
context = db.relationship("Line",
primaryjoin="and_(Line.l_num>=AnnotationVersion.first_line_num-5,"
"Line.l_num<=AnnotationVersion.last_line_num+5,"
"Line.book_id==AnnotationVersion.book_id)",
foreign_keys=[first_line_num, last_line_num],
viewonly=True, uselist=True)

Related

Ordering relationship by property of child elements

I use flask-sqlalchemy on a Flask project to model my database.
I need to sort the elements of a many-to-many relationship based on properties of different child elements of one side.
I have "Work" (the parent element), "Tag" (the children), "Type" (a one-to-many relationship on Tag) and "Block" (a one-to-many relationship on Type). Tags and Works are joined with a mapping table "work_tag_mapping".
In essence, each tag has exactly one type, each type belongs to exactly one block, and many tags can be added on many works.
I now want the list of tags on a work be sorted by block first and type second (both have a "position" column for that purpose).
Here are my tables (simplified for the sake of the question):
class Work(db.Model):
__tablename__ = 'work'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255, collation='utf8_bin'))
tags = db.relationship('Tag', order_by="Tag.type.block.position, Tag.type.position", secondary=work_tag_mapping)
class Tag(db.Model):
__tablename__ = 'tag'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255, collation='utf8_bin'))
type_id = db.Column(db.Integer, db.ForeignKey('type.id'), nullable=False)
type = db.relationship('Type')
work_tag_mapping = db.Table('work_tag_mapping',
db.Column('id', db.Integer, primary_key=True),
db.Column('work_id', db.Integer, db.ForeignKey('work.id'), nullable=False),
db.Column('tag_id', db.Integer, db.ForeignKey('tag.id'), nullable=False)
)
class Type(db.Model):
__tablename__ = 'type'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255, collation='utf8_bin'))
position = db.Column(db.Integer)
block_id = db.Column(db.Integer, db.ForeignKey('block.id'), nullable=False)
block = db.relationship('Block')
class Block(db.Model):
__tablename__ = 'block'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255, collation='utf8_bin'))
position = db.Column(db.Integer)
Now, it is the "order_by" in the "tags" relationship that doesn't work as I initially hoped.
The error I get is "sqlalchemy.exc.InvalidRequestError: Property 'type' is not an instance of ColumnProperty (i.e. does not correspond directly to a Column)."
I am new to SQLalchemy, Flask and indeed Python, and none of the ressources or questions here mention a case like this.
While this appears not to be possible directly, adding a getter and performing the sorting on retrieval does the trick. Adding lazy='dynamic' ensures the collection behaves as a query, so joins can be performed.
_tags = db.relationship('Tag', lazy='dynamic')
#hybrid_property
def tags(self):
return self._tags.join(Type).join(Block).order_by(Block.position, Type.position)

How to set the default value of a column in sqlalchemy to the value of a column from a relationship?

I am setting up a Sqlalchemy mapper for a sqlite database. My User class has a non-nullable relationship with my Team class. The code I already have is as follows:
class Team(Base):
__tablename__ = 'teams'
team_id = Column(Integer, primary_key=True)
# Using Integer as holder for boolean
is_local = Column(Integer, default=0)
class User(Base):
__tablename__ = 'users'
user_id = Column(Integer, primary_key=True)
team_id = Column(Integer, ForeignKey(Team.team_id), default=1, nullable=False)
team = relationship('Team')
is_local = Column(Integer, default=0)
I would like to establish that the value of User.is_local is by default the value of Team.is_local for the User's linked team.
However, after the creation of the User, I would still like the ability to modify the user's is_local value without changing the values of the team or any other user on the team.
So if I were to execute
faraway = Team(is_local=1)
session.add(faraway)
session.commit()
u = User(team=faraway)
session.add(u)
session.commit()
print(bool(u.is_local))
The result should be True
So far, I have tried context-sensitive default functions as suggested by https://stackoverflow.com/a/36579924, but I have not been able to find the syntax allowing me to reference Team.is_local
Is there a simple way to do this?
The first suggestion from SuperShoot, using a sql expression as the default appears to work. Specifically,
is_local = Column(Integer, default=select([Team.is_local]).where(Team.team_id==team_id))
gives me the logic I require.

How to make 'tags' affect a relation(column_property)

I'm working on a project with Flask-SQLAlchemy.
The model looks like this:
cars have components,
components can have issues
car has a column_property 'needs_repair' which is true when a car's component has issues
needs_repair = column_property(exists().where(and_(
carcomponent.columns['car_id'] == id,
carcomponent.columns['component_id'] == componentissue.columns['component_id']
)))
I added a table for tags with a 'skip'-column, tags are assigned via a table issue_car_tag(ignoring components, only referencing specific car-issue-relations).
Now, i want needs_repair to be true if all assigned tags have skip = False or no tags are assigned
How do I extend the column_property to achieve this?
edit:
Model/table definitions:
class Component(Base):
id = db.Column(db.Integer, primary_key=True)
[...]
issues = db.relationship('ISsue', secondary=componentissue, lazy='dynamic',
back_populates='components')
cars = db.relationship('Car', lazy = 'dynamic', secondary=carcomponent,
back_populates="component"
broken = column_property(exists().where(componentissue.columns['component_id'] == id))
class Car(Base):
id = db.Column(db.Integer, primary_key=True)
[...]
components = db.relationship('Component', secondary=carcomponent,
back_populates="cars", lazy='dynamic')
needs_repair = column_property(exists().where(and_(
carcomponent.columns['car_id'] == id,
carcomponent.columns['component_id'] == componentissue.columns['component_id']
)))
class Issue(Base):
__tablename__ = "issues"
[...]
components = db.relationship('Component' lazy = 'dynamic', secondary=componentissue,
back_populates='issues')
class Tag(Base):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Text, unique=True)
description = db.Column(db.Text, nullable=False, default="")
skip = db.Column(db.Boolean, default = False)
class Issue_Car_Tag(Base):
id = db.Column(db.Integer, primary_key=True)
tag_id = db.Column(db.Integer, db.ForeignKey('tag.id'))
car_id = db.Column(db.Integer, db.ForeignKey('car.id'))
issue_id = db.Column(db.Integer, db.ForeignKey('issue.id'))
tag = db.relationship('Tag', backref=db.backref('issue_car_tags'))
car = db.relationship('Car', backref=db.backref('issue_car_tags'))
issue = db.relationship('Issue', backref=db.backref('issue_car_tags'))
If you'd move the definition of Car after the definitions of Tag and Issue_Car_Tag or reference those tables in some other manner, you could produce the following query construction
func.coalesce(func.bool_and(not_(Tag.skip)), False).\
select().\
where(Tag.id == Issue_Car_Tag.tag_id).\
where(Issue_Car_Tag.car_id == id).\
as_scalar()
and use that in an OR with your existing check:
needs_repair = column_property(
or_(func.coalesce(func.bool_and(not_(Tag.skip)), False).
select().
where(Tag.id == Issue_Car_Tag.tag_id).
where(Issue_Car_Tag.car_id == id).
as_scalar(),
exists().where(and_(
carcomponent.c.car_id == id,
carcomponent.c.component_id == componentissue.c.component_id))))
The query selects tags related to a car using the association table issue_car_tag and aggregates the skip values, coalescing an empty result or all null values.
Note that this results in false if no tags are assigned, so you have to handle that separately. If I've understood your existing query correctly, this is handled by your EXISTS expression already. Put another way, the new query results in true if tags exist and all have skip set to false.

Relationship between two tables, SQLAlchemy

I want to make a relationship between AuthorComments and Reply to his comments.
Here is my models.py:
class AuthorComments(Base):
id = db.Column(db.Integer, primary_key=True)
author_id = db.Column(db.Integer, db.ForeignKey('author.id'))
name = db.Column(db.String(50))
email = db.Column(db.String(50), unique=True)
comment = db.Column(db.Text)
live = db.Column(db.Boolean)
comments = db.relationship('Reply', backref='reply', lazy='joined')
def __init__(self,author, name, email, comment, live=True):
self.author_id = author.id
self.name = name
self.email = email
self.comment = comment
self.live = live
class Reply(Base):
id = db.Column(db.Integer, primary_key=True)
reply_id = db.Column(db.Integer, db.ForeignKey('author.id'))
name = db.Column(db.String(50))
email = db.Column(db.String(50), unique=True)
comment = db.Column(db.Text)
live = db.Column(db.Boolean)
def __init__(self,author, name, email, comment, live=True):
self.reply_id = author.id
self.name = name
self.email = email
self.comment = comment
self.live = live
Why am I getting this error:
sqlalchemy.exc.InvalidRequestError
InvalidRequestError: One or more mappers failed to initialize - can't proceed with initialization of other mappers. Original exception was: Could not determine join condition between parent/child tables on relationship AuthorComments.comments - there are no foreign keys linking these tables. Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or specify a 'primaryjoin' expression.
Your trouble is that SQLAlchemy doesn't know, for a given row of the child table (Reply), which row of the parent table (AuthorComments) to select! You need to define a foreign-key column in Reply that references a column of its parent AuthorComments.
Here is the documentation on defining one-to-many relationships in SQLAlchemy.
Something like this:
class AuthorComments(Base):
__tablename__ = 'author_comment'
...
class Reply(Base):
...
author_comment_id = db.Column(db.Integer, db.ForeignKey('author_comment.id'))
...
author_comment = db.relationship(
'AuthorComments',
backref='replies',
lazy='joined'
)
will result in each reply acquiring a relationship to an author_comment such that some_reply.author_comment_id == some_author_comment.id, or None if no such equality exists.
The backref allows each author_comment to, reciprocally, have a relationship to a collection of replies called replies, satisfying the above condition.

SQLAlchemy: #property mapping?

I have following models:
class Details(db.Model):
details_id = db.Column(db.Integer, primary_key=True)
details_main = db.Column(db.String(50))
details_desc = db.Column(db.String(50))
class Data(db.Model):
data_id = db.Column(db.Integer, primary_key=True)
data_date = db.Column(db.Date)
details_main = db.Column(db.String(50))
#property
def details_desc(self):
result = object_session(self).\
scalar(
select([Details.details_desc]).
where(Details.details_main == self.details_main)
)
return result
Now, I would like to run query using filter which depends on defined property. I get empty results (of course proper data is in DB). It doesn't work because, probably, I have to map this property. The question is how to do this? (One limitation: FK are not allowed).
Data.query\
.filter(Data.details_desc == unicode('test'))\
.all()
You can implement this with a regular relationship and an association proxy:
class Data(db.Model):
data_id = db.Column(db.Integer, primary_key=True)
data_date = db.Column(db.Date)
details_main = db.Column(db.String(50))
details = relationship(
Details,
primaryjoin=remote(Details.details_main) == foreign(details_main))
details_desc = association_proxy('details', 'details_desc')
Since there are no foreign keys in the schema, you need to tell SQLAlchemy yourself what the join condition for the relationship should be. This is what the remote() and foreign() annotations do.
With that in place, you can use an association_proxy "across" the relationship to create a property on Data which will work the way you want.

Categories