DRY validation in Bottle? - python

My Bottle apps haven't been very DRY, here's a test-case:
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
One way of solving this issue is to have a global error handler, and raise errors all over the place.
Another is to use decorators, which also have overhead issues.
Is there a better way of doing the validation side of each route? - I'm thinking of something like:
foo_app.post('/foo', middleware=[HAS_BODY_F, ID_IN_DB_F])

Ended up finding Bottle's built-in "middleware" called "plugins" (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'}

Here is my solution to DRY validation, ended up with a decorator:
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'}

Related

django pytest how to test a view with argument(id)

i have a question regarding using pytest. These are my very 1st tests. I have 2 views which
i want to test (simplest possible way).
Views:
class MenuView(View):
def get(self, request):
return render(request, 'diet_app/menu.html')
class CuisineDetailsView(View):
def get(self, request, id):
cuisine = Cuisine.objects.get(id=id)
recipes = cuisine.recipe_set.all()
return render(request, 'diet_app/cuisine_details.html', {'cuisine': cuisine, 'recipes': recipes})
Here are my tests:
def test_menu(client):
url = reverse('menu')
response = client.get(url)
assert response.status_code == 200
#pytest.mark.django_db
def test_cuisine_details_view(client):
url = reverse('cuisine-details')
response = client.get(url)
assert response.status_code == 200
Urls:
path('menu/', MenuView.as_view(), name='menu'),
path('cuisine_details/<int:id>/', CuisineDetailsView.as_view(), name='cuisine-details'),
1st test (menu view) is working properly
2nd test (cuisine details view) shows error
.NoReverseMatch: Reverse for 'cuisine-details' with no arguments not found. 1 pattern(s) tried: ['cuisine_details\\/(?P<id>
I know i should probably put somethere ID argument but tried few options and havent succeed. Will be grateful for any help/advise
You must pass the id as argument to the reverse function.
#pytest.mark.django_db
def test_cuisine_details_view(client):
url = reverse('cuisine-details', kwargs={'id': 123})
response = client.get(url)
assert response.status_code == 200

Unresolved Reference "content" in JSONResponse

So, I've been trying to write unit tests for a function that returns a JSONResponse that contains a status_code and content. The function looks like this:
def exception_handler(request):
return JSONResponse(
status_code=status.HTTP_404_NOT_FOUND,
content=jsonable_encoder({"detail": NotFoundException} if is_development() else {}),
)
I'm testing for two cases: is_development() returns True or False. When I'm calling the function to test it like this: response = exception_handler(request) I can access response.status_code but not response.content. Why is that? I wanna access the content in order to check if it is correct.
I cannot really reproduce your problem. Here is the code which works in my case.
# ---- Some setup, to make your example work ----
from fastapi.encoders import jsonable_encoder
class status:
HTTP_404_NOT_FOUND = 404
class JSONResponse:
def __init__(self, status_code, content):
self.status_code = status_code
self.content = content
# ----- Here your code -----
def exception_handler(request):
return JSONResponse(
status_code=status.HTTP_404_NOT_FOUND,
content=jsonable_encoder({"detail": None} if is_development() else {}),
)
def is_development():
"""True and False work"""
return False
print(vars(exception_handler(None)))
Output:
{'status_code': 404, 'content': {}}
Could you provide more information?

How create a function with method patch

I have a function with a method put, but a want other function with method patch, but I don't know how do it.
Here a method put:
#app.route('/api/v1/benefit/', methods=['PUT'])
def update_benefited(id):
benefited = Benefited.query.filter_by(id=id).first()
if benefited is None:
return jsonify({'message': 'Benefited does not exists'}), 404
json = request.get_json(force=True)
if json.get('username') is None:
return jsonify({'message': 'Bad request'}), 400
benefited.username = json['username']
benefited.age = json['age']
benefited.job = json['job']
benefited.update()
return jsonify({'benefited': benefited.json()})

Flask-restful basic Authentication

I am new to Flask and I need some help for my school work.
I am trying to build a simple ToDo list system using flask-restful.
My current code looks like this:
class ToDoList(Resource):
'''TODO LIST'''
operation = ['delete']
decorators = [auth.login_required, advertise('operation')]
def post(self):
"""remove all item in the TODO list"""
operation = request.args.get('op')
if operation == 'delete':
collection2.delete_many({})
return {'Success': 'OK'}, 200
return {'Error':'Illegal Operation'}, 400
def get(self):
"""return a list of the TODO name"""
list_1 = collection2.find()
list_2 = []
for each in list_1:
list_2.append(JSONEncoder().encode(each))
return {'list':list_2}, 200
It works, but I want only the post method to require authentication, and get method without authentication so anyone can acquire the list without login. I am using the flask-restful I don't know how to give the decorators separately to each function.
I used flaskrestplus to do basic authentication. All the required authorizations are provided as an authorizations dictionary. Then they are passed to the API.
Also the authorizations can be applied at the method level using
#api.doc(security='basicAuth')
The validation logic (can be ldap validation or db validation) can be writted in a decorator called requires_Auth. This decorator is invoked using
decorators = [requires_Auth]
Complete code
from flask import Flask, request
from flask_restplus import Api, Resource
from functools import wraps
def requires_Auth(f):
#wraps(f)
def decorator(*args, **kwargs):
auth = request.authorization
if auth:
print "inside decorator", auth.username,auth.password
return f(*args, **kwargs)
else:
return "Login required!!!!",401
return decorator
authorizations = {
'basicAuth': {
'type': 'basic',
'in': 'header',
'name': 'Authorization'
}
}
api = Api(app, version='1.0',
authorizations=authorizations
)
ns = api.namespace('/', description='Authentication API')
#ns.route('/withDecorator')
class HelloWorldWithDecorator(Resource):
decorators = [requires_Auth]
#api.doc(security='basicAuth')
def get(self):
return {'hello': 'world'}
api.add_namespace(ns)
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5001)
From Flask-RESTful documentation [1]:
Alternatively, you can specify a dictionary of iterables that map to HTTP methods and the decorators will only apply to matching requests.
def cache(f):
#wraps(f)
def cacher(*args, **kwargs):
# caching stuff
return cacher
class MyResource(restful.Resource):
method_decorators = {'get': [cache]}
def get(self, *args, **kwargs):
return something_interesting(*args, **kwargs)
def post(self, *args, **kwargs):
return create_something(*args, **kwargs)
In your case it would be:
method_decorators = {'post': [auth.login_required]}

