sqlalchemy.exc.CircularDependencyError: Circular dependency detected - python

The business logic - One Category may have multiple (1:M) attributes, like Category "Memory" could have attributes Speed, Size, Type etc.
at the same time one Category could be sorted by the attribute value (this is stored inside Category.sortByAttribute - which is foreign key to LookupCategoryAttributes table.
Trying to construct it via SQLAlchemy, but getting circular dependency detected. What is wrong?
class Attribute(Base):
__tablename__ = "LookupCategoryAttributes"
types = ["date", "float", "integer", "select", "string", "text"]
# Properties
ID = Column(BigInteger, primary_key=True)
categoryID = Column(BigInteger, ForeignKey('LookupCategories.ID'), nullable=False )
attribute = Column(VARCHAR(255), nullable=False)
listValues = Column(VARCHAR(4000))
typeID = Column(VARCHAR(40), nullable=False)
isRequired = Column(SmallInteger, nullable=False, default=0)
displayInMenu = Column(SmallInteger, nullable=False, default=0)
displayInFilter = Column(SmallInteger, nullable=False, default=0)
class Category(Base):
__tablename__ = "LookupCategories"
# Properties
ID = Column(BigInteger, primary_key=True)
category = Column(VARCHAR(255), nullable=False)
description = Column(VARCHAR(1000), nullable=False)
parentCategoryID = Column(BigInteger, ForeignKey('LookupCategories.ID'))
leftPos = Column(Integer)
rightPos = Column(Integer)
sortByAttribute = Column(BigInteger, ForeignKey('LookupCategoryAttributes.ID'))
sortOrder = Column(SmallInteger, default=1)
# Relationships
ParentCategory = relationship("Category", uselist=False, remote_side=[ID], backref='SubCategories')
SortByAttribute = relationship("Attribute", uselist=False, foreign_keys=[sortByAttribute], primaryjoin="Attribute.ID==Category.sortByAttribute")
Attributes = relationship("Attribute", backref="Category", primaryjoin="Attribute.categoryID==Category.ID")
and then the code looks like this:
category = Category(record['Name'], extID=extID)
attr1 = Attribute(v)
attr2 = Attribute(v)
category.Attributes.append(attr1)
category.Attributes.append(attr2)
category.SortByAttribute = attr1
when I execute commit I get:
sqlalchemy.exc.CircularDependencyError: Circular dependency detected.

Okay found the answer - use post_update in relationship
http://docs.sqlalchemy.org/en/latest/orm/relationship_persistence.html#post-update
so what I did is inside Category class is changed this:
SortByAttribute = relationship(
"Attribute",
uselist=False,
foreign_keys=[sortByAttribute],
primaryjoin="Attribute.ID==Category.sortByAttribute"
)
to this:
SortByAttribute = relationship(
"Attribute",
uselist=False,
foreign_keys=[sortByAttribute],
primaryjoin="Attribute.ID==Category.sortByAttribute",
post_update=True
)

Related

SqlAlchemy - two foreign keys and bidirectional relationship

I have a small base for a website for a real estate agency, below are two tables:
class Person(Base):
__tablename__ = "people"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, nullable=False)
surname = Column(String, nullable=False)
city = Column(String, nullable=True)
# TODO - add lists
class Property(Base):
__tablename__ = "properties"
id = Column(Integer, primary_key=True, index=True)
city = Column(String, nullable=False)
address = Column(String, nullable=False)
owner_id = Column(Integer, ForeignKey("people.id"), nullable=False)
manager_id = Column(Integer, ForeignKey("people.id"), nullable=False)
# TODO - rework
owner = relationship("Person", foreign_keys=[owner_id], backref=backref("owners"))
manager = relationship("Person", foreign_keys=[manager_id], backref=backref("managers"))
I would like my 'Person' object to have two lists of properties - "owned_properites" and "properties_to_manage" (without losing reference to the owner/manager in the 'Property' class). But i don't know how to define a relationship to make auto mapping work properly.
If the class 'Property' only had one foreign key to the 'Person', for example - only "owner_id" key and "owner" object then it would be simple:
#in Property
owner_id = Column(Integer, ForeignKey("people.id"), nullable=False)
owner = relationship("Person", back_populates="property")
#in Person
owned_properties = relationship("Property", back_populates="owner")
But how to do the same with two keys, as shown at the beginning?

