Is multi-level polymorphism possible in SQLAlchemy? - python

Is it possible to have multi-level polymorphism in SQLAlchemy? Here's an example:
class Entity(Base):
__tablename__ = 'entities'
id = Column(Integer, primary_key=True)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
entity_type = Column(Unicode(20), nullable=False)
__mapper_args__ = {'polymorphic_on': entity_type}
class File(Entity):
__tablename__ = 'files'
id = Column(None, ForeignKey('entities.id'), primary_key=True)
filepath = Column(Unicode(255), nullable=False)
file_type = Column(Unicode(20), nullable=False)
__mapper_args__ = {'polymorphic_identity': u'file', 'polymorphic_on': file_type)
class Image(File):
__mapper_args__ = {'polymorphic_identity': u'image'}
__tablename__ = 'images'
id = Column(None, ForeignKey('files.id'), primary_key=True)
width = Column(Integer)
height = Column(Integer)
When I call Base.metadata.create_all(), SQLAlchemy raises the following error:
IntegrityError: (IntegrityError) entities.entity_type may not be NULL`.
This error goes away if I remove the Image model and the polymorphic_on key in File.
What gives?

Yes. The problem with your code is that you're making Image a type of file, when you must aim for the head of the tree, making Image a type of Entity.
Example:
from sqlalchemy import (Table, Column, Integer, String, create_engine,
MetaData, ForeignKey)
from sqlalchemy.orm import mapper, create_session
from sqlalchemy.ext.declarative import declarative_base
e = create_engine('sqlite:////tmp/foo.db', echo=True)
Base = declarative_base(bind=e)
class Employee(Base):
__tablename__ = 'employees'
employee_id = Column(Integer, primary_key=True)
name = Column(String(50))
type = Column(String(30), nullable=False)
__mapper_args__ = {'polymorphic_on': type}
def __init__(self, name):
self.name = name
class Manager(Employee):
__tablename__ = 'managers'
__mapper_args__ = {'polymorphic_identity': 'manager'}
employee_id = Column(Integer, ForeignKey('employees.employee_id'),
primary_key=True)
manager_data = Column(String(50))
def __init__(self, name, manager_data):
super(Manager, self).__init__(name)
self.manager_data = manager_data
class Owner(Manager):
__tablename__ = 'owners'
__mapper_args__ = {'polymorphic_identity': 'owner'}
employee_id = Column(Integer, ForeignKey('managers.employee_id'),
primary_key=True)
owner_secret = Column(String(50))
def __init__(self, name, manager_data, owner_secret):
super(Owner, self).__init__(name, manager_data)
self.owner_secret = owner_secret
Base.metadata.drop_all()
Base.metadata.create_all()
s = create_session(bind=e, autoflush=True, autocommit=False)
o = Owner('nosklo', 'mgr001', 'ownerpwd')
s.add(o)
s.commit()

Not possible (see SQL ALchemy doc):
Currently, only one discriminator column may be set, typically on the base-most class in the hierarchy. “Cascading” polymorphic columns are not yet supported.
So you should follow #nosklo proposal to change your heritage pattern.

Related

sqlalchemy.exc.InvalidRequestError: When initializing mapper mapped class Event->event, expression 'Tag' failed to locate a name ('Tag')

I am trying to use ORM with SQLAlchemy in Python. My current solution fails and throws an exception right in the moment the ORM is first used. I receive the following exception:
sqlalchemy.exc.InvalidRequestError: When initializing mapper mapped class Event->event, expression 'Tag' failed to locate a name ('Tag'). If this is a class name, consider adding this relationship() to the <class 'backend.source.database.event.Event'> class after both dependent classes have been defined.
My classes are defined like in the offical SQLAlchemy-Documentation (https://docs.sqlalchemy.org/en/14/orm/basic_relationships.html#many-to-many), which is why I am kinda confused about that error.
association = Table('event_to_tag', declarative_base().metadata,
Column('event_id', Integer, ForeignKey('event.id'), primary_key=True),
Column('tag_id', Integer, ForeignKey('tag.id'), primary_key=True))
class Event(declarative_base()):
__tablename__ = "event"
id = Column(Integer, primary_key=True)
title = Column(String(255))
location = Column(String(255))
organizer_id = Column(Integer, ForeignKey(Organizer.id))
start = Column(DateTime)
end = Column(DateTime)
lang = Column(String(255))
costs = Column(DECIMAL)
registration = Column(TINYINT)
url = Column(String(255))
description = Column(Text)
tags = relationship("Tag", secondary=association, back_populates="events")
class Tag(declarative_base()):
__tablename__ = "tag"
id = Column(Integer, primary_key=True)
name = Column(String(255))
events = relationship("Event", secondary=association, back_populates="tags")
Thank you, greetings
I think you need to define a Base = declarative_base(), need to use in your models and associations.
from sqlalchemy import Column, Integer, ForeignKey, String, DECIMAL, Text, DateTime, Table, create_engine
from sqlalchemy.dialects.mssql import TINYINT
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import declarative_base, relationship, scoped_session, sessionmaker
Base = declarative_base()
association = Table('event_to_tag',
Base.metadata,
Column('event_id', Integer, ForeignKey('events.id'), primary_key=True),
Column('tag_id', Integer, ForeignKey('tags.id'), primary_key=True))
class Event(Base):
__tablename__ = "events"
id = Column(Integer, primary_key=True)
title = Column(String(255))
location = Column(String(255))
# organizer_id = Column(Integer, ForeignKey(Organizer.id))
start = Column(DateTime)
end = Column(DateTime)
lang = Column(String(255))
costs = Column(DECIMAL)
registration = Column(UUID)
url = Column(String(255))
description = Column(Text)
tags = relationship("Tag", secondary=association, back_populates="events")
class Tag(Base):
__tablename__ = "tags"
id = Column(Integer, primary_key=True)
name = Column(String(255))
events = relationship("Event", secondary=association, back_populates="tags")
class CreateEngine:
def __init__(self):
self.connection_string = "postgresql+psycopg2://<user_name>:<password>#127.0.0.1/<table_name>"
self.engine = create_engine(self.connection_string)
def create_table(self):
return Base.metadata.create_all(self.engine)
def create_session(self):
session_factory = sessionmaker(bind=self.engine)
Session = scoped_session(session_factory)
with Session() as session:
pass
if __name__ == "__main__":
CreateEngine().create_table()

SQLAlchemy, concise ways of defining many similar classes/tables

So I have a lot of tables with a general stucture of
Base = declarative_base()
class Thing(Base):
__tablename__ = 'thing'
uid = Column(Integer, Sequence('Thing_id_seq'), primary_key=True)
name = Column(String)
def __repr__(self):
return "something"
class ThingEntry(Base):
__tablename__ = 'thingentry'
uid = Column(Integer, Sequence('ThingEntry_id_seq'), primary_key=True)
foo = Column(Integer, ForeignKey('foo.uid'))
entity = Column(Integer, ForeignKey('thing'))
class Quu(Base):
__tablename__ = 'quu'
uid = Column(Integer, Sequence('Quu_id_seq'), primary_key=True)
name = Column(String)
description = Column(String)
def __repr__(self):
return "something"
class QuuEntry(Base):
__tablename__ = 'quuentry'
uid = Column(Integer, Sequence('QuuEntry_id_seq'), primary_key=True)
foo = Column(Integer, ForeignKey('foo.uid'))
entity = Column(Integer, ForeignKey('quu'))
What are some more concise ways of defining all these classes/tables? This method has a lot of code duplication/self-repeating.
I was thinking of some kind of inheritance so that I could bring that code down to
class Thing(Base):
pass
class ThingEntry(Base):
pass
class Quu(Base):
description = Column(String)
class QuuEntry(Base):
pass
With some magic auto-assigning the other values (__tablename__, uid, foo, etc), but I'm not sure if that's possible or optimal.
You should look at documentation about auto reflection http://docs.sqlalchemy.org/en/rel_1_1/core/reflection.html
Used a factory approach with metaclasses, as such:
class ObjectFactory:
def __new__(cls, class_name, parents, attributes):
attributes['__tablename__'] = class_name
attributes['uid'] = Column(Integer, Sequence(class_name + '_id_seq'), primary_key=True)
attributes['name'] = Column(String)
class EntryFactory:
def __new__(cls, class_name, parents, attributes):
attributes['__tablename__'] = class_name
attributes['uid'] = Column(Integer, Sequence(class_name + '_id_seq'), primary_key=True)
attributes['foo'] = Column(Integer, ForeignKey('foo.uid'), nullable=False)
attributes['entity_id'] = Column(Integer, ForeignKey(class_name[:-5]), nullable=False)
class Thing(Base, metaclass=ObjectFactory):
pass
class ThingEntry(Base, metaclass=EntryFactory):
pass
class Quu(Base, metaclass=ObjectFactory):
description = Column(String)
class QuuEntry(Base, metaclass=EntryFactory):
pass

In sqlalchemy how to access anonymous field in subquery

First, I used Sqlalchemy's polymorphic architecture.
ChildA and ChildB extends Child.
ChildA has name column.
ChildB has age column.
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='children')
class ChildA(Child):
__tablename__ = 'child_a'
id = Column(Integer, primary_key=True)
name = Column(String(50))
class ChildB(Child):
__tablename__ = 'child_b'
id = Column(Integer, primary_key=True)
age = Column(Integer)
parent = DBSession.query(Parent).first()
subquery = parent.children.join(ChildA).subquery()
So I want to access ChildA.name column from subquery.
Something like subquery.c.ChildA.name == 'Tom'
If I understood properly what you are trying to do, you don't really need a subquery, it could be simply something like
In [13]:
f = session.query(Parent, ChildA).join(ChildA).first()
print(f.ChildA.name)
Pedro
For the use of subqueries, I would recommend you take a look to sqlalchemy tutorial.
On the other hand, I wasn't able to use the classes as you've defined them, I had to add a ForeignKey like this
class ChildA(Child):
__tablename__ = 'child_a'
id = Column(Integer, ForeignKey('child.id'), primary_key=True)
name = Column(String(50))
class ChildB(Child):
__tablename__ = 'child_b'
id = Column(Integer, ForeignKey('child.id'), primary_key=True)
age = Column(Integer)
I have no doubt that it works for you, this probably depends on the setup.
And finally, I would like to recommend you to use a column for the type of child. With this, it will be easier to recognize the children you are using. Something like this,
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('parent.id'))
parent = relationship(Parent, backref='children')
type = Column(String(20))
__mapper_args__ = {
'polymorphic_identity':'child',
'polymorphic_on':type
}
class ChildA(Child):
__tablename__ = 'child_a'
id = Column(Integer, ForeignKey('child.id'), primary_key=True)
name = Column(String(50))
__mapper_args__ = {
'polymorphic_identity':'child_a',
}
class ChildB(Child):
__tablename__ = 'child_b'
id = Column(Integer, ForeignKey('child.id'), primary_key=True)
age = Column(Integer)
__mapper_args__ = {
'polymorphic_identity':'child_b',
}
Please take a look to sqlalchemy docs for details.
Hope it helps.

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.

Sqlalchemy foreign key to a subset of polymorphic classes

I am using sqlalchemy to create a structure that resembles a graph, so that there are several types of nodes and links joining them. The nodes are defined like this:
class Node(Base):
__tablename__ = 'node'
id = Column(Integer, primary_key=True)
type = Column(Unicode)
__mapper_args__ = {'polymorphic_on': type}
class InputNode(Node):
__tablename__ = 'inputnode'
id = Column(Integer, ForeignKey('node.id'), primary_key=True)
__mapper_args__ = {'polymorphic_identity': 'input'}
class ThruNode(Node):
__tablename__ = 'thrunode'
id = Column(Integer, ForeignKey('node.id'), primary_key=True)
__mapper_args__ = {'polymorphic_identity': 'thru'}
class OutputNode(Node):
__tablename__ = 'outputnode'
id = Column(Integer, ForeignKey('node.id'), primary_key=True)
__mapper_args__ = {'polymorphic_identity': 'output'}
Now I want to create a Link table which would looks something like this:
class Link(Base):
__tablename__ = 'link'
input = Column(Integer, ForeignKey('node.id', where='type IN ("input", "thru")'))
output = Column(Integer, ForeignKey('node.id', where='type IN ("thru", "output")'))
The bit I'm struggling with is how to do the where part of it, since as I've written it is not valid in sqlalchemy. I had though of using a CheckConstraint or a ForeignKeyConstraint, but I can't see how either of them could actually be used to do this.
I haven't tryed it nor am I an expert of this, but shouldn't this work?
class Link(Base):
__tablename__ = 'link'
input = Column(Integer, ForeignKey('thrunode.id'))
output = Column(Integer, ForeignKey('outputnode.id'))
First I had another idea that maybe you could have used different names for the ids and than use those, kind of like:
instead of:
class InputNode(Node):
__tablename__ = 'inputnode'
id = Column(Integer, ForeignKey('node.id'), primary_key=True)
__mapper_args__ = {'polymorphic_identity': 'input'}
this:
class ThruNode(Node):
[...]
thrunode_id = Column(Integer, ForeignKey('node.id'), primary_key=True)
[...]
and then:
class Link(Base):
__tablename__ = 'link'
input = Column(Integer, ForeignKey('node.thrunode_id'))
output = Column(Integer, ForeignKey('node.outputnode_id'))
I got the idea from here sqlalchemy docs: declarative.html#joined-table-inheritance

Categories

Resources