SQLAlchemy with single table inheritance expunge error - python

I'm having an issue with python SQLAlchemy single table inheritance.
Model:
class User(Base):
__tablename__ = 'user'
userID = Column(String(64), primary_key=True)
name = Column(String(64))
type = Column('type', String(50))
__mapper_args__ = {'polymorphic_on': type}
class PasswordUser(User):
__mapper_args__ = {'polymorphic_identity': 'puser'}
password = Column(String(64))
def validatePassword(self, password):
return (self.password == password)
In userManger.py I have this:
def userGet(userID):
with DBStore.Session(db) as sess:
user = sess.query(User).filter(User.userID==userID).one()
sess.expunge(user)
return user
In a test main method:
myUser = userManager.userGet('123')
myUser.validatePassword("password321')
This produces an error:
sqlalchemy.orm.exc.DetachedInstanceError: Instance is not bound to a Session; attribute refresh operation cannot proceed
I have verified that myUser is of type 'PasswordUser', and it calls the correct 'validatePassword' method.
The stranger thing is that when I step through the code slowly (PyDev), it works without error.
It also works if my userGet method does a sess.query(PasswordUser). But I want this method to be generic so it can return any type of 'User'.
Any ideas?

The problem is that when you query just for User, only the attributes/columns for the User (base) class are queried from the database. In order to load also other columns (like password) for sub-classes, you need to instruct query to do so. You can do this by using with_polymorphic, in which case your code might look like:
def userGet(userID):
with DBStore.Session(db) as sess:
user = sess.query(User).with_polymorphic('*').filter(User.userID==userID).one()
sess.expunge(user)
return user
If you do not do that, the sqlalchemy will try to load the missing attribute (in your case, password) automatically using the session, and this is why it complains that it cannot work with detached object.

Related

flask-login using UserMixin - no 'id' attribute error despite being implemented

I have been trying to use flask-login for authentication in my flask app. While trying to implement the user_loader like this:
#login_manager.user_loader
def load_user(id):
return User.get_id(id)
i get the error
NotImplementedError: No id attribute - override get_id
My User class is defined like this:
class User(Base, UserMixin):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
email = Column(String(70), unique=True)
userName = Column(String(40), unique=True)
password = Column(String(150))
#def get_id(self):
# return (self.id)
i tried overriding the get_id method, as you can see in the User class. It just got me the error
AttributeError: 'str' object has no attribute 'id'
What am i missing? Why is the id attribute not found?
You are correctly implementing a getter for the id variable in your override, but I think that you are misinterpreting how to use the function in the code to load a user by id. In your overridden function, get_id takes in no parameters besides self, because the function simply returns the id of a user. However you are trying to use this as a function to load a user from their id. The reason we are getting this specific error is because by calling User.get_id(id), you are calling get_id with self=id and since id is a str, we get the AttributeError from trying to get the id property of id, which doesn't exist.
Try using get instead of get_id (as shown in this tutorial).
This way the returned value is an instance of User with the desired id
#login_manager.user_loader
def load_user(id):
return User.query.get(int(id))

Implement Login system in python using flask_login with different user types

I am trying to create a college system using flask but I have a problem with log in system basically I have two classes Teacher class and Student class the problem is that I cant figure a way to make the login manager handle the two classes login at the same time only one can log in and when the other class try to log in it log me as the class what I mean is when I setup the login manager to accept student it works with the student but when I try to login as a teacher it searches in the student table instead of teacher table I don't know if I explained it right but i hope code snippet help
this is models
attend = db.Table('attend',
db.Column('teacher_id',db.Integer,db.ForeignKey('teacher.id')),
db.Column('student_id', db.Integer, db.ForeignKey('student.id'))
)
class Teacher(db.Model,UserMixin):
id = db.Column(db.Integer, primary_key=True)
name=db.Column(db.String(60),nullable=False)
email=db.Column(db.String(100),nullable=False,unique=True)
password=db.Column(db.String(60),nullable=False)
department=db.Column(db.String(50),nullable=False)
students=db.relationship('Student',secondary=attend,backref='students',lazy='dynamic')
def type_auth(self):
return True
def __repr__(self) -> str:
return f'name:{self.name} email:{self.email} department:{self.department}'
class Student(db.Model,UserMixin):
id = db.Column(db.Integer, primary_key=True)
name=db.Column(db.String(60),nullable=False)
email = db.Column(db.String(100), nullable=False, unique=True)
password = db.Column(db.String(60), nullable=False)
year=db.Column(db.Integer,nullable=False)
grades = db.relationship('Grades', backref='Rates', lazy='dynamic')
def type_auth(self):
return False
def __repr__(self) -> str:
return f'name:{self.name} email:{self.email} year:{self.year}'
this login manager
def create_app():
app=Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI']=f'sqlite:///{DB_NAME}'
app.config['SECERT_KEY'] = '1853d2c8983cff3b'
app.config['FLASK_ADMIN_SWITCH']='Cyborg'
db.init_app(app)
from .auth import auth
from .routes import routes
app.register_blueprint(auth,prefix_url='/')
app.register_blueprint(routes,prefix_url='/')
login_manger=LoginManager()
login_manger.login_view='auth.login'
login_manger.login_message_category='info'
login_manger.init_app(app)
#login_manger.user_loader
def user_loader(id):
#i need help here
return app
The user_loader callback received an id as an argument and from that information is expected to return an instance of whatever you have defined as your "User" class. You have effectively defined two "User" classes, and in the current configuration there is no way for the user loader, if passed "1", to determine whether it should return the Teacher with id 1 or the Student with id 1.
A better design might be to move email and password into a new User class, and create one-to-one relationships between User and Student and Teacher. You can add some validation to ensure that a User cannot relate to both a Student and a Teacher.
You can do this:
When a user logs in, search in both the teacher and student database and then log them in. While logging, create a session variable say session['teacher'] = true, to identify the role when the person has logged in.
Or
You may also try giving an identifier for the user to choose. For example: I am a: Teacher(chekcbox) Student(checkbox). Depending on the check selected, look into that db.

Flask + SQLAlchemy - custom metaclass to modify column setters (dynamic hybrid_property)

I have an existing, working Flask app that uses SQLAlchemy. Several of the models/tables in this app have columns that store raw HTML, and I'd like to inject a function on a column's setter so that the incoming raw html gets 'cleansed'. I want to do this in the model so I don't have to sprinkle "clean this data" all through the form or route code.
I can currently already do this like so:
from application import db, clean_the_data
from sqlalchemy.ext.hybrid import hybrid_property
class Example(db.Model):
__tablename__ = 'example'
normal_column = db.Column(db.Integer,
primary_key=True,
autoincrement=True)
_html_column = db.Column('html_column', db.Text,
nullable=False)
#hybrid_property
def html_column(self):
return self._html_column
#html_column.setter
def html_column(self, value):
self._html_column = clean_the_data(value)
This works like a charm - except for the model definition the _html_column name is never seen, the cleaner function is called, and the cleaned data is used. Hooray.
I could of course stop there and just eat the ugly handling of the columns, but why do that when you can mess with metaclasses?
Note: the following all assumes that 'application' is the main Flask module, and that it contains two children: 'db' - the SQLAlchemy handle and 'clean_the_data', the function to clean up the incoming HTML.
So, I went about trying to make a new base Model class that spotted a column that needs cleaning when the class is being created, and juggled things around automatically, so that instead of the above code, you could do something like this:
from application import db
class Example(db.Model):
__tablename__ = 'example'
__html_columns__ = ['html_column'] # Our oh-so-subtle hint
normal_column = db.Column(db.Integer,
primary_key=True,
autoincrement=True)
html_column = db.Column(db.Text,
nullable=False)
Of course, the combination of trickery with metaclasses going on behind the scenes with SQLAlchemy and Flask made this less than straight-forward (and is also why the nearly matching question "Custom metaclass to create hybrid properties in SQLAlchemy" doesn't quite help - Flask gets in the way too). I've almost gotten there with the following in application/models/__init__.py:
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.hybrid import hybrid_property
# Yes, I'm importing _X stuff...I tried other ways to avoid this
# but to no avail
from flask_sqlalchemy import (Model as BaseModel,
_BoundDeclarativeMeta,
_QueryProperty)
from application import db, clean_the_data
class _HTMLBoundDeclarativeMeta(_BoundDeclarativeMeta):
def __new__(cls, name, bases, d):
# Move any fields named in __html_columns__ to a
# _field/field pair with a hybrid_property
if '__html_columns__' in d:
for field in d['__html_columns__']:
if field not in d:
continue
hidden = '_' + field
fget = lambda self: getattr(self, hidden)
fset = lambda self, value: setattr(self, hidden,
clean_the_data(value))
d[hidden] = d[field] # clobber...
d[hidden].name = field # So we don't have to explicitly
# name the column. Should probably
# force a quote on the name too
d[field] = hybrid_property(fget, fset)
del d['__html_columns__'] # Not needed any more
return _BoundDeclarativeMeta.__new__(cls, name, bases, d)
# The following copied from how flask_sqlalchemy creates it's Model
Model = declarative_base(cls=BaseModel, name='Model',
metaclass=_HTMLBoundDeclarativeMeta)
Model.query = _QueryProperty(db)
# Need to replace the original Model in flask_sqlalchemy, otherwise it
# uses the old one, while you use the new one, and tables aren't
# shared between them
db.Model = Model
Once that's set, your model class can look like:
from application import db
from application.models import Model
class Example(Model): # Or db.Model really, since it's been replaced
__tablename__ = 'example'
__html_columns__ = ['html_column'] # Our oh-so-subtle hint
normal_column = db.Column(db.Integer,
primary_key=True,
autoincrement=True)
html_column = db.Column(db.Text,
nullable=False)
This almost works, in that there's no errors, data is read and saved correctly, etc. Except the setter for the hybrid_property is never called. The getter is (I've confirmed with print statements in both), but the setter is ignored totally and the cleaner function is thus never called. The data is set though - changes are made quite happily with the un-cleaned data.
Obviously I've not quite completely emulated the static version of the code in my dynamic version, but I honestly have no idea where the issue is. As far as I can see, the hybrid_property should be registering the setter just like it has the getter, but it's just not. In the static version, the setter is registered and used just fine.
Any ideas on how to get that final step working?
Maybe use a custom type ?
from sqlalchemy import TypeDecorator, Text
class CleanedHtml(TypeDecorator):
impl = Text
def process_bind_param(self, value, dialect):
return clean_the_data(value)
Then you can just write your models this way:
class Example(db.Model):
__tablename__ = 'example'
normal_column = db.Column(db.Integer, primary_key=True, autoincrement=True)
html_column = db.Column(CleanedHtml)
More explanations are available in the documentation here: http://docs.sqlalchemy.org/en/latest/core/custom_types.html#augmenting-existing-types

How to use exclude_properties and include_properties to exclude/include SQLAlchemy model attributes from corresponding Spyne model?

I have model declared as:
class SAProduct(Base):
sku = Column(PRODUCT_SKU_TYPE, primary_key=True)
i_want_to_hide = Column(String(20), nullable=False)
name = Column(Unicode(255), nullable=True)
#property
def my_property(self):
return i_calculate_property_here(self)
and Spyne model declared as:
db = create_engine('sqlite:///:memory:')
Session = sessionmaker(bind=db)
class TableModel(ComplexModelBase):
__metaclass__ = ComplexModelMeta
__metadata__ = MetaData(bind=db)
class SProduct(TableModel):
__table__ = SAProduct.__table__
How can I make attribute i_want_to_hide to be excluded from Spyne model, and property my_property to be included as Spyne model attribute?
P.S.
Now I use monkey patching Spyne to support this syntax:
class SProduct(GComplexModel):
__model__ = Product
class Attributes:
exclude_attrs = ('i_want_to_hide',)
add_attrs = {'my_property': Boolean}
But I want get rid of it.
This doesn't directly answer your question, but please consider the following code:
from spyne import *
TableModel = TTableModel()
class SomeClass(TableModel):
__tablename__ = 'some_table'
id = Integer(pk=True)
s = Unicode
i = Integer(exc_table=True)
Here, pk stands for primary key (you can use the long form primary_key if you wish) and the i attribute will just be ignored by SqlAlchemy. e.g. it won't be created in the table, it won't be instrumented by SqlAlchemy's metaclass, etc.
As for an attribute that will be hidden from the RPC Parts of Spyne, but not from SqlAlchemy, that's a new feature coming in 2.12.
You will be able to say e.g.:
i = Integer(exc_table=True, pa={JsonObject: dict(exc=true)})
where pa stands for protocol attributes. (you can use the long form prot_attrs if you wish) Here i is ignored by every protocol that inherits JsonObject.
If you don't want it on the wsdl either, you'll have to do:
i = Integer(exc_table=True, exc_interface=True)
https://github.com/arskom/spyne/blob/fa4b1eef5815d3584287d1fef66b61846f82d2f8/spyne/interface/xml_schema/model.py#L197
Spyne offers a richer object model interface compared to SqlAlchemy. Trying to replicate this functionality without adding Spyne as a dependency means you'll have to duplicate all the work done in Spyne in your project. It's your choice!

What is the best way to store runtime information in SQLalchemy model?

What is the best way to store runtime information in model?
And is it good idea to store one in a model(like online/offline, etc)
for example:
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
username = Column(String, unique=True, nullable=False)
fullname = Column(String, default='')
password = Column(String, nullable=False)
role = Column(String, nullable=False)
status = {0: "Offline", 1: "Online", -1: "Unknown"}
def __init__(self, **kwargs):
Base.__init__(self, **kwargs)
self.init_status()
#orm.reconstructor
def init_status(self):
self._online = 0
#property
def online(self):
if self._online is None:
self._online = 0
if self.enable:
return self._online
return -1
#online.setter
def online(self, value):
if value != self.online:
dispatcher.send(sender=self, signal="state", value=value)
self._online = value
If I get object from session like
user = session.query(User).get(1)
change state
user.online = 1
and after session.close() I have detached object
Do I always have to do expunge(user) after commit() and before close()
and then if I want to change it, I have to add it to new session and the again commit,expunge,close
Is there any other ways?
P.S.
what is the most used practice, to create DAO layer or session it self work like DAO layer?
I need to have access to this state in a whole life of app, but as I undestand it's not a good way to use one session all time.
Proper way, to open session, do all my stuff with DB, then close session. But then I lost my state.
In java I have DAO layer and business object, that store all my db field and all my states regardless of session.
but with SA I already have session, DBO object and Manager object. I dont want to create so much layers, I think its not much pythonic.
Thanks.
You should store the status also in DB instead of memory.
As its not user data, preferably a different table, UserSession which has a user id FK.
If you do so, you can store other data as well e.g lastlogintime.
And even make intelligent decisions like if the lastlogintime > 30 mins, you can change the status back to offline maybe.
Storing such state in memory is not a good idea.

Categories