SQLAlchemy Relationships No Foreign Key - python

I've reflected an existing database and overwritten some columns.
Can someone tell me what's wrong with the following?
metadata = MetaData(engine)
class User(Base):
__tablename__ = 'users'
__table__ = Table('dim_user', metadata,
Column('user_id', Integer, primary_key=True),
autoload=True)
projects = relationship('Project', back_populates='users')
class Project(Base):
__tablename__ = 'projects'
__table__ = Table('dim_project', metadata,
Column('project_id', Integer, primary_key=True),
Column('user_id', Integer, ForeignKey('users.user_id')),
autoload=True)
When I try to query anything I get:
NoForeignKeysError: Could not determine join condition between parent/child tables on relationship User.projects - there are no foreign keys linking these tables. Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or specify a 'primaryjoin' expression.

As #ilja-everilä already tried to convey in his comment, your model is not consitent: you are explicitly defining __table__ properties, which override then __tablename__ properties -- you have to find out which of the are the right ones. In your ForeignKey definition you refer to the __tablename__ of User but not the __table__ definition.
In your relationship definition of User.projects you don't explicitly refer a ForeigKey nor a join condition, and since your meta data is messed up (see above) sqlalchemy is unable to determine automatically what you want.

Related

How to declare symmetric self-referential many-to-many relationship in SQLAlchemy for storing directed graph

