How do I use association proxy with classical mapping? - python

I read around the internet on examples using association proxies, but all of them I found were written with a declarative style of mapping.
Can I use them in a classical mapping? The reason why is that I'm implementing database persistence to an already existent class hierarchy and I'd like keep the db-related code in a separate file.
As an example, let's assume a model where we have an Item and an Order objects. We build a many-to-many relationship with an extra field in the association table that is, as an example, the price paid for the item:
item = Table('item', metadata,
Column('id', Integer, primary_key=True),
Column('name', String),
Column('quantity', Integer)
)
order = Order('order', metadata,
Column('id', Integer, primary_key=True),
Column('customer_id', Integer, ForeignKey('user.id'))
)
order_item = Table('order_item', metadata,
Column('order_id', Integer, ForeignKey('order.id')),
Column('item_id', Integer, ForeignKey('item.id')),
Column('price', Float)
)
# Mapping of Tables to non-orm objects
Let's assume I'd like to access in a nice way the items of an order as explained in the docs for a User-Keyword example . In the declarative way an attribute is added to the class that is an association proxy:
# ... Class definition ...
items = association_proxy('order_items', 'items')
How do I do this in a classical mapping style?

From the documentation:
The AssociationProxy object produced by the association_proxy()
function is an instance of a Python descriptor. It is always declared
with the user-defined class being mapped, regardless of whether
Declarative or classical mappings via the mapper() function are used.
So, just define it on the class:
class Order(object):
...
items = association_proxy('order_items', 'items')

Related

SQLAlchemy Self-Referential Many to Many Symmetric Relationship

I am using python 2.7 with SQLAlchemy, and trying to model a friendship relation with a many to many relationship.
I need the table to be completely symmetric; if A is B's friend, then it must be the other way around also.
I've tried to model the relationship with secondary friendship table, and connect it to the model using primary- and secondaryjoin, but I'm starting to get the feeling that I'm going in the wrong direction.
I found this post where someone tried to model the same thing using a one to many relationship, but this does not work for me, because my friendship relationship is not a one to many.
I have managed to achieve a working model using many to many table, if I am kipping a "duplicate": when I want to add B as A's friend, I add A as B's friend too. But I feel that the proposed solution should be more neat.
The end game here is similar to Facebook's friendship modeling. A can only be B's friend if B is A's friend.
The first attempt of using custom primary- and secondaryjoin conditions could be augmented with a composite "secondary", which in this case would be a union of both possible ways of selecting from the association table. Given a toy user model such as
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
email = Column(Unicode(255), unique=True)
the association table could look like
friendship = Table(
"friendship", Base.metadata,
Column("left_id", ForeignKey("user.id"), primary_key=True),
Column("right_id", ForeignKey("user.id"), primary_key=True))
and the composite "secondary"
friends = select([friendship.c.left_id.label("left_id"),
friendship.c.right_id.label("right_id")]).\
union_all(select([friendship.c.right_id,
friendship.c.left_id])).\
alias("friends")
Using the above, User model would define the relationship as
User.friends = relationship(
"User", secondary=friends,
primaryjoin=User.id == friends.c.left_id,
secondaryjoin=User.id == friends.c.right_id,
viewonly=True)
The unfortunate side effect is that the relationship is readonly and you'd have to manually insert rows to friendship to make users friends. Also there's the issue of duplicates, because friendship could still contain both (1, 2) and (2, 1), for example. Adding a check constraint that enforces an ordering on left and right id tackles the duplicate issue:
# NOTE: This has to be done *before* creating your tables. You could also
# pass the CheckConstraint as an argument to Table directly.
chk = CheckConstraint(friendship.c.left_id < friendship.c.right_id)
friendship.append_constraint(chk)
The application would have to order the ids upon insertion, though. To remedy this the union used as "secondary" could be hidden in a writable view. SQLAlchemy does not have a construct for handling views out of the box, but there's a usage recipe for just that. Using the recipe friends becomes:
friends = view(
"friends",
Base.metadata,
select([friendship.c.left_id.label("left_id"),
friendship.c.right_id.label("right_id")]).\
union_all(select([friendship.c.right_id,
friendship.c.left_id])))
and to make the view writable some triggers are required:
# For SQLite only. Other databases have their own syntax for triggers.
DDL("""
CREATE TRIGGER friends_insert_trg1 INSTEAD OF INSERT ON friends
WHEN new.left_id < new.right_id
BEGIN
INSERT INTO friendship (left_id, right_id)
VALUES (new.left_id, new.right_id);
END;
""").execute_at("after-create", Base.metadata)
DDL("""
CREATE TRIGGER friends_insert_trg2 INSTEAD OF INSERT ON friends
WHEN new.left_id > new.right_id
BEGIN
INSERT INTO friendship (left_id, right_id)
VALUES (new.right_id, new.left_id);
END;
""").execute_at("after-create", Base.metadata)
It'd be nice to bind these to the creation of the view more closely, but this will do as well, as long as you register them after defining the view. With the triggers in place you can remove the viewonly=True argument from the User.friends relationship.
Putting it all together:
from sqlalchemy import \
Table, Column, Integer, Unicode, ForeignKey, CheckConstraint, DDL, \
select
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
from view import view
Base = declarative_base()
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
email = Column(Unicode(255), unique=True)
friendship = Table(
"friendship",
Base.metadata,
Column("left_id", ForeignKey("user.id"), primary_key=True),
Column("right_id", ForeignKey("user.id"), primary_key=True),
CheckConstraint("left_id < right_id"))
friends = view(
"friends",
Base.metadata,
select([friendship.c.left_id.label("left_id"),
friendship.c.right_id.label("right_id")]).\
union_all(select([friendship.c.right_id,
friendship.c.left_id])))
User.friends = relationship(
"User", secondary=friends,
primaryjoin=User.id == friends.c.left_id,
secondaryjoin=User.id == friends.c.right_id)
DDL("""
CREATE TRIGGER friends_insert_trg1 INSTEAD OF INSERT ON friends
WHEN new.left_id < new.right_id
BEGIN
INSERT INTO friendship (left_id, right_id)
VALUES (new.left_id, new.right_id);
END;
""").execute_at("after-create", Base.metadata)
DDL("""
CREATE TRIGGER friends_insert_trg2 INSTEAD OF INSERT ON friends
WHEN new.left_id > new.right_id
BEGIN
INSERT INTO friendship (left_id, right_id)
VALUES (new.right_id, new.left_id);
END;
""").execute_at("after-create", Base.metadata)

