SQLAlchemy association data via related class constructor - python

I have this design
# coding: utf-8
from sqlalchemy import Column, VARCHAR, ForeignKey, create_engine
from sqlalchemy.dialects.mysql import INTEGER, TINYINT
from sqlalchemy.orm import relationship, sessionmaker
from sqlalchemy.inspection import inspect
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.associationproxy import association_proxy
engine = create_engine('mysql://root:toor#localhost/test', echo=True)
Model = declarative_base()
Session = sessionmaker(bind=engine)
session = Session()
class A(Model):
__tablename__ = 'A'
id = Column(INTEGER(unsigned=True), primary_key=True)
name = Column(VARCHAR(32), nullable=False, index=True)
class AB(Model):
__tablename__ = 'AB'
A_id = Column(INTEGER(10, unsigned=True), ForeignKey('A.id'), nullable=False, primary_key=True)
B_id = Column(INTEGER(10, unsigned=True), ForeignKey('B.id'), nullable=False, primary_key=True)
type = Column(TINYINT(3, unsigned=True), nullable=False)
A_rel = relationship(
"A",
foreign_keys=[A_id]
)
B_rel = relationship(
"B",
foreign_keys=[B_id],
)
mapper_AB = inspect(AB)
class B(Model):
__tablename__ = 'B'
id = Column(INTEGER(10, unsigned=True), primary_key=True)
As = relationship("A", secondary=mapper_AB.local_table, uselist=False)
ABs = relationship("AB")
type = association_proxy('ABs', 'type')
Model.metadata.create_all(engine)
a = A(
name="test",
)
session.add(a)
session.commit()
b = B(
As=a,
type=2
)
print(b.type)
session.add(b)
session.commit()
This obviously fails, but I am trying to simplify the design by adding the AB extra information (type) via one (B) of the related classes constructor.
I have gone through tons of SQLAlchemy documentation, and even lost myself inside the source code but to no avail. The 3 table design of the database cannot be modified, as is part of a bigger system and belongs to another project. My goal is to map those table into a more simplified design for further development where this models will be used.
Is this possible using SQLAlchemy mechanisms with declarative style?

Related

Sqlalchemy: self-referential many-to-many relationship with extra column in relationship table

I'm trying to create a self-referential many-to-many relationship table with an extra column called sort_order:
from sqlalchemy import Integer, ForeignKey, String, Column, Table
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.orderinglist import ordering_list
Base = declarative_base()
class Node2Node(Base):
__tablename__ = "NodeToNode"
left_node_id = Column(Integer, ForeignKey("Node.id"), primary_key=True)
right_node_id = Column(Integer, ForeignKey("Node.id"), primary_key=True)
sort_order = Column(Integer, nullable=False)
class Node(Base):
__tablename__ = "Node"
id = Column(Integer, primary_key=True)
label = Column(String)
# Many To Many with Node
right_nodes = relationship(
"Node",
secondary="NodeToNode",
primaryjoin="Node.id==NodeToNode.c.left_node_id",
secondaryjoin="Node.id==NodeToNode.c.right_node_id",
backref="left_nodes",
order_by="NodeToNode.c.sort_order",
)
def __repr__(self):
return f"<Node: {self.label}>"
When I run the following script I get an error saying NOT NULL constraint failed: NodeToNode.sort_order:
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
engine = create_engine("sqlite:///practice.sqlite3")
session = Session(bind=engine)
# Build tables
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
# Load in data
nodes = [
Node(label="A"),
Node(label="B"),
Node(label="C"),
Node(label="D"),
Node(label="E"),
]
session.add_all(nodes)
session.commit()
nodes[0].right_nodes = [nodes[3], nodes[1], nodes[2]]
nodes[0].left_nodes = [nodes[4]]
session.add_all(nodes)
session.commit()
# Display data
print("Nodes: {}".format(nodes))
print("Node A right nodes: {}".format(nodes[0].right_nodes))
print("Node A left nodes: {}".format(nodes[0].left_nodes))
session.close()
How can I set this sort_order key based on the item's index in the list?
Use Self-referential many-to-many relationship with Association Object.
See example at https://stackoverflow.com/a/62281276/9387542

Self-Referential Many-to-Many Relationship with Association Proxy in SQLAlchemy

