Deletion of a row from an association table - python-3.x

I am working on an app using python3 and SqlAlchemy for SQLite3 database management. I have some tables that have a Many to Many relationship. I've created an association table to handle this relationship.
Class Machine(Base):
__tablename__ 'machine'
machine_ID = Column(Integer, primary_key=True)
etc...
Class Options(Base):
__tableName__ 'options'
options_ID = Column(Integer, primary_key=True)
etc...
The association table
Machine_Options = table('machine_options', Base.metadata,
Column('machine_FK', Integer, ForeignKey('machine.machine_ID'),
primary_key=True),
Column('options_FK',Integer, ForeignKey('options.options_ID'),
primary_key=True))
All the items for the Machine and Options are inserted independently. When I want to associate a machine with an option I use an append query which works very well.
My problem is when I want to break this association between a machine and an option. I have tried a direct row deletion from the association table using a FILTER() clause on the machine_FK and the options_FK but SqlAlchemy gives me an error informing me that 'Machine_Options' table has no field 'machine_FK'.
I have tried to remove the row from 'Machine_Options' indirectly using joins with the machine and options table but received another error that I can not delete or update using joins.
I am looking for the code to only delete a row from the association table without affecting the original machine or options table.
So far my internet search has been fruitless.

The answer to my problem is to use myparent.children.remove(somechild)
The association is made using machine.children.append(option)
Using the same code as the 'append' and substituting 'remove' unmakes the association
The code:
def removeOption(machineKey, OptionKey):
session = connectToDatabase()
machineData = session.query(Machine).filter(Machine.machine_ID == machineKey).one()
optionData = session.query(Options).filter(Options. options_ID == OptionKey).one()
machineData.children.remove(optionData)
session.add(machineData)
session.commit()
session.close()

Related

Delete multiple rows from Association Table in SQLAlchemy using db.session.execute syntax?

I have an association table that contains relationships between two other SQLAlchemy models that I would like to delete:
class ItemCategories(db.Model):
id = Column(Integer, primary_key=True)
item_id = Column(Integer, ForeignKey("item.id"))
category_id = Column(Integer, ForeignKey("category.id"))
# ... other fields
The old syntax was to use something like:
db.session.query(ItemCategories).filter_by(category_id=5).filter(ItemCategories.name="Shelved").delete()
But with the newer syntax, I tried:
db.session.execute(db.select(ItemCategories).filter_by(category_id=5).filter(ItemCategories.name="Shelved").delete())
But this errored with:
AttributeError: 'Select' object has no attribute 'delete'
Flask-SQLAlchemy suggests doing:
db.session.delete(Model Object)
But this only deletes a single row, and I would like to delete multiple rows at once. I know I can loop through all the rows and do a session delete one-by-one, but would prefer a bulk delete instead like with the session.query line.
Is there a way to do multiple deletes with db.session.execute()?

SQLAlchemy #event.listens_for(Class, 'after_insert') for SQL Expression "INSERT INTO table(cols) VALUES (values)"

In my application data will be flowing to postgres database from different system (from hive using scoop). I want to run some code automatically when it is inserted to one of the tables (I created it with sqlaLchemy ORM - I know that after_insert hook doesn't work for sqlAlchemy Core). And for my purpose I can't use postgres trigger.
This is kind of my data model:
from sqlalchemy.orm import declarative_base, Session
from sqlalchemy import Column, Integer, String, event, DDL
Base = declarative_base()
class MainClass(Base):
__tablename__ = 'first_table'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
#event.listens_for(MainClass, 'after_insert')
def after_insert(Mapper, connection, target):
connection.execute(DDL(f'''INSERT INTO second_table(name, value) VALUES ('OK!', '{target.name}')'''))
class SecondClass(Base):
__tablename__ = 'second_table'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
value = Column(String, nullable=False)
Base.metadata.create_all(engine, checkfirst=True)
What is not working:
engine.execute('''INSERT INTO first_table(name) VALUES ('test1')''')
What is working (but I can't use such insert in my case):
insert = MainClass(name='test2')
with Session(bind=engine) as session:
session.add(insert)
session.commit()
Documentations says:
method sqlalchemy.orm.MapperEvents.after_insert(mapper, connection, target)
Receive an object instance after an INSERT statement is emitted corresponding to that instance.
Example argument forms:
from sqlalchemy import event
#event.listens_for(SomeClass, 'after_insert')
def receive_after_insert(mapper, connection, target):
"listen for the 'after_insert' event"
This event is used to modify in-Python-only state on the instance after an INSERT occurs, as well as to emit additional SQL statements on the given connection.
The event is often called for a batch of objects of the same class after their INSERT statements have been emitted at once in a previous step. In the extremely rare case that this is not desirable, the mapper() can be configured with batch=False, which will cause batches of instances to be broken up into individual (and more poorly performing) event->persist->event steps.
If this is something that sqlAlchemy ORM events doesn't support. Can you recommend me some workaround?

Multiple foreign keys referencing same table column in SQLAlchemy

I am creating a rating system. Rating is a table which contains individual ratings as rows. Each rating has a "rater" and a "ratee". These two columns reference a different table, "User", by means of a foreign key. However, they both reference the same user.id column. Code:
class Rating(db.Model):
id = db.Column(db.Integer, primary_key=True)
rater_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
ratee_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
And here is how they are represented from within the User class (table):
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(32), unique=True, nullable=False)
ratesOfOthers = db.relationship('Rating', backref='rater', lazy=True)
ratingsByOthers = db.relationship('Rating', backref='ratee', lazy=True)
Now, when I try to use this relationship, I am presented with the following error:
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship User.ratesOfOthers - there are multiple foreign key paths linking the tables. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference to the parent table.
I have tried using the foreign_keys argument from inside the User class, but that did nothing. Any help would be appreciated.
The assumption is that in your scenario a user can rate other users, and they (themselves) can be rated. Basically, there is one table called User referencing other users within an application.
A relationship in which instances of a class are linked to other instances of the same class is called self-referential relationship, and that is exactly what you have here.
Here is a diagram that represents this self-referential many-to-many relationship that keeps track of ratings:
The Ratings table is the association table of the relationship. The foreign keys in this table are pointing at entries in the User table since it is linking users to users.
To add this table to your database, this is how you can go about it:
ratings = db.Table('ratings'
db.Column('my_ratings_id', db.Integer, db.ForeignKey('user.id'))
db.Column('other_people_rating_id', db.Integet, db.ForeignKey('user.id'))
)
This is an auxiliary table (directly-translated as seen above) that has no data other than foreign keys. It is, therefore, created without an associated model class.
To declare the many-to-many relationship in the User table, add this:
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(32), unique=True, nullable=False)
def __repr__(self):
return f'{self.username}'
my_ratings = db.relationship(
'User',
secondary=ratings,
primaryjoin=(ratings.c.my_ratings_id == id),
secondaryjoin=(ratings.c.other_people_rating_id == id),
backref = db.backref('other_people_rating', lazy='dynamic'), lazy='dynamic'
)
I am defining the relationship as seen from the left side user with the name my_ratings because when you query this relationship from the left side, you will get a list of all those on the right side. Visually, this is what I mean:
Examining all the arguments of the db.relationship() call, you will see that:
User is the right side entity of the relationship.
secondary configures the ratings association table
primaryjoin indicates the condition that links the left side entity with the association table. The user id should match my_ratings_id
secondaryjoin indicates the condition that links the right side entity with the association table. Again, other_people_rating_id should match the user id
backref defines how this relationship will be accessed from the right side entity. From the left side, the relationship is named my_ratings, so from the right side, I decided to name it other_people_rating to represent all the left side users that are linked to the target user in the right side.
The dynamic mode is used to set up the query to not run until specifically requested.
The second lazy parameter applies to the left side query instead of the right side.

SQLAlchemy ForeignKey with dynamic PostgreSQL Schema

How might one use SqlAlchemy dynamic schema translation with foreign key relationships using the declarative ORM? Below is an example, of what I am trying to do for a multi-tenant DB (each 'user' mapping to a schema). I am not sure how one goes about setting ForeignKey constraints in SQLAlchemy
Assuming the following table declarations:
class BaseMixin():
__table_args__ = {'schema':'dynamic'}
class Image(Base, BaseMixin):
__tablename__ == 'image'
id = Column(Integer, primary_key=True, autoincrement=True)
class Point(Base, BaseMixin):
__tablename__ == 'point'
fk = Column(Integer, ForeignKey("image.id", ondelete="CASCADE"))
The Point table has a FK relationship to the Image table. I am getting the following error when I attempt to query these tables:
NoReferencedTableError: Foreign key associated with column 'point.fk' could not find table 'image' with which to generate a foreign key to target column 'id'
The session is parametrized with the following:
session.connection(execution_options={
"schema_translate_map": {"dynamic": "my_custom_schema"}})
From the docs, it looks like the schema_translate_map update on the session should be replacing dynamic with the user's schema. Oddly, the table instantiation is happening as expected. The error is popping up when attempting to query.
You may need to specify the schema in the ForeignKey i.e. update this:
fk = Column(Integer, ForeignKey("image.id", ondelete="CASCADE"))
to:
fk = Column(Integer, ForeignKey("dynamic.image.id", ondelete="CASCADE"))

Change SQLAlchemy __tablename__

I am using SQLAlchemy to handle requests from an API endpoint; my database tables (I have hundreds) are differentiated via a unique string (e.g. test_table_123)...
In the code below, __tablename__ is static. If possible, I would like that to change based on the specific table I would like to retrieve, as it would be tedious to write several hundred unique classes.
from config import db, ma # SQLAlchemy is init'd and tied to Flask in this config module
class specific_table(db.Model):
__tablename__ = 'test_table_123'
var1 = db.Column(db.Integer, primary_key=True)
var2 = db.Column(db.String, index=True)
var3 = db.Column(db.String)
class whole_table_schema(ma.ModelSchema):
class Meta:
model = specific_table
sqla_session = db.session
def single_table(table_name):
# collect the data from the unique table
my_data = specific_table().query.order_by(specific_table.level_0).all()
Thank you very much for your time in advance.
You can use reflect feature of SQLAlchemy.
engine = db.engine
metadata = MetaData()
metadata.reflect(bind=engine)
and finally
db.session.query(metadata.tables[table_name])
If you want smoother experience with querying, as previous solution cannot offer one, you might declare and map your tables: tables = {table_name: create_table(table_name) for table_name in table_names}, where create_table constructs models with different __tablename__. Instead of creating all tables at once, you can create them on demand.

Resources