Skip to content

Commit

Permalink
Merge pull request #36 from Trii/dev
Browse files Browse the repository at this point in the history
Support for webapp2 on Google App Engine
  • Loading branch information
sloria committed Mar 26, 2015
2 parents 3286396 + 53c449c commit b0bb636
Show file tree
Hide file tree
Showing 7 changed files with 259 additions and 0 deletions.
1 change: 1 addition & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ Credits
* @philtay <https://github.com/philtay>
* Andriy Yurchuk <https://github.com/Ch00k>
* Stas Sușcov <https://github.com/stas>
* Josh Johnston <https://github.com/Trii>
1 change: 1 addition & 0 deletions dev-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Django>=1.6.5
bottle>=0.12.3
tornado>=4.0
pyramid>=1.5.2
webapp2>=2.5.2

# Syntax checking
flake8==2.4.0
6 changes: 6 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,12 @@ webargs.pyramidparser
:inherited-members:


webargs.webapp2parser
---------------------

.. automodule:: webargs.webapp2parser
:inherited-members:

Project Info
============

Expand Down
1 change: 1 addition & 0 deletions examples/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ python-dateutil
Flask
bottle
tornado
webapp2
49 changes: 49 additions & 0 deletions examples/webapp2_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""A Hello, World! example using Webapp2 in a Google App Engine environment
Run the app:
$ python webapp2_example.py
Try the following with httpie (a cURL-like utility, http://httpie.org):
$ pip install httpie
$ http GET :5001/hello
$ http GET :5001/hello name==Ada
$ http POST :5001/hello_dict name=awesome
$ http POST :5001/hello_dict
"""

import webapp2

from webargs import Arg
from webargs.webapp2parser import use_args, use_kwargs

hello_args = {
'name': Arg(str, default='World')
}


class MainPage(webapp2.RequestHandler):

@use_args(hello_args)
def get_args(self, args):
# args is a dict of parsed items from hello_args
self.response.write('Hello, {name}!'.format(name=args['name']))

@use_kwargs(hello_args)
def get_kwargs(self, name=None):
self.response.write('Hello, {name}!'.format(name=name))

app = webapp2.WSGIApplication([
webapp2.Route(r'/hello', MainPage, handler_method='get_args'),
webapp2.Route(r'/hello_dict', MainPage, handler_method='get_kwargs'),
], debug=True)


if __name__ == '__main__':
from wsgiref.simple_server import make_server
httpd = make_server('', 5001, app)
print "Serving on port 5001..."
httpd.serve_forever()
123 changes: 123 additions & 0 deletions tests/test_webapp2parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# -*- coding: utf-8 -*-
"""Tests for the webapp2 parser"""
import urllib
import json

import pytest
from webargs.core import PY2
from .compat import text_type
from webargs import Arg, ValidationError

pytestmark = pytest.mark.skipif(not PY2, reason='webapp2 is only compatible with Python 2')

import webtest

if PY2:
# everything should be skipped via `pytestmark` here so it is OK
import webapp2
from webargs.webapp2parser import parser

hello_args = {
'name': Arg(text_type, default='World'),
}

hello_multiple = {
'name': Arg(multiple=True),
}

hello_validate = {
'num': Arg(int, validate=lambda n: n != 3, error="Houston, we've had a problem."),
}


def test_parse_querystring_args():
request = webapp2.Request.blank('/echo?name=Fred')
assert parser.parse(hello_args, req=request) == {'name': 'Fred'}


def test_parse_querystring_multiple():
expected = {'name': ['steve', 'Loria']}
request = webapp2.Request.blank('/echomulti?name=steve&name=Loria')
assert parser.parse(hello_multiple, req=request) == expected


def test_parse_form():
expected = {'name': 'Joe'}
request = webapp2.Request.blank('/echo', POST=expected)
assert parser.parse(hello_args, req=request) == expected


def test_parse_form_multiple():
expected = {'name': ['steve', 'Loria']}
request = webapp2.Request.blank('/echo', POST=urllib.urlencode(expected, doseq=True))
assert parser.parse(hello_multiple, req=request) == expected


def test_parsing_form_default():
request = webapp2.Request.blank('/echo', POST='')
assert parser.parse(hello_args, req=request) == {'name': 'World'}


def test_parse_json():
expected = {'name': 'Fred'}
request = webapp2.Request.blank('/echo', POST=json.dumps(expected), headers={'content-type': 'application/json'})
assert parser.parse(hello_args, req=request) == expected


def test_parse_json_default():
request = webapp2.Request.blank('/echo', POST='', headers={'content-type': 'application/json'})
assert parser.parse(hello_args, req=request) == {'name': 'World'}


def test_parsing_cookies():
# whitespace is not valid in a cookie name or value per RFC 6265
# http://tools.ietf.org/html/rfc6265#section-4.1.1
expected = {'name': 'Jean-LucPicard'}
response = webapp2.Response()
response.set_cookie('name', expected['name'])
request = webapp2.Request.blank('/', headers={'Cookie': response.headers['Set-Cookie']})
assert parser.parse(hello_args, req=request, locations=('cookies',)) == expected


def test_parsing_headers():
expected = {'name': 'Fred'}
request = webapp2.Request.blank('/', headers=expected)
assert parser.parse(hello_args, req=request, locations=('headers',)) == expected


def test_parse_files():
"""Test parsing file upload using WebTest since I don't know how to mock that using a webob.Request"""
class Handler(webapp2.RequestHandler):
@parser.use_args({'myfile': Arg(multiple=True)}, locations=('files',))
def post(self, args):
self.response.content_type = 'application/json'
_value = lambda f: f.getvalue().decode('utf-8')
data = dict((i.filename, _value(i.file)) for i in args['myfile'])
self.response.write(json.dumps(data))
app = webapp2.WSGIApplication([('/', Handler)])
testapp = webtest.TestApp(app)
payload = [('myfile', 'baz.txt', b'bar'), ('myfile', 'moo.txt', b'zoo')]
res = testapp.post('/', upload_files=payload)
assert res.json == {'baz.txt': 'bar', 'moo.txt': 'zoo'}


def test_exception_on_validation_error():
request = webapp2.Request.blank('/', POST={'num': '3'})
with pytest.raises(ValidationError):
parser.parse(hello_validate, req=request)


def test_validation_error_with_message():
request = webapp2.Request.blank('/', POST={'num': '3'})
with pytest.raises(ValidationError) as exc:
parser.parse(hello_validate, req=request)
assert "Houston, we've had a problem." in exc.value


def test_default_app_request():
"""Test that parser.parse uses the request from webapp2.get_request() if no request is passed"""
expected = {'name': 'Joe'}
request = webapp2.Request.blank('/echo', POST=expected)
app = webapp2.WSGIApplication([])
app.set_globals(app, request)
assert parser.parse(hello_args) == expected
78 changes: 78 additions & 0 deletions webargs/webapp2parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
"""Webapp2 request argument parsing module.
Example: ::
import webapp2
from webargs import Arg
from webargs.webobparser import use_args
hello_args = {
'name': Arg(str, default='World')
}
class MainPage(webapp2.RequestHandler):
@use_args(hello_args)
def get_args(self, args):
self.response.write('Hello, {name}!'.format(name=args['name']))
@use_args(hello_args, as_kwargs=True)
def get_kwargs(self, name=None):
self.response.write('Hello, {name}!'.format(name=name))
app = webapp2.WSGIApplication([
webapp2.Route(r'/hello', MainPage, handler_method='get_args'),
webapp2.Route(r'/hello_dict', MainPage, handler_method='get_kwargs'),
], debug=True)
"""
import logging


from webargs import core
import webapp2
import webapp2_extras.json
import webob.multidict

logger = logging.getLogger(__name__)


class Webapp2Parser(core.Parser):
"""webapp2 request argument parser."""

def parse_json(self, req, name, arg):
"""Pull a json value from the request."""
try:
json_data = webapp2_extras.json.decode(req.body)
return core.get_value(json_data, name, arg.multiple)
except ValueError:
return core.Missing

def parse_querystring(self, req, name, arg):
"""Pull a querystring value from the request."""
return core.get_value(req.GET, name, arg.multiple)

def parse_form(self, req, name, arg):
"""Pull a form value from the request."""
return core.get_value(req.POST, name, arg.multiple)

def parse_cookies(self, req, name, arg):
"""Pull the value from the cookiejar."""
return core.get_value(req.cookies, name, arg.multiple)

def parse_headers(self, req, name, arg):
"""Pull a value from the header data."""
return core.get_value(req.headers, name, arg.multiple)

def parse_files(self, req, name, arg):
"""Pull a file from the request."""
files = ((k, v) for k, v in req.POST.items() if hasattr(v, 'file'))
return core.get_value(webob.multidict.MultiDict(files), name, arg.multiple)

def parse(self, argmap, req=None, locations=None, validate=None, force_all=False):
"""Wrap :meth:`core.Parser.parse` to inject the active :class:`webapp2.Request` in"""
req = req or webapp2.get_request()
return super(Webapp2Parser, self).parse(argmap, req, locations, validate, force_all)

parser = Webapp2Parser()
use_args = parser.use_args
use_kwargs = parser.use_kwargs

0 comments on commit b0bb636

Please sign in to comment.