Integer field not autoincrementing in SQLAlchemy

I have a Flask-SQLAlchemy model with an Integer field that I'd like to autoincrement. It's not a primary key; it's a surrogate ID. The model looks like:
class StreetSegment(db.Model):
id = db.Column(db.Integer, autoincrement=True)
seg_id = db.Column(db.Integer, primary_key=True)
When I create the table in my Postgres database, the id field is created as a plain integer. If I insert rows without specifying a value for id, it doesn't get populated. Is there some way I can force SQLAlchemy to use SERIAL even if it isn't the primary key?
Use Sequence instead of autoincrement:
id = db.Column(db.Integer, db.Sequence("seq_street_segment_id"))
SQLAlchemy does not support auto_increment for non-primary-key columns.
If your database supports it, you can setup the same behavior using sequences. PostgreSQL supports this. Sequences actually are not bound to a very specific column. Instead, they exist on the database level and can be reused. Sequences are the exact construct, SQLAlchemy uses for auto incrementing primary-key columns.
To use a sequence as described in the accepted answer, it must exist. Following, I have an example of an alembic migration with SQLAlchemy to achieve that.
You can associate a sequence with a column in the column constructor. The DDL Expression Constructs API helps you creating and dropping the sequence.
An example:
from alembic import op
import sqlalchemy as sa
measurement_id_seq = sa.Sequence('Measurement_MeasurementId_seq') # represents the sequence
def upgrade():
op.execute(sa.schema.CreateSequence(measurement_id_seq)) # create the sequence
op.create_table(
'Measurement',
sa.Column('DataSourceId',
sa.Integer,
sa.ForeignKey('DataSource.DataSourceId'),
nullable=False),
sa.Column('LocationId',
sa.Integer,
sa.ForeignKey('Location.LocationId'),
nullable=False),
sa.Column('MeasurementId',
sa.Integer,
measurement_id_seq, # the sequence as SchemaItem
server_default=measurement_id_seq.next_value())) # next value of the sequence as default
[...]
op.create_primary_key('Measurement_pkey', 'Measurement',
['DataSourceId', 'LocationId', 'Timestamp'])
pass
def downgrade():
op.execute(
sa.schema.DropSequence(sa.Sequence('Measurement_MeasurementId_seq')))
op.drop_constraint('Measurement_pkey', 'Measurement')
op.drop_table('Measurement')
pass

SQLAlchemy classical mapper "could not assemble any primary key columns for mapped table" despite presence of a primary key?

