简体   繁体   中英

How to catch an exception in Flask from sqlalchemy @validate decorator

I have an interesting problem. Basically I have this Flask restful API app, where I have an HTML page on which logged in users can create an API user. This is handled by SQLAlchemy Model Class Apiusers in which I am using validates decorator to make sure that username and email address abide by the rules. All of it works fine, when user enters invalid information it throws an exception but I can't catch it in Flask no matter what. I want to catch it and flash a message about it back to user, but instead it throws a builtins.AssertionError which ends up giving 500 internal server error page when not in the Debug mode.

Code is very similar to this: https://nunie123.github.io/sqlalchemy-validation.html

Tried using different types of exceptions - ValueError, defining custom exception class and finally catching all errors with except: but exception always ends up being unhandled. I also came across somewhat similar problem as noted here: https://github.com/marshmallow-code/webargs/issues/122 where there is a mention that flask restful hijacks handling of all HTTP Exceptions, but it's from 2016. In either case I was able to catch SQLAlchemy unique constraint exceptions all fine at the same spot before using validates, so I don't think that is an issue.

models.py:

from sqlalchemy.orm import validates    

class Apiusers(db.Model):
    __tablename__ = 'apiusers'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), index=True, unique=True)
    email = db.Column(db.String(120), index=True)
    password_hash = db.Column(db.String(128))


    def __repr__(self):
        return self.username

    @validates('username')
    def validate_username(self, key, username):
      if not username:
        raise AssertError('No username provided')
      if Apiusers.query.filter(Apiusers.username == username).first():
        raise AssertError('Username is already in use')

      if len(username) < 5 or len(username) > 20:
        raise AssertError('Username must be between 5 and 20 characters')

      return username

    @validates('email')
    def validate_email(self, key, email):
      if not email:
        raise AssertError('No email provided')
      if not re.match("[^@]+@[^@]+\.[^@]+", email):
        raise AssertError('Provided email is not an email address')
      return email


views.py:

@app.route('/register', methods=['GET', 'POST'])
def register():
  if request.method == 'POST':
    _user = request.form['username']
    _email = request.form['emails']
    record = Apiusers(username=_user, email=_email)
    try:
      db.session.add(record)
      db.session.commit()
    except AssertError as e:
      flash('{}'.format(str(e)), 'danger')
      return render_template("register.html")
    return render_template("regresult.html")

Here is the stacktrace as well when trying to add an empty username:

Traceback (most recent call last):
File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 2309, in 
__call__
return self.wsgi_app(environ, start_response)
File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 2295, in 
wsgi_app
response = self.handle_exception(e)
File "/usr/local/lib/python3.6/site-packages/flask_restful/__init__.py", line 269, in error_router
return original_handler(e)
File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1741, in handle_exception
reraise(exc_type, exc_value, tb)
File "/usr/local/lib/python3.6/site-packages/flask/_compat.py", line 35, in reraise
raise value
File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 2292, in wsgi_app
response = self.full_dispatch_request()
File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1815, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/usr/local/lib/python3.6/site-packages/flask_restful/__init__.py", line 269, in error_router
return original_handler(e)
File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1718, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/usr/local/lib/python3.6/site-packages/flask/_compat.py", line 35, in reraise
raise value
File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1813, in full_dispatch_request
rv = self.dispatch_request()
File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1799, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/opt/app/api2/app.py", line 189, in regresult
record = Apiusers(username=_user, email=_email)
File "<string>", line 4, in __init__

File "/usr/local/lib/python3.6/site-packages/sqlalchemy/orm/state.py", line 428, in _initialize_instance
manager.dispatch.init_failure(self, args, kwargs)
File "/usr/local/lib/python3.6/site-packages/sqlalchemy/util/langhelpers.py", line 67, in __exit__
compat.reraise(exc_type, exc_value, exc_tb)
File "/usr/local/lib/python3.6/site-packages/sqlalchemy/util/compat.py", line 277, in reraise
raise value
File "/usr/local/lib/python3.6/site-packages/sqlalchemy/orm/state.py", line 425, in _initialize_instance
return manager.original_init(*mixed[1:], **kwargs)
File "/usr/local/lib/python3.6/site-packages/sqlalchemy/ext/declarative/base.py", line 801, in _declarative_constructor
setattr(self, k, kwargs[k])
File "/usr/local/lib/python3.6/site-packages/sqlalchemy/orm/attributes.py", line 263, in __set__
instance_state(instance), instance_dict(instance), value, None
File "/usr/local/lib/python3.6/site-packages/sqlalchemy/orm/attributes.py", line 810, in set
state, dict_, value, old, initiator
File "/usr/local/lib/python3.6/site-packages/sqlalchemy/orm/attributes.py", line 818, in fire_replace_event
state, value, previous, initiator or self._replace_token
File "/usr/local/lib/python3.6/site-packages/sqlalchemy/orm/util.py", line 168, in set_
return validator(state.obj(), key, value)
File "/opt/app/api2/app.py", line 95, in validate_name
assert value != '', "Invalid username"
AssertionError: Invalid username

An unhandled assertion will cause the program to terminate. See here . Try instead something like:

if not email:
    flash('Your error message')
    return redirect(request.url)

if not re.match("[^@]+@[^@]+\.[^@]+", email):
    flash('Your error message')
    return redirect(request.url)

Actually just figured out the answer. After moving codeline that calls Apiusers model in the try - except block, all exceptions are caught and handled all fine :-) Previously they ended up being unhandled as that line was outside of the block. That's what happens when you miss your afternoon coffee.

try:
  record = Apiusers(username=_user, email=_email)
  db.session.add(record)
  db.session.commit()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM