SQLAlchemy - create an instance in another instances __init__ - python

Newish to SQLAlchemy (so my terminology may be a bit off). I want to create a database object inside the constructor of another, but the problem is I can't add said object to the session, so I get an error.
My schema looks a bit like the following:
class Tag(Base):
__tablename__ = 'tag'
id = Column(Integer, Sequence('tag_id_seq'), primary_key=True, nullable=False)
type = Column(String(1), nullable=False)
name = Column(String(255), unique=True, nullable=False)
def __init__(self, type, name):
self.type=type
self.name=name
def __repr__(self):
return "<Tag('%s')>" % (self.id)
class Venue:
__tablename__ = 'venue'
tag_id = Column(Integer)
tag_type = Column(String(1), nullable=False)
location = Column(String(255), nullable=False)
tag = ForeignKeyConstraint(
(tag_id, tag_type),
(Tag.id, Tag.type),
onupdate='RESTRICT',
ondelete='RESTRICT',
)
def __init__(self,name,location):
self.tag = Tag('V',name)
self.location = location
When I do the following:
session.add(Venue("Santa's Cafe","South Pole"))
I get an error:
UnmappedInstanceError: Class '__builtin__.instance' is not mapped
I assume this is because the the Tag object created in Venue's constructor is not added to the session. My question is how/when do I do this. (I'd really prefer to create that Tag object in the constructor if possible). I think I could do this with a scoped_session but that seems like a really bad solution.
Thanks

Inherit Venue from Base. Otherwise Venue won't be mapped.
Move ForeignKeyConstraint to __table_args__.
Replace currently meaningless tag property with relationship to Tag. The default value of cascade parameter to relationship contains 'save-update' rule that will add new referred object to the same session as parent.
From documentation:
save-update - cascade the Session.add() operation. This cascade
applies both to future and past calls to add(), meaning new items
added to a collection or scalar relationship get placed into the same
session as that of the parent, and also applies to items which have
been removed from this relationship but are still part of unflushed
history.

Related

How to insert related object only if not exists via SQLAlchemy ORM Relationship?

i'm a SQLAlchemy begginer ok?
Consider a have a on-to-may relatioship like
class Parent(Base):
__tablename__ = "parent_table"
id = Column(Integer, primary_key=True)
children = relationship("Child")
class Child(Base):
__tablename__ = "child_table"
id = Column(Integer, primary_key=True)
name = Column(String, unique=True)
parent_id = Column(Integer, ForeignKey("parent_table.id"))
I'm using repositories classes to control insert, finds and deletes for this Entities.
When a do a Parent insert, it insert all Child related objects automatically, but if there is a Child object with the same name it will raises a Integrity Error.
How do i set Child to insert only when it not exists?
I need to do this in a way that i call ParentRepository object do not extrapolate your responsibilities.
I try to put this behavior on the Child Repository class but, it doen't works when i try via Parent Repository because it's doents change default cascade insert behavior.

Is it possible to have SQL Alchemy database models point to attributes of a different model, so that when one model's data changes, so does the other?

