Many to one relationships within table in SqlAlchemy - python-3.x

I am trying to create a model class Document which optionally contains 2 pointers to 2 other elements of the same class (table).
original_id which should hold the index of another member of the table which is "the" original version of the document (can be null).
first_id which should hold the index of another member of the table which is the "first" document that was stored (can be null).
I defined my class as
class Document(Base):
__tablename__ = 'documents'
id = Column(Integer, primary_key=True)
name = Column(String)
original_id = Column(Integer, ForeignKey('documents.id'))
original = relationship('Document', foreign_keys= original_id)
first_id = Column(Integer, ForeignKey('documents.id'))
first = relationship('Document', foreign_keys= first_id)
def __repr__(self):
return f'{self.name} ({self.id})'
But when I test it as follows:
d1 = Document(name='Dee one')
d2 = Document(name='Dee two')
d1.original.append(d2) # d1 has for original document d2
I am surprised by the implementation in terms of ids stored in what row (after committing)
>>> d1.original_id
None
>>> d2.original_id
1
>>> d1.original
[Dee two (2)]
>>> d2.original
[]
I wanted d1.original_id to hold 2 (the index for Dee two).
Obviously I am doing something wrong, but I am struggling to understand what. It seems I am ending up with a many to one relation but in the wrong direction.
I am using SQLAlchemy 1.3.5

Originally, I wanted to have a class that contains 2 nullable pointers to other Nodes. To make the relationship clearer, let's call the class Person, and the links Father and Mother. Every person has at most a known Father and a known Mother.
So following the docs, I created the class as
class Person(Base):
__tablename__ = 'persons'
id = Column(Integer, primary_key=True)
name = Column(String)
father_id = Column(Integer, ForeignKey('persons.id'))
father = relationship('Person')
When doing so, the father relationship is 1-to-many, i.e. a Person can have many fathers, and a father can be only the father of 1. Which is not what is expected.
Following Many to One relationship with SQLAlchemy in the same table I found a way to make that a 1 to many relationship. Enter the remote_side parameter:
class Person(Base):
__tablename__ = 'persons'
id = Column(Integer, primary_key=True)
name = Column(String)
father_id = Column(Integer, ForeignKey('persons.id'))
father = relationship('Person', remote_side=id)
^^^^^^^^^^^^^^
Now the relationship is as expected. However with multiple links (father and mother), this fails again. I.e. if I refine the class as:
class Person(Base):
__tablename__ = 'persons'
id = Column(Integer, primary_key=True)
name = Column(String)
father_id = Column(Integer, ForeignKey('persons.id'))
father = relationship('Person', remote_side= id)
mother_id = Column(Integer, ForeignKey('persons.id'))
mother = relationship('Person', remote_side= id)
SQLAlchemy fails with an "AmbiguousForeignKeysError". The solution is then to specify what field is used to link to father and mother, as follows:
class Person(Base):
__tablename__ = 'persons'
id = Column(Integer, primary_key=True)
name = Column(String)
father_id = Column(Integer, ForeignKey('persons.id'))
father = relationship('Person', remote_side= id, foreign_keys=father_id)
^^^^^^^^^^^^^^^^^^^^^^
mother_id = Column(Integer, ForeignKey('persons.id'))
mother = relationship('Person', remote_side= id, foreign_keys=mother_id)
^^^^^^^^^^^^^^^^^^^^^^
Now that works as expected.
So in some way, this is an extension of the solution provided in Many to One relationship with SQLAlchemy in the same table
Many thanks Gord Thomson for your remarks.

Related

How to get a one-to-one relationship?

There are for example 2 tables: Parent , Child
I'm trying to build a one-to-one relationship.
class Parent(Base):
__tablename__ = "parent"
id = Column(Integer, primary_key=True)
class Child(Base):
__tablename__ = "child"
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey("parent.id"))
parent = relationship("Parent", backref=backref("child", uselist=False))
I'm trying to build a one-to-one relationship.
parent_one = Parent()
db.add(parent_one)
db.commit()
child = Child(parent=parent_one)
db.add(child)
db.commit()
The code above will work and in the child table I will see: enter image description here
But if I execute the following code, then I won't get any exceptions (even though Parent 1 already has Child 1):
parent_one = db.query(Parent).filter(Parent.id == 1).first()
child = Child(parent=parent_one)
db.add(child)
db.commit()
And in the child table I will see: - enter image description here
I was expecting an exception and this confuses me even more... Why is this happening?

