SQLAlchemy mapping class relationship error - python

I am facing this InvalidRequestError that my relationships between two classes are not mapped properly. This is one(Question) to Many(Choice) relationship.
from sqlalchemy.ext.declarative import declartive_base
Base = declarative_base()
class ChoiceModel(Base):
__tablename__ = 'choice'
id_seq = Sequence('choice_id_seq', metadata=Base.metadata)
id = Column(Integer, id_seq, server_default=id_seq.next_value(), primary_key=True, index=True)
choice_context = Column(Text, nullable=False)
question_id = Column(Integer, ForeignKey('question.id'))
question = relationship('QuestionModel', back_populates='choices')
class QuestionModel(Base):
__tablename__ = 'question'
id_seq = Sequence('question_id_seq', metadata=Base.metadata)
id = Column(Integer, id_seq, server_default=id_seq.next_value(), primary_key=True, index=True)
correct = Column(Boolean, nullable=False)
choice_list = Column(ARRAY(Integer))
choices = relationship("ChoiceModel", back_populates='question', cascade="all, delete-orphan")
And the error I am getting,
sqlalchemy.exc.InvalidRequestError:
When initializing mapper mapped class ChoiceModel->choice,
expression 'Question' failed to locate a name ('Question').
If this is a class name, consider adding this relationship() to the
<class 'project.core.models.choice_model.ChoiceModel'> class after both dependent classes have been defined.
I have read SQLAlchemy official document similar questions in stackoverflow that most of them used wrong class name, or didn't have back_populates.
My questions are,
should I have to change my classes name, like Choice, Question instead of having postfix Model ??
If I mapped correctly, what causes this error?
I have set my PostgreSQL tables name, choice and question
*python==3.10
*ubuntu==20.04
*fastapi==0.73.0
*SQLAlchemy==1.4.31
*postgresql==14.2
Thanks in advance!

Related

How to properly define a many-to-many relationship in SQLAlchemy?

In my Flask application I am using SQLAlchemy, all tables are defined in one single file models.py:
training_ids_association_table = db.Table(
"training_ids_association",
db.Model.metadata,
Column("training_id", Integer, ForeignKey("training_sessions.id")),
Column("ids_id", Integer, ForeignKey("image_data_sets.id")),
)
class ImageDataSet(db.Model):
__tablename__ = "image_data_sets"
id = Column(Integer, primary_key=True)
trainings = relationship("Training", secondary=training_ids_association_table, back_populates="image_data_sets")
class TrainingSession(db.Model):
__tablename__ = "training_sessions"
id = Column(Integer, primary_key=True)
image_data_sets = relationship("DataSet", secondary=training_ids_association_table, back_populates="trainings")
So what I want to achieve here is a many-to-many relationship:
One ImageDataSet can belong to multiple TrainingSession's
One TrainingSession can include multiple ImageDataSet's
However, as soon as I call TrainingSession.query() in my code, the following error is raised:
Exception has occurred: InvalidRequestError
When initializing mapper mapped class ImageDataSet->image_data_sets, expression 'Training' failed to locate a name ('Training'). If this is a class name, consider adding this relationship() to the <class 'app.base.models.ImageDataSet'> class after both dependent classes have been defined.
I found some related threads here, but they are either asking for one-to-many relationships, or they define their tables in different files. Both is not the case here.
Any ideas what I am doing wrong?
You mispelled the names of the models, try this:
training_ids_association_table = db.Table(
"training_ids_association",
db.Model.metadata,
Column("training_id", Integer, ForeignKey("training_sessions.id")),
Column("ids_id", Integer, ForeignKey("image_data_sets.id")),
)
class ImageDataSet(db.Model):
__tablename__ = "image_data_sets"
id = Column(Integer, primary_key=True)
trainings = relationship("TrainingSession", secondary=training_ids_association_table, back_populates="image_data_sets")
class TrainingSession(db.Model):
__tablename__ = "training_sessions"
id = Column(Integer, primary_key=True)
image_data_sets = relationship("ImageDataSet", secondary=training_ids_association_table, back_populates="trainings")

Error when initializing mapper mapped SQLAlchemy

