Modified:
/project/app/app.yaml
/tests/resources/alternative_routing.py
/tests/test_config.py
/tipfy/app.py
/tipfy/handler.py
/tipfy/local.py
/tipfy/routing.py
/tipfy/testing.py
=======================================
--- /project/app/app.yaml Sun Mar 27 04:41:08 2011
+++ /project/app/app.yaml Thu Mar 31 07:24:20 2011
@@ -3,12 +3,17 @@
runtime: python
api_version: 1
+builtins:
+- appstats: on
+- datastore_admin: on
+- remote_api: on
+
inbound_services:
- warmup
error_handlers:
- file: templates/error_handlers/default_error.html
-
+
- error_code: over_quota
file: templates/error_handlers/over_quota.html
@@ -18,23 +23,22 @@
- error_code: timeout
file: templates/error_handlers/timeout.html
-builtins:
-- appstats: on
-- datastore_admin: on
-- remote_api: on
-
handlers:
-- url: /favicon\.ico
- static_files: static/favicon.ico
- upload: static/favicon.ico
- mime_type: image/vnd.microsoft.icon
-
-- url: /robots\.txt
- static_files: static/robots.txt
- upload: static/robots.txt
-
-- url: /static
- static_dir: static
+#- url: /favicon\.ico
+# static_files: static/favicon.ico
+# upload: static/favicon.ico
+# mime_type: image/vnd.microsoft.icon
+
+#- url: /robots\.txt
+# static_files: static/robots.txt
+# upload: static/robots.txt
+
+#- url: /static
+# static_dir: static
+
+- url: /static/(.*)
+ static_files: static/\1
+ upload: static/(.*)
- url: /_ah/queue/deferred
script: main.py
=======================================
--- /tests/resources/alternative_routing.py Sun Mar 20 04:48:01 2011
+++ /tests/resources/alternative_routing.py Thu Mar 31 07:24:20 2011
@@ -19,13 +19,13 @@
return Response('other-bar')
-def home(app, request):
+def home(request):
return 'home'
-def foo(app, request):
+def foo(request):
return 'foo'
-def bar(app, request):
+def bar(request):
return 'bar'
=======================================
--- /tests/test_config.py Sun Oct 3 20:20:14 2010
+++ /tests/test_config.py Thu Mar 31 07:24:20 2011
@@ -2,6 +2,8 @@
"""
Tests for tipfy config
"""
+from __future__ import with_statement
+
import unittest
from tipfy import Tipfy, RequestHandler, REQUIRED_VALUE
@@ -259,16 +261,16 @@
def test_request_handler_get_config(self):
app = Tipfy()
-
- handler = RequestHandler(app, None)
-
-
self.assertEqual(handler.get_config('resources.i18n', 'locale'), 'en_US')
-
self.assertEqual(handler.get_config('resources.i18n', 'locale', 'foo'), 'en_US')
- self.assertEqual(handler.get_config('resources.i18n'), {
- 'locale': 'en_US',
- 'timezone': 'America/Chicago',
- 'required': REQUIRED_VALUE,
- })
+ with app.get_test_context() as request:
+ handler = RequestHandler(request)
+
+
self.assertEqual(handler.get_config('resources.i18n', 'locale'), 'en_US')
+
self.assertEqual(handler.get_config('resources.i18n', 'locale', 'foo'), 'en_US')
+ self.assertEqual(handler.get_config('resources.i18n'), {
+ 'locale': 'en_US',
+ 'timezone': 'America/Chicago',
+ 'required': REQUIRED_VALUE,
+ })
class TestLoadConfigGetItem(unittest.TestCase):
=======================================
--- /tipfy/app.py Wed Mar 30 06:54:43 2011
+++ /tipfy/app.py Thu Mar 31 07:24:20 2011
@@ -8,6 +8,8 @@
:copyright: 2011 by tipfy.org.
:license: BSD, see LICENSE.txt for more details.
"""
+from __future__ import with_statement
+
import logging
import os
import urlparse
@@ -23,7 +25,7 @@
from . import default_config
from .config import Config, REQUIRED_VALUE
-from .local import current_app, current_handler, local
+from .local import current_app, current_handler, get_request, local
from .json import json_decode
from .routing import Router, Rule
@@ -81,6 +83,43 @@
default_mimetype = 'text/html'
+class RequestContext(object):
+ """Sets and releases the context locals used during a request.
+
+ User meth:`App.get_test_context` to build a `RequestContext` for
+ testing purposes.
+ """
+ def __init__(self, app, environ):
+ """Initializes the request context.
+
+ :param app:
+ An :class:`App` instance.
+ :param environ:
+ A WSGI environment.
+ """
+ self.app = app
+ self.environ = environ
+
+ def __enter__(self):
+ """Enters the request context.
+
+ :returns:
+ A :class:`Request` instance.
+ """
+ local.request = request = self.app.request_class(self.environ)
+ local.app = request.app = self.app
+ return request
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ """Exits the request context.
+
+ This will release the context locals except if an exception is
caught
+ in debug mode. In this case the locals are kept to be inspected.
+ """
+ if exc_type is None or not self.app.debug:
+ local.__release_local__()
+
+
class App(object):
"""The WSGI application."""
# Allowed request methods.
@@ -116,12 +155,11 @@
logging.getLogger().setLevel(logging.DEBUG)
def __call__(self, environ, start_response):
- """Shortcut for :meth:`App.wsgi_app`."""
- local.app = self
+ """Called when a request comes in."""
if self.debug and self.config['tipfy']['enable_debugger']:
return self._debugged_wsgi_app(environ, start_response)
- return self.wsgi_app(environ, start_response)
+ return self.dispatch(environ, start_response)
def dispatch(self, environ, start_response):
"""This is the actual WSGI application. This is not implemented in
@@ -132,7 +170,7 @@
It's a better idea to do this instead::
- app.wsgi_app = MyMiddleware(app.wsgi_app)
+ app.dispatch = MyMiddleware(app.dispatch)
Then you still have the original application object around and
can continue to call methods on it.
@@ -145,35 +183,28 @@
A callable accepting a status code, a list of headers and an
optional exception context to start the response.
"""
- cleanup = True
- try:
- local.request = request = self.request_class(environ)
- request.app = self
- if request.method not in self.allowed_methods:
- abort(501)
-
- rv = self.router.dispatch(request)
- response = self.make_response(request, rv)
- except Exception, e:
+ with RequestContext(self, environ) as request:
try:
- response = self.handle_exception(request, e)
- except HTTPException, e:
- response = self.make_response(request, e)
- except Exception, e:
- if self.debug:
- cleanup = not self.config['tipfy']['enable_debugger']
- raise
-
- # We only log unhandled non-HTTP exceptions. Users should
- # take care of logging in custom error handlers.
- logging.exception(e)
- rv = werkzeug.exceptions.InternalServerError()
+ if request.method not in self.allowed_methods:
+ abort(501)
+
+ rv = self.router.dispatch(request)
response = self.make_response(request, rv)
- finally:
- if cleanup:
- local.__release_local__()
-
- return response(environ, start_response)
+ except Exception, e:
+ try:
+ rv = self.handle_exception(request, e)
+ response = self.make_response(request, rv)
+ except HTTPException, e:
+ response = self.make_response(request, e)
+ except Exception, e:
+ if self.debug:
+ raise
+
+ logging.exception(e)
+ rv = werkzeug.exceptions.InternalServerError()
+ response = self.make_response(request, rv)
+
+ return response(environ, start_response)
def handle_exception(self, request, exception):
"""Handles an exception. To set app-wide error handlers, define
them
@@ -221,7 +252,7 @@
raise
request.exception = exception
- rv = handler(request.app, request)
+ rv = handler(request)
if not isinstance(rv, werkzeug.wrappers.BaseResponse):
if hasattr(rv, '__call__'):
# If it is a callable but not a response, we call it again.
@@ -286,9 +317,23 @@
:returns:
A ``werkzeug.Client`` with the WSGI application wrapped for
tests.
"""
- from werkzeug import Client
+ from werkzeug.test import Client
return Client(self, self.response_class, use_cookies=True)
+ def get_test_context(self, *args, **kwargs):
+ """Creates a test client for this application.
+
+ :param args:
+ Positional arguments to construct a
`werkzeug.test.EnvironBuilder`.
+ :param kwargs:
+ Keyword arguments to construct a
`werkzeug.test.EnvironBuilder`.
+ :returns:
+ A :class:``RequestContext`` instance.
+ """
+ from werkzeug.test import EnvironBuilder
+ builder = EnvironBuilder(*args, **kwargs)
+ return RequestContext(self, builder.get_environ())
+
def get_test_handler(self, *args, **kwargs):
"""Returns a handler set as a current handler for testing purposes.
@@ -324,7 +369,7 @@
def _debugged_wsgi_app(self):
"""Returns the WSGI app wrapped by an interactive debugger."""
from tipfy.debugger import DebuggedApplication
- return DebuggedApplication(self.wsgi_app, evalex=True)
+ return DebuggedApplication(self.dispatch, evalex=True)
@werkzeug.utils.cached_property
def auth_store_class(self):
@@ -383,7 +428,7 @@
if location.startswith(('.', '/')):
# Make it absolute.
- location = urlparse.urljoin(local.request.url, location)
+ location = urlparse.urljoin(get_request().url, location)
display_location = location
if isinstance(location, unicode):
=======================================
--- /tipfy/handler.py Wed Mar 30 06:54:43 2011
+++ /tipfy/handler.py Thu Mar 31 07:24:20 2011
@@ -35,16 +35,24 @@
A Tipfy-compatible handler can be implemented using only these two
methods.
"""
- def __init__(self, app, request):
+ def __init__(self, request, app=None):
"""Initializes the handler.
- :param app:
- A :class:`tipfy.app.App` instance.
:param request:
A :class:`Request` instance.
+ :param app:
+ A :class:`tipfy.app.App` instance.
"""
- self.app = app
- self.request = request
+ if app:
+ # App argument is kept for backwards compatibility. Previously
we
+ # called passing (app, request) but because view functions are
now
+ # supported only request is passed and app is an attribute of
the
+ # request object.
+ self.app = request
+ self.request = app
+ else:
+ self.request = request
+
# A context for shared data, e.g., template variables.
self.context = {}
@@ -78,6 +86,15 @@
except Exception, e:
return self.handle_exception(exception=e)
+ @werkzeug.utils.cached_property
+ def app(self):
+ """The current WSGI app instance.
+
+ :returns:
+ The current WSGI app instance.
+ """
+ return self.request.app
+
@werkzeug.utils.cached_property
def auth(self):
"""The auth store which provides access to the authenticated user
and
=======================================
--- /tipfy/local.py Wed Mar 30 06:54:43 2011
+++ /tipfy/local.py Thu Mar 31 07:24:20 2011
@@ -13,12 +13,6 @@
#: Context-local.
local = werkzeug.local.Local()
-#: A proxy to the current :class:`tipfy.app.App` instance.
-app = local('app')
-
-#: A proxy to the current :class:`tipfy.app.Request` instance.
-request = local('request')
-
def get_app():
"""Returns the current WSGI app instance.
@@ -28,6 +22,7 @@
"""
return local.app
+
def get_request():
"""Returns the current request instance.
@@ -48,4 +43,4 @@
#: there to dynamically get the currently active handler.
current_handler = local('current_handler')
#: Same as current_handler, only for the active WSGI app.
-current_app = app
+current_app = local('app')
=======================================
--- /tipfy/routing.py Wed Mar 30 06:54:43 2011
+++ /tipfy/routing.py Thu Mar 31 07:24:20 2011
@@ -13,7 +13,7 @@
Rule as BaseRule, RuleFactory, Subdomain, Submount)
from werkzeug.wrappers import BaseResponse
-from .local import local
+from .local import get_request, local
class Router(object):
@@ -90,7 +90,7 @@
rule.handler = handler = self.handlers[handler]
- rv = local.current_handler = handler(request.app, request)
+ rv = local.current_handler = handler(request)
if not isinstance(rv, BaseResponse) and hasattr(rv, '__call__'):
# If it is a callable but not a response, we call it again.
rv = rv()
@@ -406,7 +406,7 @@
.. seealso:: :meth:`Router.url_for`.
"""
- request = local.request
+ request = get_request()
return request.rule_adapter.url_for(request, _name, kwargs)
=======================================
--- /tipfy/testing.py Wed Mar 30 05:57:11 2011
+++ /tipfy/testing.py Thu Mar 31 07:24:20 2011
@@ -64,6 +64,8 @@
self.request = app.request_class.from_values(*args, **kwargs)
def __enter__(self):
+ local.request = self.request
+ local.app = self.request.app = self.app
if self.handler is not None:
local.current_handler = self.handler
else:
@@ -75,7 +77,7 @@
else:
handler_class = self.handler_class
- local.current_handler = handler_class(self.app, self.request)
+ local.current_handler = handler_class(self.request)
return local.current_handler