I have the following models:
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
children = relationship("Child", backref="parent")
...
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
...
There are more attributes for each class. The children are always created first. In my api, I want to send POST requests that creates a Parent with x amount of children. For just a few children, I want the complete child object with all attributes to be in the request body. But there could be a use case where a parent has 100 children. For those, I want to implement a "light" version, where in the request body there would only be the id of each child, instead of the entire object with all attributes for each child.
Is there a built-in way or an example or an official term for such a behavior? I was googling light requests sqlalchemy but choulnd't really find much.
I think that there is not built-in mechanism of what you desire, because it is not a responsibility of a request how your objects are serialized. For your case I would implement something like that:
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
other_attr = Column(String)
def to_dict(self, is_light = False):
if is_light:
return {"id": seld.id}
else:
return {"id": self.id, "other_attr": self.other_attr}
Related
I'm trying to have a additional property on my object-relationship-mapped object.
I need to have a property that is calculated at the database using the ROW_NUMBER() function.
My Class looks something like this:
class Example(db.Model):
id = db.Column(db.Integer, primary_key=True)
group = db.Column(db.Integer)
I think I need to use the hybrid_property for this. I don't know to define the property but I think I have an idea about the expression:
class Example(db.Model):
id = db.Column(db.Integer, primary_key=True)
group_id = db.Column(db.Integer)
#hybrid_property
def group_specific_id(self):
# no idea what to put here
pass
#group_specific_id.expression
def project_specific_id(cls):
return db.session.query(func.row_number().over(partition_by=cls.group_id))
Am I on the right path? Can anyone help me here?
This definition should work for you:
class Example(db.Model):
id = db.Column(db.Integer, primary_key=True)
group_id = db.Column(db.Integer)
#hybrid_property
def group_specific_id(self):
return db.session.query(func.row_number().over(partition_by=self.group_id))
#group_specific_id.expression
def group_specific_id(cls):
return db.session.query(func.row_number().over(partition_by=cls.group_id))
And now you can use hybrid property like that:
example = Example(group_id=2)
example.group_specific_id
or like an expression
examples = session.query(Example).filter(Example.group_specific_id == <smth>)
I'm trying to write a query that will return exactly one child object from a parent.
My model is as follows:
class Parent (Base):
__tablename__ = 'parents'
id = Column(Integer, primary_key=True)
name = Column(String)
children_id = Column(Integer, ForeignKey('children.id'))
childern = relationship('Child', back_populates='parents')
class Child (Base):
__tablename__ = 'children'
id = Column(Integer, primary_key=True)
name = Column(String)
parent = relationship('Parent', back_populates='children')
Consider this example: there are three parents with their children:
Alice: Zander, Ygrite, Xaiver
Bob: Will, Ulric, Tammy
Chris: Will, Sam, Xaiver
Notice that Chris has coincidentally named some of his children the same as the others, even though they are distinct children.
If I want to find specifically Chris's child Xavier my first instinct is to either query for Parent Chris then iterate over his children until I find Xavier or to query for all children named Xavier then to iterate over them until I find the one with parent Chris.
However, this solution does not scale and definitely doesn't use SQLAlchemy's capabilities.
In continuing to research this challenge I came across this question and the accepted response: SQLAlchery query filter on child attribute
I tested this query...
s.query(Child).join(Parent, Child.parent).filter(Child.name == 'Xavier').filter(Parent.name == 'Chris')
...and it does exactly what I want it to do.
I need some models for instance following:
Work - e.g. works of literature.
Worker - e.g. composer, translator or something similar has contribution to work.
Thus, a 'type' field is required to distinguish workers by division of work. As SQLAlchemy's documentation, this case can benifit from association object like following:
class Work(base):
id = Column(Integer, primary_key=True)
name = Column(String(50))
description = Column(Text)
class Worker(base):
id = Column(Integer, primary_key=True)
name = Column(String(50))
description = Column(Text)
class Assignment(base):
work_id = Column(Integer, Foreignkey('work.id'), primary_key=True)
worker_id = Column(Integer, Foreignkey('worker.id'), primary_key=True)
type = Column(SmallInteger, nullable=True)
Nonetheless, how to take advantage of backref and alternatvie join condition for building relation immediately to implement that each Work object can retrieve and modify corresponding Worker(s) via different attributions for distinction. For example:
work = session.query(Work).get(1)
work.name
>>> 'A Dream of The Red Mansions'
work.composers
>>> [<Worker('Xueqin Cao')>]
work.translators
>>> [<Worker('Xianyi Yang')>, <Worker('Naidie Dai')>]
Vice versa:
worker = session.query(Worker).get(1)
worker.name
>>> 'Xueqin Cao'
worker.composed
>>> [<Work('A Dream of The Red Mansions')>]
worker.translated
>>> []
Adding secondaryjoin directly without secondary specified seems not feasible, besides, SQLAlchemy's docs notes that:
When using the association object pattern, it is advisable that the association-mapped table not be used as the secondary argument on a relationship() elsewhere, unless that relationship() contains the option viewonly=True. SQLAlchemy otherwise may attempt to emit redundant INSERT and DELETE statements on the same table, if similar state is detected on the related attribute as well as the associated object.
Then, is there some way to build these relations elegantly and readily ?
There's three general ways to go here.
One is, do a "vanilla" setup where you have "work"/"workers" set up without distinguishing on "type" - then, use relationship() for "composer", "composed", "translator", "translated" by using "secondary" to Assignment.__table__ along with custom join conditions, as well as viewonly=True. So you'd do writes via the vanilla properties only. A disadvantage here is that there's no immediate synchronization between the "vanilla" and "specific" collections.
Another is, same with the "vanilla" setup, but just use plain Python descriptors to give "composer", "composed", "translator", "translated" views in memory, that is, [obj.worker for obj in self.workers if obj.type == 'composer']. This is the simplest way to go. Whatever you put in the "vanilla" collections shows right up in the "filtered" collection, the SQL is simple, and there's fewer SELECT statements in play (one per Worker/Work instead of N per Worker/Work).
Finally, the approach that's closest to what you're asking, with primary joins and backrefs, but note with the association object, the backrefs are between Work/Assignment and Assignment/Worker, but not between Work/Worker directly. This approach probably winds up using more SQL to get at the results but is the most complete, and also has the nifty feature that the "type" is written automatically. We're also using a "one way backref", as Assignment doesn't have a simple way of relating back outwards (there's ways to do it but it would be tedious). Using a Python function to automate creation of the relationships reduces the boilerplate, and note here I'm using a string for "type", this can be an integer if you add more arguments to the system:
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.associationproxy import association_proxy
Base = declarative_base()
def _work_assignment(name):
assign_ = relationship("Assignment",
primaryjoin="and_(Assignment.work_id==Work.id, "
"Assignment.type=='%s')" % name,
back_populates="work", cascade="all, delete-orphan")
assoc = association_proxy("%s_assign" % name, "worker",
creator=lambda worker: Assignment(worker=worker, type=name))
return assoc, assign_
def _worker_assignment(name):
assign_ = relationship("Assignment",
primaryjoin="and_(Assignment.worker_id==Worker.id, "
"Assignment.type=='%s')" % name,
back_populates="worker", cascade="all, delete-orphan")
assoc = association_proxy("%s_assign" % name, "work",
creator=lambda work: Assignment(work=work, type=name))
return assoc, assign_
class Work(Base):
__tablename__ = 'work'
id = Column(Integer, primary_key=True)
name = Column(String(50))
description = Column(Text)
composers, composer_assign = _work_assignment("composer")
translators, translator_assign = _work_assignment("translator")
class Worker(Base):
__tablename__ = 'worker'
id = Column(Integer, primary_key=True)
name = Column(String(50))
description = Column(Text)
composed, composer_assign = _worker_assignment("composer")
translated, translator_assign = _worker_assignment("translator")
class Assignment(Base):
__tablename__ = 'assignment'
work_id = Column(Integer, ForeignKey('work.id'), primary_key=True)
worker_id = Column(Integer, ForeignKey('worker.id'), primary_key=True)
type = Column(String, nullable=False)
worker = relationship("Worker")
work = relationship("Work")
e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)
session = Session(e)
ww1, ww2, ww3 = Worker(name='Xueqin Cao'), Worker(name='Xianyi Yang'), Worker(name='Naidie Dai')
w1 = Work(name='A Dream of The Red Mansions')
w1.composers.append(ww1)
w1.translators.extend([ww2, ww3])
session.add(w1)
session.commit()
work = session.query(Work).get(1)
assert work.name == 'A Dream of The Red Mansions'
assert work.composers == [ww1]
assert work.translators == [ww2, ww3]
worker = session.query(Worker).get(ww1.id)
assert worker.name == 'Xueqin Cao'
assert worker.composed == [work]
assert worker.translated == []
worker.composed[:] = []
# either do this...
session.expire(work, ['composer_assign'])
# or this....basically need composer_assign to reload
# session.commit()
assert work.composers == []
For example (eagerload/joinedload do the same thing):
session = Session()
parents = session.query(Parent).options(joinedload(Parent.children)).all()
session.close()
print parents[0].children # This works
print parents[0].children[0].parent # This gives a lazy loading error
Adding the following loop before closing the session works (and doesn't hit the DB):
for p in parents:
for c in p.children:
c.parent
Which is pretty dumb. Is there a way to alter the original query so that it loads both sides of the relation without adding more joins in the output SQL?
update In case it's relevant; here's the mapping
class Parent(Entity):
__tablename__ = "parent"
id = Column(Integer, primary_key=True)
children = relation("Child", backref="parent")
class Child(Entity):
__tablename__ = "child"
id = Column(Integer, primary_key=True)
parentId = Column(Integer, ForeignKey("parent.id"), index=True)
That's what contains_eager() option is for. Try the following:
parents = session.query(Parent).options(joinedload(Parent.children),
contains_eager('children.parent')).all()
In SQLAlchemy, we can declare tables and their relations like this:
user = Table(
'users', metadata,
Column('id', Integer, primary_key=True))
address = Table(
'adresses', metadata,
Column('id', Integer, primary_key=True),
Column('user_id', Integer, ForeignKey('user.id')))
class User(object): pass
class Address(object): pass
session.mapper(User, user, properties=dict(
'address' = relation(Address, backref='user', cascade="all")))
(Please notice the cascade relation in the line above.)
However, we can also use an alternate shorthand style, called declarative style, in which we can express the same things in fewer lines of code, omitting the mapper() relationships:
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
class Adress(Base):
__tablename__ = 'adresses'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('user.id')))
But if we use this declarative style, is there an alternative way to define cascade relationships?
Actually the use of "declarative" does not mean you omit the relationships. You still specify them, roughly the same way, but directly on the class as attributes rather than in the mapper:
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
address = relation('Address', backref='user', cascade='all')
class Address(Base):
__tablename__ = 'addresses'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('users.id'))
(Note that I've fixed the spelling of "address", in case you cut-and-paste this.)
The declarative documentation covers this. Note that in the simple cases, it figures out what foreign keys to use automatically, but you can be more explicit if required.
Also note the use of the string 'Address' since at the time I use it in the User class the related class has not yet been defined.