mirror of https://github.com/pallets/flask.git
Added flask.copy_current_request_context which simplies working with greenlets
This commit is contained in:
parent
0faed95385
commit
097353695e
2
CHANGES
2
CHANGES
|
@ -52,6 +52,8 @@ Release date to be decided.
|
||||||
- Changed logic for picking defaults for cookie values from sessions
|
- Changed logic for picking defaults for cookie values from sessions
|
||||||
to work better with Google Chrome.
|
to work better with Google Chrome.
|
||||||
- Added `message_flashed` signal that simplifies flashing testing.
|
- Added `message_flashed` signal that simplifies flashing testing.
|
||||||
|
- Added support for copying of request contexts for better working with
|
||||||
|
greenlets.
|
||||||
|
|
||||||
Version 0.9
|
Version 0.9
|
||||||
-----------
|
-----------
|
||||||
|
|
|
@ -280,6 +280,8 @@ Useful Functions and Classes
|
||||||
|
|
||||||
.. autofunction:: has_request_context
|
.. autofunction:: has_request_context
|
||||||
|
|
||||||
|
.. autofunction:: copy_current_request_context
|
||||||
|
|
||||||
.. autofunction:: has_app_context
|
.. autofunction:: has_app_context
|
||||||
|
|
||||||
.. autofunction:: url_for
|
.. autofunction:: url_for
|
||||||
|
|
|
@ -26,7 +26,7 @@ from .helpers import url_for, flash, send_file, send_from_directory, \
|
||||||
from .globals import current_app, g, request, session, _request_ctx_stack, \
|
from .globals import current_app, g, request, session, _request_ctx_stack, \
|
||||||
_app_ctx_stack
|
_app_ctx_stack
|
||||||
from .ctx import has_request_context, has_app_context, \
|
from .ctx import has_request_context, has_app_context, \
|
||||||
after_this_request
|
after_this_request, copy_current_request_context
|
||||||
from .module import Module
|
from .module import Module
|
||||||
from .blueprints import Blueprint
|
from .blueprints import Blueprint
|
||||||
from .templating import render_template, render_template_string
|
from .templating import render_template, render_template_string
|
||||||
|
|
|
@ -1821,3 +1821,9 @@ class Flask(_PackageBoundObject):
|
||||||
def __call__(self, environ, start_response):
|
def __call__(self, environ, start_response):
|
||||||
"""Shortcut for :attr:`wsgi_app`."""
|
"""Shortcut for :attr:`wsgi_app`."""
|
||||||
return self.wsgi_app(environ, start_response)
|
return self.wsgi_app(environ, start_response)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<%s %r>' % (
|
||||||
|
self.__class__.__name__,
|
||||||
|
self.name,
|
||||||
|
)
|
||||||
|
|
77
flask/ctx.py
77
flask/ctx.py
|
@ -10,6 +10,7 @@
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
from functools import update_wrapper
|
||||||
|
|
||||||
from werkzeug.exceptions import HTTPException
|
from werkzeug.exceptions import HTTPException
|
||||||
|
|
||||||
|
@ -19,7 +20,24 @@ from .module import blueprint_is_module
|
||||||
|
|
||||||
class _AppCtxGlobals(object):
|
class _AppCtxGlobals(object):
|
||||||
"""A plain object."""
|
"""A plain object."""
|
||||||
pass
|
|
||||||
|
def __getitem__(self, name):
|
||||||
|
try:
|
||||||
|
return getattr(self, name)
|
||||||
|
except AttributeError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def __setitem__(self, name, value):
|
||||||
|
setattr(self, name, value)
|
||||||
|
|
||||||
|
def __delitem__(self, name, value):
|
||||||
|
delattr(self, name, value)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
top = _app_ctx_stack.top
|
||||||
|
if top is not None:
|
||||||
|
return '<flask.g of %r>' % top.app.name
|
||||||
|
return object.__repr__(self)
|
||||||
|
|
||||||
|
|
||||||
def after_this_request(f):
|
def after_this_request(f):
|
||||||
|
@ -47,6 +65,41 @@ def after_this_request(f):
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
def copy_current_request_context(f):
|
||||||
|
"""A helper function that decorates a function to retain the current
|
||||||
|
request context. This is useful when working with greenlets. The moment
|
||||||
|
the function is decorated a copy of the request context is created and
|
||||||
|
then pushed when the function is called.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
import gevent
|
||||||
|
from flask import copy_current_request_context
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
@copy_current_request_context
|
||||||
|
def do_some_work():
|
||||||
|
# do some work here, it can access flask.request like you
|
||||||
|
# would otherwise in the view function.
|
||||||
|
...
|
||||||
|
gevent.spawn(do_some_work)
|
||||||
|
return 'Regular response'
|
||||||
|
|
||||||
|
.. versionadded:: 0.10
|
||||||
|
"""
|
||||||
|
top = _request_ctx_stack.top
|
||||||
|
if top is None:
|
||||||
|
raise RuntimeError('This decorator can only be used at local scopes '
|
||||||
|
'when a request context is on the stack. For instance within '
|
||||||
|
'view functions.')
|
||||||
|
reqctx = top.copy()
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
with reqctx:
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
return update_wrapper(wrapper, f)
|
||||||
|
|
||||||
|
|
||||||
def has_request_context():
|
def has_request_context():
|
||||||
"""If you have code that wants to test if a request context is there or
|
"""If you have code that wants to test if a request context is there or
|
||||||
not this function can be used. For instance, you may want to take advantage
|
not this function can be used. For instance, you may want to take advantage
|
||||||
|
@ -161,9 +214,11 @@ class RequestContext(object):
|
||||||
that situation, otherwise your unittests will leak memory.
|
that situation, otherwise your unittests will leak memory.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, app, environ):
|
def __init__(self, app, environ, request=None):
|
||||||
self.app = app
|
self.app = app
|
||||||
self.request = app.request_class(environ)
|
if request is None:
|
||||||
|
request = app.request_class(environ)
|
||||||
|
self.request = request
|
||||||
self.url_adapter = app.create_url_adapter(self.request)
|
self.url_adapter = app.create_url_adapter(self.request)
|
||||||
self.flashes = None
|
self.flashes = None
|
||||||
self.session = None
|
self.session = None
|
||||||
|
@ -202,6 +257,20 @@ class RequestContext(object):
|
||||||
g = property(_get_g, _set_g)
|
g = property(_get_g, _set_g)
|
||||||
del _get_g, _set_g
|
del _get_g, _set_g
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
"""Creates a copy of this request context with the same request object.
|
||||||
|
This can be used to move a request context to a different greenlet.
|
||||||
|
Because the actual request object is the same this cannot be used to
|
||||||
|
move a request context to a different thread unless access to the
|
||||||
|
request object is locked.
|
||||||
|
|
||||||
|
.. versionadded:: 0.10
|
||||||
|
"""
|
||||||
|
return self.__class__(self.app,
|
||||||
|
environ=self.request.environ,
|
||||||
|
request=self.request
|
||||||
|
)
|
||||||
|
|
||||||
def match_request(self):
|
def match_request(self):
|
||||||
"""Can be overridden by a subclass to hook into the matching
|
"""Can be overridden by a subclass to hook into the matching
|
||||||
of the request.
|
of the request.
|
||||||
|
@ -299,5 +368,5 @@ class RequestContext(object):
|
||||||
self.__class__.__name__,
|
self.__class__.__name__,
|
||||||
self.request.url,
|
self.request.url,
|
||||||
self.request.method,
|
self.request.method,
|
||||||
self.app.name
|
self.app.name,
|
||||||
)
|
)
|
||||||
|
|
|
@ -666,19 +666,6 @@ class BasicFunctionalityTestCase(FlaskTestCase):
|
||||||
else:
|
else:
|
||||||
self.fail('Expected exception')
|
self.fail('Expected exception')
|
||||||
|
|
||||||
def test_teardown_on_pop(self):
|
|
||||||
buffer = []
|
|
||||||
app = flask.Flask(__name__)
|
|
||||||
@app.teardown_request
|
|
||||||
def end_of_request(exception):
|
|
||||||
buffer.append(exception)
|
|
||||||
|
|
||||||
ctx = app.test_request_context()
|
|
||||||
ctx.push()
|
|
||||||
self.assert_equal(buffer, [])
|
|
||||||
ctx.pop()
|
|
||||||
self.assert_equal(buffer, [None])
|
|
||||||
|
|
||||||
def test_response_creation(self):
|
def test_response_creation(self):
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
@app.route('/unicode')
|
@app.route('/unicode')
|
||||||
|
@ -821,53 +808,6 @@ class BasicFunctionalityTestCase(FlaskTestCase):
|
||||||
self.assert_equal(repr(flask.g), '<LocalProxy unbound>')
|
self.assert_equal(repr(flask.g), '<LocalProxy unbound>')
|
||||||
self.assertFalse(flask.g)
|
self.assertFalse(flask.g)
|
||||||
|
|
||||||
def test_proper_test_request_context(self):
|
|
||||||
app = flask.Flask(__name__)
|
|
||||||
app.config.update(
|
|
||||||
SERVER_NAME='localhost.localdomain:5000'
|
|
||||||
)
|
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
def index():
|
|
||||||
return None
|
|
||||||
|
|
||||||
@app.route('/', subdomain='foo')
|
|
||||||
def sub():
|
|
||||||
return None
|
|
||||||
|
|
||||||
with app.test_request_context('/'):
|
|
||||||
self.assert_equal(flask.url_for('index', _external=True), 'http://localhost.localdomain:5000/')
|
|
||||||
|
|
||||||
with app.test_request_context('/'):
|
|
||||||
self.assert_equal(flask.url_for('sub', _external=True), 'http://foo.localhost.localdomain:5000/')
|
|
||||||
|
|
||||||
try:
|
|
||||||
with app.test_request_context('/', environ_overrides={'HTTP_HOST': 'localhost'}):
|
|
||||||
pass
|
|
||||||
except Exception, e:
|
|
||||||
self.assert_(isinstance(e, ValueError))
|
|
||||||
self.assert_equal(str(e), "the server name provided " +
|
|
||||||
"('localhost.localdomain:5000') does not match the " + \
|
|
||||||
"server name from the WSGI environment ('localhost')")
|
|
||||||
|
|
||||||
try:
|
|
||||||
app.config.update(SERVER_NAME='localhost')
|
|
||||||
with app.test_request_context('/', environ_overrides={'SERVER_NAME': 'localhost'}):
|
|
||||||
pass
|
|
||||||
except ValueError, e:
|
|
||||||
raise ValueError(
|
|
||||||
"No ValueError exception should have been raised \"%s\"" % e
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
app.config.update(SERVER_NAME='localhost:80')
|
|
||||||
with app.test_request_context('/', environ_overrides={'SERVER_NAME': 'localhost:80'}):
|
|
||||||
pass
|
|
||||||
except ValueError, e:
|
|
||||||
raise ValueError(
|
|
||||||
"No ValueError exception should have been raised \"%s\"" % e
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_test_app_proper_environ(self):
|
def test_test_app_proper_environ(self):
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
app.config.update(
|
app.config.update(
|
||||||
|
@ -1127,53 +1067,6 @@ class BasicFunctionalityTestCase(FlaskTestCase):
|
||||||
self.assert_(flask._app_ctx_stack.top is None)
|
self.assert_(flask._app_ctx_stack.top is None)
|
||||||
|
|
||||||
|
|
||||||
class ContextTestCase(FlaskTestCase):
|
|
||||||
|
|
||||||
def test_context_binding(self):
|
|
||||||
app = flask.Flask(__name__)
|
|
||||||
@app.route('/')
|
|
||||||
def index():
|
|
||||||
return 'Hello %s!' % flask.request.args['name']
|
|
||||||
@app.route('/meh')
|
|
||||||
def meh():
|
|
||||||
return flask.request.url
|
|
||||||
|
|
||||||
with app.test_request_context('/?name=World'):
|
|
||||||
self.assert_equal(index(), 'Hello World!')
|
|
||||||
with app.test_request_context('/meh'):
|
|
||||||
self.assert_equal(meh(), 'http://localhost/meh')
|
|
||||||
self.assert_(flask._request_ctx_stack.top is None)
|
|
||||||
|
|
||||||
def test_context_test(self):
|
|
||||||
app = flask.Flask(__name__)
|
|
||||||
self.assert_(not flask.request)
|
|
||||||
self.assert_(not flask.has_request_context())
|
|
||||||
ctx = app.test_request_context()
|
|
||||||
ctx.push()
|
|
||||||
try:
|
|
||||||
self.assert_(flask.request)
|
|
||||||
self.assert_(flask.has_request_context())
|
|
||||||
finally:
|
|
||||||
ctx.pop()
|
|
||||||
|
|
||||||
def test_manual_context_binding(self):
|
|
||||||
app = flask.Flask(__name__)
|
|
||||||
@app.route('/')
|
|
||||||
def index():
|
|
||||||
return 'Hello %s!' % flask.request.args['name']
|
|
||||||
|
|
||||||
ctx = app.test_request_context('/?name=World')
|
|
||||||
ctx.push()
|
|
||||||
self.assert_equal(index(), 'Hello World!')
|
|
||||||
ctx.pop()
|
|
||||||
try:
|
|
||||||
index()
|
|
||||||
except RuntimeError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
self.assert_(0, 'expected runtime error')
|
|
||||||
|
|
||||||
|
|
||||||
class SubdomainTestCase(FlaskTestCase):
|
class SubdomainTestCase(FlaskTestCase):
|
||||||
|
|
||||||
def test_basic_support(self):
|
def test_basic_support(self):
|
||||||
|
@ -1251,6 +1144,5 @@ class SubdomainTestCase(FlaskTestCase):
|
||||||
def suite():
|
def suite():
|
||||||
suite = unittest.TestSuite()
|
suite = unittest.TestSuite()
|
||||||
suite.addTest(unittest.makeSuite(BasicFunctionalityTestCase))
|
suite.addTest(unittest.makeSuite(BasicFunctionalityTestCase))
|
||||||
suite.addTest(unittest.makeSuite(ContextTestCase))
|
|
||||||
suite.addTest(unittest.makeSuite(SubdomainTestCase))
|
suite.addTest(unittest.makeSuite(SubdomainTestCase))
|
||||||
return suite
|
return suite
|
||||||
|
|
|
@ -0,0 +1,187 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
flask.testsuite.reqctx
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Tests the request context.
|
||||||
|
|
||||||
|
:copyright: (c) 2012 by Armin Ronacher.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
import flask
|
||||||
|
import unittest
|
||||||
|
try:
|
||||||
|
from greenlet import greenlet
|
||||||
|
except ImportError:
|
||||||
|
greenlet = None
|
||||||
|
from flask.testsuite import FlaskTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class RequestContextTestCase(FlaskTestCase):
|
||||||
|
|
||||||
|
def test_teardown_on_pop(self):
|
||||||
|
buffer = []
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
@app.teardown_request
|
||||||
|
def end_of_request(exception):
|
||||||
|
buffer.append(exception)
|
||||||
|
|
||||||
|
ctx = app.test_request_context()
|
||||||
|
ctx.push()
|
||||||
|
self.assert_equal(buffer, [])
|
||||||
|
ctx.pop()
|
||||||
|
self.assert_equal(buffer, [None])
|
||||||
|
|
||||||
|
def test_proper_test_request_context(self):
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
app.config.update(
|
||||||
|
SERVER_NAME='localhost.localdomain:5000'
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return None
|
||||||
|
|
||||||
|
@app.route('/', subdomain='foo')
|
||||||
|
def sub():
|
||||||
|
return None
|
||||||
|
|
||||||
|
with app.test_request_context('/'):
|
||||||
|
self.assert_equal(flask.url_for('index', _external=True), 'http://localhost.localdomain:5000/')
|
||||||
|
|
||||||
|
with app.test_request_context('/'):
|
||||||
|
self.assert_equal(flask.url_for('sub', _external=True), 'http://foo.localhost.localdomain:5000/')
|
||||||
|
|
||||||
|
try:
|
||||||
|
with app.test_request_context('/', environ_overrides={'HTTP_HOST': 'localhost'}):
|
||||||
|
pass
|
||||||
|
except Exception, e:
|
||||||
|
self.assert_(isinstance(e, ValueError))
|
||||||
|
self.assert_equal(str(e), "the server name provided " +
|
||||||
|
"('localhost.localdomain:5000') does not match the " + \
|
||||||
|
"server name from the WSGI environment ('localhost')")
|
||||||
|
|
||||||
|
try:
|
||||||
|
app.config.update(SERVER_NAME='localhost')
|
||||||
|
with app.test_request_context('/', environ_overrides={'SERVER_NAME': 'localhost'}):
|
||||||
|
pass
|
||||||
|
except ValueError, e:
|
||||||
|
raise ValueError(
|
||||||
|
"No ValueError exception should have been raised \"%s\"" % e
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
app.config.update(SERVER_NAME='localhost:80')
|
||||||
|
with app.test_request_context('/', environ_overrides={'SERVER_NAME': 'localhost:80'}):
|
||||||
|
pass
|
||||||
|
except ValueError, e:
|
||||||
|
raise ValueError(
|
||||||
|
"No ValueError exception should have been raised \"%s\"" % e
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_context_binding(self):
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return 'Hello %s!' % flask.request.args['name']
|
||||||
|
@app.route('/meh')
|
||||||
|
def meh():
|
||||||
|
return flask.request.url
|
||||||
|
|
||||||
|
with app.test_request_context('/?name=World'):
|
||||||
|
self.assert_equal(index(), 'Hello World!')
|
||||||
|
with app.test_request_context('/meh'):
|
||||||
|
self.assert_equal(meh(), 'http://localhost/meh')
|
||||||
|
self.assert_(flask._request_ctx_stack.top is None)
|
||||||
|
|
||||||
|
def test_context_test(self):
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
self.assert_(not flask.request)
|
||||||
|
self.assert_(not flask.has_request_context())
|
||||||
|
ctx = app.test_request_context()
|
||||||
|
ctx.push()
|
||||||
|
try:
|
||||||
|
self.assert_(flask.request)
|
||||||
|
self.assert_(flask.has_request_context())
|
||||||
|
finally:
|
||||||
|
ctx.pop()
|
||||||
|
|
||||||
|
def test_manual_context_binding(self):
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return 'Hello %s!' % flask.request.args['name']
|
||||||
|
|
||||||
|
ctx = app.test_request_context('/?name=World')
|
||||||
|
ctx.push()
|
||||||
|
self.assert_equal(index(), 'Hello World!')
|
||||||
|
ctx.pop()
|
||||||
|
try:
|
||||||
|
index()
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.assert_(0, 'expected runtime error')
|
||||||
|
|
||||||
|
def test_greenlet_context_copying(self):
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
greenlets = []
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
reqctx = flask._request_ctx_stack.top.copy()
|
||||||
|
def g():
|
||||||
|
self.assert_(not flask.request)
|
||||||
|
self.assert_(not flask.current_app)
|
||||||
|
with reqctx:
|
||||||
|
self.assert_(flask.request)
|
||||||
|
self.assert_equal(flask.current_app, app)
|
||||||
|
self.assert_equal(flask.request.path, '/')
|
||||||
|
self.assert_equal(flask.request.args['foo'], 'bar')
|
||||||
|
self.assert_(not flask.request)
|
||||||
|
return 42
|
||||||
|
greenlets.append(greenlet(g))
|
||||||
|
return 'Hello World!'
|
||||||
|
|
||||||
|
rv = app.test_client().get('/?foo=bar')
|
||||||
|
self.assert_equal(rv.data, 'Hello World!')
|
||||||
|
|
||||||
|
result = greenlets[0].run()
|
||||||
|
self.assert_equal(result, 42)
|
||||||
|
|
||||||
|
def test_greenlet_context_copying_api(self):
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
greenlets = []
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
reqctx = flask._request_ctx_stack.top.copy()
|
||||||
|
@flask.copy_current_request_context
|
||||||
|
def g():
|
||||||
|
self.assert_(flask.request)
|
||||||
|
self.assert_equal(flask.current_app, app)
|
||||||
|
self.assert_equal(flask.request.path, '/')
|
||||||
|
self.assert_equal(flask.request.args['foo'], 'bar')
|
||||||
|
return 42
|
||||||
|
greenlets.append(greenlet(g))
|
||||||
|
return 'Hello World!'
|
||||||
|
|
||||||
|
rv = app.test_client().get('/?foo=bar')
|
||||||
|
self.assert_equal(rv.data, 'Hello World!')
|
||||||
|
|
||||||
|
result = greenlets[0].run()
|
||||||
|
self.assert_equal(result, 42)
|
||||||
|
|
||||||
|
# Disable test if we don't have greenlets available
|
||||||
|
if greenlet is None:
|
||||||
|
test_greenlet_context_copying = None
|
||||||
|
test_greenlet_context_copying_api = None
|
||||||
|
|
||||||
|
|
||||||
|
def suite():
|
||||||
|
suite = unittest.TestSuite()
|
||||||
|
suite.addTest(unittest.makeSuite(RequestContextTestCase))
|
||||||
|
return suite
|
Loading…
Reference in New Issue