I have a table who has a reference to 2 other tables in a relation One to Many.
I've tried a lot things but i can't find a similar example of what I want to do. Bellow there is code and description when occurs the error.
Conversas.py:
from .model import db
from sqlalchemy.dialects.postgresql import JSON
class Conversa(db.Model):
__tablename__ = "conversas"
id = db.Column('ConversaId',db.Integer, primary_key=True)
fkEmpresa = db.Column('FkEmpresa', db.Integer, db.ForeignKey('empresas.empresaId'),nullable=False)
fkTipoUsuario = db.Column('FKTipoUsuario', db.Integer,db.ForeignKey('tiposUsuarios.tipoUsuarioId'),nullable=False)
# other fields
Empresas.py:
from .model import db
class Empresa(db.Model):
__tablename__ = "empresas"
id = db.Column('empresaId',db.Integer, primary_key=True)
#some fields
empresaCaracteristicas = db.relationship('EmpresaCaracteristica', backref='empresas', lazy=True)
conversas = db.relationship('Conversa', backref='empresas', lazy=True)
TipoUsuario.py:
class TipoUsuario(db.Model):
__tablename__ = "tiposUsuarios"
id = db.Column('tipoUsuarioId',db.Integer, primary_key=True)
#some fields
usuarios = db.relationship('Usuario', backref='tiposUsuarios', lazy=True)
conversas = db.relationship('Conversa', backref='tiposUsuarios', lazy=True)
I've tried to insert some values and this error occurs:
sqlalchemy.exc.InvalidRequestError: When initializing mapper mapped failed to locate a name ("name '' is not defined"). If this is a class name, consider adding this relationship() to the <class 'banco_nestorgr.tabelas.tipoUsuarioModel.TipoUsuario'> class after both dependent classes have been defined
The backref in class TipoUsuario(db.Model): shouldn't be the same
try to rename backref

Flask / SQLAlchemy - Request tables with many-to-many relationships

