Do all relationships modeled in SQLAlchemy have to be bi-directional? - python

I am learning python and sqlalchemy and modelled this relationship between shops and locale. I get the error:
InvalidRequestError: One or more mappers failed to initialize - can't
proceed with initialization of other mappers. Triggering mapper:
'Mapper|Shop|shop'. Original exception was: Mapper
'Mapper|Locale|locale' has no property 'shop'
when I try to retrieve a lolcale from the db.
from sqlalchemy import Column, ForeignKey, PrimaryKeyConstraint, String
from sqlalchemy.orm import relationship
class Shop(maria.Base):
__tablename__ = 'shop'
__table_args__ = {'extend_existing': True }
name = Column(String(25), primary_key=True)
locale = Column(String, ForeignKey('locale.country'), primary_key=True)
url = Column(String, nullable=False)
country = relationship("Locale", back_populates='shop')
def __repr__(self):
return "{\n\tname:'%s',\n\tlocale:'%s',\n\turl:'%s'\n}" % (self.name, self.locale, self.url)
class Locale(maria.Base):
__tablename__ = 'locale'
__table_args__ = {'extend_existing': True}
country = Column(String(50), primary_key=True)
code = Column(String(11), primary_key=True)
def __repr__(self):
return "{\n\tcountry:'%s',\n\tcode:'%s'\n}" % (self.country, self.code)

SQLAlchemy ORM relationships are not required to be bi-directional. If using back_populates argument you are declaring it as such though. Using back_populates requires that you declare the other end as well:
Takes a string name and has the same meaning as backref, except the complementing property is not created automatically, and instead must be configured explicitly on the other mapper. The complementing property should also indicate back_populates to this relationship to ensure proper functioning.
(Latter emphasis mine)
Since you've not declared the property at the other end, SQLAlchemy complains. Just remove the back_populates argument:
class Shop(maria.Base):
...
country = relationship("Locale")
...

Related

How do I override an incorrectly defined SQLAlchemy table?

We use SQLAlchemy to read/write data, but not create tables (as it done by DBAs). Due to this, some of the definitions which are incorrect have not yet been caught (though it works for reads/writes).
Is there a way to override them for testing purposes (create tables on the fly etc.) without touching the original definition? A simple class-overriding doesn't seem to work, and I don't see any other solution to this problem:
from sqlalchemy import Column, Integer, Numeric, String
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Product(Base):
__tablename__ = "product"
idn = Column(Numeric, primary_key=True) # should be Integer
code = Column(String, primary_key=True) # should be unique (not primary)
class Product(Product):
__tablename__ = "product"
__table_args__ = {"extend_existing": True} # want to override, not extend
idn = Column(Integer, primary_key=True)
code = Column(String, unique=True)

Python Sqlalchemy - tablename as a variable

I am using SQLAlchemy in Python and am declaring my classes inheriting from a declarative base as follows:
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class SomeClass(Base):
__tablename__ = 'some_table'
id = Column(Integer, primary_key=True)
name = Column(String(50))
As a user I would like to define the __tablename__ as a parameter, and not a hard-coded value , something like this:
class SomeClass(Base):
__tablename__ = f'{environment}_some_table'
id = Column(Integer, primary_key=True)
name = Column(String(50))
It is my understanding that f'{environment}_some_table' will be be evaluated when I import this package, and therefore I won't be able to modify it at a later stage (i.e. in an interactive notebook). I have a broken piece of code that tries to solve this through nested classes and encapsulation, but I do not manage to reference the instance variable of an outer class.
class Outer:
def __init__(self, env):
self.environment = env
class SomeClass(Base):
__tablename__ = f'{self.environment}_some_table'
id = Column(Integer, primary_key=True)
name = Column(String(50))
I have read in a couple of SO questions that it is better not to use nested classes since no special relationship is established between these classes.
So what kind of design should I use to solve this problem?
Thanks in advance!
you can make all your model definitions inside a function scope so the will be depended on outer arguments:
def create_models(environment):
class SomeClass(Base):
__tablename__ = f'{environment}_some_table'
id = Column(Integer, primary_key=True)
name = Column(String(50))
...
globals().update(locals()) # update outer scope if needed
... # some time later
create_models('cheese')
... # now create the db session/engine/etc ...
another choice to look at is the builtin reload method. check it out :)

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

SQLAlchemy multiple foreign keys in one mapped class to the same primary key

