SQLAlchemy association proxy and lists - python

I'm trying to work out how to get an association proxy on a many to many relationship to return an empty list by default.
So based on the example at:
https://raw.githubusercontent.com/zzzeek/sqlalchemy/master/examples/generic_associations/discriminator_on_association.py
We have:
from sqlalchemy.ext.declarative import as_declarative, declared_attr
from sqlalchemy import create_engine, Integer, Column, \
String, ForeignKey
from sqlalchemy.orm import Session, relationship, backref
from sqlalchemy.ext.associationproxy import association_proxy
#as_declarative()
class Base(object):
"""Base class which provides automated table name
and surrogate primary key column.
"""
#declared_attr
def __tablename__(cls):
return cls.__name__.lower()
id = Column(Integer, primary_key=True)
class AddressAssociation(Base):
"""Associates a collection of Address objects
with a particular parent.
"""
__tablename__ = "address_association"
discriminator = Column(String)
"""Refers to the type of parent."""
__mapper_args__ = {"polymorphic_on": discriminator}
class Address(Base):
"""The Address class.
This represents all address records in a
single table.
"""
association_id = Column(Integer, ForeignKey("address_association.id"))
street = Column(String)
city = Column(String)
zip = Column(String)
association = relationship("AddressAssociation", backref="addresses")
parent = association_proxy("association", "parent")
def __repr__(self):
return "%s(street=%r, city=%r, zip=%r)" % \
(self.__class__.__name__, self.street,
self.city, self.zip)
class HasAddresses(object):
"""HasAddresses mixin, creates a relationship to
the address_association table for each parent.
"""
#declared_attr
def address_association_id(cls):
return Column(Integer, ForeignKey("address_association.id"))
#declared_attr
def address_association(cls):
name = cls.__name__
discriminator = name.lower()
assoc_cls = type(
"%sAddressAssociation" % name,
(AddressAssociation, ),
dict(
__tablename__=None,
__mapper_args__={
"polymorphic_identity": discriminator
}
)
)
cls.addresses = association_proxy(
"address_association", "addresses",
creator=lambda addresses: assoc_cls(addresses=addresses)
)
return relationship(assoc_cls,
backref=backref("parent", uselist=False))
class Customer(HasAddresses, Base):
name = Column(String)
class Supplier(HasAddresses, Base):
company_name = Column(String)
engine = create_engine('sqlite://', echo=True)
Base.metadata.create_all(engine)
session = Session(engine)
But if I try to look at the address of a Customer, I get None rather than [] that I'd expect:
(Pdb) foo = Customer()
(Pdb) foo.addresses
(Pdb)
(Pdb) type(foo.addresses)
<type 'NoneType'>
Which means I can't simply iterate over foo.addresses without first checking it is not None.
I tried setting uselist = True on the association proxy:
return relationship(assoc_cls,
uselist=True,
backref=backref("parent", uselist=False))
Which sort of works, but then the constructor fails:
(Pdb) foo.addresses
[]
(Pdb) foo.addresses.append(Address())
*** TypeError: Incompatible collection type: Address is not list-like
(Pdb)
I tried messing with the creator to make it construct a list, but then cause all manner of side effects, included nested lists:
cls.addresses = association_proxy(
"address_association", "addresses",
creator=lambda addresses: assoc_cls(addresses=[addresses,])
Any idea the best way to sort this out properly?
-Matt

Related

How to create a Union table inside the SQL database using SQLAlchemy?

I have the following structure :
import sqlalchemy as sa
import sqlalchemy.orm as orm
Base = orm.declarative_base()
class Equity(Base) :
__tablename__ = 'equities'
id = sa.Column(sa.String, primary_key=True)
name = sa.Column(sa.String, nullable=False)
currency = sa.Column(sa.String, nullable=False)
country = sa.Column(sa.String, nullable=False)
sector = sa.Column(sa.String, nullable=True)
def __repr__(self) :
return f"Equity('Ticker: {self.id}', 'Name: {self.name}')"
class Bond(Base) :
__tablename__ = 'bonds'
id = sa.Column(sa.String, primary_key=True)
name = sa.Column(sa.String, nullable=False)
country = sa.Column(sa.String, nullable=False)
currency = sa.Column(sa.String, nullable=False)
sector = sa.Column(sa.String, nullable=False)
def __repr__(self) :
return f"Bond('Ticker: {self.id}', 'Name: {self.name}')"
And I want to create a new permanent table inside the database that is the UNION of those two tables (using the columns ID and CURRENCY)
I know I can create that outside using this :
results = session.query(Equity.id, Equity.currency).union(session.query(Bond.id, Bond.currency))
But I want (if possible) to have a relationship table inside my Database that automatically updates when I change anything on either on the EQUITIES or BONDS table. Something like this :
class NewUnionTable(Base) :
<relationship><union>
Can someone help me create this, please?
Appreciate very much
From the Views recipe and your classes (simplified), here is a functioning version of the view of the union of your sets of columns.
Read through the recipe for more details, but simply it's creating the necessary compiler extensions and using them when appropriate via an event.
from sqlalchemy import Column, String, create_engine, event, inspect, select, table, union
from sqlalchemy.ext import compiler
from sqlalchemy.orm import Session, declarative_base
from sqlalchemy.schema import DDLElement
# boilerplate from https://github.com/sqlalchemy/sqlalchemy/wiki/Views
class CreateView(DDLElement):
def __init__(self, name, selectable):
self.name = name
self.selectable = selectable
class DropView(DDLElement):
def __init__(self, name):
self.name = name
#compiler.compiles(CreateView)
def _create_view(element, compiler, **kw):
return "CREATE VIEW %s AS %s" % (
element.name,
compiler.sql_compiler.process(element.selectable, literal_binds=True),
)
#compiler.compiles(DropView)
def _drop_view(element, compiler, **kw):
return "DROP VIEW %s" % (element.name)
def view_exists(ddl, target, connection, **kw):
return ddl.name in inspect(connection).get_view_names()
def view_doesnt_exist(ddl, target, connection, **kw):
return not view_exists(ddl, target, connection, **kw)
def view(name, metadata, selectable):
t = table(name)
t._columns._populate_separate_keys(
col._make_proxy(t) for col in selectable.selected_columns
)
event.listen(
metadata,
"after_create",
CreateView(name, selectable).execute_if(callable_=view_doesnt_exist),
)
event.listen(
metadata,
"before_drop",
DropView(name).execute_if(callable_=view_exists),
)
return t
# demo
Base = declarative_base()
class Equity(Base):
__tablename__ = "equities"
id = Column(String, primary_key=True)
currency = Column(String, nullable=False)
class Bond(Base):
__tablename__ = "bonds"
id = Column(String, primary_key=True)
currency = Column(String, nullable=False)
common_view = view(
"bonds_equities_union_view",
Base.metadata,
union(
select(Equity.id.label("id"), Equity.currency.label("currency")),
select(Bond.id.label("id"), Bond.currency.label("currency")),
),
)
engine = create_engine("sqlite://", echo=True, future=True)
Base.metadata.create_all(engine)
with Session(engine) as session:
session.add_all(
[
Equity(id="AAA", currency="EUR"),
Equity(id="AAB", currency="USD"),
Bond(id="AAA", currency="EUR"),
Equity(id="EEF", currency="GBP"),
]
)
session.commit()
with Session(engine) as session:
results = session.execute(select(common_view)).all()
print(results) # [('AAA', 'EUR'), ('AAB', 'USD'), ('EEF', 'GBP')]
NB. this is a union, so it's only distinct values. Notice four instances inserted but only three in the view. This deduplication (sort + filter) is slow on large datasets, if you do not care use a union_all which allows duplicates.

Sqlalchemy self referencing many-to-many with association proxy and object mapped table

I'm trying to build a self referencing many-to-many model in Flask. I've already checked a lot of example on the internet, but in my case I would like to use an object mapped table for handling the relationship. Also because I'm using Flask with marshmallow I would like to include association proxies in my table to make the serialization of the model easier. Every example what I found is using backref but I wanted to make it with back_populates for the sake of readability. Currently I'm not sure if it's possible.
Please find below my minimal example to demonstrate the issue.
from sqlalchemy import Table, Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
engine = create_engine('sqlite:///:memory:', echo=True)
Session = sessionmaker(bind=engine)
session = Session()
Base = declarative_base()
class NodeRelation(Base):
__tablename__ = "node_relation"
parent_id = Column(Integer, ForeignKey('node.id'), primary_key=True)
parent = relationship('Node',
primaryjoin="NodeRelation.parent_id == node.c.id",
back_populates='parent_childs',
#foreign_keys=parent_id
)
child_id = Column(Integer, ForeignKey('node.id'), primary_key=True)
child = relationship('Node',
primaryjoin="NodeRelation.child_id == node.c.id",
back_populates='child_parents',
#foreign_keys=child_id
)
def __init__(self, parent=None, child=None, **kwargs):
super().__init__(**kwargs)
if child:
self.child = child
if parent:
self.parent = parent
def __repr__(self):
return "(parent_id: %s, child_id: %s)" % (self.parent_id, self.child_id)
class Node(Base):
__tablename__ = "node"
id = Column(Integer, primary_key=True, autoincrement=True)
title = Column(String())
parent_childs = relationship('NodeRelation',
primaryjoin="Node.id==node_relation.c.parent_id",
back_populates='parent',
cascade='all, delete',
#foreign_keys=NodeRelation.parent_id
)
parents = association_proxy('parent_childs', 'parent',
creator=lambda parent: NodeRelation(parent=parent))
child_parents = relationship('NodeRelation',
primaryjoin="Node.id==node_relation.c.child_id",
back_populates='child',
cascade='all, delete',
#foreign_keys=NodeRelation.child_id
)
childs = association_proxy('child_parents', 'child',
creator=lambda child: NodeRelation(child=child))
def __init__(self, title, **kwargs):
super().__init__(**kwargs)
self.title = title
def __repr__(self):
return "(id: %s, title: %s, childs: %s)" % (self.id, self.title, self.childs)
Base.metadata.create_all(engine)
n1 = Node("First")
n2 = Node("Second")
"""
# This is failing with: NOT NULL constraint failed: node_relation.parent_id
n1.childs.append(n2)
session.add(n1)
session.add(n2)
session.commit()
"""
# This one is working
c = NodeRelation(n1, n2)
session.add(n1)
session.add(n2)
session.add(c)
# Node 1 and Node 2 exists
q = session.query(NodeRelation).all()
print(q)
# This is failing with infinite recursion when childs property is displayed.
q2 = session.query(Node).all()
print(q2)
If I use the n1.childs.append() I've got a null contsraint error. If I directly construct the mapper object with n1 and n2 it's working fine, but as soon as I access the childs property I've got infinite recursion.
EDIT:
I've figured out that the custom creator lambda causing the appending error. So the updated code look like this:
from sqlalchemy import Table, Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
engine = create_engine('sqlite:///:memory:', echo=True)
Session = sessionmaker(bind=engine)
session = Session()
Base = declarative_base()
class NodeRelation(Base):
__tablename__ = "node_relation"
parent_id = Column(Integer, ForeignKey('node.id'), primary_key=True)
parent = relationship('Node',
primaryjoin="NodeRelation.parent_id == node.c.id",
back_populates='parent_childs',
#foreign_keys=parent_id
)
child_id = Column(Integer, ForeignKey('node.id'), primary_key=True)
child = relationship('Node',
primaryjoin="NodeRelation.child_id == node.c.id",
back_populates='child_parents',
#foreign_keys=child_id
)
def __init__(self, parent=None, child=None, **kwargs):
super().__init__(**kwargs)
if child:
self.child = child
if parent:
self.parent = parent
def __repr__(self):
return "(parent_id: %s, child_id: %s)" % (self.parent_id, self.child_id)
class Node(Base):
__tablename__ = "node"
id = Column(Integer, primary_key=True, autoincrement=True)
title = Column(String())
parent_childs = relationship('NodeRelation',
primaryjoin="Node.id==node_relation.c.parent_id",
back_populates='parent',
cascade='all, delete',
#foreign_keys=NodeRelation.parent_id
)
parents = association_proxy('parent_childs', 'parent')
child_parents = relationship('NodeRelation',
primaryjoin="Node.id==node_relation.c.child_id",
back_populates='child',
cascade='all, delete',
#foreign_keys=NodeRelation.child_id
)
childs = association_proxy('child_parents', 'child')
def __init__(self, title, **kwargs):
super().__init__(**kwargs)
self.title = title
def __repr__(self):
return "(id: %s, title: %s, childs: %s)" % (self.id, self.title, self.childs)
Base.metadata.create_all(engine)
n1 = Node("First")
n2 = Node("Second")
# This is failing with: NOT NULL constraint failed: node_relation.parent_id
n1.childs.append(n2)
session.add(n1)
session.add(n2)
session.commit()
# Node 1 and Node 2 exists
q = session.query(NodeRelation).all()
print(q)
# This is failing with infinite recursion when childs property is displayed.
q2 = session.query(Node).all()
print(q2)
So the only problem is that, when I try to access the childs property I've got a infinite recursion. I guess I messed up something with the relations.

SQLAlchemy how to create a relationship or mapping of a table's "type" row to the table model or mixin

I need to set the entity_type_id as a column value when I persist a row to a generic table of various entity_types. I should be able to load the entity_type_id for every specific instance at instantiation time because it is accessible via a simple select statement. I'd like to have that id automatically retrieved/set at the class (or instance) level without executing a query and/or manually setting to every time I persist a row of an "entity_type".
I tried an entity_type_id #property on the mixin that returns the id of the entity_type using the object_session but for reasons I don't fully understand the orm still inserts null as the entity_type_id value when I commit/flush the session. (my guess is having the "property" itself isn't the same thing as setting the attribute value on the instance and/or causing an issue because the column name from the base class has the same name)
Here's a slimmed down version of the relevant models in my schema:
class EntityType(Base):
__tablename__ = 'entity_type'
id = Column(UUID(as_uuid=True), primary_key=True, server_default=FetchedValue())
table_name = Column(String, nullable=False)
ui_label = Column(Text, unique=True, nullable=False)
entry_key = Column(Text, unique=True, nullable=False)
Base class model:
class TrackedEntity(Base):
#declared_attr
def __tablename__(cls):
return convert(cls.__name__)
__table_args__ = (
UniqueConstraint('entity_type_id', 'label'),
)
id = Column(UUID(as_uuid=True), primary_key=True, server_default=FetchedValue())
entity_type_id = Column('entity_type_id', ForeignKey('entity_type.id'))
label = Column('label', String, nullable=False)
entity_type = relationship('EntityType')
polymorphic_discriminator = column_property(select([EntityType.table_name]).where(EntityType.id == entity_type_id).as_scalar())
#declared_attr
def entity_type_label(cls):
return association_proxy('entity_type', 'label')
#declared_attr
def __mapper_args__(cls):
if cls.__name__ == 'TrackedEntity':
return {
"polymorphic_on": cls.polymorphic_discriminator,
"polymorphic_identity": cls.__tablename__
}
else:
return {"polymorphic_identity": cls.__tablename__}
Children class mixin:
class TrackedEntityMixin(object):
# noinspection PyMethodParameters
#declared_attr
def id(cls) -> Column:
return Column(ForeignKey('tracked_entity.id'), primary_key=True)
#gets me the id but isn't very helpful like this, still needs to be manually set like child.entity_type_id = child._entity_type_id
#property
def _entity_type_id(self):
return object_session(self). \
scalar(
select([EntityType.id]).
where(EntityType.table_name == self.__tablename__)
)
A child class model:
class DesignedMolecule(TrackedEntityMixin, TrackedEntity):
extra = Column('extra', String)
parents = relationship('TrackedEntity', secondary='mix_dm_parent_entity')

Generating JSON from SQLAlchemy base class including subclases from relationship

I'm trying to generate the JSON of my SQLAlchemy classes, I followed this example:
https://blogs.gnome.org/danni/2013/03/07/generating-json-from-sqlalchemy-objects/
It’s working very fine, but now I want to include all the data of the subclasses generated by the relationship of SQLAchemy. I've tried several things, the last one is trying to iterate over the subclases but I don't know why the method subclasses doesn't return anything. This is the function tojson modified:
def tojson(self):
res=self.columnitems
for cls in self.__class__.__subclasses__():
res[cls.__name__]=cls.tojson()
return res
Do you know any way to do it?
Thanks in advance
I can't comment yet but based on the information provided I'm assuming you are trying to generate a json from your (related) sqlalchemy classes. You can use the marshmallow (https://marshmallow.readthedocs.io/en/latest/) for this.
The (quick) example below shows how you can generate a json using marshmallow of two related tables.
from sqlalchemy import Column, Integer, String, Boolean
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine, ForeignKey
from sqlalchemy.orm import sessionmaker, relationship
Base = declarative_base()
# Creating dummy classes...
class Owner(Base):
__tablename__ = 'owner'
id = Column('id', Integer, primary_key=True)
name = Column('name', String(250))
interested_in_cars = Column('interest', Boolean)
car = relationship('Car', uselist=False, back_populates="owner")
def __init__(self, name, interested_in_cars, id):
self.id = id
self.name = name
self.interested_in_cars = interested_in_cars
def __repr__(self):
return '< (id={id}) Owner: {name} - interested: {interested_in_cars} >'.format(id=self.id,
name=self.name,
interested_in_cars=self.interested_in_cars)
class Car(Base):
__tablename__ = 'car'
id = Column('id', Integer, primary_key=True)
brand = Column(String(250))
owner_id = Column(Integer, ForeignKey('owner.id'))
owner = relationship('Owner', back_populates='car')
def __init__(self, owner_id, brand):
self.owner_id = owner_id
self.brand = brand
def __repr__(self):
return '< Owner: {owner_id} - Car: {brand} >'.format(owner_id=self.owner_id, brand=self.brand)
engine = create_engine('sqlite:///')
session = sessionmaker()
session.configure(bind=engine)
ex_ses = session()
Base.metadata.create_all(engine)
owner_1 = Owner(interested_in_cars=True, name='Owner a', id=1)
owner_2 = Owner(interested_in_cars=False, name='Owner b', id=2)
ex_ses.add(owner_1)
ex_ses.add(owner_2)
# ID's - quick example
car_1 = Car(owner_id=1, brand='Car a')
car_2 = Car(owner_id=2, brand='Car b')
ex_ses.add(car_1)
ex_ses.add(car_2)
ex_ses.commit()
# Using marshmallow to generate the json
from marshmallow import Schema, fields, pprint
class OwnerShema(Schema):
id = fields.Int()
name = fields.String()
interested_in_cars = fields.Boolean()
car = fields.Nested('CarShema')
class CarShema(Schema):
id = fields.Int()
brand = fields.String()
# Example Owners and cars
owners_cars = ex_ses.query(Owner).all()
print('Owners and cars: ', owners_cars)
owners_cars_shema = OwnerShema()
pprint(owners_cars_shema.dump(owners_cars, many=True).data)
For more information see the marshmallow documentation (link provided above).

sqlalchemy relation through another (declarative)

Is anyone familiar with ActiveRecord's "has_many :through" relations for models? I'm not really a Rails guy, but that's basically what I'm trying to do.
As a contrived example consider Projects, Programmers, and Assignments:
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy import Column, ForeignKey
from sqlalchemy.types import Integer, String, Text
from sqlalchemy.orm import relation
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Assignment(Base):
__tablename__ = 'assignment'
id = Column(Integer, primary_key=True)
description = Column(Text)
programmer_id = Column(Integer, ForeignKey('programmer.id'))
project_id = Column(Integer, ForeignKey('project.id'))
def __init__(self, description=description):
self.description = description
def __repr__(self):
return '<Assignment("%s")>' % self.description
class Programmer(Base):
__tablename__ = 'programmer'
id = Column(Integer, primary_key=True)
name = Column(String(64))
assignments = relation("Assignment", backref='programmer')
def __init__(self, name=name):
self.name = name
def __repr__(self):
return '<Programmer("%s")>' % self.name
class Project(Base):
__tablename__ = 'project'
id = Column(Integer, primary_key=True)
name = Column(String(64))
description = Column(Text)
assignments = relation("Assignment", backref='project')
def __init__(self, name=name, description=description):
self.name = name
self.description = description
def __repr__(self):
return '<Project("%s", "%s...")>' % (self.name, self.description[:10])
engine = create_engine('sqlite://')
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
Projects have many Assignments.
Programmers have many Assignments. (understatement?)
But in my office at least, Programmers also have many Projects - I'd like this relationship to be inferred through the Assignments assigned to the Programmer.
I'd like the Programmer model to have a attribute "projects" which will return a list of Projects associated to the Programmer through the Assignment model.
me = session.query(Programmer).filter_by(name='clay').one()
projects = session.query(Project).\
join(Project.assignments).\
join(Assignment.programmer).\
filter(Programmer.id==me.id).all()
How can I describe this relationship clearly and simply using the sqlalchemy declarative syntax?
Thanks!
There are two ways I see:
Define a relation Programmer.projects with secondary='assignment'.
I define Assignment.project as relation and Programmer.projects as association_proxy('assignments', 'project') (probably you'd also like to define a creator). See Simplifying Association Object Relationships chapter for more information.

Categories