On 6/30/15 3:54 PM, Viktor Roytman wrote:
I have a three classes that I am using to represent a tree:
Tree has some information about the tree at large and a list of nodes.
Node has some information specific to a node, a list of subtrees, and needs a particular piece of information about the Tree for a constraint.
SubTree has a list of nodes.

Here is some (working) example code for this setup:

|
fromsqlalchemy import(
Column,Integer,UniqueConstraint,ForeignKeyConstraint,
    create_engine
)
fromsqlalchemy.ext.declarative importdeclarative_base
fromsqlalchemy.orm importsessionmaker,relationship


Base=declarative_base()


classTree(Base):
    __tablename__ ='tree'
    id =Column(Integer,primary_key=True)
    important_value =Column(Integer,nullable=False)
    nodes =relationship('Node')


classSubTree(Base):
    __tablename__ ='sub_tree'
    id =Column(Integer,primary_key=True)
    parent_node_id =Column(Integer,nullable=False)
    important_value =Column(Integer,nullable=False)
    nodes =relationship(
'Node',
        foreign_keys='[Node.parent_sub_tree_id, Node.important_value]',
)
    __table_args__ =(
UniqueConstraint('id','important_value'),
ForeignKeyConstraint(
['parent_node_id','important_value'],
['node.id','node.important_value']
),
)


classNode(Base):
    __tablename__ ='node'
    id =Column(Integer,primary_key=True)
    parent_tree_id =Column(Integer)
    important_value =Column(Integer,nullable=False)
    parent_sub_tree_id =Column(Integer)
    sub_trees =relationship(
'SubTree',
        foreign_keys=(
'[SubTree.parent_node_id, SubTree.important_value]'
),
)
    __table_args__ =(
UniqueConstraint('id','important_value'),
ForeignKeyConstraint(
['parent_tree_id','important_value'],
['tree.id','tree.important_value']
),
ForeignKeyConstraint(
['parent_sub_tree_id','important_value'],
['sub_tree.id','sub_tree.important_value']
),
)


engine =create_engine('...')
Base.metadata.create_all(engine)

session =sessionmaker(bind=engine,autocommit=True)()
withsession.begin():
    session.add(
Tree(
            important_value=3,
            nodes=[
Node(
                    sub_trees=[
SubTree(
                            nodes=[
Node(),
],
),
],
),
],
)
)
|

When I run this example, I get this warning:

|
/.../lib/python3.4/site-packages/sqlalchemy/orm/relationships.py:2667:SAWarning:relationship 'Tree.nodes'will copy column tree.important_value to column node.important_value,which conflicts withrelationship(s):'SubTree.nodes'(copies sub_tree.important_value to node.important_value).Considerapplying viewonly=Trueto read-only relationships,orprovide a primaryjoin condition marking writable columns withthe foreign()annotation.
for(pr,fr_)inother_props)
|

I can't figure out how to modify the relationships to make SQLAlchemy happy with my (admittedly convoluted) scheme.

so the warning is expressing this contradiction:

If I have a Node, and I associate some SubTree with that Node's "sub_trees" collection, foreign key here means that the value of Node.important_value will be copied into SubTree.important_value at flush time. It has to; because you've declared that all SubTree.important_value columns must refer to a value that is already present in Node.important_value.

The next sentence I will write by literally reversing the words:

If I have a SubTree, and I associate some Node with that SubTree's "nodes" collection, foreign key here means that the value of SubTree.important_value will be copied into Node.important_value at flush time. It has to; because you've declared that all Node.important_value columns must refer to a value that is already present in SubTree.important_value.

I've run your test and it seems that the approach is to avoid the issue by following the pattern that a Node of a SubTree will have the same important_value as the Node to which the SubTree is a child.

Here's how I can hit your contradiction:

    n1 = Node(thingy='x')

    n2 = Node(thingy='y')

    t1 = Tree(important_value=3)
    t2 = Tree(important_value=4)

    s1 = SubTree()

    t1.nodes.append(n1)
    t2.nodes.append(n2)

    n1.sub_trees.append(s1)

    s1.nodes.append(n2)

    session.add_all([
        n1, n2, t1, t2, s1
    ])

and the error (note I've given the names x, y, z to the constraints to get them to be droppable):

sqlalchemy.exc.IntegrityError: (psycopg2.IntegrityError) insert or update on table "node" violates foreign key constraint "y" DETAIL: Key (parent_tree_id, important_value)=(2, 3) is not present in table "tree".

So...the warning is basically saying, that's a thing. The object model allows a contradiction that isn't allowed in the relational model.

The INSERTs still proceed provided you've assembled the objects appropriately, so you could just try living with the warning. I guess ideally, if we could make separate Node classes to indicate each intent, e.g. Tree.nodes only get a TreeNode, SubTree.nodes only get a SubTreenode, that would be a way of avoiding the above contradiction, though I tried that and at the moment the warning we see is only thinking about the tables, not the classes, so we still get the warning. Even if I make a completely separate SubTreeNode class that isn't even a subclass. To trick it into not warning like that would require more gymnastics than is appropriate, really. Usually what we do for these "the user is almost certainly doing it wrong" cases when there's a real use is that we add a flag to turn off the warning. But then all those confused users who are doing it actually wrong then flip on that flag to try to fix their problem. I see endless mappings with incorrect use of "single_parent=True" in this regard.

The best I can come up with at the moment is to take out Node.important_value from the foreign keys list of SubTree.nodes, and copy that "important_value" yourself. This is *somewhat* appropriate at least, because "Node.important_value" really has to match up to that Tree.important_value in any case, so really it's from Tree.important_value -> <everything below it>.

class SubTree(Base):
    __tablename__ = 'sub_tree'
    id = Column(Integer, primary_key=True)
    parent_node_id = Column(Integer, nullable=False)
    important_value = Column(Integer, nullable=False)
    nodes = relationship('Node', foreign_keys='[Node.parent_sub_tree_id]')
    __table_args__ = (
        UniqueConstraint('id', 'important_value'),
        ForeignKeyConstraint(
            ['parent_node_id', 'important_value'],
            ['node.id', 'node.important_value'],
            name='x'
        ),
    )

    session.add(
        Tree(
            important_value=3,
            nodes=[
                Node(
                    sub_trees=[
                        SubTree(
                            nodes=[
                                Node(important_value=3),
                            ],
                        ),
                    ],
                ),
            ],
        )
    )










Any ideas?
--
You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To unsubscribe from this group and stop receiving emails from it, send an email to sqlalchemy+unsubscr...@googlegroups.com <mailto:sqlalchemy+unsubscr...@googlegroups.com>. To post to this group, send email to sqlalchemy@googlegroups.com <mailto:sqlalchemy@googlegroups.com>.
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to sqlalchemy+unsubscr...@googlegroups.com.
To post to this group, send email to sqlalchemy@googlegroups.com.
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

Reply via email to