I'm making a very simple warehouse management system, and I'd like for users to be able to create templates for items. The template will show up on a list, and then can individually be used to create instances of an item that will also gain a quantity and warehouse attribute.
The goal is, if one of the item templates gets modified to specify a different size or price, the size or price attributes of the actual item instance gets changed as well.
Here is my code in case that helps you visualize what I'm trying to do. I'm not sure if this is possible or if there is a different solution I should consider. It's my first time working with Flask SQLAlchemy.
class ItemTemplate(db.model):
"""This template will simply store the information related to an item type.
Individual items that will be associated with the warehouse they're stored in
will inherit this information from the item templates."""
_id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(15), unique=True, nullable=False)
price = db.column(db.Float, nullable=False)
cost = db.column(db.Float, nullable=False)
size = db.column(db.Integer, nullable=False)
lowThreshold = db.column(db.Integer, nullable=False)
# Actual items
class Item(db.model):
"""This template will be used to represent the actual items that are associated with a warehouse."""
_id = db.Column(db.Integer, primary_key=True)
quantity = db.Column(db.Integer, primary_key=True)
"""Here I want the Item attributes to be able to just point to attributes from the ItemTemplate class.
ItemTemplate(name='tape') <--- will be a template with the information for tape.
Item(name='tape') <--- will be an actually instance of tape that should inherit all the attributes from the tape template.
I want these attributes to be like pointers so that if the tape template has its name changed, for instance, to
'scotch tape', all the Item instances that point to the tape template will have their names changed."""
# Warehouse
class Warehouse(db.model):
_id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(15), unique=True, nullable=False)
capacity = db.Column(db.column(db.Integer, nullable=False))
items = db.relationship("Item", backref="warehouse", lazy=True)```
As I understand, you just declare a One-Many relationship between ItemTemplate and Item, that one template will be used for many items.
Define Model
Just try to declare their relationship like this
class ItemTemplate(db.model):
_id = db.Column(db.Integer, primary_key=True)
... # Other attribute
instances = db.relationship('item', backref='item_template', lazy=True)
class Item(db.model):
_id = db.Column(db.Integer, primary_key=True)
quantity = db.Column(db.Integer, primary_key=True)
item_template_id = db.Column(db.Integer, db.ForeignKey('item_template._id'), nullable=False)
Docs for more information about relationship:
https://flask-sqlalchemy.palletsprojects.com/en/2.x/models/#one-to-many-relationships
Query
Next time querying, just join two tables and you can have your ItemTemplate.name
items_qr = db.session.query(Item, ItemTemplate.name).join(ItemTemplate)
for item, item_name in items_qr:
print(item.id, item_name)
SQLAlchemy Doc for query.join(): https://docs.sqlalchemy.org/en/14/orm/query.html#sqlalchemy.orm.Query.join
Some relative SO questions may help
flask Sqlalchemy One to Many getting parent attributes
One-to-many Flask | SQLAlchemy

SAWarning: Object of type <Child> not in session, add operation along 'Parent.children' will not proceed

I'm stuck on this issue and I don't know how to fix it. This is my models.py file:
models.py
class TripDetail(db.Model):
"""
Base class for every table that contains info about a trip.
"""
__abstract__ = True
__bind_key__ = 'instructions'
id = db.Column(db.Integer, primary_key=True)
# list of fields
class Overview(TripDetail):
"""
Class that contains general information about a trip.
"""
__tablename__ = 'overviews'
__table_args__ = (
db.ForeignKeyConstraint(['user_id', 'calendar_id'], ['calendars.user_id', 'calendars.id'], ondelete='CASCADE'),
) # constraints on other tables, omitted here
user_id = db.Column(db.Integer, primary_key=True)
calendar_id = db.Column(db.Integer, primary_key=True)
calendar = db.relationship('Calendar', backref=db.backref('overviews', cascade='delete'), passive_deletes=True)
# other fields
class Step(TripDetail):
__tablename__ = 'steps'
overview_id = db.Column(db.Integer, db.ForeignKey('overviews.id', ondelete='CASCADE'))
overview = db.relationship('Overview', backref=db.backref('steps', cascade='delete'), passive_deletes=True)
# also other fields
And this is how I add items to the DB (the response parameter contains a dict that matches the classes, in such a way that it can be unpacked directly):
def add_instruction(response):
"""
Adds a travel instruction to the database.
"""
steps = response.pop('steps')
overview = Overview(**response)
for step in steps:
Step(overview=overview, **step)
db.session.add(overview)
db.session.commit()
logger.info(f"Stored instruction with PK {(overview.id, overview.user_id, overview.calendar_id, overview.event_id)}")
Now, the overviews table is filled up correctly, but steps stays empty. Inspecting the logs, I receive this warning:
SAWarning: Object of type not in session, add operation along 'Overview.steps' will not proceed
(orm_util.state_class_str(state), operation, prop))
What am I doing wrong?
Normally, when add()ing objects to a session, their related objects will get auto-added like you wanted. That behavior is controlled by the relationship's cascade.
Setting cascade to 'delete' in Steps.overview removes the default 'save-update', which is what turns on the auto-adding. You could just add it back with cascade='save-update, delete', but take a look at the possible traits and see what else you might need. A common set is 'all, delete-orphan'.
And remember these are strictly ORM behaviors; setting a 'delete' in your cascade won't set the column's ON [event] CASCADE.
Well, I've solved this by expliciting adding the created step to the session. Still have no idea what the warning means though, so I'll just leave this here. My fix:
for step in steps:
step = Step(overview=overview, **step) # explicitly add
db.session.add(step)

SqlAlchemy attribute that tracks an assigned attribute

Goal: Create an SQLAlchemy attribute which tracks/follows changes in another object's SQLAlchemy attribute.
Given:
class ClazzA():
attributeA = Column(JSONDict)
class ClazzB():
attributeB = Column(?)
objectA = ClazzA()
objectA.attributeA = {'foo': 1}
objectB = ClazzB()
objectB.attributeB = objectA.attributeA
objectA.attributeA['foo'] = 2
JSONDict is associated with MutableDict as described here: http://docs.sqlalchemy.org/en/latest/orm/extensions/mutable.html#module-sqlalchemy.ext.mutable , i.e. the JSONDict type allows for mutation tracking.
So we have this dictionary on objectA whose changes are being recorded by SQLAlchemy. I would like for attributeB to track attributeA such that even if the application is restarted (i.e. the attributes are reloaded from the DB), then attributeB will continue to reflect changes made to attributeA's dictionary.
Of course, this is closely related to the fact that Python doesn't have an idea of pointers. I was wondering if SQLAlchemy has a solution for this particular problem.
TL;DR
You want a one-to-many relationship.
from sqlalchemy import ForeignKey, Integer, Column
from sqlalchemy.orm import relationship
class Widget(Base):
__tablename__ = 'widget'
widget_id = Column(Integer, primary_key=True)
# name columns, type columns, ...
json = Column(JSONDict)
class ClazzB(Base):
__tablename__ = 'clazzb'
clazzb_id = Column(Integer, primary_key=True)
# Your "attributeB"
widget_id = Column(Integer,
ForeignKey('widget.widget_id',
onupdate='cascade',
ondelete='cascade'),
nullable=False)
widget = relationship('Widget')
# possible association_proxy
#widget_json = association_proxy('widget', 'json')
Using a Relationship
Define a relationship between models ClazzA and ClazzB. Now since we don't have the whole picture, the below definitions are just examples.
from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship
class ClazzA(Base): # replace Base with the base class of your models
__tablename__ = 'clazza' # replace with the real tablename
# T is the type of your primary key, the column name is just an example
clazza_id = Column(T, primary_key=True)
class ClazzB(Base):
# The column that will relate this model to ClazzA
clazza_id = Column(T, ForeignKey('clazza.clazza_id',
onupdate='cascade',
ondelete='cascade'),
nullable=False)
# A handy accessor for relationship between mapped classes,
# not strictly required. Configurable to be either very lazy
# (loaded if accessed by issuing a SELECT) or eager (JOINed
# when loading objectB for example)
objectA = relationship('ClazzA')
Now instead of adding a reference to attributeA of ClazzA to ClazzB add a reference to related objectA to objectB on initialization.
objectB = ClazzB(..., objectA=objectA)
The two are now related and to access attributeA of related objectA through objectB do
objectB.objectA.attributeA
No need to track changes to attributeA, since it is the attributeA of the instance.
Now if you must have an attribute attributeB on ClazzB (to avoid refactoring existing code or some such), you could add a property
class ClazzB:
#property
def attributeB(self):
return self.objectA.attributeA
which will return the attributeA of the related objectA with
objectB.attributeB
objectB.attributeB['something'] = 'else'
and so on.
There is also an SQLAlchemy method for accessing attributes across relationships: association proxy. It supports simple querying, but is not for example subscriptable.
class ClazzB(Base):
attributeB = association_proxy('objectA', 'attributeA')
If you wish for ClazzB.attributeB to access values from the JSONDict under certain key, you can for example use something like this
class ClazzB(Base):
key = Column(Unicode)
#property
def attributeB(self):
return self.objectA.attributeA[self.key]
You can also make attributeB work as an SQL expression on class level using hybrid properties, if you need such a thing. You would have to write your class level expressions yourself though.

Python 3 and sqlachemy model's properties

I have two declarative sqlalchemy models.
class Users(Base):
__tablename__ = 'Users'
ID = Column(INTEGER, primary_key=True)
_Activities = relationship('Activities', lazy='subquery')
class UserCourseActivities(Base):
__tablename__ = 'Activities'
ActivityID = Column(INTEGER, primary_key=True)
UserID = Column(INTEGER, ForeignKey('Users.ID'))
ActivityCount = Column(INTEGER)
Is there a way to have each instance of Users have a total (activity count) in their __dict__? I've tried adding other class attributes, but I fear I might have to use classical mappings. The Users table has a lot of relations that make the declarative method much more attractive. Is there any way to accomplish this?
Can I use the #column_property decorator? I have no idea how to actually use it though.
Turns out that column property isn't a decorator.
activity_total = column_property(
select(
[func.sum(
Activities.ActivityCount
)
]).\
where(Activities.UserID==PK1).\
correlate_except(Activities)
) #This is officially the ugliest thing I have ever seen
This 'column' shows up in the User instances __dict__ too.

Categories