I'm trying to create a Self-Referential Many-to-Many Relationship. The example outlined in the SQLAlchemy documentation works great. Here are the models I created:
from sqlalchemy import Integer, ForeignKey, String, Column, Table
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
Base = declarative_base()
Table('NodeToNode', Base.metadata,
Column('leftNodeId', Integer, ForeignKey('Node.id'), primary_key=True),
Column('rightNodeId', Integer, ForeignKey('Node.id'), primary_key=True)
)
class Node(Base):
__tablename__ = 'Node'
id = Column(Integer, primary_key=True)
label = Column(String)
rightNodes = relationship('Node',
secondary='NodeToNode',
primaryjoin='Node.id==NodeToNode.c.leftNodeId',
secondaryjoin='Node.id==NodeToNode.c.rightNodeId',
backref='leftNodes'
)
And the script for adding data in:
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
engine = create_engine('sqlite:///practice.sqlite3')
session = Session(bind=engine)
nodes = [
Node(label='A'),
Node(label='B'),
Node(label='C'),
Node(label='D'),
Node(label='E'),
]
nodes[0].rightNodes = [nodes[1], nodes[3], nodes[2]]
nodes[0].leftNodes = [nodes[4]]
session.add_all(nodes)
session.commit()
I want to add a column to the association table so I'd assume I need to convert the association table to its own class:
class NodeToNode(Base):
__tablename__ = 'NodeToNode'
leftNodeId = Column(Integer, ForeignKey('Node.id', onupdate='CASCADE'), primary_key=True)
rightNodeId = Column(Integer, ForeignKey('Node.id', onupdate='CASCADE'), primary_key=True)
sortOrder = Column(Integer, nullable=False)
This, however, results in the following error:
sqlalchemy.exc.InvalidRequestError: Class <class 'models.node.NodeToNode'> does not have a mapped column named 'c'
Any idea what I'm doing wrong here?

sqlalchemy foreignkey relation with parent class

I have the following database schema:
Parent table:
id - primary key identifier.
type - polymorphic_identity.
name - string data column.
Child table - inherits Parent:
id - primary key identifier.
parent_id - foreignkey to Parent.
category - string data column.
Summing up I have two tables. Table Child inherits from Parent and also have a foreignkey to it.
UPD: I really need both inheritance and foreignkey. This example is only a short demo which reproduces the problem.
I used declarative_base to declare the schema:
# -*- coding: utf-8 -*-
from sqlalchemy import Column, String, Integer, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
Base = declarative_base()
class Parent(Base):
__tablename__ = "Parent"
id = Column(Integer, primary_key=True)
type = Column(String(250))
name = Column(String(250))
__mapper_args__ = {
'polymorphic_identity':'Parent',
'polymorphic_on':type
}
class Child(Parent):
__tablename__ = 'Child'
id = Column(Integer, ForeignKey('Parent.id'), primary_key=True)
parent_id = Column(ForeignKey("Parent.id"), nullable=True)
category = Column(String(250))
__mapper_args__ = {
'polymorphic_identity':'Child',
}
engine = create_engine('postgresql+psycopg2://joe:joe#localhost/alch')
session = sessionmaker()
session.configure(bind=engine)
Base.metadata.create_all(engine)
But when I run the code I get the following error:
sqlalchemy.exc.AmbiguousForeignKeysError: Can't determine join between 'Parent' and 'Child'; tables have more than one foreign key constraint relationship between them. Please specify the 'onclause' of this join explicitly.
I have tried to set relationship attribute myself for Parent or for Child separately and for both too. Tried to use primaryjoin and foreign_keys parameters of relationship. But the error was the same.
I'm totally confused about this error.
I need help.
I found the solution here.
SQLAlchemy needs a hint in this situation: a inherit_condition field in Child's __mapper_args__ does the trick.
# -*- coding: utf-8 -*-
from sqlalchemy import Column, String, Integer, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
Base = declarative_base()
class Parent(Base):
__tablename__ = "Parent"
id = Column(Integer, primary_key=True)
type = Column(String(250))
name = Column(String(250))
__mapper_args__ = {
'polymorphic_identity':'Parent',
'polymorphic_on':type
}
class Child(Parent):
__tablename__ = 'Child'
id = Column(Integer, ForeignKey('Parent.id'), primary_key=True)
parent_id = Column(ForeignKey("Parent.id"), nullable=True)
category = Column(String(250))
parent = relationship(Parent, foreign_keys=[parent_id])
__mapper_args__ = {
'polymorphic_identity':'Child',
'inherit_condition': id == Parent.id,
}
engine = create_engine('postgresql+psycopg2://joe:joe#localhost/alch')
session = sessionmaker()
session.configure(bind=engine)
Base.metadata.create_all(engine)
Have you tried removing the Foreign Key for the Child id field?
id = Column(Integer, ForeignKey('Parent.id'), primary_key=True)
parent_id = Column(ForeignKey("Parent.id"), nullable=True)
You need something like this:
id = Column(Integer, auto_increment=True, primary_key=True)
parent_id = Column(ForeignKey("Parent.id"), nullable=True)

