-
-
Notifications
You must be signed in to change notification settings - Fork 155
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #36 from Trii/dev
Support for webapp2 on Google App Engine
- Loading branch information
Showing
7 changed files
with
259 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,3 +2,4 @@ python-dateutil | |
Flask | ||
bottle | ||
tornado | ||
webapp2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |