- Author:
- [email protected]
- Posted:
- June 18, 2008
- Language:
- Python
- Version:
- .96
- Score:
- 3 (after 3 ratings)
Defines a decorator ``API_magic'' which simplifies input-checking API views.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 | """
API_magic
Author: Flowgram.com
======== Purpose ========
Defines a decorator ``API_magic'' which simplifies input-checking API views.
======== Parameters to API_magic ========
API_magic takes four parameters
1) The HTTP method (GET or POST) that the API call expects
2) A list ``required_args'' of the names of required arguments. These are then passed to the
function as positional arguments, possibly in an altered form. See "Parameter Check and
Transformation" below.
3) A dictionary ``permissions'' of permissions the requesting user must possess in order to execute
the call on the given parameters. See "Permissions" below.
4) A boolean ``login_required'' indicating whether the requesting user must be authenticated.
======== Return value of decorated function ========
If there is an error in the method, arguments, transformations, or permissions checking, API_magic
will return a response based on the error.
If the API call returns a value (other than None), that value will be returned.
Finally, the call will return a default 'ok' response.
======== Requirements ========
The following variables must be defined:
- ``response'': A function taking two arguments: a response code and an optional body message,
returning an HttpResponse object.
- ``permission_functions'': A dictionary mapping permission names to permission-checking functions.
A permission-checking function takes two arguments: a User objects and some other object, and
returns True if the user has the permission on that object.
- ``permission_responses'': A dictionary mapping permission names to response functions. A response
function takes an error message and returns an HttpResponse object.
- ``transformations'': A dictionary mapping request argument names to
(type_name, converter_function) tuples. A converter_function is a function that takes the value
of the request argument (a string) and returns an object of type type_name.
======== Example usage ========
# code before using API_magic
def some_api_function(request):
if request.method != 'POST':
return response('method-post')
if not 'foobar_id' in request.POST.keys():
return response('input-missing', 'foobar_id is a required POST arg')
try:
foobar = Foobar.objects.get(id=request.POST['foobar_id'])
except Foobar.DoesNotExist:
msg = "foobar_id %s could not be converted to Foobar object" % foobar_id
return response('input-invalid', msg)
if not permissions.can_edit(foobar, request.user):
msg = "%s does not have permission to edit that foobar_id" % request.user
return response('permission-edit', msg)
if not 'title' in request.POST.keys():
return response('input-missing', 'title is a required POST arg')
...do work here...
return response('Done')
# code after using API_magic
@API_magic('POST', ['foobar_id', 'title'], {'foobar_id': 'edit'})
def some_api_function(request, foobar, title):
...do work here...
return response('Done')
======== Parameter Check and Transformation ========
A function decorated with API_magic validates the presence of required arguments named in the
second parameter. For the example just above, it would make sure that there are arguments named
'foobar_id' and 'title' in request.POST, returning an API error response otherwise. Of course,
optional parameters can still be used, you just don't include them in the required arguments list.
Next, transformations are applied based on the argument's name. Transformations generally convert
the argument from strings to another type. This is probably the most magical thing going on.
There is a dictionary in this module called 'transformations' which maps arg-names to the
transformation to be applied. Any explicitly required function argument with a name among the
transformations will be transformed before it reaches the main function code. The keys in the
'transformations' dictionary are (typename, convert function) pairs. The typename is a string
to be used in the API error message that is returned if the conversion fails.
For the example above, the 'foobar_id' argument would be transformed to a Foobar object before
it is passed to the function.
======== Permissions ========
The third argument to API_magic is an optional dictionary of permissions to be checked. The keys
are strings that match required-argument names, and the values are strings that are keys in both
the permission_functions and permission_responses dictionaries locally defined. The values of the
permission_functions dictionary are permission-checking functions which return a boolean value.
The argument is transformed *before* it is passed to the permission checker -- for the example
above, the 'edit' permission checker is passed a Foobar object, not a foobar_id (string). If
the permission check fails (returns false), the decorated function will return an error
response defined by the permission_responses function for the given permission -- these functions
take a string error messsage as an argument.
"""
from functools import wraps
from django.contrib.auth.models import User
def response(code, msg=''):
"""Simple example response generator"""
from django.utils import simplejson
r = {'code': code,
'succeeded': 1 if code=='ok' else 0,
'body': msg
}
return HttpResponse(simplejson.dumps(r), mimetype='application/json')
# you need to make your own functions here, obviously:
from core import permissions
permission_functions = {
'view' : permissions.can_view,
'edit' : permissions.can_edit,
}
permission_responses = {
'view' : lambda x: response('permission-view', x),
'edit' : lambda x: response('permission-edit', x)
}
def fetch_foobar(foobarid):
from core.models import Foobar
return Foobar.objects.get(id=foobarid)
transformations = {
# arg name # Type name # converter function
'foobar_id' : ('Foobar', fetch_foobar),
'time' : ('integer', int),
}
def API_magic(method='POST', required_args=[], permissions={}, login_required=False):
def check_args(func):
# First, validate arguments *to API_magic* and raise an exception if they are bad.
# This happens when the code is first loaded by the server.
#
# validate the method:
if method not in ['GET', 'POST']: # add more here if you want
err_msg = "Invalid method %s passed to API_magic by %s" % (method, func.__name__)
raise ValueError(err_msg)
# validate the permissions:
for arg_name, perm_name in permissions.items():
if arg_name not in required_args:
err_msg = "Permission on unknown arg %s passed to API_magic by %s" % (arg_name, func.__name__)
raise ValueError(err_msg)
if perm_name not in permission_functions.keys():
err_msg = "Permission %s with unknown checker function passed to API_magic by %s"\
% (perm_name, func.__name__)
raise ValueError(err_msg)
if perm_name not in permission_responses.keys():
err_msg = "Permission %s with unknown response passed to API_magic by %s" % (perm_name, func.__name__)
raise ValueError(err_msg)
# Next, create an input-validating version of the decorated function:
@wraps(func)
def new_f(request, *args, **kwargs):
# Validate authentication
if login_required:
if not request.user.is_authenticated():
return response('login')
# Validate call method and get the arguments
if method == 'GET':
if request.method != 'GET':
return response('method-get')
arg_box = request.GET
else: # method == 'POST':
if request.method != 'POST':
return response('method-post')
arg_box = request.POST
# For each required argument:
for name in required_args:
# Make sure it's present
if not name in arg_box.keys():
return response('input-missing', "%s is a required %s argument" % (name, method))
value = arg_box[name]
# Apply any transformation
if name in transformations.keys():
type_name, converter = transformations[name]
try:
value = converter(value)
except:
err_msg = "%s %s could not be converted to %s object" % (name, value, type_name)
return response('input-invalid', err_msg)
# Check permissions on it
if name in permissions.keys():
permission_name = permissions[name]
perm_checker = permission_functions[permission_name]
perm_responder = permission_responses[permission_name]
if not perm_checker(request.user, value):
err_msg = "%s does not have permission to %s that %s" % (request.user, permission_name, name)
return perm_responder(err_msg)
# Add the (possibly transformed) value to the fixed function args
args += (value,)
# Call function with new arguments and return the function's return, or a default 'ok'
r = func(request, *args, **kwargs)
return r if r else response('ok')
return new_f
return check_args
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 11 months, 2 weeks ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months, 2 weeks ago
- Serializer factory with Django Rest Framework by julio 1 year, 6 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 7 months ago
- Help text hyperlinks by sa2812 1 year, 7 months ago
Comments
Please login first before commenting.