[Advanced Python] Find exceptions and tell me in the right way

目标:一个 REST API,如何写异常,让client端使用的舒舒服服。

  

先来个热身


一、异常处理

Ref: Flask Rest API -Part:4- Exception Handling

需要单独写一个这样的文件:errors.py

#~/movie-bag/resources/errors.py

class InternalServerError(Exception):
    pass

class SchemaValidationError(Exception):
    pass

class MovieAlreadyExistsError(Exception):
    pass

class UpdatingMovieError(Exception):
    pass

class DeletingMovieError(Exception):
    pass

class MovieNotExistsError(Exception):
    pass

class EmailAlreadyExistsError(Exception):
    pass

class UnauthorizedError(Exception):
    pass

errors = {
    "InternalServerError": {
        "message": "Something went wrong",
        "status": 500
    },
     "SchemaValidationError": {
         "message": "Request is missing required fields",
         "status": 400
     },
     "MovieAlreadyExistsError": {
         "message": "Movie with given name already exists",
         "status": 400
     },
     "UpdatingMovieError": {
         "message": "Updating movie added by other is forbidden",
         "status": 403
     },
     "DeletingMovieError": {
         "message": "Deleting movie added by other is forbidden",
         "status": 403
     },
     "MovieNotExistsError": {
         "message": "Movie with given id doesn't exists",
         "status": 400
     },
     "EmailAlreadyExistsError": {
         "message": "User with given email address already exists",
         "status": 400
     },
     "UnauthorizedError": {
         "message": "Invalid username or password",
         "status": 401
     }
}
View Code

 

Ref: Python异常编程技巧

  • 异常自定义的必要性

自定义异常的必要性,至少自己看的清楚。

class Error(Exception):
  """Root exception for all exceptions raised by this module."""

--------------------------------
class SocketTimeError(Error):   pass class SocketRefuseError(Error):   pass def connect():   pass

调用异常的示范:

try:
  connect()

except SocketTimeError as err:
  log.error(error)

except SocketRefuseError as err:   log.error(error) except Error as err:   log.error("API Unexpected error:%s" % err) except Exception as err:   log.error("API bug cause exception.")

 

  • 异常代码分离

异常处理与正常流程处理最好分离。

def action_executor():
  action_a()
  action_b() 
  action_c()

# 主函数力求简洁明了
def action():   try:     action_executor()   except ActionException as e:     log.error(e) action() action_d()

 

 

二、REST API 编写技巧

Ref: http://www.pythondoc.com/Flask-RESTful/quickstart.html

The better solution than using abort to signal errors for invalid API usage is to implement your own exception type and install an error handler for it that produces the errors in the format the user is expecting.

以下是个 “使用abort的过时的例子”

from flask import Flask
from flask.ext.restful import reqparse, abort, Api, Resource

app = Flask(__name__)
api = Api(app)

TODOS = {
    'todo1': {'task': 'build an API'},
    'todo2': {'task': '?????'},
    'todo3': {'task': 'profit!'},
}


def abort_if_todo_doesnt_exist(todo_id):
    if todo_id not in TODOS:
        abort(404, message="Todo {} doesn't exist".format(todo_id))

parser = reqparse.RequestParser()
parser.add_argument('task', type=str)


# Todo
#   show a single todo item and lets you delete them
class Todo(Resource):
    def get(self, todo_id):
        abort_if_todo_doesnt_exist(todo_id)
        return TODOS[todo_id]

    def delete(self, todo_id):
        abort_if_todo_doesnt_exist(todo_id)
        del TODOS[todo_id]
        return '', 204

    def put(self, todo_id):
        args = parser.parse_args()
        task = {'task': args['task']}
        TODOS[todo_id] = task
        return task, 201


# TodoList
#   shows a list of all todos, and lets you POST to add new tasks
class TodoList(Resource):
    def get(self):
        return TODOS

    def post(self):
        args = parser.parse_args()
        todo_id = int(max(TODOS.keys()).lstrip('todo')) + 1
        todo_id = 'todo%i' % todo_id
        TODOS[todo_id] = {'task': args['task']}
        return TODOS[todo_id], 201

##
## Actually setup the Api resource routing here
##
api.add_resource(TodoList, '/todos')
api.add_resource(Todo, '/todos/<todo_id>')


if __name__ == '__main__':
    app.run(debug=True)

 

 

 

正规系统学习


Ref: MarkGao11520/python-flask-restful-api-book

【慕课网-Python Flask构建可扩展的RESTful API-笔记】

标准的REST比较适合开放性的API。只负责提供数据,不负责业务逻辑。

标准的REST会造成HTTP请求的数量大幅度的增加。

 

HTTP状态码通常分为5种类型,分别以1~5五个数字开头,由3位整数组成:

------------------------------------------------------------------------------------------------

200:请求成功      处理方式:获得响应的内容,进行处理

201:请求完成,结果是创建了新资源。新创建资源的URI可在响应的实体中得到    处理方式:爬虫中不会遇到

202:请求被接受,但处理尚未完成    处理方式:阻塞等待

