forked from orbit-oss/flask
commit
cd8a374504
84 changed files with 585 additions and 2051 deletions
|
|
@ -1,85 +1,44 @@
|
||||||
trigger:
|
trigger:
|
||||||
- 'master'
|
- master
|
||||||
- '*.x'
|
- '*.x'
|
||||||
|
|
||||||
jobs:
|
variables:
|
||||||
- job: Tests
|
vmImage: ubuntu-latest
|
||||||
variables:
|
python.version: '3.8'
|
||||||
vmImage: 'ubuntu-latest'
|
TOXENV: py
|
||||||
python.version: '3.8'
|
|
||||||
TOXENV: 'py,coverage-ci'
|
|
||||||
hasTestResults: 'true'
|
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
Python 3.8 Linux:
|
Linux:
|
||||||
vmImage: 'ubuntu-latest'
|
|
||||||
Python 3.8 Windows:
|
|
||||||
vmImage: 'windows-latest'
|
|
||||||
Python 3.8 Mac:
|
|
||||||
vmImage: 'macos-latest'
|
|
||||||
PyPy 3 Linux:
|
|
||||||
python.version: 'pypy3'
|
|
||||||
Python 3.7 Linux:
|
|
||||||
python.version: '3.7'
|
|
||||||
Python 3.6 Linux:
|
|
||||||
python.version: '3.6'
|
|
||||||
Python 3.5 Linux:
|
|
||||||
python.version: '3.5'
|
|
||||||
Python 2.7 Linux:
|
|
||||||
python.version: '2.7'
|
|
||||||
Python 2.7 Windows:
|
|
||||||
python.version: '2.7'
|
|
||||||
vmImage: 'windows-latest'
|
|
||||||
Docs:
|
|
||||||
TOXENV: 'docs'
|
|
||||||
hasTestResults: 'false'
|
|
||||||
Style:
|
|
||||||
TOXENV: 'style'
|
|
||||||
hasTestResults: 'false'
|
|
||||||
VersionRange:
|
|
||||||
TOXENV: 'devel,lowest,coverage-ci'
|
|
||||||
|
|
||||||
pool:
|
|
||||||
vmImage: $[ variables.vmImage ]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- task: UsePythonVersion@0
|
|
||||||
inputs:
|
|
||||||
versionSpec: $(python.version)
|
|
||||||
displayName: Use Python $(python.version)
|
|
||||||
|
|
||||||
- script: pip --disable-pip-version-check install -U tox
|
|
||||||
displayName: Install tox
|
|
||||||
|
|
||||||
- script: tox -s false -- --junitxml=test-results.xml tests examples
|
|
||||||
displayName: Run tox
|
|
||||||
|
|
||||||
- task: PublishTestResults@2
|
|
||||||
inputs:
|
|
||||||
testResultsFiles: test-results.xml
|
|
||||||
testRunTitle: $(Agent.JobName)
|
|
||||||
condition: eq(variables['hasTestResults'], 'true')
|
|
||||||
displayName: Publish test results
|
|
||||||
|
|
||||||
- task: PublishCodeCoverageResults@1
|
|
||||||
inputs:
|
|
||||||
codeCoverageTool: Cobertura
|
|
||||||
summaryFileLocation: coverage.xml
|
|
||||||
condition: eq(variables['hasTestResults'], 'true')
|
|
||||||
displayName: Publish coverage results
|
|
||||||
|
|
||||||
# Test on the nightly version of Python.
|
|
||||||
# Use a container since Azure Pipelines may not have the latest build.
|
|
||||||
- job: FlaskOnNightly
|
|
||||||
pool:
|
|
||||||
vmImage: ubuntu-latest
|
vmImage: ubuntu-latest
|
||||||
container: python:rc-stretch
|
Windows:
|
||||||
steps:
|
vmImage: windows-latest
|
||||||
- script: |
|
Mac:
|
||||||
echo "##vso[task.prependPath]$HOME/.local/bin"
|
vmImage: macos-latest
|
||||||
pip --disable-pip-version-check install --user -U tox
|
Python 3.7:
|
||||||
displayName: Install tox
|
python.version: '3.7'
|
||||||
|
Python 3.6:
|
||||||
|
python.version: '3.6'
|
||||||
|
PyPy 3:
|
||||||
|
python.version: pypy3
|
||||||
|
Version Range:
|
||||||
|
TOXENV: 'devel,lowest'
|
||||||
|
Docs:
|
||||||
|
TOXENV: docs
|
||||||
|
Style:
|
||||||
|
TOXENV: style
|
||||||
|
|
||||||
- script: tox -e nightly
|
pool:
|
||||||
displayName: Run tox
|
vmImage: $[ variables.vmImage ]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- task: UsePythonVersion@0
|
||||||
|
inputs:
|
||||||
|
versionSpec: $(python.version)
|
||||||
|
displayName: Use Python $(python.version)
|
||||||
|
|
||||||
|
- script: pip --disable-pip-version-check install -U tox
|
||||||
|
displayName: Install tox
|
||||||
|
|
||||||
|
- script: tox
|
||||||
|
displayName: Run tox
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,29 @@
|
||||||
repos:
|
repos:
|
||||||
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
|
rev: v2.1.0
|
||||||
|
hooks:
|
||||||
|
- id: pyupgrade
|
||||||
|
args: ["--py36-plus"]
|
||||||
- repo: https://github.com/asottile/reorder_python_imports
|
- repo: https://github.com/asottile/reorder_python_imports
|
||||||
rev: v1.5.0
|
rev: v2.1.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: reorder-python-imports
|
- id: reorder-python-imports
|
||||||
name: Reorder Python imports (src, tests)
|
name: Reorder Python imports (src, tests)
|
||||||
files: "^(?!examples/)"
|
files: "^(?!examples/)"
|
||||||
args: ["--application-directories", ".:src"]
|
args: ["--application-directories", "src"]
|
||||||
- repo: https://github.com/python/black
|
- repo: https://github.com/python/black
|
||||||
rev: 19.3b0
|
rev: 19.10b0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
- repo: https://gitlab.com/pycqa/flake8
|
- repo: https://gitlab.com/pycqa/flake8
|
||||||
rev: 3.7.7
|
rev: 3.7.9
|
||||||
hooks:
|
hooks:
|
||||||
- id: flake8
|
- id: flake8
|
||||||
additional_dependencies: [flake8-bugbear]
|
additional_dependencies:
|
||||||
|
- flake8-bugbear
|
||||||
|
- flake8-implicit-str-concat
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v2.2.3
|
rev: v2.5.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-byte-order-marker
|
- id: check-byte-order-marker
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
|
|
|
||||||
18
CHANGES.rst
18
CHANGES.rst
|
|
@ -1,10 +1,11 @@
|
||||||
.. currentmodule:: flask
|
.. currentmodule:: flask
|
||||||
|
|
||||||
Version 1.2.0
|
Version 2.0.0
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
Unreleased
|
Unreleased
|
||||||
|
|
||||||
|
- Drop support for Python 2 and 3.5.
|
||||||
- Add :meth:`sessions.SessionInterface.get_cookie_name` to allow
|
- Add :meth:`sessions.SessionInterface.get_cookie_name` to allow
|
||||||
setting the session cookie name dynamically. :pr:`3369`
|
setting the session cookie name dynamically. :pr:`3369`
|
||||||
- Add :meth:`Config.from_file` to load config using arbitrary file
|
- Add :meth:`Config.from_file` to load config using arbitrary file
|
||||||
|
|
@ -452,7 +453,7 @@ Released 2016-05-29, codename Absinthe
|
||||||
|
|
||||||
- Added support to serializing top-level arrays to
|
- Added support to serializing top-level arrays to
|
||||||
:func:`flask.jsonify`. This introduces a security risk in ancient
|
:func:`flask.jsonify`. This introduces a security risk in ancient
|
||||||
browsers. See :ref:`json-security` for details.
|
browsers.
|
||||||
- Added before_render_template signal.
|
- Added before_render_template signal.
|
||||||
- Added ``**kwargs`` to :meth:`flask.Test.test_client` to support
|
- Added ``**kwargs`` to :meth:`flask.Test.test_client` to support
|
||||||
passing additional keyword arguments to the constructor of
|
passing additional keyword arguments to the constructor of
|
||||||
|
|
@ -567,8 +568,7 @@ Version 0.10
|
||||||
Released 2013-06-13, codename Limoncello
|
Released 2013-06-13, codename Limoncello
|
||||||
|
|
||||||
- Changed default cookie serialization format from pickle to JSON to
|
- Changed default cookie serialization format from pickle to JSON to
|
||||||
limit the impact an attacker can do if the secret key leaks. See
|
limit the impact an attacker can do if the secret key leaks.
|
||||||
:ref:`upgrading-to-010` for more information.
|
|
||||||
- Added ``template_test`` methods in addition to the already existing
|
- Added ``template_test`` methods in addition to the already existing
|
||||||
``template_filter`` method family.
|
``template_filter`` method family.
|
||||||
- Added ``template_global`` methods in addition to the already
|
- Added ``template_global`` methods in addition to the already
|
||||||
|
|
@ -597,8 +597,7 @@ Released 2013-06-13, codename Limoncello
|
||||||
- Added an option to generate non-ascii encoded JSON which should
|
- Added an option to generate non-ascii encoded JSON which should
|
||||||
result in less bytes being transmitted over the network. It's
|
result in less bytes being transmitted over the network. It's
|
||||||
disabled by default to not cause confusion with existing libraries
|
disabled by default to not cause confusion with existing libraries
|
||||||
that might expect ``flask.json.dumps`` to return bytestrings by
|
that might expect ``flask.json.dumps`` to return bytes by default.
|
||||||
default.
|
|
||||||
- ``flask.g`` is now stored on the app context instead of the request
|
- ``flask.g`` is now stored on the app context instead of the request
|
||||||
context.
|
context.
|
||||||
- ``flask.g`` now gained a ``get()`` method for not erroring out on
|
- ``flask.g`` now gained a ``get()`` method for not erroring out on
|
||||||
|
|
@ -762,7 +761,7 @@ Released 2011-09-29, codename Rakija
|
||||||
designated place to drop files that are modified at runtime (uploads
|
designated place to drop files that are modified at runtime (uploads
|
||||||
etc.). Also this is conceptually only instance depending and outside
|
etc.). Also this is conceptually only instance depending and outside
|
||||||
version control so it's the perfect place to put configuration files
|
version control so it's the perfect place to put configuration files
|
||||||
etc. For more information see :ref:`instance-folders`.
|
etc.
|
||||||
- Added the ``APPLICATION_ROOT`` configuration variable.
|
- Added the ``APPLICATION_ROOT`` configuration variable.
|
||||||
- Implemented :meth:`~flask.testing.TestClient.session_transaction` to
|
- Implemented :meth:`~flask.testing.TestClient.session_transaction` to
|
||||||
easily modify sessions from the test environment.
|
easily modify sessions from the test environment.
|
||||||
|
|
@ -842,8 +841,7 @@ Released 2011-06-28, codename Grappa
|
||||||
- Added ``teardown_request`` decorator, for functions that should run
|
- Added ``teardown_request`` decorator, for functions that should run
|
||||||
at the end of a request regardless of whether an exception occurred.
|
at the end of a request regardless of whether an exception occurred.
|
||||||
Also the behavior for ``after_request`` was changed. It's now no
|
Also the behavior for ``after_request`` was changed. It's now no
|
||||||
longer executed when an exception is raised. See
|
longer executed when an exception is raised.
|
||||||
:ref:`upgrading-to-new-teardown-handling`
|
|
||||||
- Implemented :func:`flask.has_request_context`
|
- Implemented :func:`flask.has_request_context`
|
||||||
- Deprecated ``init_jinja_globals``. Override the
|
- Deprecated ``init_jinja_globals``. Override the
|
||||||
:meth:`~flask.Flask.create_jinja_environment` method instead to
|
:meth:`~flask.Flask.create_jinja_environment` method instead to
|
||||||
|
|
@ -860,7 +858,7 @@ Released 2011-06-28, codename Grappa
|
||||||
errors that might occur during request processing (for instance
|
errors that might occur during request processing (for instance
|
||||||
database connection errors, timeouts from remote resources etc.).
|
database connection errors, timeouts from remote resources etc.).
|
||||||
- Blueprints can provide blueprint specific error handlers.
|
- Blueprints can provide blueprint specific error handlers.
|
||||||
- Implemented generic :ref:`views` (class-based views).
|
- Implemented generic class-based views.
|
||||||
|
|
||||||
|
|
||||||
Version 0.6.1
|
Version 0.6.1
|
||||||
|
|
|
||||||
|
|
@ -135,8 +135,9 @@ depends on which part of Flask you're working on. Travis-CI will run the full
|
||||||
suite when you submit your pull request.
|
suite when you submit your pull request.
|
||||||
|
|
||||||
The full test suite takes a long time to run because it tests multiple
|
The full test suite takes a long time to run because it tests multiple
|
||||||
combinations of Python and dependencies. You need to have Python 2.7, 3.4,
|
combinations of Python and dependencies. If you don't have a Python
|
||||||
3.5, 3.6, 3.7 and PyPy 2.7 installed to run all of the environments. Then run::
|
version installed, it will be skipped with a warning message at the end.
|
||||||
|
Run::
|
||||||
|
|
||||||
tox
|
tox
|
||||||
|
|
||||||
|
|
|
||||||
12
docs/api.rst
12
docs/api.rst
|
|
@ -58,12 +58,12 @@ Incoming Request Data
|
||||||
the following:
|
the following:
|
||||||
|
|
||||||
============= ======================================================
|
============= ======================================================
|
||||||
`path` ``u'/π/page.html'``
|
`path` ``'/π/page.html'``
|
||||||
`full_path` ``u'/π/page.html?x=y'``
|
`full_path` ``'/π/page.html?x=y'``
|
||||||
`script_root` ``u'/myapplication'``
|
`script_root` ``'/myapplication'``
|
||||||
`base_url` ``u'http://www.example.com/myapplication/π/page.html'``
|
`base_url` ``'http://www.example.com/myapplication/π/page.html'``
|
||||||
`url` ``u'http://www.example.com/myapplication/π/page.html?x=y'``
|
`url` ``'http://www.example.com/myapplication/π/page.html?x=y'``
|
||||||
`url_root` ``u'http://www.example.com/myapplication/'``
|
`url_root` ``'http://www.example.com/myapplication/'``
|
||||||
============= ======================================================
|
============= ======================================================
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
10
docs/conf.py
10
docs/conf.py
|
|
@ -52,14 +52,12 @@ singlehtml_sidebars = {"index": ["project.html", "localtoc.html"]}
|
||||||
html_static_path = ["_static"]
|
html_static_path = ["_static"]
|
||||||
html_favicon = "_static/flask-icon.png"
|
html_favicon = "_static/flask-icon.png"
|
||||||
html_logo = "_static/flask-icon.png"
|
html_logo = "_static/flask-icon.png"
|
||||||
html_title = "Flask Documentation ({})".format(version)
|
html_title = f"Flask Documentation ({version})"
|
||||||
html_show_sourcelink = False
|
html_show_sourcelink = False
|
||||||
|
|
||||||
# LaTeX ----------------------------------------------------------------
|
# LaTeX ----------------------------------------------------------------
|
||||||
|
|
||||||
latex_documents = [
|
latex_documents = [(master_doc, f"Flask-{version}.tex", html_title, author, "manual")]
|
||||||
(master_doc, "Flask-{}.tex".format(version), html_title, author, "manual")
|
|
||||||
]
|
|
||||||
|
|
||||||
# Local Extensions -----------------------------------------------------
|
# Local Extensions -----------------------------------------------------
|
||||||
|
|
||||||
|
|
@ -76,9 +74,9 @@ def github_link(name, rawtext, text, lineno, inliner, options=None, content=None
|
||||||
words = None
|
words = None
|
||||||
|
|
||||||
if packaging.version.parse(release).is_devrelease:
|
if packaging.version.parse(release).is_devrelease:
|
||||||
url = "{0}master/{1}".format(base_url, text)
|
url = f"{base_url}master/{text}"
|
||||||
else:
|
else:
|
||||||
url = "{0}{1}/{2}".format(base_url, release, text)
|
url = f"{base_url}{release}/{text}"
|
||||||
|
|
||||||
if words is None:
|
if words is None:
|
||||||
words = url
|
words = url
|
||||||
|
|
|
||||||
|
|
@ -161,8 +161,8 @@ The following configuration values are used internally by Flask:
|
||||||
|
|
||||||
A secret key that will be used for securely signing the session cookie
|
A secret key that will be used for securely signing the session cookie
|
||||||
and can be used for any other security related needs by extensions or your
|
and can be used for any other security related needs by extensions or your
|
||||||
application. It should be a long random string of bytes, although unicode
|
application. It should be a long random ``bytes`` or ``str``. For
|
||||||
is accepted too. For example, copy the output of this to your config::
|
example, copy the output of this to your config::
|
||||||
|
|
||||||
$ python -c 'import os; print(os.urandom(16))'
|
$ python -c 'import os; print(os.urandom(16))'
|
||||||
b'_5#y2L"F4Q8z\n\xec]/'
|
b'_5#y2L"F4Q8z\n\xec]/'
|
||||||
|
|
@ -302,10 +302,10 @@ The following configuration values are used internally by Flask:
|
||||||
|
|
||||||
.. py:data:: JSON_AS_ASCII
|
.. py:data:: JSON_AS_ASCII
|
||||||
|
|
||||||
Serialize objects to ASCII-encoded JSON. If this is disabled, the JSON
|
Serialize objects to ASCII-encoded JSON. If this is disabled, the
|
||||||
will be returned as a Unicode string, or encoded as ``UTF-8`` by
|
JSON returned from ``jsonify`` will contain Unicode characters. This
|
||||||
``jsonify``. This has security implications when rendering the JSON into
|
has security implications when rendering the JSON into JavaScript in
|
||||||
JavaScript in templates, and should typically remain enabled.
|
templates, and should typically remain enabled.
|
||||||
|
|
||||||
Default: ``True``
|
Default: ``True``
|
||||||
|
|
||||||
|
|
@ -596,8 +596,8 @@ your configuration classes::
|
||||||
DB_SERVER = '192.168.1.56'
|
DB_SERVER = '192.168.1.56'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def DATABASE_URI(self): # Note: all caps
|
def DATABASE_URI(self): # Note: all caps
|
||||||
return 'mysql://user@{}/foo'.format(self.DB_SERVER)
|
return f"mysql://user@{self.DB_SERVER}/foo"
|
||||||
|
|
||||||
class ProductionConfig(Config):
|
class ProductionConfig(Config):
|
||||||
"""Uses production database server."""
|
"""Uses production database server."""
|
||||||
|
|
@ -678,7 +678,7 @@ locations are used:
|
||||||
|
|
||||||
- Installed module or package::
|
- Installed module or package::
|
||||||
|
|
||||||
$PREFIX/lib/python2.X/site-packages/myapp
|
$PREFIX/lib/pythonX.Y/site-packages/myapp
|
||||||
$PREFIX/var/myapp-instance
|
$PREFIX/var/myapp-instance
|
||||||
|
|
||||||
``$PREFIX`` is the prefix of your Python installation. This can be
|
``$PREFIX`` is the prefix of your Python installation. This can be
|
||||||
|
|
|
||||||
|
|
@ -210,11 +210,6 @@ you have to modify your ``.wsgi`` file slightly.
|
||||||
|
|
||||||
Add the following lines to the top of your ``.wsgi`` file::
|
Add the following lines to the top of your ``.wsgi`` file::
|
||||||
|
|
||||||
activate_this = '/path/to/env/bin/activate_this.py'
|
|
||||||
execfile(activate_this, dict(__file__=activate_this))
|
|
||||||
|
|
||||||
For Python 3 add the following lines to the top of your ``.wsgi`` file::
|
|
||||||
|
|
||||||
activate_this = '/path/to/env/bin/activate_this.py'
|
activate_this = '/path/to/env/bin/activate_this.py'
|
||||||
with open(activate_this) as file_:
|
with open(activate_this) as file_:
|
||||||
exec(file_.read(), dict(__file__=activate_this))
|
exec(file_.read(), dict(__file__=activate_this))
|
||||||
|
|
|
||||||
|
|
@ -109,14 +109,14 @@ has a certain understanding about how things work. On the surface they
|
||||||
all work the same: you tell the engine to evaluate a template with a set
|
all work the same: you tell the engine to evaluate a template with a set
|
||||||
of variables and take the return value as string.
|
of variables and take the return value as string.
|
||||||
|
|
||||||
But that's about where similarities end. Jinja2 for example has an
|
But that's about where similarities end. Jinja2 for example has an
|
||||||
extensive filter system, a certain way to do template inheritance, support
|
extensive filter system, a certain way to do template inheritance,
|
||||||
for reusable blocks (macros) that can be used from inside templates and
|
support for reusable blocks (macros) that can be used from inside
|
||||||
also from Python code, uses Unicode for all operations, supports
|
templates and also from Python code, supports iterative template
|
||||||
iterative template rendering, configurable syntax and more. On the other
|
rendering, configurable syntax and more. On the other hand an engine
|
||||||
hand an engine like Genshi is based on XML stream evaluation, template
|
like Genshi is based on XML stream evaluation, template inheritance by
|
||||||
inheritance by taking the availability of XPath into account and more.
|
taking the availability of XPath into account and more. Mako on the
|
||||||
Mako on the other hand treats templates similar to Python modules.
|
other hand treats templates similar to Python modules.
|
||||||
|
|
||||||
When it comes to connecting a template engine with an application or
|
When it comes to connecting a template engine with an application or
|
||||||
framework there is more than just rendering templates. For instance,
|
framework there is more than just rendering templates. For instance,
|
||||||
|
|
|
||||||
|
|
@ -84,10 +84,7 @@ Design notes, legal information and changelog are here for the interested.
|
||||||
design
|
design
|
||||||
htmlfaq
|
htmlfaq
|
||||||
security
|
security
|
||||||
unicode
|
|
||||||
extensiondev
|
extensiondev
|
||||||
styleguide
|
|
||||||
upgrading
|
|
||||||
changelog
|
changelog
|
||||||
license
|
license
|
||||||
contributing
|
contributing
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,13 @@
|
||||||
Installation
|
Installation
|
||||||
============
|
============
|
||||||
|
|
||||||
|
|
||||||
Python Version
|
Python Version
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
We recommend using the latest version of Python 3. Flask supports Python 3.5
|
We recommend using the latest version of Python. Flask supports Python
|
||||||
and newer, Python 2.7, and PyPy.
|
3.6 and newer.
|
||||||
|
|
||||||
|
|
||||||
Dependencies
|
Dependencies
|
||||||
------------
|
------------
|
||||||
|
|
@ -31,6 +33,7 @@ These distributions will be installed automatically when installing Flask.
|
||||||
.. _ItsDangerous: https://palletsprojects.com/p/itsdangerous/
|
.. _ItsDangerous: https://palletsprojects.com/p/itsdangerous/
|
||||||
.. _Click: https://palletsprojects.com/p/click/
|
.. _Click: https://palletsprojects.com/p/click/
|
||||||
|
|
||||||
|
|
||||||
Optional dependencies
|
Optional dependencies
|
||||||
~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
@ -51,6 +54,7 @@ use them if you install them.
|
||||||
.. _python-dotenv: https://github.com/theskumar/python-dotenv#readme
|
.. _python-dotenv: https://github.com/theskumar/python-dotenv#readme
|
||||||
.. _watchdog: https://pythonhosted.org/watchdog/
|
.. _watchdog: https://pythonhosted.org/watchdog/
|
||||||
|
|
||||||
|
|
||||||
Virtual environments
|
Virtual environments
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
|
@ -66,13 +70,9 @@ Virtual environments are independent groups of Python libraries, one for each
|
||||||
project. Packages installed for one project will not affect other projects or
|
project. Packages installed for one project will not affect other projects or
|
||||||
the operating system's packages.
|
the operating system's packages.
|
||||||
|
|
||||||
Python 3 comes bundled with the :mod:`venv` module to create virtual
|
Python comes bundled with the :mod:`venv` module to create virtual
|
||||||
environments. If you're using a modern version of Python, you can continue on
|
environments.
|
||||||
to the next section.
|
|
||||||
|
|
||||||
If you're using Python 2, see :ref:`install-install-virtualenv` first.
|
|
||||||
|
|
||||||
.. _install-create-env:
|
|
||||||
|
|
||||||
Create an environment
|
Create an environment
|
||||||
~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
@ -91,18 +91,6 @@ On Windows:
|
||||||
|
|
||||||
$ py -3 -m venv venv
|
$ py -3 -m venv venv
|
||||||
|
|
||||||
If you needed to install virtualenv because you are using Python 2, use
|
|
||||||
the following command instead:
|
|
||||||
|
|
||||||
.. code-block:: sh
|
|
||||||
|
|
||||||
$ python2 -m virtualenv venv
|
|
||||||
|
|
||||||
On Windows:
|
|
||||||
|
|
||||||
.. code-block:: bat
|
|
||||||
|
|
||||||
> \Python27\Scripts\virtualenv.exe venv
|
|
||||||
|
|
||||||
.. _install-activate-env:
|
.. _install-activate-env:
|
||||||
|
|
||||||
|
|
@ -121,12 +109,15 @@ On Windows:
|
||||||
|
|
||||||
> venv\Scripts\activate
|
> venv\Scripts\activate
|
||||||
|
|
||||||
Your shell prompt will change to show the name of the activated environment.
|
Your shell prompt will change to show the name of the activated
|
||||||
|
environment.
|
||||||
|
|
||||||
|
|
||||||
Install Flask
|
Install Flask
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
Within the activated environment, use the following command to install Flask:
|
Within the activated environment, use the following command to install
|
||||||
|
Flask:
|
||||||
|
|
||||||
.. code-block:: sh
|
.. code-block:: sh
|
||||||
|
|
||||||
|
|
@ -134,53 +125,3 @@ Within the activated environment, use the following command to install Flask:
|
||||||
|
|
||||||
Flask is now installed. Check out the :doc:`/quickstart` or go to the
|
Flask is now installed. Check out the :doc:`/quickstart` or go to the
|
||||||
:doc:`Documentation Overview </index>`.
|
:doc:`Documentation Overview </index>`.
|
||||||
|
|
||||||
Living on the edge
|
|
||||||
~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
If you want to work with the latest Flask code before it's released, install or
|
|
||||||
update the code from the master branch:
|
|
||||||
|
|
||||||
.. code-block:: sh
|
|
||||||
|
|
||||||
$ pip install -U https://github.com/pallets/flask/archive/master.tar.gz
|
|
||||||
|
|
||||||
.. _install-install-virtualenv:
|
|
||||||
|
|
||||||
Install virtualenv
|
|
||||||
------------------
|
|
||||||
|
|
||||||
If you are using Python 2, the venv module is not available. Instead,
|
|
||||||
install `virtualenv`_.
|
|
||||||
|
|
||||||
On Linux, virtualenv is provided by your package manager:
|
|
||||||
|
|
||||||
.. code-block:: sh
|
|
||||||
|
|
||||||
# Debian, Ubuntu
|
|
||||||
$ sudo apt-get install python-virtualenv
|
|
||||||
|
|
||||||
# CentOS, Fedora
|
|
||||||
$ sudo yum install python-virtualenv
|
|
||||||
|
|
||||||
# Arch
|
|
||||||
$ sudo pacman -S python-virtualenv
|
|
||||||
|
|
||||||
If you are on Mac OS X or Windows, download `get-pip.py`_, then:
|
|
||||||
|
|
||||||
.. code-block:: sh
|
|
||||||
|
|
||||||
$ sudo python2 Downloads/get-pip.py
|
|
||||||
$ sudo python2 -m pip install virtualenv
|
|
||||||
|
|
||||||
On Windows, as an administrator:
|
|
||||||
|
|
||||||
.. code-block:: bat
|
|
||||||
|
|
||||||
> \Python27\python.exe Downloads\get-pip.py
|
|
||||||
> \Python27\python.exe -m pip install virtualenv
|
|
||||||
|
|
||||||
Now you can return above and :ref:`install-create-env`.
|
|
||||||
|
|
||||||
.. _virtualenv: https://virtualenv.pypa.io/
|
|
||||||
.. _get-pip.py: https://bootstrap.pypa.io/get-pip.py
|
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,6 @@ complex constructs that make larger applications easier to distribute:
|
||||||
other package.
|
other package.
|
||||||
- **installation manager**: :command:`pip` can install other libraries for you.
|
- **installation manager**: :command:`pip` can install other libraries for you.
|
||||||
|
|
||||||
If you have Python 2 (>=2.7.9) or Python 3 (>=3.4) installed from python.org,
|
|
||||||
you will already have pip and setuptools on your system. Otherwise, you
|
|
||||||
will need to install them yourself.
|
|
||||||
|
|
||||||
Flask itself, and all the libraries you can find on PyPI are distributed with
|
Flask itself, and all the libraries you can find on PyPI are distributed with
|
||||||
either setuptools or distutils.
|
either setuptools or distutils.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ error messages could be displayed with a red background.
|
||||||
To flash a message with a different category, just use the second argument
|
To flash a message with a different category, just use the second argument
|
||||||
to the :func:`~flask.flash` function::
|
to the :func:`~flask.flash` function::
|
||||||
|
|
||||||
flash(u'Invalid password provided', 'error')
|
flash('Invalid password provided', 'error')
|
||||||
|
|
||||||
Inside the template you then have to tell the
|
Inside the template you then have to tell the
|
||||||
:func:`~flask.get_flashed_messages` function to also return the
|
:func:`~flask.get_flashed_messages` function to also return the
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ write this by having a function that calls into
|
||||||
name and a dot, and by wrapping `view_func` in a `LazyView` as needed. ::
|
name and a dot, and by wrapping `view_func` in a `LazyView` as needed. ::
|
||||||
|
|
||||||
def url(import_name, url_rules=[], **options):
|
def url(import_name, url_rules=[], **options):
|
||||||
view = LazyView('yourapplication.' + import_name)
|
view = LazyView(f"yourapplication.{import_name}")
|
||||||
for url_rule in url_rules:
|
for url_rule in url_rules:
|
||||||
app.add_url_rule(url_rule, view_func=view, **options)
|
app.add_url_rule(url_rule, view_func=view, **options)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,4 +52,4 @@ Example usage::
|
||||||
files = request.files
|
files = request.files
|
||||||
# At this point the hash is fully constructed.
|
# At this point the hash is fully constructed.
|
||||||
checksum = hash.hexdigest()
|
checksum = hash.hexdigest()
|
||||||
return 'Hash was: %s' % checksum
|
return f"Hash was: {checksum}"
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ Here's the example :file:`database.py` module for your application::
|
||||||
from sqlalchemy.orm import scoped_session, sessionmaker
|
from sqlalchemy.orm import scoped_session, sessionmaker
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
|
||||||
engine = create_engine('sqlite:////tmp/test.db', convert_unicode=True)
|
engine = create_engine('sqlite:////tmp/test.db')
|
||||||
db_session = scoped_session(sessionmaker(autocommit=False,
|
db_session = scoped_session(sessionmaker(autocommit=False,
|
||||||
autoflush=False,
|
autoflush=False,
|
||||||
bind=engine))
|
bind=engine))
|
||||||
|
|
@ -104,9 +104,9 @@ You can insert entries into the database like this:
|
||||||
Querying is simple as well:
|
Querying is simple as well:
|
||||||
|
|
||||||
>>> User.query.all()
|
>>> User.query.all()
|
||||||
[<User u'admin'>]
|
[<User 'admin'>]
|
||||||
>>> User.query.filter(User.name == 'admin').first()
|
>>> User.query.filter(User.name == 'admin').first()
|
||||||
<User u'admin'>
|
<User 'admin'>
|
||||||
|
|
||||||
.. _SQLAlchemy: https://www.sqlalchemy.org/
|
.. _SQLAlchemy: https://www.sqlalchemy.org/
|
||||||
.. _declarative:
|
.. _declarative:
|
||||||
|
|
@ -127,7 +127,7 @@ Here is an example :file:`database.py` module for your application::
|
||||||
from sqlalchemy import create_engine, MetaData
|
from sqlalchemy import create_engine, MetaData
|
||||||
from sqlalchemy.orm import scoped_session, sessionmaker
|
from sqlalchemy.orm import scoped_session, sessionmaker
|
||||||
|
|
||||||
engine = create_engine('sqlite:////tmp/test.db', convert_unicode=True)
|
engine = create_engine('sqlite:////tmp/test.db')
|
||||||
metadata = MetaData()
|
metadata = MetaData()
|
||||||
db_session = scoped_session(sessionmaker(autocommit=False,
|
db_session = scoped_session(sessionmaker(autocommit=False,
|
||||||
autoflush=False,
|
autoflush=False,
|
||||||
|
|
@ -179,7 +179,7 @@ you basically only need the engine::
|
||||||
|
|
||||||
from sqlalchemy import create_engine, MetaData, Table
|
from sqlalchemy import create_engine, MetaData, Table
|
||||||
|
|
||||||
engine = create_engine('sqlite:////tmp/test.db', convert_unicode=True)
|
engine = create_engine('sqlite:////tmp/test.db')
|
||||||
metadata = MetaData(bind=engine)
|
metadata = MetaData(bind=engine)
|
||||||
|
|
||||||
Then you can either declare the tables in your code like in the examples
|
Then you can either declare the tables in your code like in the examples
|
||||||
|
|
@ -200,19 +200,19 @@ SQLAlchemy will automatically commit for us.
|
||||||
To query your database, you use the engine directly or use a connection:
|
To query your database, you use the engine directly or use a connection:
|
||||||
|
|
||||||
>>> users.select(users.c.id == 1).execute().first()
|
>>> users.select(users.c.id == 1).execute().first()
|
||||||
(1, u'admin', u'admin@localhost')
|
(1, 'admin', 'admin@localhost')
|
||||||
|
|
||||||
These results are also dict-like tuples:
|
These results are also dict-like tuples:
|
||||||
|
|
||||||
>>> r = users.select(users.c.id == 1).execute().first()
|
>>> r = users.select(users.c.id == 1).execute().first()
|
||||||
>>> r['name']
|
>>> r['name']
|
||||||
u'admin'
|
'admin'
|
||||||
|
|
||||||
You can also pass strings of SQL statements to the
|
You can also pass strings of SQL statements to the
|
||||||
:meth:`~sqlalchemy.engine.base.Connection.execute` method:
|
:meth:`~sqlalchemy.engine.base.Connection.execute` method:
|
||||||
|
|
||||||
>>> engine.execute('select * from users where id = :1', [1]).first()
|
>>> engine.execute('select * from users where id = :1', [1]).first()
|
||||||
(1, u'admin', u'admin@localhost')
|
(1, 'admin', 'admin@localhost')
|
||||||
|
|
||||||
For more information about SQLAlchemy, head over to the
|
For more information about SQLAlchemy, head over to the
|
||||||
`website <https://www.sqlalchemy.org/>`_.
|
`website <https://www.sqlalchemy.org/>`_.
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ data and to then invoke that function and pass it to a response object::
|
||||||
def generate_large_csv():
|
def generate_large_csv():
|
||||||
def generate():
|
def generate():
|
||||||
for row in iter_all_rows():
|
for row in iter_all_rows():
|
||||||
yield ','.join(row) + '\n'
|
yield f"{','.join(row)}\n"
|
||||||
return Response(generate(), mimetype='text/csv')
|
return Response(generate(), mimetype='text/csv')
|
||||||
|
|
||||||
Each ``yield`` expression is directly sent to the browser. Note though
|
Each ``yield`` expression is directly sent to the browser. Note though
|
||||||
|
|
|
||||||
|
|
@ -142,8 +142,7 @@ Here is the code for that decorator::
|
||||||
def decorated_function(*args, **kwargs):
|
def decorated_function(*args, **kwargs):
|
||||||
template_name = template
|
template_name = template
|
||||||
if template_name is None:
|
if template_name is None:
|
||||||
template_name = request.endpoint \
|
template_name = f"'{request.endpoint.replace('.', '/')}.html'"
|
||||||
.replace('.', '/') + '.html'
|
|
||||||
ctx = f(*args, **kwargs)
|
ctx = f(*args, **kwargs)
|
||||||
if ctx is None:
|
if ctx is None:
|
||||||
ctx = {}
|
ctx = {}
|
||||||
|
|
|
||||||
|
|
@ -98,9 +98,9 @@ This macro accepts a couple of keyword arguments that are forwarded to
|
||||||
WTForm's field function, which renders the field for us. The keyword
|
WTForm's field function, which renders the field for us. The keyword
|
||||||
arguments will be inserted as HTML attributes. So, for example, you can
|
arguments will be inserted as HTML attributes. So, for example, you can
|
||||||
call ``render_field(form.username, class='username')`` to add a class to
|
call ``render_field(form.username, class='username')`` to add a class to
|
||||||
the input element. Note that WTForms returns standard Python unicode
|
the input element. Note that WTForms returns standard Python strings,
|
||||||
strings, so we have to tell Jinja2 that this data is already HTML-escaped
|
so we have to tell Jinja2 that this data is already HTML-escaped with
|
||||||
with the ``|safe`` filter.
|
the ``|safe`` filter.
|
||||||
|
|
||||||
Here is the :file:`register.html` template for the function we used above, which
|
Here is the :file:`register.html` template for the function we used above, which
|
||||||
takes advantage of the :file:`_formhelpers.html` template:
|
takes advantage of the :file:`_formhelpers.html` template:
|
||||||
|
|
|
||||||
|
|
@ -293,8 +293,7 @@ Why would you want to build URLs using the URL reversing function
|
||||||
1. Reversing is often more descriptive than hard-coding the URLs.
|
1. Reversing is often more descriptive than hard-coding the URLs.
|
||||||
2. You can change your URLs in one go instead of needing to remember to
|
2. You can change your URLs in one go instead of needing to remember to
|
||||||
manually change hard-coded URLs.
|
manually change hard-coded URLs.
|
||||||
3. URL building handles escaping of special characters and Unicode data
|
3. URL building handles escaping of special characters transparently.
|
||||||
transparently.
|
|
||||||
4. The generated paths are always absolute, avoiding unexpected behavior
|
4. The generated paths are always absolute, avoiding unexpected behavior
|
||||||
of relative paths in browsers.
|
of relative paths in browsers.
|
||||||
5. If your application is placed outside the URL root, for example, in
|
5. If your application is placed outside the URL root, for example, in
|
||||||
|
|
@ -448,11 +447,11 @@ Here is a basic introduction to how the :class:`~markupsafe.Markup` class works:
|
||||||
|
|
||||||
>>> from markupsafe import Markup
|
>>> from markupsafe import Markup
|
||||||
>>> Markup('<strong>Hello %s!</strong>') % '<blink>hacker</blink>'
|
>>> Markup('<strong>Hello %s!</strong>') % '<blink>hacker</blink>'
|
||||||
Markup(u'<strong>Hello <blink>hacker</blink>!</strong>')
|
Markup('<strong>Hello <blink>hacker</blink>!</strong>')
|
||||||
>>> Markup.escape('<blink>hacker</blink>')
|
>>> Markup.escape('<blink>hacker</blink>')
|
||||||
Markup(u'<blink>hacker</blink>')
|
Markup('<blink>hacker</blink>')
|
||||||
>>> Markup('<em>Marked up</em> » HTML').striptags()
|
>>> Markup('<em>Marked up</em> » HTML').striptags()
|
||||||
u'Marked up \xbb HTML'
|
'Marked up \xbb HTML'
|
||||||
|
|
||||||
.. versionchanged:: 0.5
|
.. versionchanged:: 0.5
|
||||||
|
|
||||||
|
|
@ -610,8 +609,8 @@ Werkzeug provides for you::
|
||||||
@app.route('/upload', methods=['GET', 'POST'])
|
@app.route('/upload', methods=['GET', 'POST'])
|
||||||
def upload_file():
|
def upload_file():
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
f = request.files['the_file']
|
file = request.files['the_file']
|
||||||
f.save('/var/www/uploads/' + secure_filename(f.filename))
|
file.save(f"/var/www/uploads/{secure_filename(f.filename)}")
|
||||||
...
|
...
|
||||||
|
|
||||||
For some better examples, checkout the :ref:`uploading-files` pattern.
|
For some better examples, checkout the :ref:`uploading-files` pattern.
|
||||||
|
|
|
||||||
|
|
@ -1,200 +0,0 @@
|
||||||
Pocoo Styleguide
|
|
||||||
================
|
|
||||||
|
|
||||||
The Pocoo styleguide is the styleguide for all Pocoo Projects, including
|
|
||||||
Flask. This styleguide is a requirement for Patches to Flask and a
|
|
||||||
recommendation for Flask extensions.
|
|
||||||
|
|
||||||
In general the Pocoo Styleguide closely follows :pep:`8` with some small
|
|
||||||
differences and extensions.
|
|
||||||
|
|
||||||
General Layout
|
|
||||||
--------------
|
|
||||||
|
|
||||||
Indentation:
|
|
||||||
4 real spaces. No tabs, no exceptions.
|
|
||||||
|
|
||||||
Maximum line length:
|
|
||||||
79 characters with a soft limit for 84 if absolutely necessary. Try
|
|
||||||
to avoid too nested code by cleverly placing `break`, `continue` and
|
|
||||||
`return` statements.
|
|
||||||
|
|
||||||
Continuing long statements:
|
|
||||||
To continue a statement you can use backslashes in which case you should
|
|
||||||
align the next line with the last dot or equal sign, or indent four
|
|
||||||
spaces::
|
|
||||||
|
|
||||||
this_is_a_very_long(function_call, 'with many parameters') \
|
|
||||||
.that_returns_an_object_with_an_attribute
|
|
||||||
|
|
||||||
MyModel.query.filter(MyModel.scalar > 120) \
|
|
||||||
.order_by(MyModel.name.desc()) \
|
|
||||||
.limit(10)
|
|
||||||
|
|
||||||
If you break in a statement with parentheses or braces, align to the
|
|
||||||
braces::
|
|
||||||
|
|
||||||
this_is_a_very_long(function_call, 'with many parameters',
|
|
||||||
23, 42, 'and even more')
|
|
||||||
|
|
||||||
For lists or tuples with many items, break immediately after the
|
|
||||||
opening brace::
|
|
||||||
|
|
||||||
items = [
|
|
||||||
'this is the first', 'set of items', 'with more items',
|
|
||||||
'to come in this line', 'like this'
|
|
||||||
]
|
|
||||||
|
|
||||||
Blank lines:
|
|
||||||
Top level functions and classes are separated by two lines, everything
|
|
||||||
else by one. Do not use too many blank lines to separate logical
|
|
||||||
segments in code. Example::
|
|
||||||
|
|
||||||
def hello(name):
|
|
||||||
print f'Hello {name}!'
|
|
||||||
|
|
||||||
|
|
||||||
def goodbye(name):
|
|
||||||
print f'See you {name}.'
|
|
||||||
|
|
||||||
|
|
||||||
class MyClass(object):
|
|
||||||
"""This is a simple docstring"""
|
|
||||||
|
|
||||||
def __init__(self, name):
|
|
||||||
self.name = name
|
|
||||||
|
|
||||||
def get_annoying_name(self):
|
|
||||||
return self.name.upper() + '!!!!111'
|
|
||||||
|
|
||||||
Expressions and Statements
|
|
||||||
--------------------------
|
|
||||||
|
|
||||||
General whitespace rules:
|
|
||||||
- No whitespace for unary operators that are not words
|
|
||||||
(e.g.: ``-``, ``~`` etc.) as well on the inner side of parentheses.
|
|
||||||
- Whitespace is placed between binary operators.
|
|
||||||
|
|
||||||
Good::
|
|
||||||
|
|
||||||
exp = -1.05
|
|
||||||
value = (item_value / item_count) * offset / exp
|
|
||||||
value = my_list[index]
|
|
||||||
value = my_dict['key']
|
|
||||||
|
|
||||||
Bad::
|
|
||||||
|
|
||||||
exp = - 1.05
|
|
||||||
value = ( item_value / item_count ) * offset / exp
|
|
||||||
value = (item_value/item_count)*offset/exp
|
|
||||||
value=( item_value/item_count ) * offset/exp
|
|
||||||
value = my_list[ index ]
|
|
||||||
value = my_dict ['key']
|
|
||||||
|
|
||||||
Yoda statements are a no-go:
|
|
||||||
Never compare constant with variable, always variable with constant:
|
|
||||||
|
|
||||||
Good::
|
|
||||||
|
|
||||||
if method == 'md5':
|
|
||||||
pass
|
|
||||||
|
|
||||||
Bad::
|
|
||||||
|
|
||||||
if 'md5' == method:
|
|
||||||
pass
|
|
||||||
|
|
||||||
Comparisons:
|
|
||||||
- against arbitrary types: ``==`` and ``!=``
|
|
||||||
- against singletons with ``is`` and ``is not`` (eg: ``foo is not
|
|
||||||
None``)
|
|
||||||
- never compare something with ``True`` or ``False`` (for example never
|
|
||||||
do ``foo == False``, do ``not foo`` instead)
|
|
||||||
|
|
||||||
Negated containment checks:
|
|
||||||
use ``foo not in bar`` instead of ``not foo in bar``
|
|
||||||
|
|
||||||
Instance checks:
|
|
||||||
``isinstance(a, C)`` instead of ``type(A) is C``, but try to avoid
|
|
||||||
instance checks in general. Check for features.
|
|
||||||
|
|
||||||
|
|
||||||
Naming Conventions
|
|
||||||
------------------
|
|
||||||
|
|
||||||
- Class names: ``CamelCase``, with acronyms kept uppercase (``HTTPWriter``
|
|
||||||
and not ``HttpWriter``)
|
|
||||||
- Variable names: ``lowercase_with_underscores``
|
|
||||||
- Method and function names: ``lowercase_with_underscores``
|
|
||||||
- Constants: ``UPPERCASE_WITH_UNDERSCORES``
|
|
||||||
- precompiled regular expressions: ``name_re``
|
|
||||||
|
|
||||||
Protected members are prefixed with a single underscore. Double
|
|
||||||
underscores are reserved for mixin classes.
|
|
||||||
|
|
||||||
On classes with keywords, trailing underscores are appended. Clashes with
|
|
||||||
builtins are allowed and **must not** be resolved by appending an
|
|
||||||
underline to the variable name. If the function needs to access a
|
|
||||||
shadowed builtin, rebind the builtin to a different name instead.
|
|
||||||
|
|
||||||
Function and method arguments:
|
|
||||||
- class methods: ``cls`` as first parameter
|
|
||||||
- instance methods: ``self`` as first parameter
|
|
||||||
- lambdas for properties might have the first parameter replaced
|
|
||||||
with ``x`` like in ``display_name = property(lambda x: x.real_name
|
|
||||||
or x.username)``
|
|
||||||
|
|
||||||
|
|
||||||
Docstrings
|
|
||||||
----------
|
|
||||||
|
|
||||||
Docstring conventions:
|
|
||||||
All docstrings are formatted with reStructuredText as understood by
|
|
||||||
Sphinx. Depending on the number of lines in the docstring, they are
|
|
||||||
laid out differently. If it's just one line, the closing triple
|
|
||||||
quote is on the same line as the opening, otherwise the text is on
|
|
||||||
the same line as the opening quote and the triple quote that closes
|
|
||||||
the string on its own line::
|
|
||||||
|
|
||||||
def foo():
|
|
||||||
"""This is a simple docstring"""
|
|
||||||
|
|
||||||
|
|
||||||
def bar():
|
|
||||||
"""This is a longer docstring with so much information in there
|
|
||||||
that it spans three lines. In this case the closing triple quote
|
|
||||||
is on its own line.
|
|
||||||
"""
|
|
||||||
|
|
||||||
Module header:
|
|
||||||
The module header consists of a utf-8 encoding declaration (if non
|
|
||||||
ASCII letters are used, but it is recommended all the time) and a
|
|
||||||
standard docstring::
|
|
||||||
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
package.module
|
|
||||||
~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
A brief description goes here.
|
|
||||||
|
|
||||||
:copyright: (c) YEAR by AUTHOR.
|
|
||||||
:license: LICENSE_NAME, see LICENSE_FILE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
Please keep in mind that proper copyrights and license files are a
|
|
||||||
requirement for approved Flask extensions.
|
|
||||||
|
|
||||||
|
|
||||||
Comments
|
|
||||||
--------
|
|
||||||
|
|
||||||
Rules for comments are similar to docstrings. Both are formatted with
|
|
||||||
reStructuredText. If a comment is used to document an attribute, put a
|
|
||||||
colon after the opening pound sign (``#``)::
|
|
||||||
|
|
||||||
class User(object):
|
|
||||||
#: the name of the user as unicode string
|
|
||||||
name = Column(String)
|
|
||||||
#: the sha1 hash of the password + inline salt
|
|
||||||
pw_hash = Column(String)
|
|
||||||
|
|
@ -222,8 +222,8 @@ functions)::
|
||||||
|
|
||||||
@app.context_processor
|
@app.context_processor
|
||||||
def utility_processor():
|
def utility_processor():
|
||||||
def format_price(amount, currency=u'€'):
|
def format_price(amount, currency="€"):
|
||||||
return u'{0:.2f}{1}'.format(amount, currency)
|
return f"{amount:.2f}{currency}"
|
||||||
return dict(format_price=format_price)
|
return dict(format_price=format_price)
|
||||||
|
|
||||||
The context processor above makes the `format_price` function available to all
|
The context processor above makes the `format_price` function available to all
|
||||||
|
|
|
||||||
|
|
@ -165,16 +165,19 @@ invalid credentials. Add this new test function::
|
||||||
def test_login_logout(client):
|
def test_login_logout(client):
|
||||||
"""Make sure login and logout works."""
|
"""Make sure login and logout works."""
|
||||||
|
|
||||||
rv = login(client, flaskr.app.config['USERNAME'], flaskr.app.config['PASSWORD'])
|
username = flaskr.app.config["USERNAME"]
|
||||||
|
password = flaskr.app.config["PASSWORD"]
|
||||||
|
|
||||||
|
rv = login(client, username, password)
|
||||||
assert b'You were logged in' in rv.data
|
assert b'You were logged in' in rv.data
|
||||||
|
|
||||||
rv = logout(client)
|
rv = logout(client)
|
||||||
assert b'You were logged out' in rv.data
|
assert b'You were logged out' in rv.data
|
||||||
|
|
||||||
rv = login(client, flaskr.app.config['USERNAME'] + 'x', flaskr.app.config['PASSWORD'])
|
rv = login(client, f"{username}x", password)
|
||||||
assert b'Invalid username' in rv.data
|
assert b'Invalid username' in rv.data
|
||||||
|
|
||||||
rv = login(client, flaskr.app.config['USERNAME'], flaskr.app.config['PASSWORD'] + 'x')
|
rv = login(client, username, f'{password}x')
|
||||||
assert b'Invalid password' in rv.data
|
assert b'Invalid password' in rv.data
|
||||||
|
|
||||||
Test Adding Messages
|
Test Adding Messages
|
||||||
|
|
|
||||||
|
|
@ -207,7 +207,7 @@ it from each view.
|
||||||
).fetchone()
|
).fetchone()
|
||||||
|
|
||||||
if post is None:
|
if post is None:
|
||||||
abort(404, "Post id {0} doesn't exist.".format(id))
|
abort(404, f"Post id {id} doesn't exist.")
|
||||||
|
|
||||||
if check_author and post['author_id'] != g.user['id']:
|
if check_author and post['author_id'] != g.user['id']:
|
||||||
abort(403)
|
abort(403)
|
||||||
|
|
|
||||||
|
|
@ -301,8 +301,8 @@ URL when the register view redirects to the login view.
|
||||||
|
|
||||||
:attr:`~Response.data` contains the body of the response as bytes. If
|
:attr:`~Response.data` contains the body of the response as bytes. If
|
||||||
you expect a certain value to render on the page, check that it's in
|
you expect a certain value to render on the page, check that it's in
|
||||||
``data``. Bytes must be compared to bytes. If you want to compare
|
``data``. Bytes must be compared to bytes. If you want to compare text,
|
||||||
Unicode text, use :meth:`get_data(as_text=True) <werkzeug.wrappers.BaseResponse.get_data>`
|
use :meth:`get_data(as_text=True) <werkzeug.wrappers.BaseResponse.get_data>`
|
||||||
instead.
|
instead.
|
||||||
|
|
||||||
``pytest.mark.parametrize`` tells Pytest to run the same test function
|
``pytest.mark.parametrize`` tells Pytest to run the same test function
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ write templates to generate the HTML form.
|
||||||
elif db.execute(
|
elif db.execute(
|
||||||
'SELECT id FROM user WHERE username = ?', (username,)
|
'SELECT id FROM user WHERE username = ?', (username,)
|
||||||
).fetchone() is not None:
|
).fetchone() is not None:
|
||||||
error = 'User {} is already registered.'.format(username)
|
error = f"User {username} is already registered."
|
||||||
|
|
||||||
if error is None:
|
if error is None:
|
||||||
db.execute(
|
db.execute(
|
||||||
|
|
|
||||||
107
docs/unicode.rst
107
docs/unicode.rst
|
|
@ -1,107 +0,0 @@
|
||||||
Unicode in Flask
|
|
||||||
================
|
|
||||||
|
|
||||||
Flask, like Jinja2 and Werkzeug, is totally Unicode based when it comes to
|
|
||||||
text. Not only these libraries, also the majority of web related Python
|
|
||||||
libraries that deal with text. If you don't know Unicode so far, you
|
|
||||||
should probably read `The Absolute Minimum Every Software Developer
|
|
||||||
Absolutely, Positively Must Know About Unicode and Character Sets
|
|
||||||
<https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/>`_.
|
|
||||||
This part of the documentation just tries to cover the very basics so
|
|
||||||
that you have a pleasant experience with Unicode related things.
|
|
||||||
|
|
||||||
Automatic Conversion
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
Flask has a few assumptions about your application (which you can change
|
|
||||||
of course) that give you basic and painless Unicode support:
|
|
||||||
|
|
||||||
- the encoding for text on your website is UTF-8
|
|
||||||
- internally you will always use Unicode exclusively for text except
|
|
||||||
for literal strings with only ASCII character points.
|
|
||||||
- encoding and decoding happens whenever you are talking over a protocol
|
|
||||||
that requires bytes to be transmitted.
|
|
||||||
|
|
||||||
So what does this mean to you?
|
|
||||||
|
|
||||||
HTTP is based on bytes. Not only the protocol, also the system used to
|
|
||||||
address documents on servers (so called URIs or URLs). However HTML which
|
|
||||||
is usually transmitted on top of HTTP supports a large variety of
|
|
||||||
character sets and which ones are used, are transmitted in an HTTP header.
|
|
||||||
To not make this too complex Flask just assumes that if you are sending
|
|
||||||
Unicode out you want it to be UTF-8 encoded. Flask will do the encoding
|
|
||||||
and setting of the appropriate headers for you.
|
|
||||||
|
|
||||||
The same is true if you are talking to databases with the help of
|
|
||||||
SQLAlchemy or a similar ORM system. Some databases have a protocol that
|
|
||||||
already transmits Unicode and if they do not, SQLAlchemy or your other ORM
|
|
||||||
should take care of that.
|
|
||||||
|
|
||||||
The Golden Rule
|
|
||||||
---------------
|
|
||||||
|
|
||||||
So the rule of thumb: if you are not dealing with binary data, work with
|
|
||||||
Unicode. What does working with Unicode in Python 2.x mean?
|
|
||||||
|
|
||||||
- as long as you are using ASCII code points only (basically numbers,
|
|
||||||
some special characters of Latin letters without umlauts or anything
|
|
||||||
fancy) you can use regular string literals (``'Hello World'``).
|
|
||||||
- if you need anything else than ASCII in a string you have to mark
|
|
||||||
this string as Unicode string by prefixing it with a lowercase `u`.
|
|
||||||
(like ``u'Hänsel und Gretel'``)
|
|
||||||
- if you are using non-Unicode characters in your Python files you have
|
|
||||||
to tell Python which encoding your file uses. Again, I recommend
|
|
||||||
UTF-8 for this purpose. To tell the interpreter your encoding you can
|
|
||||||
put the ``# -*- coding: utf-8 -*-`` into the first or second line of
|
|
||||||
your Python source file.
|
|
||||||
- Jinja is configured to decode the template files from UTF-8. So make
|
|
||||||
sure to tell your editor to save the file as UTF-8 there as well.
|
|
||||||
|
|
||||||
Encoding and Decoding Yourself
|
|
||||||
------------------------------
|
|
||||||
|
|
||||||
If you are talking with a filesystem or something that is not really based
|
|
||||||
on Unicode you will have to ensure that you decode properly when working
|
|
||||||
with Unicode interface. So for example if you want to load a file on the
|
|
||||||
filesystem and embed it into a Jinja2 template you will have to decode it
|
|
||||||
from the encoding of that file. Here the old problem that text files do
|
|
||||||
not specify their encoding comes into play. So do yourself a favour and
|
|
||||||
limit yourself to UTF-8 for text files as well.
|
|
||||||
|
|
||||||
Anyways. To load such a file with Unicode you can use the built-in
|
|
||||||
:meth:`str.decode` method::
|
|
||||||
|
|
||||||
def read_file(filename, charset='utf-8'):
|
|
||||||
with open(filename, 'r') as f:
|
|
||||||
return f.read().decode(charset)
|
|
||||||
|
|
||||||
To go from Unicode into a specific charset such as UTF-8 you can use the
|
|
||||||
:meth:`unicode.encode` method::
|
|
||||||
|
|
||||||
def write_file(filename, contents, charset='utf-8'):
|
|
||||||
with open(filename, 'w') as f:
|
|
||||||
f.write(contents.encode(charset))
|
|
||||||
|
|
||||||
Configuring Editors
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
Most editors save as UTF-8 by default nowadays but in case your editor is
|
|
||||||
not configured to do this you have to change it. Here some common ways to
|
|
||||||
set your editor to store as UTF-8:
|
|
||||||
|
|
||||||
- Vim: put ``set enc=utf-8`` to your ``.vimrc`` file.
|
|
||||||
|
|
||||||
- Emacs: either use an encoding cookie or put this into your ``.emacs``
|
|
||||||
file::
|
|
||||||
|
|
||||||
(prefer-coding-system 'utf-8)
|
|
||||||
(setq default-buffer-file-coding-system 'utf-8)
|
|
||||||
|
|
||||||
- Notepad++:
|
|
||||||
|
|
||||||
1. Go to *Settings -> Preferences ...*
|
|
||||||
2. Select the "New Document/Default Directory" tab
|
|
||||||
3. Select "UTF-8 without BOM" as encoding
|
|
||||||
|
|
||||||
It is also recommended to use the Unix newline format, you can select
|
|
||||||
it in the same panel but this is not a requirement.
|
|
||||||
|
|
@ -1,472 +0,0 @@
|
||||||
Upgrading to Newer Releases
|
|
||||||
===========================
|
|
||||||
|
|
||||||
Flask itself is changing like any software is changing over time. Most of
|
|
||||||
the changes are the nice kind, the kind where you don't have to change
|
|
||||||
anything in your code to profit from a new release.
|
|
||||||
|
|
||||||
However every once in a while there are changes that do require some
|
|
||||||
changes in your code or there are changes that make it possible for you to
|
|
||||||
improve your own code quality by taking advantage of new features in
|
|
||||||
Flask.
|
|
||||||
|
|
||||||
This section of the documentation enumerates all the changes in Flask from
|
|
||||||
release to release and how you can change your code to have a painless
|
|
||||||
updating experience.
|
|
||||||
|
|
||||||
Use the :command:`pip` command to upgrade your existing Flask installation by
|
|
||||||
providing the ``--upgrade`` parameter::
|
|
||||||
|
|
||||||
$ pip install --upgrade Flask
|
|
||||||
|
|
||||||
.. _upgrading-to-012:
|
|
||||||
|
|
||||||
Version 0.12
|
|
||||||
------------
|
|
||||||
|
|
||||||
Changes to send_file
|
|
||||||
````````````````````
|
|
||||||
|
|
||||||
The ``filename`` is no longer automatically inferred from file-like objects.
|
|
||||||
This means that the following code will no longer automatically have
|
|
||||||
``X-Sendfile`` support, etag generation or MIME-type guessing::
|
|
||||||
|
|
||||||
response = send_file(open('/path/to/file.txt'))
|
|
||||||
|
|
||||||
Any of the following is functionally equivalent::
|
|
||||||
|
|
||||||
fname = '/path/to/file.txt'
|
|
||||||
|
|
||||||
# Just pass the filepath directly
|
|
||||||
response = send_file(fname)
|
|
||||||
|
|
||||||
# Set the MIME-type and ETag explicitly
|
|
||||||
response = send_file(open(fname), mimetype='text/plain')
|
|
||||||
response.set_etag(...)
|
|
||||||
|
|
||||||
# Set `attachment_filename` for MIME-type guessing
|
|
||||||
# ETag still needs to be manually set
|
|
||||||
response = send_file(open(fname), attachment_filename=fname)
|
|
||||||
response.set_etag(...)
|
|
||||||
|
|
||||||
The reason for this is that some file-like objects have an invalid or even
|
|
||||||
misleading ``name`` attribute. Silently swallowing errors in such cases was not
|
|
||||||
a satisfying solution.
|
|
||||||
|
|
||||||
Additionally the default of falling back to ``application/octet-stream`` has
|
|
||||||
been restricted. If Flask can't guess one or the user didn't provide one, the
|
|
||||||
function fails if no filename information was provided.
|
|
||||||
|
|
||||||
.. _upgrading-to-011:
|
|
||||||
|
|
||||||
Version 0.11
|
|
||||||
------------
|
|
||||||
|
|
||||||
0.11 is an odd release in the Flask release cycle because it was supposed
|
|
||||||
to be the 1.0 release. However because there was such a long lead time up
|
|
||||||
to the release we decided to push out a 0.11 release first with some
|
|
||||||
changes removed to make the transition easier. If you have been tracking
|
|
||||||
the master branch which was 1.0 you might see some unexpected changes.
|
|
||||||
|
|
||||||
In case you did track the master branch you will notice that
|
|
||||||
:command:`flask --app` is removed now.
|
|
||||||
You need to use the environment variable to specify an application.
|
|
||||||
|
|
||||||
Debugging
|
|
||||||
`````````
|
|
||||||
|
|
||||||
Flask 0.11 removed the ``debug_log_format`` attribute from Flask
|
|
||||||
applications. Instead the new ``LOGGER_HANDLER_POLICY`` configuration can
|
|
||||||
be used to disable the default log handlers and custom log handlers can be
|
|
||||||
set up.
|
|
||||||
|
|
||||||
Error handling
|
|
||||||
``````````````
|
|
||||||
|
|
||||||
The behavior of error handlers was changed.
|
|
||||||
The precedence of handlers used to be based on the decoration/call order of
|
|
||||||
:meth:`~flask.Flask.errorhandler` and
|
|
||||||
:meth:`~flask.Flask.register_error_handler`, respectively.
|
|
||||||
Now the inheritance hierarchy takes precedence and handlers for more
|
|
||||||
specific exception classes are executed instead of more general ones.
|
|
||||||
See :ref:`error-handlers` for specifics.
|
|
||||||
|
|
||||||
Trying to register a handler on an instance now raises :exc:`ValueError`.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
There used to be a logic error allowing you to register handlers
|
|
||||||
only for exception *instances*. This was unintended and plain wrong,
|
|
||||||
and therefore was replaced with the intended behavior of registering
|
|
||||||
handlers only using exception classes and HTTP error codes.
|
|
||||||
|
|
||||||
Templating
|
|
||||||
``````````
|
|
||||||
|
|
||||||
The :func:`~flask.templating.render_template_string` function has changed to
|
|
||||||
autoescape template variables by default. This better matches the behavior
|
|
||||||
of :func:`~flask.templating.render_template`.
|
|
||||||
|
|
||||||
Extension imports
|
|
||||||
`````````````````
|
|
||||||
|
|
||||||
Extension imports of the form ``flask.ext.foo`` are deprecated, you should use
|
|
||||||
``flask_foo``.
|
|
||||||
|
|
||||||
The old form still works, but Flask will issue a
|
|
||||||
``flask.exthook.ExtDeprecationWarning`` for each extension you import the old
|
|
||||||
way. We also provide a migration utility called `flask-ext-migrate
|
|
||||||
<https://github.com/pallets/flask-ext-migrate>`_ that is supposed to
|
|
||||||
automatically rewrite your imports for this.
|
|
||||||
|
|
||||||
.. _upgrading-to-010:
|
|
||||||
|
|
||||||
Version 0.10
|
|
||||||
------------
|
|
||||||
|
|
||||||
The biggest change going from 0.9 to 0.10 is that the cookie serialization
|
|
||||||
format changed from pickle to a specialized JSON format. This change has
|
|
||||||
been done in order to avoid the damage an attacker can do if the secret
|
|
||||||
key is leaked. When you upgrade you will notice two major changes: all
|
|
||||||
sessions that were issued before the upgrade are invalidated and you can
|
|
||||||
only store a limited amount of types in the session. The new sessions are
|
|
||||||
by design much more restricted to only allow JSON with a few small
|
|
||||||
extensions for tuples and strings with HTML markup.
|
|
||||||
|
|
||||||
In order to not break people's sessions it is possible to continue using
|
|
||||||
the old session system by using the `Flask-OldSessions`_ extension.
|
|
||||||
|
|
||||||
Flask also started storing the :data:`flask.g` object on the application
|
|
||||||
context instead of the request context. This change should be transparent
|
|
||||||
for you but it means that you now can store things on the ``g`` object
|
|
||||||
when there is no request context yet but an application context. The old
|
|
||||||
``flask.Flask.request_globals_class`` attribute was renamed to
|
|
||||||
:attr:`flask.Flask.app_ctx_globals_class`.
|
|
||||||
|
|
||||||
.. _Flask-OldSessions: https://pythonhosted.org/Flask-OldSessions/
|
|
||||||
|
|
||||||
Version 0.9
|
|
||||||
-----------
|
|
||||||
|
|
||||||
The behavior of returning tuples from a function was simplified. If you
|
|
||||||
return a tuple it no longer defines the arguments for the response object
|
|
||||||
you're creating, it's now always a tuple in the form ``(response, status,
|
|
||||||
headers)`` where at least one item has to be provided. If you depend on
|
|
||||||
the old behavior, you can add it easily by subclassing Flask::
|
|
||||||
|
|
||||||
class TraditionalFlask(Flask):
|
|
||||||
def make_response(self, rv):
|
|
||||||
if isinstance(rv, tuple):
|
|
||||||
return self.response_class(*rv)
|
|
||||||
return Flask.make_response(self, rv)
|
|
||||||
|
|
||||||
If you maintain an extension that was using :data:`~flask._request_ctx_stack`
|
|
||||||
before, please consider changing to :data:`~flask._app_ctx_stack` if it makes
|
|
||||||
sense for your extension. For instance, the app context stack makes sense for
|
|
||||||
extensions which connect to databases. Using the app context stack instead of
|
|
||||||
the request context stack will make extensions more readily handle use cases
|
|
||||||
outside of requests.
|
|
||||||
|
|
||||||
Version 0.8
|
|
||||||
-----------
|
|
||||||
|
|
||||||
Flask introduced a new session interface system. We also noticed that
|
|
||||||
there was a naming collision between ``flask.session`` the module that
|
|
||||||
implements sessions and :data:`flask.session` which is the global session
|
|
||||||
object. With that introduction we moved the implementation details for
|
|
||||||
the session system into a new module called :mod:`flask.sessions`. If you
|
|
||||||
used the previously undocumented session support we urge you to upgrade.
|
|
||||||
|
|
||||||
If invalid JSON data was submitted Flask will now raise a
|
|
||||||
:exc:`~werkzeug.exceptions.BadRequest` exception instead of letting the
|
|
||||||
default :exc:`ValueError` bubble up. This has the advantage that you no
|
|
||||||
longer have to handle that error to avoid an internal server error showing
|
|
||||||
up for the user. If you were catching this down explicitly in the past
|
|
||||||
as :exc:`ValueError` you will need to change this.
|
|
||||||
|
|
||||||
Due to a bug in the test client Flask 0.7 did not trigger teardown
|
|
||||||
handlers when the test client was used in a with statement. This was
|
|
||||||
since fixed but might require some changes in your test suites if you
|
|
||||||
relied on this behavior.
|
|
||||||
|
|
||||||
Version 0.7
|
|
||||||
-----------
|
|
||||||
|
|
||||||
In Flask 0.7 we cleaned up the code base internally a lot and did some
|
|
||||||
backwards incompatible changes that make it easier to implement larger
|
|
||||||
applications with Flask. Because we want to make upgrading as easy as
|
|
||||||
possible we tried to counter the problems arising from these changes by
|
|
||||||
providing a script that can ease the transition.
|
|
||||||
|
|
||||||
The script scans your whole application and generates a unified diff with
|
|
||||||
changes it assumes are safe to apply. However as this is an automated
|
|
||||||
tool it won't be able to find all use cases and it might miss some. We
|
|
||||||
internally spread a lot of deprecation warnings all over the place to make
|
|
||||||
it easy to find pieces of code that it was unable to upgrade.
|
|
||||||
|
|
||||||
We strongly recommend that you hand review the generated patchfile and
|
|
||||||
only apply the chunks that look good.
|
|
||||||
|
|
||||||
If you are using git as version control system for your project we
|
|
||||||
recommend applying the patch with ``path -p1 < patchfile.diff`` and then
|
|
||||||
using the interactive commit feature to only apply the chunks that look
|
|
||||||
good.
|
|
||||||
|
|
||||||
To apply the upgrade script do the following:
|
|
||||||
|
|
||||||
1. Download the script: `flask-07-upgrade.py
|
|
||||||
<https://raw.githubusercontent.com/pallets/flask/0.12.3/scripts/flask-07-upgrade.py>`_
|
|
||||||
2. Run it in the directory of your application::
|
|
||||||
|
|
||||||
$ python flask-07-upgrade.py > patchfile.diff
|
|
||||||
|
|
||||||
3. Review the generated patchfile.
|
|
||||||
4. Apply the patch::
|
|
||||||
|
|
||||||
$ patch -p1 < patchfile.diff
|
|
||||||
|
|
||||||
5. If you were using per-module template folders you need to move some
|
|
||||||
templates around. Previously if you had a folder named :file:`templates`
|
|
||||||
next to a blueprint named ``admin`` the implicit template path
|
|
||||||
automatically was :file:`admin/index.html` for a template file called
|
|
||||||
:file:`templates/index.html`. This no longer is the case. Now you need
|
|
||||||
to name the template :file:`templates/admin/index.html`. The tool will
|
|
||||||
not detect this so you will have to do that on your own.
|
|
||||||
|
|
||||||
Please note that deprecation warnings are disabled by default starting
|
|
||||||
with Python 2.7. In order to see the deprecation warnings that might be
|
|
||||||
emitted you have to enabled them with the :mod:`warnings` module.
|
|
||||||
|
|
||||||
If you are working with windows and you lack the ``patch`` command line
|
|
||||||
utility you can get it as part of various Unix runtime environments for
|
|
||||||
windows including cygwin, msysgit or ming32. Also source control systems
|
|
||||||
like svn, hg or git have builtin support for applying unified diffs as
|
|
||||||
generated by the tool. Check the manual of your version control system
|
|
||||||
for more information.
|
|
||||||
|
|
||||||
Bug in Request Locals
|
|
||||||
`````````````````````
|
|
||||||
|
|
||||||
Due to a bug in earlier implementations the request local proxies now
|
|
||||||
raise a :exc:`RuntimeError` instead of an :exc:`AttributeError` when they
|
|
||||||
are unbound. If you caught these exceptions with :exc:`AttributeError`
|
|
||||||
before, you should catch them with :exc:`RuntimeError` now.
|
|
||||||
|
|
||||||
Additionally the :func:`~flask.send_file` function is now issuing
|
|
||||||
deprecation warnings if you depend on functionality that will be removed
|
|
||||||
in Flask 0.11. Previously it was possible to use etags and mimetypes
|
|
||||||
when file objects were passed. This was unreliable and caused issues
|
|
||||||
for a few setups. If you get a deprecation warning, make sure to
|
|
||||||
update your application to work with either filenames there or disable
|
|
||||||
etag attaching and attach them yourself.
|
|
||||||
|
|
||||||
Old code::
|
|
||||||
|
|
||||||
return send_file(my_file_object)
|
|
||||||
return send_file(my_file_object)
|
|
||||||
|
|
||||||
New code::
|
|
||||||
|
|
||||||
return send_file(my_file_object, add_etags=False)
|
|
||||||
|
|
||||||
.. _upgrading-to-new-teardown-handling:
|
|
||||||
|
|
||||||
Upgrading to new Teardown Handling
|
|
||||||
``````````````````````````````````
|
|
||||||
|
|
||||||
We streamlined the behavior of the callbacks for request handling. For
|
|
||||||
things that modify the response the :meth:`~flask.Flask.after_request`
|
|
||||||
decorators continue to work as expected, but for things that absolutely
|
|
||||||
must happen at the end of request we introduced the new
|
|
||||||
:meth:`~flask.Flask.teardown_request` decorator. Unfortunately that
|
|
||||||
change also made after-request work differently under error conditions.
|
|
||||||
It's not consistently skipped if exceptions happen whereas previously it
|
|
||||||
might have been called twice to ensure it is executed at the end of the
|
|
||||||
request.
|
|
||||||
|
|
||||||
If you have database connection code that looks like this::
|
|
||||||
|
|
||||||
@app.after_request
|
|
||||||
def after_request(response):
|
|
||||||
g.db.close()
|
|
||||||
return response
|
|
||||||
|
|
||||||
You are now encouraged to use this instead::
|
|
||||||
|
|
||||||
@app.teardown_request
|
|
||||||
def after_request(exception):
|
|
||||||
if hasattr(g, 'db'):
|
|
||||||
g.db.close()
|
|
||||||
|
|
||||||
On the upside this change greatly improves the internal code flow and
|
|
||||||
makes it easier to customize the dispatching and error handling. This
|
|
||||||
makes it now a lot easier to write unit tests as you can prevent closing
|
|
||||||
down of database connections for a while. You can take advantage of the
|
|
||||||
fact that the teardown callbacks are called when the response context is
|
|
||||||
removed from the stack so a test can query the database after request
|
|
||||||
handling::
|
|
||||||
|
|
||||||
with app.test_client() as client:
|
|
||||||
resp = client.get('/')
|
|
||||||
# g.db is still bound if there is such a thing
|
|
||||||
|
|
||||||
# and here it's gone
|
|
||||||
|
|
||||||
Manual Error Handler Attaching
|
|
||||||
``````````````````````````````
|
|
||||||
|
|
||||||
While it is still possible to attach error handlers to
|
|
||||||
:attr:`Flask.error_handlers` it's discouraged to do so and in fact
|
|
||||||
deprecated. In general we no longer recommend custom error handler
|
|
||||||
attaching via assignments to the underlying dictionary due to the more
|
|
||||||
complex internal handling to support arbitrary exception classes and
|
|
||||||
blueprints. See :meth:`Flask.errorhandler` for more information.
|
|
||||||
|
|
||||||
The proper upgrade is to change this::
|
|
||||||
|
|
||||||
app.error_handlers[403] = handle_error
|
|
||||||
|
|
||||||
Into this::
|
|
||||||
|
|
||||||
app.register_error_handler(403, handle_error)
|
|
||||||
|
|
||||||
Alternatively you should just attach the function with a decorator::
|
|
||||||
|
|
||||||
@app.errorhandler(403)
|
|
||||||
def handle_error(e):
|
|
||||||
...
|
|
||||||
|
|
||||||
(Note that :meth:`register_error_handler` is new in Flask 0.7)
|
|
||||||
|
|
||||||
Blueprint Support
|
|
||||||
`````````````````
|
|
||||||
|
|
||||||
Blueprints replace the previous concept of “Modules” in Flask. They
|
|
||||||
provide better semantics for various features and work better with large
|
|
||||||
applications. The update script provided should be able to upgrade your
|
|
||||||
applications automatically, but there might be some cases where it fails
|
|
||||||
to upgrade. What changed?
|
|
||||||
|
|
||||||
- Blueprints need explicit names. Modules had an automatic name
|
|
||||||
guessing scheme where the shortname for the module was taken from the
|
|
||||||
last part of the import module. The upgrade script tries to guess
|
|
||||||
that name but it might fail as this information could change at
|
|
||||||
runtime.
|
|
||||||
- Blueprints have an inverse behavior for :meth:`url_for`. Previously
|
|
||||||
``.foo`` told :meth:`url_for` that it should look for the endpoint
|
|
||||||
``foo`` on the application. Now it means “relative to current module”.
|
|
||||||
The script will inverse all calls to :meth:`url_for` automatically for
|
|
||||||
you. It will do this in a very eager way so you might end up with
|
|
||||||
some unnecessary leading dots in your code if you're not using
|
|
||||||
modules.
|
|
||||||
- Blueprints do not automatically provide static folders. They will
|
|
||||||
also no longer automatically export templates from a folder called
|
|
||||||
:file:`templates` next to their location however but it can be enabled from
|
|
||||||
the constructor. Same with static files: if you want to continue
|
|
||||||
serving static files you need to tell the constructor explicitly the
|
|
||||||
path to the static folder (which can be relative to the blueprint's
|
|
||||||
module path).
|
|
||||||
- Rendering templates was simplified. Now the blueprints can provide
|
|
||||||
template folders which are added to a general template searchpath.
|
|
||||||
This means that you need to add another subfolder with the blueprint's
|
|
||||||
name into that folder if you want :file:`blueprintname/template.html` as
|
|
||||||
the template name.
|
|
||||||
|
|
||||||
If you continue to use the ``Module`` object which is deprecated, Flask will
|
|
||||||
restore the previous behavior as good as possible. However we strongly
|
|
||||||
recommend upgrading to the new blueprints as they provide a lot of useful
|
|
||||||
improvement such as the ability to attach a blueprint multiple times,
|
|
||||||
blueprint specific error handlers and a lot more.
|
|
||||||
|
|
||||||
|
|
||||||
Version 0.6
|
|
||||||
-----------
|
|
||||||
|
|
||||||
Flask 0.6 comes with a backwards incompatible change which affects the
|
|
||||||
order of after-request handlers. Previously they were called in the order
|
|
||||||
of the registration, now they are called in reverse order. This change
|
|
||||||
was made so that Flask behaves more like people expected it to work and
|
|
||||||
how other systems handle request pre- and post-processing. If you
|
|
||||||
depend on the order of execution of post-request functions, be sure to
|
|
||||||
change the order.
|
|
||||||
|
|
||||||
Another change that breaks backwards compatibility is that context
|
|
||||||
processors will no longer override values passed directly to the template
|
|
||||||
rendering function. If for example ``request`` is as variable passed
|
|
||||||
directly to the template, the default context processor will not override
|
|
||||||
it with the current request object. This makes it easier to extend
|
|
||||||
context processors later to inject additional variables without breaking
|
|
||||||
existing template not expecting them.
|
|
||||||
|
|
||||||
Version 0.5
|
|
||||||
-----------
|
|
||||||
|
|
||||||
Flask 0.5 is the first release that comes as a Python package instead of a
|
|
||||||
single module. There were a couple of internal refactoring so if you
|
|
||||||
depend on undocumented internal details you probably have to adapt the
|
|
||||||
imports.
|
|
||||||
|
|
||||||
The following changes may be relevant to your application:
|
|
||||||
|
|
||||||
- autoescaping no longer happens for all templates. Instead it is
|
|
||||||
configured to only happen on files ending with ``.html``, ``.htm``,
|
|
||||||
``.xml`` and ``.xhtml``. If you have templates with different
|
|
||||||
extensions you should override the
|
|
||||||
:meth:`~flask.Flask.select_jinja_autoescape` method.
|
|
||||||
- Flask no longer supports zipped applications in this release. This
|
|
||||||
functionality might come back in future releases if there is demand
|
|
||||||
for this feature. Removing support for this makes the Flask internal
|
|
||||||
code easier to understand and fixes a couple of small issues that make
|
|
||||||
debugging harder than necessary.
|
|
||||||
- The ``create_jinja_loader`` function is gone. If you want to customize
|
|
||||||
the Jinja loader now, use the
|
|
||||||
:meth:`~flask.Flask.create_jinja_environment` method instead.
|
|
||||||
|
|
||||||
Version 0.4
|
|
||||||
-----------
|
|
||||||
|
|
||||||
For application developers there are no changes that require changes in
|
|
||||||
your code. In case you are developing on a Flask extension however, and
|
|
||||||
that extension has a unittest-mode you might want to link the activation
|
|
||||||
of that mode to the new ``TESTING`` flag.
|
|
||||||
|
|
||||||
Version 0.3
|
|
||||||
-----------
|
|
||||||
|
|
||||||
Flask 0.3 introduces configuration support and logging as well as
|
|
||||||
categories for flashing messages. All these are features that are 100%
|
|
||||||
backwards compatible but you might want to take advantage of them.
|
|
||||||
|
|
||||||
Configuration Support
|
|
||||||
`````````````````````
|
|
||||||
|
|
||||||
The configuration support makes it easier to write any kind of application
|
|
||||||
that requires some sort of configuration. (Which most likely is the case
|
|
||||||
for any application out there).
|
|
||||||
|
|
||||||
If you previously had code like this::
|
|
||||||
|
|
||||||
app.debug = DEBUG
|
|
||||||
app.secret_key = SECRET_KEY
|
|
||||||
|
|
||||||
You no longer have to do that, instead you can just load a configuration
|
|
||||||
into the config object. How this works is outlined in :ref:`config`.
|
|
||||||
|
|
||||||
Logging Integration
|
|
||||||
```````````````````
|
|
||||||
|
|
||||||
Flask now configures a logger for you with some basic and useful defaults.
|
|
||||||
If you run your application in production and want to profit from
|
|
||||||
automatic error logging, you might be interested in attaching a proper log
|
|
||||||
handler. Also you can start logging warnings and errors into the logger
|
|
||||||
when appropriately. For more information on that, read
|
|
||||||
:ref:`application-errors`.
|
|
||||||
|
|
||||||
Categories for Flash Messages
|
|
||||||
`````````````````````````````
|
|
||||||
|
|
||||||
Flash messages can now have categories attached. This makes it possible
|
|
||||||
to render errors, warnings or regular messages differently for example.
|
|
||||||
This is an opt-in feature because it requires some rethinking in the code.
|
|
||||||
|
|
||||||
Read all about that in the :ref:`message-flashing-pattern` pattern.
|
|
||||||
|
|
@ -2,4 +2,4 @@ from flask import Flask
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
from js_example import views
|
from js_example import views # noqa: F401
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ from js_example import app
|
||||||
@app.route("/", defaults={"js": "plain"})
|
@app.route("/", defaults={"js": "plain"})
|
||||||
@app.route("/<any(plain, jquery, fetch):js>")
|
@app.route("/<any(plain, jquery, fetch):js>")
|
||||||
def index(js):
|
def index(js):
|
||||||
return render_template("{0}.html".format(js), js=js)
|
return render_template(f"{js}.html", js=js)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/add", methods=["POST"])
|
@app.route("/add", methods=["POST"])
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
import io
|
|
||||||
|
|
||||||
from setuptools import find_packages
|
from setuptools import find_packages
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
|
||||||
with io.open("README.rst", "rt", encoding="utf8") as f:
|
with open("README.rst", encoding="utf8") as f:
|
||||||
readme = f.read()
|
readme = f.read()
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ def register():
|
||||||
db.execute("SELECT id FROM user WHERE username = ?", (username,)).fetchone()
|
db.execute("SELECT id FROM user WHERE username = ?", (username,)).fetchone()
|
||||||
is not None
|
is not None
|
||||||
):
|
):
|
||||||
error = "User {0} is already registered.".format(username)
|
error = f"User {username} is already registered."
|
||||||
|
|
||||||
if error is None:
|
if error is None:
|
||||||
# the name is available, store it in the database and go to
|
# the name is available, store it in the database and go to
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ def get_post(id, check_author=True):
|
||||||
)
|
)
|
||||||
|
|
||||||
if post is None:
|
if post is None:
|
||||||
abort(404, "Post id {0} doesn't exist.".format(id))
|
abort(404, f"Post id {id} doesn't exist.")
|
||||||
|
|
||||||
if check_author and post["author_id"] != g.user["id"]:
|
if check_author and post["author_id"] != g.user["id"]:
|
||||||
abort(403)
|
abort(403)
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
import io
|
|
||||||
|
|
||||||
from setuptools import find_packages
|
from setuptools import find_packages
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
|
||||||
with io.open("README.rst", "rt", encoding="utf8") as f:
|
with open("README.rst", encoding="utf8") as f:
|
||||||
readme = f.read()
|
readme = f.read()
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ def runner(app):
|
||||||
return app.test_cli_runner()
|
return app.test_cli_runner()
|
||||||
|
|
||||||
|
|
||||||
class AuthActions(object):
|
class AuthActions:
|
||||||
def __init__(self, client):
|
def __init__(self, client):
|
||||||
self._client = client
|
self._client = client
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ def test_get_close_db(app):
|
||||||
|
|
||||||
|
|
||||||
def test_init_db_command(runner, monkeypatch):
|
def test_init_db_command(runner, monkeypatch):
|
||||||
class Recorder(object):
|
class Recorder:
|
||||||
called = False
|
called = False
|
||||||
|
|
||||||
def fake_init_db():
|
def fake_init_db():
|
||||||
|
|
|
||||||
18
setup.cfg
18
setup.cfg
|
|
@ -1,8 +1,7 @@
|
||||||
[bdist_wheel]
|
|
||||||
universal = true
|
|
||||||
|
|
||||||
[tool:pytest]
|
[tool:pytest]
|
||||||
testpaths = tests
|
testpaths = tests
|
||||||
|
filterwarnings =
|
||||||
|
error
|
||||||
|
|
||||||
[coverage:run]
|
[coverage:run]
|
||||||
branch = True
|
branch = True
|
||||||
|
|
@ -12,9 +11,8 @@ source =
|
||||||
|
|
||||||
[coverage:paths]
|
[coverage:paths]
|
||||||
source =
|
source =
|
||||||
src/flask
|
src
|
||||||
.tox/*/lib/python*/site-packages/flask
|
*/site-packages
|
||||||
.tox/*/site-packages/flask
|
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
# B = bugbear
|
# B = bugbear
|
||||||
|
|
@ -22,7 +20,8 @@ source =
|
||||||
# F = flake8 pyflakes
|
# F = flake8 pyflakes
|
||||||
# W = pycodestyle warnings
|
# W = pycodestyle warnings
|
||||||
# B9 = bugbear opinions
|
# B9 = bugbear opinions
|
||||||
select = B, E, F, W, B9
|
# ISC = implicit-str-concat
|
||||||
|
select = B, E, F, W, B9, ISC
|
||||||
ignore =
|
ignore =
|
||||||
# slice notation whitespace, invalid
|
# slice notation whitespace, invalid
|
||||||
E203
|
E203
|
||||||
|
|
@ -37,6 +36,5 @@ ignore =
|
||||||
# up to 88 allowed by bugbear B950
|
# up to 88 allowed by bugbear B950
|
||||||
max-line-length = 80
|
max-line-length = 80
|
||||||
per-file-ignores =
|
per-file-ignores =
|
||||||
# __init__ modules export names
|
# __init__ module exports names
|
||||||
**/__init__.py: F401
|
src/flask/__init__.py: F401
|
||||||
src/flask/_compat.py: E731, B301, F401
|
|
||||||
|
|
|
||||||
16
setup.py
16
setup.py
|
|
@ -1,13 +1,12 @@
|
||||||
import io
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from setuptools import find_packages
|
from setuptools import find_packages
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
|
||||||
with io.open("README.rst", "rt", encoding="utf8") as f:
|
with open("README.rst", encoding="utf8") as f:
|
||||||
readme = f.read()
|
readme = f.read()
|
||||||
|
|
||||||
with io.open("src/flask/__init__.py", "rt", encoding="utf8") as f:
|
with open("src/flask/__init__.py", encoding="utf8") as f:
|
||||||
version = re.search(r'__version__ = "(.*?)"', f.read()).group(1)
|
version = re.search(r'__version__ = "(.*?)"', f.read()).group(1)
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
|
|
@ -34,15 +33,6 @@ setup(
|
||||||
"License :: OSI Approved :: BSD License",
|
"License :: OSI Approved :: BSD License",
|
||||||
"Operating System :: OS Independent",
|
"Operating System :: OS Independent",
|
||||||
"Programming Language :: Python",
|
"Programming Language :: Python",
|
||||||
"Programming Language :: Python :: 2",
|
|
||||||
"Programming Language :: Python :: 2.7",
|
|
||||||
"Programming Language :: Python :: 3",
|
|
||||||
"Programming Language :: Python :: 3.5",
|
|
||||||
"Programming Language :: Python :: 3.6",
|
|
||||||
"Programming Language :: Python :: 3.7",
|
|
||||||
"Programming Language :: Python :: 3.8",
|
|
||||||
"Programming Language :: Python :: Implementation :: CPython",
|
|
||||||
"Programming Language :: Python :: Implementation :: PyPy",
|
|
||||||
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
|
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
|
||||||
"Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
|
"Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
|
||||||
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
||||||
|
|
@ -51,7 +41,7 @@ setup(
|
||||||
packages=find_packages("src"),
|
packages=find_packages("src"),
|
||||||
package_dir={"": "src"},
|
package_dir={"": "src"},
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*",
|
python_requires=">=3.6",
|
||||||
install_requires=[
|
install_requires=[
|
||||||
"Werkzeug>=0.15",
|
"Werkzeug>=0.15",
|
||||||
"Jinja2>=2.10.1",
|
"Jinja2>=2.10.1",
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
flask
|
flask
|
||||||
~~~~~
|
~~~~~
|
||||||
|
|
@ -17,7 +16,6 @@ from werkzeug.exceptions import abort
|
||||||
from werkzeug.utils import redirect
|
from werkzeug.utils import redirect
|
||||||
|
|
||||||
from . import json
|
from . import json
|
||||||
from ._compat import json_available
|
|
||||||
from .app import Flask
|
from .app import Flask
|
||||||
from .app import Request
|
from .app import Request
|
||||||
from .app import Response
|
from .app import Response
|
||||||
|
|
@ -57,4 +55,4 @@ from .signals import template_rendered
|
||||||
from .templating import render_template
|
from .templating import render_template
|
||||||
from .templating import render_template_string
|
from .templating import render_template_string
|
||||||
|
|
||||||
__version__ = "1.2.0.dev"
|
__version__ = "2.0.0.dev"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
flask.__main__
|
flask.__main__
|
||||||
~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~
|
||||||
|
|
|
||||||
|
|
@ -1,145 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask._compat
|
|
||||||
~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Some py2/py3 compatibility support based on a stripped down
|
|
||||||
version of six so we don't have to depend on a specific version
|
|
||||||
of it.
|
|
||||||
|
|
||||||
:copyright: 2010 Pallets
|
|
||||||
:license: BSD-3-Clause
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
|
|
||||||
PY2 = sys.version_info[0] == 2
|
|
||||||
_identity = lambda x: x
|
|
||||||
|
|
||||||
try: # Python 2
|
|
||||||
text_type = unicode
|
|
||||||
string_types = (str, unicode)
|
|
||||||
integer_types = (int, long)
|
|
||||||
except NameError: # Python 3
|
|
||||||
text_type = str
|
|
||||||
string_types = (str,)
|
|
||||||
integer_types = (int,)
|
|
||||||
|
|
||||||
if not PY2:
|
|
||||||
iterkeys = lambda d: iter(d.keys())
|
|
||||||
itervalues = lambda d: iter(d.values())
|
|
||||||
iteritems = lambda d: iter(d.items())
|
|
||||||
|
|
||||||
from inspect import getfullargspec as getargspec
|
|
||||||
from io import StringIO
|
|
||||||
import collections.abc as collections_abc
|
|
||||||
|
|
||||||
def reraise(tp, value, tb=None):
|
|
||||||
if value.__traceback__ is not tb:
|
|
||||||
raise value.with_traceback(tb)
|
|
||||||
raise value
|
|
||||||
|
|
||||||
implements_to_string = _identity
|
|
||||||
|
|
||||||
else:
|
|
||||||
iterkeys = lambda d: d.iterkeys()
|
|
||||||
itervalues = lambda d: d.itervalues()
|
|
||||||
iteritems = lambda d: d.iteritems()
|
|
||||||
|
|
||||||
from inspect import getargspec
|
|
||||||
from cStringIO import StringIO
|
|
||||||
import collections as collections_abc
|
|
||||||
|
|
||||||
exec("def reraise(tp, value, tb=None):\n raise tp, value, tb")
|
|
||||||
|
|
||||||
def implements_to_string(cls):
|
|
||||||
cls.__unicode__ = cls.__str__
|
|
||||||
cls.__str__ = lambda x: x.__unicode__().encode("utf-8")
|
|
||||||
return cls
|
|
||||||
|
|
||||||
|
|
||||||
def with_metaclass(meta, *bases):
|
|
||||||
"""Create a base class with a metaclass."""
|
|
||||||
# This requires a bit of explanation: the basic idea is to make a
|
|
||||||
# dummy metaclass for one level of class instantiation that replaces
|
|
||||||
# itself with the actual metaclass.
|
|
||||||
class metaclass(type):
|
|
||||||
def __new__(metacls, name, this_bases, d):
|
|
||||||
return meta(name, bases, d)
|
|
||||||
|
|
||||||
return type.__new__(metaclass, "temporary_class", (), {})
|
|
||||||
|
|
||||||
|
|
||||||
# Certain versions of pypy have a bug where clearing the exception stack
|
|
||||||
# breaks the __exit__ function in a very peculiar way. The second level of
|
|
||||||
# exception blocks is necessary because pypy seems to forget to check if an
|
|
||||||
# exception happened until the next bytecode instruction?
|
|
||||||
#
|
|
||||||
# Relevant PyPy bugfix commit:
|
|
||||||
# https://bitbucket.org/pypy/pypy/commits/77ecf91c635a287e88e60d8ddb0f4e9df4003301
|
|
||||||
# According to ronan on #pypy IRC, it is released in PyPy2 2.3 and later
|
|
||||||
# versions.
|
|
||||||
#
|
|
||||||
# Ubuntu 14.04 has PyPy 2.2.1, which does exhibit this bug.
|
|
||||||
BROKEN_PYPY_CTXMGR_EXIT = False
|
|
||||||
if hasattr(sys, "pypy_version_info"):
|
|
||||||
|
|
||||||
class _Mgr(object):
|
|
||||||
def __enter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, *args):
|
|
||||||
if hasattr(sys, "exc_clear"):
|
|
||||||
# Python 3 (PyPy3) doesn't have exc_clear
|
|
||||||
sys.exc_clear()
|
|
||||||
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
with _Mgr():
|
|
||||||
raise AssertionError()
|
|
||||||
except: # noqa: B001
|
|
||||||
# We intentionally use a bare except here. See the comment above
|
|
||||||
# regarding a pypy bug as to why.
|
|
||||||
raise
|
|
||||||
except TypeError:
|
|
||||||
BROKEN_PYPY_CTXMGR_EXIT = True
|
|
||||||
except AssertionError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
from os import fspath
|
|
||||||
except ImportError:
|
|
||||||
# Backwards compatibility as proposed in PEP 0519:
|
|
||||||
# https://www.python.org/dev/peps/pep-0519/#backwards-compatibility
|
|
||||||
def fspath(path):
|
|
||||||
return path.__fspath__() if hasattr(path, "__fspath__") else path
|
|
||||||
|
|
||||||
|
|
||||||
class _DeprecatedBool(object):
|
|
||||||
def __init__(self, name, version, value):
|
|
||||||
self.message = "'{}' is deprecated and will be removed in version {}.".format(
|
|
||||||
name, version
|
|
||||||
)
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
def _warn(self):
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
warnings.warn(self.message, DeprecationWarning, stacklevel=2)
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
self._warn()
|
|
||||||
return other == self.value
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
self._warn()
|
|
||||||
return other != self.value
|
|
||||||
|
|
||||||
def __bool__(self):
|
|
||||||
self._warn()
|
|
||||||
return self.value
|
|
||||||
|
|
||||||
__nonzero__ = __bool__
|
|
||||||
|
|
||||||
|
|
||||||
json_available = _DeprecatedBool("flask.json_available", "2.0.0", True)
|
|
||||||
206
src/flask/app.py
206
src/flask/app.py
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
flask.app
|
flask.app
|
||||||
~~~~~~~~~
|
~~~~~~~~~
|
||||||
|
|
@ -10,7 +9,6 @@
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from functools import update_wrapper
|
from functools import update_wrapper
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
|
@ -32,10 +30,6 @@ from werkzeug.wrappers import BaseResponse
|
||||||
|
|
||||||
from . import cli
|
from . import cli
|
||||||
from . import json
|
from . import json
|
||||||
from ._compat import integer_types
|
|
||||||
from ._compat import reraise
|
|
||||||
from ._compat import string_types
|
|
||||||
from ._compat import text_type
|
|
||||||
from .config import Config
|
from .config import Config
|
||||||
from .config import ConfigAttribute
|
from .config import ConfigAttribute
|
||||||
from .ctx import _AppCtxGlobals
|
from .ctx import _AppCtxGlobals
|
||||||
|
|
@ -191,11 +185,9 @@ class Flask(_PackageBoundObject):
|
||||||
for loading the config are assumed to
|
for loading the config are assumed to
|
||||||
be relative to the instance path instead
|
be relative to the instance path instead
|
||||||
of the application root.
|
of the application root.
|
||||||
:param root_path: Flask by default will automatically calculate the path
|
:param root_path: The path to the root of the application files.
|
||||||
to the root of the application. In certain situations
|
This should only be set manually when it can't be detected
|
||||||
this cannot be achieved (for instance if the package
|
automatically, such as for namespace packages.
|
||||||
is a Python 3 namespace package) and needs to be
|
|
||||||
manually defined.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#: The class that is used for request objects. See :class:`~flask.Request`
|
#: The class that is used for request objects. See :class:`~flask.Request`
|
||||||
|
|
@ -593,7 +585,7 @@ class Flask(_PackageBoundObject):
|
||||||
bool(static_host) == host_matching
|
bool(static_host) == host_matching
|
||||||
), "Invalid static_host/host_matching combination"
|
), "Invalid static_host/host_matching combination"
|
||||||
self.add_url_rule(
|
self.add_url_rule(
|
||||||
self.static_url_path + "/<path:filename>",
|
f"{self.static_url_path}/<path:filename>",
|
||||||
endpoint="static",
|
endpoint="static",
|
||||||
host=static_host,
|
host=static_host,
|
||||||
view_func=self.send_static_file,
|
view_func=self.send_static_file,
|
||||||
|
|
@ -719,7 +711,7 @@ class Flask(_PackageBoundObject):
|
||||||
prefix, package_path = find_package(self.import_name)
|
prefix, package_path = find_package(self.import_name)
|
||||||
if prefix is None:
|
if prefix is None:
|
||||||
return os.path.join(package_path, "instance")
|
return os.path.join(package_path, "instance")
|
||||||
return os.path.join(prefix, "var", self.name + "-instance")
|
return os.path.join(prefix, "var", f"{self.name}-instance")
|
||||||
|
|
||||||
def open_instance_resource(self, resource, mode="rb"):
|
def open_instance_resource(self, resource, mode="rb"):
|
||||||
"""Opens a resource from the application's instance folder
|
"""Opens a resource from the application's instance folder
|
||||||
|
|
@ -1068,70 +1060,6 @@ class Flask(_PackageBoundObject):
|
||||||
|
|
||||||
return cls(self, **kwargs)
|
return cls(self, **kwargs)
|
||||||
|
|
||||||
def open_session(self, request):
|
|
||||||
"""Creates or opens a new session. Default implementation stores all
|
|
||||||
session data in a signed cookie. This requires that the
|
|
||||||
:attr:`secret_key` is set. Instead of overriding this method
|
|
||||||
we recommend replacing the :class:`session_interface`.
|
|
||||||
|
|
||||||
.. deprecated: 1.0
|
|
||||||
Will be removed in 2.0. Use
|
|
||||||
``session_interface.open_session`` instead.
|
|
||||||
|
|
||||||
:param request: an instance of :attr:`request_class`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
warnings.warn(
|
|
||||||
DeprecationWarning(
|
|
||||||
'"open_session" is deprecated and will be removed in'
|
|
||||||
' 2.0. Use "session_interface.open_session" instead.'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return self.session_interface.open_session(self, request)
|
|
||||||
|
|
||||||
def save_session(self, session, response):
|
|
||||||
"""Saves the session if it needs updates. For the default
|
|
||||||
implementation, check :meth:`open_session`. Instead of overriding this
|
|
||||||
method we recommend replacing the :class:`session_interface`.
|
|
||||||
|
|
||||||
.. deprecated: 1.0
|
|
||||||
Will be removed in 2.0. Use
|
|
||||||
``session_interface.save_session`` instead.
|
|
||||||
|
|
||||||
:param session: the session to be saved (a
|
|
||||||
:class:`~werkzeug.contrib.securecookie.SecureCookie`
|
|
||||||
object)
|
|
||||||
:param response: an instance of :attr:`response_class`
|
|
||||||
"""
|
|
||||||
|
|
||||||
warnings.warn(
|
|
||||||
DeprecationWarning(
|
|
||||||
'"save_session" is deprecated and will be removed in'
|
|
||||||
' 2.0. Use "session_interface.save_session" instead.'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return self.session_interface.save_session(self, session, response)
|
|
||||||
|
|
||||||
def make_null_session(self):
|
|
||||||
"""Creates a new instance of a missing session. Instead of overriding
|
|
||||||
this method we recommend replacing the :class:`session_interface`.
|
|
||||||
|
|
||||||
.. deprecated: 1.0
|
|
||||||
Will be removed in 2.0. Use
|
|
||||||
``session_interface.make_null_session`` instead.
|
|
||||||
|
|
||||||
.. versionadded:: 0.7
|
|
||||||
"""
|
|
||||||
|
|
||||||
warnings.warn(
|
|
||||||
DeprecationWarning(
|
|
||||||
'"make_null_session" is deprecated and will be removed'
|
|
||||||
' in 2.0. Use "session_interface.make_null_session"'
|
|
||||||
" instead."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return self.session_interface.make_null_session(self)
|
|
||||||
|
|
||||||
@setupmethod
|
@setupmethod
|
||||||
def register_blueprint(self, blueprint, **options):
|
def register_blueprint(self, blueprint, **options):
|
||||||
"""Register a :class:`~flask.Blueprint` on the application. Keyword
|
"""Register a :class:`~flask.Blueprint` on the application. Keyword
|
||||||
|
|
@ -1156,10 +1084,11 @@ class Flask(_PackageBoundObject):
|
||||||
|
|
||||||
if blueprint.name in self.blueprints:
|
if blueprint.name in self.blueprints:
|
||||||
assert self.blueprints[blueprint.name] is blueprint, (
|
assert self.blueprints[blueprint.name] is blueprint, (
|
||||||
"A name collision occurred between blueprints %r and %r. Both"
|
"A name collision occurred between blueprints"
|
||||||
' share the same name "%s". Blueprints that are created on the'
|
f" {blueprint!r} and {self.blueprints[blueprint.name]!r}."
|
||||||
" fly need unique names."
|
f" Both share the same name {blueprint.name!r}."
|
||||||
% (blueprint, self.blueprints[blueprint.name], blueprint.name)
|
f" Blueprints that are created on the fly need unique"
|
||||||
|
f" names."
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.blueprints[blueprint.name] = blueprint
|
self.blueprints[blueprint.name] = blueprint
|
||||||
|
|
@ -1182,7 +1111,7 @@ class Flask(_PackageBoundObject):
|
||||||
endpoint=None,
|
endpoint=None,
|
||||||
view_func=None,
|
view_func=None,
|
||||||
provide_automatic_options=None,
|
provide_automatic_options=None,
|
||||||
**options
|
**options,
|
||||||
):
|
):
|
||||||
"""Connects a URL rule. Works exactly like the :meth:`route`
|
"""Connects a URL rule. Works exactly like the :meth:`route`
|
||||||
decorator. If a view_func is provided it will be registered with the
|
decorator. If a view_func is provided it will be registered with the
|
||||||
|
|
@ -1246,12 +1175,12 @@ class Flask(_PackageBoundObject):
|
||||||
# a tuple of only ``GET`` as default.
|
# a tuple of only ``GET`` as default.
|
||||||
if methods is None:
|
if methods is None:
|
||||||
methods = getattr(view_func, "methods", None) or ("GET",)
|
methods = getattr(view_func, "methods", None) or ("GET",)
|
||||||
if isinstance(methods, string_types):
|
if isinstance(methods, str):
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
"Allowed methods have to be iterables of strings, "
|
"Allowed methods must be a list of strings, for"
|
||||||
'for example: @app.route(..., methods=["POST"])'
|
' example: @app.route(..., methods=["POST"])'
|
||||||
)
|
)
|
||||||
methods = set(item.upper() for item in methods)
|
methods = {item.upper() for item in methods}
|
||||||
|
|
||||||
# Methods that should always be added
|
# Methods that should always be added
|
||||||
required_methods = set(getattr(view_func, "required_methods", ()))
|
required_methods = set(getattr(view_func, "required_methods", ()))
|
||||||
|
|
@ -1281,8 +1210,8 @@ class Flask(_PackageBoundObject):
|
||||||
old_func = self.view_functions.get(endpoint)
|
old_func = self.view_functions.get(endpoint)
|
||||||
if old_func is not None and old_func != view_func:
|
if old_func is not None and old_func != view_func:
|
||||||
raise AssertionError(
|
raise AssertionError(
|
||||||
"View function mapping is overwriting an "
|
"View function mapping is overwriting an existing"
|
||||||
"existing endpoint function: %s" % endpoint
|
f" endpoint function: {endpoint}"
|
||||||
)
|
)
|
||||||
self.view_functions[endpoint] = view_func
|
self.view_functions[endpoint] = view_func
|
||||||
|
|
||||||
|
|
@ -1345,7 +1274,7 @@ class Flask(_PackageBoundObject):
|
||||||
:param exc_class_or_code: Any exception class, or an HTTP status
|
:param exc_class_or_code: Any exception class, or an HTTP status
|
||||||
code as an integer.
|
code as an integer.
|
||||||
"""
|
"""
|
||||||
if isinstance(exc_class_or_code, integer_types):
|
if isinstance(exc_class_or_code, int):
|
||||||
exc_class = default_exceptions[exc_class_or_code]
|
exc_class = default_exceptions[exc_class_or_code]
|
||||||
else:
|
else:
|
||||||
exc_class = exc_class_or_code
|
exc_class = exc_class_or_code
|
||||||
|
|
@ -1413,17 +1342,18 @@ class Flask(_PackageBoundObject):
|
||||||
"""
|
"""
|
||||||
if isinstance(code_or_exception, HTTPException): # old broken behavior
|
if isinstance(code_or_exception, HTTPException): # old broken behavior
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Tried to register a handler for an exception instance {0!r}."
|
"Tried to register a handler for an exception instance"
|
||||||
" Handlers can only be registered for exception classes or"
|
f" {code_or_exception!r}. Handlers can only be"
|
||||||
" HTTP error codes.".format(code_or_exception)
|
" registered for exception classes or HTTP error codes."
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
exc_class, code = self._get_exc_class_and_code(code_or_exception)
|
exc_class, code = self._get_exc_class_and_code(code_or_exception)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise KeyError(
|
raise KeyError(
|
||||||
"'{0}' is not a recognized HTTP error code. Use a subclass of"
|
f"'{code_or_exception}' is not a recognized HTTP error"
|
||||||
" HTTPException with that code instead.".format(code_or_exception)
|
" code. Use a subclass of HTTPException with that code"
|
||||||
|
" instead."
|
||||||
)
|
)
|
||||||
|
|
||||||
handlers = self.error_handler_spec.setdefault(key, {}).setdefault(code, {})
|
handlers = self.error_handler_spec.setdefault(key, {}).setdefault(code, {})
|
||||||
|
|
@ -1794,13 +1724,6 @@ class Flask(_PackageBoundObject):
|
||||||
|
|
||||||
.. versionadded:: 0.7
|
.. versionadded:: 0.7
|
||||||
"""
|
"""
|
||||||
exc_type, exc_value, tb = sys.exc_info()
|
|
||||||
assert exc_value is e
|
|
||||||
# ensure not to trash sys.exc_info() at that point in case someone
|
|
||||||
# wants the traceback preserved in handle_http_exception. Of course
|
|
||||||
# we cannot prevent users from trashing it themselves in a custom
|
|
||||||
# trap_http_exception method so that's their fault then.
|
|
||||||
|
|
||||||
if isinstance(e, BadRequestKeyError):
|
if isinstance(e, BadRequestKeyError):
|
||||||
if self.debug or self.config["TRAP_BAD_REQUEST_ERRORS"]:
|
if self.debug or self.config["TRAP_BAD_REQUEST_ERRORS"]:
|
||||||
e.show_exception = True
|
e.show_exception = True
|
||||||
|
|
@ -1809,7 +1732,7 @@ class Flask(_PackageBoundObject):
|
||||||
# message, add it in manually.
|
# message, add it in manually.
|
||||||
# TODO: clean up once Werkzeug >= 0.15.5 is required
|
# TODO: clean up once Werkzeug >= 0.15.5 is required
|
||||||
if e.args[0] not in e.get_description():
|
if e.args[0] not in e.get_description():
|
||||||
e.description = "KeyError: '{}'".format(*e.args)
|
e.description = f"KeyError: {e.args[0]!r}"
|
||||||
elif not hasattr(BadRequestKeyError, "show_exception"):
|
elif not hasattr(BadRequestKeyError, "show_exception"):
|
||||||
e.args = ()
|
e.args = ()
|
||||||
|
|
||||||
|
|
@ -1819,7 +1742,8 @@ class Flask(_PackageBoundObject):
|
||||||
handler = self._find_error_handler(e)
|
handler = self._find_error_handler(e)
|
||||||
|
|
||||||
if handler is None:
|
if handler is None:
|
||||||
reraise(exc_type, exc_value, tb)
|
raise
|
||||||
|
|
||||||
return handler(e)
|
return handler(e)
|
||||||
|
|
||||||
def handle_exception(self, e):
|
def handle_exception(self, e):
|
||||||
|
|
@ -1856,20 +1780,18 @@ class Flask(_PackageBoundObject):
|
||||||
|
|
||||||
.. versionadded:: 0.3
|
.. versionadded:: 0.3
|
||||||
"""
|
"""
|
||||||
exc_type, exc_value, tb = sys.exc_info()
|
exc_info = sys.exc_info()
|
||||||
got_request_exception.send(self, exception=e)
|
got_request_exception.send(self, exception=e)
|
||||||
|
|
||||||
if self.propagate_exceptions:
|
if self.propagate_exceptions:
|
||||||
# if we want to repropagate the exception, we can attempt to
|
# Re-raise if called with an active exception, otherwise
|
||||||
# raise it with the whole traceback in case we can do that
|
# raise the passed in exception.
|
||||||
# (the function was actually called from the except part)
|
if exc_info[1] is e:
|
||||||
# otherwise, we just raise the error again
|
raise
|
||||||
if exc_value is e:
|
|
||||||
reraise(exc_type, exc_value, tb)
|
|
||||||
else:
|
|
||||||
raise e
|
|
||||||
|
|
||||||
self.log_exception((exc_type, exc_value, tb))
|
raise e
|
||||||
|
|
||||||
|
self.log_exception(exc_info)
|
||||||
server_error = InternalServerError()
|
server_error = InternalServerError()
|
||||||
# TODO: pass as param when Werkzeug>=1.0.0 is required
|
# TODO: pass as param when Werkzeug>=1.0.0 is required
|
||||||
# TODO: also remove note about this from docstring and docs
|
# TODO: also remove note about this from docstring and docs
|
||||||
|
|
@ -1890,7 +1812,7 @@ class Flask(_PackageBoundObject):
|
||||||
.. versionadded:: 0.8
|
.. versionadded:: 0.8
|
||||||
"""
|
"""
|
||||||
self.logger.error(
|
self.logger.error(
|
||||||
"Exception on %s [%s]" % (request.path, request.method), exc_info=exc_info
|
f"Exception on {request.path} [{request.method}]", exc_info=exc_info
|
||||||
)
|
)
|
||||||
|
|
||||||
def raise_routing_exception(self, request):
|
def raise_routing_exception(self, request):
|
||||||
|
|
@ -2026,11 +1948,11 @@ class Flask(_PackageBoundObject):
|
||||||
without returning, is not allowed. The following types are allowed
|
without returning, is not allowed. The following types are allowed
|
||||||
for ``view_rv``:
|
for ``view_rv``:
|
||||||
|
|
||||||
``str`` (``unicode`` in Python 2)
|
``str``
|
||||||
A response object is created with the string encoded to UTF-8
|
A response object is created with the string encoded to UTF-8
|
||||||
as the body.
|
as the body.
|
||||||
|
|
||||||
``bytes`` (``str`` in Python 2)
|
``bytes``
|
||||||
A response object is created with the bytes as the body.
|
A response object is created with the bytes as the body.
|
||||||
|
|
||||||
``dict``
|
``dict``
|
||||||
|
|
@ -2086,14 +2008,14 @@ class Flask(_PackageBoundObject):
|
||||||
# the body must not be None
|
# the body must not be None
|
||||||
if rv is None:
|
if rv is None:
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
'The view function for "{}" did not return a valid response. The'
|
f"The view function for {request.endpoint!r} did not"
|
||||||
" function either returned None or ended without a return"
|
" return a valid response. The function either returned"
|
||||||
" statement.".format(request.endpoint)
|
" None or ended without a return statement."
|
||||||
)
|
)
|
||||||
|
|
||||||
# make sure the body is an instance of the response class
|
# make sure the body is an instance of the response class
|
||||||
if not isinstance(rv, self.response_class):
|
if not isinstance(rv, self.response_class):
|
||||||
if isinstance(rv, (text_type, bytes, bytearray)):
|
if isinstance(rv, (str, bytes, bytearray)):
|
||||||
# let the response class set the status and headers instead of
|
# let the response class set the status and headers instead of
|
||||||
# waiting to do it manually, so that the class can handle any
|
# waiting to do it manually, so that the class can handle any
|
||||||
# special logic
|
# special logic
|
||||||
|
|
@ -2107,24 +2029,23 @@ class Flask(_PackageBoundObject):
|
||||||
try:
|
try:
|
||||||
rv = self.response_class.force_type(rv, request.environ)
|
rv = self.response_class.force_type(rv, request.environ)
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
new_error = TypeError(
|
raise TypeError(
|
||||||
"{e}\nThe view function did not return a valid"
|
f"{e}\nThe view function did not return a valid"
|
||||||
" response. The return type must be a string, dict, tuple,"
|
" response. The return type must be a string,"
|
||||||
" Response instance, or WSGI callable, but it was a"
|
" dict, tuple, Response instance, or WSGI"
|
||||||
" {rv.__class__.__name__}.".format(e=e, rv=rv)
|
f" callable, but it was a {type(rv).__name__}."
|
||||||
)
|
).with_traceback(sys.exc_info()[2])
|
||||||
reraise(TypeError, new_error, sys.exc_info()[2])
|
|
||||||
else:
|
else:
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
"The view function did not return a valid"
|
"The view function did not return a valid"
|
||||||
" response. The return type must be a string, dict, tuple,"
|
" response. The return type must be a string,"
|
||||||
" Response instance, or WSGI callable, but it was a"
|
" dict, tuple, Response instance, or WSGI"
|
||||||
" {rv.__class__.__name__}.".format(rv=rv)
|
f" callable, but it was a {type(rv).__name__}."
|
||||||
)
|
)
|
||||||
|
|
||||||
# prefer the status if it was provided
|
# prefer the status if it was provided
|
||||||
if status is not None:
|
if status is not None:
|
||||||
if isinstance(status, (text_type, bytes, bytearray)):
|
if isinstance(status, (str, bytes, bytearray)):
|
||||||
rv.status = status
|
rv.status = status
|
||||||
else:
|
else:
|
||||||
rv.status_code = status
|
rv.status_code = status
|
||||||
|
|
@ -2188,23 +2109,24 @@ class Flask(_PackageBoundObject):
|
||||||
func(endpoint, values)
|
func(endpoint, values)
|
||||||
|
|
||||||
def handle_url_build_error(self, error, endpoint, values):
|
def handle_url_build_error(self, error, endpoint, values):
|
||||||
"""Handle :class:`~werkzeug.routing.BuildError` on :meth:`url_for`.
|
"""Handle :class:`~werkzeug.routing.BuildError` on
|
||||||
|
:meth:`url_for`.
|
||||||
"""
|
"""
|
||||||
exc_type, exc_value, tb = sys.exc_info()
|
|
||||||
for handler in self.url_build_error_handlers:
|
for handler in self.url_build_error_handlers:
|
||||||
try:
|
try:
|
||||||
rv = handler(error, endpoint, values)
|
rv = handler(error, endpoint, values)
|
||||||
|
except BuildError as e:
|
||||||
|
# make error available outside except block
|
||||||
|
error = e
|
||||||
|
else:
|
||||||
if rv is not None:
|
if rv is not None:
|
||||||
return rv
|
return rv
|
||||||
except BuildError as e:
|
|
||||||
# make error available outside except block (py3)
|
|
||||||
error = e
|
|
||||||
|
|
||||||
# At this point we want to reraise the exception. If the error is
|
# Re-raise if called with an active exception, otherwise raise
|
||||||
# still the same one we can reraise it with the original traceback,
|
# the passed in exception.
|
||||||
# otherwise we raise it from here.
|
if error is sys.exc_info()[1]:
|
||||||
if error is exc_value:
|
raise
|
||||||
reraise(exc_type, exc_value, tb)
|
|
||||||
raise error
|
raise error
|
||||||
|
|
||||||
def preprocess_request(self):
|
def preprocess_request(self):
|
||||||
|
|
@ -2455,4 +2377,4 @@ class Flask(_PackageBoundObject):
|
||||||
return self.wsgi_app(environ, start_response)
|
return self.wsgi_app(environ, start_response)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<%s %r>" % (self.__class__.__name__, self.name)
|
return f"<{type(self).__name__} {self.name!r}>"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
flask.blueprints
|
flask.blueprints
|
||||||
~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
@ -18,7 +17,7 @@ from .helpers import _PackageBoundObject
|
||||||
_sentinel = object()
|
_sentinel = object()
|
||||||
|
|
||||||
|
|
||||||
class BlueprintSetupState(object):
|
class BlueprintSetupState:
|
||||||
"""Temporary holder object for registering a blueprint with the
|
"""Temporary holder object for registering a blueprint with the
|
||||||
application. An instance of this class is created by the
|
application. An instance of this class is created by the
|
||||||
:meth:`~flask.Blueprint.make_setup_state` method and later passed
|
:meth:`~flask.Blueprint.make_setup_state` method and later passed
|
||||||
|
|
@ -80,10 +79,10 @@ class BlueprintSetupState(object):
|
||||||
defaults = dict(defaults, **options.pop("defaults"))
|
defaults = dict(defaults, **options.pop("defaults"))
|
||||||
self.app.add_url_rule(
|
self.app.add_url_rule(
|
||||||
rule,
|
rule,
|
||||||
"%s.%s" % (self.blueprint.name, endpoint),
|
f"{self.blueprint.name}.{endpoint}",
|
||||||
view_func,
|
view_func,
|
||||||
defaults=defaults,
|
defaults=defaults,
|
||||||
**options
|
**options,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -247,7 +246,7 @@ class Blueprint(_PackageBoundObject):
|
||||||
|
|
||||||
if self.has_static_folder:
|
if self.has_static_folder:
|
||||||
state.add_url_rule(
|
state.add_url_rule(
|
||||||
self.static_url_path + "/<path:filename>",
|
f"{self.static_url_path}/<path:filename>",
|
||||||
view_func=self.send_static_file,
|
view_func=self.send_static_file,
|
||||||
endpoint="static",
|
endpoint="static",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
123
src/flask/cli.py
123
src/flask/cli.py
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
flask.cli
|
flask.cli
|
||||||
~~~~~~~~~
|
~~~~~~~~~
|
||||||
|
|
@ -8,8 +7,6 @@
|
||||||
:copyright: 2010 Pallets
|
:copyright: 2010 Pallets
|
||||||
:license: BSD-3-Clause
|
:license: BSD-3-Clause
|
||||||
"""
|
"""
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import ast
|
import ast
|
||||||
import inspect
|
import inspect
|
||||||
import os
|
import os
|
||||||
|
|
@ -25,10 +22,6 @@ from threading import Thread
|
||||||
import click
|
import click
|
||||||
from werkzeug.utils import import_string
|
from werkzeug.utils import import_string
|
||||||
|
|
||||||
from ._compat import getargspec
|
|
||||||
from ._compat import itervalues
|
|
||||||
from ._compat import reraise
|
|
||||||
from ._compat import text_type
|
|
||||||
from .globals import current_app
|
from .globals import current_app
|
||||||
from .helpers import get_debug_flag
|
from .helpers import get_debug_flag
|
||||||
from .helpers import get_env
|
from .helpers import get_env
|
||||||
|
|
@ -63,19 +56,19 @@ def find_best_app(script_info, module):
|
||||||
return app
|
return app
|
||||||
|
|
||||||
# Otherwise find the only object that is a Flask instance.
|
# Otherwise find the only object that is a Flask instance.
|
||||||
matches = [v for v in itervalues(module.__dict__) if isinstance(v, Flask)]
|
matches = [v for v in module.__dict__.values() if isinstance(v, Flask)]
|
||||||
|
|
||||||
if len(matches) == 1:
|
if len(matches) == 1:
|
||||||
return matches[0]
|
return matches[0]
|
||||||
elif len(matches) > 1:
|
elif len(matches) > 1:
|
||||||
raise NoAppException(
|
raise NoAppException(
|
||||||
'Detected multiple Flask applications in module "{module}". Use '
|
"Detected multiple Flask applications in module"
|
||||||
'"FLASK_APP={module}:name" to specify the correct '
|
f" {module.__name__!r}. Use 'FLASK_APP={module.__name__}:name'"
|
||||||
"one.".format(module=module.__name__)
|
f" to specify the correct one."
|
||||||
)
|
)
|
||||||
|
|
||||||
# Search for app factory functions.
|
# Search for app factory functions.
|
||||||
for attr_name in ("create_app", "make_app"):
|
for attr_name in {"create_app", "make_app"}:
|
||||||
app_factory = getattr(module, attr_name, None)
|
app_factory = getattr(module, attr_name, None)
|
||||||
|
|
||||||
if inspect.isfunction(app_factory):
|
if inspect.isfunction(app_factory):
|
||||||
|
|
@ -88,15 +81,16 @@ def find_best_app(script_info, module):
|
||||||
if not _called_with_wrong_args(app_factory):
|
if not _called_with_wrong_args(app_factory):
|
||||||
raise
|
raise
|
||||||
raise NoAppException(
|
raise NoAppException(
|
||||||
'Detected factory "{factory}" in module "{module}", but '
|
f"Detected factory {attr_name!r} in module {module.__name__!r},"
|
||||||
"could not call it without arguments. Use "
|
" but could not call it without arguments. Use"
|
||||||
"\"FLASK_APP='{module}:{factory}(args)'\" to specify "
|
f" \"FLASK_APP='{module.__name__}:{attr_name}(args)'\""
|
||||||
"arguments.".format(factory=attr_name, module=module.__name__)
|
" to specify arguments."
|
||||||
)
|
)
|
||||||
|
|
||||||
raise NoAppException(
|
raise NoAppException(
|
||||||
'Failed to find Flask application or factory in module "{module}". '
|
"Failed to find Flask application or factory in module"
|
||||||
'Use "FLASK_APP={module}:name to specify one.'.format(module=module.__name__)
|
f" {module.__name__!r}. Use 'FLASK_APP={module.__name__}:name'"
|
||||||
|
" to specify one."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -105,7 +99,7 @@ def call_factory(script_info, app_factory, arguments=()):
|
||||||
of arguments. Checks for the existence of a script_info argument and calls
|
of arguments. Checks for the existence of a script_info argument and calls
|
||||||
the app_factory depending on that and the arguments provided.
|
the app_factory depending on that and the arguments provided.
|
||||||
"""
|
"""
|
||||||
args_spec = getargspec(app_factory)
|
args_spec = inspect.getfullargspec(app_factory)
|
||||||
arg_names = args_spec.args
|
arg_names = args_spec.args
|
||||||
arg_defaults = args_spec.defaults
|
arg_defaults = args_spec.defaults
|
||||||
|
|
||||||
|
|
@ -157,8 +151,7 @@ def find_app_by_string(script_info, module, app_name):
|
||||||
|
|
||||||
if not match:
|
if not match:
|
||||||
raise NoAppException(
|
raise NoAppException(
|
||||||
'"{name}" is not a valid variable name or function '
|
f"{app_name!r} is not a valid variable name or function expression."
|
||||||
"expression.".format(name=app_name)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
name, args = match.groups()
|
name, args = match.groups()
|
||||||
|
|
@ -171,12 +164,9 @@ def find_app_by_string(script_info, module, app_name):
|
||||||
if inspect.isfunction(attr):
|
if inspect.isfunction(attr):
|
||||||
if args:
|
if args:
|
||||||
try:
|
try:
|
||||||
args = ast.literal_eval("({args},)".format(args=args))
|
args = ast.literal_eval(f"({args},)")
|
||||||
except (ValueError, SyntaxError) as e:
|
except (ValueError, SyntaxError):
|
||||||
raise NoAppException(
|
raise NoAppException(f"Could not parse the arguments in {app_name!r}.")
|
||||||
"Could not parse the arguments in "
|
|
||||||
'"{app_name}".'.format(e=e, app_name=app_name)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
args = ()
|
args = ()
|
||||||
|
|
||||||
|
|
@ -187,10 +177,9 @@ def find_app_by_string(script_info, module, app_name):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
raise NoAppException(
|
raise NoAppException(
|
||||||
'{e}\nThe factory "{app_name}" in module "{module}" could not '
|
f"{e}\nThe factory {app_name!r} in module"
|
||||||
"be called with the specified arguments.".format(
|
f" {module.__name__!r} could not be called with the"
|
||||||
e=e, app_name=app_name, module=module.__name__
|
" specified arguments."
|
||||||
)
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
app = attr
|
app = attr
|
||||||
|
|
@ -199,8 +188,8 @@ def find_app_by_string(script_info, module, app_name):
|
||||||
return app
|
return app
|
||||||
|
|
||||||
raise NoAppException(
|
raise NoAppException(
|
||||||
"A valid Flask application was not obtained from "
|
"A valid Flask application was not obtained from"
|
||||||
'"{module}:{app_name}".'.format(module=module.__name__, app_name=app_name)
|
f" '{module.__name__}:{app_name}'."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -241,13 +230,13 @@ def locate_app(script_info, module_name, app_name, raise_if_not_found=True):
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# Reraise the ImportError if it occurred within the imported module.
|
# Reraise the ImportError if it occurred within the imported module.
|
||||||
# Determine this by checking whether the trace has a depth > 1.
|
# Determine this by checking whether the trace has a depth > 1.
|
||||||
if sys.exc_info()[-1].tb_next:
|
if sys.exc_info()[2].tb_next:
|
||||||
raise NoAppException(
|
raise NoAppException(
|
||||||
'While importing "{name}", an ImportError was raised:'
|
f"While importing {module_name!r}, an ImportError was"
|
||||||
"\n\n{tb}".format(name=module_name, tb=traceback.format_exc())
|
f" raised:\n\n{traceback.format_exc()}"
|
||||||
)
|
)
|
||||||
elif raise_if_not_found:
|
elif raise_if_not_found:
|
||||||
raise NoAppException('Could not import "{name}".'.format(name=module_name))
|
raise NoAppException(f"Could not import {module_name!r}.")
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -266,14 +255,10 @@ def get_version(ctx, param, value):
|
||||||
import werkzeug
|
import werkzeug
|
||||||
from . import __version__
|
from . import __version__
|
||||||
|
|
||||||
message = "Python %(python)s\nFlask %(flask)s\nWerkzeug %(werkzeug)s"
|
|
||||||
click.echo(
|
click.echo(
|
||||||
message
|
f"Python {platform.python_version()}\n"
|
||||||
% {
|
f"Flask {__version__}\n"
|
||||||
"python": platform.python_version(),
|
f"Werkzeug {werkzeug.__version__}",
|
||||||
"flask": __version__,
|
|
||||||
"werkzeug": werkzeug.__version__,
|
|
||||||
},
|
|
||||||
color=ctx.color,
|
color=ctx.color,
|
||||||
)
|
)
|
||||||
ctx.exit()
|
ctx.exit()
|
||||||
|
|
@ -289,7 +274,7 @@ version_option = click.Option(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class DispatchingApp(object):
|
class DispatchingApp:
|
||||||
"""Special application that dispatches to a Flask application which
|
"""Special application that dispatches to a Flask application which
|
||||||
is imported by name in a background thread. If an error happens
|
is imported by name in a background thread. If an error happens
|
||||||
it is recorded and shown as part of the WSGI handling which in case
|
it is recorded and shown as part of the WSGI handling which in case
|
||||||
|
|
@ -327,7 +312,7 @@ class DispatchingApp(object):
|
||||||
exc_info = self._bg_loading_exc_info
|
exc_info = self._bg_loading_exc_info
|
||||||
if exc_info is not None:
|
if exc_info is not None:
|
||||||
self._bg_loading_exc_info = None
|
self._bg_loading_exc_info = None
|
||||||
reraise(*exc_info)
|
raise exc_info
|
||||||
|
|
||||||
def _load_unlocked(self):
|
def _load_unlocked(self):
|
||||||
__traceback_hide__ = True # noqa: F841
|
__traceback_hide__ = True # noqa: F841
|
||||||
|
|
@ -348,7 +333,7 @@ class DispatchingApp(object):
|
||||||
return rv(environ, start_response)
|
return rv(environ, start_response)
|
||||||
|
|
||||||
|
|
||||||
class ScriptInfo(object):
|
class ScriptInfo:
|
||||||
"""Helper object to deal with Flask applications. This is usually not
|
"""Helper object to deal with Flask applications. This is usually not
|
||||||
necessary to interface with as it's used internally in the dispatching
|
necessary to interface with as it's used internally in the dispatching
|
||||||
to click. In future versions of Flask this object will most likely play
|
to click. In future versions of Flask this object will most likely play
|
||||||
|
|
@ -495,7 +480,7 @@ class FlaskGroup(AppGroup):
|
||||||
add_version_option=True,
|
add_version_option=True,
|
||||||
load_dotenv=True,
|
load_dotenv=True,
|
||||||
set_debug_flag=True,
|
set_debug_flag=True,
|
||||||
**extra
|
**extra,
|
||||||
):
|
):
|
||||||
params = list(extra.pop("params", None) or ())
|
params = list(extra.pop("params", None) or ())
|
||||||
|
|
||||||
|
|
@ -587,7 +572,7 @@ class FlaskGroup(AppGroup):
|
||||||
|
|
||||||
kwargs["obj"] = obj
|
kwargs["obj"] = obj
|
||||||
kwargs.setdefault("auto_envvar_prefix", "FLASK")
|
kwargs.setdefault("auto_envvar_prefix", "FLASK")
|
||||||
return super(FlaskGroup, self).main(*args, **kwargs)
|
return super().main(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def _path_is_ancestor(path, other):
|
def _path_is_ancestor(path, other):
|
||||||
|
|
@ -666,25 +651,25 @@ def show_server_banner(env, debug, app_import_path, eager_loading):
|
||||||
return
|
return
|
||||||
|
|
||||||
if app_import_path is not None:
|
if app_import_path is not None:
|
||||||
message = ' * Serving Flask app "{0}"'.format(app_import_path)
|
message = f" * Serving Flask app {app_import_path!r}"
|
||||||
|
|
||||||
if not eager_loading:
|
if not eager_loading:
|
||||||
message += " (lazy loading)"
|
message += " (lazy loading)"
|
||||||
|
|
||||||
click.echo(message)
|
click.echo(message)
|
||||||
|
|
||||||
click.echo(" * Environment: {0}".format(env))
|
click.echo(f" * Environment: {env}")
|
||||||
|
|
||||||
if env == "production":
|
if env == "production":
|
||||||
click.secho(
|
click.secho(
|
||||||
" WARNING: This is a development server. "
|
" WARNING: This is a development server. Do not use it in"
|
||||||
"Do not use it in a production deployment.",
|
" a production deployment.",
|
||||||
fg="red",
|
fg="red",
|
||||||
)
|
)
|
||||||
click.secho(" Use a production WSGI server instead.", dim=True)
|
click.secho(" Use a production WSGI server instead.", dim=True)
|
||||||
|
|
||||||
if debug is not None:
|
if debug is not None:
|
||||||
click.echo(" * Debug mode: {0}".format("on" if debug else "off"))
|
click.echo(f" * Debug mode: {'on' if debug else 'off'}")
|
||||||
|
|
||||||
|
|
||||||
class CertParamType(click.ParamType):
|
class CertParamType(click.ParamType):
|
||||||
|
|
@ -725,12 +710,8 @@ class CertParamType(click.ParamType):
|
||||||
|
|
||||||
obj = import_string(value, silent=True)
|
obj = import_string(value, silent=True)
|
||||||
|
|
||||||
if sys.version_info < (2, 7, 9):
|
if isinstance(obj, ssl.SSLContext):
|
||||||
if obj:
|
return obj
|
||||||
return obj
|
|
||||||
else:
|
|
||||||
if isinstance(obj, ssl.SSLContext):
|
|
||||||
return obj
|
|
||||||
|
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
@ -741,11 +722,7 @@ def _validate_key(ctx, param, value):
|
||||||
"""
|
"""
|
||||||
cert = ctx.params.get("cert")
|
cert = ctx.params.get("cert")
|
||||||
is_adhoc = cert == "adhoc"
|
is_adhoc = cert == "adhoc"
|
||||||
|
is_context = ssl and isinstance(cert, ssl.SSLContext)
|
||||||
if sys.version_info < (2, 7, 9):
|
|
||||||
is_context = cert and not isinstance(cert, (text_type, bytes))
|
|
||||||
else:
|
|
||||||
is_context = ssl and isinstance(cert, ssl.SSLContext)
|
|
||||||
|
|
||||||
if value is not None:
|
if value is not None:
|
||||||
if is_adhoc:
|
if is_adhoc:
|
||||||
|
|
@ -778,7 +755,7 @@ class SeparatedPathType(click.Path):
|
||||||
|
|
||||||
def convert(self, value, param, ctx):
|
def convert(self, value, param, ctx):
|
||||||
items = self.split_envvar_value(value)
|
items = self.split_envvar_value(value)
|
||||||
super_convert = super(SeparatedPathType, self).convert
|
super_convert = super().convert
|
||||||
return [super_convert(item, param, ctx) for item in items]
|
return [super_convert(item, param, ctx) for item in items]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -824,7 +801,7 @@ class SeparatedPathType(click.Path):
|
||||||
type=SeparatedPathType(),
|
type=SeparatedPathType(),
|
||||||
help=(
|
help=(
|
||||||
"Extra files that trigger a reload on change. Multiple paths"
|
"Extra files that trigger a reload on change. Multiple paths"
|
||||||
" are separated by '{}'.".format(os.path.pathsep)
|
f" are separated by {os.path.pathsep!r}."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@pass_script_info
|
@pass_script_info
|
||||||
|
|
@ -878,12 +855,10 @@ def shell_command():
|
||||||
from .globals import _app_ctx_stack
|
from .globals import _app_ctx_stack
|
||||||
|
|
||||||
app = _app_ctx_stack.top.app
|
app = _app_ctx_stack.top.app
|
||||||
banner = "Python %s on %s\nApp: %s [%s]\nInstance: %s" % (
|
banner = (
|
||||||
sys.version,
|
f"Python {sys.version} on {sys.platform}\n"
|
||||||
sys.platform,
|
f"App: {app.import_name} [{app.env}]\n"
|
||||||
app.import_name,
|
f"Instance: {app.instance_path}"
|
||||||
app.env,
|
|
||||||
app.instance_path,
|
|
||||||
)
|
)
|
||||||
ctx = {}
|
ctx = {}
|
||||||
|
|
||||||
|
|
@ -891,7 +866,7 @@ def shell_command():
|
||||||
# is using it.
|
# is using it.
|
||||||
startup = os.environ.get("PYTHONSTARTUP")
|
startup = os.environ.get("PYTHONSTARTUP")
|
||||||
if startup and os.path.isfile(startup):
|
if startup and os.path.isfile(startup):
|
||||||
with open(startup, "r") as f:
|
with open(startup) as f:
|
||||||
eval(compile(f.read(), startup, "exec"), ctx)
|
eval(compile(f.read(), startup, "exec"), ctx)
|
||||||
|
|
||||||
ctx.update(app.make_shell_context())
|
ctx.update(app.make_shell_context())
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
flask.config
|
flask.config
|
||||||
~~~~~~~~~~~~
|
~~~~~~~~~~~~
|
||||||
|
|
@ -11,15 +10,11 @@
|
||||||
import errno
|
import errno
|
||||||
import os
|
import os
|
||||||
import types
|
import types
|
||||||
import warnings
|
|
||||||
|
|
||||||
from werkzeug.utils import import_string
|
from werkzeug.utils import import_string
|
||||||
|
|
||||||
from ._compat import iteritems
|
|
||||||
from ._compat import string_types
|
|
||||||
|
|
||||||
|
class ConfigAttribute:
|
||||||
class ConfigAttribute(object):
|
|
||||||
"""Makes an attribute forward to the config"""
|
"""Makes an attribute forward to the config"""
|
||||||
|
|
||||||
def __init__(self, name, get_converter=None):
|
def __init__(self, name, get_converter=None):
|
||||||
|
|
@ -103,10 +98,10 @@ class Config(dict):
|
||||||
if silent:
|
if silent:
|
||||||
return False
|
return False
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"The environment variable %r is not set "
|
f"The environment variable {variable_name!r} is not set"
|
||||||
"and as such configuration could not be "
|
" and as such configuration could not be loaded. Set"
|
||||||
"loaded. Set this variable and make it "
|
" this variable and make it point to a configuration"
|
||||||
"point to a configuration file" % variable_name
|
" file"
|
||||||
)
|
)
|
||||||
return self.from_pyfile(rv, silent=silent)
|
return self.from_pyfile(rv, silent=silent)
|
||||||
|
|
||||||
|
|
@ -130,10 +125,10 @@ class Config(dict):
|
||||||
try:
|
try:
|
||||||
with open(filename, mode="rb") as config_file:
|
with open(filename, mode="rb") as config_file:
|
||||||
exec(compile(config_file.read(), filename, "exec"), d.__dict__)
|
exec(compile(config_file.read(), filename, "exec"), d.__dict__)
|
||||||
except IOError as e:
|
except OSError as e:
|
||||||
if silent and e.errno in (errno.ENOENT, errno.EISDIR, errno.ENOTDIR):
|
if silent and e.errno in (errno.ENOENT, errno.EISDIR, errno.ENOTDIR):
|
||||||
return False
|
return False
|
||||||
e.strerror = "Unable to load configuration file (%s)" % e.strerror
|
e.strerror = f"Unable to load configuration file ({e.strerror})"
|
||||||
raise
|
raise
|
||||||
self.from_object(d)
|
self.from_object(d)
|
||||||
return True
|
return True
|
||||||
|
|
@ -170,7 +165,7 @@ class Config(dict):
|
||||||
|
|
||||||
:param obj: an import name or object
|
:param obj: an import name or object
|
||||||
"""
|
"""
|
||||||
if isinstance(obj, string_types):
|
if isinstance(obj, str):
|
||||||
obj = import_string(obj)
|
obj = import_string(obj)
|
||||||
for key in dir(obj):
|
for key in dir(obj):
|
||||||
if key.isupper():
|
if key.isupper():
|
||||||
|
|
@ -201,38 +196,15 @@ class Config(dict):
|
||||||
try:
|
try:
|
||||||
with open(filename) as f:
|
with open(filename) as f:
|
||||||
obj = load(f)
|
obj = load(f)
|
||||||
except IOError as e:
|
except OSError as e:
|
||||||
if silent and e.errno in (errno.ENOENT, errno.EISDIR):
|
if silent and e.errno in (errno.ENOENT, errno.EISDIR):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
e.strerror = "Unable to load configuration file (%s)" % e.strerror
|
e.strerror = f"Unable to load configuration file ({e.strerror})"
|
||||||
raise
|
raise
|
||||||
|
|
||||||
return self.from_mapping(obj)
|
return self.from_mapping(obj)
|
||||||
|
|
||||||
def from_json(self, filename, silent=False):
|
|
||||||
"""Update the values in the config from a JSON file. The loaded
|
|
||||||
data is passed to the :meth:`from_mapping` method.
|
|
||||||
|
|
||||||
:param filename: The path to the JSON file. This can be an
|
|
||||||
absolute path or relative to the config root path.
|
|
||||||
:param silent: Ignore the file if it doesn't exist.
|
|
||||||
|
|
||||||
.. deprecated:: 2.0
|
|
||||||
Use :meth:`from_file` with :meth:`json.load` instead.
|
|
||||||
|
|
||||||
.. versionadded:: 0.11
|
|
||||||
"""
|
|
||||||
warnings.warn(
|
|
||||||
"'from_json' is deprecated and will be removed in 2.0."
|
|
||||||
" Use 'from_file(filename, load=json.load)' instead.",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
from .json import load
|
|
||||||
|
|
||||||
return self.from_file(filename, load, silent=silent)
|
|
||||||
|
|
||||||
def from_mapping(self, *mapping, **kwargs):
|
def from_mapping(self, *mapping, **kwargs):
|
||||||
"""Updates the config like :meth:`update` ignoring items with non-upper
|
"""Updates the config like :meth:`update` ignoring items with non-upper
|
||||||
keys.
|
keys.
|
||||||
|
|
@ -247,7 +219,7 @@ class Config(dict):
|
||||||
mappings.append(mapping[0])
|
mappings.append(mapping[0])
|
||||||
elif len(mapping) > 1:
|
elif len(mapping) > 1:
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
"expected at most 1 positional argument, got %d" % len(mapping)
|
f"expected at most 1 positional argument, got {len(mapping)}"
|
||||||
)
|
)
|
||||||
mappings.append(kwargs.items())
|
mappings.append(kwargs.items())
|
||||||
for mapping in mappings:
|
for mapping in mappings:
|
||||||
|
|
@ -285,7 +257,7 @@ class Config(dict):
|
||||||
.. versionadded:: 0.11
|
.. versionadded:: 0.11
|
||||||
"""
|
"""
|
||||||
rv = {}
|
rv = {}
|
||||||
for k, v in iteritems(self):
|
for k, v in self.items():
|
||||||
if not k.startswith(namespace):
|
if not k.startswith(namespace):
|
||||||
continue
|
continue
|
||||||
if trim_namespace:
|
if trim_namespace:
|
||||||
|
|
@ -298,4 +270,4 @@ class Config(dict):
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<%s %s>" % (self.__class__.__name__, dict.__repr__(self))
|
return f"<{type(self).__name__} {dict.__repr__(self)}>"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
flask.ctx
|
flask.ctx
|
||||||
~~~~~~~~~
|
~~~~~~~~~
|
||||||
|
|
@ -13,8 +12,6 @@ from functools import update_wrapper
|
||||||
|
|
||||||
from werkzeug.exceptions import HTTPException
|
from werkzeug.exceptions import HTTPException
|
||||||
|
|
||||||
from ._compat import BROKEN_PYPY_CTXMGR_EXIT
|
|
||||||
from ._compat import reraise
|
|
||||||
from .globals import _app_ctx_stack
|
from .globals import _app_ctx_stack
|
||||||
from .globals import _request_ctx_stack
|
from .globals import _request_ctx_stack
|
||||||
from .signals import appcontext_popped
|
from .signals import appcontext_popped
|
||||||
|
|
@ -25,7 +22,7 @@ from .signals import appcontext_pushed
|
||||||
_sentinel = object()
|
_sentinel = object()
|
||||||
|
|
||||||
|
|
||||||
class _AppCtxGlobals(object):
|
class _AppCtxGlobals:
|
||||||
"""A plain object. Used as a namespace for storing data during an
|
"""A plain object. Used as a namespace for storing data during an
|
||||||
application context.
|
application context.
|
||||||
|
|
||||||
|
|
@ -91,7 +88,7 @@ class _AppCtxGlobals(object):
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
top = _app_ctx_stack.top
|
top = _app_ctx_stack.top
|
||||||
if top is not None:
|
if top is not None:
|
||||||
return "<flask.g of %r>" % top.app.name
|
return f"<flask.g of {top.app.name!r}>"
|
||||||
return object.__repr__(self)
|
return object.__repr__(self)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -202,7 +199,7 @@ def has_app_context():
|
||||||
return _app_ctx_stack.top is not None
|
return _app_ctx_stack.top is not None
|
||||||
|
|
||||||
|
|
||||||
class AppContext(object):
|
class AppContext:
|
||||||
"""The application context binds an application object implicitly
|
"""The application context binds an application object implicitly
|
||||||
to the current thread or greenlet, similar to how the
|
to the current thread or greenlet, similar to how the
|
||||||
:class:`RequestContext` binds request information. The application
|
:class:`RequestContext` binds request information. The application
|
||||||
|
|
@ -223,8 +220,6 @@ class AppContext(object):
|
||||||
def push(self):
|
def push(self):
|
||||||
"""Binds the app context to the current context."""
|
"""Binds the app context to the current context."""
|
||||||
self._refcnt += 1
|
self._refcnt += 1
|
||||||
if hasattr(sys, "exc_clear"):
|
|
||||||
sys.exc_clear()
|
|
||||||
_app_ctx_stack.push(self)
|
_app_ctx_stack.push(self)
|
||||||
appcontext_pushed.send(self.app)
|
appcontext_pushed.send(self.app)
|
||||||
|
|
||||||
|
|
@ -238,7 +233,7 @@ class AppContext(object):
|
||||||
self.app.do_teardown_appcontext(exc)
|
self.app.do_teardown_appcontext(exc)
|
||||||
finally:
|
finally:
|
||||||
rv = _app_ctx_stack.pop()
|
rv = _app_ctx_stack.pop()
|
||||||
assert rv is self, "Popped wrong app context. (%r instead of %r)" % (rv, self)
|
assert rv is self, f"Popped wrong app context. ({rv!r} instead of {self!r})"
|
||||||
appcontext_popped.send(self.app)
|
appcontext_popped.send(self.app)
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
|
|
@ -248,11 +243,8 @@ class AppContext(object):
|
||||||
def __exit__(self, exc_type, exc_value, tb):
|
def __exit__(self, exc_type, exc_value, tb):
|
||||||
self.pop(exc_value)
|
self.pop(exc_value)
|
||||||
|
|
||||||
if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:
|
|
||||||
reraise(exc_type, exc_value, tb)
|
|
||||||
|
|
||||||
|
class RequestContext:
|
||||||
class RequestContext(object):
|
|
||||||
"""The request context contains all request relevant information. It is
|
"""The request context contains all request relevant information. It is
|
||||||
created at the beginning of the request and pushed to the
|
created at the beginning of the request and pushed to the
|
||||||
`_request_ctx_stack` and removed at the end of it. It will create the
|
`_request_ctx_stack` and removed at the end of it. It will create the
|
||||||
|
|
@ -376,9 +368,6 @@ class RequestContext(object):
|
||||||
else:
|
else:
|
||||||
self._implicit_app_ctx_stack.append(None)
|
self._implicit_app_ctx_stack.append(None)
|
||||||
|
|
||||||
if hasattr(sys, "exc_clear"):
|
|
||||||
sys.exc_clear()
|
|
||||||
|
|
||||||
_request_ctx_stack.push(self)
|
_request_ctx_stack.push(self)
|
||||||
|
|
||||||
# Open the session at the moment that the request context is available.
|
# Open the session at the moment that the request context is available.
|
||||||
|
|
@ -404,9 +393,9 @@ class RequestContext(object):
|
||||||
Added the `exc` argument.
|
Added the `exc` argument.
|
||||||
"""
|
"""
|
||||||
app_ctx = self._implicit_app_ctx_stack.pop()
|
app_ctx = self._implicit_app_ctx_stack.pop()
|
||||||
|
clear_request = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
clear_request = False
|
|
||||||
if not self._implicit_app_ctx_stack:
|
if not self._implicit_app_ctx_stack:
|
||||||
self.preserved = False
|
self.preserved = False
|
||||||
self._preserved_exc = None
|
self._preserved_exc = None
|
||||||
|
|
@ -414,13 +403,6 @@ class RequestContext(object):
|
||||||
exc = sys.exc_info()[1]
|
exc = sys.exc_info()[1]
|
||||||
self.app.do_teardown_request(exc)
|
self.app.do_teardown_request(exc)
|
||||||
|
|
||||||
# If this interpreter supports clearing the exception information
|
|
||||||
# we do that now. This will only go into effect on Python 2.x,
|
|
||||||
# on 3.x it disappears automatically at the end of the exception
|
|
||||||
# stack.
|
|
||||||
if hasattr(sys, "exc_clear"):
|
|
||||||
sys.exc_clear()
|
|
||||||
|
|
||||||
request_close = getattr(self.request, "close", None)
|
request_close = getattr(self.request, "close", None)
|
||||||
if request_close is not None:
|
if request_close is not None:
|
||||||
request_close()
|
request_close()
|
||||||
|
|
@ -437,10 +419,9 @@ class RequestContext(object):
|
||||||
if app_ctx is not None:
|
if app_ctx is not None:
|
||||||
app_ctx.pop(exc)
|
app_ctx.pop(exc)
|
||||||
|
|
||||||
assert rv is self, "Popped wrong request context. (%r instead of %r)" % (
|
assert (
|
||||||
rv,
|
rv is self
|
||||||
self,
|
), f"Popped wrong request context. ({rv!r} instead of {self!r})"
|
||||||
)
|
|
||||||
|
|
||||||
def auto_pop(self, exc):
|
def auto_pop(self, exc):
|
||||||
if self.request.environ.get("flask._preserve_context") or (
|
if self.request.environ.get("flask._preserve_context") or (
|
||||||
|
|
@ -463,13 +444,8 @@ class RequestContext(object):
|
||||||
# See flask.testing for how this works.
|
# See flask.testing for how this works.
|
||||||
self.auto_pop(exc_value)
|
self.auto_pop(exc_value)
|
||||||
|
|
||||||
if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:
|
|
||||||
reraise(exc_type, exc_value, tb)
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<%s '%s' [%s] of %s>" % (
|
return (
|
||||||
self.__class__.__name__,
|
f"<{type(self).__name__} {self.request.url!r}"
|
||||||
self.request.url,
|
f" [{self.request.method}] of {self.app.name}>"
|
||||||
self.request.method,
|
|
||||||
self.app.name,
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
flask.debughelpers
|
flask.debughelpers
|
||||||
~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
@ -11,8 +10,6 @@
|
||||||
import os
|
import os
|
||||||
from warnings import warn
|
from warnings import warn
|
||||||
|
|
||||||
from ._compat import implements_to_string
|
|
||||||
from ._compat import text_type
|
|
||||||
from .app import Flask
|
from .app import Flask
|
||||||
from .blueprints import Blueprint
|
from .blueprints import Blueprint
|
||||||
from .globals import _request_ctx_stack
|
from .globals import _request_ctx_stack
|
||||||
|
|
@ -24,7 +21,6 @@ class UnexpectedUnicodeError(AssertionError, UnicodeError):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@implements_to_string
|
|
||||||
class DebugFilesKeyError(KeyError, AssertionError):
|
class DebugFilesKeyError(KeyError, AssertionError):
|
||||||
"""Raised from request.files during debugging. The idea is that it can
|
"""Raised from request.files during debugging. The idea is that it can
|
||||||
provide a better error message than just a generic KeyError/BadRequest.
|
provide a better error message than just a generic KeyError/BadRequest.
|
||||||
|
|
@ -33,17 +29,18 @@ class DebugFilesKeyError(KeyError, AssertionError):
|
||||||
def __init__(self, request, key):
|
def __init__(self, request, key):
|
||||||
form_matches = request.form.getlist(key)
|
form_matches = request.form.getlist(key)
|
||||||
buf = [
|
buf = [
|
||||||
'You tried to access the file "%s" in the request.files '
|
f"You tried to access the file {key!r} in the request.files"
|
||||||
"dictionary but it does not exist. The mimetype for the request "
|
" dictionary but it does not exist. The mimetype for the"
|
||||||
'is "%s" instead of "multipart/form-data" which means that no '
|
f" request is {request.mimetype!r} instead of"
|
||||||
"file contents were transmitted. To fix this error you should "
|
" 'multipart/form-data' which means that no file contents"
|
||||||
'provide enctype="multipart/form-data" in your form.'
|
" were transmitted. To fix this error you should provide"
|
||||||
% (key, request.mimetype)
|
' enctype="multipart/form-data" in your form.'
|
||||||
]
|
]
|
||||||
if form_matches:
|
if form_matches:
|
||||||
|
names = ", ".join(repr(x) for x in form_matches)
|
||||||
buf.append(
|
buf.append(
|
||||||
"\n\nThe browser instead transmitted some file names. "
|
"\n\nThe browser instead transmitted some file names. "
|
||||||
"This was submitted: %s" % ", ".join('"%s"' % x for x in form_matches)
|
f"This was submitted: {names}"
|
||||||
)
|
)
|
||||||
self.msg = "".join(buf)
|
self.msg = "".join(buf)
|
||||||
|
|
||||||
|
|
@ -60,24 +57,24 @@ class FormDataRoutingRedirect(AssertionError):
|
||||||
def __init__(self, request):
|
def __init__(self, request):
|
||||||
exc = request.routing_exception
|
exc = request.routing_exception
|
||||||
buf = [
|
buf = [
|
||||||
"A request was sent to this URL (%s) but a redirect was "
|
f"A request was sent to this URL ({request.url}) but a"
|
||||||
'issued automatically by the routing system to "%s".'
|
" redirect was issued automatically by the routing system"
|
||||||
% (request.url, exc.new_url)
|
f" to {exc.new_url!r}."
|
||||||
]
|
]
|
||||||
|
|
||||||
# In case just a slash was appended we can be extra helpful
|
# In case just a slash was appended we can be extra helpful
|
||||||
if request.base_url + "/" == exc.new_url.split("?")[0]:
|
if f"{request.base_url}/" == exc.new_url.split("?")[0]:
|
||||||
buf.append(
|
buf.append(
|
||||||
" The URL was defined with a trailing slash so "
|
" The URL was defined with a trailing slash so Flask"
|
||||||
"Flask will automatically redirect to the URL "
|
" will automatically redirect to the URL with the"
|
||||||
"with the trailing slash if it was accessed "
|
" trailing slash if it was accessed without one."
|
||||||
"without one."
|
|
||||||
)
|
)
|
||||||
|
|
||||||
buf.append(
|
buf.append(
|
||||||
" Make sure to directly send your %s-request to this URL "
|
" Make sure to directly send your"
|
||||||
"since we can't make browsers or HTTP clients redirect "
|
f" {request.method}-request to this URL since we can't make"
|
||||||
"with form data reliably or without user interaction." % request.method
|
" browsers or HTTP clients redirect with form data reliably"
|
||||||
|
" or without user interaction."
|
||||||
)
|
)
|
||||||
buf.append("\n\nNote: this exception is only raised in debug mode")
|
buf.append("\n\nNote: this exception is only raised in debug mode")
|
||||||
AssertionError.__init__(self, "".join(buf).encode("utf-8"))
|
AssertionError.__init__(self, "".join(buf).encode("utf-8"))
|
||||||
|
|
@ -105,25 +102,25 @@ def attach_enctype_error_multidict(request):
|
||||||
|
|
||||||
|
|
||||||
def _dump_loader_info(loader):
|
def _dump_loader_info(loader):
|
||||||
yield "class: %s.%s" % (type(loader).__module__, type(loader).__name__)
|
yield f"class: {type(loader).__module__}.{type(loader).__name__}"
|
||||||
for key, value in sorted(loader.__dict__.items()):
|
for key, value in sorted(loader.__dict__.items()):
|
||||||
if key.startswith("_"):
|
if key.startswith("_"):
|
||||||
continue
|
continue
|
||||||
if isinstance(value, (tuple, list)):
|
if isinstance(value, (tuple, list)):
|
||||||
if not all(isinstance(x, (str, text_type)) for x in value):
|
if not all(isinstance(x, str) for x in value):
|
||||||
continue
|
continue
|
||||||
yield "%s:" % key
|
yield f"{key}:"
|
||||||
for item in value:
|
for item in value:
|
||||||
yield " - %s" % item
|
yield f" - {item}"
|
||||||
continue
|
continue
|
||||||
elif not isinstance(value, (str, text_type, int, float, bool)):
|
elif not isinstance(value, (str, int, float, bool)):
|
||||||
continue
|
continue
|
||||||
yield "%s: %r" % (key, value)
|
yield f"{key}: {value!r}"
|
||||||
|
|
||||||
|
|
||||||
def explain_template_loading_attempts(app, template, attempts):
|
def explain_template_loading_attempts(app, template, attempts):
|
||||||
"""This should help developers understand what failed"""
|
"""This should help developers understand what failed"""
|
||||||
info = ['Locating template "%s":' % template]
|
info = [f"Locating template {template!r}:"]
|
||||||
total_found = 0
|
total_found = 0
|
||||||
blueprint = None
|
blueprint = None
|
||||||
reqctx = _request_ctx_stack.top
|
reqctx = _request_ctx_stack.top
|
||||||
|
|
@ -132,23 +129,23 @@ def explain_template_loading_attempts(app, template, attempts):
|
||||||
|
|
||||||
for idx, (loader, srcobj, triple) in enumerate(attempts):
|
for idx, (loader, srcobj, triple) in enumerate(attempts):
|
||||||
if isinstance(srcobj, Flask):
|
if isinstance(srcobj, Flask):
|
||||||
src_info = 'application "%s"' % srcobj.import_name
|
src_info = f"application {srcobj.import_name!r}"
|
||||||
elif isinstance(srcobj, Blueprint):
|
elif isinstance(srcobj, Blueprint):
|
||||||
src_info = 'blueprint "%s" (%s)' % (srcobj.name, srcobj.import_name)
|
src_info = f"blueprint {srcobj.name!r} ({srcobj.import_name})"
|
||||||
else:
|
else:
|
||||||
src_info = repr(srcobj)
|
src_info = repr(srcobj)
|
||||||
|
|
||||||
info.append("% 5d: trying loader of %s" % (idx + 1, src_info))
|
info.append(f"{idx + 1:5}: trying loader of {src_info}")
|
||||||
|
|
||||||
for line in _dump_loader_info(loader):
|
for line in _dump_loader_info(loader):
|
||||||
info.append(" %s" % line)
|
info.append(f" {line}")
|
||||||
|
|
||||||
if triple is None:
|
if triple is None:
|
||||||
detail = "no match"
|
detail = "no match"
|
||||||
else:
|
else:
|
||||||
detail = "found (%r)" % (triple[1] or "<string>")
|
detail = f"found ({triple[1] or '<string>'!r})"
|
||||||
total_found += 1
|
total_found += 1
|
||||||
info.append(" -> %s" % detail)
|
info.append(f" -> {detail}")
|
||||||
|
|
||||||
seems_fishy = False
|
seems_fishy = False
|
||||||
if total_found == 0:
|
if total_found == 0:
|
||||||
|
|
@ -160,8 +157,8 @@ def explain_template_loading_attempts(app, template, attempts):
|
||||||
|
|
||||||
if blueprint is not None and seems_fishy:
|
if blueprint is not None and seems_fishy:
|
||||||
info.append(
|
info.append(
|
||||||
" The template was looked up from an endpoint that "
|
" The template was looked up from an endpoint that belongs"
|
||||||
'belongs to the blueprint "%s".' % blueprint
|
f" to the blueprint {blueprint!r}."
|
||||||
)
|
)
|
||||||
info.append(" Maybe you did not place a template in the right folder?")
|
info.append(" Maybe you did not place a template in the right folder?")
|
||||||
info.append(" See https://flask.palletsprojects.com/blueprints/#templates")
|
info.append(" See https://flask.palletsprojects.com/blueprints/#templates")
|
||||||
|
|
@ -173,11 +170,10 @@ def explain_ignored_app_run():
|
||||||
if os.environ.get("WERKZEUG_RUN_MAIN") != "true":
|
if os.environ.get("WERKZEUG_RUN_MAIN") != "true":
|
||||||
warn(
|
warn(
|
||||||
Warning(
|
Warning(
|
||||||
"Silently ignoring app.run() because the "
|
"Silently ignoring app.run() because the application is"
|
||||||
"application is run from the flask command line "
|
" run from the flask command line executable. Consider"
|
||||||
"executable. Consider putting app.run() behind an "
|
' putting app.run() behind an if __name__ == "__main__"'
|
||||||
'if __name__ == "__main__" guard to silence this '
|
" guard to silence this warning."
|
||||||
"warning."
|
|
||||||
),
|
),
|
||||||
stacklevel=3,
|
stacklevel=3,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
flask.globals
|
flask.globals
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
flask.helpers
|
flask.helpers
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
@ -30,10 +29,6 @@ from werkzeug.routing import BuildError
|
||||||
from werkzeug.urls import url_quote
|
from werkzeug.urls import url_quote
|
||||||
from werkzeug.wsgi import wrap_file
|
from werkzeug.wsgi import wrap_file
|
||||||
|
|
||||||
from ._compat import fspath
|
|
||||||
from ._compat import PY2
|
|
||||||
from ._compat import string_types
|
|
||||||
from ._compat import text_type
|
|
||||||
from .globals import _app_ctx_stack
|
from .globals import _app_ctx_stack
|
||||||
from .globals import _request_ctx_stack
|
from .globals import _request_ctx_stack
|
||||||
from .globals import current_app
|
from .globals import current_app
|
||||||
|
|
@ -159,8 +154,7 @@ def stream_with_context(generator_or_function):
|
||||||
# don't need that because they are closed on their destruction
|
# don't need that because they are closed on their destruction
|
||||||
# automatically.
|
# automatically.
|
||||||
try:
|
try:
|
||||||
for item in gen:
|
yield from gen
|
||||||
yield item
|
|
||||||
finally:
|
finally:
|
||||||
if hasattr(gen, "close"):
|
if hasattr(gen, "close"):
|
||||||
gen.close()
|
gen.close()
|
||||||
|
|
@ -317,7 +311,7 @@ def url_for(endpoint, **values):
|
||||||
|
|
||||||
if endpoint[:1] == ".":
|
if endpoint[:1] == ".":
|
||||||
if blueprint_name is not None:
|
if blueprint_name is not None:
|
||||||
endpoint = blueprint_name + endpoint
|
endpoint = f"{blueprint_name}{endpoint}"
|
||||||
else:
|
else:
|
||||||
endpoint = endpoint[1:]
|
endpoint = endpoint[1:]
|
||||||
|
|
||||||
|
|
@ -370,7 +364,7 @@ def url_for(endpoint, **values):
|
||||||
return appctx.app.handle_url_build_error(error, endpoint, values)
|
return appctx.app.handle_url_build_error(error, endpoint, values)
|
||||||
|
|
||||||
if anchor is not None:
|
if anchor is not None:
|
||||||
rv += "#" + url_quote(anchor)
|
rv += f"#{url_quote(anchor)}"
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -576,9 +570,9 @@ def send_file(
|
||||||
fsize = None
|
fsize = None
|
||||||
|
|
||||||
if hasattr(filename_or_fp, "__fspath__"):
|
if hasattr(filename_or_fp, "__fspath__"):
|
||||||
filename_or_fp = fspath(filename_or_fp)
|
filename_or_fp = os.fspath(filename_or_fp)
|
||||||
|
|
||||||
if isinstance(filename_or_fp, string_types):
|
if isinstance(filename_or_fp, str):
|
||||||
filename = filename_or_fp
|
filename = filename_or_fp
|
||||||
if not os.path.isabs(filename):
|
if not os.path.isabs(filename):
|
||||||
filename = os.path.join(current_app.root_path, filename)
|
filename = os.path.join(current_app.root_path, filename)
|
||||||
|
|
@ -608,17 +602,18 @@ def send_file(
|
||||||
if attachment_filename is None:
|
if attachment_filename is None:
|
||||||
raise TypeError("filename unavailable, required for sending as attachment")
|
raise TypeError("filename unavailable, required for sending as attachment")
|
||||||
|
|
||||||
if not isinstance(attachment_filename, text_type):
|
if not isinstance(attachment_filename, str):
|
||||||
attachment_filename = attachment_filename.decode("utf-8")
|
attachment_filename = attachment_filename.decode("utf-8")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
attachment_filename = attachment_filename.encode("ascii")
|
attachment_filename = attachment_filename.encode("ascii")
|
||||||
except UnicodeEncodeError:
|
except UnicodeEncodeError:
|
||||||
|
quoted = url_quote(attachment_filename, safe="")
|
||||||
filenames = {
|
filenames = {
|
||||||
"filename": unicodedata.normalize("NFKD", attachment_filename).encode(
|
"filename": unicodedata.normalize("NFKD", attachment_filename).encode(
|
||||||
"ascii", "ignore"
|
"ascii", "ignore"
|
||||||
),
|
),
|
||||||
"filename*": "UTF-8''%s" % url_quote(attachment_filename, safe=b""),
|
"filename*": f"UTF-8''{quoted}",
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
filenames = {"filename": attachment_filename}
|
filenames = {"filename": attachment_filename}
|
||||||
|
|
@ -638,11 +633,7 @@ def send_file(
|
||||||
mtime = os.path.getmtime(filename)
|
mtime = os.path.getmtime(filename)
|
||||||
fsize = os.path.getsize(filename)
|
fsize = os.path.getsize(filename)
|
||||||
elif isinstance(file, io.BytesIO):
|
elif isinstance(file, io.BytesIO):
|
||||||
try:
|
fsize = file.getbuffer().nbytes
|
||||||
fsize = file.getbuffer().nbytes
|
|
||||||
except AttributeError:
|
|
||||||
# Python 2 doesn't have getbuffer
|
|
||||||
fsize = len(file.getvalue())
|
|
||||||
elif isinstance(file, io.TextIOBase):
|
elif isinstance(file, io.TextIOBase):
|
||||||
raise ValueError("Files must be opened in binary mode or use BytesIO.")
|
raise ValueError("Files must be opened in binary mode or use BytesIO.")
|
||||||
|
|
||||||
|
|
@ -671,23 +662,19 @@ def send_file(
|
||||||
from warnings import warn
|
from warnings import warn
|
||||||
|
|
||||||
try:
|
try:
|
||||||
rv.set_etag(
|
check = (
|
||||||
"%s-%s-%s"
|
adler32(
|
||||||
% (
|
filename.encode("utf-8") if isinstance(filename, str) else filename
|
||||||
os.path.getmtime(filename),
|
|
||||||
os.path.getsize(filename),
|
|
||||||
adler32(
|
|
||||||
filename.encode("utf-8")
|
|
||||||
if isinstance(filename, text_type)
|
|
||||||
else filename
|
|
||||||
)
|
|
||||||
& 0xFFFFFFFF,
|
|
||||||
)
|
)
|
||||||
|
& 0xFFFFFFFF
|
||||||
|
)
|
||||||
|
rv.set_etag(
|
||||||
|
f"{os.path.getmtime(filename)}-{os.path.getsize(filename)}-{check}"
|
||||||
)
|
)
|
||||||
except OSError:
|
except OSError:
|
||||||
warn(
|
warn(
|
||||||
"Access %s failed, maybe it does not exist, so ignore etags in "
|
f"Access {filename} failed, maybe it does not exist, so"
|
||||||
"headers" % filename,
|
" ignore etags in headers",
|
||||||
stacklevel=2,
|
stacklevel=2,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -769,8 +756,8 @@ def send_from_directory(directory, filename, **options):
|
||||||
:param options: optional keyword arguments that are directly
|
:param options: optional keyword arguments that are directly
|
||||||
forwarded to :func:`send_file`.
|
forwarded to :func:`send_file`.
|
||||||
"""
|
"""
|
||||||
filename = fspath(filename)
|
filename = os.fspath(filename)
|
||||||
directory = fspath(directory)
|
directory = os.fspath(directory)
|
||||||
filename = safe_join(directory, filename)
|
filename = safe_join(directory, filename)
|
||||||
if not os.path.isabs(filename):
|
if not os.path.isabs(filename):
|
||||||
filename = os.path.join(current_app.root_path, filename)
|
filename = os.path.join(current_app.root_path, filename)
|
||||||
|
|
@ -803,8 +790,6 @@ def get_root_path(import_name):
|
||||||
if loader is None or import_name == "__main__":
|
if loader is None or import_name == "__main__":
|
||||||
return os.getcwd()
|
return os.getcwd()
|
||||||
|
|
||||||
# For .egg, zipimporter does not have get_filename until Python 2.7.
|
|
||||||
# Some other loaders might exhibit the same behavior.
|
|
||||||
if hasattr(loader, "get_filename"):
|
if hasattr(loader, "get_filename"):
|
||||||
filepath = loader.get_filename(import_name)
|
filepath = loader.get_filename(import_name)
|
||||||
else:
|
else:
|
||||||
|
|
@ -818,13 +803,12 @@ def get_root_path(import_name):
|
||||||
# first module that is contained in our package.
|
# first module that is contained in our package.
|
||||||
if filepath is None:
|
if filepath is None:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"No root path can be found for the provided "
|
"No root path can be found for the provided module"
|
||||||
'module "%s". This can happen because the '
|
f" {import_name!r}. This can happen because the module"
|
||||||
"module came from an import hook that does "
|
" came from an import hook that does not provide file"
|
||||||
"not provide file name information or because "
|
" name information or because it's a namespace package."
|
||||||
"it's a namespace package. In this case "
|
" In this case the root path needs to be explicitly"
|
||||||
"the root path needs to be explicitly "
|
" provided."
|
||||||
"provided." % import_name
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# filepath is import_name.py for a module, or __init__.py for a package.
|
# filepath is import_name.py for a module, or __init__.py for a package.
|
||||||
|
|
@ -835,6 +819,7 @@ def _matching_loader_thinks_module_is_package(loader, mod_name):
|
||||||
"""Given the loader that loaded a module and the module this function
|
"""Given the loader that loaded a module and the module this function
|
||||||
attempts to figure out if the given module is actually a package.
|
attempts to figure out if the given module is actually a package.
|
||||||
"""
|
"""
|
||||||
|
cls = type(loader)
|
||||||
# If the loader can tell us if something is a package, we can
|
# If the loader can tell us if something is a package, we can
|
||||||
# directly ask the loader.
|
# directly ask the loader.
|
||||||
if hasattr(loader, "is_package"):
|
if hasattr(loader, "is_package"):
|
||||||
|
|
@ -842,49 +827,41 @@ def _matching_loader_thinks_module_is_package(loader, mod_name):
|
||||||
# importlib's namespace loaders do not have this functionality but
|
# importlib's namespace loaders do not have this functionality but
|
||||||
# all the modules it loads are packages, so we can take advantage of
|
# all the modules it loads are packages, so we can take advantage of
|
||||||
# this information.
|
# this information.
|
||||||
elif (
|
elif cls.__module__ == "_frozen_importlib" and cls.__name__ == "NamespaceLoader":
|
||||||
loader.__class__.__module__ == "_frozen_importlib"
|
|
||||||
and loader.__class__.__name__ == "NamespaceLoader"
|
|
||||||
):
|
|
||||||
return True
|
return True
|
||||||
# Otherwise we need to fail with an error that explains what went
|
# Otherwise we need to fail with an error that explains what went
|
||||||
# wrong.
|
# wrong.
|
||||||
raise AttributeError(
|
raise AttributeError(
|
||||||
(
|
f"{cls.__name__}.is_package() method is missing but is required"
|
||||||
"%s.is_package() method is missing but is required by Flask of "
|
" for PEP 302 import hooks."
|
||||||
"PEP 302 import hooks. If you do not use import hooks and "
|
|
||||||
"you encounter this error please file a bug against Flask."
|
|
||||||
)
|
|
||||||
% loader.__class__.__name__
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _find_package_path(root_mod_name):
|
def _find_package_path(root_mod_name):
|
||||||
"""Find the path where the module's root exists in"""
|
"""Find the path where the module's root exists in"""
|
||||||
if sys.version_info >= (3, 4):
|
import importlib.util
|
||||||
import importlib.util
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
spec = importlib.util.find_spec(root_mod_name)
|
spec = importlib.util.find_spec(root_mod_name)
|
||||||
if spec is None:
|
if spec is None:
|
||||||
raise ValueError("not found")
|
raise ValueError("not found")
|
||||||
# ImportError: the machinery told us it does not exist
|
# ImportError: the machinery told us it does not exist
|
||||||
# ValueError:
|
# ValueError:
|
||||||
# - the module name was invalid
|
# - the module name was invalid
|
||||||
# - the module name is __main__
|
# - the module name is __main__
|
||||||
# - *we* raised `ValueError` due to `spec` being `None`
|
# - *we* raised `ValueError` due to `spec` being `None`
|
||||||
except (ImportError, ValueError):
|
except (ImportError, ValueError):
|
||||||
pass # handled below
|
pass # handled below
|
||||||
|
else:
|
||||||
|
# namespace package
|
||||||
|
if spec.origin in {"namespace", None}:
|
||||||
|
return os.path.dirname(next(iter(spec.submodule_search_locations)))
|
||||||
|
# a package (with __init__.py)
|
||||||
|
elif spec.submodule_search_locations:
|
||||||
|
return os.path.dirname(os.path.dirname(spec.origin))
|
||||||
|
# just a normal module
|
||||||
else:
|
else:
|
||||||
# namespace package
|
return os.path.dirname(spec.origin)
|
||||||
if spec.origin in {"namespace", None}:
|
|
||||||
return os.path.dirname(next(iter(spec.submodule_search_locations)))
|
|
||||||
# a package (with __init__.py)
|
|
||||||
elif spec.submodule_search_locations:
|
|
||||||
return os.path.dirname(os.path.dirname(spec.origin))
|
|
||||||
# just a normal module
|
|
||||||
else:
|
|
||||||
return os.path.dirname(spec.origin)
|
|
||||||
|
|
||||||
# we were unable to find the `package_path` using PEP 451 loaders
|
# we were unable to find the `package_path` using PEP 451 loaders
|
||||||
loader = pkgutil.get_loader(root_mod_name)
|
loader = pkgutil.get_loader(root_mod_name)
|
||||||
|
|
@ -892,7 +869,6 @@ def _find_package_path(root_mod_name):
|
||||||
# import name is not found, or interactive/main module
|
# import name is not found, or interactive/main module
|
||||||
return os.getcwd()
|
return os.getcwd()
|
||||||
else:
|
else:
|
||||||
# For .egg, zipimporter does not have get_filename until Python 2.7.
|
|
||||||
if hasattr(loader, "get_filename"):
|
if hasattr(loader, "get_filename"):
|
||||||
filename = loader.get_filename(root_mod_name)
|
filename = loader.get_filename(root_mod_name)
|
||||||
elif hasattr(loader, "archive"):
|
elif hasattr(loader, "archive"):
|
||||||
|
|
@ -909,8 +885,8 @@ def _find_package_path(root_mod_name):
|
||||||
package_path = os.path.abspath(os.path.dirname(filename))
|
package_path = os.path.abspath(os.path.dirname(filename))
|
||||||
|
|
||||||
# In case the root module is a package we need to chop of the
|
# In case the root module is a package we need to chop of the
|
||||||
# rightmost part. This needs to go through a helper function
|
# rightmost part. This needs to go through a helper function
|
||||||
# because of python 3.3 namespace packages.
|
# because of namespace packages.
|
||||||
if _matching_loader_thinks_module_is_package(loader, root_mod_name):
|
if _matching_loader_thinks_module_is_package(loader, root_mod_name):
|
||||||
package_path = os.path.dirname(package_path)
|
package_path = os.path.dirname(package_path)
|
||||||
|
|
||||||
|
|
@ -945,7 +921,7 @@ def find_package(import_name):
|
||||||
return None, package_path
|
return None, package_path
|
||||||
|
|
||||||
|
|
||||||
class locked_cached_property(object):
|
class locked_cached_property:
|
||||||
"""A decorator that converts a function into a lazy property. The
|
"""A decorator that converts a function into a lazy property. The
|
||||||
function wrapped is called the first time to retrieve the result
|
function wrapped is called the first time to retrieve the result
|
||||||
and then that calculated result is used the next time you access
|
and then that calculated result is used the next time you access
|
||||||
|
|
@ -971,7 +947,7 @@ class locked_cached_property(object):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
class _PackageBoundObject(object):
|
class _PackageBoundObject:
|
||||||
#: The name of the package or module that this app belongs to. Do not
|
#: The name of the package or module that this app belongs to. Do not
|
||||||
#: change this once it is set by the constructor.
|
#: change this once it is set by the constructor.
|
||||||
import_name = None
|
import_name = None
|
||||||
|
|
@ -1028,7 +1004,7 @@ class _PackageBoundObject(object):
|
||||||
|
|
||||||
if self.static_folder is not None:
|
if self.static_folder is not None:
|
||||||
basename = os.path.basename(self.static_folder)
|
basename = os.path.basename(self.static_folder)
|
||||||
return ("/" + basename).rstrip("/")
|
return f"/{basename}".rstrip("/")
|
||||||
|
|
||||||
@static_url_path.setter
|
@static_url_path.setter
|
||||||
def static_url_path(self, value):
|
def static_url_path(self, value):
|
||||||
|
|
@ -1140,26 +1116,16 @@ def total_seconds(td):
|
||||||
def is_ip(value):
|
def is_ip(value):
|
||||||
"""Determine if the given string is an IP address.
|
"""Determine if the given string is an IP address.
|
||||||
|
|
||||||
Python 2 on Windows doesn't provide ``inet_pton``, so this only
|
|
||||||
checks IPv4 addresses in that environment.
|
|
||||||
|
|
||||||
:param value: value to check
|
:param value: value to check
|
||||||
:type value: str
|
:type value: str
|
||||||
|
|
||||||
:return: True if string is an IP address
|
:return: True if string is an IP address
|
||||||
:rtype: bool
|
:rtype: bool
|
||||||
"""
|
"""
|
||||||
if PY2 and os.name == "nt":
|
|
||||||
try:
|
|
||||||
socket.inet_aton(value)
|
|
||||||
return True
|
|
||||||
except socket.error:
|
|
||||||
return False
|
|
||||||
|
|
||||||
for family in (socket.AF_INET, socket.AF_INET6):
|
for family in (socket.AF_INET, socket.AF_INET6):
|
||||||
try:
|
try:
|
||||||
socket.inet_pton(family, value)
|
socket.inet_pton(family, value)
|
||||||
except socket.error:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
flask.json
|
flask.json
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|
@ -16,14 +15,13 @@ from itsdangerous import json as _json
|
||||||
from jinja2 import Markup
|
from jinja2 import Markup
|
||||||
from werkzeug.http import http_date
|
from werkzeug.http import http_date
|
||||||
|
|
||||||
from .._compat import PY2
|
|
||||||
from .._compat import text_type
|
|
||||||
from ..globals import current_app
|
from ..globals import current_app
|
||||||
from ..globals import request
|
from ..globals import request
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import dataclasses
|
import dataclasses
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
# Python < 3.7
|
||||||
dataclasses = None
|
dataclasses = None
|
||||||
|
|
||||||
# Figure out if simplejson escapes slashes. This behavior was changed
|
# Figure out if simplejson escapes slashes. This behavior was changed
|
||||||
|
|
@ -96,7 +94,7 @@ class JSONEncoder(_json.JSONEncoder):
|
||||||
if dataclasses and dataclasses.is_dataclass(o):
|
if dataclasses and dataclasses.is_dataclass(o):
|
||||||
return dataclasses.asdict(o)
|
return dataclasses.asdict(o)
|
||||||
if hasattr(o, "__html__"):
|
if hasattr(o, "__html__"):
|
||||||
return text_type(o.__html__())
|
return str(o.__html__())
|
||||||
return _json.JSONEncoder.default(self, o)
|
return _json.JSONEncoder.default(self, o)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -209,7 +207,7 @@ def dumps(obj, app=None, **kwargs):
|
||||||
_dump_arg_defaults(kwargs, app=app)
|
_dump_arg_defaults(kwargs, app=app)
|
||||||
encoding = kwargs.pop("encoding", None)
|
encoding = kwargs.pop("encoding", None)
|
||||||
rv = _json.dumps(obj, **kwargs)
|
rv = _json.dumps(obj, **kwargs)
|
||||||
if encoding is not None and isinstance(rv, text_type):
|
if encoding is not None and isinstance(rv, str):
|
||||||
rv = rv.encode(encoding)
|
rv = rv.encode(encoding)
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
|
@ -256,8 +254,7 @@ def loads(s, app=None, **kwargs):
|
||||||
def load(fp, app=None, **kwargs):
|
def load(fp, app=None, **kwargs):
|
||||||
"""Like :func:`loads` but reads from a file object."""
|
"""Like :func:`loads` but reads from a file object."""
|
||||||
_load_arg_defaults(kwargs, app=app)
|
_load_arg_defaults(kwargs, app=app)
|
||||||
if not PY2:
|
fp = _wrap_reader_for_text(fp, kwargs.pop("encoding", None) or "utf-8")
|
||||||
fp = _wrap_reader_for_text(fp, kwargs.pop("encoding", None) or "utf-8")
|
|
||||||
return _json.load(fp, **kwargs)
|
return _json.load(fp, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -288,10 +285,10 @@ def htmlsafe_dumps(obj, **kwargs):
|
||||||
"""
|
"""
|
||||||
rv = (
|
rv = (
|
||||||
dumps(obj, **kwargs)
|
dumps(obj, **kwargs)
|
||||||
.replace(u"<", u"\\u003c")
|
.replace("<", "\\u003c")
|
||||||
.replace(u">", u"\\u003e")
|
.replace(">", "\\u003e")
|
||||||
.replace(u"&", u"\\u0026")
|
.replace("&", "\\u0026")
|
||||||
.replace(u"'", u"\\u0027")
|
.replace("'", "\\u0027")
|
||||||
)
|
)
|
||||||
if not _slash_escape:
|
if not _slash_escape:
|
||||||
rv = rv.replace("\\/", "/")
|
rv = rv.replace("\\/", "/")
|
||||||
|
|
@ -300,7 +297,7 @@ def htmlsafe_dumps(obj, **kwargs):
|
||||||
|
|
||||||
def htmlsafe_dump(obj, fp, **kwargs):
|
def htmlsafe_dump(obj, fp, **kwargs):
|
||||||
"""Like :func:`htmlsafe_dumps` but writes into a file object."""
|
"""Like :func:`htmlsafe_dumps` but writes into a file object."""
|
||||||
fp.write(text_type(htmlsafe_dumps(obj, **kwargs)))
|
fp.write(str(htmlsafe_dumps(obj, **kwargs)))
|
||||||
|
|
||||||
|
|
||||||
def jsonify(*args, **kwargs):
|
def jsonify(*args, **kwargs):
|
||||||
|
|
@ -367,7 +364,7 @@ def jsonify(*args, **kwargs):
|
||||||
data = args or kwargs
|
data = args or kwargs
|
||||||
|
|
||||||
return current_app.response_class(
|
return current_app.response_class(
|
||||||
dumps(data, indent=indent, separators=separators) + "\n",
|
f"{dumps(data, indent=indent, separators=separators)}\n",
|
||||||
mimetype=current_app.config["JSONIFY_MIMETYPE"],
|
mimetype=current_app.config["JSONIFY_MIMETYPE"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
Tagged JSON
|
Tagged JSON
|
||||||
~~~~~~~~~~~
|
~~~~~~~~~~~
|
||||||
|
|
@ -50,13 +49,11 @@ from jinja2 import Markup
|
||||||
from werkzeug.http import http_date
|
from werkzeug.http import http_date
|
||||||
from werkzeug.http import parse_date
|
from werkzeug.http import parse_date
|
||||||
|
|
||||||
from .._compat import iteritems
|
|
||||||
from .._compat import text_type
|
|
||||||
from ..json import dumps
|
from ..json import dumps
|
||||||
from ..json import loads
|
from ..json import loads
|
||||||
|
|
||||||
|
|
||||||
class JSONTag(object):
|
class JSONTag:
|
||||||
"""Base class for defining type tags for :class:`TaggedJSONSerializer`."""
|
"""Base class for defining type tags for :class:`TaggedJSONSerializer`."""
|
||||||
|
|
||||||
__slots__ = ("serializer",)
|
__slots__ = ("serializer",)
|
||||||
|
|
@ -108,7 +105,7 @@ class TagDict(JSONTag):
|
||||||
|
|
||||||
def to_json(self, value):
|
def to_json(self, value):
|
||||||
key = next(iter(value))
|
key = next(iter(value))
|
||||||
return {key + "__": self.serializer.tag(value[key])}
|
return {f"{key}__": self.serializer.tag(value[key])}
|
||||||
|
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
key = next(iter(value))
|
key = next(iter(value))
|
||||||
|
|
@ -124,7 +121,7 @@ class PassDict(JSONTag):
|
||||||
def to_json(self, value):
|
def to_json(self, value):
|
||||||
# JSON objects may only have string keys, so don't bother tagging the
|
# JSON objects may only have string keys, so don't bother tagging the
|
||||||
# key here.
|
# key here.
|
||||||
return dict((k, self.serializer.tag(v)) for k, v in iteritems(value))
|
return {k: self.serializer.tag(v) for k, v in value.items()}
|
||||||
|
|
||||||
tag = to_json
|
tag = to_json
|
||||||
|
|
||||||
|
|
@ -181,7 +178,7 @@ class TagMarkup(JSONTag):
|
||||||
return callable(getattr(value, "__html__", None))
|
return callable(getattr(value, "__html__", None))
|
||||||
|
|
||||||
def to_json(self, value):
|
def to_json(self, value):
|
||||||
return text_type(value.__html__())
|
return str(value.__html__())
|
||||||
|
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
return Markup(value)
|
return Markup(value)
|
||||||
|
|
@ -215,7 +212,7 @@ class TagDateTime(JSONTag):
|
||||||
return parse_date(value)
|
return parse_date(value)
|
||||||
|
|
||||||
|
|
||||||
class TaggedJSONSerializer(object):
|
class TaggedJSONSerializer:
|
||||||
"""Serializer that uses a tag system to compactly represent objects that
|
"""Serializer that uses a tag system to compactly represent objects that
|
||||||
are not JSON types. Passed as the intermediate serializer to
|
are not JSON types. Passed as the intermediate serializer to
|
||||||
:class:`itsdangerous.Serializer`.
|
:class:`itsdangerous.Serializer`.
|
||||||
|
|
@ -271,7 +268,7 @@ class TaggedJSONSerializer(object):
|
||||||
|
|
||||||
if key is not None:
|
if key is not None:
|
||||||
if not force and key in self.tags:
|
if not force and key in self.tags:
|
||||||
raise KeyError("Tag '{0}' is already registered.".format(key))
|
raise KeyError(f"Tag '{key}' is already registered.")
|
||||||
|
|
||||||
self.tags[key] = tag
|
self.tags[key] = tag
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
flask.logging
|
flask.logging
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
@ -6,11 +5,8 @@ flask.logging
|
||||||
:copyright: 2010 Pallets
|
:copyright: 2010 Pallets
|
||||||
:license: BSD-3-Clause
|
:license: BSD-3-Clause
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
|
||||||
|
|
||||||
from werkzeug.local import LocalProxy
|
from werkzeug.local import LocalProxy
|
||||||
|
|
||||||
|
|
@ -57,20 +53,6 @@ default_handler.setFormatter(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _has_config(logger):
|
|
||||||
"""Decide if a logger has direct configuration applied by checking
|
|
||||||
its properties against the defaults.
|
|
||||||
|
|
||||||
:param logger: The :class:`~logging.Logger` to inspect.
|
|
||||||
"""
|
|
||||||
return (
|
|
||||||
logger.level != logging.NOTSET
|
|
||||||
or logger.handlers
|
|
||||||
or logger.filters
|
|
||||||
or not logger.propagate
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def create_logger(app):
|
def create_logger(app):
|
||||||
"""Get the Flask app's logger and configure it if needed.
|
"""Get the Flask app's logger and configure it if needed.
|
||||||
|
|
||||||
|
|
@ -86,20 +68,6 @@ def create_logger(app):
|
||||||
"""
|
"""
|
||||||
logger = logging.getLogger(app.name)
|
logger = logging.getLogger(app.name)
|
||||||
|
|
||||||
# 1.1.0 changes name of logger, warn if config is detected for old
|
|
||||||
# name and not new name
|
|
||||||
for old_name in ("flask.app", "flask"):
|
|
||||||
old_logger = logging.getLogger(old_name)
|
|
||||||
|
|
||||||
if _has_config(old_logger) and not _has_config(logger):
|
|
||||||
warnings.warn(
|
|
||||||
"'app.logger' is named '{name}' for this application,"
|
|
||||||
" but configuration was found for '{old_name}', which"
|
|
||||||
" no longer has an effect. The logging configuration"
|
|
||||||
" should be moved to '{name}'.".format(name=app.name, old_name=old_name)
|
|
||||||
)
|
|
||||||
break
|
|
||||||
|
|
||||||
if app.debug and not logger.level:
|
if app.debug and not logger.level:
|
||||||
logger.setLevel(logging.DEBUG)
|
logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
flask.sessions
|
flask.sessions
|
||||||
~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~
|
||||||
|
|
@ -10,19 +9,19 @@
|
||||||
"""
|
"""
|
||||||
import hashlib
|
import hashlib
|
||||||
import warnings
|
import warnings
|
||||||
|
from collections.abc import MutableMapping
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from itsdangerous import BadSignature
|
from itsdangerous import BadSignature
|
||||||
from itsdangerous import URLSafeTimedSerializer
|
from itsdangerous import URLSafeTimedSerializer
|
||||||
from werkzeug.datastructures import CallbackDict
|
from werkzeug.datastructures import CallbackDict
|
||||||
|
|
||||||
from ._compat import collections_abc
|
|
||||||
from .helpers import is_ip
|
from .helpers import is_ip
|
||||||
from .helpers import total_seconds
|
from .helpers import total_seconds
|
||||||
from .json.tag import TaggedJSONSerializer
|
from .json.tag import TaggedJSONSerializer
|
||||||
|
|
||||||
|
|
||||||
class SessionMixin(collections_abc.MutableMapping):
|
class SessionMixin(MutableMapping):
|
||||||
"""Expands a basic dictionary with session attributes."""
|
"""Expands a basic dictionary with session attributes."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -77,19 +76,19 @@ class SecureCookieSession(CallbackDict, SessionMixin):
|
||||||
self.modified = True
|
self.modified = True
|
||||||
self.accessed = True
|
self.accessed = True
|
||||||
|
|
||||||
super(SecureCookieSession, self).__init__(initial, on_update)
|
super().__init__(initial, on_update)
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
self.accessed = True
|
self.accessed = True
|
||||||
return super(SecureCookieSession, self).__getitem__(key)
|
return super().__getitem__(key)
|
||||||
|
|
||||||
def get(self, key, default=None):
|
def get(self, key, default=None):
|
||||||
self.accessed = True
|
self.accessed = True
|
||||||
return super(SecureCookieSession, self).get(key, default)
|
return super().get(key, default)
|
||||||
|
|
||||||
def setdefault(self, key, default=None):
|
def setdefault(self, key, default=None):
|
||||||
self.accessed = True
|
self.accessed = True
|
||||||
return super(SecureCookieSession, self).setdefault(key, default)
|
return super().setdefault(key, default)
|
||||||
|
|
||||||
|
|
||||||
class NullSession(SecureCookieSession):
|
class NullSession(SecureCookieSession):
|
||||||
|
|
@ -109,7 +108,7 @@ class NullSession(SecureCookieSession):
|
||||||
del _fail
|
del _fail
|
||||||
|
|
||||||
|
|
||||||
class SessionInterface(object):
|
class SessionInterface:
|
||||||
"""The basic interface you have to implement in order to replace the
|
"""The basic interface you have to implement in order to replace the
|
||||||
default session interface which uses werkzeug's securecookie
|
default session interface which uses werkzeug's securecookie
|
||||||
implementation. The only methods you have to implement are
|
implementation. The only methods you have to implement are
|
||||||
|
|
@ -209,13 +208,13 @@ class SessionInterface(object):
|
||||||
rv = rv.rsplit(":", 1)[0].lstrip(".")
|
rv = rv.rsplit(":", 1)[0].lstrip(".")
|
||||||
|
|
||||||
if "." not in rv:
|
if "." not in rv:
|
||||||
# Chrome doesn't allow names without a '.'
|
# Chrome doesn't allow names without a '.'. This should only
|
||||||
# this should only come up with localhost
|
# come up with localhost. Hack around this by not setting
|
||||||
# hack around this by not setting the name, and show a warning
|
# the name, and show a warning.
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
'"{rv}" is not a valid cookie domain, it must contain a ".".'
|
f"{rv!r} is not a valid cookie domain, it must contain"
|
||||||
" Add an entry to your hosts file, for example"
|
" a '.'. Add an entry to your hosts file, for example"
|
||||||
' "{rv}.localdomain", and use that instead.'.format(rv=rv)
|
f" '{rv}.localdomain', and use that instead."
|
||||||
)
|
)
|
||||||
app.config["SESSION_COOKIE_DOMAIN"] = False
|
app.config["SESSION_COOKIE_DOMAIN"] = False
|
||||||
return None
|
return None
|
||||||
|
|
@ -233,7 +232,7 @@ class SessionInterface(object):
|
||||||
# if this is not an ip and app is mounted at the root, allow subdomain
|
# if this is not an ip and app is mounted at the root, allow subdomain
|
||||||
# matching by adding a '.' prefix
|
# matching by adding a '.' prefix
|
||||||
if self.get_cookie_path(app) == "/" and not ip:
|
if self.get_cookie_path(app) == "/" and not ip:
|
||||||
rv = "." + rv
|
rv = f".{rv}"
|
||||||
|
|
||||||
app.config["SESSION_COOKIE_DOMAIN"] = rv
|
app.config["SESSION_COOKIE_DOMAIN"] = rv
|
||||||
return rv
|
return rv
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
flask.signals
|
flask.signals
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
@ -16,11 +15,11 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
signals_available = False
|
signals_available = False
|
||||||
|
|
||||||
class Namespace(object):
|
class Namespace:
|
||||||
def signal(self, name, doc=None):
|
def signal(self, name, doc=None):
|
||||||
return _FakeSignal(name, doc)
|
return _FakeSignal(name, doc)
|
||||||
|
|
||||||
class _FakeSignal(object):
|
class _FakeSignal:
|
||||||
"""If blinker is unavailable, create a fake class with the same
|
"""If blinker is unavailable, create a fake class with the same
|
||||||
interface that allows sending of signals but will fail with an
|
interface that allows sending of signals but will fail with an
|
||||||
error on anything else. Instead of doing anything on send, it
|
error on anything else. Instead of doing anything on send, it
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
flask.templating
|
flask.templating
|
||||||
~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
flask.testing
|
flask.testing
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
@ -9,7 +8,6 @@
|
||||||
:copyright: 2010 Pallets
|
:copyright: 2010 Pallets
|
||||||
:license: BSD-3-Clause
|
:license: BSD-3-Clause
|
||||||
"""
|
"""
|
||||||
import warnings
|
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
|
||||||
import werkzeug.test
|
import werkzeug.test
|
||||||
|
|
@ -52,7 +50,7 @@ class EnvironBuilder(werkzeug.test.EnvironBuilder):
|
||||||
subdomain=None,
|
subdomain=None,
|
||||||
url_scheme=None,
|
url_scheme=None,
|
||||||
*args,
|
*args,
|
||||||
**kwargs
|
**kwargs,
|
||||||
):
|
):
|
||||||
assert not (base_url or subdomain or url_scheme) or (
|
assert not (base_url or subdomain or url_scheme) or (
|
||||||
base_url is not None
|
base_url is not None
|
||||||
|
|
@ -65,16 +63,15 @@ class EnvironBuilder(werkzeug.test.EnvironBuilder):
|
||||||
app_root = app.config["APPLICATION_ROOT"]
|
app_root = app.config["APPLICATION_ROOT"]
|
||||||
|
|
||||||
if subdomain:
|
if subdomain:
|
||||||
http_host = "{0}.{1}".format(subdomain, http_host)
|
http_host = f"{subdomain}.{http_host}"
|
||||||
|
|
||||||
if url_scheme is None:
|
if url_scheme is None:
|
||||||
url_scheme = app.config["PREFERRED_URL_SCHEME"]
|
url_scheme = app.config["PREFERRED_URL_SCHEME"]
|
||||||
|
|
||||||
url = url_parse(path)
|
url = url_parse(path)
|
||||||
base_url = "{scheme}://{netloc}/{path}".format(
|
base_url = (
|
||||||
scheme=url.scheme or url_scheme,
|
f"{url.scheme or url_scheme}://{url.netloc or http_host}"
|
||||||
netloc=url.netloc or http_host,
|
f"/{app_root.lstrip('/')}"
|
||||||
path=app_root.lstrip("/"),
|
|
||||||
)
|
)
|
||||||
path = url.path
|
path = url.path
|
||||||
|
|
||||||
|
|
@ -83,7 +80,7 @@ class EnvironBuilder(werkzeug.test.EnvironBuilder):
|
||||||
path += sep + url.query
|
path += sep + url.query
|
||||||
|
|
||||||
self.app = app
|
self.app = app
|
||||||
super(EnvironBuilder, self).__init__(path, base_url, *args, **kwargs)
|
super().__init__(path, base_url, *args, **kwargs)
|
||||||
|
|
||||||
def json_dumps(self, obj, **kwargs):
|
def json_dumps(self, obj, **kwargs):
|
||||||
"""Serialize ``obj`` to a JSON-formatted string.
|
"""Serialize ``obj`` to a JSON-formatted string.
|
||||||
|
|
@ -95,23 +92,6 @@ class EnvironBuilder(werkzeug.test.EnvironBuilder):
|
||||||
return json_dumps(obj, **kwargs)
|
return json_dumps(obj, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def make_test_environ_builder(*args, **kwargs):
|
|
||||||
"""Create a :class:`flask.testing.EnvironBuilder`.
|
|
||||||
|
|
||||||
.. deprecated: 1.1
|
|
||||||
Will be removed in 2.0. Construct
|
|
||||||
``flask.testing.EnvironBuilder`` directly instead.
|
|
||||||
"""
|
|
||||||
warnings.warn(
|
|
||||||
DeprecationWarning(
|
|
||||||
'"make_test_environ_builder()" is deprecated and will be'
|
|
||||||
' removed in 2.0. Construct "flask.testing.EnvironBuilder"'
|
|
||||||
" directly instead."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return EnvironBuilder(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class FlaskClient(Client):
|
class FlaskClient(Client):
|
||||||
"""Works like a regular Werkzeug test client but has some knowledge about
|
"""Works like a regular Werkzeug test client but has some knowledge about
|
||||||
how Flask works to defer the cleanup of the request context stack to the
|
how Flask works to defer the cleanup of the request context stack to the
|
||||||
|
|
@ -130,10 +110,10 @@ class FlaskClient(Client):
|
||||||
preserve_context = False
|
preserve_context = False
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(FlaskClient, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.environ_base = {
|
self.environ_base = {
|
||||||
"REMOTE_ADDR": "127.0.0.1",
|
"REMOTE_ADDR": "127.0.0.1",
|
||||||
"HTTP_USER_AGENT": "werkzeug/" + werkzeug.__version__,
|
"HTTP_USER_AGENT": f"werkzeug/{werkzeug.__version__}",
|
||||||
}
|
}
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
|
|
@ -257,7 +237,7 @@ class FlaskCliRunner(CliRunner):
|
||||||
|
|
||||||
def __init__(self, app, **kwargs):
|
def __init__(self, app, **kwargs):
|
||||||
self.app = app
|
self.app = app
|
||||||
super(FlaskCliRunner, self).__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
def invoke(self, cli=None, args=None, **kwargs):
|
def invoke(self, cli=None, args=None, **kwargs):
|
||||||
"""Invokes a CLI command in an isolated environment. See
|
"""Invokes a CLI command in an isolated environment. See
|
||||||
|
|
@ -280,4 +260,4 @@ class FlaskCliRunner(CliRunner):
|
||||||
if "obj" not in kwargs:
|
if "obj" not in kwargs:
|
||||||
kwargs["obj"] = ScriptInfo(create_app=lambda: self.app)
|
kwargs["obj"] = ScriptInfo(create_app=lambda: self.app)
|
||||||
|
|
||||||
return super(FlaskCliRunner, self).invoke(cli, args, **kwargs)
|
return super().invoke(cli, args, **kwargs)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
flask.views
|
flask.views
|
||||||
~~~~~~~~~~~
|
~~~~~~~~~~~
|
||||||
|
|
@ -8,7 +7,6 @@
|
||||||
:copyright: 2010 Pallets
|
:copyright: 2010 Pallets
|
||||||
:license: BSD-3-Clause
|
:license: BSD-3-Clause
|
||||||
"""
|
"""
|
||||||
from ._compat import with_metaclass
|
|
||||||
from .globals import request
|
from .globals import request
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -17,7 +15,7 @@ http_method_funcs = frozenset(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class View(object):
|
class View:
|
||||||
"""Alternative way to use view functions. A subclass has to implement
|
"""Alternative way to use view functions. A subclass has to implement
|
||||||
:meth:`dispatch_request` which is called with the view arguments from
|
:meth:`dispatch_request` which is called with the view arguments from
|
||||||
the URL routing system. If :attr:`methods` is provided the methods
|
the URL routing system. If :attr:`methods` is provided the methods
|
||||||
|
|
@ -28,7 +26,7 @@ class View(object):
|
||||||
methods = ['GET']
|
methods = ['GET']
|
||||||
|
|
||||||
def dispatch_request(self, name):
|
def dispatch_request(self, name):
|
||||||
return 'Hello %s!' % name
|
return f"Hello {name}!"
|
||||||
|
|
||||||
app.add_url_rule('/hello/<name>', view_func=MyView.as_view('myview'))
|
app.add_url_rule('/hello/<name>', view_func=MyView.as_view('myview'))
|
||||||
|
|
||||||
|
|
@ -114,7 +112,7 @@ class MethodViewType(type):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(cls, name, bases, d):
|
def __init__(cls, name, bases, d):
|
||||||
super(MethodViewType, cls).__init__(name, bases, d)
|
super().__init__(name, bases, d)
|
||||||
|
|
||||||
if "methods" not in d:
|
if "methods" not in d:
|
||||||
methods = set()
|
methods = set()
|
||||||
|
|
@ -135,7 +133,7 @@ class MethodViewType(type):
|
||||||
cls.methods = methods
|
cls.methods = methods
|
||||||
|
|
||||||
|
|
||||||
class MethodView(with_metaclass(MethodViewType, View)):
|
class MethodView(View, metaclass=MethodViewType):
|
||||||
"""A class-based view that dispatches request methods to the corresponding
|
"""A class-based view that dispatches request methods to the corresponding
|
||||||
class methods. For example, if you implement a ``get`` method, it will be
|
class methods. For example, if you implement a ``get`` method, it will be
|
||||||
used to handle ``GET`` requests. ::
|
used to handle ``GET`` requests. ::
|
||||||
|
|
@ -159,5 +157,5 @@ class MethodView(with_metaclass(MethodViewType, View)):
|
||||||
if meth is None and request.method == "HEAD":
|
if meth is None and request.method == "HEAD":
|
||||||
meth = getattr(self, "get", None)
|
meth = getattr(self, "get", None)
|
||||||
|
|
||||||
assert meth is not None, "Unimplemented method %r" % request.method
|
assert meth is not None, f"Unimplemented method {request.method!r}"
|
||||||
return meth(*args, **kwargs)
|
return meth(*args, **kwargs)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
flask.wrappers
|
flask.wrappers
|
||||||
~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~
|
||||||
|
|
@ -22,7 +21,7 @@ class JSONMixin(_JSONMixin):
|
||||||
|
|
||||||
def on_json_loading_failed(self, e):
|
def on_json_loading_failed(self, e):
|
||||||
if current_app and current_app.debug:
|
if current_app and current_app.debug:
|
||||||
raise BadRequest("Failed to decode JSON object: {0}".format(e))
|
raise BadRequest(f"Failed to decode JSON object: {e}")
|
||||||
|
|
||||||
raise BadRequest()
|
raise BadRequest()
|
||||||
|
|
||||||
|
|
@ -134,4 +133,4 @@ class Response(ResponseBase, JSONMixin):
|
||||||
return current_app.config["MAX_COOKIE_SIZE"]
|
return current_app.config["MAX_COOKIE_SIZE"]
|
||||||
|
|
||||||
# return Werkzeug's default when not in an app context
|
# return Werkzeug's default when not in an app context
|
||||||
return super(Response, self).max_cookie_size
|
return super().max_cookie_size
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
tests.conftest
|
tests.conftest
|
||||||
~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~
|
||||||
|
|
@ -112,14 +111,13 @@ def limit_loader(request, monkeypatch):
|
||||||
if not request.param:
|
if not request.param:
|
||||||
return
|
return
|
||||||
|
|
||||||
class LimitedLoader(object):
|
class LimitedLoader:
|
||||||
def __init__(self, loader):
|
def __init__(self, loader):
|
||||||
self.loader = loader
|
self.loader = loader
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
if name in ("archive", "get_filename"):
|
if name in {"archive", "get_filename"}:
|
||||||
msg = "Mocking a loader which does not have `%s.`" % name
|
raise AttributeError(f"Mocking a loader which does not have {name!r}.")
|
||||||
raise AttributeError(msg)
|
|
||||||
return getattr(self.loader, name)
|
return getattr(self.loader, name)
|
||||||
|
|
||||||
old_get_loader = pkgutil.get_loader
|
old_get_loader = pkgutil.get_loader
|
||||||
|
|
@ -149,7 +147,7 @@ def site_packages(modules_tmpdir, monkeypatch):
|
||||||
"""Create a fake site-packages."""
|
"""Create a fake site-packages."""
|
||||||
rv = (
|
rv = (
|
||||||
modules_tmpdir.mkdir("lib")
|
modules_tmpdir.mkdir("lib")
|
||||||
.mkdir("python{x[0]}.{x[1]}".format(x=sys.version_info))
|
.mkdir(f"python{sys.version_info.major}.{sys.version_info.minor}")
|
||||||
.mkdir("site-packages")
|
.mkdir("site-packages")
|
||||||
)
|
)
|
||||||
monkeypatch.syspath_prepend(str(rv))
|
monkeypatch.syspath_prepend(str(rv))
|
||||||
|
|
@ -162,23 +160,21 @@ def install_egg(modules_tmpdir, monkeypatch):
|
||||||
sys.path."""
|
sys.path."""
|
||||||
|
|
||||||
def inner(name, base=modules_tmpdir):
|
def inner(name, base=modules_tmpdir):
|
||||||
if not isinstance(name, str):
|
|
||||||
raise ValueError(name)
|
|
||||||
base.join(name).ensure_dir()
|
base.join(name).ensure_dir()
|
||||||
base.join(name).join("__init__.py").ensure()
|
base.join(name).join("__init__.py").ensure()
|
||||||
|
|
||||||
egg_setup = base.join("setup.py")
|
egg_setup = base.join("setup.py")
|
||||||
egg_setup.write(
|
egg_setup.write(
|
||||||
textwrap.dedent(
|
textwrap.dedent(
|
||||||
"""
|
f"""
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
setup(name='{0}',
|
setup(
|
||||||
version='1.0',
|
name="{name}",
|
||||||
packages=['site_egg'],
|
version="1.0",
|
||||||
zip_safe=True)
|
packages=["site_egg"],
|
||||||
""".format(
|
zip_safe=True,
|
||||||
name
|
|
||||||
)
|
)
|
||||||
|
"""
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -187,7 +183,7 @@ def install_egg(modules_tmpdir, monkeypatch):
|
||||||
subprocess.check_call(
|
subprocess.check_call(
|
||||||
[sys.executable, "setup.py", "bdist_egg"], cwd=str(modules_tmpdir)
|
[sys.executable, "setup.py", "bdist_egg"], cwd=str(modules_tmpdir)
|
||||||
)
|
)
|
||||||
egg_path, = modules_tmpdir.join("dist/").listdir()
|
(egg_path,) = modules_tmpdir.join("dist/").listdir()
|
||||||
monkeypatch.syspath_prepend(str(egg_path))
|
monkeypatch.syspath_prepend(str(egg_path))
|
||||||
return egg_path
|
return egg_path
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
tests.appctx
|
tests.appctx
|
||||||
~~~~~~~~~~~~
|
~~~~~~~~~~~~
|
||||||
|
|
@ -163,7 +162,7 @@ def test_app_ctx_globals_methods(app, app_ctx):
|
||||||
|
|
||||||
|
|
||||||
def test_custom_app_ctx_globals_class(app):
|
def test_custom_app_ctx_globals_class(app):
|
||||||
class CustomRequestGlobals(object):
|
class CustomRequestGlobals:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.spam = "eggs"
|
self.spam = "eggs"
|
||||||
|
|
||||||
|
|
@ -190,7 +189,7 @@ def test_context_refcounts(app, client):
|
||||||
pass
|
pass
|
||||||
env = flask._request_ctx_stack.top.request.environ
|
env = flask._request_ctx_stack.top.request.environ
|
||||||
assert env["werkzeug.request"] is not None
|
assert env["werkzeug.request"] is not None
|
||||||
return u""
|
return ""
|
||||||
|
|
||||||
res = client.get("/")
|
res = client.get("/")
|
||||||
assert res.status_code == 200
|
assert res.status_code == 200
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
|
|
||||||
testapp = Flask("testapp")
|
testapp = Flask("testapp")
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
|
|
||||||
raise ImportError()
|
raise ImportError()
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
|
|
||||||
app1 = Flask("app1")
|
app1 = Flask("app1")
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
tests.basic
|
tests.basic
|
||||||
~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
@ -24,7 +23,6 @@ from werkzeug.http import parse_date
|
||||||
from werkzeug.routing import BuildError
|
from werkzeug.routing import BuildError
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask._compat import text_type
|
|
||||||
|
|
||||||
|
|
||||||
def test_options_work(app, client):
|
def test_options_work(app, client):
|
||||||
|
|
@ -92,12 +90,7 @@ def test_provide_automatic_options_kwarg(app, client):
|
||||||
assert rv.status_code == 405
|
assert rv.status_code == 405
|
||||||
assert sorted(rv.allow) == ["GET", "HEAD"]
|
assert sorted(rv.allow) == ["GET", "HEAD"]
|
||||||
|
|
||||||
# Older versions of Werkzeug.test.Client don't have an options method
|
rv = client.open("/", method="OPTIONS")
|
||||||
if hasattr(client, "options"):
|
|
||||||
rv = client.options("/")
|
|
||||||
else:
|
|
||||||
rv = client.open("/", method="OPTIONS")
|
|
||||||
|
|
||||||
assert rv.status_code == 405
|
assert rv.status_code == 405
|
||||||
|
|
||||||
rv = client.head("/")
|
rv = client.head("/")
|
||||||
|
|
@ -110,11 +103,7 @@ def test_provide_automatic_options_kwarg(app, client):
|
||||||
assert rv.status_code == 405
|
assert rv.status_code == 405
|
||||||
assert sorted(rv.allow) == ["GET", "HEAD", "POST"]
|
assert sorted(rv.allow) == ["GET", "HEAD", "POST"]
|
||||||
|
|
||||||
if hasattr(client, "options"):
|
rv = client.open("/more", method="OPTIONS")
|
||||||
rv = client.options("/more")
|
|
||||||
else:
|
|
||||||
rv = client.open("/more", method="OPTIONS")
|
|
||||||
|
|
||||||
assert rv.status_code == 405
|
assert rv.status_code == 405
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -288,7 +277,7 @@ def test_session_using_server_name_port_and_path(app, client):
|
||||||
|
|
||||||
|
|
||||||
def test_session_using_application_root(app, client):
|
def test_session_using_application_root(app, client):
|
||||||
class PrefixPathMiddleware(object):
|
class PrefixPathMiddleware:
|
||||||
def __init__(self, app, prefix):
|
def __init__(self, app, prefix):
|
||||||
self.app = app
|
self.app = app
|
||||||
self.prefix = prefix
|
self.prefix = prefix
|
||||||
|
|
@ -372,7 +361,7 @@ def test_session_localhost_warning(recwarn, app, client):
|
||||||
rv = client.get("/", "http://localhost:5000/")
|
rv = client.get("/", "http://localhost:5000/")
|
||||||
assert "domain" not in rv.headers["set-cookie"].lower()
|
assert "domain" not in rv.headers["set-cookie"].lower()
|
||||||
w = recwarn.pop(UserWarning)
|
w = recwarn.pop(UserWarning)
|
||||||
assert '"localhost" is not a valid cookie domain' in str(w.message)
|
assert "'localhost' is not a valid cookie domain" in str(w.message)
|
||||||
|
|
||||||
|
|
||||||
def test_session_ip_warning(recwarn, app, client):
|
def test_session_ip_warning(recwarn, app, client):
|
||||||
|
|
@ -413,7 +402,7 @@ def test_session_expiration(app, client):
|
||||||
|
|
||||||
@app.route("/test")
|
@app.route("/test")
|
||||||
def test():
|
def test():
|
||||||
return text_type(flask.session.permanent)
|
return str(flask.session.permanent)
|
||||||
|
|
||||||
rv = client.get("/")
|
rv = client.get("/")
|
||||||
assert "set-cookie" in rv.headers
|
assert "set-cookie" in rv.headers
|
||||||
|
|
@ -593,18 +582,18 @@ def test_extended_flashing(app):
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def index():
|
def index():
|
||||||
flask.flash(u"Hello World")
|
flask.flash("Hello World")
|
||||||
flask.flash(u"Hello World", "error")
|
flask.flash("Hello World", "error")
|
||||||
flask.flash(flask.Markup(u"<em>Testing</em>"), "warning")
|
flask.flash(flask.Markup("<em>Testing</em>"), "warning")
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@app.route("/test/")
|
@app.route("/test/")
|
||||||
def test():
|
def test():
|
||||||
messages = flask.get_flashed_messages()
|
messages = flask.get_flashed_messages()
|
||||||
assert list(messages) == [
|
assert list(messages) == [
|
||||||
u"Hello World",
|
"Hello World",
|
||||||
u"Hello World",
|
"Hello World",
|
||||||
flask.Markup(u"<em>Testing</em>"),
|
flask.Markup("<em>Testing</em>"),
|
||||||
]
|
]
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
@ -613,9 +602,9 @@ def test_extended_flashing(app):
|
||||||
messages = flask.get_flashed_messages(with_categories=True)
|
messages = flask.get_flashed_messages(with_categories=True)
|
||||||
assert len(messages) == 3
|
assert len(messages) == 3
|
||||||
assert list(messages) == [
|
assert list(messages) == [
|
||||||
("message", u"Hello World"),
|
("message", "Hello World"),
|
||||||
("error", u"Hello World"),
|
("error", "Hello World"),
|
||||||
("warning", flask.Markup(u"<em>Testing</em>")),
|
("warning", flask.Markup("<em>Testing</em>")),
|
||||||
]
|
]
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
@ -624,7 +613,7 @@ def test_extended_flashing(app):
|
||||||
messages = flask.get_flashed_messages(
|
messages = flask.get_flashed_messages(
|
||||||
category_filter=["message"], with_categories=True
|
category_filter=["message"], with_categories=True
|
||||||
)
|
)
|
||||||
assert list(messages) == [("message", u"Hello World")]
|
assert list(messages) == [("message", "Hello World")]
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@app.route("/test_filters/")
|
@app.route("/test_filters/")
|
||||||
|
|
@ -633,8 +622,8 @@ def test_extended_flashing(app):
|
||||||
category_filter=["message", "warning"], with_categories=True
|
category_filter=["message", "warning"], with_categories=True
|
||||||
)
|
)
|
||||||
assert list(messages) == [
|
assert list(messages) == [
|
||||||
("message", u"Hello World"),
|
("message", "Hello World"),
|
||||||
("warning", flask.Markup(u"<em>Testing</em>")),
|
("warning", flask.Markup("<em>Testing</em>")),
|
||||||
]
|
]
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
@ -642,8 +631,8 @@ def test_extended_flashing(app):
|
||||||
def test_filters2():
|
def test_filters2():
|
||||||
messages = flask.get_flashed_messages(category_filter=["message", "warning"])
|
messages = flask.get_flashed_messages(category_filter=["message", "warning"])
|
||||||
assert len(messages) == 2
|
assert len(messages) == 2
|
||||||
assert messages[0] == u"Hello World"
|
assert messages[0] == "Hello World"
|
||||||
assert messages[1] == flask.Markup(u"<em>Testing</em>")
|
assert messages[1] == flask.Markup("<em>Testing</em>")
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
# Create new test client on each test to clean flashed messages.
|
# Create new test client on each test to clean flashed messages.
|
||||||
|
|
@ -1106,17 +1095,17 @@ def test_enctype_debug_helper(app, client):
|
||||||
with pytest.raises(DebugFilesKeyError) as e:
|
with pytest.raises(DebugFilesKeyError) as e:
|
||||||
client.post("/fail", data={"foo": "index.txt"})
|
client.post("/fail", data={"foo": "index.txt"})
|
||||||
assert "no file contents were transmitted" in str(e.value)
|
assert "no file contents were transmitted" in str(e.value)
|
||||||
assert 'This was submitted: "index.txt"' in str(e.value)
|
assert "This was submitted: 'index.txt'" in str(e.value)
|
||||||
|
|
||||||
|
|
||||||
def test_response_types(app, client):
|
def test_response_types(app, client):
|
||||||
@app.route("/text")
|
@app.route("/text")
|
||||||
def from_text():
|
def from_text():
|
||||||
return u"Hällo Wörld"
|
return "Hällo Wörld"
|
||||||
|
|
||||||
@app.route("/bytes")
|
@app.route("/bytes")
|
||||||
def from_bytes():
|
def from_bytes():
|
||||||
return u"Hällo Wörld".encode("utf-8")
|
return "Hällo Wörld".encode()
|
||||||
|
|
||||||
@app.route("/full_tuple")
|
@app.route("/full_tuple")
|
||||||
def from_full_tuple():
|
def from_full_tuple():
|
||||||
|
|
@ -1153,8 +1142,8 @@ def test_response_types(app, client):
|
||||||
def from_dict():
|
def from_dict():
|
||||||
return {"foo": "bar"}, 201
|
return {"foo": "bar"}, 201
|
||||||
|
|
||||||
assert client.get("/text").data == u"Hällo Wörld".encode("utf-8")
|
assert client.get("/text").data == "Hällo Wörld".encode()
|
||||||
assert client.get("/bytes").data == u"Hällo Wörld".encode("utf-8")
|
assert client.get("/bytes").data == "Hällo Wörld".encode()
|
||||||
|
|
||||||
rv = client.get("/full_tuple")
|
rv = client.get("/full_tuple")
|
||||||
assert rv.data == b"Meh"
|
assert rv.data == b"Meh"
|
||||||
|
|
@ -1621,11 +1610,11 @@ def test_inject_blueprint_url_defaults(app):
|
||||||
|
|
||||||
|
|
||||||
def test_nonascii_pathinfo(app, client):
|
def test_nonascii_pathinfo(app, client):
|
||||||
@app.route(u"/киртест")
|
@app.route("/киртест")
|
||||||
def index():
|
def index():
|
||||||
return "Hello World!"
|
return "Hello World!"
|
||||||
|
|
||||||
rv = client.get(u"/киртест")
|
rv = client.get("/киртест")
|
||||||
assert rv.data == b"Hello World!"
|
assert rv.data == b"Hello World!"
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1832,7 +1821,7 @@ def test_subdomain_matching():
|
||||||
|
|
||||||
@app.route("/", subdomain="<user>")
|
@app.route("/", subdomain="<user>")
|
||||||
def index(user):
|
def index(user):
|
||||||
return "index for %s" % user
|
return f"index for {user}"
|
||||||
|
|
||||||
rv = client.get("/", "http://mitsuhiko.localhost.localdomain/")
|
rv = client.get("/", "http://mitsuhiko.localhost.localdomain/")
|
||||||
assert rv.data == b"index for mitsuhiko"
|
assert rv.data == b"index for mitsuhiko"
|
||||||
|
|
@ -1845,7 +1834,7 @@ def test_subdomain_matching_with_ports():
|
||||||
|
|
||||||
@app.route("/", subdomain="<user>")
|
@app.route("/", subdomain="<user>")
|
||||||
def index(user):
|
def index(user):
|
||||||
return "index for %s" % user
|
return f"index for {user}"
|
||||||
|
|
||||||
rv = client.get("/", "http://mitsuhiko.localhost.localdomain:3000/")
|
rv = client.get("/", "http://mitsuhiko.localhost.localdomain:3000/")
|
||||||
assert rv.data == b"index for mitsuhiko"
|
assert rv.data == b"index for mitsuhiko"
|
||||||
|
|
@ -1885,7 +1874,7 @@ def test_multi_route_rules(app, client):
|
||||||
|
|
||||||
|
|
||||||
def test_multi_route_class_views(app, client):
|
def test_multi_route_class_views(app, client):
|
||||||
class View(object):
|
class View:
|
||||||
def __init__(self, app):
|
def __init__(self, app):
|
||||||
app.add_url_rule("/", "index", self.index)
|
app.add_url_rule("/", "index", self.index)
|
||||||
app.add_url_rule("/<test>/", "index", self.index)
|
app.add_url_rule("/<test>/", "index", self.index)
|
||||||
|
|
@ -1917,12 +1906,12 @@ def test_run_server_port(monkeypatch, app):
|
||||||
|
|
||||||
# Mocks werkzeug.serving.run_simple method
|
# Mocks werkzeug.serving.run_simple method
|
||||||
def run_simple_mock(hostname, port, application, *args, **kwargs):
|
def run_simple_mock(hostname, port, application, *args, **kwargs):
|
||||||
rv["result"] = "running on %s:%s ..." % (hostname, port)
|
rv["result"] = f"running on {hostname}:{port} ..."
|
||||||
|
|
||||||
monkeypatch.setattr(werkzeug.serving, "run_simple", run_simple_mock)
|
monkeypatch.setattr(werkzeug.serving, "run_simple", run_simple_mock)
|
||||||
hostname, port = "localhost", 8000
|
hostname, port = "localhost", 8000
|
||||||
app.run(hostname, port, debug=True)
|
app.run(hostname, port, debug=True)
|
||||||
assert rv["result"] == "running on %s:%s ..." % (hostname, port)
|
assert rv["result"] == f"running on {hostname}:{port} ..."
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
tests.blueprints
|
tests.blueprints
|
||||||
~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
@ -15,7 +14,6 @@ from jinja2 import TemplateNotFound
|
||||||
from werkzeug.http import parse_cache_control_header
|
from werkzeug.http import parse_cache_control_header
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask._compat import text_type
|
|
||||||
|
|
||||||
|
|
||||||
def test_blueprint_specific_error_handling(app, client):
|
def test_blueprint_specific_error_handling(app, client):
|
||||||
|
|
@ -146,11 +144,11 @@ def test_blueprint_url_defaults(app, client):
|
||||||
|
|
||||||
@bp.route("/foo", defaults={"baz": 42})
|
@bp.route("/foo", defaults={"baz": 42})
|
||||||
def foo(bar, baz):
|
def foo(bar, baz):
|
||||||
return "%s/%d" % (bar, baz)
|
return f"{bar}/{baz:d}"
|
||||||
|
|
||||||
@bp.route("/bar")
|
@bp.route("/bar")
|
||||||
def bar(bar):
|
def bar(bar):
|
||||||
return text_type(bar)
|
return str(bar)
|
||||||
|
|
||||||
app.register_blueprint(bp, url_prefix="/1", url_defaults={"bar": 23})
|
app.register_blueprint(bp, url_prefix="/1", url_defaults={"bar": 23})
|
||||||
app.register_blueprint(bp, url_prefix="/2", url_defaults={"bar": 19})
|
app.register_blueprint(bp, url_prefix="/2", url_defaults={"bar": 19})
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
tests.test_cli
|
tests.test_cli
|
||||||
~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~
|
||||||
|
|
@ -8,8 +7,6 @@
|
||||||
"""
|
"""
|
||||||
# This file was part of Flask-CLI and was modified under the terms of
|
# This file was part of Flask-CLI and was modified under the terms of
|
||||||
# its Revised BSD License. Copyright © 2015 CERN.
|
# its Revised BSD License. Copyright © 2015 CERN.
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import ssl
|
import ssl
|
||||||
import sys
|
import sys
|
||||||
|
|
@ -261,7 +258,7 @@ def test_get_version(test_apps, capsys):
|
||||||
from werkzeug import __version__ as werkzeug_version
|
from werkzeug import __version__ as werkzeug_version
|
||||||
from platform import python_version
|
from platform import python_version
|
||||||
|
|
||||||
class MockCtx(object):
|
class MockCtx:
|
||||||
resilient_parsing = False
|
resilient_parsing = False
|
||||||
color = None
|
color = None
|
||||||
|
|
||||||
|
|
@ -271,9 +268,9 @@ def test_get_version(test_apps, capsys):
|
||||||
ctx = MockCtx()
|
ctx = MockCtx()
|
||||||
get_version(ctx, None, "test")
|
get_version(ctx, None, "test")
|
||||||
out, err = capsys.readouterr()
|
out, err = capsys.readouterr()
|
||||||
assert "Python " + python_version() in out
|
assert f"Python {python_version()}" in out
|
||||||
assert "Flask " + flask_version in out
|
assert f"Flask {flask_version}" in out
|
||||||
assert "Werkzeug " + werkzeug_version in out
|
assert f"Werkzeug {werkzeug_version}" in out
|
||||||
|
|
||||||
|
|
||||||
def test_scriptinfo(test_apps, monkeypatch):
|
def test_scriptinfo(test_apps, monkeypatch):
|
||||||
|
|
@ -291,7 +288,7 @@ def test_scriptinfo(test_apps, monkeypatch):
|
||||||
app = obj.load_app()
|
app = obj.load_app()
|
||||||
assert app.name == "testapp"
|
assert app.name == "testapp"
|
||||||
assert obj.load_app() is app
|
assert obj.load_app() is app
|
||||||
obj = ScriptInfo(app_import_path=cli_app_path + ":testapp")
|
obj = ScriptInfo(app_import_path=f"{cli_app_path}:testapp")
|
||||||
app = obj.load_app()
|
app = obj.load_app()
|
||||||
assert app.name == "testapp"
|
assert app.name == "testapp"
|
||||||
assert obj.load_app() is app
|
assert obj.load_app() is app
|
||||||
|
|
@ -409,7 +406,7 @@ def test_flaskgroup_debug(runner, set_debug_flag):
|
||||||
|
|
||||||
result = runner.invoke(cli, ["test"])
|
result = runner.invoke(cli, ["test"])
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert result.output == "%s\n" % str(not set_debug_flag)
|
assert result.output == f"{not set_debug_flag}\n"
|
||||||
|
|
||||||
|
|
||||||
def test_print_exceptions(runner):
|
def test_print_exceptions(runner):
|
||||||
|
|
@ -589,16 +586,11 @@ def test_run_cert_import(monkeypatch):
|
||||||
with pytest.raises(click.BadParameter):
|
with pytest.raises(click.BadParameter):
|
||||||
run_command.make_context("run", ["--cert", "not_here"])
|
run_command.make_context("run", ["--cert", "not_here"])
|
||||||
|
|
||||||
# not an SSLContext
|
with pytest.raises(click.BadParameter):
|
||||||
if sys.version_info >= (2, 7, 9):
|
run_command.make_context("run", ["--cert", "flask"])
|
||||||
with pytest.raises(click.BadParameter):
|
|
||||||
run_command.make_context("run", ["--cert", "flask"])
|
|
||||||
|
|
||||||
# SSLContext
|
# SSLContext
|
||||||
if sys.version_info < (2, 7, 9):
|
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||||
ssl_context = object()
|
|
||||||
else:
|
|
||||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
|
|
||||||
|
|
||||||
monkeypatch.setitem(sys.modules, "ssl_context", ssl_context)
|
monkeypatch.setitem(sys.modules, "ssl_context", ssl_context)
|
||||||
ctx = run_command.make_context("run", ["--cert", "ssl_context"])
|
ctx = run_command.make_context("run", ["--cert", "ssl_context"])
|
||||||
|
|
@ -664,4 +656,4 @@ def test_cli_empty(app):
|
||||||
app.register_blueprint(bp)
|
app.register_blueprint(bp)
|
||||||
|
|
||||||
result = app.test_cli_runner().invoke(args=["blue", "--help"])
|
result = app.test_cli_runner().invoke(args=["blue", "--help"])
|
||||||
assert result.exit_code == 2, "Unexpected success:\n\n" + result.output
|
assert result.exit_code == 2, f"Unexpected success:\n\n{result.output}"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
tests.test_config
|
tests.test_config
|
||||||
~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
@ -14,7 +13,6 @@ from datetime import timedelta
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask._compat import PY2
|
|
||||||
|
|
||||||
|
|
||||||
# config keys used for the TestConfig
|
# config keys used for the TestConfig
|
||||||
|
|
@ -30,7 +28,7 @@ def common_object_test(app):
|
||||||
|
|
||||||
def test_config_from_pyfile():
|
def test_config_from_pyfile():
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
app.config.from_pyfile(__file__.rsplit(".", 1)[0] + ".py")
|
app.config.from_pyfile(f"{__file__.rsplit('.', 1)[0]}.py")
|
||||||
common_object_test(app)
|
common_object_test(app)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -66,7 +64,7 @@ def test_config_from_mapping():
|
||||||
|
|
||||||
|
|
||||||
def test_config_from_class():
|
def test_config_from_class():
|
||||||
class Base(object):
|
class Base:
|
||||||
TEST_KEY = "foo"
|
TEST_KEY = "foo"
|
||||||
|
|
||||||
class Test(Base):
|
class Test(Base):
|
||||||
|
|
@ -86,7 +84,7 @@ def test_config_from_envvar(monkeypatch):
|
||||||
assert not app.config.from_envvar("FOO_SETTINGS", silent=True)
|
assert not app.config.from_envvar("FOO_SETTINGS", silent=True)
|
||||||
|
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"os.environ", {"FOO_SETTINGS": __file__.rsplit(".", 1)[0] + ".py"}
|
"os.environ", {"FOO_SETTINGS": f"{__file__.rsplit('.', 1)[0]}.py"}
|
||||||
)
|
)
|
||||||
assert app.config.from_envvar("FOO_SETTINGS")
|
assert app.config.from_envvar("FOO_SETTINGS")
|
||||||
common_object_test(app)
|
common_object_test(app)
|
||||||
|
|
@ -187,17 +185,13 @@ def test_from_pyfile_weird_encoding(tmpdir, encoding):
|
||||||
f = tmpdir.join("my_config.py")
|
f = tmpdir.join("my_config.py")
|
||||||
f.write_binary(
|
f.write_binary(
|
||||||
textwrap.dedent(
|
textwrap.dedent(
|
||||||
u"""
|
f"""
|
||||||
# -*- coding: {0} -*-
|
# -*- coding: {encoding} -*-
|
||||||
TEST_VALUE = "föö"
|
TEST_VALUE = "föö"
|
||||||
""".format(
|
"""
|
||||||
encoding
|
|
||||||
)
|
|
||||||
).encode(encoding)
|
).encode(encoding)
|
||||||
)
|
)
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
app.config.from_pyfile(str(f))
|
app.config.from_pyfile(str(f))
|
||||||
value = app.config["TEST_VALUE"]
|
value = app.config["TEST_VALUE"]
|
||||||
if PY2:
|
assert value == "föö"
|
||||||
value = value.decode(encoding)
|
|
||||||
assert value == u"föö"
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ def test_custom_converters(app, client):
|
||||||
return value.split(",")
|
return value.split(",")
|
||||||
|
|
||||||
def to_url(self, value):
|
def to_url(self, value):
|
||||||
base_to_url = super(ListConverter, self).to_url
|
base_to_url = super().to_url
|
||||||
return ",".join(base_to_url(x) for x in value)
|
return ",".join(base_to_url(x) for x in value)
|
||||||
|
|
||||||
app.url_map.converters["list"] = ListConverter
|
app.url_map.converters["list"] = ListConverter
|
||||||
|
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
import pytest
|
|
||||||
|
|
||||||
from flask import json_available
|
|
||||||
|
|
||||||
|
|
||||||
def test_json_available():
|
|
||||||
with pytest.deprecated_call() as rec:
|
|
||||||
assert json_available
|
|
||||||
assert json_available == True # noqa E712
|
|
||||||
assert json_available != False # noqa E712
|
|
||||||
|
|
||||||
assert len(rec.list) == 3
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
tests.helpers
|
tests.helpers
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
@ -24,9 +23,6 @@ from werkzeug.http import parse_options_header
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import json
|
from flask import json
|
||||||
from flask._compat import PY2
|
|
||||||
from flask._compat import StringIO
|
|
||||||
from flask._compat import text_type
|
|
||||||
from flask.helpers import get_debug_flag
|
from flask.helpers import get_debug_flag
|
||||||
from flask.helpers import get_env
|
from flask.helpers import get_env
|
||||||
|
|
||||||
|
|
@ -41,7 +37,7 @@ def has_encoding(name):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class FakePath(object):
|
class FakePath:
|
||||||
"""Fake object to represent a ``PathLike object``.
|
"""Fake object to represent a ``PathLike object``.
|
||||||
|
|
||||||
This represents a ``pathlib.Path`` object in python 3.
|
This represents a ``pathlib.Path`` object in python 3.
|
||||||
|
|
@ -76,9 +72,9 @@ class FixedOffset(datetime.tzinfo):
|
||||||
return datetime.timedelta()
|
return datetime.timedelta()
|
||||||
|
|
||||||
|
|
||||||
class TestJSON(object):
|
class TestJSON:
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"value", (1, "t", True, False, None, [], [1, 2, 3], {}, {"foo": u"🐍"})
|
"value", (1, "t", True, False, None, [], [1, 2, 3], {}, {"foo": "🐍"})
|
||||||
)
|
)
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"encoding",
|
"encoding",
|
||||||
|
|
@ -95,7 +91,6 @@ class TestJSON(object):
|
||||||
)
|
)
|
||||||
def test_detect_encoding(self, value, encoding):
|
def test_detect_encoding(self, value, encoding):
|
||||||
data = json.dumps(value).encode(encoding)
|
data = json.dumps(value).encode(encoding)
|
||||||
assert json.detect_encoding(data) == encoding
|
|
||||||
assert json.loads(data) == value
|
assert json.loads(data) == value
|
||||||
|
|
||||||
@pytest.mark.parametrize("debug", (True, False))
|
@pytest.mark.parametrize("debug", (True, False))
|
||||||
|
|
@ -116,7 +111,7 @@ class TestJSON(object):
|
||||||
def test_json_bad_requests(self, app, client):
|
def test_json_bad_requests(self, app, client):
|
||||||
@app.route("/json", methods=["POST"])
|
@app.route("/json", methods=["POST"])
|
||||||
def return_json():
|
def return_json():
|
||||||
return flask.jsonify(foo=text_type(flask.request.get_json()))
|
return flask.jsonify(foo=str(flask.request.get_json()))
|
||||||
|
|
||||||
rv = client.post("/json", data="malformed", content_type="application/json")
|
rv = client.post("/json", data="malformed", content_type="application/json")
|
||||||
assert rv.status_code == 400
|
assert rv.status_code == 400
|
||||||
|
|
@ -130,17 +125,17 @@ class TestJSON(object):
|
||||||
assert rv.data == b"foo"
|
assert rv.data == b"foo"
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"test_value,expected", [(True, '"\\u2603"'), (False, u'"\u2603"')]
|
"test_value,expected", [(True, '"\\u2603"'), (False, '"\u2603"')]
|
||||||
)
|
)
|
||||||
def test_json_as_unicode(self, test_value, expected, app, app_ctx):
|
def test_json_as_unicode(self, test_value, expected, app, app_ctx):
|
||||||
|
|
||||||
app.config["JSON_AS_ASCII"] = test_value
|
app.config["JSON_AS_ASCII"] = test_value
|
||||||
rv = flask.json.dumps(u"\N{SNOWMAN}")
|
rv = flask.json.dumps("\N{SNOWMAN}")
|
||||||
assert rv == expected
|
assert rv == expected
|
||||||
|
|
||||||
def test_json_dump_to_file(self, app, app_ctx):
|
def test_json_dump_to_file(self, app, app_ctx):
|
||||||
test_data = {"name": "Flask"}
|
test_data = {"name": "Flask"}
|
||||||
out = StringIO()
|
out = io.StringIO()
|
||||||
|
|
||||||
flask.json.dump(test_data, out)
|
flask.json.dump(test_data, out)
|
||||||
out.seek(0)
|
out.seek(0)
|
||||||
|
|
@ -221,7 +216,7 @@ class TestJSON(object):
|
||||||
)
|
)
|
||||||
|
|
||||||
for i, d in enumerate(test_dates):
|
for i, d in enumerate(test_dates):
|
||||||
url = "/datetest{0}".format(i)
|
url = f"/datetest{i}"
|
||||||
app.add_url_rule(url, str(i), lambda val=d: flask.jsonify(x=val))
|
app.add_url_rule(url, str(i), lambda val=d: flask.jsonify(x=val))
|
||||||
rv = client.get(url)
|
rv = client.get(url)
|
||||||
assert rv.mimetype == "application/json"
|
assert rv.mimetype == "application/json"
|
||||||
|
|
@ -254,7 +249,7 @@ class TestJSON(object):
|
||||||
@app.route("/add", methods=["POST"])
|
@app.route("/add", methods=["POST"])
|
||||||
def add():
|
def add():
|
||||||
json = flask.request.get_json()
|
json = flask.request.get_json()
|
||||||
return text_type(json["a"] + json["b"])
|
return str(json["a"] + json["b"])
|
||||||
|
|
||||||
rv = client.post(
|
rv = client.post(
|
||||||
"/add",
|
"/add",
|
||||||
|
|
@ -266,8 +261,8 @@ class TestJSON(object):
|
||||||
def test_template_escaping(self, app, req_ctx):
|
def test_template_escaping(self, app, req_ctx):
|
||||||
render = flask.render_template_string
|
render = flask.render_template_string
|
||||||
rv = flask.json.htmlsafe_dumps("</script>")
|
rv = flask.json.htmlsafe_dumps("</script>")
|
||||||
assert rv == u'"\\u003c/script\\u003e"'
|
assert rv == '"\\u003c/script\\u003e"'
|
||||||
assert type(rv) == text_type
|
assert type(rv) is str
|
||||||
rv = render('{{ "</script>"|tojson }}')
|
rv = render('{{ "</script>"|tojson }}')
|
||||||
assert rv == '"\\u003c/script\\u003e"'
|
assert rv == '"\\u003c/script\\u003e"'
|
||||||
rv = render('{{ "<\0/script>"|tojson }}')
|
rv = render('{{ "<\0/script>"|tojson }}')
|
||||||
|
|
@ -284,14 +279,14 @@ class TestJSON(object):
|
||||||
assert rv == '<a ng-data=\'{"x": ["foo", "bar", "baz\\u0027"]}\'></a>'
|
assert rv == '<a ng-data=\'{"x": ["foo", "bar", "baz\\u0027"]}\'></a>'
|
||||||
|
|
||||||
def test_json_customization(self, app, client):
|
def test_json_customization(self, app, client):
|
||||||
class X(object): # noqa: B903, for Python2 compatibility
|
class X: # noqa: B903, for Python2 compatibility
|
||||||
def __init__(self, val):
|
def __init__(self, val):
|
||||||
self.val = val
|
self.val = val
|
||||||
|
|
||||||
class MyEncoder(flask.json.JSONEncoder):
|
class MyEncoder(flask.json.JSONEncoder):
|
||||||
def default(self, o):
|
def default(self, o):
|
||||||
if isinstance(o, X):
|
if isinstance(o, X):
|
||||||
return "<%d>" % o.val
|
return f"<{o.val}>"
|
||||||
return flask.json.JSONEncoder.default(self, o)
|
return flask.json.JSONEncoder.default(self, o)
|
||||||
|
|
||||||
class MyDecoder(flask.json.JSONDecoder):
|
class MyDecoder(flask.json.JSONDecoder):
|
||||||
|
|
@ -319,14 +314,16 @@ class TestJSON(object):
|
||||||
assert rv.data == b'"<42>"'
|
assert rv.data == b'"<42>"'
|
||||||
|
|
||||||
def test_blueprint_json_customization(self, app, client):
|
def test_blueprint_json_customization(self, app, client):
|
||||||
class X(object): # noqa: B903, for Python2 compatibility
|
class X:
|
||||||
|
__slots__ = ("val",)
|
||||||
|
|
||||||
def __init__(self, val):
|
def __init__(self, val):
|
||||||
self.val = val
|
self.val = val
|
||||||
|
|
||||||
class MyEncoder(flask.json.JSONEncoder):
|
class MyEncoder(flask.json.JSONEncoder):
|
||||||
def default(self, o):
|
def default(self, o):
|
||||||
if isinstance(o, X):
|
if isinstance(o, X):
|
||||||
return "<%d>" % o.val
|
return f"<{o.val}>"
|
||||||
|
|
||||||
return flask.json.JSONEncoder.default(self, o)
|
return flask.json.JSONEncoder.default(self, o)
|
||||||
|
|
||||||
|
|
@ -372,9 +369,9 @@ class TestJSON(object):
|
||||||
def index():
|
def index():
|
||||||
return flask.request.args["foo"]
|
return flask.request.args["foo"]
|
||||||
|
|
||||||
rv = client.get(u"/?foo=정상처리".encode("euc-kr"))
|
rv = client.get("/?foo=정상처리".encode("euc-kr"))
|
||||||
assert rv.status_code == 200
|
assert rv.status_code == 200
|
||||||
assert rv.data == u"정상처리".encode("utf-8")
|
assert rv.data == "정상처리".encode()
|
||||||
|
|
||||||
def test_json_key_sorting(self, app, client):
|
def test_json_key_sorting(self, app, client):
|
||||||
app.debug = True
|
app.debug = True
|
||||||
|
|
@ -447,7 +444,7 @@ class TestJSON(object):
|
||||||
assert lines == sorted_by_str
|
assert lines == sorted_by_str
|
||||||
|
|
||||||
|
|
||||||
class PyStringIO(object):
|
class PyBytesIO:
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self._io = io.BytesIO(*args, **kwargs)
|
self._io = io.BytesIO(*args, **kwargs)
|
||||||
|
|
||||||
|
|
@ -455,7 +452,7 @@ class PyStringIO(object):
|
||||||
return getattr(self._io, name)
|
return getattr(self._io, name)
|
||||||
|
|
||||||
|
|
||||||
class TestSendfile(object):
|
class TestSendfile:
|
||||||
def test_send_file_regular(self, app, req_ctx):
|
def test_send_file_regular(self, app, req_ctx):
|
||||||
rv = flask.send_file("static/index.html")
|
rv = flask.send_file("static/index.html")
|
||||||
assert rv.direct_passthrough
|
assert rv.direct_passthrough
|
||||||
|
|
@ -503,11 +500,7 @@ class TestSendfile(object):
|
||||||
[
|
[
|
||||||
lambda app: open(os.path.join(app.static_folder, "index.html"), "rb"),
|
lambda app: open(os.path.join(app.static_folder, "index.html"), "rb"),
|
||||||
lambda app: io.BytesIO(b"Test"),
|
lambda app: io.BytesIO(b"Test"),
|
||||||
pytest.param(
|
lambda app: PyBytesIO(b"Test"),
|
||||||
lambda app: StringIO("Test"),
|
|
||||||
marks=pytest.mark.skipif(not PY2, reason="Python 2 only"),
|
|
||||||
),
|
|
||||||
lambda app: PyStringIO(b"Test"),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@pytest.mark.usefixtures("req_ctx")
|
@pytest.mark.usefixtures("req_ctx")
|
||||||
|
|
@ -524,11 +517,8 @@ class TestSendfile(object):
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"opener",
|
"opener",
|
||||||
[
|
[
|
||||||
lambda app: io.StringIO(u"Test"),
|
lambda app: io.StringIO("Test"),
|
||||||
pytest.param(
|
lambda app: open(os.path.join(app.static_folder, "index.html")),
|
||||||
lambda app: open(os.path.join(app.static_folder, "index.html")),
|
|
||||||
marks=pytest.mark.skipif(PY2, reason="Python 3 only"),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@pytest.mark.usefixtures("req_ctx")
|
@pytest.mark.usefixtures("req_ctx")
|
||||||
|
|
@ -684,15 +674,13 @@ class TestSendfile(object):
|
||||||
(
|
(
|
||||||
("index.html", "index.html", False),
|
("index.html", "index.html", False),
|
||||||
(
|
(
|
||||||
u"Ñandú/pingüino.txt",
|
"Ñandú/pingüino.txt",
|
||||||
'"Nandu/pinguino.txt"',
|
'"Nandu/pinguino.txt"',
|
||||||
"%C3%91and%C3%BA%EF%BC%8Fping%C3%BCino.txt",
|
"%C3%91and%C3%BA%EF%BC%8Fping%C3%BCino.txt",
|
||||||
),
|
),
|
||||||
(u"Vögel.txt", "Vogel.txt", "V%C3%B6gel.txt"),
|
("Vögel.txt", "Vogel.txt", "V%C3%B6gel.txt"),
|
||||||
# Native string not marked as Unicode on Python 2
|
|
||||||
("tést.txt", "test.txt", "t%C3%A9st.txt"),
|
|
||||||
# ":/" are not safe in filename* value
|
# ":/" are not safe in filename* value
|
||||||
(u"те:/ст", '":/"', "%D1%82%D0%B5%3A%2F%D1%81%D1%82"),
|
("те:/ст", '":/"', "%D1%82%D0%B5%3A%2F%D1%81%D1%82"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_attachment_filename_encoding(self, filename, ascii, utf8):
|
def test_attachment_filename_encoding(self, filename, ascii, utf8):
|
||||||
|
|
@ -701,9 +689,9 @@ class TestSendfile(object):
|
||||||
)
|
)
|
||||||
rv.close()
|
rv.close()
|
||||||
content_disposition = rv.headers["Content-Disposition"]
|
content_disposition = rv.headers["Content-Disposition"]
|
||||||
assert "filename=%s" % ascii in content_disposition
|
assert f"filename={ascii}" in content_disposition
|
||||||
if utf8:
|
if utf8:
|
||||||
assert "filename*=UTF-8''" + utf8 in content_disposition
|
assert f"filename*=UTF-8''{utf8}" in content_disposition
|
||||||
else:
|
else:
|
||||||
assert "filename*=UTF-8''" not in content_disposition
|
assert "filename*=UTF-8''" not in content_disposition
|
||||||
|
|
||||||
|
|
@ -788,7 +776,7 @@ class TestSendfile(object):
|
||||||
flask.send_from_directory("static", "bad\x00")
|
flask.send_from_directory("static", "bad\x00")
|
||||||
|
|
||||||
|
|
||||||
class TestUrlFor(object):
|
class TestUrlFor:
|
||||||
def test_url_for_with_anchor(self, app, req_ctx):
|
def test_url_for_with_anchor(self, app, req_ctx):
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def index():
|
def index():
|
||||||
|
|
@ -832,7 +820,7 @@ class TestUrlFor(object):
|
||||||
def get(self, id=None):
|
def get(self, id=None):
|
||||||
if id is None:
|
if id is None:
|
||||||
return "List"
|
return "List"
|
||||||
return "Get %d" % id
|
return f"Get {id:d}"
|
||||||
|
|
||||||
def post(self):
|
def post(self):
|
||||||
return "Create"
|
return "Create"
|
||||||
|
|
@ -847,7 +835,7 @@ class TestUrlFor(object):
|
||||||
assert flask.url_for("myview", _method="POST") == "/myview/create"
|
assert flask.url_for("myview", _method="POST") == "/myview/create"
|
||||||
|
|
||||||
|
|
||||||
class TestNoImports(object):
|
class TestNoImports:
|
||||||
"""Test Flasks are created without import.
|
"""Test Flasks are created without import.
|
||||||
|
|
||||||
Avoiding ``__import__`` helps create Flask instances where there are errors
|
Avoiding ``__import__`` helps create Flask instances where there are errors
|
||||||
|
|
@ -866,7 +854,7 @@ class TestNoImports(object):
|
||||||
AssertionError("Flask(import_name) is importing import_name.")
|
AssertionError("Flask(import_name) is importing import_name.")
|
||||||
|
|
||||||
|
|
||||||
class TestStreaming(object):
|
class TestStreaming:
|
||||||
def test_streaming_with_context(self, app, client):
|
def test_streaming_with_context(self, app, client):
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def index():
|
def index():
|
||||||
|
|
@ -897,7 +885,7 @@ class TestStreaming(object):
|
||||||
def test_streaming_with_context_and_custom_close(self, app, client):
|
def test_streaming_with_context_and_custom_close(self, app, client):
|
||||||
called = []
|
called = []
|
||||||
|
|
||||||
class Wrapper(object):
|
class Wrapper:
|
||||||
def __init__(self, gen):
|
def __init__(self, gen):
|
||||||
self._gen = gen
|
self._gen = gen
|
||||||
|
|
||||||
|
|
@ -940,7 +928,7 @@ class TestStreaming(object):
|
||||||
assert rv.data == b"flask"
|
assert rv.data == b"flask"
|
||||||
|
|
||||||
|
|
||||||
class TestSafeJoin(object):
|
class TestSafeJoin:
|
||||||
def test_safe_join(self):
|
def test_safe_join(self):
|
||||||
# Valid combinations of *args and expected joined paths.
|
# Valid combinations of *args and expected joined paths.
|
||||||
passing = (
|
passing = (
|
||||||
|
|
@ -981,7 +969,7 @@ class TestSafeJoin(object):
|
||||||
print(flask.safe_join(*args))
|
print(flask.safe_join(*args))
|
||||||
|
|
||||||
|
|
||||||
class TestHelpers(object):
|
class TestHelpers:
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"debug, expected_flag, expected_default_flag",
|
"debug, expected_flag, expected_default_flag",
|
||||||
[
|
[
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
tests.test_instance
|
tests.test_instance
|
||||||
~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
@ -12,7 +11,6 @@ import sys
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask._compat import PY2
|
|
||||||
|
|
||||||
|
|
||||||
def test_explicit_instance_paths(modules_tmpdir):
|
def test_explicit_instance_paths(modules_tmpdir):
|
||||||
|
|
@ -24,7 +22,7 @@ def test_explicit_instance_paths(modules_tmpdir):
|
||||||
assert app.instance_path == str(modules_tmpdir)
|
assert app.instance_path == str(modules_tmpdir)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(reason="TODO: weird interaction with tox")
|
@pytest.mark.xfail(reason="weird interaction with tox")
|
||||||
def test_main_module_paths(modules_tmpdir, purge_module):
|
def test_main_module_paths(modules_tmpdir, purge_module):
|
||||||
app = modules_tmpdir.join("main_app.py")
|
app = modules_tmpdir.join("main_app.py")
|
||||||
app.write('import flask\n\napp = flask.Flask("__main__")')
|
app.write('import flask\n\napp = flask.Flask("__main__")')
|
||||||
|
|
@ -36,6 +34,7 @@ def test_main_module_paths(modules_tmpdir, purge_module):
|
||||||
assert app.instance_path == os.path.join(here, "instance")
|
assert app.instance_path == os.path.join(here, "instance")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail(reason="weird interaction with tox")
|
||||||
def test_uninstalled_module_paths(modules_tmpdir, purge_module):
|
def test_uninstalled_module_paths(modules_tmpdir, purge_module):
|
||||||
app = modules_tmpdir.join("config_module_app.py").write(
|
app = modules_tmpdir.join("config_module_app.py").write(
|
||||||
"import os\n"
|
"import os\n"
|
||||||
|
|
@ -50,6 +49,7 @@ def test_uninstalled_module_paths(modules_tmpdir, purge_module):
|
||||||
assert app.instance_path == str(modules_tmpdir.join("instance"))
|
assert app.instance_path == str(modules_tmpdir.join("instance"))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail(reason="weird interaction with tox")
|
||||||
def test_uninstalled_package_paths(modules_tmpdir, purge_module):
|
def test_uninstalled_package_paths(modules_tmpdir, purge_module):
|
||||||
app = modules_tmpdir.mkdir("config_package_app")
|
app = modules_tmpdir.mkdir("config_package_app")
|
||||||
init = app.join("__init__.py")
|
init = app.join("__init__.py")
|
||||||
|
|
@ -126,19 +126,3 @@ def test_egg_installed_paths(install_egg, modules_tmpdir, modules_tmpdir_prefix)
|
||||||
finally:
|
finally:
|
||||||
if "site_egg" in sys.modules:
|
if "site_egg" in sys.modules:
|
||||||
del sys.modules["site_egg"]
|
del sys.modules["site_egg"]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not PY2, reason="This only works under Python 2.")
|
|
||||||
def test_meta_path_loader_without_is_package(request, modules_tmpdir):
|
|
||||||
app = modules_tmpdir.join("unimportable.py")
|
|
||||||
app.write("import flask\napp = flask.Flask(__name__)")
|
|
||||||
|
|
||||||
class Loader(object):
|
|
||||||
def find_module(self, name, path=None):
|
|
||||||
return self
|
|
||||||
|
|
||||||
sys.meta_path.append(Loader())
|
|
||||||
request.addfinalizer(sys.meta_path.pop)
|
|
||||||
|
|
||||||
with pytest.raises(AttributeError):
|
|
||||||
import unimportable # noqa: F401
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
tests.test_json_tag
|
tests.test_json_tag
|
||||||
~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
@ -48,7 +47,7 @@ def test_duplicate_tag():
|
||||||
|
|
||||||
|
|
||||||
def test_custom_tag():
|
def test_custom_tag():
|
||||||
class Foo(object): # noqa: B903, for Python2 compatibility
|
class Foo: # noqa: B903, for Python2 compatibility
|
||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
self.data = data
|
self.data = data
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
tests.test_logging
|
tests.test_logging
|
||||||
~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
@ -8,10 +7,10 @@ tests.test_logging
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from flask._compat import StringIO
|
|
||||||
from flask.logging import default_handler
|
from flask.logging import default_handler
|
||||||
from flask.logging import has_level_handler
|
from flask.logging import has_level_handler
|
||||||
from flask.logging import wsgi_errors_stream
|
from flask.logging import wsgi_errors_stream
|
||||||
|
|
@ -104,12 +103,3 @@ def test_log_view_exception(app, client):
|
||||||
err = stream.getvalue()
|
err = stream.getvalue()
|
||||||
assert "Exception on / [GET]" in err
|
assert "Exception on / [GET]" in err
|
||||||
assert "Exception: test" in err
|
assert "Exception: test" in err
|
||||||
|
|
||||||
|
|
||||||
def test_warn_old_config(app, request):
|
|
||||||
old_logger = logging.getLogger("flask.app")
|
|
||||||
old_logger.setLevel(logging.DEBUG)
|
|
||||||
request.addfinalizer(lambda: old_logger.setLevel(logging.NOTSET))
|
|
||||||
|
|
||||||
with pytest.warns(UserWarning):
|
|
||||||
assert app.logger.getEffectiveLevel() == logging.WARNING
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
import io
|
|
||||||
|
|
||||||
|
|
||||||
def test_changelog_utf8_compatible():
|
|
||||||
with io.open("CHANGES.rst", encoding="UTF-8") as f:
|
|
||||||
f.read()
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
tests.regression
|
tests.regression
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
@ -9,7 +8,7 @@
|
||||||
:license: BSD-3-Clause
|
:license: BSD-3-Clause
|
||||||
"""
|
"""
|
||||||
import gc
|
import gc
|
||||||
import sys
|
import platform
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
@ -20,7 +19,7 @@ import flask
|
||||||
_gc_lock = threading.Lock()
|
_gc_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
class assert_no_leak(object):
|
class assert_no_leak:
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
gc.disable()
|
gc.disable()
|
||||||
_gc_lock.acquire()
|
_gc_lock.acquire()
|
||||||
|
|
@ -44,6 +43,7 @@ class assert_no_leak(object):
|
||||||
gc.enable()
|
gc.enable()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(platform.python_implementation() == "PyPy", reason="CPython only")
|
||||||
def test_memory_consumption():
|
def test_memory_consumption():
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
|
|
||||||
|
|
@ -60,11 +60,9 @@ def test_memory_consumption():
|
||||||
# Trigger caches
|
# Trigger caches
|
||||||
fire()
|
fire()
|
||||||
|
|
||||||
# This test only works on CPython 2.7.
|
with assert_no_leak():
|
||||||
if sys.version_info >= (2, 7) and not hasattr(sys, "pypy_translation_info"):
|
for _x in range(10):
|
||||||
with assert_no_leak():
|
fire()
|
||||||
for _x in range(10):
|
|
||||||
fire()
|
|
||||||
|
|
||||||
|
|
||||||
def test_safe_join_toplevel_pardir():
|
def test_safe_join_toplevel_pardir():
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
tests.reqctx
|
tests.reqctx
|
||||||
~~~~~~~~~~~~
|
~~~~~~~~~~~~
|
||||||
|
|
@ -111,7 +110,7 @@ def test_proper_test_request_context(app):
|
||||||
def test_context_binding(app):
|
def test_context_binding(app):
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def index():
|
def index():
|
||||||
return "Hello %s!" % flask.request.args["name"]
|
return f"Hello {flask.request.args['name']}!"
|
||||||
|
|
||||||
@app.route("/meh")
|
@app.route("/meh")
|
||||||
def meh():
|
def meh():
|
||||||
|
|
@ -139,7 +138,7 @@ def test_context_test(app):
|
||||||
def test_manual_context_binding(app):
|
def test_manual_context_binding(app):
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def index():
|
def index():
|
||||||
return "Hello %s!" % flask.request.args["name"]
|
return f"Hello {flask.request.args['name']}!"
|
||||||
|
|
||||||
ctx = app.test_request_context("/?name=World")
|
ctx = app.test_request_context("/?name=World")
|
||||||
ctx.push()
|
ctx.push()
|
||||||
|
|
@ -150,7 +149,7 @@ def test_manual_context_binding(app):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(greenlet is None, reason="greenlet not installed")
|
@pytest.mark.skipif(greenlet is None, reason="greenlet not installed")
|
||||||
class TestGreenletContextCopying(object):
|
class TestGreenletContextCopying:
|
||||||
def test_greenlet_context_copying(self, app, client):
|
def test_greenlet_context_copying(self, app, client):
|
||||||
greenlets = []
|
greenlets = []
|
||||||
|
|
||||||
|
|
@ -239,7 +238,7 @@ def test_session_dynamic_cookie_name():
|
||||||
if flask.request.url.endswith("dynamic_cookie"):
|
if flask.request.url.endswith("dynamic_cookie"):
|
||||||
return "dynamic_cookie_name"
|
return "dynamic_cookie_name"
|
||||||
else:
|
else:
|
||||||
return super(PathAwareSessionInterface, self).get_cookie_name(app)
|
return super().get_cookie_name(app)
|
||||||
|
|
||||||
class CustomFlask(flask.Flask):
|
class CustomFlask(flask.Flask):
|
||||||
session_interface = PathAwareSessionInterface()
|
session_interface = PathAwareSessionInterface()
|
||||||
|
|
@ -285,17 +284,13 @@ def test_session_dynamic_cookie_name():
|
||||||
def test_bad_environ_raises_bad_request():
|
def test_bad_environ_raises_bad_request():
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
|
|
||||||
# We cannot use app.test_client() for the Unicode-rich Host header,
|
|
||||||
# because werkzeug enforces latin1 on Python 2.
|
|
||||||
# However it works when actually passed to the server.
|
|
||||||
|
|
||||||
from flask.testing import EnvironBuilder
|
from flask.testing import EnvironBuilder
|
||||||
|
|
||||||
builder = EnvironBuilder(app)
|
builder = EnvironBuilder(app)
|
||||||
environ = builder.get_environ()
|
environ = builder.get_environ()
|
||||||
|
|
||||||
# use a non-printable character in the Host - this is key to this test
|
# use a non-printable character in the Host - this is key to this test
|
||||||
environ["HTTP_HOST"] = u"\x8a"
|
environ["HTTP_HOST"] = "\x8a"
|
||||||
|
|
||||||
with app.request_context(environ):
|
with app.request_context(environ):
|
||||||
response = app.full_dispatch_request()
|
response = app.full_dispatch_request()
|
||||||
|
|
@ -309,17 +304,13 @@ def test_environ_for_valid_idna_completes():
|
||||||
def index():
|
def index():
|
||||||
return "Hello World!"
|
return "Hello World!"
|
||||||
|
|
||||||
# We cannot use app.test_client() for the Unicode-rich Host header,
|
|
||||||
# because werkzeug enforces latin1 on Python 2.
|
|
||||||
# However it works when actually passed to the server.
|
|
||||||
|
|
||||||
from flask.testing import EnvironBuilder
|
from flask.testing import EnvironBuilder
|
||||||
|
|
||||||
builder = EnvironBuilder(app)
|
builder = EnvironBuilder(app)
|
||||||
environ = builder.get_environ()
|
environ = builder.get_environ()
|
||||||
|
|
||||||
# these characters are all IDNA-compatible
|
# these characters are all IDNA-compatible
|
||||||
environ["HTTP_HOST"] = u"ąśźäüжŠßя.com"
|
environ["HTTP_HOST"] = "ąśźäüжŠßя.com"
|
||||||
|
|
||||||
with app.request_context(environ):
|
with app.request_context(environ):
|
||||||
response = app.full_dispatch_request()
|
response = app.full_dispatch_request()
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
tests.signals
|
tests.signals
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
tests.subclassing
|
tests.subclassing
|
||||||
~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
@ -9,8 +8,9 @@
|
||||||
:copyright: 2010 Pallets
|
:copyright: 2010 Pallets
|
||||||
:license: BSD-3-Clause
|
:license: BSD-3-Clause
|
||||||
"""
|
"""
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask._compat import StringIO
|
|
||||||
|
|
||||||
|
|
||||||
def test_suppressed_exception_logging():
|
def test_suppressed_exception_logging():
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
tests.templating
|
tests.templating
|
||||||
~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
@ -415,16 +414,16 @@ def test_template_loader_debugging(test_apps, monkeypatch):
|
||||||
def handle(self, record):
|
def handle(self, record):
|
||||||
called.append(True)
|
called.append(True)
|
||||||
text = str(record.msg)
|
text = str(record.msg)
|
||||||
assert '1: trying loader of application "blueprintapp"' in text
|
assert "1: trying loader of application 'blueprintapp'" in text
|
||||||
assert (
|
assert (
|
||||||
'2: trying loader of blueprint "admin" ' "(blueprintapp.apps.admin)"
|
"2: trying loader of blueprint 'admin' (blueprintapp.apps.admin)"
|
||||||
) in text
|
) in text
|
||||||
assert (
|
assert (
|
||||||
'trying loader of blueprint "frontend" ' "(blueprintapp.apps.frontend)"
|
"trying loader of blueprint 'frontend' (blueprintapp.apps.frontend)"
|
||||||
) in text
|
) in text
|
||||||
assert "Error: the template could not be found" in text
|
assert "Error: the template could not be found" in text
|
||||||
assert (
|
assert (
|
||||||
"looked up from an endpoint that belongs to " 'the blueprint "frontend"'
|
"looked up from an endpoint that belongs to the blueprint 'frontend'"
|
||||||
) in text
|
) in text
|
||||||
assert "See https://flask.palletsprojects.com/blueprints/#templates" in text
|
assert "See https://flask.palletsprojects.com/blueprints/#templates" in text
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
tests.testing
|
tests.testing
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
@ -14,12 +13,10 @@ import werkzeug
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import appcontext_popped
|
from flask import appcontext_popped
|
||||||
from flask._compat import text_type
|
|
||||||
from flask.cli import ScriptInfo
|
from flask.cli import ScriptInfo
|
||||||
from flask.json import jsonify
|
from flask.json import jsonify
|
||||||
from flask.testing import EnvironBuilder
|
from flask.testing import EnvironBuilder
|
||||||
from flask.testing import FlaskCliRunner
|
from flask.testing import FlaskCliRunner
|
||||||
from flask.testing import make_test_environ_builder
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import blinker
|
import blinker
|
||||||
|
|
@ -62,7 +59,7 @@ def test_environ_base_default(app, client, app_ctx):
|
||||||
|
|
||||||
rv = client.get("/")
|
rv = client.get("/")
|
||||||
assert rv.data == b"127.0.0.1"
|
assert rv.data == b"127.0.0.1"
|
||||||
assert flask.g.user_agent == "werkzeug/" + werkzeug.__version__
|
assert flask.g.user_agent == f"werkzeug/{werkzeug.__version__}"
|
||||||
|
|
||||||
|
|
||||||
def test_environ_base_modified(app, client, app_ctx):
|
def test_environ_base_modified(app, client, app_ctx):
|
||||||
|
|
@ -121,20 +118,11 @@ def test_path_is_url(app):
|
||||||
assert eb.path == "/"
|
assert eb.path == "/"
|
||||||
|
|
||||||
|
|
||||||
def test_make_test_environ_builder(app):
|
|
||||||
with pytest.deprecated_call():
|
|
||||||
eb = make_test_environ_builder(app, "https://example.com/")
|
|
||||||
assert eb.url_scheme == "https"
|
|
||||||
assert eb.host == "example.com"
|
|
||||||
assert eb.script_root == ""
|
|
||||||
assert eb.path == "/"
|
|
||||||
|
|
||||||
|
|
||||||
def test_environbuilder_json_dumps(app):
|
def test_environbuilder_json_dumps(app):
|
||||||
"""EnvironBuilder.json_dumps() takes settings from the app."""
|
"""EnvironBuilder.json_dumps() takes settings from the app."""
|
||||||
app.config["JSON_AS_ASCII"] = False
|
app.config["JSON_AS_ASCII"] = False
|
||||||
eb = EnvironBuilder(app, json=u"\u20ac")
|
eb = EnvironBuilder(app, json="\u20ac")
|
||||||
assert eb.input_stream.read().decode("utf8") == u'"\u20ac"'
|
assert eb.input_stream.read().decode("utf8") == '"\u20ac"'
|
||||||
|
|
||||||
|
|
||||||
def test_blueprint_with_subdomain():
|
def test_blueprint_with_subdomain():
|
||||||
|
|
@ -180,12 +168,10 @@ def test_redirect_keep_session(app, client, app_ctx):
|
||||||
rv = client.get("/")
|
rv = client.get("/")
|
||||||
assert rv.data == b"index"
|
assert rv.data == b"index"
|
||||||
assert flask.session.get("data") == "foo"
|
assert flask.session.get("data") == "foo"
|
||||||
|
|
||||||
rv = client.post("/", data={}, follow_redirects=True)
|
rv = client.post("/", data={}, follow_redirects=True)
|
||||||
assert rv.data == b"foo"
|
assert rv.data == b"foo"
|
||||||
|
assert flask.session.get("data") == "foo"
|
||||||
# This support requires a new Werkzeug version
|
|
||||||
if not hasattr(client, "redirect_client"):
|
|
||||||
assert flask.session.get("data") == "foo"
|
|
||||||
|
|
||||||
rv = client.get("/getsession")
|
rv = client.get("/getsession")
|
||||||
assert rv.data == b"foo"
|
assert rv.data == b"foo"
|
||||||
|
|
@ -194,7 +180,7 @@ def test_redirect_keep_session(app, client, app_ctx):
|
||||||
def test_session_transactions(app, client):
|
def test_session_transactions(app, client):
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def index():
|
def index():
|
||||||
return text_type(flask.session["foo"])
|
return str(flask.session["foo"])
|
||||||
|
|
||||||
with client:
|
with client:
|
||||||
with client.session_transaction() as sess:
|
with client.session_transaction() as sess:
|
||||||
|
|
@ -335,9 +321,9 @@ def test_json_request_and_response(app, client):
|
||||||
def test_client_json_no_app_context(app, client):
|
def test_client_json_no_app_context(app, client):
|
||||||
@app.route("/hello", methods=["POST"])
|
@app.route("/hello", methods=["POST"])
|
||||||
def hello():
|
def hello():
|
||||||
return "Hello, {}!".format(flask.request.json["name"])
|
return f"Hello, {flask.request.json['name']}!"
|
||||||
|
|
||||||
class Namespace(object):
|
class Namespace:
|
||||||
count = 0
|
count = 0
|
||||||
|
|
||||||
def add(self, app):
|
def add(self, app):
|
||||||
|
|
@ -415,7 +401,7 @@ def test_cli_invoke(app):
|
||||||
|
|
||||||
|
|
||||||
def test_cli_custom_obj(app):
|
def test_cli_custom_obj(app):
|
||||||
class NS(object):
|
class NS:
|
||||||
called = False
|
called = False
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
tests.test_user_error_handler
|
tests.test_user_error_handler
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
@ -30,7 +29,7 @@ def test_error_handler_no_match(app, client):
|
||||||
original = getattr(e, "original_exception", None)
|
original = getattr(e, "original_exception", None)
|
||||||
|
|
||||||
if original is not None:
|
if original is not None:
|
||||||
return "wrapped " + type(original).__name__
|
return f"wrapped {type(original).__name__}"
|
||||||
|
|
||||||
return "direct"
|
return "direct"
|
||||||
|
|
||||||
|
|
@ -208,7 +207,7 @@ def test_default_error_handler():
|
||||||
assert c.get("/slash", follow_redirects=True).data == b"slash"
|
assert c.get("/slash", follow_redirects=True).data == b"slash"
|
||||||
|
|
||||||
|
|
||||||
class TestGenericHandlers(object):
|
class TestGenericHandlers:
|
||||||
"""Test how very generic handlers are dispatched to."""
|
"""Test how very generic handlers are dispatched to."""
|
||||||
|
|
||||||
class Custom(Exception):
|
class Custom(Exception):
|
||||||
|
|
@ -239,9 +238,9 @@ class TestGenericHandlers(object):
|
||||||
original = getattr(e, "original_exception", None)
|
original = getattr(e, "original_exception", None)
|
||||||
|
|
||||||
if original is not None:
|
if original is not None:
|
||||||
return "wrapped " + type(original).__name__
|
return f"wrapped {type(original).__name__}"
|
||||||
|
|
||||||
return "direct " + type(e).__name__
|
return f"direct {type(e).__name__}"
|
||||||
|
|
||||||
@pytest.mark.parametrize("to_handle", (InternalServerError, 500))
|
@pytest.mark.parametrize("to_handle", (InternalServerError, 500))
|
||||||
def test_handle_class_or_code(self, app, client, to_handle):
|
def test_handle_class_or_code(self, app, client, to_handle):
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
tests.views
|
tests.views
|
||||||
~~~~~~~~~~~
|
~~~~~~~~~~~
|
||||||
|
|
|
||||||
35
tox.ini
35
tox.ini
|
|
@ -1,16 +1,14 @@
|
||||||
[tox]
|
[tox]
|
||||||
envlist =
|
envlist =
|
||||||
py{38,37,36,35,27,py3,py}
|
py{38,37,36,py3}
|
||||||
py38-{simplejson,devel,lowest}
|
py38-{simplejson,devel,lowest}
|
||||||
|
style
|
||||||
docs
|
docs
|
||||||
coverage
|
|
||||||
skip_missing_interpreters = true
|
skip_missing_interpreters = true
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
passenv = LANG
|
|
||||||
deps =
|
deps =
|
||||||
pytest
|
pytest
|
||||||
coverage
|
|
||||||
greenlet
|
greenlet
|
||||||
blinker
|
blinker
|
||||||
python-dotenv
|
python-dotenv
|
||||||
|
|
@ -29,18 +27,10 @@ deps =
|
||||||
simplejson: simplejson
|
simplejson: simplejson
|
||||||
|
|
||||||
commands =
|
commands =
|
||||||
# the examples need to be installed to test successfully
|
|
||||||
pip install -q -e examples/tutorial[test]
|
pip install -q -e examples/tutorial[test]
|
||||||
pip install -q -e examples/javascript[test]
|
pip install -q -e examples/javascript[test]
|
||||||
|
|
||||||
# pytest-cov doesn't seem to play nice with -p
|
pytest --tb=short --basetemp={envtmpdir} {posargs:tests examples}
|
||||||
coverage run -p -m pytest --tb=short -Werror {posargs:tests examples}
|
|
||||||
|
|
||||||
[testenv:nightly]
|
|
||||||
# courtesy Python nightly test, don't fail the build in CI
|
|
||||||
ignore_outcome = true
|
|
||||||
commands =
|
|
||||||
coverage run -p -m pytest --tb=short -Werror --junitxml=test-results.xml tests
|
|
||||||
|
|
||||||
[testenv:style]
|
[testenv:style]
|
||||||
deps = pre-commit
|
deps = pre-commit
|
||||||
|
|
@ -48,22 +38,5 @@ skip_install = true
|
||||||
commands = pre-commit run --all-files --show-diff-on-failure
|
commands = pre-commit run --all-files --show-diff-on-failure
|
||||||
|
|
||||||
[testenv:docs]
|
[testenv:docs]
|
||||||
deps =
|
deps = -r docs/requirements.txt
|
||||||
-r docs/requirements.txt
|
|
||||||
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:coverage]
|
|
||||||
deps = coverage
|
|
||||||
skip_install = true
|
|
||||||
commands =
|
|
||||||
coverage combine
|
|
||||||
coverage html
|
|
||||||
coverage report
|
|
||||||
|
|
||||||
[testenv:coverage-ci]
|
|
||||||
deps = coverage
|
|
||||||
skip_install = true
|
|
||||||
commands =
|
|
||||||
coverage combine
|
|
||||||
coverage xml
|
|
||||||
coverage report
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue