SQLAlchemy declared_attr on Column with Foreign Key - python

I have a setup where I am using an abstract Machine base class which has several subclasses for different type of machines. The reasoning is that I want to use different database schemas for those machines, which have some tables that are similar but some that are entirely different.
class Machine(Base):
__abstract__ = True
__tablename__ = 'machines'
id = Column('id', BigInteger, nullable=False, primary_key=True)
class MachineA(Machine):
system = relationship('SystemA')
__table_args__ = {'schema': 'A'}
class MachineB(Machine):
system = relationship('SystemB')
__table_args__ = {'schema': 'B'}
So far this is working as intended, but as soon as I start adding tables with foreign keys for the individual schemas, I am running into an issue.
Here is an example for a system table:
class System(Base):
__tablename__ = 'system'
__abstract__ = True
id = Column('id', BigInteger, primary_key=True, autoincrement=True)
machine_id = Column(BigInteger, ForeignKey('machines.id'), nullable=False)
ts = Column('ts', TIMESTAMP(timezone=True), nullable=False)
value = Column('value', JSONB())
__table_args__ = (UniqueConstraint('machine_id', ts, value),
Index('system_machine_id_index', machine_id),
Index('system_ts_index', ts))
class SystemA(System):
__table_args__ = (*System.__table_args__,
{'schema': 'A'})
class SystemB(System):
__table_args__ = (*System.__table_args__,
{'schema': 'B'})
When creating the tables, the following error is raised:
sqlalchemy.exc.InvalidRequestError: Columns with foreign keys to other columns must be declared as #declared_attr callables on declarative mixin classes.
For dataclass field() objects, use a lambda:.
I tried using:
#declared_attr
def machine_id(cls):
return Column(BigInteger, ForeignKey('machines.id'), nullable=False)
which didn't work. Am I missing something? Thanks!

Found the solution. I had to make machine_id a string:
__table_args__ = (UniqueConstraint('machine_id', ts, value),
Index('system_machine_id_index', 'machine_id'),
Index('system_ts_index', ts))
and more importantly, reference the schema in the ForeignKey call:
#declared_attr
def machine_id(cls):
return Column(BigInteger, ForeignKey('A.machines.id'), nullable=False)
Specifying the schema in the __table_args__ is not enough for the mixin declaration.

you can try this
#declarative_mixin
class System(Base):
...
#declared_attr
def machine_id(cls):
...

Related

How do I *dynamically* set the schema in SQLAlchemy for MSSQL?

On this question I learned how to set the schema on an ORM definition:
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Notification(Base):
__tablename__ = "dog"
__table_args__ = {"schema": "animal"}
id = Column(Integer, primary_key=True)
name = Column(String)
But I now need to make the schema configurable. I have tried passing the table_args parameter at object creation, but it's still trying the schema I put on the class definition.
The better solution I have found so far is to create a function that returns the class:
function get_notification_class(schema: str):
class Notification(Base):
__tablename__ = "dog"
__table_args__ = {"schema": schema}
id = Column(Integer, primary_key=True)
name = Column(String)
return Notification
And then, to use it
Notification = get_notification_class('animal')
obj = Notification('1', 'doggy')

Can't map Many-to-Many relationship in SQLAlchemy

I have two objects, User and Room both of which inherit from a Base Object.
class BaseModel:
__metaclass__ = Serializable
created = Column(DateTime, default=func.now())
modified = Column(DateTime, default=func.now(), onupdate=func.now())
#declared_attr
def __tablename__(self):
return self.__name__.lower()
Base = declarative_base(cls=BaseModel)
This is my User model with the many-to-many association with Room declared on top.
association_table = Table('users_rooms', Base.metadata,
Column('user_id', Integer, ForeignKey('user.id')),
Column('room_id', Integer, ForeignKey('room.id'))
)
class User(Base):
__table_args__ = {'extend_existing': True}
id = Column(Integer, primary_key=True)
mobile = Column(String(20), index=True, unique=True)
rooms = relationship("Room", secondary=association_table,
back_populates="users")
And this is the Room model.
association_table = Table('users_rooms', Base.metadata,
Column('user_id', Integer, ForeignKey('user.id')),
Column('room_id', Integer, ForeignKey('room.id'))
)
class Room(Base):
__table_args__ = {'extend_existing': True}
id = Column(Integer, primary_key=True)
room_type = Column(String(50), default=RoomType.PRIVATE)
hex_code = Column(String(100), unique=True)
users = relationship("User", secondary=association_table, back_populates="rooms")
When I try to compile this, I get the following error.
sqlalchemy.exc.InvalidRequestError: Table 'users_rooms' is already defined for this MetaData instance. Specify 'extend_existing=True' to redefine options and columns on an existing Table object.
The error is trying to tell you that you do not need to – and shouldn't – define the association table in both modules. Define it in one of them, or in a module of its own, and either import it, or refer to it lazily in relationship() using secondary="users_rooms":
# Room model. Note the absence of `association_table`
class Room(Base):
id = Column(Integer, primary_key=True)
room_type = Column(String(50), default=RoomType.PRIVATE)
hex_code = Column(String(100), unique=True)
users = relationship("User", secondary="users_rooms", back_populates="rooms")
The table name as a string value passed in secondary= is looked up from the MetaData collection associated with the Room model.
You also should not need to sprinkle
__table_args__ = {'extend_existing': True}
in your models. If you get errors similar to the one in this question without it, the tables have already been created and included in the MetaData collection before your models are constructed. You may have used reflection, for example.