SQLAlchemy how to use a "fully module-qualified path"?

I'm contributing to a project that is using sqlalchemy. This project has a model.py file where they define all their classes, for example Foobar(BASE). Now, I've created another module mymodel.py where I need to extend some of those classes. So for example, in mymodule.py I have Foobar(model.Foobar) which I use to extend the parent class with new properties and functions. The problem is that when using either of those classes, I get this error from sqlalchemy: sqlalchemy.exc.InvalidRequestError: Multiple classes found for path "Foobar" in the registry of this declarative base. Please use a fully module-qualified path..
My question then is, how can I fix this error without renaming my classes? Since they are defined in a different module, how do I use a "fully module-qualified path"?
Faced the same issue, and also struggled to understand what the error is referring to by "fully module-qualified path" or where should I make the change.
After some trial and error, the key insight for me was that it has to do with SQLAlchemy relationships. Furthermore, in my case it also had to do with having the same table name in different schemas (I was using Postgres).
Simplified scenario that triggered the error:
# database/models/family_1.py
from database import MyOneAndOnlyBase
...
class Parent(MyOneAndOnlyBase):
__tablename__ = "parent"
id = Column(Integer, primary_key=True)
children = relationship("Child")
__table_args__ = {"schema": "family_1"}
class Child(MyOneAndOnlyBase):
__tablename__ = "child"
id = Column(Integer, primary_key=True)
__table_args__ = {"schema": "family_1"}
# database/models/family_2.py
from database import MyOneAndOnlyBase
...
class Parent(MyOneAndOnlyBase):
__tablename__ = "parent"
id = Column(Integer, primary_key=True)
children = relationship("Child")
__table_args__ = {"schema": "family_2"}
class Child(MyOneAndOnlyBase):
__tablename__ = "child"
id = Column(Integer, primary_key=True)
__table_args__ = {"schema": "family_2"}
And the way to fix it using a fully module-qualified path:
# database/models/family_1.py
from database import MyOneAndOnlyBase
...
class Parent(MyOneAndOnlyBase):
__tablename__ = "parent"
id = Column(Integer, primary_key=True)
children = relationship("database.models.family_1.Child")
__table_args__ = {"schema": "family_1"}
class Child(MyOneAndOnlyBase):
__tablename__ = "child"
id = Column(Integer, primary_key=True)
__table_args__ = {"schema": "family_1"}
# database/models/family_2.py
from database import MyOneAndOnlyBase
...
class Parent(MyOneAndOnlyBase):
__tablename__ = "parent"
id = Column(Integer, primary_key=True)
children = relationship("database.models.family_2.Child")
__table_args__ = {"schema": "family_2"}
class Child(MyOneAndOnlyBase):
__tablename__ = "child"
id = Column(Integer, primary_key=True)
__table_args__ = {"schema": "family_2"}
As stated by SQLAlchemy there a two classes found named Foobar. One is model.Foobar and the second one is mymodel.Foobar. You need to use the fully module-qualified path which is mymodel.Foobar to reference the new class.

I am not able to load Self Reference in SQLAlchemy

I currently having an issue with a self reference relationship.
I have the table Customer which can have a parent (also a Customer) like this:
class CustomerModel(db.Model):
__tablename__ = 'customer'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('customer.id'))
parent = relationship("CustomerModel", foreign_keys=[parent_id])
So my problem is that when I'm trying to load the parent, the following query is built by SQLAlchemy:
Lets take this customer for example: Customer(id=1, parent_id=10)
SELECT *
FROM customer
WHERE 1 = customer.parent_id
So the WHERE condition is wrong because it compares the parent_id to the id of the customer I'm trying to load the parent from.
The correct query should be:
SELECT *
FROM customer
WHERE 10 = customer.parent_id
What am I doing wrong?
So i finally found the answer.
I need to add the param remote_side like this:
class CustomerModel(db.Model):
__tablename__ = 'customer'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('customer.id'))
parent = relationship("CustomerModel", foreign_keys=[parent_id], remote_side=[id])

Flask ImportError: cannot import name [duplicate]