Subquery/alias duplication in SqlAlchemy with polymorphic tables and "limit"

Given this polymorphic model
class OrganizationBase(Base):
__tablename__ = "x_organization_base"
__mapper_args__ = {
"polymorphic_identity": "base",
"polymorphic_on": "model_type",
}
model_type = db.Column(db.String(), nullable=False)
id = Column(Integer(), primary_key=True)
cont'd
class UmbrellaOrganization(OrganizationBase):
__tablename__ = "x_umbrella_organization"
__mapper_args__ = {"polymorphic_identity": "umbrella"}
id = db.Column(Integer, db.ForeignKey(OrganizationBase.id), primary_key=True)
umbrella_accounting_id = db.Column(db.String(255), nullable=False, unique=True)
properties = db.relationship(
"UmbrellaOrganizationProperty",
lazy="joined",
backref=backref("umbrella_organization", uselist=False),
)
class Organization(OrganizationBase):
__tablename__ = "x_organization"
__mapper_args__ = {"polymorphic_identity": "organization"}
id = db.Column(Integer, db.ForeignKey(OrganizationBase.id), primary_key=True)
umbrella_accounting_id = db.Column(
db.String(255),
db.ForeignKey(UmbrellaOrganization.umbrella_accounting_id),
nullable=False,
index=True,
)
and this eagerly loaded relationship
class UmbrellaOrganizationProperty(Base):
__tablename__ = "x_umbrella_organization_property"
id = Column(Integer(), primary_key=True)
umbrella_organization_id = db.Column(
Integer, db.ForeignKey(UmbrellaOrganization.id), nullable=False, index=True
)
type = db.Column(db.String(), nullable=False)
this query will produce invalid SQL:
query = (
db.session.query(
Organization,
UmbrellaOrganization,
)
.join(
UmbrellaOrganization,
UmbrellaOrganization.umbrella_accounting_id == Organization.umbrella_accounting_id,
)
)
y = query.limit(5)
Specically, there main query will be duplicated with the same alias 'anon_1' occuring twice:
ProgrammingError: (psycopg2.errors.DuplicateAlias) table name "anon_1" specified more than once
This only happens with limit() applied.
It appears that the polymorphism mapper wants to join the (eagerly loaded) UmbrellaOrganziationProperty to both the UmbrellaOrganization and OrganizationBase, even though it does not belong there. Without changing the model, the only way I have found to prevent this is telling it to not load OrganizationProperty eagerly, by adding this query option:
.options(lazyload(UmbrellaOrganization.properties))
This is potentially problematic because client code may expect the properties in the results. What else can I do?

SqlAlchemy. Cannot add two Foreign keys. There are multiple foreign key paths linking the tables

