Merge branch '1.0-maintenance'

This commit is contained in:
David Lord 2019-01-05 15:12:17 -08:00
commit f7d50d4b67
No known key found for this signature in database
GPG key ID: 7A1C87E3F5BC42A8
10 changed files with 43 additions and 85 deletions

View file

@ -22,11 +22,11 @@ extensions = [
intersphinx_mapping = { intersphinx_mapping = {
"python": ("https://docs.python.org/3/", None), "python": ("https://docs.python.org/3/", None),
"werkzeug": ("http://werkzeug.pocoo.org/docs/", None), "werkzeug": ("http://werkzeug.pocoo.org/docs/", None),
"click": ("http://click.pocoo.org/", None), "click": ("https://click.palletsprojects.com/", None),
"jinja": ("http://jinja.pocoo.org/docs/", None), "jinja": ("http://jinja.pocoo.org/docs/", None),
"itsdangerous": ("https://pythonhosted.org/itsdangerous", None), "itsdangerous": ("https://itsdangerous.palletsprojects.com/", None),
"sqlalchemy": ("https://docs.sqlalchemy.org/en/latest/", None), "sqlalchemy": ("https://docs.sqlalchemy.org/", None),
"wtforms": ("https://wtforms.readthedocs.io/en/latest/", None), "wtforms": ("https://wtforms.readthedocs.io/en/stable/", None),
"blinker": ("https://pythonhosted.org/blinker/", None), "blinker": ("https://pythonhosted.org/blinker/", None),
} }

View file

@ -1,4 +1,4 @@
:orphan: .. rst-class:: hide-header
.. rst-class:: hide-header .. rst-class:: hide-header

View file

@ -10,60 +10,9 @@ still be good enough if they were 5 minutes old. So then the idea is that
you actually put the result of that calculation into a cache for some you actually put the result of that calculation into a cache for some
time. time.
Flask itself does not provide caching for you, but Werkzeug, one of the Flask itself does not provide caching for you, but `Flask-Caching`_, an
libraries it is based on, has some very basic cache support. It supports extentions for Flask does. Flask-Caching supports various backends, and it is
multiple cache backends, normally you want to use a memcached server. even possible to develop your own caching backend.
Setting up a Cache
------------------
You create a cache object once and keep it around, similar to how .. _Flask-Caching: https://flask-caching.readthedocs.io/en/latest/
:class:`~flask.Flask` objects are created. If you are using the
development server you can create a
:class:`~werkzeug.contrib.cache.SimpleCache` object, that one is a simple
cache that keeps the item stored in the memory of the Python interpreter::
from werkzeug.contrib.cache import SimpleCache
cache = SimpleCache()
If you want to use memcached, make sure to have one of the memcache modules
supported (you get them from `PyPI <https://pypi.org/>`_) and a
memcached server running somewhere. This is how you connect to such an
memcached server then::
from werkzeug.contrib.cache import MemcachedCache
cache = MemcachedCache(['127.0.0.1:11211'])
If you are using App Engine, you can connect to the App Engine memcache
server easily::
from werkzeug.contrib.cache import GAEMemcachedCache
cache = GAEMemcachedCache()
Using a Cache
-------------
Now how can one use such a cache? There are two very important
operations: :meth:`~werkzeug.contrib.cache.BaseCache.get` and
:meth:`~werkzeug.contrib.cache.BaseCache.set`. This is how to use them:
To get an item from the cache call
:meth:`~werkzeug.contrib.cache.BaseCache.get` with a string as key name.
If something is in the cache, it is returned. Otherwise that function
will return ``None``::
rv = cache.get('my-item')
To add items to the cache, use the :meth:`~werkzeug.contrib.cache.BaseCache.set`
method instead. The first argument is the key and the second the value
that should be set. Also a timeout can be provided after which the cache
will automatically remove item.
Here a full example how this looks like normally::
def get_my_item():
rv = cache.get('my-item')
if rv is None:
rv = calculate_value()
cache.set('my-item', rv, timeout=5 * 60)
return rv

View file

@ -41,5 +41,6 @@ user in a cookie in a :meth:`~flask.Flask.before_request` callback::
@after_this_request @after_this_request
def remember_language(response): def remember_language(response):
response.set_cookie('user_lang', language) response.set_cookie('user_lang', language)
return response
g.language = language g.language = language

3
docs/requirements.txt Normal file
View file

@ -0,0 +1,3 @@
Sphinx~=1.8.0
Pallets-Sphinx-Themes~=1.1.0
sphinxcontrib-log-cabinet~=1.0.0

View file

@ -1698,16 +1698,17 @@ class Flask(_PackageBoundObject):
# we cannot prevent users from trashing it themselves in a custom # we cannot prevent users from trashing it themselves in a custom
# trap_http_exception method so that's their fault then. # trap_http_exception method so that's their fault then.
# MultiDict passes the key to the exception, but that's ignored if isinstance(e, BadRequestKeyError):
# when generating the response message. Set an informative if self.debug or self.config["TRAP_BAD_REQUEST_ERRORS"]:
# description for key errors in debug mode or when trapping errors. # Werkzeug < 0.15 doesn't add the KeyError to the 400
if ( # message, add it in manually.
(self.debug or self.config['TRAP_BAD_REQUEST_ERRORS']) description = e.get_description()
and isinstance(e, BadRequestKeyError)
# only set it if it's still the default description if e.args[0] not in description:
and e.description is BadRequestKeyError.description e.description = "KeyError: '{}'".format(*e.args)
): else:
e.description = "KeyError: '{0}'".format(*e.args) # Werkzeug >= 0.15 does add it, remove it in production
e.args = ()
if isinstance(e, HTTPException) and not self.trap_http_exception(e): if isinstance(e, HTTPException) and not self.trap_http_exception(e):
return self.handle_http_exception(e) return self.handle_http_exception(e)

View file

@ -370,7 +370,7 @@ class ScriptInfo(object):
app = call_factory(self, self.create_app) app = call_factory(self, self.create_app)
else: else:
if self.app_import_path: if self.app_import_path:
path, name = (self.app_import_path.split(':', 1) + [None])[:2] path, name = (re.split(r':(?![\\/])', self.app_import_path, 1) + [None])[:2]
import_name = prepare_import(path) import_name = prepare_import(path)
app = locate_app(self, import_name, name) app = locate_app(self, import_name, name)
else: else:

View file

@ -1045,7 +1045,7 @@ def test_trapping_of_bad_request_key_errors(app, client):
with pytest.raises(KeyError) as e: with pytest.raises(KeyError) as e:
client.get("/key") client.get("/key")
assert e.errisinstance(BadRequest) assert e.errisinstance(BadRequest)
assert 'missing_key' in e.value.description assert 'missing_key' in e.value.get_description()
rv = client.get('/abort') rv = client.get('/abort')
assert rv.status_code == 400 assert rv.status_code == 400

View file

@ -261,8 +261,21 @@ def test_get_version(test_apps, capsys):
def test_scriptinfo(test_apps, monkeypatch): def test_scriptinfo(test_apps, monkeypatch):
"""Test of ScriptInfo.""" """Test of ScriptInfo."""
obj = ScriptInfo(app_import_path="cliapp.app:testapp") obj = ScriptInfo(app_import_path="cliapp.app:testapp")
assert obj.load_app().name == "testapp" app = obj.load_app()
assert obj.load_app().name == "testapp" assert app.name == "testapp"
assert obj.load_app() is app
# import app with module's absolute path
cli_app_path = os.path.abspath(os.path.join(
os.path.dirname(__file__), 'test_apps', 'cliapp', 'app.py'))
obj = ScriptInfo(app_import_path=cli_app_path)
app = obj.load_app()
assert app.name == 'testapp'
assert obj.load_app() is app
obj = ScriptInfo(app_import_path=cli_app_path + ':testapp')
app = obj.load_app()
assert app.name == 'testapp'
assert obj.load_app() is app
def create_app(info): def create_app(info):
return Flask("createapp") return Flask("createapp")
@ -270,7 +283,7 @@ def test_scriptinfo(test_apps, monkeypatch):
obj = ScriptInfo(create_app=create_app) obj = ScriptInfo(create_app=create_app)
app = obj.load_app() app = obj.load_app()
assert app.name == "createapp" assert app.name == "createapp"
assert obj.load_app() == app assert obj.load_app() is app
obj = ScriptInfo() obj = ScriptInfo()
pytest.raises(NoAppException, obj.load_app) pytest.raises(NoAppException, obj.load_app)

11
tox.ini
View file

@ -39,18 +39,9 @@ commands =
[testenv:docs-html] [testenv:docs-html]
deps = deps =
sphinx -r docs/requirements.txt
pallets-sphinx-themes
sphinxcontrib-log-cabinet
commands = sphinx-build -W -b html -d {envtmpdir}/doctrees docs {envtmpdir}/html commands = sphinx-build -W -b html -d {envtmpdir}/doctrees docs {envtmpdir}/html
[testenv:docs-linkcheck]
deps =
sphinx
pallets-sphinx-themes
sphinxcontrib-log-cabinet
commands = sphinx-build -W -b linkcheck -d {envtmpdir}/doctrees docs {envtmpdir}/linkcheck
[testenv:coverage-report] [testenv:coverage-report]
deps = coverage deps = coverage
skip_install = true skip_install = true