I'm working on a project with Alembic and SQLAlchemy, but I'm having trouble creating a simple entry in the database as a test. I get the following error:
sqlalchemy.exc.ArgumentError: Mapper Mapper|Sale|sales_cache could not assemble any primary key columns for mapped table 'sales_cache'
I've established the primary key (account_id) in both places below, any idea why SQLAlchemy doesn't recognize that or how to fix it? The other answers I've read have all dealt with exception cases for multiple/no primary keys, and have been solved accordingly, but this is a pretty vanilla model that keeps failing.
I've read up on other answers, most of which deal with the declarative system:
class Sale(Base):
__tablename__ = 'sales_cache'
But I'm required to use the classical mapping system; here's my mapped class and schema, respectively:
class Sale(object):
def __init__(self, notification):
self._sale_id = self._notification.object_id
self._account_id = self._notification.account_id
### schema.py file ###
from sqlalchemy.schema import MetaData, Table, Column
from sqlalchemy.types import (Unicode, Integer)
from database import metadata
metadata = MetaData()
sales_cache = Table('sales_cache', metadata,
Column('account_id', Integer, primary_key=True, autoincrement=False),
Column('sale_id', Integer, nullable=False)
)
And this is the relevant line from my alembic revision:
sa.Column('account_id', sa.Integer(), primary_key=True, autoincrement=False),
I thought it might be failing because I was setting self._sale_id and self._account_id instead of self.sale_id and self.account_id (without the underscore), but nothing changed when I tried it this way too.
Thanks in advance

Can SQLAlchemy secondary tables be db.Model instances?

I'm working on a Flask-SQLAlchemy project and I've implemented a nice JSON serialization method that applies to my SQLAlchemy models. After querying the DB, I can then easily present that data via a REST API. When I'm using secondary tables for many-to-many relationships, those tables are instances of db.Table, like so:
elections_voters = db.Table(
'elections_voters',
db.metadata,
db.Column('election_id', db.Integer, db.ForeignKey('elections.id'), primary_key=True),
db.Column('user_id', db.Integer, db.ForeignKey('users.id'), primary_key=True),
)
class Election(MyModel):
__tablename__ = 'elections'
id = db.Column(db.Integer, db.Sequence('election_id_seq'), autoincrement=True, primary_key=True)
name = db.Column(db.Unicode(255))
voters = db.relationship('User', secondary=elections_voters, backref='electionsVoting')
Let's say I wanted an API that presented just a list of voters for a particular election. I'd do something like Election.query.get_or_404(election_id), then return election.voters.mycustomserialize(), as voters would be populated by SQLAlchemy. However, it's not an instance of db.Model like its parent, so I can't use my serializing method on that child.
Is there a way to set up my models such that the elections_voters secondary table is a full instance of db.Model rather than just db.Table, and is that wise? My serialization method needs access to the column names, which is why I haven't just split it out into a standalone method.
I was over-thinking it:
In this case, elections.voters is just a list, not a model instance. I can just use list comprehension to handle the serializing.
Also, there's Association Objects that do what I described, but I don't think I'll need them.

How do I make a relationship to a classless value in SQLAlchemy?

I'm using SQLAlchemy under Flask. I have a table that represents a mode my system can be in. I also have a table that contains lists of elevations that are applicable to each mode. (So this is a many-to-many relationship.) In the past when I've added an attribute (like elevations inside ScanModes here) to a class mapped to a table, the target was also a class.
Does it make more sense to wrap the elevations in a class (and make a corresponding table?) so I can use relationship to make ScanModes.elevations work, or should I use a query-enabled property? I'm also open to other suggestions.
elevation_mode_table = db.Table('elevation_mode', db.metadata,
db.Column('scan_mode', db.String,
db.ForeignKey('scan_modes'),
nullable=False),
db.Column('elevation', db.Float,
nullable=False),
db.PrimaryKeyConstraint('scan_mode',
'elevation'))
class ScanModes(db.Model):
scan_mode = db.Column(db.String, primary_key=True)
elevations = ?
def __init__(self, scan_mode):
self.scan_mode = scan_mode
the most straightforward approach would be to just map elevation_mode_table to a class:
from sqlalchemy.orm import mapper
class ElevationMode(object):
pass
mapper(ElevationMode, elevation_mode_table)
class ScanModes(db.Model):
elevations = relationship(ElevationMode)
of course even easier is to just have ElevationMode be a declared class in the first place, if you need to deal with elevation_mode_table you'd get that from ElevationMode.__table__ ...
The "query enabled property" idea here, sure you could do that too though you'd lose the caching benefits of relationship.

Categories