I'm new in sqlalchemy. I need to create two foreign keys in child (Student) class.
Now I can do something like this:
>>> student_one = Student(name='Sam')
>>> student_two = Student(name='Nick')
>>> group_one = Group(group_number='ST141', students=[student_one, student_two], senior_student=student_one)
>>> group_one.students
>>> group_one.senior_student
That would be correct. And now I want to have senior_student_of field in Student table. I've tried a lot of ways to do something with but couldn't add it.
This is my code:
class Group(Base):
__tablename__ = 'groups'
id = Column(Integer, primary_key=True)
group_number = Column(String(10), nullable=True, default='')
study_hours = Column(Integer, default=0, nullable=True)
lab_studies = Column(Integer, default=0, nullable=True)
pract_studies = Column(Integer, default=0, nullable=True)
curator = relationship('Tutor', backref='tutors', lazy='dynamic')
students = relationship('Student', backref='groups', lazy='dynamic')
senior_student = relationship('Student', uselist=False, lazy='joined')
def __repr__(self):
return self.group_number
class Student(Base):
__tablename__ = 'students'
id = Column(Integer, primary_key=True)
surname = Column(String(20), nullable=True, default='', )
name = Column(String(20), nullable=True, default='')
patronymic = Column(String(20), nullable=True, default='')
absences = Column(Integer, nullable=True, default=0)
undone_labs = Column(Integer, nullable=True, default=0)
unready_labs = Column(Integer, nullable=True, default=0)
group_id = Column(Integer, ForeignKey(Group.id))
group = relationship(Group)
# This don't work
senior_student_of_id = Column(Integer, ForeignKey(Group.id))
senior_student_of = relationship(Group)
Always get an error:
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship Group.students - there are multiple foreign key paths linking the tables. Specify the 'f
oreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference to the parent table.
Finally, I'll need to get student_one.senior_student_of correctly and see ST141 there.
Couldn't search right solution. Thank you for help! :)
You can do:
group = relationship(Group, foreign_keys=[group_id, ])
senior_student_of = relationship(Group, foreign_keys=[senior_student_of_id, ])
Also remove the relationship definitions from Group. Use backref to define the backref, like this:
class Group(Base):
__tablename__ = 'groups'
id = Column(Integer, primary_key=True)
group_number = Column(String(10), nullable=True, default='')
study_hours = Column(Integer, default=0, nullable=True)
lab_studies = Column(Integer, default=0, nullable=True)
pract_studies = Column(Integer, default=0, nullable=True)
def __repr__(self):
return self.group_number
class Student(Base):
__tablename__ = 'students'
id = Column(Integer, primary_key=True)
surname = Column(String(20), nullable=True, default='', )
name = Column(String(20), nullable=True, default='')
patronymic = Column(String(20), nullable=True, default='')
absences = Column(Integer, nullable=True, default=0)
undone_labs = Column(Integer, nullable=True, default=0)
unready_labs = Column(Integer, nullable=True, default=0)
group_id = Column(Integer, ForeignKey(Group.id))
group = relationship(Group, foreign_keys=[group_id, ], backref="students")
senior_student_of_id = Column(Integer, ForeignKey(Group.id), backref="senior_student")
senior_student_of = relationship(Group, foreign_keys=[senior_student_of_id, ])
I have never used the uselist=False though, so I do not know it that works.
I am not sure if this is what you really meant:
students = relationship('Student', backref='groups', lazy='dynamic')
This will put the backref groups into the Student, which means you will have Student.groups. But you also define Student.group in the Student class.

Deleting from self-referential inherited objects does not cascade in SQLAlchemy-SQLite

I posted a question previously here where I tried to build a hierarchy using different objects. Each object can have any type of object as it's parent, and any type as children. I solved it by using the Node class suggested by SQLAlchemy here and letting the other objects inherit from it.
Now I'm having the problem that deleting a node does not delete it's children. I have tried a lot of solutions like different cascade settings, using ondelete='CASCADE' in the foreignkey, as well as DBSession.execute('pragma foreign_keys=on') but none are working. I think the problem is in the ParentID key because in the child it is not null when the parent is delete.
I'm pretty new to SQLAlchemy so I'm not at all sure where I'm going wrong, any help would be appreciated.
These are my current models:
DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
# DBSession.execute('pragma foreign_keys=on')
Base = declarative_base()
class Node(Base):
def getID():
return uuid.uuid1().hex
__tablename__ = 'Node'
ID = Column(Text, primary_key=True, default=getID)
ParentID = Column(Text, ForeignKey('Node.ID', ondelete='CASCADE'))
type = Column(Text(50))
Children = relationship("Node",
backref=backref('Parent',
remote_side=[ID]
),
single_parent=True,
cascade="all, delete, delete-orphan",
passive_deletes = True
)
__mapper_args__ = {
'polymorphic_identity':'Node',
'polymorphic_on':type
}
class A(Node):
__tablename__ = 'A'
ID = Column(Text, ForeignKey('Node.ID', ondelete='CASCADE'), primary_key=True)
Name = Column(Text)
Description = Column(Text)
__mapper_args__ = {'polymorphic_identity':'A'}
class B(Node):
__tablename__ = 'B'
ID = Column(Text, ForeignKey('Node.ID', ondelete='CASCADE'), primary_key=True)
Name = Column(Text)
Description = Column(Text)
__mapper_args__ = {'polymorphic_identity':'B'}
class C(Node):
__tablename__ = 'C'
ID = Column(Text, ForeignKey('Node.ID', ondelete='CASCADE'), primary_key=True)
Name = Column(Text)
Description = Column(Text)
Quantity = Column(Integer)
Rate = Column(Integer)
__mapper_args__ = {'polymorphic_identity':'C' }
This is how I build a hierarchy:
a = A(Name="PName",
Description="PDesc",
ParentID='0')
b = B(Name="BGName",
Description="BGDesc",
ParentID=project.ID)
c = C(Name="BIName",
Description="BIDesc",
Quantity=10,
Rate=5,
ParentID=budgetgroup.ID)
# Append the children nodes to their parents
b.Children.append(c)
a.Children.append(b)
DBSession.add(a)
And this is how I delete it:
def deleteitem(id):
deleteid = id
deletethis = DBSession.query(Node).filter_by(ID=deleteid).first()
qry = DBSession.delete(deletethis)
# qry = DBSession.query(Node).filter_by(ID=deleteid).delete(
# synchronize_session='fetch')
transaction.commit()
Note: neither the one way or the other commented out delete cascades.
I was able to find a solution from this answer here.
Now my Node class looks as follows:
class Node(Base):
__tablename__ = 'Node'
ID = Column(Integer, primary_key=True)
ParentID = Column(Integer, ForeignKey('Node.ID', ondelete='CASCADE'))
type = Column(Text(50))
Children = relationship(
'Node',
cascade="all",
backref=backref("Parent", remote_side='Node.ID'),
)
__mapper_args__ = {
'polymorphic_identity':'Node',
'polymorphic_on':type
}
And this seems to work, all my deletes are cascading.

