From 363205bdc3abcfeed08c7e85e397fb7a7dc9ef07 Mon Sep 17 00:00:00 2001 From: Jarek Lipski Date: Wed, 19 Sep 2018 18:55:12 +0200 Subject: [PATCH 1/6] fix @click.command example by adding parens Otherwise the example fails with the following error: "name = name or cmd.name AttributeError: 'function' object has no attribute 'name'". More details: https://stackoverflow.com/a/51923415/4619705 --- docs/cli.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cli.rst b/docs/cli.rst index 7ce63fd2..6bae15db 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -306,7 +306,7 @@ decorator instead of the Flask decorator, you can use import click from flask.cli import with_appcontext - @click.command + @click.command() @with_appcontext def do_work(): ... From c38499bbf26cfa9683e30c52139a94dc5a50dcc4 Mon Sep 17 00:00:00 2001 From: garenchan <1412950785@qq.com> Date: Wed, 24 Oct 2018 21:13:11 +0800 Subject: [PATCH 2/6] ignore colon with slash when split app_import_path Flask currently supports importing app through a combination of module path and app variable name, such as '/usr/app.py:my_app'. When the module path contains a colon, it will conflict with this import way and a `flask.cli.NoAppException` will be raised. A file path on a Windows system may contain a colon followed by a slash. So we solved this problem on Windows by ignoring the colon followed by a slash when we split app_import_path. Fix issue #2961. --- flask/cli.py | 2 +- tests/test_cli.py | 19 ++++++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/flask/cli.py b/flask/cli.py index 9f913d9c..f55f31c7 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -369,7 +369,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_cli.py b/tests/test_cli.py index 5f2fc48a..18d3aa2b 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) From eef6c34b57bf0367c1f9468bc4226ab59a1295a0 Mon Sep 17 00:00:00 2001 From: jangjunha Date: Thu, 25 Oct 2018 23:23:48 +0900 Subject: [PATCH 3/6] Fix deferred callback doc --- docs/patterns/deferredcallbacks.rst | 1 + 1 file changed, 1 insertion(+) 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 From aec0cdc0164454bb8e5bcc7a59b95370d6d8a1c0 Mon Sep 17 00:00:00 2001 From: Andrew Crouse Date: Mon, 12 Nov 2018 13:20:07 -0700 Subject: [PATCH 4/6] Update documentation for users to use flask-caching instead of the deprecated Werkzeug cache --- docs/patterns/caching.rst | 59 +++------------------------------------ 1 file changed, 4 insertions(+), 55 deletions(-) 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/ From 49efc4423398e4895cb22a772c4aa4269b4eaa78 Mon Sep 17 00:00:00 2001 From: David Lord Date: Sat, 5 Jan 2019 12:49:59 -0800 Subject: [PATCH 5/6] clear KeyError in production for Werkzeug 0.15 --- flask/app.py | 21 +++++++++++---------- tests/test_basic.py | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/flask/app.py b/flask/app.py index 87c59003..1d7050b7 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/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 From 8b567fea2397ae4c8c593efa4da62cc9e7ea9746 Mon Sep 17 00:00:00 2001 From: David Lord Date: Sat, 5 Jan 2019 14:59:25 -0800 Subject: [PATCH 6/6] fix docs build --- docs/conf.py | 169 ++++++++++++------------------------------ docs/index.rst | 5 +- docs/requirements.txt | 3 + tox.ini | 11 +-- 4 files changed, 56 insertions(+), 132 deletions(-) create mode 100644 docs/requirements.txt diff --git a/docs/conf.py b/docs/conf.py index 04172441..a6bf4e5b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,168 +1,99 @@ # -*- coding: utf-8 -*- from __future__ import print_function -import inspect -import re - -from pallets_sphinx_themes import DocVersion, ProjectLink, get_version +from pallets_sphinx_themes import ProjectLink, get_version # Project -------------------------------------------------------------- -project = 'Flask' -copyright = '2010 Pallets Team' -author = 'Pallets Team' -release, version = get_version('Flask') +project = "Flask" +copyright = "2010 Pallets Team" +author = "Pallets Team" +release, version = get_version("Flask") # General -------------------------------------------------------------- -master_doc = 'index' - +master_doc = "index" extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.intersphinx', - 'sphinxcontrib.log_cabinet', + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "sphinxcontrib.log_cabinet", + "pallets_sphinx_themes", ] - intersphinx_mapping = { - 'python': ('https://docs.python.org/3/', None), - 'werkzeug': ('http://werkzeug.pocoo.org/docs/', None), - 'click': ('http://click.pocoo.org/', 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), - 'blinker': ('https://pythonhosted.org/blinker/', None), + "python": ("https://docs.python.org/3/", None), + "werkzeug": ("http://werkzeug.pocoo.org/docs/", None), + "click": ("https://click.palletsprojects.com/", None), + "jinja": ("http://jinja.pocoo.org/docs/", 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), } # HTML ----------------------------------------------------------------- -html_theme = 'flask' +html_theme = "flask" +html_theme_options = {"index_sidebar_logo": False} html_context = { - 'project_links': [ - ProjectLink('Donate to Pallets', 'https://psfmember.org/civicrm/contribute/transact?reset=1&id=20'), - ProjectLink('Flask Website', 'https://palletsprojects.com/p/flask/'), - ProjectLink('PyPI releases', 'https://pypi.org/project/Flask/'), - ProjectLink('Source Code', 'https://github.com/pallets/flask/'), - ProjectLink( - 'Issue Tracker', 'https://github.com/pallets/flask/issues/'), - ], - 'versions': [ - DocVersion('dev', 'Development', 'unstable'), - DocVersion('1.0', 'Flask 1.0', 'stable'), - DocVersion('0.12', 'Flask 0.12'), - ], - 'canonical_url': 'http://flask.pocoo.org/docs/{}/'.format(version), - 'carbon_ads_args': 'zoneid=1673&serve=C6AILKT&placement=pocooorg', -} -html_sidebars = { - 'index': [ - 'project.html', - 'versions.html', - 'carbon_ads.html', - 'searchbox.html', - ], - '**': [ - 'localtoc.html', - 'relations.html', - 'versions.html', - 'carbon_ads.html', - 'searchbox.html', + "project_links": [ + ProjectLink("Donate to Pallets", "https://palletsprojects.com/donate"), + ProjectLink("Flask Website", "https://palletsprojects.com/p/flask/"), + ProjectLink("PyPI releases", "https://pypi.org/project/Flask/"), + ProjectLink("Source Code", "https://github.com/pallets/flask/"), + ProjectLink("Issue Tracker", "https://github.com/pallets/flask/issues/"), ] } -html_static_path = ['_static'] -html_favicon = '_static/flask-favicon.ico' -html_logo = '_static/flask.png' -html_additional_pages = { - '404': '404.html', +html_sidebars = { + "index": ["project.html", "localtoc.html", "searchbox.html"], + "**": ["localtoc.html", "relations.html", "searchbox.html"], } +singlehtml_sidebars = {"index": ["project.html", "localtoc.html"]} +html_static_path = ["_static"] +html_favicon = "_static/flask-favicon.ico" +html_logo = "_static/flask.png" +html_title = "Flask Documentation ({})".format(version) html_show_sourcelink = False # LaTeX ---------------------------------------------------------------- latex_documents = [ - (master_doc, 'Flask.tex', 'Flask Documentation', 'Pallets Team', 'manual'), + (master_doc, "Flask.tex", "Flask Documentation", "Pallets Team", "manual") ] latex_use_modindex = False latex_elements = { - 'papersize': 'a4paper', - 'pointsize': '12pt', - 'fontpkg': r'\usepackage{mathpazo}', - 'preamble': r'\usepackage{flaskstyle}', + "papersize": "a4paper", + "pointsize": "12pt", + "fontpkg": r"\usepackage{mathpazo}", + "preamble": r"\usepackage{flaskstyle}", } latex_use_parts = True -latex_additional_files = ['flaskstyle.sty', 'logo.pdf'] - -# linkcheck ------------------------------------------------------------ - -linkcheck_anchors = False +latex_additional_files = ["flaskstyle.sty", "logo.pdf"] # Local Extensions ----------------------------------------------------- -def unwrap_decorators(): - import sphinx.util.inspect as inspect - import functools - old_getargspec = inspect.getargspec - def getargspec(x): - return old_getargspec(getattr(x, '_original_function', x)) - inspect.getargspec = getargspec - - old_update_wrapper = functools.update_wrapper - def update_wrapper(wrapper, wrapped, *a, **kw): - rv = old_update_wrapper(wrapper, wrapped, *a, **kw) - rv._original_function = wrapped - return rv - functools.update_wrapper = update_wrapper - - -unwrap_decorators() -del unwrap_decorators - - -_internal_mark_re = re.compile(r'^\s*:internal:\s*$(?m)', re.M) - - -def skip_internal(app, what, name, obj, skip, options): - docstring = inspect.getdoc(obj) or '' - - if skip or _internal_mark_re.search(docstring) is not None: - return True - - -def cut_module_meta(app, what, name, obj, options, lines): - """Remove metadata from autodoc output.""" - if what != 'module': - return - - lines[:] = [ - line for line in lines - if not line.startswith((':copyright:', ':license:')) - ] - - -def github_link( - name, rawtext, text, lineno, inliner, options=None, content=None -): +def github_link(name, rawtext, text, lineno, inliner, options=None, content=None): app = inliner.document.settings.env.app release = app.config.release - base_url = 'https://github.com/pallets/flask/tree/' + base_url = "https://github.com/pallets/flask/tree/" - if text.endswith('>'): - words, text = text[:-1].rsplit('<', 1) + if text.endswith(">"): + words, text = text[:-1].rsplit("<", 1) words = words.strip() else: words = None - if release.endswith('dev'): - url = '{0}master/{1}'.format(base_url, text) + if release.endswith("dev"): + url = "{0}master/{1}".format(base_url, text) else: - url = '{0}{1}/{2}'.format(base_url, release, text) + url = "{0}{1}/{2}".format(base_url, release, text) if words is None: words = url from docutils.nodes import reference from docutils.parsers.rst.roles import set_classes + options = options or {} set_classes(options) node = reference(rawtext, words, refuri=url, **options) @@ -170,6 +101,4 @@ def github_link( def setup(app): - app.connect('autodoc-skip-member', skip_internal) - app.connect('autodoc-process-docstring', cut_module_meta) - app.add_role('gh', github_link) + app.add_role("gh", github_link) diff --git a/docs/index.rst b/docs/index.rst index 64ed4e92..52acff41 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,11 +1,12 @@ -:orphan: +.. rst-class:: hide-header Welcome to Flask ================ .. image:: _static/logo-full.png :alt: Flask: web development, one drop at a time - :align: right + :align: center + :target: https://palletsprojects.com/p/flask/ Welcome to Flask's documentation. Get started with :ref:`installation` and then get an overview with the :ref:`quickstart`. There is also a 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/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