Python SQLAlchemy after_insert MapperEvent - python

So I am new to SQLAlchemy, and I would like after a new User object is created that a Permissions object is created by default. Reading the documentation it seems that after_insert mapper event is what I should be using. The problem is that the event listener is called before the object is actually committed.
Is there a way to have the Permissions object at least put in possibly a transaction or on the list of objects to be created after the user object is actually committed.
class Users():
__tablename__ = 'users'
user_id = Column(String(36), primary_key=True, nullable=False)
.......
class Permissions():
__tablename__ = 'permissions'
user_id = Column(String(36), ForeignKey('users.user_id'), primary_key=True,
nullable=False)
..........
#event.listens_for(Users, "after_insert)
def create_permissions(mapper, connection, user):
connection.execute(Permissions.__table__.insert(user_id=user.user_id))
IntegrityError: (IntegrityError) (1452, 'Cannot add or update a child row: a foreign key constraint fails
Ideally I would like to do this without a Database Trigger on the Users table.

You do not need to attach session to ORM object. Just use object_session function from sqlalchemy.orm module like following:
from sqlalchemy.orm import object_session
#event.listens_for(Users, 'after_insert')
def create_permissions(mapper, connection, user):
object_session(user).add(Permissions(user_id=user.user_id))

So by changing my class defintions around, and attaching the session object to each class I am able to add a new Permissions object at time after_insert is signaled. And the proper sequences of commits take place.
class Users():
__tablename__ = 'users'
user_id = Column(String(36), primary_key=True, nullable=False)
............
class Permissions():
__tablename__ = 'permissions'
user_id = Column(String(36), ForeignKey('users.user_id'), primary_key=True,
nullable=False)
............
#event.listens_for(Users, "after_insert")
def create_permissions(mapper, connection, user):
user.session.add(Permissions(user_id=user.user_id))

Related

Exception related to async Sqlalchemy

I wrote my sqlalchemy models, so i needed to retrieve related objects when getting parent.
User model
class User(AbstractBaseModel):
__tablename__ = "users"
id = Column(Integer, primary_key=True, autoincrement=True)
username = Column(String, unique=True, index=True, nullable=False)
hashed_password = Column(String, nullable=False)
is_active = Column(Boolean, default=True)
tests = relationship("Test", backref=backref("holder", lazy="subquery"))
sessions = relationship("Session")
Test model
class Test(AbstractBaseModel):
__tablename__ = "tests"
title = Column(String(256))
holder_id = Column(Integer, ForeignKey("users.id"))
published = Column(Boolean)
questions = relationship("Question")
sessions = relationship("Session")
So when i am trying to retreive holder object i am getting an sqlalchemy.orm.exc.DetachedInstanceError:
Parent instance <Test at 0x7f5e391522c0> is not bound to a Session; lazy load operation of attribute 'holder' cannot proceed (Background on this error at: https://sqlalche.me/e/14/bhk3)
it raises after i am using this code:
async def create(self, obj):
await self._before_create()
self._database_session.add(obj)
await self._database_session.commit()
await self._after_create()
I tried all types of lazyloading, and tried to add separate holder attr with relationship in tests but it did not help. If someone knows how to get related objects in async sqlalchemy please let me know as soon as possible.
So it is debugger screenshot:

Pre-populate a Flask SQLAlchemy database

I have two models, one is Identification which contains two IDs (first_id and second_id) and the second is User. The idea is that only authorised users will be given their first_id and second_id pair of values. They go to the site and login by entering the two id's plus a username and password (which they generate there and then).
I am trying to achieve two things here:
Pre-populate the Identification table with many (let's say 100) first_id/second_id values that will serve as the correct value pairs for logging in.
Set up the User class in such a way that only if the user enters a correct first_id/second_id pair in the login form can they log in (presumable this involves checking the form data with the Identification table somehow).
Here are the model classes:
class Identification(db.Model):
id = db.Column(db.Integer, primary_key=True)
first_id= db.Column(db.Text, unique=True)
second_id= db.Column(db.Text, unique=True)
def __init__(self, first_id, second_id):
self.first_id= first_id
self.second_id= second_id
def __repr__(self):
return f"ID: {self.id}, first_id: {self.first_id}, second_id: {self.second_id}"
class User(db.Model, UserMixin):
__tablename__ = 'user'
first_id= db.relationship('Identification', backref = 'identificationFID', uselist=False)
second_id = db.relationship('Identification', backref = 'identificationSID', uselist=False)
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.Text, unique=True, index=True)
password_hash = db.Column(db.Text(128))
identification_id = db.Column(db.Integer, db.ForeignKey('identification.id'), unique=True)
first_id = db.Column(db.Text, unique=True)
second_id = db.Column(db.Text, unique=True)
I would appreciate any help on this as I'm struggling and this is really above my understanding of python/Flask. Thanks all!
The answer above didn't work for me, because the create_tables() function since being part of the User class, requested that I pass an Instance of that class.
The solution I came up with, was to call the function after db.create_all(). This seemed like a good place to put the call, because of the #app.before_first_request decorator.
init.py
#app.before_first_request
def create_tables():
"""Create Tables and populate certain ones"""
db.create_all()
from app.models.init_defaults import init_defaults
init_defaults()
init_defaults.py
def init_defaults():
"""Pre-Populate Role Table"""
if Role.query.first() is None:
roles = ['Admin', 'Master', 'Apprentice']
for role in roles:
user_role = Role(access_level=role)
if role != 'Apprentice':
user_role.set_password('Passw0rd!')
db.session.add(user_role)
db.session.commit()
pass
Due to the decorator the function is now only called once per instance. Another solution I could imagine working, would be to use events:
https://dzone.com/articles/how-to-initialize-database-with-default-values-in
Note: This is a development solution not fit for production.
You can use mock data to populate these tables.
create a function in this py file where you can add objects to DB using ORM
and then call the function in __init__.py, which will populate data once your flask server starts.
Update:-
here is a code for your reference.
Model.py
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import sessionmaker
Base = declarative_base()
engine = create_engine('sqlite:///:memory:', echo=True)
Session = sessionmaker(bind=engine)
session = Session()
class User(Base):
\__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String)
fullname = Column(String)
#Create getter setters
def create_tables():
Base.metadata.create_all(engine)
user = User()
user.id=1
user.name="ABC"
user.fullname="ABCDEF"
session.add(user)
# similarly create more user objects with mock data and add it using session
__init__.py
from model import User
User.create_tables()
Reference

What could be the cause of "Dependency rule tried to blank-out primary key column"

When im trying to delete category instance identified by 'id' with its category_image and files instances the way like this:
c = Category.query.get(id)
for ci in c.images:
db.session.delete(ci)
db.session.flush()
for ci in c.images:
db.session.delete(ci.file)
db.session.flush() # if i type here db.session.commit() all is fine
db.session.delete(c)
db.session.commit()
i'm getting a AssertionError: Dependency rule tried to blank-out primary key column 'category_image.id_category' on instance ''. But when i replace flush which is after deleting category_image.files with commit, then it works. I've notice it after i changed CategoryImage table to intermediary. Before changes it has it's own pk that wasn't combined and all was working properly. Here're my current models definitions.
class File(db.Model):
__tablename__ = 'file'
id_file = Column(Integer, Sequence('seq_id_file'), primary_key=True, nullable=False)
name = Column(Text, nullable=False)
path = Column(Text, nullable=False, unique=True)
protected = Column(Boolean, nullable=False, default=False)
class Category(db.Model):
__tablename__ = 'category'
id_category = Column(Integer, Sequence('seq_id_category'), primary_key=True, nullable=False)
name = Column(UnicodeText, nullable=False, unique=True)
images = relationship('CategoryImage', backref='images')
class CategoryImage(db.Model):
__tablename__ = 'category_image'
__table_args__ = (
PrimaryKeyConstraint('id_category', 'id_file', name='seq_id_category_image'),
)
id_category = Column(Integer, ForeignKey(Category.id_category), nullable=False)
id_file = Column(Integer, ForeignKey(File.id_file), nullable=False)
id_size_type = Column(Integer, nullable=)
file = relationship(File)
Now i'm trying to figure out what just happened. Correct me if i'm using things wrong.
I just noticed that i have to delete objects beeing in relation with intermediate model in the same order as it was declared in table_args, PrimaryKeyConstraint('id_category', 'id_file'). So when i perform it this way: session.delete(category_image), session.delete(category), session.delete(file) and commit it or flush everywhere before commit, then all works fine. If anyone spot something about it in alch docs let me know.
Here is what is happening. Once you call session.delete() on some object it is like having marked the object for deletion but not yet deleted from db. when you call the flush() after deleting (note: db still has the object as it is yet not committed) but session has marked the object as deleted. So the objects become inconsistent. In order to make the delete smooth you can always wrap your delete operations within a transaction and once they are deleted from the session you need to call the db.commit() once to make db session consistent with the db.
Hope it helps.

How to set hash of a column in another column in flask-admin?

I want to save hash of name to hash_name column Also I use Flask-Admin to manage my data.
class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.Unicode, unique=True, nullable=False)
hash_name = db.Column(db.Unicode, unique=True)
admin.add_view(ModelView(User, db.session))
Also I set default with uuid package for hash_name but this page in result had a problem .my uuid never changed . I refreshed but not changed
If you only use flask-admin's SQLAlchemy ModelViews for editing, then it's possible to do following:
class UserView(sqla.ModelView):
# Hide `hash_name` in list and form views
column_exclude_list = ('hash_name',)
form_excluded_columns = ('hash_name',)
# Generate new hash on `name` change
def on_model_change(self, form, model, is_created):
if len(model.name):
model.hash_name = generate_hash_name(model.name)
Otherwise use #mehdy's event approach.
I think you can use sqlalchemy's even listeners to manipulate your object before committing it to the database:
from sqlalchemy import event
...
#event.listens_for(User, "before_commit")
def gen_default(mapper, connection, instance):
instance.hash_name = hash_function(instance.name)
so before each commit it will be invoked and updates the hash_name attribute with the proper hash on name