204:服务器端已经实现了请求,但是没有返回新的信 息。如果客户是用户代理,则无须为此更新自身的文档视图。    处理方式:丢弃

300:该状态码不被HTTP/1.0的应用程序直接使用, 只是作为3XX类型回应的默认解释。存在多个可用的被请求资源。    处理方式:若程序中能够处理,则进行进一步处理,如果程序中不能处理,则丢弃
301:请求到的资源都会分配一个永久的URL,这样就可以在将来通过该URL来访问此资源    处理方式:重定向到分配的URL
302:请求到的资源在一个不同的URL处临时保存     处理方式:重定向到临时的URL

304 请求的资源未更新     处理方式:丢弃

400 非法请求     处理方式:丢弃

401 未授权     处理方式:丢弃

403 禁止     处理方式:丢弃

404 没有找到     处理方式:丢弃

5XX 回应代码以“5”开头的状态码表示服务器端发现自己出现错误,不能继续执行请求    处理方式:丢弃

 

一、自定义 APIException 

Ref: flask 重写HTTPException类并自定义异常信息

Ref: 3.3 重构代码-自定义验证对象

Ref: HTTP Exception by werkzeug

This module implements a number of Python exceptions you can raise from within your views to trigger a standard non-200 response. 

 

Flask:abort()函数 --> 直接返回“规范错误格式”,但有点不友好。例如:

@app.route('/abort0')
def abort0():
    abort(300) 

参考:Flask开发技巧之异常处理

 

二、继续

剩下的,主要是对 flask_restful的学习和实践。在此省略。

 

三、实战

  • 自定义 部分

from werkzeug.exceptions import HTTPException
from flask import json


class ApiException(HTTPException):
    code = 500
    status = "fail"
    information = "Unknown"

    def __init__(self, code=None, status=None, information=None, payload=None):
        Exception.__init__(self)
        if code:
            self.code = code
        if status:
            self.status = status
        if information:
            self.information = information
        self.payload = payload
        super(ApiException, self).__init__(information, None)

    def get_body(self, environ=None):
        body = dict(self.payload or ())
        body['status'] = self.status
        body['information'] = self.information
        return body

    def get_headers(self, environ=None):
        return [('Content-Type', 'application/json')]


# success
class Success(ApiException):
    code = 200
    status = "success"
    information = "Done"


class WrongPassword(ApiException):
    code = 403
    status = "fail"
    information = "todo"


class WrongCheckCode(ApiException):
    code = 403
    status = "fail"
    information = "todo"


class SameUser(ApiException):
    code = 403
    status = "fail"
    information = "todo"


class WrongUserName(ApiException):
    code = 403
    status = "fail"
    information = "todo"


class WrongAuth(ApiException):
    code = 403
    status = "fail"
    information = "User or project name is invalid."


class ParameterException(ApiException):
    code = 403
    status = "fail"
    information = "todo"


class NoQuestionnaire(ApiException):
    code = 404
    status = "fail"
    information = "todo"


class NoProblem(ApiException):
    code = 404
    status = "fail"
    information = "todo"


class WrongProblemSecretKey(ApiException):
    code = 404
    status = "fail"
    information = "todo"


class SameIp(ApiException):
    code = 403
    status = "fail"
    information = "todo"


class WrongType(ApiException):
    code = 403
    status = "fail"
    information = "todo"


class WrongCode(ApiException):
    code = 403
    status = "fail"
    information = "todo"


class RequestError(ApiException):
    code = 400
    status = "fail"
    information = "Please check request format or content."

class InvalidPathOnServer(ApiException):
    code = 403
    status = "fail"
    information = "Cannot find the path on Server"
View Code

 

  • raise部分

# 默认的
raise
WrongAuth()
# 具体情况具体返回信息 raise RequestError(information='Please check Job mode again')

 

  • handler部分

    def post(self):

        response_object = {'status': 'success'}
        response_object['information'] = 'Add task successfully.'
        train_tasks = []

        try:
            train_tasks = self.run_one_task()

        except RequestError as err:
            print("<RequestError>: {}".format(err))
            response_object['train_tasks'] = train_tasks
            response_object.update(err.get_body())

        except InvalidPathOnServer as err:
            print("<InvalidPathOnServer>: {}".format(err))
            response_object['train_tasks'] = train_tasks
            response_object.update(err.get_body())

        except WrongAuth as err:
            print("<WrongAuth>: {}".format(err))
            response_object['train_tasks'] = train_tasks
            response_object.update(err.get_body())

        except ClientError as err:
            print("<ClientError>: {}".format(err))
            response_object = {'status': 'fail'}
            response_object['information'] = 'Fail to connect with server resources.'
            response_object['train_tasks'] = train_tasks

        except Exception as err:
            print("<Exception>: {}".format(err))
            response_object = {'status': 'fail'}
            response_object['information'] = 'Unknown errors.'
            response_object['train_tasks'] = train_tasks

        else:
            response_object['train_tasks'] = train_tasks

        return jsonify(response_object)

 

End.

posted @ 2020-11-22 16:22  郝壹贰叁  阅读(83)  评论(0编辑  收藏  举报