SQLAlchemy - Multiple Classes, Identical Tables - python

Been searching for this answer for a bit now, but I can't seem to find it, as everything refers back to joined table inheritance, which I understand, but do not want to use. I am looking to create multiple classes in SQLAlchemy that are identical in table construction, only differing in the class name and database table name. I am intentionally separating the tables and not using a discriminator because I expect these tables to grow to very large sizes. There is also a potential that the table schemas may diverge slowly over time, with some fields added to one but not another.
I know the following code doesn't work, as SQLAlchemy tries to find a foreign key for joined table inheritance rather than making them independent tables, but it's basically what I'm going for. I've been over the docs, but can't figure out the way to properly implement this. Is there a way (or multiple ways) to do this?
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class HDD(Base):
"""Class representing a hard drive."""
__tablename__ = 'HDDs'
_id = Column(
Integer,
doc="Auto-incrementing primary key",
name="id",
primary_key=True)
manufacturer = Column(
String(40),
doc="Hard drive manufacturer.")
...
class SDD(HDD):
__tablename__ = 'SSDs'
...

Use __abstract__ = True with no __tablename__ on the base class, and inherit from that.
class HDD(Base):
__abstract__ = True
_id = ...
class SDD(HDD):
__tablename__ = 'SSDs'
class SSD2(HDD):
__tablename = 'SSDs2'

This looks like Concrete Table Inheritance, as discussed in the Mapping Class Inheritance Hierarchies chapter of SQLAlchemy docs.

This is the way I ended up getting this working, with help from inklesspen from #sqlalchemy on freenode. (Thank you!)
class ComponentMixin(object):
"""Pythonic Mixin that defines columns common to most component types."""
_id = Column(
Integer,
doc="Auto-incrementing primary key",
name="id",
primary_key=True)
....
#declared_attr
def _server_id(self): # pylint: disable=R0201
"""
Foreign key to owning server.
MUST BE A DECLARED_ATTR since it's a foreign key!
"""
Subclass below inherits:
class HDD(ComponentMixin, ConcreteBase, Base):
"""Class representing a hard drive."""
__tablename__ = 'HDDs'
__mapper_args__ = {
'polymorphic_identity': 'hdd',
'concrete': True}

Related

Difference and benefit of using Declarative Base Classes instead of table objects in SQLAlchemy

I am having problems understanding the benefit of the usage of declarative classes in SQLAlchemy.
As I understand the ORM is a way to apply the concept of database tables to the class system of OOP. However I don't understand why the table class doesn't already satisfy this requirement.
So to form my question via an example:
What is the benefit of using this:
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
from sqlalchemy import Column, Integer, String
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(16))
fullname = Column(String(60))
nickname = Column(String(50))
Instead of this:
from sqlalchemy import *
metadata = MetaData()
user = Table('users', metadata,
Column('id', Integer, primary_key=True),
Column('name', String(16)),
Column('fullname ', String(60)),
Column('nickname ', String(50))
)
The latter one is already a class representation, isn't it? Why are we building another class over the already existing table class? What's the benefit?
I have the same question recently, you can refer to SQLAlchemy doc.
Some examples in the documentation still use the classical approach, but note that the classical as well as Declarative approaches are fully interchangeable.
I think the benefit of using Declarative Mapping is that,
more convinent to use foreign key.
when you want to create table with some table_args/mapper_args, just write in all down in the class.

slqlalchemy UniqueConstraint VS Index(unique=True)

I am using MySQL (running InnoDB), and wrapped the entire thing using sqlalchemy. Now, I would like to generate changes in my database by using (see docs)
sqlalchemy_utils.functions.create_database(...)
Generally the above function does what it is supposed to. The only exception being the generation of unique indexes.
Say, I define a table like this:
## ...
# DeclBase = declarative_base()
## ...
class MyTable(DeclBase):
__tablename__ = 'my_table'
id = Column(Integer, primary_key=True)
attr_1 = Column(String(32))
attr_2 = Column(Integer, nullable=False)
attr_3 = Column(DateTime)
attr_4 = Column(
Integer,
ForeignKey('other_table.id', onupdate='CASCADE', ondelete='CASCADE'),
nullable=False
)
u_idx = UniqueConstraint(attr_2, attr_3, 'my_table_uidx')
when I call create_database I will get sqlalchemy to create the table 'my_table' with all columns as specified. The foreign key is also setup fine, but no unique index can be found on the database side. I then tried using a Index(unique=True) instead. So instead of
u_idx = UniqueConstraint(attr_2, attr_3, 'my_table_uidx')
I put
u_idx_1 = Index('my_table_uidx', attr_2, attr_3, unique=True)
My impression was this logically produces a similar result. This time sqlalchemy indeed created the unique index on the db.
Maybe I am miserably misunderstanding something about the difference between UniqueConstraint and Index(unique=True), or the way sqlalchemy uses them to automate generation of databases.
Can anyone shed some light on this?
The main difference is that while the Index API allows defining an index outside of a table definition as long as it can reference the table through the passed SQL constructs, a UniqueConstraint and constraints in general must be defined inline in the table definition:
To apply table-level constraint objects such as ForeignKeyConstraint to a table defined using Declarative, use the __table_args__ attribute, described at Table Configuration.
The thing to understand is that during construction of a declarative class a new Table is constructed, if not passed an explicit __table__. In your example model class the UniqueConstraint instance is bound to a class attribute, but the declarative base does not include constraints in the created Table instance from attributes. You must pass it in the table arguments:
class MyTable(DeclBase):
__tablename__ = 'my_table'
...
# A positional argument tuple, passed to Table constructor
__table_args__ = (
UniqueConstraint(attr_2, attr_3, name='my_table_uidx'),
)
Note that you must pass the constraint name as a keyword argument. You could also pass the constraint using Table.append_constraint(), if called before any attempts to create the table:
class MyTable(DeclBase):
...
MyTable.__table__.append_constraint(
UniqueConstraint('attr_2', 'attr_3', name='my_table_uidx'))

