REST API helpers¶
There are a number of helpers that make building out the REST API easier.
Etags¶
HTTP etags are a way for clients to decide whether their copy of a resource
has changed or not. Mailman’s REST API calculates this in a cheap and dirty
way. Pass in the dictionary representing the resource and that dictionary
gets modified to contain the etag under the http_etag
key.
>>> from mailman.rest.helpers import etag
>>> resource = dict(geddy='bass', alex='guitar', neil='drums')
>>> json_data = etag(resource)
>>> print(resource['http_etag'])
"6929ecfbda2282980a4818fb75f82e812077f77a"
For convenience, the etag function also returns the JSON representation of the dictionary after tagging, since that’s almost always what you want.
>>> import json
>>> data = json.loads(json_data)
# This is pretty close to what we want, so it's convenient to use.
>>> dump_msgdata(data)
alex : guitar
geddy : bass
http_etag: "6929ecfbda2282980a4818fb75f82e812077f77a"
neil : drums
POST and PUT unpacking¶
Another helper unpacks POST
and PUT
request variables, validating and
converting their values.
>>> from mailman.rest.validator import Validator
>>> validator = Validator(one=int, two=str, three=bool)
>>> class FakeRequest:
... params = None
>>> FakeRequest.params = dict(one='1', two='two', three='yes')
On valid input, the validator can be used as a **keyword
argument.
>>> def print_request(one, two, three):
... print(repr(one), repr(two), repr(three))
>>> print_request(**validator(FakeRequest))
1 'two' True
On invalid input, an exception is raised.
>>> FakeRequest.params['one'] = 'hello'
>>> print_request(**validator(FakeRequest))
Traceback (most recent call last):
...
ValueError: Cannot convert parameters: one
On missing input, an exception is raised.
>>> del FakeRequest.params['one']
>>> print_request(**validator(FakeRequest))
Traceback (most recent call last):
...
ValueError: Missing parameters: one
If more than one key is missing, it will be reflected in the error message.
>>> del FakeRequest.params['two']
>>> print_request(**validator(FakeRequest))
Traceback (most recent call last):
...
ValueError: Missing parameters: one, two
Extra keys are also not allowed.
>>> FakeRequest.params = dict(one='1', two='two', three='yes',
... four='', five='')
>>> print_request(**validator(FakeRequest))
Traceback (most recent call last):
...
ValueError: Unexpected parameters: five, four
However, if optional keys are missing, it’s okay.
>>> validator = Validator(one=int, two=str, three=bool,
... four=int, five=int,
... _optional=('four', 'five'))
>>> FakeRequest.params = dict(one='1', two='two', three='yes',
... four='4', five='5')
>>> def print_request(one, two, three, four=None, five=None):
... print(repr(one), repr(two), repr(three), repr(four), repr(five))
>>> print_request(**validator(FakeRequest))
1 'two' True 4 5
>>> del FakeRequest.params['four']
>>> print_request(**validator(FakeRequest))
1 'two' True None 5
>>> del FakeRequest.params['five']
>>> print_request(**validator(FakeRequest))
1 'two' True None None
But if the optional values are present, they must of course also be valid.
>>> FakeRequest.params = dict(one='1', two='two', three='yes',
... four='no', five='maybe')
>>> print_request(**validator(FakeRequest))
Traceback (most recent call last):
...
ValueError: Cannot convert parameters: five, four
Arrays¶
Some POST
forms include more than one value for a particular key. This is
how lists and arrays are modeled. The validator does the right thing with
such form data. Specifically, when a key shows up multiple times in the form
data, a list is given to the validator.
# We can't use a normal dictionary because we'll have multiple keys, but
# the validator only wants to call .items() on the object.
>>> class MultiDict:
... def __init__(self, *params): self.values = list(params)
... def items(self): return iter(self.values)
>>> form_data = MultiDict(
... ('one', '1'),
... ('many', '3'),
... ('many', '4'),
... ('many', '5'),
... )
This is a validation function that ensures the value is a list.
>>> def must_be_list(value):
... if not isinstance(value, list):
... raise ValueError('not a list')
... return [int(item) for item in value]
This is a validation function that ensure the value is not a list.
>>> def must_be_scalar(value):
... if isinstance(value, list):
... raise ValueError('is a list')
... return int(value)
And a validator to pull it all together.
>>> validator = Validator(one=must_be_scalar, many=must_be_list)
>>> FakeRequest.params = form_data
>>> values = validator(FakeRequest)
>>> print(values['one'])
1
>>> print(values['many'])
[3, 4, 5]
The list values are guaranteed to be in the same order they show up in the form data.
>>> FakeRequest.params = MultiDict(
... ('one', '1'),
... ('many', '3'),
... ('many', '5'),
... ('many', '4'),
... )
>>> values = validator(FakeRequest)
>>> print(values['one'])
1
>>> print(values['many'])
[3, 5, 4]