I am trying to make a circular one-to-one relationship (not sure what the correct term is) with SQLAlchemy that looks the following:
class Parent(Base):
__tablename__ = 'parents'
id = db.Column(Integer, primary_key=True)
child_id = db.Column(db.Integer,db.ForeignKey("children.id", use_alter=True))
child = db.relationship("Child",
uselist=False,
foreign_keys=[child_id],
post_update=True)
class Child(Base):
__tablename__ = 'children'
id = db.Column(db.Integer, primary_key=True)
parent_id = db.Column(db.Integer, db.ForeignKey("parents.id"))
user = db.relationship("Parent",
uselist=False,
foreign_keys=[parent_id])
Everything works as expected until I try to db.drop_all() and I get an error that the sqlalchemy.sql.schema.ForeignKeyConstraint name is None. Am I doing something wrong when trying to make this circular one-to-one relationship? I would really like to be able to query just the single column to get the id of the other one, hence the circular reference.
From the SQLAlchemy Docs:
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
child = relationship("Child", uselist=False, back_populates="parent")
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('parent.id'))
parent = relationship("Parent", back_populates="child")
Then you can Parent.child or Child.parent all day long

How to use order_by in SQLAlchemy when using relationship() with secondary-parameter (many-to-many)?

I create a many-to-many relationship between a Person (as an author) and a Reference (as a scientific publication).
ReferenceAuthor = sa.Table('ReferenceAuthor', _Base.metadata,
sa.Column('ReferenceID', sa.Integer, sa.ForeignKey('Reference.ID'), primary_key=True),
sa.Column('PersonID', sa.Integer, sa.ForeignKey('Person.ID'), primary_key=True),
sa.Column('Index', sa.Integer)
)
class Reference(_Base):
__tablename__ = 'Reference'
_id = sa.Column('ID', sa.Integer, primary_key=True)
_authors = sao.relationship('Person', secondary=ReferenceAuthor)
class Person(_Base):
__tablename__ = 'Person'
_id = sa.Column('ID', sa.Integer, primary_key=True)
I would like to have the objects in Reference().authors ordered by ReferenceAuthor.Index.
The three tables here come from a foreign sqlite3-database. I am not able to change the structure of that tables. I have to deal with them like they are.
I could finde some examples about order_by in relationship() but nothing combined with the use of secondary=.
I tried an association_proxy and read the docs about that.
class ReferenceAuthor(_Base):
__tablename__ = 'ReferenceAuthor'
_reference_id = sa.Column('ReferenceID', sa.Integer, sa.ForeignKey('Reference.ID'), primary_key=True)
_person_id = sa.Column('PersonID', sa.Integer, sa.ForeignKey('Person.ID'), primary_key=True)
_index = sa.Column('Index', sa.Integer)
_person = sao.relationship('Person')
class Reference(_Base):
__tablename__ = 'Reference'
_id = sa.Column('ID', sa.Integer, primary_key=True)
_authors = saa.association_proxy('ReferenceAuthor', 'Person')
When I read the docs I think I understand the concept behind it. But with my code it doesn't work and I don't understand why. The error is
sqlalchemy.exc.InvalidRequestError: Mapper 'Mapper|Reference|Reference' has no property 'ReferenceAuthor'.
Solution 1:
Depending on the nice comment-answer of #univerio:
class ReferenceAuthor(_Base):
__tablename__ = 'ReferenceAuthor'
_reference_id = sa.Column('ReferenceID', sa.Integer, sa.ForeignKey('Reference.ID'), primary_key=True)
_person_id = sa.Column('PersonID', sa.Integer, sa.ForeignKey('Person.ID'), primary_key=True)
_index = sa.Column('Index', sa.Integer)
_person = sao.relationship('Person')
class Reference(_Base):
__tablename__ = 'Reference'
_id = sa.Column('ID', sa.Integer, primary_key=True)
_reference_authors = sao.relationship('ReferenceAuthor',
order_by=ReferenceAuthor._index)
_authors = saa.association_proxy('_reference_authors', '_person')
Solution 2:
Got this hint from the sqla-mailinglist. Depending on the first code in my initial question:
_authors = sao.relationship('Person',
secondary=ReferenceAuthor,
order_by=ReferenceAuthor.c.Index)
This looks quite simpler.
Side question: What is the difference between that two solutions?

Resources