How to define two relationships to the same table in SQLAlchemy - python

I’ve looked all over the SQLAlchemy tutorial and other similar questions but I seem to be struggling to get this join to work:
The scenario: I have a pages table represented by the Page model. Pages can be created by an user and edited by an user, but not necessarily the same one. My Page model looks like this (abridged):
class Page(Base):
__tablename__ = 'pages'
id = Column(Integer, primary_key = True)
slug = Column(Text)
title = Column(Text)
direct_link = Column(Text)
body = Column(Text)
category_id = Column(Integer, ForeignKey('categories.id'))
published_on = Column(DateTime)
publishing_user_id = Column(Integer, ForeignKey('users.id'))
last_edit_on = Column(DateTime)
last_edit_user_id = Column(Integer, ForeignKey('users.id'))
# Define relationships
publish_user = relationship('User', backref = backref('pages', order_by = id), primaryjoin = "Page.publishing_user_id == User.id")
edit_user = relationship('User', primaryjoin = "Page.last_edit_user_id == User.id")
category = relationship('Category', backref = backref('pages', order_by = id))
My users are stored in the users table represented by the User model. As I said I’ve been all over the SQLAlchemy docs looking for this, I’ve tried to make it look as similar to their example as possible, but no to no avail. Any help would be greatly appreciated.

As of version 0.8, SQLAlchemy can resolve the ambiguous join using only the foreign_keys keyword parameter to relationship.
publish_user = relationship(User, foreign_keys=[publishing_user_id],
backref=backref('pages', order_by=id))
edit_user = relationship(User, foreign_keys=[last_edit_user_id])
Documentation at http://docs.sqlalchemy.org/en/rel_0_9/orm/join_conditions.html#handling-multiple-join-paths

I think you almost got it right; only instead of Model names you should use Table names when defining primaryjoin. So instead of
# Define relationships
publish_user = relationship('User', backref = backref('pages', order_by = id),
primaryjoin = "Page.publishing_user_id == User.id")
edit_user = relationship('User',
primaryjoin = "Page.last_edit_user_id == User.id")
use:
# Define relationships
publish_user = relationship('User', backref = backref('pages', order_by = id),
primaryjoin = "pages.publishing_user_id == users.id")
edit_user = relationship('User',
primaryjoin = "pages.last_edit_user_id == users.id")

Try foreign_keys option:
publish_user = relationship(User, foreign_keys=publishing_user_id,
primaryjoin=publishing_user_id == User.id,
backref=backref('pages', order_by=id))
edit_user = relationship(User, foreign_keys=last_edit_user_id,
primaryjoin=last_edit_user_id == User.id)

The example in this documentation
http://docs.sqlalchemy.org/en/rel_0_9/orm/join_conditions.html#handling-multiple-join-paths isn't for one-to-many.... I think.
In the one-to-many case here's what worked for me:
class Pipeline(Base):
__tablename__ = 'pipelines'
id = Column(UUID(as_uuid=True), primary_key=True, unique=True, default=uuid.uuid4)
...
input_resources = relationship("Resource", foreign_keys="Resource.input_pipeline_id")
output_resources = relationship("Resource", foreign_keys="Resource.output_pipeline_id")
...
class Resource(Base):
__tablename__ = 'resources'
id = Column(UUID(as_uuid=True), primary_key=True, unique=True, default=uuid.uuid4)
....
input_pipeline_id = Column(UUID(as_uuid=True), ForeignKey("pipelines.id"))
output_pipeline_id = Column(UUID(as_uuid=True), ForeignKey("pipelines.id"))
...

Related

Many to many query in sqlalchemy

There are tables for my question.
class TemplateExtra(ExtraBase, InsertMixin, TimestampMixin):
__tablename__ = 'template_extra'
id = Column(Integer, primary_key=True, autoincrement=False)
name = Column(Text, nullable=False)
roles = relationship(
'RecipientRoleExtra',
secondary='template_to_role',
)
class RecipientRoleExtra(
ExtraBase, InsertMixin, TimestampMixin,
SelectMixin, UpdateMixin,
):
__tablename__ = 'recipient_role'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(Text, nullable=False)
description = Column(Text, nullable=False)
class TemplateToRecipientRoleExtra(ExtraBase, InsertMixin, TimestampMixin):
__tablename__ = 'template_to_role'
id = Column(Integer, primary_key=True, autoincrement=True)
template_id = Column(Integer, ForeignKey('template_extra.id'))
role_id = Column(Integer, ForeignKey('recipient_role.id'))
I want to select all templates with prefetched roles in two sql-queries like Django ORM does with prefetch_related. Can I do it?
This is my current attempt.
def test_custom():
# creating engine with echo=True
s = DBSession()
for t in s.query(TemplateExtra).join(RecipientRoleExtra, TemplateExtra.roles).all():
print(f'id = {t.id}')
for r in t.roles:
print(f'-- {r.name}')
But..
it generates select query for every template to select its roles. Can I make sqlalchemy to do only one query?
generated queries for roles are without join, just FROM recipient_role, template_to_role with WHERE %(param_1)s = template_to_role.template_id AND recipient_role.id = template_to_role.role_id. Is it correct?
Can u help me?
Based on this answer:
flask many to many join as done by prefetch_related from django
Maybe somthing like this:
roles = TemplateExtra.query.options(db.joinedload(TemplateExtra.roles)).all
Let me know if it worked.

