From 55caf167c933529815ae463aff1311422986c33c Mon Sep 17 00:00:00 2001 From: esensar Date: Mon, 7 May 2018 13:57:53 +0200 Subject: [PATCH] Use marshmallow and webargs for parsing and marshaling --- app/api/__init__.py | 14 +++++++++++ app/api/resources/account.py | 42 +++++++++++---------------------- app/api/resources/token.py | 45 ++++++++++-------------------------- requirements.txt | 3 +++ 4 files changed, 42 insertions(+), 62 deletions(-) diff --git a/app/api/__init__.py b/app/api/__init__.py index 251c937..8bfc1f6 100644 --- a/app/api/__init__.py +++ b/app/api/__init__.py @@ -2,6 +2,7 @@ from flask import Blueprint from flask_restful import Api from .resources.account import AccountResource from .resources.token import TokenResource +from marshmallow import ValidationError api_bp = Blueprint('api', __name__) api = Api(api_bp) @@ -9,3 +10,16 @@ api = Api(api_bp) # Add resources api.add_resource(AccountResource, '/v1/accounts') api.add_resource(TokenResource, '/v1/token') + + +@api_bp.errorhandler(ValidationError) +def handle_validation_error(e): + return {'status': 'error', 'message': str(e)}, 422 + + +@api_bp.errorhandler(Exception) +def handle_unknown_errors(e): + return ({ + 'status': 'failed', + 'message': 'Unknown error has occurred! ({0})'.format(str(e)) + }, 500) diff --git a/app/api/resources/account.py b/app/api/resources/account.py index db85f0b..c8a1b3d 100644 --- a/app/api/resources/account.py +++ b/app/api/resources/account.py @@ -1,38 +1,22 @@ -from flask_restful import Resource, reqparse, abort +from flask_restful import Resource, abort +from webargs import fields +from webargs.flaskparser import use_args import app.accounts as accounts -def user(user_dict): - """ - Type definition of user object required as a parameter - - Required keys: - * username - string - * password - string - * email - string - - :returns user dictionary with required keys - :rtype dict - :raises ValueError if parameter is not dict or is missing required keys - """ - if not isinstance(user_dict, dict): - raise ValueError("User should contain username, password and email") - if ('username' not in user_dict or - 'password' not in user_dict or - 'email' not in user_dict): - raise ValueError("User should contain username, password and email") - return user_dict - - class AccountResource(Resource): - parser = reqparse.RequestParser(bundle_errors=True) - parser.add_argument('user', location='json', type=user, - help='User is not valid. Error: {error_msg}', - required=True) + user_args = { + 'user': fields.Nested({ + 'username': fields.Str(required=True), + 'email': fields.Email(required=True), + 'password': fields.Str(required=True) + }, required=True, location='json') + } - def post(self): + @use_args(user_args) + def post(self, args): try: - args = AccountResource.parser.parse_args()['user'] + args = args['user'] success = accounts.create_account( args['username'], args['email'], diff --git a/app/api/resources/token.py b/app/api/resources/token.py index 926fb7f..5e1c03c 100644 --- a/app/api/resources/token.py +++ b/app/api/resources/token.py @@ -1,46 +1,25 @@ -from flask_restful import Resource, reqparse, abort, fields, marshal_with +from flask_restful import Resource, abort +from webargs import fields +from webargs.flaskparser import use_args import app.accounts as accounts -def user(user_dict): - """ - Type definition of user object required as a parameter - - Required keys: - * username - string - * password - string - - :returns user dictionary with required keys - :rtype dict - :raises ValueError if parameter is not dict or is missing required keys - """ - if not isinstance(user_dict, dict): - raise ValueError("User should contain username, password and email") - if ('username' not in user_dict or - 'password' not in user_dict): - raise ValueError("User should contain username, password and email") - return user_dict - - class TokenResource(Resource): - parser = reqparse.RequestParser(bundle_errors=True) - parser.add_argument('user', location='json', type=user, - help='User is not valid. Error: {error_msg}', - required=True) - - res_fields = { - 'status': fields.String(default='Success'), - 'token': fields.String + user_args = { + 'user': fields.Nested({ + 'username': fields.Str(), + 'password': fields.Str() + }, required=True, location='json') } - @marshal_with(res_fields) - def post(self): + @use_args(user_args) + def post(self, args): try: - args = TokenResource.parser.parse_args()['user'] + args = args['user'] token = accounts.create_token( args['username'], args['password']) if token: - return {'token': token}, 200 + return {'status': 'success', 'token': token}, 200 except ValueError: abort(401, message='Invalid credentials', status='error') diff --git a/requirements.txt b/requirements.txt index ca7de3c..6afd445 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,8 +16,10 @@ itsdangerous==0.24 Jinja2==2.10 Mako==1.0.7 MarkupSafe==1.0 +marshmallow==3.0.0b9 paho-mqtt==1.3.1 psycopg2==2.7.4 +psycopg2-binary==2.7.4 pycparser==2.18 PyJWT==1.4.2 python-dateutil==2.7.2 @@ -26,4 +28,5 @@ pytz==2018.4 six==1.11.0 SQLAlchemy==1.2.7 typing==3.6.4 +webargs==2.1.0 Werkzeug==0.14.1