Am trying to setup a postgresql table that has two foreign keys that point to the same primary key in another table.
When I run the script I get the error
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship Company.stakeholder - there are multiple foreign key paths linking the tables. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference to the parent table.
That is the exact error in the SQLAlchemy Documentation yet when I replicate what they have offered as a solution the error doesn't go away. What could I be doing wrong?
#The business case here is that a company can be a stakeholder in another company.
class Company(Base):
__tablename__ = 'company'
id = Column(Integer, primary_key=True)
name = Column(String(50), nullable=False)
class Stakeholder(Base):
__tablename__ = 'stakeholder'
id = Column(Integer, primary_key=True)
company_id = Column(Integer, ForeignKey('company.id'), nullable=False)
stakeholder_id = Column(Integer, ForeignKey('company.id'), nullable=False)
company = relationship("Company", foreign_keys='company_id')
stakeholder = relationship("Company", foreign_keys='stakeholder_id')
I have seen similar questions here but some of the answers recommend one uses a primaryjoin yet in the documentation it states that you don't need the primaryjoin in this situation.
Tried removing quotes from the foreign_keys and making them a list. From official documentation on Relationship Configuration: Handling Multiple Join Paths
Changed in version 0.8: relationship() can resolve ambiguity between
foreign key targets on the basis of the foreign_keys argument alone;
the primaryjoin argument is no longer needed in this situation.
Self-contained code below works with sqlalchemy>=0.9:
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship, scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
engine = create_engine(u'sqlite:///:memory:', echo=True)
session = scoped_session(sessionmaker(bind=engine))
Base = declarative_base()
#The business case here is that a company can be a stakeholder in another company.
class Company(Base):
__tablename__ = 'company'
id = Column(Integer, primary_key=True)
name = Column(String(50), nullable=False)
class Stakeholder(Base):
__tablename__ = 'stakeholder'
id = Column(Integer, primary_key=True)
company_id = Column(Integer, ForeignKey('company.id'), nullable=False)
stakeholder_id = Column(Integer, ForeignKey('company.id'), nullable=False)
company = relationship("Company", foreign_keys=[company_id])
stakeholder = relationship("Company", foreign_keys=[stakeholder_id])
Base.metadata.create_all(engine)
# simple query test
q1 = session.query(Company).all()
q2 = session.query(Stakeholder).all()
The latest documentation:
http://docs.sqlalchemy.org/en/latest/orm/join_conditions.html#handling-multiple-join-paths
The form of foreign_keys= in the documentation produces a NameError, not sure how it is expected to work when the class hasn't been created yet. With some hacking I was able to succeed with this:
company_id = Column(Integer, ForeignKey('company.id'), nullable=False)
company = relationship("Company", foreign_keys='Stakeholder.company_id')
stakeholder_id = Column(Integer, ForeignKey('company.id'), nullable=False)
stakeholder = relationship("Company",
foreign_keys='Stakeholder.stakeholder_id')
In other words:
… foreign_keys='CurrentClass.thing_id')

SQLAlchemy - create an instance in another instances __init__

Newish to SQLAlchemy (so my terminology may be a bit off). I want to create a database object inside the constructor of another, but the problem is I can't add said object to the session, so I get an error.
My schema looks a bit like the following:
class Tag(Base):
__tablename__ = 'tag'
id = Column(Integer, Sequence('tag_id_seq'), primary_key=True, nullable=False)
type = Column(String(1), nullable=False)
name = Column(String(255), unique=True, nullable=False)
def __init__(self, type, name):
self.type=type
self.name=name
def __repr__(self):
return "<Tag('%s')>" % (self.id)
class Venue:
__tablename__ = 'venue'
tag_id = Column(Integer)
tag_type = Column(String(1), nullable=False)
location = Column(String(255), nullable=False)
tag = ForeignKeyConstraint(
(tag_id, tag_type),
(Tag.id, Tag.type),
onupdate='RESTRICT',
ondelete='RESTRICT',
)
def __init__(self,name,location):
self.tag = Tag('V',name)
self.location = location
When I do the following:
session.add(Venue("Santa's Cafe","South Pole"))
I get an error:
UnmappedInstanceError: Class '__builtin__.instance' is not mapped
I assume this is because the the Tag object created in Venue's constructor is not added to the session. My question is how/when do I do this. (I'd really prefer to create that Tag object in the constructor if possible). I think I could do this with a scoped_session but that seems like a really bad solution.
Thanks
Inherit Venue from Base. Otherwise Venue won't be mapped.
Move ForeignKeyConstraint to __table_args__.
Replace currently meaningless tag property with relationship to Tag. The default value of cascade parameter to relationship contains 'save-update' rule that will add new referred object to the same session as parent.
From documentation:
save-update - cascade the Session.add() operation. This cascade
applies both to future and past calls to add(), meaning new items
added to a collection or scalar relationship get placed into the same
session as that of the parent, and also applies to items which have
been removed from this relationship but are still part of unflushed
history.

Categories