issue creating sqlalchemy relationship/join with multiple foreign keys [duplicate]

I have this simple model of Author - Books and can't find a way to make firstName and lastName a composite key and use it in relation. Any ideas?
from sqlalchemy import create_engine, ForeignKey, Column, String, Integer
from sqlalchemy.orm import relationship, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
engine = create_engine('mssql://user:pass#library')
engine.echo = True
session = sessionmaker(engine)()
class Author(Base):
__tablename__ = 'authors'
firstName = Column(String(20), primary_key=True)
lastName = Column(String(20), primary_key=True)
books = relationship('Book', backref='author')
class Book(Base):
__tablename__ = 'books'
title = Column(String(20), primary_key=True)
author_firstName = Column(String(20), ForeignKey('authors.firstName'))
author_lastName = Column(String(20), ForeignKey('authors.lastName'))
The problem is that you have defined each of the dependent columns as foreign keys separately, when that's not really what you intend, you of course want a composite foreign key. Sqlalchemy is responding to this by saying (in a not very clear way), that it cannot guess which foreign key to use (firstName or lastName).
The solution, declaring a composite foreign key, is a tad clunky in declarative, but still fairly obvious:
class Book(Base):
__tablename__ = 'books'
title = Column(String(20), primary_key=True)
author_firstName = Column(String(20))
author_lastName = Column(String(20))
__table_args__ = (ForeignKeyConstraint([author_firstName, author_lastName],
[Author.firstName, Author.lastName]),
{})
The important thing here is that the ForeignKey definitions are gone from the individual columns, and a ForeignKeyConstraint is added to a __table_args__ class variable. With this, the relationship defined on Author.books works just right.

Unable to create self referencing foreign key in flask-sqlalchemy

I have a model Region and each Region can have sub-regions. Each sub-region has a field parent_id which is the id of its parent region. Here is how my model looks like
class Region(db.Model):
__tablename__ = 'regions'
__table_args__ = {'schema': 'schema_name'}
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100))
parent_id = db.Column(db.Integer, db.ForeignKey('regions.id'))
parent = db.relationship('Region', primaryjoin=('Region.parent_id==Region.id'), backref='sub-regions')
created_at = db.Column(db.DateTime, default=db.func.now())
deleted_at = db.Column(db.DateTime)
Bu when i try to do db.create_all i get this error sqlalchemy.exc.NoReferencedTableError: Foreign key associated with column 'regions.parent_id' could not find table 'regions' with which to generate a foreign key to target column 'id'
Why cant it find regions when i am specifying it in __tablename__? I am using flask-sqlalchemy version 1.0
EDIT --
i removed the line
__table_args__ = {'schema': 'schema_name'}
from my code and it works. Beats the hell out of me.
You must tell SQLAlchemy what the "remote side" of the relationship is, to distinguish between the current row and the row being joined to. The relevant explanation is located partway into this section of the documentation on relationships.
The relationship might look like this:
parent = db.relationship('Region', remote_side=id, backref='sub_regions')
This is an example demonstrating a self-referential relationship:
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.engine import create_engine
from sqlalchemy.ext.declarative.api import declarative_base
from sqlalchemy.orm import sessionmaker, relationship
engine = create_engine('sqlite:///:memory:', echo=True)
Session = sessionmaker(engine)
Base = declarative_base(engine)
session = Session()
class Region(Base):
__tablename__ = 'region'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
parent_id = Column(Integer, ForeignKey('region.id'), index=True)
parent = relationship(lambda: Region, remote_side=id, backref='sub_regions')
Base.metadata.create_all()
r1 = Region(name='United States of America')
r2 = Region(name='California', parent=r1)
session.add_all((r1, r2))
session.commit()
ca = session.query(Region).filter_by(name='California').first()
print ca.parent.name
There will be many lines of SQL output since echo is on, and the script will print 'United States of America' at the end.
I had the same issue with the schema name argument. What I changed to get it to work was to reference the table class directly in ForeignKey and relationships instead of using a string.
Example:
parent_id = Column(Integer, ForeignKey(Region.id), index=True)
parent = relationship(lambda: Region, remote_side=id, backref='sub_regions')
If you use a schema for any table, other tables that have foreign keys referencing those schema tables must provide the name of the schema. See the docs here
class Table(db.Model):
__tablename__ = 'table_1_name'
__table_args__ = {'schema': 'my_schema'}
id = Column('id', Integer, primary_key=True)
...
class AnotherTable(db.Model):
__tablename__ = 'table_2_name'
# Doesn't matter if this belongs to the same or different schema
# __table_args__ = {'schema': 'my_schema'}
id = Column('id', Integer, primary_key=True)
t1_id = Column(Integer, ForeignKey('my_schema.table_1_name.id'))
...
Works for both SQLAlchemy and Flask-SQLAlchemy. Hope this helps. :D
I only see slight differences from #davidism, but here's what works for me in straight SQLAlchemy.
from sqlalchemy import Column
from sqlalchemy import Integer
from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.orm import backref
class Region(Base):
__tablename__ = 'region'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('region.id'), index=True)
sub_regions = relationship('Region', backref=backref('parent', remote_side='Region.id'))
As he points out I'm guessing you wont need the imports, but should prefix them with db, so something like:
class Region(db.Model):
__tablename__ = 'region'
id = db.Column(db.Integer, primary_key=True)
parent_id = db.Column(db.Integer, db.ForeignKey('region.id'), index=True)
sub_regions = db.relationship('Region', backref=db.backref('parent', remote_side='Region.id'))

