SQLAlchemy generic relationship mixin - python

I'm working on a SQLAlchemy defining a bunch of mixin classes that applications should be able to import and extend their model.
When looking at the documentation, mixin classes are create knowing the final table name however, in the case of a generic library, the final table name that will be used by the application is not known.
Take the following mixin classes:
import sqlalchemy as sa
class UserMixin(object):
id = sa.Column(sa.Integer(), primary_key=True)
first_name = sa.Column(sa.Unicode(255))
last_name = sa.Column(sa.Unicode(255))
class ItemMixin(object):
id = sa.Column(sa.Integer(), primary_key=True)
name = sa.Column(sa.Unicode(255))
short_description = sa.Column(sa.Unicode(255))
class OrdersMixin(object):
id = sa.Column(sa.Integer(), primary_key=True)
user_id = sa.Column(sa.Integer(), sa.ForeignKey('???'))
item_id = sa.Column(sa.Integer(), sa.ForeignKey('???'))
Then an application defining its models:
import sqlalchemy as sa
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class MyUser(UserMixin, Base):
__tablename__ = 'myuser'
class MyItem(ItemMixin, Base):
__tablename__ = 'myitem'
total = sa.Column(sa.Integer())
class MyOrders(OrdersMixin, Base):
__tablename__ = 'myorders'
I have two issues with this model:
Except from redefining the relationship columns in the extending models, how can the mixin class build the relationship on its own.
Type of the foreign key is assumed by the mixin class, but the id of the table may come from the application itself or from another mixin class.
Is the model I'm trying to implement correct? What would be the right way to tackle this problem?

A Mixin is a class that copies its data into a table, but for SQL it matters if you're the owner of that data (the table) vs being a reference (the foreign key).
It looks like you're attempting to create Mixins that are both sources of truth and references. Which isn't possible in SQL.
Taking your example one step further and defining OrdersMixin like this will make the issues more obvious I think.
class OrdersMixin(UserMixin, ItemMixin):
id = sa.Column(sa.Integer(), primary_key=True)
For example MyOrders would end up like this once things are resolved.
class MyOrders(Base):
__tablename__ = 'myorders'
# This is from UserMixin
id = sa.Column(sa.Integer(), primary_key=True)
first_name = sa.Column(sa.Unicode(255))
last_name = sa.Column(sa.Unicode(255))
# This is from ItemMixin
id = sa.Column(sa.Integer(), primary_key=True)
name = sa.Column(sa.Unicode(255))
short_description = sa.Column(sa.Unicode(255))
# From OrdersMixin
id = sa.Column(sa.Integer(), primary_key=True) # This is defined last so it overrides all others with the same name.
user_id = sa.Column(sa.Integer(), sa.ForeignKey('???'))
item_id = sa.Column(sa.Integer(), sa.ForeignKey('???'))
With how you have the Mixin defined any table that used that Mixin would have primary_keys for the id column, which would conflict. Additionally you are duplicating every column in the Mixin, which in general you want to avoid in SQL (see Database normal form).
The final result would be something like this. Which is a whole bunch of columns meaning you wouldn't need to refer to any other tables and all of the references id you had were overwritten, meaning you wouldn't be able to join them anyway.
class MyOrders(Base):
__tablename__ = 'myorders'
first_name = sa.Column(sa.Unicode(255))
last_name = sa.Column(sa.Unicode(255))
name = sa.Column(sa.Unicode(255))
short_description = sa.Column(sa.Unicode(255))
id = sa.Column(sa.Integer(), primary_key=True) # This is defined last so it overrides all others with the same name.
user_id = sa.Column(sa.Integer(), sa.ForeignKey('???'))
item_id = sa.Column(sa.Integer(), sa.ForeignKey('???'))
To avoid that I keep my Mixins separate from initial table definition. I.e. I use a Mixin for when I want another table to refer to that table.
The following is close to what I think you were hoping to achieve.
import sqlalchemy as sa
from sqlalchemy import orm
class UserMixin(object):
user_id = sa.Column(sa.Integer(), ForeignKey("myuser.id"), index=True)
user = orm.relationship("MyUser")
class ItemMixin(object):
item_id = sa.Column(sa.Integer(), ForeignKey("myitem.id"), index=True)
item = orm.relationship("MyItem")
class OrdersMixin(UserMixin, ItemMixin):
order_id = sa.Column(sa.Integer(), sa.ForeignKey('myorders.id'))
user_id = sa.Column(sa.Integer(), sa.ForeignKey('myorders.user_id'))
item_id = sa.Column(sa.Integer(), sa.ForeignKey('myorders.item_id'))
Note in the Mixins I gave every column a unique name so that there aren't conflicts and in OrdersMixin even though I'm using UserMixin and ItemMixin I'm overriding the user_id and item_id columns because otherwise anything using the OrdersMixin would have foreign keys pointing to three different tables which would confuse the automatic query builder. But it will still add the user and item relations (and since they are defined as foreign keys to the original tables in MyOrders table I think the relationship will just work).
Then I would change your tables to look like this.
import sqlalchemy as sa
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class MyUser(Base):
__tablename__ = "myuser"
id = sa.Column(sa.Integer(),primary_key=True)
first_name = sa.Column(sa.Unicode(255))
last_name = sa.Column(sa.Unicode(255))
class MyItem(Base):
__tablename__ = "myitem"
id = sa.Column(sa.Integer(),primary_key=True)
name = sa.Column(sa.Unicode(255))
short_description = sa.Column(sa.Unicode(255))
class MyOrders(Base, UserMixin, OrdersMixin):
__tablename__ = "myorders"
id = sa.Column(sa.Integer(),primary_key=True)
The original table definition owns the columns (source of truth) defining them individually and Mixins (of this kind) are good to define references so subsequent references don't need define each of them individually. A Mixin can't be defined to be both a reference and a source of truth. In light of that instead of overriding the column each time like OrdersMixin it's better to just define it once canonically (the table) and once as a reference (the Mixin).