Load subset of joined columns in SQLAlchemy

I'm trying to load a subset of columns from multiple joined tables in SQLAlchemy and can't figure out the magic syntax.
Here's a simple example of what I'm trying to do. It results in an error (below):
from sqlalchemy import create_engine
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, load_only
Base = declarative_base()
class Table1(Base):
__tablename__ = 'table1'
table1_id = Column(Integer, primary_key=True)
table1_val = Column(String)
r1 = relationship('Table2', backref = 'r2')
class Table2(Base):
__tablename__ = 'table2'
table2_id = Column(Integer, ForeignKey('table1.table1_id'), primary_key=True)
table2_val = Column(String)
from sqlalchemy.orm import sessionmaker
some_engine = create_engine('postgresql://scott:tiger#localhost/')
Session = sessionmaker(bind=some_engine)
session = Session()
query = session.query(Table1).join(Table2).options(load_only('table1_val','table2_val'))
ArgumentError: Can't find property named 'table2_val' on the mapped entity Mapper|Table1|table1 in this Query.
How do I pick and choose columns from these multiple tables?
Question was answered at [https://bitbucket.org/zzzeek/sqlalchemy/issues/3679/multiple-load_only-in-queryoptions-ignore]
Here is the corrected code:
from sqlalchemy import create_engine
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import joinedload, Load, relationship, load_only
Base = declarative_base()
class Table1(Base):
__tablename__ = 'table1'
global_id = Column(Integer, primary_key=True)
table1_val1 = Column(String)
table1_val2 = Column(String)
r1 = relationship('Table2', backref = 'r2')
class Table2(Base):
__tablename__ = 'table2'
global_id = Column(Integer, ForeignKey('table1.global_id'), primary_key=True)
table2_val1 = Column(String)
table2_val2 = Column(String)
from sqlalchemy.orm import sessionmaker
some_engine = create_engine('sqlite://')
Base.metadata.create_all(some_engine)
Session = sessionmaker(bind=some_engine)
session = Session()
session.add(Table1(table1_val1='1val1',table1_val2='1val2',r1=[Table2(table2_val1='2val1', table2_val2='2val2')]))
session.commit() # expires the attribute from the session
query = session.query(Table1).options(
# note that the 'load_only()' is applied to the 'joinedload' path, not
# put on its own Load() path
joinedload('r1',innerjoin=True).load_only('table2_val1'),
Load(Table1).load_only('table1_val1'))
foo = query.all()
assert 'table1_val2' not in foo[0].__dict__
assert 'table2_val2' not in foo[0].r1[0].__dict__

sqlalchemy: Inserting into joined tables

I try to add a record to a joined-inheritance table.
For some reason I cannot fathom, the dependent table is not INSERTed into.
## Inheritance test case.
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.orm import Session, relationship, backref
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Employee(Base):
__tablename__ = 'personnel'
__mapper_args__ = { 'polymorphic_on': 'etyp' }
id = Column(Integer, primary_key=True)
name = Column(String(50))
etyp = Column(String(10))
class Engineer(Employee):
__mapper_args__ = { 'polymorphic_identity':'eng' }
__tablename__ = "engineers"
id = Column(Integer, ForeignKey(Employee.id), index=True)
eng_data = Column(String(50))
engine = create_engine('sqlite:///', echo=True)
Base.metadata.create_all(engine)
session = Session(bind=engine)
e1 = Engineer(name="wally", eng_data="lazy guy")
session.add(e1)
session.commit()
# note that the Engineer table is not INSERTed into
e1 = session.query(Employee).filter_by(name="wally").one()
# the next line triggers an exception because it tries to lookup
# the Engineer row, which is missing
print e1.eng_data
This is Python 2.7, sqlalchemy 0.9.4 (Debian testing).
Making engineers.id a primary key will solve the problem:
class Engineer(Employee):
# ...
id = Column(Integer, ForeignKey(Employee.id), primary_key=True)

Categories

Resources