From Elixir to SqlAlchemy Declarative, Polymorphic Nodes with Child Inheritance [duplicate]

This question already has an answer here:
Creating container relationship in declarative SQLAlchemy
(1 answer)
Closed 3 years ago.
I have already finished a good bit of my python/Elixir interface on my existing database. I am now considering to drop Elixir and move everything into pure SQLAlchemy, most likely wanting to use Declarative methods.
I am not sure where to even start with this particular inheritance relationship. I don't think sqlalchemy performs inheritance in this manner (or as "magically"), and I am a bit confused how the same would look in sqlalchemy:
This is a polymorphic multi-table join, with each class mapped to its own database table. When finished, another class (not included here) will have a OneToMany with 'Comp'. The Comp subclasses have a Primary Key that is a Foreign key to Comp.id.
class Comp(Entity):
using_options(inheritance='multi')
parent = ManyToOne('Assembly', onupdate='cascade', ondelete='set null')
quantity = Field(Numeric(4), default=1)
def __repr__(self):
return "<Comp>"
## If not familiar with Elixir, each of the following "refid" point to a different
## table depending on its class. This is the primary need for polymorphism.
class CompAssm(Comp):
using_options(inheritance='multi')
refid = ManyToOne('Assembly', onupdate='cascade', ondelete='set null')
def __repr__(self):
return "<CompAssm>"
class CompItem(Comp):
using_options(inheritance='multi')
refid = ManyToOne('Item', onupdate='cascade')
def __repr__(self):
return "<CompItem>"
class CompLabor(Comp):
using_options(inheritance='multi')
refid = ManyToOne('Labor', onupdate='cascade')
def __repr__(self):
return "<CompLabor>"
I think this is the general direction, but may still need tweaking.
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Assembly(Base):
__tablename__ = 'assembly'
assm_id = Column(Integer, primary_key=True)
children = relationship('Comp')
### other assembly stuff
class Comp(Base):
__tablename__ = 'components'
id = Column(Integer, primary_key=True)
comp_type = Column('type', String(50))
__mapper_args__ = {'polymorphic_on': comp_type}
parent = Column(Integer, ForeignKey('assembly.assm_id'))
quantity = Column(Integer)
class CompAssm(Comp):
__tablename__ = 'compassm'
__mapper_args__ = {'polymorphic_identity': 'compassm'}
id = Column(Integer, ForeignKey('components.id'), primary_key=True)
refid = Column(Integer, ForeignKey('assembly.assm_id'))
class CompItem(Comp):
__tablename__ = 'compitem'
__mapper_args__ = {'polymorphic_identity': 'compitem'}
id = Column(Integer, ForeignKey('components.id'), primary_key=True)
refid = Column(Integer, ForeignKey('items.id'))
class CompLabor(Comp):
__tablename__ = 'complabor'
__mapper_args__ = {'polymorphic_identity': 'complabor'}
id = Column(Integer, ForeignKey('components.id'), primary_key=True)
refid = Column(Integer, ForeignKey('labors.id'))

Categories

Resources