SQLAlchemy Object is already attached to session, then DetachedInstanceError on refresh, still adds to database

I'm having some trouble with making a Many-to-Many relationship in Flask using SQLAlchemy. I have my two models, and the relationship table. When I create a Group object, I want that group to have the user who creates the group as a member, and the user should have the group being created as a group.
However, when I submit the form to create the group, I get a InvalidRequestError, saying Object '<User at 0x7f85ad606a50>' is already attached to session '1' (this is '2')
Then, if I refresh the page, resubmitting the form, it successfully creates the group and database relationship. However, it shows a DetachedInstanceError, saying that Parent instance <User at 0x7f85ad606a50> is not bound to a Session; lazy load operation of attribute 'groups' cannot proceed. That error shows until I restart the server.
Relevant code:
db:
db = SQLAlchemy(app)
Models:
groups = db.Table('user_groups',
db.Column('group_id', db.Integer, db.ForeignKey('groups.id')),
db.Column('user_id', db.Integer, db.ForeignKey('users.id'))
)
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
groups = db.relationship('Group', secondary=groups,
backref=db.backref('members', lazy='dynamic'))
class Group(db.Model):
__tablename__ = 'groups'
id = db.Column(db.Integer, primary_key=True)
Attempting to add the group to the database:
g = Group()
db.session.add(g)
u = User.query.filter_by(googleID=session.get('id')).first()
g.members.append(u)
db.session.commit()
Any help is appreciated!
So I fixed it... The issue was that I was importing the database variable (db) into my model file. Apparently that's a no-no. So I just copy/pasted it into the main file.

Categories

Resources