SQLAlchemy Attempting to Twice Delete Many to Many Secondary Relationship PostgreSQL - sqlalchemy

Same issue report in SQLAlchemy Attempting to Twice Delete Many to Many Secondary Relationship.
But accepted answer is not working.
Model structure is
class Product(Base):
""" The SQLAlchemy declarative model class for a Product object. """
__tablename__ = 'products'
id = Column(Integer, primary_key=True)
part_number = Column(String(10), nullable=False, unique=True)
name = Column(String(80), nullable=False, unique=True)
description = Column(String(2000), nullable=False)
categories = relationship('Category', secondary=product_categories,
backref=backref('categories', lazy='dynamic'))
class Category(Base):
""" The SQLAlchemy declarative model class for a Category object. """
__tablename__ = 'categories'
id = Column(Integer, primary_key=True)
lft = Column(Integer, nullable=False)
rgt = Column(Integer, nullable=False)
name = Column(String(80), nullable=False)
description = Column(String(2000), nullable=False)
order = Column(Integer)
products = relationship('Product', secondary=product_categories,
backref=backref('products', lazy='dynamic', order_by=name))
product_categories = Table('product_categories', Base.metadata,
Column('products_id', Integer, ForeignKey('products.id')),
Column('categories_id', Integer, ForeignKey('categories.id'))
)
Issue is when try to delete Product instance, SQLAlchemy returns an error
Traceback (most recent call last):
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/pyramid_debugtoolbar-2.3-py2.7.egg/pyramid_debugtoolbar/toolbar.py", line 178, in toolbar_tween
response = _handler(request)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/pyramid_debugtoolbar-2.3-py2.7.egg/pyramid_debugtoolbar/panels/performance.py", line 57, in resource_timer_handler
result = handler(request)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/pyramid/tweens.py", line 21, in excview_tween
response = handler(request)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/pyramid_tm-0.10-py2.7.egg/pyramid_tm/__init__.py", line 95, in tm_tween
reraise(*exc_info)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/pyramid_tm-0.10-py2.7.egg/pyramid_tm/__init__.py", line 83, in tm_tween
manager.commit()
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/transaction-1.4.3-py2.7.egg/transaction/_manager.py", line 111, in commit
return self.get().commit()
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/transaction-1.4.3-py2.7.egg/transaction/_transaction.py", line 280, in commit
reraise(t, v, tb)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/transaction-1.4.3-py2.7.egg/transaction/_transaction.py", line 271, in commit
self._commitResources()
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/transaction-1.4.3-py2.7.egg/transaction/_transaction.py", line 417, in _commitResources
reraise(t, v, tb)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/transaction-1.4.3-py2.7.egg/transaction/_transaction.py", line 389, in _commitResources
rm.tpc_begin(self)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/zope.sqlalchemy-0.7.5-py2.7.egg/zope/sqlalchemy/datamanager.py", line 90, in tpc_begin
self.session.flush()
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/SQLAlchemy-0.9.8-py2.7-macosx-10.6-intel.egg/sqlalchemy/orm/session.py", line 1919, in flush
self._flush(objects)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/SQLAlchemy-0.9.8-py2.7-macosx-10.6-intel.egg/sqlalchemy/orm/session.py", line 2037, in _flush
transaction.rollback(_capture_exception=True)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/SQLAlchemy-0.9.8-py2.7-macosx-10.6-intel.egg/sqlalchemy/util/langhelpers.py", line 60, in __exit__
compat.reraise(exc_type, exc_value, exc_tb)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/SQLAlchemy-0.9.8-py2.7-macosx-10.6-intel.egg/sqlalchemy/orm/session.py", line 2001, in _flush
flush_context.execute()
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/SQLAlchemy-0.9.8-py2.7-macosx-10.6-intel.egg/sqlalchemy/orm/unitofwork.py", line 372, in execute
rec.execute(self)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/SQLAlchemy-0.9.8-py2.7-macosx-10.6-intel.egg/sqlalchemy/orm/unitofwork.py", line 479, in execute
self.dependency_processor.process_deletes(uow, states)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/SQLAlchemy-0.9.8-py2.7-macosx-10.6-intel.egg/sqlalchemy/orm/dependency.py", line 1023, in process_deletes
secondary_update, secondary_delete)
File "/Users/derek/pyramid/myproject/lib/python2.7/site-packages/SQLAlchemy-0.9.8-py2.7-macosx-10.6-intel.egg/sqlalchemy/orm/dependency.py", line 1111, in _run_crud
result.rowcount)
StaleDataError: DELETE statement on table 'product_categories' expected to delete 1 row(s); Only 0 were matched.
Database is Postgres and When I remove the backref from one model, no issue but I need to use backref on both models.
Setting supports_sane_rowcount not working for postgres
engine.dialect.supports_sane_rowcount = engine.dialect.supports_sane_multi_rowcount = False
Is there any workable solution for this?

