SqlAlchemy many to many relation with mm table - python

I am still a beginner in Python and I am stuck with the following relation.
Three tables:
tx_bdproductsdb_domain_model_product
sys_category
sys_category_record_mm
sys_category class looks like this:
class Category(Base):
__tablename__ = "sys_category"
uid = Column(
Integer,
ForeignKey("sys_category_record_mm.uid_local"),
primary_key=True,
autoincrement=True,
)
title = Column(String)
products = relationship(
"Product",
uselist=False,
secondary="sys_category_record_mm",
back_populates="categories",
foreign_keys=[uid],
)
Products looks like this:
class Product(Base):
__tablename__ = "tx_bdproductsdb_domain_model_product"
uid = Column(
Integer,
ForeignKey(SysCategoryMMProduct.uid_foreign),
primary_key=True,
autoincrement=True,
)
category = Column(Integer)
categories = relationship(
Category,
secondary=SysCategoryMMProduct,
back_populates="products",
foreign_keys=[uid],
)
And here is the mm table class that should link the two.
class SysCategoryMMProduct(Base):
__tablename__ = "sys_category_record_mm"
uid_local = Column(Integer, ForeignKey(Category.uid), primary_key=True)
uid_foreign = Column(
Integer, ForeignKey("tx_bdproductsdb_domain_model_product.uid")
)
fieldname = Column(String)
I'm currently stuck, does anyone have any ideas? I get the following messages in the console:
sqlalchemy.exc.NoForeignKeysError: Could not determine join condition between parent/child tables on relationship Category.products - there are no foreign keys linking these tables via secondary table 'sys_category_record_mm'. Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or specify 'primaryjoin' and 'secondaryjoin' expressions.
root#booba:/var/pythonWorks/crawler/develop/releases/current# python3 Scraper2.py
Traceback (most recent call last):
File "/usr/local/lib/python3.8/dist-packages/sqlalchemy/orm/relationships.py", line 2739, in _determine_joins
self.secondaryjoin = join_condition(
File "<string>", line 2, in join_condition
File "/usr/local/lib/python3.8/dist-packages/sqlalchemy/sql/selectable.py", line 1229, in _join_condition
raise exc.NoForeignKeysError(
sqlalchemy.exc.NoForeignKeysError: Can't find any foreign key relationships between 'tx_bdproductsdb_domain_model_product' and 'sys_category_record_mm'.
sqlalchemy.exc.NoForeignKeysError: Could not determine join condition between parent/child tables on relationship Category.products - there are no foreign keys linking these tables via secondary table 'sys_category_record_mm'. Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or specify 'primaryjoin' and 'secondaryjoin' expressions.
Thank you :)

When using an association class you should reference the association directly. You need this instead of secondary because you have data associated with the link (ie. fieldname). I changed some of your naming schema to make it more clear.
There is a pretty good explanation of the association pattern in the sqlalchemy docs. There is a big red warning at the end of that section about mixing the use of the secondary and the Association pattern.
I use backref="related_categories" to automatically create the property related_categories on Product. This is a list of association objects, and not actual categories.
from sqlalchemy import (
create_engine,
Integer,
String,
ForeignKey,
)
from sqlalchemy.schema import (
Column,
)
from sqlalchemy.orm import declarative_base, relationship
from sqlalchemy.orm import Session
Base = declarative_base()
# This connection string is made up
engine = create_engine(
'postgresql+psycopg2://user:pw#/db',
echo=False)
class Category(Base):
__tablename__ = "categories"
uid = Column(
Integer,
primary_key=True,
autoincrement=True,
)
title = Column(String)
class Product(Base):
__tablename__ = "products"
uid = Column(
Integer,
primary_key=True,
autoincrement=True,
)
title = Column(String)
class SysCategoryMMProduct(Base):
__tablename__ = "categories_products"
uid = Column(Integer, primary_key=True)
category_uid = Column(Integer, ForeignKey("categories.uid"))
product_uid = Column(Integer, ForeignKey("products.uid"))
fieldname = Column(String)
product = relationship(
"Product",
backref="related_categories",
)
category = relationship(
"Category",
backref="related_products",
)
Base.metadata.create_all(engine)
with Session(engine) as session:
category = Category(title="kitchen")
session.add(category)
product = Product(title="spoon")
session.add(product)
association = SysCategoryMMProduct(
product=product,
category=category,
fieldname="Extra metadata")
session.add(association)
session.commit()
category = session.query(Category).first()
assert len(category.related_products) == 1
assert category.related_products[0].product.related_categories[0].category == category
q = session.query(Category).join(Category.related_products).join(SysCategoryMMProduct.product).filter(Product.title == "spoon")
print (q)
assert q.first() == category
The last query looks like:
SELECT categories.uid AS categories_uid, categories.title AS categories_title
FROM categories JOIN categories_products ON categories.uid = categories_products.category_uid JOIN products ON products.uid = categories_products.product_uid
WHERE products.title = 'spoon'

Related

How to specify the join condition on a many-to-many relationship using SQLAlchemy

Here is my model:
user_map = Table(
"user_map",
Column('user_id', Integer, ForeignKey('user.id'), primary_key=True),
Column('map_id', Integer, ForeignKey('map.id'), primary_key=True),
PrimaryKeyConstraint('user_id', 'map_id', name='pk_user_map')
)
class Map(Base):
id = Column(Integer, primary_key=True)
name = Column(String)
owner_id = Column(Integer, ForeignKey('user.id'))
shared_maps = relationship(
'User',
secondary=user_map,
backref=backref('maps', lazy='dynamic')
)
class User(Base):
id = Column(Integer, primary_key=True)
name = Column(String)
email = Column(String, unique=True)
shared_maps = Map.query.filter(Map.shared_maps.any()).all()
I want to query the user_map table, using the join condition "Map.id == user_map.map_id", but SQLAlchemy is trying to join using "Map.id == user_map.map_id and Map.owner_id == user_map.user_id". How can I specify my join condition?
I tried to use primaryjoin attribute in the relationship and to specify the condition inside the .join() but without success. Thanks in advance!
Based on your code, I've rebuilt the setup; I guess your relationships were mixed up. Furthermore, I've hardly ever seen primary keys (or PrimaryKeyConstraints) in sqlalchemy's many-to-many association tables. It may make sense from a non-orm perspective, but as far as I know, it is unusual or even not required at all.
import sqlalchemy as sa
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, sessionmaker
Base = declarative_base()
UsersXMaps = sa.Table(
'users_x_maps',
Base.metadata,
sa.Column('user', sa.Integer, sa.ForeignKey('users.id')),
sa.Column('map', sa.Integer, sa.ForeignKey('maps.id'))
)
class User(Base):
__tablename__ = 'users'
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.String)
mail = sa.Column(sa.String, unique=True)
own_maps = relationship('Map', back_populates='owner')
maps = relationship(
'Map',
secondary=UsersXMaps,
back_populates='users'
)
def __str__(self):
return '{} ({}) with {} maps'.format(
self.name, self.mail, len(self.own_maps))
class Map(Base):
__tablename__ = 'maps'
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.String)
owner_id = sa.Column(sa.Integer, sa.ForeignKey('users.id'))
owner = relationship('User', back_populates='own_maps')
users = relationship(
'User',
secondary=UsersXMaps,
back_populates='maps'
)
def __str__(self):
return '{} (by {})'.format(self.name, self.owner.name)
So far for the setup; I've extended it a bit for proper output when printing strings. Additionally, your Map.shared_maps relationship actually refers to Users, not Maps, so I also renamed that one.
When binding your association table to the two classes, you can refer to it from both sides (even though back_populates seems to overwrite/replace the original definition) - this simplifies joins from either side.
Executing the following works as expected:
if __name__ == '__main__':
engine = sa.create_engine('sqlite:///usermaps.db')
sfactory = sessionmaker(engine)
session = sfactory()
Base.metadata.create_all(bind=engine)
bilbo = User(id=1, name='Bilbo', mail='bilbo#shire.nz')
frodo = User(id=2, name='Frodo', mail='frodo#shire.nz')
mordor = Map(id=1, name='Mordor', owner=frodo, users=[bilbo, frodo])
gondor = Map(id=2, name='Gondor', owner=bilbo, users=[bilbo, frodo])
rohan = Map(id=3, name='Rohan', owner=bilbo, users=[bilbo, frodo])
session.add_all([frodo, bilbo, mordor, gondor, rohan])
session.commit()
print('Maps by owner:')
for owner in [bilbo, frodo]:
print(owner)
for item in session.query(Map).filter(Map.owner == owner).all():
print(' - ' + str(item))
print('Maps by users:')
for item in session.query(Map).filter(Map.users.any()).all():
print(' - ' + str(item))
The output is:
Maps by owner:
Bilbo (bilbo#shire.nz) with 2 maps
- Gondor (by Bilbo)
- Rohan (by Bilbo)
Frodo (frodo#shire.nz) with 1 maps
- Mordor (by Frodo)
Maps by users:
- Mordor (by Frodo)
- Gondor (by Bilbo)
- Rohan (by Bilbo)

Error in foreign key constraint with SQLAlchemy

I am trying to implement very simple example table from an old course now in SQLAlchemy...
I have got this far but when I run the code...
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Date, MetaData
from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy import create_engine
Base = declarative_base()
engine = create_engine('mysql://x # amazonaws.com:3306/db', echo=True)
class Guest(Base):
__tablename__ = "guests"
guest_no = Column(String(4), primary_key=True)
g_name = Column(String(20))
g_address = Column(String(30))
booking = relationship("Booking", back_populates="guests")
class Hotel(Base):
__tablename__ = "hotels"
hotel_no = Column(String(4), primary_key=True)
h_name = Column(String(20))
h_address = Column(String(30))
room = relationship("Room", back_populates="hotels")
booking = relationship("Booking", back_populates="hotels")
class Room(Base):
__tablename__ = "rooms"
hotel_no = Column(String(4), ForeignKey('hotels.hotel_no'), primary_key=True)
room_no = Column(String(4), primary_key=True)
r_type = Column(String(1))
r_price = Column(Integer)
hotel = relationship("Hotel", back_populates="rooms")
booking = relationship("Booking", back_populates="rooms")
class Booking(Base):
__tablename__ = "bookings"
hotel_no = Column(String(4), ForeignKey('hotels.hotel_no'), primary_key=True)
guest_no = Column(String(4), ForeignKey('guests.guest_no'), primary_key=True)
date_form = Column(Date, primary_key=True)
date_to = Column(Date)
room_no = Column(String(4), ForeignKey('rooms.room_no'), primary_key=True)
hotel = relationship("Hotel", back_populates="bookings")
guest = relationship("Guest", back_populates="bookings")
room = relationship("Room", back_populates="bookings")
Base.metadata.create_all(engine)
it gives me an error about the room_no foreign key...
2017-09-11 16:16:03 2b8010c29700 Error in foreign key constraint of table db/bookings:
FOREIGN KEY(room_no) REFERENCES rooms (room_no)
):
Cannot find an index in the referenced table where the
referenced columns appear as the first columns, or column types
in the table and the referenced table do not match for constraint.
Note that the internal storage type of ENUM and SET changed in
tables created with >= InnoDB-4.1.12, and such columns in old tables
cannot be referenced by such columns in new tables.
See http://dev.mysql.com/doc/refman/5.6/en/innodb-foreign-key-constraints.html
for correct foreign key definition.
I looked around a bit and I made sure they were both the same type (they were) and were both primary keys (they previously were not) but the error persists.
Does anyone have insight into what is causing this?
Because rooms has a composite primary key: (hotel_no, room_no) you'll need to specify both columns in your foreign key relationship on the booking table:
__table_args__ = (
ForeignKeyConstraint(
['hotel_no', 'room_no'],
['rooms.hotel_no', 'rooms.room_no']
),
)

sqlalchemy error (sqlalchemy.exc.NoForeignKeysError)

I am playing with a toy example to see the back populates in action but hitting an error that I can't understand. Below I have two 'models' that back populate each other. When I try to create a User object it throws an error. What am I missing?
"sqlalchemy.exc.NoForeignKeysError: Could not determine join condition between parent/child tables on relationship Child.user - there are no foreign keys linking these tables. Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or specify a 'primaryjoin' expression."
engine = create_engine('sqlite:///:memory:', echo=False)
Session = sessionmaker(bind=engine)
session = Session()
Base = declarative_base(bind=engine)
class User(Base):
__tablename__ = 'user'
user_id = Column("UserID", Integer, primary_key=True)
name = Column("Name", String(50))
age = Column("Age", SmallInteger)
child = relationship("Child", back_populates="user")
class Child(Base):
__tablename__ = 'child'
child_id = Column("ChildID", Integer, primary_key=True)
school = Column("School", String)
grade = Column("Grade", String)
user_id = Column("UserID", Integer, ForeignKey('User.UserID'), index=True, nullable=True)
user = relationship("User", back_populates="child")
ForeignKey requires the table and column name, not model and attribute name, so it should be:
user_id = Column("UserID", Integer, ForeignKey('user.UserID'), ...)
because your User model has a table name of user.

Filter on many-many relationship of parent with sqlalchemy

Suppose I have the following three classes describing a music collection:
from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint
from sqlalchemy import Table
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
TRACK_NAME_LEN = 64
ALBUM_NAME_LEN = 64
TAG_NAME_LEN = 64
URL_LEN = 255
Base = declarative_base()
class Track(Base):
__tablename__ = 'tracks'
id = Column(Integer, primary_key=True)
name = Column(String(TRACK_NAME_LEN), nullable=False)
# many -> one
album_id = Column(Integer, ForeignKey('albums.id'), nullable=True)
album = relationship('Album', back_populates='tracks')
# Auxiliary table for many <--> many relationship
album_tag = Table('album_tag', Base.metadata,
Column('album_id', Integer, ForeignKey('albums.id')),
Column('tag_id', Integer, ForeignKey('tags.id')))
class Album(Base):
__tablename__ = 'albums'
id = Column(Integer, primary_key=True)
name = Column(String(ALBUM_NAME_LEN), nullable=False)
# one -> many
tracks = relationship('Track', back_populates='album')
# many -> many
tags = relationship(
'Tag',
secondary=album_tag,
back_populates='albums')
class Tag(Base):
__tablename__ = 'tags'
id = Column(Integer, primary_key=True)
name = Column(String(TAG_NAME_LEN), nullable=False, unique=True)
# many -> many
albums = relationship(
'Album',
secondary=album_tag,
back_populates='tags')
An album may have many different tags.
I'd like a query returning all tracks whose album has a given tag.
Here's something that doesn't work:
rock = session.query(Tag).filter(name='rock').one()
session.query(Track).join(Album).filter(Album.tags.any(rock))
There are any number of other failed attempts.
How do we achieve this?
This works:
session.query(Track).filter(Track.album.has(Album.tags.any(name='tango'))).all()

Trouble mapping multiple foreign keys to parent table

I have two tables, users and contacts. I query the contacts table and get a list of a user's contacts. I would then like to be able to write Contact.first_name (where first_name is a row from the users table) and print out that contact's first name.
Currently, my Contact object does not recognize any attributes of the user table.
Here is some code:
class User(Base):
""" Basic User definition """
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
first_name = Column(Unicode(255))
last_name = Column(Unicode(255))
contacts = relationship('Contact', backref='users')
class Contact(Base):
__tablename__ = 'contacts'
id = Column(Integer, primary_key=True)
user_id = Column(Integer)
contact_id = Column(Integer)
__table_args__ = (ForeignKeyConstraint([id], [User.id]), {})
Here is my query:
Contact.query.filter(Contact.user_id == self.user_id).filter(Contact.state == True).all()
To be honest, I'm unsure of how to properly map my two foreign keys Contact.user_id and Contact.contact_id to the User.id row. Maybe this is the source of my problem?
I'm very new to using SQLAlchemy, so this is a learning experience here. Thanks for your help.
What you have here is class User which essentially refers to itself. In other words, it's a self-referential many-to-many relationship. Your model definitions should look like this:
# This is so called association table, which links two tables in many-to-many
# relationship. In this case it links same table's ('users') different rows.
user_contacts = Table(
'user_contacts', Base.metadata,
Column('user_id', Integer, ForeignKey('users.id'), primary_key=True),
Column('contact_id', Integer, ForeignKey('users.id'), primary_key=True),
)
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
first_name = Column(String)
last_name = Column(String)
contacts = relationship(
'User',
secondary=user_contacts,
primaryjoin=id==user_contacts.c.user_id,
secondaryjoin=id==user_contacts.c.contact_id
)
Then you can do things like the following:
u1 = User(first_name='Foo', last_name='Foo')
u2 = User(first_name='Bar', last_name='Bar')
u3 = User(first_name='Baz', last_name='Baz')
u1.contacts = [u2, u3]
session.add(u1)
session.commit()
# ... and in some other place in your code ...
u = User.query.get(1)
print u.contacts[0].first_name

Categories