from sqlalchemy import DateTime, Table, Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
__all__ = [ "GraphNode" ]
Base = declarative_base()
graph_edges = Table(
'graph_edges', Base.metadata,
Column('from_node_id', Integer, ForeignKey('graph_nodes.id'), primary_key=True),
Column('to_node_id', Integer, ForeignKey('graph_nodes.id'), primary_key=True)
)
class GraphNode(Base):
__tablename__ = 'graph_nodes'
id = Column(Integer, primary_key=True, autoincrement=False)
node_data = Column(String(50), nullable=False)
from_me = relationship("GraphNode", secondary=graph_edges,
primaryjoin=id==graph_edges.c.from_node_id,
secondaryjoin=id==graph_edges.c.to_node_id)
to_me = relationship("GraphNode", secondary=graph_edges,
primaryjoin=id==graph_edges.c.to_node_id,
secondaryjoin=id==graph_edges.c.from_node_id)
When I do this, I get this error from SQLAlchemy:
SAWarning: relationship 'GraphNode.to_me' will copy column graph_nodes.id to column graph_edges.from_node_id, which conflicts with relationship(s): 'GraphNode.from_me' (copies graph_nodes.id to graph_edges.from_node_id). If this is not the intention, consider if these relationships should be linked with back_populates, or if viewonly=True should be applied to one or more if they are read-only. For the less common case that foreign key constraints are partially overlapping, the orm.foreign() annotation can be used to isolate the columns that should be written towards. To silence this warning, add the parameter 'overlaps="from_me"' to the 'GraphNode.to_me' relationship. (Background on this error at: https://sqlalche.me/e/14/qzyx)
It sounds like SQLAlchemy is noticing that putting something in one node's from_me list will result in a change to some other node's to_me list. This is the desired behavior. I want to be able to traverse the links backwards as well as forwards.
Is there any way to do this properly without SQLAlchemy complaining at me?
Here is what solved the problem. The solution was sort of in the error message, and I'm not completely sure why it worked. But, here it is:
from sqlalchemy import DateTime, Table, Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
__all__ = [ "GraphNode" ]
Base = declarative_base()
graph_edges = Table(
'graph_edges', Base.metadata,
Column('from_node_id', Integer, ForeignKey('graph_nodes.id'), primary_key=True),
Column('to_node_id', Integer, ForeignKey('graph_nodes.id'), primary_key=True)
)
class GraphNode(Base):
__tablename__ = 'graph_nodes'
id = Column(Integer, primary_key=True, autoincrement=False)
node_data = Column(String(50), nullable=False)
from_me = relationship("GraphNode", secondary=graph_edges,
primaryjoin=id==graph_edges.c.from_node_id,
secondaryjoin=id==graph_edges.c.to_node_id,
back_populates="to_me")
to_me = relationship("GraphNode", secondary=graph_edges,
primaryjoin=id==graph_edges.c.to_node_id,
secondaryjoin=id==graph_edges.c.from_node_id,
back_populates="from_me")
Basically, I added the back_populates parameter to each relationship. It had the effect I desired.
I really wish I understood what was going on better. I feel more like I'm following a cookbook than starting with ingredients and deciding what sort of meal I want to make with them. I always hate doing that when I'm programming because I'll not understand what's critical and what isn't and make a tiny change that turns out to be massively important.

Create table in Oracle with auto-increment field using Sqlalchemy

I am creating a table in my Oracle DB using python and sqlalchemy. I would like to have an auto-increment ID column as primary key. How can I modify the following code to add the autoload option or anything like that ?
engine = creat_engine("oracle:// ....")
Base = declarative_base()
class MyTable(Base):
__tablename__ = 'MyTable'
ID = Column(Integer, Sequence('my_id_seq'), primary_key=True)
SomeColumn = Column(VARCHAR(50))
Base.metadata.create_all(engine)
p.s. I don't want to create a separate sequence and trigger (as shown here).
UPDATE:
when trying to do the following, I get syntax error because of "autoload=True":
class MyTable(Base):
__table__ = Table('myTable', Base.metadata, autoload=True,
Column('id', Integer, Sequence('my_id_seq'), primary_key=True),
Column('somecolumn', VARCHAR(50))
)
SyntaxError: non-keyword arg after keyword arg

SQLAlchemy Metadata Relationship with Multiple Foreign Keys

I'm new to SQLAlchemy and trying to set up an ORM for an existing database. I'm setting up the tables using metadata and specifying the foreign keys myself. The table setup looks like this:
class User(Base):
__table__ = Table('users', metadata,
Column('user_id', Integer, primary_key=True),
autoload=True)
class Transaction(Base):
__table__ = Table('transaction', metadata,
Column('transaction_id', Integer, primary_key=True),
Column('seller_id', Integer, ForeignKey('users.user_id')),
Column('buyer_id', Integer, ForeignKey('users.user_id')),
autoload=True)
seller = relationship('User', foreign_keys=[seller_id])
buyer = relationship('User', foreign_keys=[buyer_id])
This doesn't run, with the error:
NameError: name 'seller_id' is not defined
Any idea what's wrong?
To understand why you get the error you should refresh your understanding on class construction in Python:
When a class definition is entered, a new namespace is created, and used as the local scope — thus, all assignments to local variables go into this new namespace. In particular, function definitions bind the name of the new function here.
In your example you have no assignments to seller_id that would introduce the name, and so an attempt to use that name during class construction raises the NameError. What you do have available in the current namespace during class construction is the __table__ that you assigned. In fact this exact use case is documented under "Using a Hybrid Approach with __table__":
Note that when the __table__ approach is used, the object is immediately usable as a plain Table within the class declaration body itself, as a Python class is only another syntactical block.
In other words access the columns through the Table object bound to the name __table__:
class Transaction(Base):
__table__ = Table('transaction', metadata,
Column('transaction_id', Integer, primary_key=True),
Column('seller_id', Integer, ForeignKey('users.user_id')),
Column('buyer_id', Integer, ForeignKey('users.user_id')),
autoload=True)
seller = relationship('User', foreign_keys=[__table__.c.seller_id])
buyer = relationship('User', foreign_keys=[__table__.c.buyer_id])

sqlalchemy.exc.AmbiguousForeignKeysError after Inheritance

I'm using sqlacodegen for reflecting a bunch of tables from my database.
And i'm getting the following error:
sqlalchemy.exc.AmbiguousForeignKeysError: Can't determine join between 'Employee' and 'Sales'; tables have more than one foreign key constraint relationship between them. Please specify the 'onclause' of this join explicitly.
Here's a simplified version of my tables.
I read in the documentation that I should use the foreign_keys parameter to resolve ambiguity between foreign key targets. Although, I think this problem is because of the inheritance. Could someone help me understand what is going on.
# coding: utf-8
from sqlalchemy import Column, ForeignKey, Integer
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
Base = declarative_base()
class Employee(Base):
__tablename__ = 'Employee'
EmployeeId = Column(Integer, primary_key=True)
class Sales(Employee):
__tablename__ = 'Sales'
EmployeeID = Column(ForeignKey('Employee.EmployeeId'), primary_key=True)
OldemployeeID = Column(ForeignKey('Employee.EmployeeId'))
employee = relationship('Employee', foreign_keys=[EmployeeID])
old_employee = relationship("Employee", foreign_keys=[OldemployeeID])
When your tables have multiple possible paths to inherit between them (Sales.EmployeeID or Sales.OldEmployeeID), SqlAlchemy doesn't know which one to use and you'll need to tell it the path explicitly, by using inherit_condition. For instance to inherit by EmployeeID:
class Sales(Employee):
...
__mapper_args__ = { "inherit_condition": EmployeeID == Employee.EmployeeId }
For the sake of example, you could also inherit by OldEmployeeID, by entering OldEmployeeID == Employee.EmployeeId - this would mean that both your Sales primary key and the Employee primary key are allowed to be different.
Just use backref and use Integer on both EmployeeID and OldemployeeID. Otherwise you will get an another error.
class Sales(Employee):
__tablename__ = 'Sales'
EmployeeID = Column(Integer, ForeignKey('Employee.EmployeeId'), primary_key=True)
OldemployeeID = Column(Integer, ForeignKey('Employee.EmployeeId'))
employee = relationship('Employee', foreign_keys=[EmployeeID], backref='Employee')
old_employee = relationship("Employee", foreign_keys=[OldemployeeID], backref='Employee')

Can only define secondary relationship using association table object but not name

Trying to create an association relationship between the classes Note and Document. The problem I'm facing is that my secondary relationship only works when I use the association table object and not that table name. What I mean is that the relationship:
notes = relationship(u'Note', secondary=t_Documented, backref='documents')
works but the following does NOT work:
notes = relationship(u'Note', secondary='Documented', backref='documents')
When querying, I get the error:
sqlalchemy.exc.InvalidRequestError: When initializing mapper
Mapper|Document|Document, expression 'Documented' failed to locate a
name ("name 'Documented' is not defined"). If this is a class name,
consider adding this relationship() to the
class after both dependent classes have been defined.
I would rather use the name as my model is generated using sqlacodegen.
Moreover, SQLAlchemy docs say I can use the name (http://docs.sqlalchemy.org/en/rel_0_9/orm/relationships.html#many-to-many) with caveta "with the declarative extension in use". I Googled the term which led me to this. Question is how in my case can I augment the Base.
# model.py
# coding: utf-8
from sqlalchemy import Column, Date, DateTime, ForeignKey, ForeignKeyConstraint, Index, Integer, Numeric, String, Table, Text, text
from sqlalchemy.orm import backref, relationship
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
metadata = Base.metadata
t_Documented = Table(
'Documented', metadata,
Column('id', Integer, primary_key=True),
Column('note_id', ForeignKey(u'MySchema.Note.id'), nullable=False),
Column('document_id', ForeignKey(u'MySchema.Document.id'), nullable=False, index=True),
Column('inserted', DateTime, nullable=False, server_default=text("'0000-00-00 00:00:00'")),
Column('updated', DateTime, nullable=False, server_default=text("'0000-00-00 00:00:00'")),
Index('Documented_AK1', 'note_id', 'document_id'),
schema='MySchema'
)
class Note(Base):
__tablename__ = 'Note'
__table_args__ = {u'schema': 'MySchema'}
id = Column(Integer, primary_key=True)
class Document(Note):
__tablename__ = 'Document'
__table_args__ = {u'schema': 'MySchema'}
id = Column(ForeignKey(u'MySchema.Note.id'), primary_key=True)
title = Column(String(100), nullable=False)
author = Column(String(100), nullable=False)
notes = relationship(u'Note', secondary='Documented', backref='documents')
Using SQLAlchemy 0.9.4 and Python 2.6.6. Connector is MySQLDB and I'm using MySQL database.
your table has a schema of "MySchema" so that has to be part of it:
notes = relationship(u'Note', secondary='MySchema.Documented', backref='documents')

Categories