【问题标题】:DRY validation in Bottle?瓶中的 DRY 验证?
【发布时间】:2015-08-10 06:36:21
【问题描述】:

我的 Bottle 应用不是很干燥,这里有一个测试用例:

from uuid import uuid4
from bottle import Bottle, response

foo_app = Bottle()

@foo_app.post('/foo')
def create():
    if not request.json:
        response.status = 400
        return {'error': 'ValidationError', 'error_message': 'Body required'}
    body = request.json
    body.update({'id': uuid4().get_hex())
    # persist to db
    # ORM might set 'id' on the Model layer rather than setting it here
    # ORM will validate, as will db, so wrap this in a try/catch
    response.status = 201
    return body

@foo_app.put('/foo/<id>')
def update(id):
    if not request.json:
        response.status = 400
        return {'error': 'ValidationError', 'error_message': 'Body required'}
    elif 'id' not in request.json:
        response.status = 400
        return {'error': 'ValidationError', 'error_message': '`id` required'}
    db = {} # should be actual db cursor or whatever
    if 'id' not in db:
        response.status = 404
        return {'error': 'Not Found',
                'error_message': 'Foo `id` "{id}" not found'.format(id)}
    body = request.json
    # persist to db, return updated object
    # another try/catch here in case of update error (from ORM and/or db)
    return body

解决此问题的一种方法是拥有一个全局错误处理程序,并在所有地方引发错误。

另一个是使用装饰器,这也有开销问题。

是否有更好的方法来验证每条路线? - 我正在考虑类似的事情:

foo_app.post('/foo', middleware=[HAS_BODY_F, ID_IN_DB_F])

【问题讨论】:

  • 只是好奇(因为我到处都在使用它们) - 你有关于“装饰器......有开销问题”的参考吗?
  • 不记得具体是什么时候听说的,但这里有一篇关于它的博客文章:blog.dscpl.com.au/2014/02/…
  • 谢谢,感谢您的参考。
  • FWIW,共识here(比我自己更伟大的 Pythonistas)是装饰器不会固有地增加太多开销,所以我想我建议重新考虑这种方法.
  • 好吧,我想我可以添加一个装饰器(它需要一个函数列表)。查看我的回答,并在您认为合适的情况下发表评论/投票。谢谢。

标签: python validation bottle middleware python-decorators


【解决方案1】:

这是我的 DRY 验证解决方案,最终得到了一个装饰器:

from itertools import imap, ifilter    
from bottle import Bottle, request, response

app = Bottle()

middleware = lambda functions: lambda caller: lambda *args, **kwargs: next(
    ifilter(None, imap(lambda g: g(*args, **kwargs), functions)),
    caller(*args, **kwargs)
)


def has_body(*args, **kwargs):
    if not request.json:
        response.status = 400
        return {'error': 'ValidationError',
                'error_message': 'Body is required (and must be JSON).'}


def body_req(required):
    def inner(*args, **kwargs):
        intersection = required.intersection(set(request.json.keys()))
        if intersection != required:
            response.status = 400
            return {'error': 'ValidationError',
                   'error_message': 'Key(s): {} are not in JSON payload'.format(
                    ', '.join(imap(lambda key: "'{key}'".format(key=key),
                              required - intersection)))}

    return inner


@app.post('/foo')
@middleware([has_body, body_req({'id', 'f'})])
def get_foo():
    return {'foo': 'bar'}

【讨论】:

    【解决方案2】:

    最终找到了 Bottle 的内置“中间件”,称为“插件”(reference):

    from bottle import Bottle, request, response
    
    app = Bottle()
    
    
    def has_body(f):
        def inner(*args, **kwargs):
            if request.json:
                return f(*args, **kwargs)
    
            response.status = 400
            return {'error': 'ValidationError',
                    'error_message': 'Body is required (and must be JSON).'}
        return inner
    
    
    def body_req(required):
        def body_req_middleware(f):
            def inner(*args, **kwargs):
                intersection = required.intersection(set(request.json.keys()))
                if intersection != required:
                    response.status = 400
                    return {'error': 'ValidationError',
                            'error_message': 'Key(s): {} are not in JSON payload'
                            ''.format(', '.join('{!r}'.format(k)
                                                for k in required - intersection))}
                return f(*args, **kwargs)
            return inner
        return body_req_middleware
    
    
    @app.post('/foo', apply=(has_body, body_req({'id', 'f'})))
    def get_foo():
        return {'foo': 'bar'}
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-24
      • 1970-01-01
      相关资源
      最近更新 更多