I used Flask and SQLAlchemy to create an application based on a database. Here is the classes that I have defined:
models.py
class HasTag(db.Model):
tagged_document_id = db.Column(db.Integer, db.ForeignKey('Document.id'), primary_key=True)
document_tag_id = db.Column(db.Integer, db.ForeignKey('Tag.id'), primary_key=True)
class Document(db.Model):
id = db.Column(db.Integer, unique=True, nullable=False, primary_key=True, autoincrement=True)
title = db.Column(db.Text)
tag = db.relationship("Tag",
secondary=HasTag,
back_populates="tagged_document",
lazy="dynamic")
class Tag(db.Model):
id = db.Column(db.Integer, unique=True, nullable=False, primary_key=True, autoincrement=True)
label = db.Column(db.String, nullable=False)
tagged_document = db.relationship("Document",
secondary=HasTag,
back_populates="tag",
lazy="dynamic")
In the application, I have an advanced search form where it is possible to do a full text search through the different fields of the Document table.
routes.py
#app.route("/search")
def search():
keyword = request.args.get("keyword", None)
query = Document.query
if keyword:
query = Document.query.filter(or_(
Document.title.like("%{}%".format(keyword)),
...
))
The thing is, I'd like to be able to search the keyword given by the user also in the label of the tag. I tried something like:
if keyword:
query = Document.query.join(Tag).filter(or_(
Document.title.like("%{}%".format(keyword)),
...,
Tag.label.like("%{}%".format(keyword))
))
But I get this error: AttributeError: 'HasTag' object has no attribute 'foreign_keys'
Can you help me? Thanks!
I have a similar structure in one of my projects, and this is how I define relatioship:
leagues = db.relationship("League",
secondary=LeagueTeamAssociation.__tablename__,
back_populates="teams")
So, You need to provide table name to secondary parameter, either using above syntax (You'll need to add __tablename__ to your HasTag class) or using string "has_tag" (provided that this is the name of the table in the database).

Self referential relationship including a relationship attribute

Situation
I have the Self-Referential Many-to-Many Relationship (almost identical to the sqlalchemy manual entry of the same heading). This relationship is governed by the table entity_weights. This code works!
Question
How do I include an attribute in class Entity representing the table column entity_weights.weight. Lets say the attribute would be called Entity.child_weights. It is important that the rank of Entity.child_entities and Entity.child_weights are identical.
entity_weights = Table('entity_weights', Base.metadata,
Column('id',Integer, primary_key=True),
Column('parent_entity_id',Integer, ForeignKey('entity.id')),
Column('entity_id',Integer, ForeignKey('entity.id')),
Column('weight',Float))
class Entity(Base):
__tablename__ = 'entity'
id = Column(Integer, primary_key=True)
name = Column(String)
domicile_id = Column(Integer, ForeignKey('domicile.id'))
entity_type = Column('type',Enum('asset','institution','model'))
source_table_id = Column(Integer)
child_entities = relationship('Entity',
secondary=entity_weights,
primaryjoin=id==entity_weights.c.parent_entity_id,
secondaryjoin=id==entity_weights.c.entity_id,
backref='parent_entity_id'
)
The cleanest solution I've found for this scenario is to break up the child_entities relationship by adding entity_weights as a one-to-many relationship on Entity and use an association proxy to proxy the weight value as well as the remote side of the many-to-many relationship:
class EntityWeight(Base):
__tablename__ = "entity_weights"
id = Column(Integer, primary_key=True)
parent_entity_id = Column(Integer, ForeignKey('entity.id'))
entity_id = Column(Integer, ForeignKey('entity.id'))
weight = Column(Float)
entity = relationship("Entity", primaryjoin=lambda: EntityWeight.entity_id == Entity.id)
class Entity(Base):
...
_child_weights = relationship(EntityWeight, primaryjoin=id == EntityWeight.parent_entity_id)
child_weights = association_proxy("_child_weights", "weight")
child_entities = association_proxy("_child_weights", "entity")

How to build many-to-many relations using SQLAlchemy: a good example

I have read the SQLAlchemy documentation and tutorial about building many-to-many relation but I could not figure out how to do it properly when the association table contains more than the 2 foreign keys.
I have a table of items and every item has many details. Details can be the same on many items, so there is a many-to-many relation between items and details
I have the following:
class Item(Base):
__tablename__ = 'Item'
id = Column(Integer, primary_key=True)
name = Column(String(255))
description = Column(Text)
class Detail(Base):
__tablename__ = 'Detail'
id = Column(Integer, primary_key=True)
name = Column(String)
value = Column(String)
My association table is (It's defined before the other 2 in the code):
class ItemDetail(Base):
__tablename__ = 'ItemDetail'
id = Column(Integer, primary_key=True)
itemId = Column(Integer, ForeignKey('Item.id'))
detailId = Column(Integer, ForeignKey('Detail.id'))
endDate = Column(Date)
In the documentation, it's said that I need to use the "association object". I could not figure out how to use it properly, since it's mixed declarative with mapper forms and the examples seem not to be complete. I added the line:
details = relation(ItemDetail)
as a member of Item class and the line:
itemDetail = relation('Detail')
as a member of the association table, as described in the documentation.
when I do item = session.query(Item).first(), the item.details is not a list of Detail objects, but a list of ItemDetail objects.
How can I get details properly in Item objects, i.e., item.details should be a list of Detail objects?
From the comments I see you've found the answer. But the SQLAlchemy documentation is quite overwhelming for a 'new user' and I was struggling with the same question. So for future reference:
ItemDetail = Table('ItemDetail',
Column('id', Integer, primary_key=True),
Column('itemId', Integer, ForeignKey('Item.id')),
Column('detailId', Integer, ForeignKey('Detail.id')),
Column('endDate', Date))
class Item(Base):
__tablename__ = 'Item'
id = Column(Integer, primary_key=True)
name = Column(String(255))
description = Column(Text)
details = relationship('Detail', secondary=ItemDetail, backref='Item')
class Detail(Base):
__tablename__ = 'Detail'
id = Column(Integer, primary_key=True)
name = Column(String)
value = Column(String)
items = relationship('Item', secondary=ItemDetail, backref='Detail')
Like Miguel, I'm also using a Declarative approach for my junction table. However, I kept running into errors like
sqlalchemy.exc.ArgumentError: secondary argument <class 'main.ProjectUser'> passed to to relationship() User.projects must be a Table object or other FROM clause; can't send a mapped class directly as rows in 'secondary' are persisted independently of a class that is mapped to that same table.
With some fiddling, I was able to come up with the following. (Note my classes are different than OP's but the concept is the same.)
Example
Here's a full working example
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.orm import declarative_base, relationship, Session
# Make the engine
engine = create_engine("sqlite+pysqlite:///:memory:", future=True, echo=False)
# Make the DeclarativeMeta
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String)
projects = relationship('Project', secondary='project_users', back_populates='users')
class Project(Base):
__tablename__ = "projects"
id = Column(Integer, primary_key=True)
name = Column(String)
users = relationship('User', secondary='project_users', back_populates='projects')
class ProjectUser(Base):
__tablename__ = "project_users"
id = Column(Integer, primary_key=True)
notes = Column(String, nullable=True)
user_id = Column(Integer, ForeignKey('users.id'))
project_id = Column(Integer, ForeignKey('projects.id'))
# Create the tables in the database
Base.metadata.create_all(engine)
# Test it
with Session(bind=engine) as session:
# add users
usr1 = User(name="bob")
session.add(usr1)
usr2 = User(name="alice")
session.add(usr2)
session.commit()
# add projects
prj1 = Project(name="Project 1")
session.add(prj1)
prj2 = Project(name="Project 2")
session.add(prj2)
session.commit()
# map users to projects
prj1.users = [usr1, usr2]
prj2.users = [usr2]
session.commit()
with Session(bind=engine) as session:
print(session.query(User).where(User.id == 1).one().projects)
print(session.query(Project).where(Project.id == 1).one().users)
Notes
reference the table name in the secondary argument like secondary='project_users' as opposed to secondary=ProjectUser
use back_populates instead of backref
I made a detailed writeup about this here.
Previous Answer worked for me, but I used a Class base approach for the table ItemDetail. This is the Sample code:
class ItemDetail(Base):
__tablename__ = 'ItemDetail'
id = Column(Integer, primary_key=True, index=True)
itemId = Column(Integer, ForeignKey('Item.id'))
detailId = Column(Integer, ForeignKey('Detail.id'))
endDate = Column(Date)
class Item(Base):
__tablename__ = 'Item'
id = Column(Integer, primary_key=True)
name = Column(String(255))
description = Column(Text)
details = relationship('Detail', secondary=ItemDetail.__table__, backref='Item')
class Detail(Base):
__tablename__ = 'Detail'
id = Column(Integer, primary_key=True)
name = Column(String)
value = Column(String)
items = relationship('Item', secondary=ItemDetail.__table__, backref='Detail')

Categories