When I use your classes I get warnings like
SAWarning: relationship 'Product.products' will copy column products.id to column product_categories.products_id, which conflicts with relationship(s): 'Category.categories' (copies products.id to product_categories.products_id), 'Product.categories' (copies products.id to product_categories.products_id). If this is not the intention, consider if these relationships should be linked with back_populates, or if viewonly=True should be applied to one or more if they are read-only. For the less common case that foreign key constraints are partially overlapping, the orm.foreign() annotation can be used to isolate the columns that should be written towards. To silence this warning, add the parameter 'overlaps="categories,categories"' to the 'Product.products' relationship. (Background on this error at: https://sqlalche.me/e/14/qzyx)
The issue goes away if I use back_populates instead of backref:
class Product(Base):
# …
categories = relationship(
"Category",
secondary=product_categories,
back_populates="products",
lazy="dynamic",
order_by = "Category.name",
)
class Category(Base):
# …
products = relationship(
"Product",
secondary=product_categories,
back_populates="categories",
lazy="dynamic",
order_by="Product.name",
)
Complete working example:
from sqlalchemy import (
create_engine,
Column,
Integer,
String,
Table,
ForeignKey,
select,
)
from sqlalchemy.orm import declarative_base, relationship, Session
engine = create_engine(
"postgresql://scott:tiger#192.168.0.199/test",
)
Base = declarative_base()
product_categories = Table(
"product_categories",
Base.metadata,
Column("products_id", Integer, ForeignKey("products.id")),
Column("categories_id", Integer, ForeignKey("categories.id")),
)
class Product(Base):
__tablename__ = "products"
id = Column(Integer, primary_key=True)
part_number = Column(String(10), nullable=False, unique=True)
name = Column(String(80), nullable=False, unique=True)
description = Column(String(2000), nullable=False)
categories = relationship(
"Category",
secondary=product_categories,
back_populates="products",
lazy="dynamic",
order_by = "Category.name",
)
class Category(Base):
__tablename__ = "categories"
id = Column(Integer, primary_key=True)
lft = Column(Integer, nullable=False)
rgt = Column(Integer, nullable=False)
name = Column(String(80), nullable=False)
description = Column(String(2000), nullable=False)
order = Column(Integer)
products = relationship(
"Product",
secondary=product_categories,
back_populates="categories",
lazy="dynamic",
order_by="Product.name",
)
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
# setup
with Session(engine) as sess:
category_a = Category(
lft=0, rgt=0, name="category_a", description="category_a"
)
product_a = Product(
part_number="product_a",
name="product_a",
description="product_a",
categories=[category_a],
)
sess.add_all([category_a, product_a])
sess.commit()
# test
engine.echo = True
with Session(engine) as sess:
p = sess.execute(
select(Product).where(Product.part_number == "product_a")
).scalar()
sess.delete(p)
sess.commit()
"""SQL emitted:
DELETE FROM product_categories WHERE product_categories.products_id = %(products_id)s AND product_categories.categories_id = %(categories_id)s
[generated in 0.00074s] {'products_id': 1, 'categories_id': 1}
DELETE FROM products WHERE products.id = %(id)s
[generated in 0.00069s] {'id': 1}
COMMIT
"""

Related

SqlAlchemy error: Foreign key could not find table

So I'm building an app and I'm trying to save new changes to my database but when I try to commit the changes in the flask using db.session.commit() it returns me the following error:
sqlalchemy.exc.NoReferencedTableError: Foreign key associated with column 'products.country_id' could not find table 'countries' with which to generate a foreign key to target column 'id'
In my models.py I have the following:
from app import db
from . import db
from datetime import datetime
def now():
return datetime.now()
class Countries(db.Model):
__tablename__ = 'countries'
__table_args__ = {'schema': 'products_data'}
id = db.Column(db.Integer, primary_key=True, unique=True, autoincrement=True)
name = db.Column(db.String(255))
code = db.Column(db.String(45))
def __repr__(self):
return f'Id {self.id}'
class Categories(db.Model):
__tablename__ = 'categories'
__table_args__ = {'schema': 'products_data'}
id = db.Column(db.Integer, primary_key=True, unique=True, autoincrement=True)
name = db.Column(db.String(255))
def __repr__(self):
return f'Id {self.id}'
class Brands(db.Model):
__tablename__ = 'brands'
__table_args__ = {'schema': 'products_data'}
id = db.Column(db.Integer, primary_key=True, unique=True, autoincrement=True)
name = db.Column(db.String(255))
logo = db.Column(db.String(5000))
feed = db.Column(db.String(5000))
feed_type = db.Column(db.String(45))
category_id = db.Column(db.Integer, db.ForeignKey('categories.id'))
country_id = db.Column(db.Integer, db.ForeignKey('countries.id'))
awinmid = db.Column(db.Integer)
def __repr__(self):
return f'Id {self.id}'
class Products(db.Model):
__tablename__ = 'products'
__table_args__ = {'schema': 'products_data'}
id = db.Column(db.Integer, primary_key=True, unique=True, autoincrement=True)
name = db.Column(db.String(255))
url = db.Column(db.Text)
category_id = db.Column(db.Integer, db.ForeignKey('categories.id'))
country_id = db.Column(db.Integer, db.ForeignKey('countries.id'))
price = db.Column(db.Float)
currency = db.Column(db.String(45))
discount_price = db.Column(db.Float)
shipping = db.Column(db.Float)
brand_id = db.Column(db.Integer, db.ForeignKey('brands.id'))
Am I doing anything wrong when associating a column in products with a foreign key? This is the first time I encounter this error so I'm really lost on what to do right now.
To fix I just added the schema to the db.ForeignKey and it worked
Example:
db.ForeignKey('products_data.countries.id')
PS:
Not my idea. Just wanted to post the answer in case someone visits the post later with the same problem.
Gord Thompson thanks for the help!
First of all, I don t see any table Categories. Secondly, you copy pasted your schema from the Products table into your Countries one.
PS: By default sqlalchemy gives the tables the name of the class (lower cased). So your __tablename__='products' does nothing actually.
EDIT:
The problem with your code lies in how you set the __table_args__ attribute. You assign an object to it, which by their specifications is wrong.
Take a look at the following example and modify your code accordingly
__table_args__ = ({'schema': 'products_data'})
Also for further reference, take a look at this https://docs.sqlalchemy.org/en/13/orm/extensions/declarative/table_config.html

Implementation of One-to-One relationship in Flask SQLAlchemy

I am inexperienced programmer. I'd like to add one-to-one relationship CurrencyDefault (that value will be assigned to the field in FlaskForm) between User and Currency:
class User(UserMixin, db.Model):
id = db.Column(db.Integer, db.Sequence('user_id_seq'), primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(64), index=True, unique=True)
pswd_hash = db.Column(db.String(128))
expenses = db.relationship('Expense', backref='user')
currency = db.relationship('Currency', backref='user')
curr_default = ?
# ...
class Currency(db.Model):
id = db.Column(db.Integer, db.Sequence('expense_id_seq'), primary_key=True)
abbr = db.Column(db.String(10))
name = db.Column(db.String(64))
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
default = ?
# ...
What I want to achieve is to assign to each user.id one currency.id (one-to-one)
I'd like to ask for some advice what is the best way to make it.
After considering the problem I have some ideas like:
Association Table with uselist=False relationship,
Create a new class CurrencyDefault(id, user_id, currency_id),
Or maybe there is other, better way to achieve it?
I'm very curious of your point of view on this problem.
Implementing solution:
This is how my classes look like now:
class User(UserMixin, db.Model):
id = db.Column(db.Integer, db.Sequence('user_id_seq'), primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(64), index=True, unique=True)
pswd_hash = db.Column(db.String(128))
currency_default_choice = db.Column(db.Integer, b.ForeignKey('currency.id'))
expenses = db.relationship('Expense', backref='user')
currency = db.relationship('Currency', backref='user', foreign_keys="Currency.user_id")
# currency_default = db.relationship(
# 'Currency',
# foreign_keys='User.currency_default_choice',
# backref='currency_default',
# uselist=False,
# )
# ...
class Currency(db.Model):
id = db.Column(db.Integer, db.Sequence('expense_id_seq'), primary_key=True)
abbr = db.Column(db.String(10), b.ForeignKey('currency_official_abbr.abbr'))
name = db.Column(db.String(64))
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
currency_default = db.relationship(
'User',
foreign_keys='User.currency_default_choice',
backref='currency_default',
uselist=False,
)
The first problem I find is that I can set Currency.id object created by other_user as currency_default_choice. How to restrict currency_default_choice only to the <Currency> that was created by this user?
What is the difference between setting relationship having foreign key in User class (currency_default_choice = db.Column(db.Integer, b.ForeignKey('currency.id'))) with:
class Currency(db.Model):
# ...
currency_default = db.relationship(
'User',
foreign_keys='User.currency_default_choice',
backref='currency_default',
uselist=False,
)
and setting this relationship on User side with:
class User(db.Model):
# ...
currency_default = db.relationship(
'Currency',
foreign_keys='User.currency_default_choice',
backref='currency_default',
uselist=False,
)
Ad.2. What seems to me is that there is no difference between these two ways because the backref parameter implicates bidirectional behavior so it doesn't matter if I placed db.relationship() in User or Currency class. Is it correct?
Using Python shell I added value to the User.currency_default
>>> app = create_app()
>>> app.app_context().push()
>>> admin = User.query.filter_by(username='admin').first()
<User(id= 1, username = admin, email = admin#admin.com)
>>> currency = Currency.query.filter_by(user=admin)
>>> currency
<flask_sqlalchemy.BaseQuery object at 0x03EA05D0>
>>> currency[0].id
1
>>> admin.currency_default = currency[0]
>>> db.session.commit()
>>> currency[0].currency_default
<User(id= 1, username = admin, email = admin#admin.com)
>>> admin.currency_default_choice
1
and then using Admin Panel after running flask run I wanted to remove introduced value but I got error that I don't understand. Why there is circular dependency between (Currency.currency_default),(User.currency_default) and (User.currency)? I don't understand what is happening. How to fix it?
sqlalchemy.exc.CircularDependencyError: Circular dependency detected.
(ProcessState(OneToManyDP(Currency.currency_default),
<Currency at 0x46542b0>, delete=False),
ProcessState(ManyToOneDP(User.currency_default),
<User at 0x4669b10>, delete=False),
SaveUpdateState(<Currency at 0x46542b0>),
ProcessState(OneToManyDP(User.currency), <User at 0x4669b10>, delete=False),
SaveUpdateState(<User at 0x4669b10>))

Alembic Migration: How to remove polymorphic Identity

I have the following setup
class Content(Base):
"""Content object"""
__tablename__ = "content"
id = Column(Integer, primary_key=True)
name = Column(Unicode(255),unique=True, nullable=False)
title = Column(Unicode(255))
body = Column(UnicodeText)
created = Column(DateTime, default=func.now())
modified = Column(DateTime, onupdate=func.now())
type = Column(String(20))
__mapper_args__ = {
'polymorphic_on':type,
'polymorphic_identity':'content',
'with_polymorphic':'*'
}
class Locality(Content):
__tablename__ = "local"
id = Column(Integer, ForeignKey('content.id'),primary_key=True)
city_name = Column(Unicode(80))
__mapper_args__ = {'polymorphic_identity':'city'}
Now I dropped the Locality table using alembic.
Each time I query Content, I get
AssertionError: No such polymorphic_identity 'city' is defined
How do I drop this polymorphic_identity
I got over this by applying MySQL 'delete from' command on the content through MySQL Console, finding those contents whose type is 'city'
delete from content where content.type='city';

sqlalchemy.orm.exc.UnmappedInstanceError

I keep getting this error whenever i try deleting the grand-child item in sqlalchemy in pyramid application
UnmappedInstanceError: Class 'sqlalchemy.ext.declarative.api.DeclarativeMeta' is not mapped; was a class (beatstore.models.Song) supplied where an instance was required?
Here is my Delete code in both views and models
Models
class Song(Base):
__tablename__ = 'songs'
id = Column('song_id', Integer, primary_key=True)
# foreing key
# nullable = false, the song must have an artist
artist_id = Column(Integer, ForeignKey('artists.artist_id', onupdate='CASCADE', ondelete='CASCADE'), nullable=False)
artist = relationship("Artist")
# foreing key
album_id = Column(Integer, ForeignKey('albums.album_id', onupdate='CASCADE', ondelete='CASCADE'), nullable=False)
album = relationship("Album")
# foreing key
genre_id = Column(Integer, ForeignKey('genres.genre_id', onupdate='CASCADE', ondelete='CASCADE' ))
genre = relationship("Genre")
picture_path = image_attachment('PictureSong')
title = Column(Unicode(100), nullable=False)
download_link = Column(Unicode(100), nullable=False)
artist = Column(Unicode(100), nullable=False)
duration = Column(Unicode(50))
Price = Column(Unicode(50))
created = Column(DateTime, default=datetime.now , nullable=False)
Views
#view_config(route_name="song_delete")
def song_delete(song, request):
"""song delete """
id = request.matchdict['id']
dbsession = DBSession()
song = dbsession.query(Song).filter_by(id = id).first()
if song is None:
request.session.flash("error;Song not found!")
return HTTPFound(location=request.route_url("media"))
try:
transaction.begin()
dbsession.delete(Song);
transaction.commit()
request.session.flash("warning;The song is deleted!")
except IntegrityError:
# delete error
transaction.abort()
request.session.flash("error;The song could not be deleted!")
return HTTPFound(location=request.route_url("media"))
You are trying to delete the class name:
dbsession.delete(Song)
instead of the object:
dbsession.delete(song) (note lowercase s)

sqlalchemy - how to convert query with subquery into relationship

In the code below I want to replace all_holdings in Account with a property called holdings that returns the desired_holdings (which are the holdings representing the latest known quantity which can change over time). I'm having trouble figuring out how to construct the call to relationship.
In addition I'd appreciate any comments on the appropriateness of the pattern (keeping historic data in a single table and using a max date subquery to get most recent), as well as on better alternatives, or improvements to the query.
from sqlalchemy import Column, Integer, String, Date, DateTime, REAL, ForeignKey, func
from sqlalchemy.orm import relationship, aliased
from sqlalchemy.sql.operators import and_, eq
from sqlalchemy.ext.declarative import declarative_base
from db import session
import datetime
import string
Base = declarative_base()
class MySQLSettings(object):
__table_args__ = {'mysql_engine':'InnoDB'}
class Account(MySQLSettings, Base):
__tablename__ = 'account'
id = Column(Integer, primary_key=True)
name = Column(String(64))
all_holdings = relationship('Holding', backref='account')
def desired_holdings(self):
max_date_subq = session.query(Holding.account_id.label('account_id'),
Holding.stock_id.label('stock_id'),
func.max(Holding.as_of).label('max_as_of')). \
group_by(Holding.account_id, Holding.stock_id).subquery()
desired_query = session.query(Holding).join(Account,
Account.id==account.id).join(max_date_subq).\
filter(max_date_subq.c.account_id==account.id).\
filter(Holding.as_of==max_date_subq.c.max_as_of).\
filter(Holding.account_id==max_date_subq.c.account_id).\
filter(Holding.stock_id==max_date_subq.c.stock_id)
return desired_query.all()
def __init__(self, name):
self.name = name
class Stock(MySQLSettings, Base):
__tablename__ = 'stock'
id = Column(Integer, primary_key=True)
name = Column(String(64))
def __init__(self, name):
self.name = name
class Holding(MySQLSettings, Base):
__tablename__ = 'holding'
id = Column(Integer, primary_key=True)
account_id = Column(Integer, ForeignKey('account.id'), nullable=False)
stock_id = Column(Integer, ForeignKey('stock.id'), nullable=False)
quantity = Column(REAL)
as_of = Column(Date)
stock = relationship('Stock')
def __str__(self):
return "Holding(%f, '%s' '%s')"%(self.quantity, self.stock.name, str(self.as_of))
def __init__(self, account, stock, quantity, as_of):
self.account_id = account.id
self.stock_id = stock.id
self.quantity = quantity
self.as_of = as_of
if __name__ == "__main__":
ibm = Stock('ibm')
session.add(ibm)
account = Account('a')
session.add(account)
session.flush()
session.add_all([ Holding(account, ibm, 100, datetime.date(2001, 1, 1)),
Holding(account, ibm, 200, datetime.date(2001, 1, 3)),
Holding(account, ibm, 300, datetime.date(2001, 1, 5)) ])
session.commit()
print "All holdings by relation:\n\t", \
string.join([ str(h) for h in account.all_holdings ], "\n\t")
print "Desired holdings query:\n\t", \
string.join([ str(h) for h in account.desired_holdings() ], "\n\t")
The results when run are:
All holdings by relation:
Holding(100.000000, 'ibm' '2001-01-01')
Holding(200.000000, 'ibm' '2001-01-03')
Holding(300.000000, 'ibm' '2001-01-05')
Desired holdings query:
Holding(300.000000, 'ibm' '2001-01-05')
Following answer provided by Michael Bayer after I posted to sqlalchemy google group:
The desired_holdings() query is pretty complicated and I'm not seeing a win by trying to get relationship() to do it. relationship() is oriented towards maintaining the persistence between two classes, not as much a reporting technique (and anything with max()/group_by in it is referring to reporting).
I would stick #property on top of desired_holdings, use object_session(self) to get at "session", and be done.
See more information on query-enabled properties.