SQLAlchemy Metadata Relationship with Multiple Foreign Keys - python

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])

Related

SqlAlchemy : how to use relationship() together with Array of ForeignKey Column?

I am wondering if I could use sqlalchemy relationship() with Array of ForeignKey,
like the below examples :
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.types import ARRAY
from sqlalchemy.orm import relationship
from database import Base
class Topic(Base):
__tablename__ = 'topics'
id = Column(Integer, primary_key=True, index=True)
topic_name = Column(String)
class Room(Base):
__tablename__ = 'rooms'
id = Column(Integer, primary_key=True, index=True)
room_name = Column(String)
body = Column(String)
topics_id = Column(ARRAY(Integer, ForeignKey("topics.id")))
topics = relationship("Topic", foreign_keys='topics_id', uselist=True) #Single-direction
The relationship is that a Room has many of topics in it, but a Topic does not need to belong to any room.
Since the Room model already has a topics_id field that store array of foreign_keys of Topics model,
so I tried use this topics_id, passing into relationship()
Plus I do not want the Topic to has a link to Room model
but it give me this error
sqlalchemy.exc.InvalidRequestError: When initializing mapper mapped class Room->rooms, expression 'topics_id' failed to locate a name ("name 'topics_id' is not defined"). If this is a class name, consider adding this relationship() to the <class 'models.Room'> class after both dependent classes have been defined.
You can create a many-to-many relation for a schema like this. In the many-to-many relation, your topics can be a part of many rooms and every room can have multiple topics.
You can read about it here on the sqlalchemy official documentation
https://docs.sqlalchemy.org/en/14/orm/basic_relationships.html#many-to-many

How can I set attributes on a "parent" object if a "child" object is assigned to a "relation" in SQLAlchemy?

When I have two objects, linked with a "relation" in SQLAlchemy, I realised that simply assigning to that relation is not enough to propagate the values to the other object. For example (see below), if I have a "user" table and a "contact" table (both are highly contrived, but demonstrate the issue well), and a "user" can have multiple "contacts". In that case I will have a foreign key between the users and contacts. If I create both an instance of User and Contact and later assign the user to the contact, I would expect the attributes of the FK to be updated (even without a DB flush) but they are not. Why? And how can I tell SA to do this automatically?
This would be something I would expect to work, but as you can see in the full example below, it does not:
user = User(name='a', lname='b')
contact(type='email', value='foo#bar.com')
contact.user = user
assert contact.username == 'a' # <-- Fails because the attribute is still `None`
Full runnable example:
"""
This code example shows two tables related to each other by a composite key,
using an SQLAlchemy "relation" to allow easy access to related items.
However, as the last few lines show, simply assigning an object A to the
relation of object B does not update the attributes of object B until at least
a "flush" is called.
"""
from sqlalchemy import Column, ForeignKeyConstraint, Unicode, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relation, sessionmaker
Base = declarative_base()
class User(Base):
__tablename__ = "user"
name = Column(Unicode, primary_key=True)
lname = Column(Unicode, primary_key=True)
class Contact(Base):
__tablename__ = "contact"
__table_args__ = (
ForeignKeyConstraint(
['username', 'userlname'],
['user.name', 'user.lname']
),
)
username = Column(Unicode, primary_key=True)
userlname = Column(Unicode, primary_key=True)
type = Column(Unicode)
value = Column(Unicode)
user = relation(User)
engine = create_engine('sqlite://')
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
user = User(name="John", lname="Doe")
contact = Contact(type='email', value='john.doe#example.com')
contact.user = user # <-- How can I tell SA to set the FKs on *contact* here?
session.add(contact)
print('Before flush: contact.username user=%r' % contact.username)
session.flush()
print('After flush : contact.username user=%r' % contact.username)
According to this answer - https://stackoverflow.com/a/52911047/4981223 it is not possible:
The FK of the child object isn't updated until you issue a flush() either explicitly or through a commit(). I think the reason for this is that if the parent object of a relationship is also a new instance with an auto-increment PK, SQLAlchemy needs to get the PK from the database before it can update the FK on the child object (but I stand to be corrected!).

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 Relationships No Foreign Key

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.

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')

Categories