diff --git a/docs/conf.py b/docs/conf.py index 053960fa..f494ea8d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -22,11 +22,11 @@ extensions = [ intersphinx_mapping = { "python": ("https://docs.python.org/3/", 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), - "itsdangerous": ("https://pythonhosted.org/itsdangerous", None), - "sqlalchemy": ("https://docs.sqlalchemy.org/en/latest/", None), - "wtforms": ("https://wtforms.readthedocs.io/en/latest/", None), + "itsdangerous": ("https://itsdangerous.palletsprojects.com/", None), + "sqlalchemy": ("https://docs.sqlalchemy.org/", None), + "wtforms": ("https://wtforms.readthedocs.io/en/stable/", None), "blinker": ("https://pythonhosted.org/blinker/", None), } diff --git a/docs/index.rst b/docs/index.rst index b2577079..9f40dee7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,4 +1,4 @@ -:orphan: +.. rst-class:: hide-header .. rst-class:: hide-header diff --git a/docs/patterns/caching.rst b/docs/patterns/caching.rst index fb96a559..ec42f117 100644 --- a/docs/patterns/caching.rst +++ b/docs/patterns/caching.rst @@ -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 time. -Flask itself does not provide caching for you, but Werkzeug, one of the -libraries it is based on, has some very basic cache support. It supports -multiple cache backends, normally you want to use a memcached server. +Flask itself does not provide caching for you, but `Flask-Caching`_, an +extentions for Flask does. Flask-Caching supports various backends, and it is +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 -: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 `_) 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 +.. _Flask-Caching: https://flask-caching.readthedocs.io/en/latest/ diff --git a/docs/patterns/deferredcallbacks.rst b/docs/patterns/deferredcallbacks.rst index bc20cdd6..c96e5891 100644 --- a/docs/patterns/deferredcallbacks.rst +++ b/docs/patterns/deferredcallbacks.rst @@ -41,5 +41,6 @@ user in a cookie in a :meth:`~flask.Flask.before_request` callback:: @after_this_request def remember_language(response): response.set_cookie('user_lang', language) + return response g.language = language diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000..e667deaa --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,3 @@ +Sphinx~=1.8.0 +Pallets-Sphinx-Themes~=1.1.0 +sphinxcontrib-log-cabinet~=1.0.0 diff --git a/flask/app.py b/flask/app.py index b103b3d9..87fcd0fb 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1698,16 +1698,17 @@ class Flask(_PackageBoundObject): # we cannot prevent users from trashing it themselves in a custom # trap_http_exception method so that's their fault then. - # MultiDict passes the key to the exception, but that's ignored - # when generating the response message. Set an informative - # description for key errors in debug mode or when trapping errors. - if ( - (self.debug or self.config['TRAP_BAD_REQUEST_ERRORS']) - and isinstance(e, BadRequestKeyError) - # only set it if it's still the default description - and e.description is BadRequestKeyError.description - ): - e.description = "KeyError: '{0}'".format(*e.args) + if isinstance(e, BadRequestKeyError): + if self.debug or self.config["TRAP_BAD_REQUEST_ERRORS"]: + # Werkzeug < 0.15 doesn't add the KeyError to the 400 + # message, add it in manually. + description = e.get_description() + + if e.args[0] not in description: + e.description = "KeyError: '{}'".format(*e.args) + else: + # Werkzeug >= 0.15 does add it, remove it in production + e.args = () if isinstance(e, HTTPException) and not self.trap_http_exception(e): return self.handle_http_exception(e) diff --git a/flask/cli.py b/flask/cli.py index 79fc904b..148f7472 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -370,7 +370,7 @@ class ScriptInfo(object): app = call_factory(self, self.create_app) else: 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) app = locate_app(self, import_name, name) else: diff --git a/tests/test_basic.py b/tests/test_basic.py index c0168ae3..08b4058c 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1045,7 +1045,7 @@ def test_trapping_of_bad_request_key_errors(app, client): with pytest.raises(KeyError) as e: client.get("/key") assert e.errisinstance(BadRequest) - assert 'missing_key' in e.value.description + assert 'missing_key' in e.value.get_description() rv = client.get('/abort') assert rv.status_code == 400 diff --git a/tests/test_cli.py b/tests/test_cli.py index 83151f6f..ea0d73de 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -261,8 +261,21 @@ def test_get_version(test_apps, capsys): def test_scriptinfo(test_apps, monkeypatch): """Test of ScriptInfo.""" obj = ScriptInfo(app_import_path="cliapp.app:testapp") - assert obj.load_app().name == "testapp" - assert obj.load_app().name == "testapp" + app = obj.load_app() + 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): return Flask("createapp") @@ -270,7 +283,7 @@ def test_scriptinfo(test_apps, monkeypatch): obj = ScriptInfo(create_app=create_app) app = obj.load_app() assert app.name == "createapp" - assert obj.load_app() == app + assert obj.load_app() is app obj = ScriptInfo() pytest.raises(NoAppException, obj.load_app) diff --git a/tox.ini b/tox.ini index 546b9279..cdeee4c3 100644 --- a/tox.ini +++ b/tox.ini @@ -39,18 +39,9 @@ commands = [testenv:docs-html] deps = - sphinx - pallets-sphinx-themes - sphinxcontrib-log-cabinet + -r docs/requirements.txt 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] deps = coverage skip_install = true