Better way to filter join against a list of values in SQLAlchemy?

In my schema, a Hardware item may have zero or more Mods recorded against it. I'm trying to filter a query based on an exact match against a list of mods. For example, I might want to filter for Hardware which has mods [2,3,5], and no others.
At the moment, I'm grouping and counting to check that the number of mods is correct, then adding a filter in a for loop to match the exact mod numbers:
...
query = query.join(Hardware).join(Mod)
query = query.group_by(Tag.tag_id).having(len(v['m']) == func.count(Mod.mod_number))
for m in v['m']:
query = query.filter(Hardware.mods.any(mod_number=m))
...
Is there a better way to express this using SQLAlchemy? In particular, the documentation recommends against using func.count() because it fails in some corner cases..
My schema looks like this:
Base = declarative_base()
class Tag(Base):
__tablename__ = 'tag'
tag_id = Column(Integer, primary_key=True)
order_code = Column(String, nullable=False)
version = Column(String, nullable=False)
status = Column(String, nullable=False)
comments = Column(String)
software = relationship(
"Software",
backref="tag",
collection_class=attribute_mapped_collection('artefact'),
)
hardware = relationship(
"Hardware",
backref="tag",
collection_class=attribute_mapped_collection('artefact'),
)
__table_args__ = (
UniqueConstraint('order_code', 'version'),
)
def as_dict(self):
d = as_dict_columns(self)
d['software'] = {s: as_dict_columns(self.software[s]) for s in self.software}
d['hardware'] = {h: self.hardware[h].as_dict() for h in self.hardware}
return d
class Software(Base):
__tablename__ = 'software'
software_id = Column(Integer, primary_key=True)
tag_id = Column(String, ForeignKey('tag.tag_id'))
artefact = Column(String, nullable=False)
version = Column(String, nullable=False)
__table_args__ = (
UniqueConstraint('tag_id', 'artefact'),
)
def __str__(self):
""" This is to deal with Jinja2/SQLAlchemy wackiness """
return self.version
class Hardware(Base):
__tablename__ = 'hardware'
hardware_id = Column(Integer, primary_key=True)
tag_id = Column(String, ForeignKey('tag.tag_id'))
product_id = Column(String, nullable=True)
artefact = Column(String, nullable=False)
version = Column(String, nullable=False)
mods = relationship("Mod", backref="hardware")
__table_args__ = (
UniqueConstraint('tag_id', 'product_id'),
)
def as_dict(self):
d = as_dict_columns(self)
d['mods'] = self.__list_mods__()
return d
class Mod(Base):
__tablename__ = 'mod'
hardware_id = Column(String, ForeignKey('hardware.hardware_id'), primary_key=True)
mod_number = Column('mod_number', Integer, primary_key=True, nullable=False)
__table_args__ = (
UniqueConstraint('hardware_id', 'mod_number'),
)

Categories

Resources