I like the idea but disagree with adding these functions to the global namespace. What about adding them to the Storage class? Pseudocode: >>> request.vars.query = ['one','two'] >>> q = request.vars.getfirst('query') >>> 'one'
Same for getlast and getall. Thoughts? On Aug 21, 11:26 am, Kevin <extemporalgen...@gmail.com> wrote: > 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 > getallfunctionsso 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 (stillproposedpatch) is athttp://pastebin.com/8f9z4k6J. > Further examples of API use (in doctest format) are in thepatch. > Also, I discovered that the *old*patchwould have broken on strings > (so don't use thatpatch), 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 <mdipie...@cs.depaul.edu> 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 <extemporalgen...@gmail.com> wrote: > > > > Hi, > > > > this is aproposedpatchtoaddglobalfunctionsfor 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. > > > > Thefunctionsare 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 > > >functionsare 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. > > > >Patchcan 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. > >