How to pass callbacks and their arguments from wrapped function to decorator with Python 3.x?

I am writing a generic wrapper around a REST API. I have several functions like the one below, responsible for retrieving a user from its email address. The part of interest is how the response is processed, based on a list of expected status codes (besides HTTP 200) and callbacks associated to each expected status code:
import requests
def get_user_from_email(email):
response = requests.get('http://example.com/api/v1/users/email:%s' % email)
# define callbacks
def return_as_json(response):
print('Found user with email [%s].' % email)
return response.json()
def user_with_email_does_not_exist(response):
print('Could not find any user with email [%s]. Returning `None`.' % email),
return None
expected_status_codes_and_callbacks = {
requests.codes.ok: return_as_json, # HTTP 200 == success
404: user_with_email_does_not_exist,
}
if response.status_code in expected_status_codes_and_callbacks:
callback = expected_status_codes_and_callbacks[response.status_code]
return callback(response)
else:
response.raise_for_status()
john_doe = get_user_from_email('john.doe#company.com')
print(john_doe is not None) # True
unregistered_user = get_user_from_email('unregistered.user#company.com')
print(unregistered_user is None) # True
The code above works well so I want to refactor and generalize the response processing part. I would love to end up with the following code:
#process_response({requests.codes.ok: return_as_json, 404: user_with_email_does_not_exist})
def get_user_from_email(email):
# define callbacks
def return_as_json(response):
print('Found user with email [%s].' % email)
return response.json()
def user_with_email_does_not_exist(response):
print('Could not find any user with email [%s]. Returning `None`.' % email),
return None
return requests.get('https://example.com/api/v1/users/email:%s' % email)
with the process_response decorator defined as:
import functools
def process_response(extra_response_codes_and_callbacks=None):
def actual_decorator(f):
#functools.wraps(f)
def wrapper(*args, **kwargs):
response = f(*args, **kwargs)
if response.status_code in expected_status_codes_and_callbacks:
action_to_perform = expected_status_codes_and_callbacks[response.status_code]
return action_to_perform(response)
else:
response.raise_for_status() # raise exception on unexpected status code
return wrapper
return actual_decorator
My problem is the decorator complains about not having access to return_as_json and user_with_email_does_not_exist because these callbacks are defined inside the wrapped function.
If I decide to move the callbacks outside of the wrapped function, for example at the same level as the decorator itself, then the callbacks have no access to the response and email variables inside the wrapped function.
# does not work either, as response and email are not visible from the callbacks
def return_as_json(response):
print('Found user with email [%s].' % email)
return response.json()
def user_with_email_does_not_exist(response):
print('Could not find any user with email [%s]. Returning `None`.' % email),
return None
#process_response({requests.codes.ok: return_as_json, 404: user_with_email_does_not_exist})
def get_user_from_email(email):
return requests.get('https://example.com/api/v1/users/email:%s' % email)
What is the right approach here? I find the decorator syntax very clean but I cannot figure out how to pass the required parts to it (either the callbacks themselves or their input arguments like response and email).
You could convert the decorator keys into strings, and then pull the inner functions from the outer function passed to the decorator via f.func_code.co_consts. Don't do it this way.
import functools, new
from types import CodeType
def decorator(callback_dict=None):
def actual_decorator(f):
code_dict = {c.co_name: c for c in f.func_code.co_consts if type(c) is CodeType}
#functools.wraps(f)
def wrapper(*args, **kwargs):
main_return = f(*args, **kwargs)
if main_return['callback'] in callback_dict:
callback_string = callback_dict[main_return['callback']]
callback = new.function(code_dict[callback_string], {})
return callback(main_return)
return wrapper
return actual_decorator
#decorator({'key_a': 'function_a'})
def main_function(callback):
def function_a(callback_object):
for k, v in callback_object.items():
if k != 'callback':
print '{}: {}'.format(k, v)
return {'callback': callback, 'key_1': 'value_1', 'key_2': 'value_2'}
main_function('key_a')
# key_1: value_1
# key_2: value_2
Can you use classes? The solution is immediate if you can use a class.
As mentioned in the comments for my other answer, here is an answer that uses classes and decorators. It's a bit counter-intuitive because get_user_from_email is declared as a class, but ends up as a function after decorating. It does have the desired syntax however, so that's a plus. Maybe this could be a starting point for a cleaner solution.
# dummy response object
from collections import namedtuple
Response = namedtuple('Response', 'data status_code error')
def callback_mapper(callback_map):
def actual_function(cls):
def wrapper(*args, **kwargs):
request = getattr(cls, 'request')
response = request(*args, **kwargs)
callback_name = callback_map.get(response.status_code)
if callback_name is not None:
callback_function = getattr(cls, callback_name)
return callback_function(response)
else:
return response.error
return wrapper
return actual_function
#callback_mapper({'200': 'json', '404': 'does_not_exist'})
class get_user_from_email:
#staticmethod
def json(response):
return 'json response: {}'.format(response.data)
#staticmethod
def does_not_exist(response):
return 'does not exist'
#staticmethod
def request(email):
response = Response('response data', '200', 'exception')
return response
print get_user_from_email('blah')
# json response: response data
Here's an approach that uses function member data on class methods in order to map the response function to the appropriate callback. This seems like the cleanest syntax to me, but still has a class turning into a function (which could be easily avoided if desired).
# dummy response object
from collections import namedtuple
Response = namedtuple('Response', 'data status_code error')
def callback(status_code):
def method(f):
f.status_code = status_code
return staticmethod(f)
return method
def request(f):
f.request = True
return staticmethod(f)
def callback_redirect(cls):
__callback_map = {}
for attribute_name in dir(cls):
attribute = getattr(cls, attribute_name)
status_code = getattr(attribute, 'status_code', '')
if status_code:
__callback_map[status_code] = attribute
if getattr(attribute, 'request', False):
__request = attribute
def call_wrapper(*args, **kwargs):
response = __request(*args, **kwargs)
callback = __callback_map.get(response.status_code)
if callback is not None:
return callback(response)
else:
return response.error
return call_wrapper
#callback_redirect
class get_user_from_email:
#callback('200')
def json(response):
return 'json response: {}'.format(response.data)
#callback('404')
def does_not_exist(response):
return 'does not exist'
#request
def request(email):
response = Response(email, '200', 'exception')
return response
print get_user_from_email('generic#email.com')
# json response: generic#email.com
You could pass the function parameters of the outer function to the handlers:
def return_as_json(response, email=None): # email param
print('Found user with email [%s].' % email)
return response.json()
#process_response({requests.codes.ok: return_as_json, 404: ...})
def get_user_from_email(email):
return requests.get('...: %s' % email)
# in decorator
# email param will be passed to return_as_json
return action_to_perform(response, *args, **kwargs)

Categories