Yeah, use case might be as follows (let me know if there's already a
web2py pattern for this -- I just downloaded the framework a couple
days ago):
Perhaps a legacy RESTful API that is being porting to web2py -- it
uses HTTP conventions to query the server, and returns type-
appropriate content based on the request URL's file extension.
Thus the following would both work as expected when passed to the ,
/search?term=sir+robin&limit=3
/search.json?term=sir+robin&limit=3
def search():
# We're assuming it's a string -- but it could be a list
# point of failure.
if not re.match('\d+$', request.vars.limit):
raise HTTP(400, "limit must be an integer")
term = request.vars.term
for row in db(db.topics.term.contains(term)).select(db.topics.ALL,
limitby=(0, int(limit))):
pass
However, the following URL would likely raise a TypeError (if not in
web2py code, at least in commonly-seen naive code):
/search.json?term=sir+robin&term=brave&limit=3
The boilerplate way of fixing the function would be as follows:
def search():
limit = request.vars.limit
if not isinstance(limit, basestring):
limit = limit[0]
if not re.match('\d+$', limit):
raise HTTP(400, "limit must be an integer")
term = request.vars.term
if not isinstance(term, basestring):
term = term[0]
for row in db(db.topics.term.contains(term)).select(db.topics.ALL,
limitby=(0, int(limit))):
pass
The shorthand way of fixing it would be as follows -- note, it also
trivially extends it to allow multiple terms which are AND'd together
(in fewer lines than the boiler-plate example):
def search():
limit = getfirst(request.vars.limit)
if not re.match('\d+$', limit):
raise HTTP(400, "limit must be an integer")
query = db
for term in getall(request.vars.term):
query = query(db.topics.term.contains(term))
for row in query.select(db.topics.ALL, limitby=(0, int(limit))):
pass
Note that in these examples, I've modified the getfirst, getlast, and
getall functions so that they're specifically designed to work with
object attributes instead of dictionary keys, which allows for the
shorter syntax you see here (which is much more useful for working
with Storage objects, which as of 1.83.2 return None for non-existant
attributes). Although it's less flexible, the abbreviated syntax
makes them much more handy for working with request.vars,
request.get_vars, and request.post_vars (from which you only
reasonably expect either lists, or non-lists). So in essence, these
act as 'scalarify' and 'listify' functions.
Note that "getall(request.vars.non_existant)" will return [] instead
of [None] (so it's not a 'strict' listify function).
The revised (still proposed patch) is at http://pastebin.com/8f9z4k6J.
Further examples of API use (in doctest format) are in the patch.
Also, I discovered that the *old* patch would have broken on strings
(so don't use that patch), which is what the code would've been
dealing about 100% of the time in real-world cases.
On Aug 21, 6:22 am, mdipierro <[email protected]> wrote:
> I never needed this but I have no opposition to include them.
> Could you provide a use case?
> What do other people think?
>
> On Aug 21, 12:27 am, Kevin <[email protected]> wrote:
>
> > Hi,
>
> > this is a proposed patch to add global functions for accessing values
> > from (in particular) request.vars and friends (any dictionary-like
> > object will work) in a way that (safely) satisfies the assumption that
> > the input vars for a given key are either singletons or lists.
>
> > The functions are rather simple:
>
> > Given the request: /a/b/c?x=abc
> > getfirst(request.vars, 'x') -> 'abc'
> > getlast(request.vars, 'x') -> 'abc'
> > getall(request.vars, 'x') -> ['abc']
>
> > Given the request: /a/b/c?x=abc&x=def
> > getfirst(request.vars, 'x') -> 'abc'
> > getlast(request.vars, 'x') -> 'def'
> > getall(request.vars, 'x') -> ['abc', 'def']
>
> > getall(request.vars, 'y') -> None
> > getall(request.vars, 'y') or [] -> []
>
> > If there is anything like this already, I certainly will retract my
> > suggestion. The potentially controversial parts are that the
> > functions are defined in gluon.utils (I couldn't find a more logical
> > place to put them, and it makes no difference to me where they end
> > up), and they're loaded into the request environment, just like the
> > html helpers.
>
> > Patch can be found at:http://pastebin.com/g6Vs9PrU
>
> > Background/motivation:
>
> > This function group is inspired by the behavior of <http://
> > pythonpaste.org/webob/reference.html#query-post-variables> and similar
> > functionality that other frameworks provide, and would be particularly
> > useful in cases where the client-side code is not managed by something
> > like web2py's FORM interface -- as of version 1.83.2, web2py prepares
> > a Storage instance such that:
>
> > /a/b/c?x=5 -> request.vars.x == '5'
> > /a/b/c?x=5&x=abc -> request.vars.x == ['5', 'abc']
>
> > This could lead to naive code like the following to fail with some
> > simple request fakery:
>
> > if request.vars.search.upper().startswith('FUZZY'): pass # some real
> > code here
>
> > It's possible that this kind of fakery could also lead to many of the
> > web2py validators failing in common cases (though I haven't looked
> > into that much).
>
> > However, it is often allowable that the first (or last) value passed
> > is authoritative, leading to a more robust system.
>
>