sqlalchemy column_property in self-referential

I can't describe the column "invited_name" (column_property). I don't know how to do this correctly.
class Worker(declarative_base()):
__tablename__ = "staff_worker_info"
id = Column(Integer, primary_key=True)
first_name = Column(String(40), nullable=False)
last_name = Column(String(40), nullable=False)
invited_id = Column(Integer, ForeignKey('staff_worker_info.id'))
invited = relationship("Worker", uselist=False, remote_side=[id], join_depth=1)
# I don't know how to describe this column
invited_name = column_property(
select([Worker.first_name]). \
where(Worker.id == invited_id).\
label('invited_n'))
I understand why this doesn't work, but I don't know how to write it differently.
I should get such a SQL query.
SELECT staff_worker_info.id, staff_worker_info.first_name staff_worker_info.last_name, staff_worker_info.invited_id,
(SELECT worker_invited.first_name
FROM staff_worker_info AS worker_invited
WHERE staff_worker_info.invited_id = worker_invited.id) AS invited_n,
FROM staff_worker_info
Might be a bit late, but I recently faced a similar question. I think your problem is quite easy to solve with only the relationship. If you want you can also solve it by using a column_property.
First, using the relationship. If you make the invited relationship joined, then the actual query that is send to the database is a self-join. You can access the first name via that relationship (reference https://docs.sqlalchemy.org/en/14/orm/self_referential.html).
class Worker(declarative_base()):
__tablename__ = "staff_worker_info"
id = Column(Integer, primary_key=True)
first_name = Column(String(40), nullable=False)
last_name = Column(String(40), nullable=False)
invited_id = Column(Integer, ForeignKey('staff_worker_info.id'))
invited = relationship("Worker", uselist=False, remote_side=[id], join_depth=1, lazy='joined')
#property
def invited_name(self):
return self.invited.first_name
Then, if the query you want to do is more complex, and it requires you to create a column_property, you can also do it as follows (reference https://docs.sqlalchemy.org/en/14/orm/mapped_sql_expr.html):
from sqlalchemy import inspect
from sqlalchemy.orm import aliased
class Worker(declarative_base()):
__tablename__ = "staff_worker_info"
id = Column(Integer, primary_key=True)
first_name = Column(String(40), nullable=False)
last_name = Column(String(40), nullable=False)
invited_id = Column(Integer, ForeignKey('staff_worker_info.id'))
invited = relationship("Worker", uselist=False, remote_side=[id], join_depth=1)
# Requires alias
worker = aliased(Worker)
inspect(Worker).add_property(
"invited_name",
column_property(
select([worker.first_name]). \
where(worker.id == Worker.invited_id)
)
)
I found a method. But he did not like it.
invited_name = column_property(
select([text("invited_table.first_name")]).
where(text("invited_table.id = staff_worker_info.invited_id")).
select_from(text("staff_worker_info AS invited_table")).
label('invited_n'))

Database structure for voting of articles and comments

I'm quite new to relational databases and web programming in general and I'm facing a problem on how to structure my database for use of a voting system, similar to reddit.
I'm using sqlalchemy, the important bits are shown below:
class Vote(Base):
__tablename__ = 'votes'
id = Column(Integer, primary_key = True)
vote_type = Column(Integer, default = 0)
user_id = Column(Integer, ForeignKey('users.id'))
#article_id = Column(Integer, ForeignKey('article_items.id'))
#comment_id = Column(Integer, ForeignKey('comments.id'))
class ArticleItem(Base):
__tablename__ = 'article_items'
id = Column(Integer, primary_key = True)
vote_ups = Column(Integer, default = 0)
vote_downs = Column(Integer, default = 0)
article = relationship("Article",
uselist = False,
backref = 'article_item')
comments = relationship("Comment")
votes = relationship("Vote", cascade = "all")
class Comment(Base):
__tablename__ = 'comments'
id = Column(Integer, primary_key = True)
data = Column(Text, nullable = False)
replies = relationship("Comment")
vote_ups = Column(Integer, default = 0)
vote_downs = Column(Integer, default = 0)
votes = relationship("Vote", cascade = "all")
parent_id = Column(Integer, ForeignKey('comments.id'))
article_id = Column(Integer, ForeignKey('article_items.id'))
As it stands now, I have to include two foreign keys in class Vote, but only one of the two keys is used at any time.
Is there any easier/recommended way to do this? Should I keep two different vote types instead; one for comments and one for articles? Or should I maybe merge ArticleItem and Comment into one class Voteable?

SqlAlchemy Database Issue

I created a Table a Bmarks which has two foreign keys which have relation with same table Url_hash
class Hashed(Base):
__tablename__ = "url_hash"
hash_id = Column(Unicode(22), primary_key=True)
url = Column(UnicodeText)
clicks = Column(Integer, default=0)
def __init__(self, url):
cleaned_url = str(unidecode(url))
self.hash_id = unicode(generate_hash(cleaned_url))
self.url = url
class Bmark(Base):
__tablename__ = "bmarks"
bid = Column(Integer, autoincrement=True, primary_key=True)
hash_id = Column(Unicode(22), ForeignKey('url_hash.hash_id'))
clean_hash_id = Column(Unicode(22), ForeignKey('url_hash.hash_id'))
description = Column(UnicodeText())
extended = Column(UnicodeText())
stored = Column(DateTime, default=datetime.utcnow)
updated = Column(DateTime, onupdate=datetime.utcnow)
clicks = Column(Integer, default=0)
inserted_by = Column(Unicode(255))
username = Column(Unicode(255), ForeignKey('users.username'),
nullable=False,)
tag_str = Column(UnicodeText())
hashed = relation(Hashed,
foreign_keys="Bmark.hash_id",
backref="bmark",
uselist=False
)
clean_hashed = relation(Hashed,
foreign_keys="Bmark.clean_hash_id",
backref="bmark",
uselist=False
)
I am trying to store url after cleaning it a little bit like removing headers,utm parameters etc for indexing purposes
Error is occurring while creating the database
sqlalchemy.exc.ArgumentError: Error creating backref 'bmark' on relationship 'Bmark.clean_hashed': property of that name exists on mapper 'Mapper|Hashed|url_hash'
Actually the error message is very informative.
Just rename one of your backref="bmark" to something else like backref="my_clean_bmark".

Sqlalchemy many to many mapping with extra fields

I created a many to many relationship with sqlalchemy like this:
subject_books = Table('subject_books', Base.metadata,
Column('subject_id', Integer, ForeignKey('subjects.id')),
Column('book_id', Integer, ForeignKey('books.id')),
Column('group', Integer)
)
class Subject(Base):
__tablename__ = 'subjects'
id = Column(Integer, primary_key=True)
value = Column(Unicode(255), unique=True)
class Book(Base):
__tablename__ = 'books'
id = Column(Integer, primary_key=True)
title = Column(Unicode(255))
isbn = Column(Unicode(24))
subjects = relationship('Subject', secondary=subject_books, collection_class=attribute_mapped_collection('group'), backref='books')
after that I created a test like following:
book = Book(title='first book',isbn='test')
book.subjects[0] = Subject(value='first subject')
book.subjects[1] = Subject(value='second subject')
session.add(book)
transaction.commit()
and it works fine. But what I really want is to store more than one subject with the same group value, so I tried the following test:
book = Book(title='first book',isbn='test')
book.subjects[0] = [Subject(value='first subject'),Subject(value='second subject')]
book.subjects[1] = [Subject(value='third subject'),Subject(value='forth subject')]
session.add(book)
transaction.commit()
but it does not work.
Can this be done using sqlalchemy?
Thanks in Advance
Razi
I think you are constructing wrong relation ship.
Your relation ship must be
book M2M subject
subject M2M group
So you have to create one more model for group and that must be assign as m2m in Subject
Your models will be like.
subject_books = Table('subject_books', Base.metadata,
Column('subject_id', Integer, ForeignKey('subjects.id')),
Column('book_id', Integer, ForeignKey('books.id')),
)
subject_group = Table('subject_groups', Base.metadata,
Column('group_id', Integer, ForeignKey('groups.id')),
Column('subject_id', Integer, ForeignKey('subjects.id')),
)
class Subject(Base):
__tablename__ = 'subjects'
id = Column(Integer, primary_key=True)
value = Column(Unicode(255), unique=True)
groups = relationship('Groups', secondary=subject_groups, backref='subjects')
class Groups(Base):
__tablename__ = 'groups'
id = Column(Integer, primary_key=True)
name = Column(Unicode(255), unique=True)
class Book(Base):
__tablename__ = 'books'
id = Column(Integer, primary_key=True)
title = Column(Unicode(255))
isbn = Column(Unicode(24))
subjects = relationship('Subject', secondary=subject_books, backref='books')
I also check the docs for attribute_mapped_collection. But each time I found that each key is associated with only one object not more then one. If you read anywhere then please provide the link so I can check that how it will be fit in your code.
I think this will be help you.

Categories