Related

Use of relationship and ForeignKey modules in SQLAlchemy

I'm a little confused over the use of the two modules from SQLAlchemy. This is the code I have:
Base = declarative_base()
class Restaurant(Base):
__tablename__ = 'restaurant'
id = Column(Integer, primary_key=True)
name = Column(String(250), nullable=False)
class MenuItem(Base):
__tablename__ = 'menu_item'
name =Column(String(80), nullable = False)
id = Column(Integer, primary_key = True)
description = Column(String(250))
price = Column(String(8))
course = Column(String(250))
restaurant_id = Column(Integer,ForeignKey('restaurant.id'))
restaurant = relationship(Restaurant)
I understand that ForeignKey is used to define the foreign key relationship between the restaurant_id column of menu_item table and the id column of restaurant table. But why then is restaurant = relationship(Restaurant) used?
restaurant_id will refer to an id (the column value). restaurant will refer to a Restaurant instance that will be lazy loaded from the db on access (or eager loaded if you set up the right stuff earlier). If you set backref on the relationship you can also access a list of MenuItem objects from a Restaurant.

setup relationship one-to-one in Flask + SQLAlchemy

I'm trying to set up a relationship between two tables which allows me to reach obj1.obj2.name where obj1 is one table, and obj2 is another table. Relationship is one-to-one (one person to one geographical region)
# Table one (Person)
class Person(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100))
region = db.Column(db.Integer, db.ForeignKey('region.id'))
# Table two (Region)
class Region(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
If I use Person.region (where Person is an object of Person class) I get the int of the primary key of the region of the user, but I would like to get the 'name' field associated with it.
I've figured out that this would work:
region = models.Region.query.filter_by(id=REGION_ID).first().name
but it's not applicable in my case since I need to access the 'name' field from a Flask template.
Any thoughts?
Here I basically use your model, but:
1) changed the name of the FK column
1) added a relationship (please read Relationship Configuration part of the documentation)
class Person(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100))
# #note: renamed the column, so that can use the name 'region' for
# relationship
region_id = db.Column(db.Integer, db.ForeignKey('region.id'))
# define relationship
region = db.relationship('Region', backref='people')
class Region(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
With this you are able to get the name of the region as below:
region_name = my_person.region.name # navigate a 'relationship' and get its 'name' attribute
In order to make sure that the region is loaded from the database at the same time as the person is, you can use joinedload option:
p = (db.session.query(Person)
.options(db.eagerload(Person.region))
.get(1)
)
print(p)
# below will not trigger any more SQL, because `p.region` is already loaded
print(p.region.name)

One-to-many relationships in factory_boy

I use SQLalchemy as my ORM and am trying to port my test fixtures to factory_boy. My schema includes two objects in a one-to-many relation. I.e. instances of one model have list like structures with instances of the other. Example:
class Person(...):
id = Column(Integer, primary_key=True)
name = Column(Text)
[...]
class Address(...):
id = Column(Integer, primary_key=True)
city = Column(Text)
[...]
person_id = Column(Integer, ForeignKey('person.id'))
person = relationship("Person", backref="addresses")
Now I am trying to create a factory which creates persons with a couple of addresses. Factory_boy has the SubFactory. But I only see how you can use that in a one-to-one relationship. I know I can create the addresses with a separate factory and then attach them, but I would like to do something like person =PersonFactory.create(num_addresses=4)`.
Does anyone know if this is currently possible in factory_boy?
I use factory_boy 2.4.1.
I am using this pattern in my project. Assuming you already have AddressFactory.
https://factoryboy.readthedocs.io/en/latest/reference.html?highlight=post_generation#factory.post_generation
class PersonFactory(factory.alchemy.SQLAlchemyFactory):
class Meta:
model = Person
#factory.post_generation
def addresses(obj, create, extracted, **kwargs):
if not create:
return
if extracted:
assert isinstance(extracted, int)
AddressFactory.create_batch(size=extracted, person_id=self.id, **kwargs)
Usage
PersonFactory(addresses=4)
This will create Person with 4 Addresses
Also this can accept kwargs
PersonFactory(addresses=2, addresses__city='London')
This will create Person with 2 Addresses which have city field set to 'London'
Here is blog post which may help https://simpleit.rocks/python/django/setting-up-a-factory-for-one-to-many-relationships-in-factoryboy/
#Kristen pointed to the right direction, but AdderssFactory didn't related to Person.
In Django we can use post_generation decorator like this.
class PersonFactory(BaseFactory):
#factory.post_generation
def addresses(self, create, extracted, **kwargs):
self.addresses_set.add(AddressFactory(person=self))
I had this exact question and was disappointed in the lack of good answers here. Turns out it is possible! Leaving this here for those who have the same question.
First, your model needs to define the relationship on the opposite model from the ForeignKey, so it should look like:
class Person(...):
id = Column(Integer, primary_key=True)
name = Column(Text)
addresses = relationship("Person", backref="person")
[...]
class Address(...):
id = Column(Integer, primary_key=True)
city = Column(Text)
[...]
person_id = Column(Integer, ForeignKey('person.id'))
Then, on your PersonFactory, you can add a post_generation hook like this:
class PersonFactory(BaseFactory):
[...attributes...]
#factory.post_generation
def addresses(self, create, extracted, **kwargs):
return AddressFactory.create_batch(4)
and replace the '4' with whatever number you want. Obviously, you need to define the AddressFactory as well.
Currently, there is no way to implement a "many-to-one RelatedFactory" such that it is "baked into your factory"...
That said, this behavior can be implemented with a bit of hackery when instantiating your PersonFactory.
The following recipe will get you what you are looking for:
from sqlalchemy import create_engine, Integer, Text, ForeignKey, Column
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, scoped_session, sessionmaker
import factory
from factory.alchemy import SQLAlchemyModelFactory as sqla_factory
import random
engine = create_engine("sqlite:////tmp/factory_boy.sql")
session = scoped_session(sessionmaker(bind=engine))
Base = declarative_base()
class Person(Base):
id = Column(Integer, primary_key=True)
name = Column(Text)
addresses = relationship("Address", backref="person")
class Address(Base):
id = Column(Integer, primary_key=True)
street = Column(Text)
street_number = Column(Integer)
person_id = Column(Integer, ForeignKey('person.id'))
class AddressFactory(sqla_factory):
class Meta:
model = Address
sqlalchemy_session = session
street_number = random.randint(0, 10000)
street = "Commonwealth Ave"
class PersonFactory(sqla_factory):
class Meta:
model = Person
sqlalchemy_session = session
name = "John Doe"
Base.metadata.create_all(engine)
for i in range(100):
person = PersonFactory(addresses=AddressFactory.create_batch(3))
You could use the solution described here: http://factoryboy.readthedocs.org/en/latest/recipes.html#reverse-dependencies-reverse-foreignkey
Basically, just declare a few RelatedFactory on your PersonFactory:
class PersonFactory(factory.alchemy.SQLAlchemyFactory):
class Meta:
model = Person
address_1 = factory.RelatedFactory(AddressFactory, 'person')
address_2 = factory.RelatedFactory(AddressFactory, 'person')

Multiple Identical Tables in Flask-SQLAlchemy

I'm creating a REST-like API to get data in and out of a legacy database. At the moment I don't have the option to alter the db structure. Nearly every table in the database has the exact same structure. Here's how I'm currently handling it using Flask-SQLAlchemy:
class MyDatasetBase(object):
timestamp = db.Column('colname', db.Float, primary_key=True)
val1 = db.Column(db.Float)
val2 = db.Column(db.Float)
val3 = db.Column(db.Float)
fkeyid = db.Column(db.Integer)
#declared_attr
def foreigntable(self):
return db.relationship('ForeignTableClass', uselist=False)
class Table1(MyDatasetBase, db.Model):
__tablename__ = 'table1'
class Table2(MyDatasetBase, db.Model):
__tablename__ = 'table2'
class ForeignTableClass(db.Model):
__tablename__ = 'ForeignTable'
id = db.Column(db.Integer, db.ForeignKey('table1.fkeyid'), db.ForeignKey('table2.fkeyid'), primary_key=True)
name = db.Column(db.String)
This all works, but some of these datasets contain a lot of tables and I feel like there has to be a more efficient way to do a few of these things.
Is there a way to get around explicitly defining a class for each of the tables derived from MyDatasetBase? If there is, will that cause problems with the foreign key stuff, where in ForeignTableClass I have to define id as being a foreign key for every table listed above?
one way to do that is to use type method :
names = ['table1', 'table2', 'table3']
for name in names:
type(name.title(), (MyDatasetBase, db.Model), { '__tablename__' : name })
# generate <class 'flask_sqlalchemy.Table1'> ...
There is maybe a more pythonic/elegant way to do that with the Metedata and Table.tometadata() method.

Overriding the table name in Flask-Alchemy

I am creating a Flask application and accessing the MySQL database using Flask-Alchemy.
I have following Class to access a table:
class price_table(db.Model):
id = db.Column(db.Integer, primary_key = True)
trans_id = db.Column(db.Integer)
timestamp = db.Column(db.Integer)
order_type = db.Column(db.String(25))
price = db.Column(db.Numeric(15,8))
quantity = db.Column(db.Numeric(25,8))
def __repr__(self):
return 'id'
For the table 'price_table' this works brilliantly, but problem is I have a few tables with the same columns as 'price_table' from which I only know the name at runtime.
I want to reuse the class above so I thought I could change tablename to the name of the table I need to read, but that does not work, the program keeps reading the 'price-table'
How do I override the tablename at runtime?
You should use: __tablename__ :
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(50), unique=True)
email = Column(String(120), unique=True)
http://flask.pocoo.org/docs/0.12/patterns/sqlalchemy/
Based on the comment left by jbub I found the following solution that does the trick just as needed.
from app import db
def ClassFactory(name):
tabledict={'id':db.Column(db.Integer, primary_key = True),
'trans_id':db.Column(db.Integer),
'timestamp':db.Column(db.Integer),
'order_type':db.Column(db.String(25)),
'price':db.Column(db.Numeric(25,8)),
'quantity':db.Column(db.Numeric(25,8)),}
newclass = type(name, (db.Model,), tabledict)
return newclass
You can overwrite price_table.table.name attribute, yet keep in mind that it will affect your price_table model so, unless you want to use it to create a new specialized version of this table in the db and you are not interacting with price_table model - I wouldn't recommend that.

Categories