How do you specify the foreign key column value in SQLAlchemy?

One of my models has the following relationship:
class User(Base):
account = relationship("Account")
I would like to set the account id manually.
My first attempt was this:
class User(Base):
account = relationship("Account")
accounts_id = Column(Integer, ForeignKey("accounts.id"), nullable=True)
#classmethod
def from_json(cls, json):
appointment = Appointment()
appointment.account_id = json["account_id"]
return appointment
The above dosen't work. We can't refer to this column because SQLAlchemy throws a fit. This is the exception:
sqlalchemy.exc.InvalidRequestError: Implicitly combining column users.accounts_id with column users.accounts_id under attribute 'accounts_id'. Please configure one or more attributes for these same-named columns explicitly.
I've tried hunting through the docs and expermiented with getting to the attribute numerous ways but I haven't been able to find, much less set it.
print(self.account.account_id)
print(self.account.relationhip)
print(self.account.properties)
print(self.account.primaryjoin)
Any ideas?
[Edit- added exception above]
Use the Account class to define the relationship, and add the backref keyword argument:
from sqlalchemy.orm import relationship
class User(Base):
accounts_id = Column(Integer, ForeignKey('account.id'))
class Account(Base):
users = relationship('User', backref='account')
When the backref keyword is used on a single relationship, it’s exactly the same as if the above two relationships were created individually using back_populates on each.
References
Linking Relationships with Backref
Controlling Cascade on Backrefs
SQLAlchemy ORM Examples

Database Relationship - Syntax

A question on the syntax involved in SQLAlchemy.
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
child_id = Column(Integer, ForeignKey('child.id'))
child = relationship("Child")
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
Why is it ForeignKey('child.id') and not ForeignKey("Child.id")?
Why is it relationship("Child") and not relationship("child")? Is there something fundamental about how databases and SQLAlchemy work that I don't understand which is why I have to ask this question? Thanks!
relationship(Child) is also valid. By capitalising inside string, sqlalchemy will look for respective model.
Relationship isn't sql standard so SQLAlchemy is using its own convention, whereas ForeignKey is SQL Standard so tablename.column is used.
In general: A relationship is defined on orm level while ForeignKey represents a database model. Now, it well might be the case that sqlalchemy is smart enough to figure from from the other, but if you keep this separation in mind, you are safe.
Specifically to your question: just read the documentation. Extract below (verbatim)
From relationship:
argument – a mapped class, or actual Mapper instance, representing the
target of the relationship.
argument may also be passed as a callable function which is evaluated
at mapper initialization time, and may be passed as a Python-evaluable
string when using Declarative.
From ForeignKey
column – A single target column for the key relationship. A Column
object or a column name as a string: tablename.columnkey or
schema.tablename.columnkey. columnkey is the key which has been
assigned to the column (defaults to the column name itself), unless
link_to_name is True in which case the rendered name of the column is
used.

sqlalchemy multiple foreignkey

In SQLAlchemy, I would like to write a set of records in table A. Then in table B I want to write a record that references an (a priori) unknown number of the records in A.
In python terms, I would create classes A and B and then have a list in B that contains objects of type A.
Can that be done?
This is not really a SQLAlchemy question, but a basic relational tables question.
You are either talking about a one-to-many relationship, or a many-to-many relationship, both of which SQLAlchemy supports out-of-the-box.
One-to-many
A one-to-many relationship is one of basic containment; a department has many employees, but each employee has only one department. The class with only one outgoing relationship gets the foreign key in this case, so employees refer to departments, not the other way around.
The example from the SQLAlchemy documentation is given as:
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
children = relationship("Child")
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('parent.id'))
So if your A classes can only be part of one B set, use this pattern.
Many-to-many
A many-to-many relationship is one where both sides can refer to more than one instance of the other side. Think of employees and projects; an employee can be part of multiple projects, and a project can involve multiple employees.
In SQL, this kind of relationship requires an association table, an extra database table that holds the mapping between the two linked types. The SQLAlchemy documentation on many-to-many relationships documents clearly how to do this; here is the example:
association_table = Table('association', Base.metadata,
Column('left_id', Integer, ForeignKey('left.id')),
Column('right_id', Integer, ForeignKey('right.id'))
)
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship("Child",
secondary=association_table)
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
Use this pattern if each A object can be part of more than one B set.

Categories

Resources