commit
cd8a374504
84 changed files with 585 additions and 2051 deletions
|
|
@ -1,85 +1,44 @@
|
|||
trigger:
|
||||
- 'master'
|
||||
- master
|
||||
- '*.x'
|
||||
|
||||
jobs:
|
||||
- job: Tests
|
||||
variables:
|
||||
vmImage: 'ubuntu-latest'
|
||||
python.version: '3.8'
|
||||
TOXENV: 'py,coverage-ci'
|
||||
hasTestResults: 'true'
|
||||
variables:
|
||||
vmImage: ubuntu-latest
|
||||
python.version: '3.8'
|
||||
TOXENV: py
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
Python 3.8 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:
|
||||
strategy:
|
||||
matrix:
|
||||
Linux:
|
||||
vmImage: ubuntu-latest
|
||||
container: python:rc-stretch
|
||||
steps:
|
||||
- script: |
|
||||
echo "##vso[task.prependPath]$HOME/.local/bin"
|
||||
pip --disable-pip-version-check install --user -U tox
|
||||
displayName: Install tox
|
||||
Windows:
|
||||
vmImage: windows-latest
|
||||
Mac:
|
||||
vmImage: macos-latest
|
||||
Python 3.7:
|
||||
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
|
||||
displayName: Run tox
|
||||
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
|
||||
displayName: Run tox
|
||||
|
|
|
|||
|
|
@ -1,22 +1,29 @@
|
|||
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
|
||||
rev: v1.5.0
|
||||
rev: v2.1.0
|
||||
hooks:
|
||||
- id: reorder-python-imports
|
||||
name: Reorder Python imports (src, tests)
|
||||
files: "^(?!examples/)"
|
||||
args: ["--application-directories", ".:src"]
|
||||
args: ["--application-directories", "src"]
|
||||
- repo: https://github.com/python/black
|
||||
rev: 19.3b0
|
||||
rev: 19.10b0
|
||||
hooks:
|
||||
- id: black
|
||||
- repo: https://gitlab.com/pycqa/flake8
|
||||
rev: 3.7.7
|
||||
rev: 3.7.9
|
||||
hooks:
|
||||
- id: flake8
|
||||
additional_dependencies: [flake8-bugbear]
|
||||
additional_dependencies:
|
||||
- flake8-bugbear
|
||||
- flake8-implicit-str-concat
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v2.2.3
|
||||
rev: v2.5.0
|
||||
hooks:
|
||||
- id: check-byte-order-marker
|
||||
- id: trailing-whitespace
|
||||
|
|
|
|||
18
CHANGES.rst
18
CHANGES.rst
|
|
@ -1,10 +1,11 @@
|
|||
.. currentmodule:: flask
|
||||
|
||||
Version 1.2.0
|
||||
Version 2.0.0
|
||||
-------------
|
||||
|
||||
Unreleased
|
||||
|
||||
- Drop support for Python 2 and 3.5.
|
||||
- Add :meth:`sessions.SessionInterface.get_cookie_name` to allow
|
||||
setting the session cookie name dynamically. :pr:`3369`
|
||||
- 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
|
||||
:func:`flask.jsonify`. This introduces a security risk in ancient
|
||||
browsers. See :ref:`json-security` for details.
|
||||
browsers.
|
||||
- Added before_render_template signal.
|
||||
- Added ``**kwargs`` to :meth:`flask.Test.test_client` to support
|
||||
passing additional keyword arguments to the constructor of
|
||||
|
|
@ -567,8 +568,7 @@ Version 0.10
|
|||
Released 2013-06-13, codename Limoncello
|
||||
|
||||
- Changed default cookie serialization format from pickle to JSON to
|
||||
limit the impact an attacker can do if the secret key leaks. See
|
||||
:ref:`upgrading-to-010` for more information.
|
||||
limit the impact an attacker can do if the secret key leaks.
|
||||
- Added ``template_test`` methods in addition to the already existing
|
||||
``template_filter`` method family.
|
||||
- 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
|
||||
result in less bytes being transmitted over the network. It's
|
||||
disabled by default to not cause confusion with existing libraries
|
||||
that might expect ``flask.json.dumps`` to return bytestrings by
|
||||
default.
|
||||
that might expect ``flask.json.dumps`` to return bytes by default.
|
||||
- ``flask.g`` is now stored on the app context instead of the request
|
||||
context.
|
||||
- ``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
|
||||
etc.). Also this is conceptually only instance depending and outside
|
||||
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.
|
||||
- Implemented :meth:`~flask.testing.TestClient.session_transaction` to
|
||||
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
|
||||
at the end of a request regardless of whether an exception occurred.
|
||||
Also the behavior for ``after_request`` was changed. It's now no
|
||||
longer executed when an exception is raised. See
|
||||
:ref:`upgrading-to-new-teardown-handling`
|
||||
longer executed when an exception is raised.
|
||||
- Implemented :func:`flask.has_request_context`
|
||||
- Deprecated ``init_jinja_globals``. Override the
|
||||
: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
|
||||
database connection errors, timeouts from remote resources etc.).
|
||||
- Blueprints can provide blueprint specific error handlers.
|
||||
- Implemented generic :ref:`views` (class-based views).
|
||||
- Implemented generic class-based views.
|
||||
|
||||
|
||||
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.
|
||||
|
||||
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,
|
||||
3.5, 3.6, 3.7 and PyPy 2.7 installed to run all of the environments. Then run::
|
||||
combinations of Python and dependencies. If you don't have a Python
|
||||
version installed, it will be skipped with a warning message at the end.
|
||||
Run::
|
||||
|
||||
tox
|
||||
|
||||
|
|
|
|||
12
docs/api.rst
12
docs/api.rst
|
|
@ -58,12 +58,12 @@ Incoming Request Data
|
|||
the following:
|
||||
|
||||
============= ======================================================
|
||||
`path` ``u'/π/page.html'``
|
||||
`full_path` ``u'/π/page.html?x=y'``
|
||||
`script_root` ``u'/myapplication'``
|
||||
`base_url` ``u'http://www.example.com/myapplication/π/page.html'``
|
||||
`url` ``u'http://www.example.com/myapplication/π/page.html?x=y'``
|
||||
`url_root` ``u'http://www.example.com/myapplication/'``
|
||||
`path` ``'/π/page.html'``
|
||||
`full_path` ``'/π/page.html?x=y'``
|
||||
`script_root` ``'/myapplication'``
|
||||
`base_url` ``'http://www.example.com/myapplication/π/page.html'``
|
||||
`url` ``'http://www.example.com/myapplication/π/page.html?x=y'``
|
||||
`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_favicon = "_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
|
||||
|
||||
# LaTeX ----------------------------------------------------------------
|
||||
|
||||
latex_documents = [
|
||||
(master_doc, "Flask-{}.tex".format(version), html_title, author, "manual")
|
||||
]
|
||||
latex_documents = [(master_doc, f"Flask-{version}.tex", html_title, author, "manual")]
|
||||
|
||||
# Local Extensions -----------------------------------------------------
|
||||
|
||||
|
|
@ -76,9 +74,9 @@ def github_link(name, rawtext, text, lineno, inliner, options=None, content=None
|
|||
words = None
|
||||
|
||||
if packaging.version.parse(release).is_devrelease:
|
||||
url = "{0}master/{1}".format(base_url, text)
|
||||
url = f"{base_url}master/{text}"
|
||||
else:
|
||||
url = "{0}{1}/{2}".format(base_url, release, text)
|
||||
url = f"{base_url}{release}/{text}"
|
||||
|
||||
if words is None:
|
||||
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
|
||||
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
|
||||
is accepted too. For example, copy the output of this to your config::
|
||||
application. It should be a long random ``bytes`` or ``str``. For
|
||||
example, copy the output of this to your config::
|
||||
|
||||
$ python -c 'import os; print(os.urandom(16))'
|
||||
b'_5#y2L"F4Q8z\n\xec]/'
|
||||
|
|
@ -302,10 +302,10 @@ The following configuration values are used internally by Flask:
|
|||
|
||||
.. py:data:: JSON_AS_ASCII
|
||||
|
||||
Serialize objects to ASCII-encoded JSON. If this is disabled, the JSON
|
||||
will be returned as a Unicode string, or encoded as ``UTF-8`` by
|
||||
``jsonify``. This has security implications when rendering the JSON into
|
||||
JavaScript in templates, and should typically remain enabled.
|
||||
Serialize objects to ASCII-encoded JSON. If this is disabled, the
|
||||
JSON returned from ``jsonify`` will contain Unicode characters. This
|
||||
has security implications when rendering the JSON into JavaScript in
|
||||
templates, and should typically remain enabled.
|
||||
|
||||
Default: ``True``
|
||||
|
||||
|
|
@ -596,8 +596,8 @@ your configuration classes::
|
|||
DB_SERVER = '192.168.1.56'
|
||||
|
||||
@property
|
||||
def DATABASE_URI(self): # Note: all caps
|
||||
return 'mysql://user@{}/foo'.format(self.DB_SERVER)
|
||||
def DATABASE_URI(self): # Note: all caps
|
||||
return f"mysql://user@{self.DB_SERVER}/foo"
|
||||
|
||||
class ProductionConfig(Config):
|
||||
"""Uses production database server."""
|
||||
|
|
@ -678,7 +678,7 @@ locations are used:
|
|||
|
||||
- Installed module or package::
|
||||
|
||||
$PREFIX/lib/python2.X/site-packages/myapp
|
||||
$PREFIX/lib/pythonX.Y/site-packages/myapp
|
||||
$PREFIX/var/myapp-instance
|
||||
|
||||
``$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::
|
||||
|
||||
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'
|
||||
with open(activate_this) as file_:
|
||||
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
|
||||
of variables and take the return value as string.
|
||||
|
||||
But that's about where similarities end. Jinja2 for example has an
|
||||
extensive filter system, a certain way to do template inheritance, support
|
||||
for reusable blocks (macros) that can be used from inside templates and
|
||||
also from Python code, uses Unicode for all operations, supports
|
||||
iterative template rendering, configurable syntax and more. On the other
|
||||
hand an engine like Genshi is based on XML stream evaluation, template
|
||||
inheritance by taking the availability of XPath into account and more.
|
||||
Mako on the other hand treats templates similar to Python modules.
|
||||
But that's about where similarities end. Jinja2 for example has an
|
||||
extensive filter system, a certain way to do template inheritance,
|
||||
support for reusable blocks (macros) that can be used from inside
|
||||
templates and also from Python code, supports iterative template
|
||||
rendering, configurable syntax and more. On the other hand an engine
|
||||
like Genshi is based on XML stream evaluation, template inheritance by
|
||||
taking the availability of XPath into account and more. Mako on the
|
||||
other hand treats templates similar to Python modules.
|
||||
|
||||
When it comes to connecting a template engine with an application or
|
||||
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
|
||||
htmlfaq
|
||||
security
|
||||
unicode
|
||||
extensiondev
|
||||
styleguide
|
||||
upgrading
|
||||
changelog
|
||||
license
|
||||
contributing
|
||||
|
|
|
|||
|
|
@ -3,11 +3,13 @@
|
|||
Installation
|
||||
============
|
||||
|
||||
|
||||
Python Version
|
||||
--------------
|
||||
|
||||
We recommend using the latest version of Python 3. Flask supports Python 3.5
|
||||
and newer, Python 2.7, and PyPy.
|
||||
We recommend using the latest version of Python. Flask supports Python
|
||||
3.6 and newer.
|
||||
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
|
@ -31,6 +33,7 @@ These distributions will be installed automatically when installing Flask.
|
|||
.. _ItsDangerous: https://palletsprojects.com/p/itsdangerous/
|
||||
.. _Click: https://palletsprojects.com/p/click/
|
||||
|
||||
|
||||
Optional dependencies
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
@ -51,6 +54,7 @@ use them if you install them.
|
|||
.. _python-dotenv: https://github.com/theskumar/python-dotenv#readme
|
||||
.. _watchdog: https://pythonhosted.org/watchdog/
|
||||
|
||||
|
||||
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
|
||||
the operating system's packages.
|
||||
|
||||
Python 3 comes bundled with the :mod:`venv` module to create virtual
|
||||
environments. If you're using a modern version of Python, you can continue on
|
||||
to the next section.
|
||||
Python comes bundled with the :mod:`venv` module to create virtual
|
||||
environments.
|
||||
|
||||
If you're using Python 2, see :ref:`install-install-virtualenv` first.
|
||||
|
||||
.. _install-create-env:
|
||||
|
||||
Create an environment
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
@ -91,18 +91,6 @@ On Windows:
|
|||
|
||||
$ 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:
|
||||
|
||||
|
|
@ -121,12 +109,15 @@ On Windows:
|
|||
|
||||
> 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
|
||||
-------------
|
||||
|
||||
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
|
||||
|
||||
|
|
@ -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
|
||||
: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.
|
||||
- **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
|
||||
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 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
|
||||
: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. ::
|
||||
|
||||
def url(import_name, url_rules=[], **options):
|
||||
view = LazyView('yourapplication.' + import_name)
|
||||
view = LazyView(f"yourapplication.{import_name}")
|
||||
for url_rule in url_rules:
|
||||
app.add_url_rule(url_rule, view_func=view, **options)
|
||||
|
||||
|
|
|
|||
|
|
@ -52,4 +52,4 @@ Example usage::
|
|||
files = request.files
|
||||
# At this point the hash is fully constructed.
|
||||
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.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,
|
||||
autoflush=False,
|
||||
bind=engine))
|
||||
|
|
@ -104,9 +104,9 @@ You can insert entries into the database like this:
|
|||
Querying is simple as well:
|
||||
|
||||
>>> User.query.all()
|
||||
[<User u'admin'>]
|
||||
[<User 'admin'>]
|
||||
>>> User.query.filter(User.name == 'admin').first()
|
||||
<User u'admin'>
|
||||
<User 'admin'>
|
||||
|
||||
.. _SQLAlchemy: https://www.sqlalchemy.org/
|
||||
.. _declarative:
|
||||
|
|
@ -127,7 +127,7 @@ Here is an example :file:`database.py` module for your application::
|
|||
from sqlalchemy import create_engine, MetaData
|
||||
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()
|
||||
db_session = scoped_session(sessionmaker(autocommit=False,
|
||||
autoflush=False,
|
||||
|
|
@ -179,7 +179,7 @@ you basically only need the engine::
|
|||
|
||||
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)
|
||||
|
||||
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:
|
||||
|
||||
>>> 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:
|
||||
|
||||
>>> r = users.select(users.c.id == 1).execute().first()
|
||||
>>> r['name']
|
||||
u'admin'
|
||||
'admin'
|
||||
|
||||
You can also pass strings of SQL statements to the
|
||||
:meth:`~sqlalchemy.engine.base.Connection.execute` method:
|
||||
|
||||
>>> 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
|
||||
`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():
|
||||
for row in iter_all_rows():
|
||||
yield ','.join(row) + '\n'
|
||||
yield f"{','.join(row)}\n"
|
||||
return Response(generate(), mimetype='text/csv')
|
||||
|
||||
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):
|
||||
template_name = template
|
||||
if template_name is None:
|
||||
template_name = request.endpoint \
|
||||
.replace('.', '/') + '.html'
|
||||
template_name = f"'{request.endpoint.replace('.', '/')}.html'"
|
||||
ctx = f(*args, **kwargs)
|
||||
if ctx is None:
|
||||
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
|
||||
arguments will be inserted as HTML attributes. So, for example, you can
|
||||
call ``render_field(form.username, class='username')`` to add a class to
|
||||
the input element. Note that WTForms returns standard Python unicode
|
||||
strings, so we have to tell Jinja2 that this data is already HTML-escaped
|
||||
with the ``|safe`` filter.
|
||||
the input element. Note that WTForms returns standard Python strings,
|
||||
so we have to tell Jinja2 that this data is already HTML-escaped with
|
||||
the ``|safe`` filter.
|
||||
|
||||
Here is the :file:`register.html` template for the function we used above, which
|
||||
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.
|
||||
2. You can change your URLs in one go instead of needing to remember to
|
||||
manually change hard-coded URLs.
|
||||
3. URL building handles escaping of special characters and Unicode data
|
||||
transparently.
|
||||
3. URL building handles escaping of special characters transparently.
|
||||
4. The generated paths are always absolute, avoiding unexpected behavior
|
||||
of relative paths in browsers.
|
||||
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
|
||||
>>> 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(u'<blink>hacker</blink>')
|
||||
Markup('<blink>hacker</blink>')
|
||||
>>> Markup('<em>Marked up</em> » HTML').striptags()
|
||||
u'Marked up \xbb HTML'
|
||||
'Marked up \xbb HTML'
|
||||
|
||||
.. versionchanged:: 0.5
|
||||
|
||||
|
|
@ -610,8 +609,8 @@ Werkzeug provides for you::
|
|||
@app.route('/upload', methods=['GET', 'POST'])
|
||||
def upload_file():
|
||||
if request.method == 'POST':
|
||||
f = request.files['the_file']
|
||||
f.save('/var/www/uploads/' + secure_filename(f.filename))
|
||||
file = request.files['the_file']
|
||||
file.save(f"/var/www/uploads/{secure_filename(f.filename)}")
|
||||
...
|
||||
|
||||
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
|
||||
def utility_processor():
|
||||
def format_price(amount, currency=u'€'):
|
||||
return u'{0:.2f}{1}'.format(amount, currency)
|
||||
def format_price(amount, currency="€"):
|
||||
return f"{amount:.2f}{currency}"
|
||||
return dict(format_price=format_price)
|
||||
|
||||
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):
|
||||
"""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
|
||||
|
||||
rv = logout(client)
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
Test Adding Messages
|
||||
|
|
|
|||
|
|
@ -207,7 +207,7 @@ it from each view.
|
|||
).fetchone()
|
||||
|
||||
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']:
|
||||
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
|
||||
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
|
||||
Unicode text, use :meth:`get_data(as_text=True) <werkzeug.wrappers.BaseResponse.get_data>`
|
||||
``data``. Bytes must be compared to bytes. If you want to compare text,
|
||||
use :meth:`get_data(as_text=True) <werkzeug.wrappers.BaseResponse.get_data>`
|
||||
instead.
|
||||
|
||||
``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(
|
||||
'SELECT id FROM user WHERE username = ?', (username,)
|
||||
).fetchone() is not None:
|
||||
error = 'User {} is already registered.'.format(username)
|
||||
error = f"User {username} is already registered."
|
||||
|
||||
if error is None:
|
||||
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__)
|
||||
|
||||
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("/<any(plain, jquery, fetch):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"])
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import io
|
||||
|
||||
from setuptools import find_packages
|
||||
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()
|
||||
|
||||
setup(
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ def register():
|
|||
db.execute("SELECT id FROM user WHERE username = ?", (username,)).fetchone()
|
||||
is not None
|
||||
):
|
||||
error = "User {0} is already registered.".format(username)
|
||||
error = f"User {username} is already registered."
|
||||
|
||||
if error is None:
|
||||
# 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:
|
||||
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"]:
|
||||
abort(403)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import io
|
||||
|
||||
from setuptools import find_packages
|
||||
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()
|
||||
|
||||
setup(
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ def runner(app):
|
|||
return app.test_cli_runner()
|
||||
|
||||
|
||||
class AuthActions(object):
|
||||
class AuthActions:
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ def test_get_close_db(app):
|
|||
|
||||
|
||||
def test_init_db_command(runner, monkeypatch):
|
||||
class Recorder(object):
|
||||
class Recorder:
|
||||
called = False
|
||||
|
||||
def fake_init_db():
|
||||
|
|
|
|||
18
setup.cfg
18
setup.cfg
|
|
@ -1,8 +1,7 @@
|
|||
[bdist_wheel]
|
||||
universal = true
|
||||
|
||||
[tool:pytest]
|
||||
testpaths = tests
|
||||
filterwarnings =
|
||||
error
|
||||
|
||||
[coverage:run]
|
||||
branch = True
|
||||
|
|
@ -12,9 +11,8 @@ source =
|
|||
|
||||
[coverage:paths]
|
||||
source =
|
||||
src/flask
|
||||
.tox/*/lib/python*/site-packages/flask
|
||||
.tox/*/site-packages/flask
|
||||
src
|
||||
*/site-packages
|
||||
|
||||
[flake8]
|
||||
# B = bugbear
|
||||
|
|
@ -22,7 +20,8 @@ source =
|
|||
# F = flake8 pyflakes
|
||||
# W = pycodestyle warnings
|
||||
# B9 = bugbear opinions
|
||||
select = B, E, F, W, B9
|
||||
# ISC = implicit-str-concat
|
||||
select = B, E, F, W, B9, ISC
|
||||
ignore =
|
||||
# slice notation whitespace, invalid
|
||||
E203
|
||||
|
|
@ -37,6 +36,5 @@ ignore =
|
|||
# up to 88 allowed by bugbear B950
|
||||
max-line-length = 80
|
||||
per-file-ignores =
|
||||
# __init__ modules export names
|
||||
**/__init__.py: F401
|
||||
src/flask/_compat.py: E731, B301, F401
|
||||
# __init__ module exports names
|
||||
src/flask/__init__.py: F401
|
||||
|
|
|
|||
16
setup.py
16
setup.py
|
|
@ -1,13 +1,12 @@
|
|||
import io
|
||||
import re
|
||||
|
||||
from setuptools import find_packages
|
||||
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()
|
||||
|
||||
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)
|
||||
|
||||
setup(
|
||||
|
|
@ -34,15 +33,6 @@ setup(
|
|||
"License :: OSI Approved :: BSD License",
|
||||
"Operating System :: OS Independent",
|
||||
"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 :: WSGI :: Application",
|
||||
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
||||
|
|
@ -51,7 +41,7 @@ setup(
|
|||
packages=find_packages("src"),
|
||||
package_dir={"": "src"},
|
||||
include_package_data=True,
|
||||
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*",
|
||||
python_requires=">=3.6",
|
||||
install_requires=[
|
||||
"Werkzeug>=0.15",
|
||||
"Jinja2>=2.10.1",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flask
|
||||
~~~~~
|
||||
|
|
@ -17,7 +16,6 @@ from werkzeug.exceptions import abort
|
|||
from werkzeug.utils import redirect
|
||||
|
||||
from . import json
|
||||
from ._compat import json_available
|
||||
from .app import Flask
|
||||
from .app import Request
|
||||
from .app import Response
|
||||
|
|
@ -57,4 +55,4 @@ from .signals import template_rendered
|
|||
from .templating import render_template
|
||||
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__
|
||||
~~~~~~~~~~~~~~
|
||||
|
|
|
|||
|
|
@ -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
|
||||
~~~~~~~~~
|
||||
|
|
@ -10,7 +9,6 @@
|
|||
"""
|
||||
import os
|
||||
import sys
|
||||
import warnings
|
||||
from datetime import timedelta
|
||||
from functools import update_wrapper
|
||||
from itertools import chain
|
||||
|
|
@ -32,10 +30,6 @@ from werkzeug.wrappers import BaseResponse
|
|||
|
||||
from . import cli
|
||||
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 ConfigAttribute
|
||||
from .ctx import _AppCtxGlobals
|
||||
|
|
@ -191,11 +185,9 @@ class Flask(_PackageBoundObject):
|
|||
for loading the config are assumed to
|
||||
be relative to the instance path instead
|
||||
of the application root.
|
||||
:param root_path: Flask by default will automatically calculate the path
|
||||
to the root of the application. In certain situations
|
||||
this cannot be achieved (for instance if the package
|
||||
is a Python 3 namespace package) and needs to be
|
||||
manually defined.
|
||||
:param root_path: The path to the root of the application files.
|
||||
This should only be set manually when it can't be detected
|
||||
automatically, such as for namespace packages.
|
||||
"""
|
||||
|
||||
#: The class that is used for request objects. See :class:`~flask.Request`
|
||||
|
|
@ -593,7 +585,7 @@ class Flask(_PackageBoundObject):
|
|||
bool(static_host) == host_matching
|
||||
), "Invalid static_host/host_matching combination"
|
||||
self.add_url_rule(
|
||||
self.static_url_path + "/<path:filename>",
|
||||
f"{self.static_url_path}/<path:filename>",
|
||||
endpoint="static",
|
||||
host=static_host,
|
||||
view_func=self.send_static_file,
|
||||
|
|
@ -719,7 +711,7 @@ class Flask(_PackageBoundObject):
|
|||
prefix, package_path = find_package(self.import_name)
|
||||
if prefix is None:
|
||||
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"):
|
||||
"""Opens a resource from the application's instance folder
|
||||
|
|
@ -1068,70 +1060,6 @@ class Flask(_PackageBoundObject):
|
|||
|
||||
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
|
||||
def register_blueprint(self, blueprint, **options):
|
||||
"""Register a :class:`~flask.Blueprint` on the application. Keyword
|
||||
|
|
@ -1156,10 +1084,11 @@ class Flask(_PackageBoundObject):
|
|||
|
||||
if blueprint.name in self.blueprints:
|
||||
assert self.blueprints[blueprint.name] is blueprint, (
|
||||
"A name collision occurred between blueprints %r and %r. Both"
|
||||
' share the same name "%s". Blueprints that are created on the'
|
||||
" fly need unique names."
|
||||
% (blueprint, self.blueprints[blueprint.name], blueprint.name)
|
||||
"A name collision occurred between blueprints"
|
||||
f" {blueprint!r} and {self.blueprints[blueprint.name]!r}."
|
||||
f" Both share the same name {blueprint.name!r}."
|
||||
f" Blueprints that are created on the fly need unique"
|
||||
f" names."
|
||||
)
|
||||
else:
|
||||
self.blueprints[blueprint.name] = blueprint
|
||||
|
|
@ -1182,7 +1111,7 @@ class Flask(_PackageBoundObject):
|
|||
endpoint=None,
|
||||
view_func=None,
|
||||
provide_automatic_options=None,
|
||||
**options
|
||||
**options,
|
||||
):
|
||||
"""Connects a URL rule. Works exactly like the :meth:`route`
|
||||
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.
|
||||
if methods is None:
|
||||
methods = getattr(view_func, "methods", None) or ("GET",)
|
||||
if isinstance(methods, string_types):
|
||||
if isinstance(methods, str):
|
||||
raise TypeError(
|
||||
"Allowed methods have to be iterables of strings, "
|
||||
'for example: @app.route(..., methods=["POST"])'
|
||||
"Allowed methods must be a list of strings, for"
|
||||
' 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
|
||||
required_methods = set(getattr(view_func, "required_methods", ()))
|
||||
|
|
@ -1281,8 +1210,8 @@ class Flask(_PackageBoundObject):
|
|||
old_func = self.view_functions.get(endpoint)
|
||||
if old_func is not None and old_func != view_func:
|
||||
raise AssertionError(
|
||||
"View function mapping is overwriting an "
|
||||
"existing endpoint function: %s" % endpoint
|
||||
"View function mapping is overwriting an existing"
|
||||
f" endpoint function: {endpoint}"
|
||||
)
|
||||
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
|
||||
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]
|
||||
else:
|
||||
exc_class = exc_class_or_code
|
||||
|
|
@ -1413,17 +1342,18 @@ class Flask(_PackageBoundObject):
|
|||
"""
|
||||
if isinstance(code_or_exception, HTTPException): # old broken behavior
|
||||
raise ValueError(
|
||||
"Tried to register a handler for an exception instance {0!r}."
|
||||
" Handlers can only be registered for exception classes or"
|
||||
" HTTP error codes.".format(code_or_exception)
|
||||
"Tried to register a handler for an exception instance"
|
||||
f" {code_or_exception!r}. Handlers can only be"
|
||||
" registered for exception classes or HTTP error codes."
|
||||
)
|
||||
|
||||
try:
|
||||
exc_class, code = self._get_exc_class_and_code(code_or_exception)
|
||||
except KeyError:
|
||||
raise KeyError(
|
||||
"'{0}' is not a recognized HTTP error code. Use a subclass of"
|
||||
" HTTPException with that code instead.".format(code_or_exception)
|
||||
f"'{code_or_exception}' is not a recognized HTTP error"
|
||||
" code. Use a subclass of HTTPException with that code"
|
||||
" instead."
|
||||
)
|
||||
|
||||
handlers = self.error_handler_spec.setdefault(key, {}).setdefault(code, {})
|
||||
|
|
@ -1794,13 +1724,6 @@ class Flask(_PackageBoundObject):
|
|||
|
||||
.. 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 self.debug or self.config["TRAP_BAD_REQUEST_ERRORS"]:
|
||||
e.show_exception = True
|
||||
|
|
@ -1809,7 +1732,7 @@ class Flask(_PackageBoundObject):
|
|||
# message, add it in manually.
|
||||
# TODO: clean up once Werkzeug >= 0.15.5 is required
|
||||
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"):
|
||||
e.args = ()
|
||||
|
||||
|
|
@ -1819,7 +1742,8 @@ class Flask(_PackageBoundObject):
|
|||
handler = self._find_error_handler(e)
|
||||
|
||||
if handler is None:
|
||||
reraise(exc_type, exc_value, tb)
|
||||
raise
|
||||
|
||||
return handler(e)
|
||||
|
||||
def handle_exception(self, e):
|
||||
|
|
@ -1856,20 +1780,18 @@ class Flask(_PackageBoundObject):
|
|||
|
||||
.. versionadded:: 0.3
|
||||
"""
|
||||
exc_type, exc_value, tb = sys.exc_info()
|
||||
exc_info = sys.exc_info()
|
||||
got_request_exception.send(self, exception=e)
|
||||
|
||||
if self.propagate_exceptions:
|
||||
# if we want to repropagate the exception, we can attempt to
|
||||
# raise it with the whole traceback in case we can do that
|
||||
# (the function was actually called from the except part)
|
||||
# otherwise, we just raise the error again
|
||||
if exc_value is e:
|
||||
reraise(exc_type, exc_value, tb)
|
||||
else:
|
||||
raise e
|
||||
# Re-raise if called with an active exception, otherwise
|
||||
# raise the passed in exception.
|
||||
if exc_info[1] is e:
|
||||
raise
|
||||
|
||||
self.log_exception((exc_type, exc_value, tb))
|
||||
raise e
|
||||
|
||||
self.log_exception(exc_info)
|
||||
server_error = InternalServerError()
|
||||
# TODO: pass as param when Werkzeug>=1.0.0 is required
|
||||
# TODO: also remove note about this from docstring and docs
|
||||
|
|
@ -1890,7 +1812,7 @@ class Flask(_PackageBoundObject):
|
|||
.. versionadded:: 0.8
|
||||
"""
|
||||
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):
|
||||
|
|
@ -2026,11 +1948,11 @@ class Flask(_PackageBoundObject):
|
|||
without returning, is not allowed. The following types are allowed
|
||||
for ``view_rv``:
|
||||
|
||||
``str`` (``unicode`` in Python 2)
|
||||
``str``
|
||||
A response object is created with the string encoded to UTF-8
|
||||
as the body.
|
||||
|
||||
``bytes`` (``str`` in Python 2)
|
||||
``bytes``
|
||||
A response object is created with the bytes as the body.
|
||||
|
||||
``dict``
|
||||
|
|
@ -2086,14 +2008,14 @@ class Flask(_PackageBoundObject):
|
|||
# the body must not be None
|
||||
if rv is None:
|
||||
raise TypeError(
|
||||
'The view function for "{}" did not return a valid response. The'
|
||||
" function either returned None or ended without a return"
|
||||
" statement.".format(request.endpoint)
|
||||
f"The view function for {request.endpoint!r} did not"
|
||||
" return a valid response. The function either returned"
|
||||
" None or ended without a return statement."
|
||||
)
|
||||
|
||||
# make sure the body is an instance of the 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
|
||||
# waiting to do it manually, so that the class can handle any
|
||||
# special logic
|
||||
|
|
@ -2107,24 +2029,23 @@ class Flask(_PackageBoundObject):
|
|||
try:
|
||||
rv = self.response_class.force_type(rv, request.environ)
|
||||
except TypeError as e:
|
||||
new_error = TypeError(
|
||||
"{e}\nThe view function did not return a valid"
|
||||
" response. The return type must be a string, dict, tuple,"
|
||||
" Response instance, or WSGI callable, but it was a"
|
||||
" {rv.__class__.__name__}.".format(e=e, rv=rv)
|
||||
)
|
||||
reraise(TypeError, new_error, sys.exc_info()[2])
|
||||
raise TypeError(
|
||||
f"{e}\nThe view function did not return a valid"
|
||||
" response. The return type must be a string,"
|
||||
" dict, tuple, Response instance, or WSGI"
|
||||
f" callable, but it was a {type(rv).__name__}."
|
||||
).with_traceback(sys.exc_info()[2])
|
||||
else:
|
||||
raise TypeError(
|
||||
"The view function did not return a valid"
|
||||
" response. The return type must be a string, dict, tuple,"
|
||||
" Response instance, or WSGI callable, but it was a"
|
||||
" {rv.__class__.__name__}.".format(rv=rv)
|
||||
" response. The return type must be a string,"
|
||||
" dict, tuple, Response instance, or WSGI"
|
||||
f" callable, but it was a {type(rv).__name__}."
|
||||
)
|
||||
|
||||
# prefer the status if it was provided
|
||||
if status is not None:
|
||||
if isinstance(status, (text_type, bytes, bytearray)):
|
||||
if isinstance(status, (str, bytes, bytearray)):
|
||||
rv.status = status
|
||||
else:
|
||||
rv.status_code = status
|
||||
|
|
@ -2188,23 +2109,24 @@ class Flask(_PackageBoundObject):
|
|||
func(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:
|
||||
try:
|
||||
rv = handler(error, endpoint, values)
|
||||
except BuildError as e:
|
||||
# make error available outside except block
|
||||
error = e
|
||||
else:
|
||||
if rv is not None:
|
||||
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
|
||||
# still the same one we can reraise it with the original traceback,
|
||||
# otherwise we raise it from here.
|
||||
if error is exc_value:
|
||||
reraise(exc_type, exc_value, tb)
|
||||
# Re-raise if called with an active exception, otherwise raise
|
||||
# the passed in exception.
|
||||
if error is sys.exc_info()[1]:
|
||||
raise
|
||||
|
||||
raise error
|
||||
|
||||
def preprocess_request(self):
|
||||
|
|
@ -2455,4 +2377,4 @@ class Flask(_PackageBoundObject):
|
|||
return self.wsgi_app(environ, start_response)
|
||||
|
||||
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
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
|
@ -18,7 +17,7 @@ from .helpers import _PackageBoundObject
|
|||
_sentinel = object()
|
||||
|
||||
|
||||
class BlueprintSetupState(object):
|
||||
class BlueprintSetupState:
|
||||
"""Temporary holder object for registering a blueprint with the
|
||||
application. An instance of this class is created by the
|
||||
:meth:`~flask.Blueprint.make_setup_state` method and later passed
|
||||
|
|
@ -80,10 +79,10 @@ class BlueprintSetupState(object):
|
|||
defaults = dict(defaults, **options.pop("defaults"))
|
||||
self.app.add_url_rule(
|
||||
rule,
|
||||
"%s.%s" % (self.blueprint.name, endpoint),
|
||||
f"{self.blueprint.name}.{endpoint}",
|
||||
view_func,
|
||||
defaults=defaults,
|
||||
**options
|
||||
**options,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -247,7 +246,7 @@ class Blueprint(_PackageBoundObject):
|
|||
|
||||
if self.has_static_folder:
|
||||
state.add_url_rule(
|
||||
self.static_url_path + "/<path:filename>",
|
||||
f"{self.static_url_path}/<path:filename>",
|
||||
view_func=self.send_static_file,
|
||||
endpoint="static",
|
||||
)
|
||||
|
|
|
|||
123
src/flask/cli.py
123
src/flask/cli.py
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flask.cli
|
||||
~~~~~~~~~
|
||||
|
|
@ -8,8 +7,6 @@
|
|||
:copyright: 2010 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
||||
from __future__ import print_function
|
||||
|
||||
import ast
|
||||
import inspect
|
||||
import os
|
||||
|
|
@ -25,10 +22,6 @@ from threading import Thread
|
|||
import click
|
||||
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 .helpers import get_debug_flag
|
||||
from .helpers import get_env
|
||||
|
|
@ -63,19 +56,19 @@ def find_best_app(script_info, module):
|
|||
return app
|
||||
|
||||
# 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:
|
||||
return matches[0]
|
||||
elif len(matches) > 1:
|
||||
raise NoAppException(
|
||||
'Detected multiple Flask applications in module "{module}". Use '
|
||||
'"FLASK_APP={module}:name" to specify the correct '
|
||||
"one.".format(module=module.__name__)
|
||||
"Detected multiple Flask applications in module"
|
||||
f" {module.__name__!r}. Use 'FLASK_APP={module.__name__}:name'"
|
||||
f" to specify the correct one."
|
||||
)
|
||||
|
||||
# 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)
|
||||
|
||||
if inspect.isfunction(app_factory):
|
||||
|
|
@ -88,15 +81,16 @@ def find_best_app(script_info, module):
|
|||
if not _called_with_wrong_args(app_factory):
|
||||
raise
|
||||
raise NoAppException(
|
||||
'Detected factory "{factory}" in module "{module}", but '
|
||||
"could not call it without arguments. Use "
|
||||
"\"FLASK_APP='{module}:{factory}(args)'\" to specify "
|
||||
"arguments.".format(factory=attr_name, module=module.__name__)
|
||||
f"Detected factory {attr_name!r} in module {module.__name__!r},"
|
||||
" but could not call it without arguments. Use"
|
||||
f" \"FLASK_APP='{module.__name__}:{attr_name}(args)'\""
|
||||
" to specify arguments."
|
||||
)
|
||||
|
||||
raise NoAppException(
|
||||
'Failed to find Flask application or factory in module "{module}". '
|
||||
'Use "FLASK_APP={module}:name to specify one.'.format(module=module.__name__)
|
||||
"Failed to find Flask application or factory in module"
|
||||
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
|
||||
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_defaults = args_spec.defaults
|
||||
|
||||
|
|
@ -157,8 +151,7 @@ def find_app_by_string(script_info, module, app_name):
|
|||
|
||||
if not match:
|
||||
raise NoAppException(
|
||||
'"{name}" is not a valid variable name or function '
|
||||
"expression.".format(name=app_name)
|
||||
f"{app_name!r} is not a valid variable name or function expression."
|
||||
)
|
||||
|
||||
name, args = match.groups()
|
||||
|
|
@ -171,12 +164,9 @@ def find_app_by_string(script_info, module, app_name):
|
|||
if inspect.isfunction(attr):
|
||||
if args:
|
||||
try:
|
||||
args = ast.literal_eval("({args},)".format(args=args))
|
||||
except (ValueError, SyntaxError) as e:
|
||||
raise NoAppException(
|
||||
"Could not parse the arguments in "
|
||||
'"{app_name}".'.format(e=e, app_name=app_name)
|
||||
)
|
||||
args = ast.literal_eval(f"({args},)")
|
||||
except (ValueError, SyntaxError):
|
||||
raise NoAppException(f"Could not parse the arguments in {app_name!r}.")
|
||||
else:
|
||||
args = ()
|
||||
|
||||
|
|
@ -187,10 +177,9 @@ def find_app_by_string(script_info, module, app_name):
|
|||
raise
|
||||
|
||||
raise NoAppException(
|
||||
'{e}\nThe factory "{app_name}" in module "{module}" could not '
|
||||
"be called with the specified arguments.".format(
|
||||
e=e, app_name=app_name, module=module.__name__
|
||||
)
|
||||
f"{e}\nThe factory {app_name!r} in module"
|
||||
f" {module.__name__!r} could not be called with the"
|
||||
" specified arguments."
|
||||
)
|
||||
else:
|
||||
app = attr
|
||||
|
|
@ -199,8 +188,8 @@ def find_app_by_string(script_info, module, app_name):
|
|||
return app
|
||||
|
||||
raise NoAppException(
|
||||
"A valid Flask application was not obtained from "
|
||||
'"{module}:{app_name}".'.format(module=module.__name__, app_name=app_name)
|
||||
"A valid Flask application was not obtained from"
|
||||
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:
|
||||
# Reraise the ImportError if it occurred within the imported module.
|
||||
# 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(
|
||||
'While importing "{name}", an ImportError was raised:'
|
||||
"\n\n{tb}".format(name=module_name, tb=traceback.format_exc())
|
||||
f"While importing {module_name!r}, an ImportError was"
|
||||
f" raised:\n\n{traceback.format_exc()}"
|
||||
)
|
||||
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:
|
||||
return
|
||||
|
||||
|
|
@ -266,14 +255,10 @@ def get_version(ctx, param, value):
|
|||
import werkzeug
|
||||
from . import __version__
|
||||
|
||||
message = "Python %(python)s\nFlask %(flask)s\nWerkzeug %(werkzeug)s"
|
||||
click.echo(
|
||||
message
|
||||
% {
|
||||
"python": platform.python_version(),
|
||||
"flask": __version__,
|
||||
"werkzeug": werkzeug.__version__,
|
||||
},
|
||||
f"Python {platform.python_version()}\n"
|
||||
f"Flask {__version__}\n"
|
||||
f"Werkzeug {werkzeug.__version__}",
|
||||
color=ctx.color,
|
||||
)
|
||||
ctx.exit()
|
||||
|
|
@ -289,7 +274,7 @@ version_option = click.Option(
|
|||
)
|
||||
|
||||
|
||||
class DispatchingApp(object):
|
||||
class DispatchingApp:
|
||||
"""Special application that dispatches to a Flask application which
|
||||
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
|
||||
|
|
@ -327,7 +312,7 @@ class DispatchingApp(object):
|
|||
exc_info = self._bg_loading_exc_info
|
||||
if exc_info is not None:
|
||||
self._bg_loading_exc_info = None
|
||||
reraise(*exc_info)
|
||||
raise exc_info
|
||||
|
||||
def _load_unlocked(self):
|
||||
__traceback_hide__ = True # noqa: F841
|
||||
|
|
@ -348,7 +333,7 @@ class DispatchingApp(object):
|
|||
return rv(environ, start_response)
|
||||
|
||||
|
||||
class ScriptInfo(object):
|
||||
class ScriptInfo:
|
||||
"""Helper object to deal with Flask applications. This is usually not
|
||||
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
|
||||
|
|
@ -495,7 +480,7 @@ class FlaskGroup(AppGroup):
|
|||
add_version_option=True,
|
||||
load_dotenv=True,
|
||||
set_debug_flag=True,
|
||||
**extra
|
||||
**extra,
|
||||
):
|
||||
params = list(extra.pop("params", None) or ())
|
||||
|
||||
|
|
@ -587,7 +572,7 @@ class FlaskGroup(AppGroup):
|
|||
|
||||
kwargs["obj"] = obj
|
||||
kwargs.setdefault("auto_envvar_prefix", "FLASK")
|
||||
return super(FlaskGroup, self).main(*args, **kwargs)
|
||||
return super().main(*args, **kwargs)
|
||||
|
||||
|
||||
def _path_is_ancestor(path, other):
|
||||
|
|
@ -666,25 +651,25 @@ def show_server_banner(env, debug, app_import_path, eager_loading):
|
|||
return
|
||||
|
||||
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:
|
||||
message += " (lazy loading)"
|
||||
|
||||
click.echo(message)
|
||||
|
||||
click.echo(" * Environment: {0}".format(env))
|
||||
click.echo(f" * Environment: {env}")
|
||||
|
||||
if env == "production":
|
||||
click.secho(
|
||||
" WARNING: This is a development server. "
|
||||
"Do not use it in a production deployment.",
|
||||
" WARNING: This is a development server. Do not use it in"
|
||||
" a production deployment.",
|
||||
fg="red",
|
||||
)
|
||||
click.secho(" Use a production WSGI server instead.", dim=True)
|
||||
|
||||
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):
|
||||
|
|
@ -725,12 +710,8 @@ class CertParamType(click.ParamType):
|
|||
|
||||
obj = import_string(value, silent=True)
|
||||
|
||||
if sys.version_info < (2, 7, 9):
|
||||
if obj:
|
||||
return obj
|
||||
else:
|
||||
if isinstance(obj, ssl.SSLContext):
|
||||
return obj
|
||||
if isinstance(obj, ssl.SSLContext):
|
||||
return obj
|
||||
|
||||
raise
|
||||
|
||||
|
|
@ -741,11 +722,7 @@ def _validate_key(ctx, param, value):
|
|||
"""
|
||||
cert = ctx.params.get("cert")
|
||||
is_adhoc = cert == "adhoc"
|
||||
|
||||
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)
|
||||
is_context = ssl and isinstance(cert, ssl.SSLContext)
|
||||
|
||||
if value is not None:
|
||||
if is_adhoc:
|
||||
|
|
@ -778,7 +755,7 @@ class SeparatedPathType(click.Path):
|
|||
|
||||
def convert(self, value, param, ctx):
|
||||
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]
|
||||
|
||||
|
||||
|
|
@ -824,7 +801,7 @@ class SeparatedPathType(click.Path):
|
|||
type=SeparatedPathType(),
|
||||
help=(
|
||||
"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
|
||||
|
|
@ -878,12 +855,10 @@ def shell_command():
|
|||
from .globals import _app_ctx_stack
|
||||
|
||||
app = _app_ctx_stack.top.app
|
||||
banner = "Python %s on %s\nApp: %s [%s]\nInstance: %s" % (
|
||||
sys.version,
|
||||
sys.platform,
|
||||
app.import_name,
|
||||
app.env,
|
||||
app.instance_path,
|
||||
banner = (
|
||||
f"Python {sys.version} on {sys.platform}\n"
|
||||
f"App: {app.import_name} [{app.env}]\n"
|
||||
f"Instance: {app.instance_path}"
|
||||
)
|
||||
ctx = {}
|
||||
|
||||
|
|
@ -891,7 +866,7 @@ def shell_command():
|
|||
# is using it.
|
||||
startup = os.environ.get("PYTHONSTARTUP")
|
||||
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)
|
||||
|
||||
ctx.update(app.make_shell_context())
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flask.config
|
||||
~~~~~~~~~~~~
|
||||
|
|
@ -11,15 +10,11 @@
|
|||
import errno
|
||||
import os
|
||||
import types
|
||||
import warnings
|
||||
|
||||
from werkzeug.utils import import_string
|
||||
|
||||
from ._compat import iteritems
|
||||
from ._compat import string_types
|
||||
|
||||
|
||||
class ConfigAttribute(object):
|
||||
class ConfigAttribute:
|
||||
"""Makes an attribute forward to the config"""
|
||||
|
||||
def __init__(self, name, get_converter=None):
|
||||
|
|
@ -103,10 +98,10 @@ class Config(dict):
|
|||
if silent:
|
||||
return False
|
||||
raise RuntimeError(
|
||||
"The environment variable %r is not set "
|
||||
"and as such configuration could not be "
|
||||
"loaded. Set this variable and make it "
|
||||
"point to a configuration file" % variable_name
|
||||
f"The environment variable {variable_name!r} is not set"
|
||||
" and as such configuration could not be loaded. Set"
|
||||
" this variable and make it point to a configuration"
|
||||
" file"
|
||||
)
|
||||
return self.from_pyfile(rv, silent=silent)
|
||||
|
||||
|
|
@ -130,10 +125,10 @@ class Config(dict):
|
|||
try:
|
||||
with open(filename, mode="rb") as config_file:
|
||||
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):
|
||||
return False
|
||||
e.strerror = "Unable to load configuration file (%s)" % e.strerror
|
||||
e.strerror = f"Unable to load configuration file ({e.strerror})"
|
||||
raise
|
||||
self.from_object(d)
|
||||
return True
|
||||
|
|
@ -170,7 +165,7 @@ class Config(dict):
|
|||
|
||||
:param obj: an import name or object
|
||||
"""
|
||||
if isinstance(obj, string_types):
|
||||
if isinstance(obj, str):
|
||||
obj = import_string(obj)
|
||||
for key in dir(obj):
|
||||
if key.isupper():
|
||||
|
|
@ -201,38 +196,15 @@ class Config(dict):
|
|||
try:
|
||||
with open(filename) as f:
|
||||
obj = load(f)
|
||||
except IOError as e:
|
||||
except OSError as e:
|
||||
if silent and e.errno in (errno.ENOENT, errno.EISDIR):
|
||||
return False
|
||||
|
||||
e.strerror = "Unable to load configuration file (%s)" % e.strerror
|
||||
e.strerror = f"Unable to load configuration file ({e.strerror})"
|
||||
raise
|
||||
|
||||
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):
|
||||
"""Updates the config like :meth:`update` ignoring items with non-upper
|
||||
keys.
|
||||
|
|
@ -247,7 +219,7 @@ class Config(dict):
|
|||
mappings.append(mapping[0])
|
||||
elif len(mapping) > 1:
|
||||
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())
|
||||
for mapping in mappings:
|
||||
|
|
@ -285,7 +257,7 @@ class Config(dict):
|
|||
.. versionadded:: 0.11
|
||||
"""
|
||||
rv = {}
|
||||
for k, v in iteritems(self):
|
||||
for k, v in self.items():
|
||||
if not k.startswith(namespace):
|
||||
continue
|
||||
if trim_namespace:
|
||||
|
|
@ -298,4 +270,4 @@ class Config(dict):
|
|||
return rv
|
||||
|
||||
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
|
||||
~~~~~~~~~
|
||||
|
|
@ -13,8 +12,6 @@ from functools import update_wrapper
|
|||
|
||||
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 _request_ctx_stack
|
||||
from .signals import appcontext_popped
|
||||
|
|
@ -25,7 +22,7 @@ from .signals import appcontext_pushed
|
|||
_sentinel = object()
|
||||
|
||||
|
||||
class _AppCtxGlobals(object):
|
||||
class _AppCtxGlobals:
|
||||
"""A plain object. Used as a namespace for storing data during an
|
||||
application context.
|
||||
|
||||
|
|
@ -91,7 +88,7 @@ class _AppCtxGlobals(object):
|
|||
def __repr__(self):
|
||||
top = _app_ctx_stack.top
|
||||
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)
|
||||
|
||||
|
||||
|
|
@ -202,7 +199,7 @@ def has_app_context():
|
|||
return _app_ctx_stack.top is not None
|
||||
|
||||
|
||||
class AppContext(object):
|
||||
class AppContext:
|
||||
"""The application context binds an application object implicitly
|
||||
to the current thread or greenlet, similar to how the
|
||||
:class:`RequestContext` binds request information. The application
|
||||
|
|
@ -223,8 +220,6 @@ class AppContext(object):
|
|||
def push(self):
|
||||
"""Binds the app context to the current context."""
|
||||
self._refcnt += 1
|
||||
if hasattr(sys, "exc_clear"):
|
||||
sys.exc_clear()
|
||||
_app_ctx_stack.push(self)
|
||||
appcontext_pushed.send(self.app)
|
||||
|
||||
|
|
@ -238,7 +233,7 @@ class AppContext(object):
|
|||
self.app.do_teardown_appcontext(exc)
|
||||
finally:
|
||||
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)
|
||||
|
||||
def __enter__(self):
|
||||
|
|
@ -248,11 +243,8 @@ class AppContext(object):
|
|||
def __exit__(self, exc_type, exc_value, tb):
|
||||
self.pop(exc_value)
|
||||
|
||||
if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:
|
||||
reraise(exc_type, exc_value, tb)
|
||||
|
||||
|
||||
class RequestContext(object):
|
||||
class RequestContext:
|
||||
"""The request context contains all request relevant information. It is
|
||||
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
|
||||
|
|
@ -376,9 +368,6 @@ class RequestContext(object):
|
|||
else:
|
||||
self._implicit_app_ctx_stack.append(None)
|
||||
|
||||
if hasattr(sys, "exc_clear"):
|
||||
sys.exc_clear()
|
||||
|
||||
_request_ctx_stack.push(self)
|
||||
|
||||
# Open the session at the moment that the request context is available.
|
||||
|
|
@ -404,9 +393,9 @@ class RequestContext(object):
|
|||
Added the `exc` argument.
|
||||
"""
|
||||
app_ctx = self._implicit_app_ctx_stack.pop()
|
||||
clear_request = False
|
||||
|
||||
try:
|
||||
clear_request = False
|
||||
if not self._implicit_app_ctx_stack:
|
||||
self.preserved = False
|
||||
self._preserved_exc = None
|
||||
|
|
@ -414,13 +403,6 @@ class RequestContext(object):
|
|||
exc = sys.exc_info()[1]
|
||||
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)
|
||||
if request_close is not None:
|
||||
request_close()
|
||||
|
|
@ -437,10 +419,9 @@ class RequestContext(object):
|
|||
if app_ctx is not None:
|
||||
app_ctx.pop(exc)
|
||||
|
||||
assert rv is self, "Popped wrong request context. (%r instead of %r)" % (
|
||||
rv,
|
||||
self,
|
||||
)
|
||||
assert (
|
||||
rv is self
|
||||
), f"Popped wrong request context. ({rv!r} instead of {self!r})"
|
||||
|
||||
def auto_pop(self, exc):
|
||||
if self.request.environ.get("flask._preserve_context") or (
|
||||
|
|
@ -463,13 +444,8 @@ class RequestContext(object):
|
|||
# See flask.testing for how this works.
|
||||
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):
|
||||
return "<%s '%s' [%s] of %s>" % (
|
||||
self.__class__.__name__,
|
||||
self.request.url,
|
||||
self.request.method,
|
||||
self.app.name,
|
||||
return (
|
||||
f"<{type(self).__name__} {self.request.url!r}"
|
||||
f" [{self.request.method}] of {self.app.name}>"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flask.debughelpers
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
|
@ -11,8 +10,6 @@
|
|||
import os
|
||||
from warnings import warn
|
||||
|
||||
from ._compat import implements_to_string
|
||||
from ._compat import text_type
|
||||
from .app import Flask
|
||||
from .blueprints import Blueprint
|
||||
from .globals import _request_ctx_stack
|
||||
|
|
@ -24,7 +21,6 @@ class UnexpectedUnicodeError(AssertionError, UnicodeError):
|
|||
"""
|
||||
|
||||
|
||||
@implements_to_string
|
||||
class DebugFilesKeyError(KeyError, AssertionError):
|
||||
"""Raised from request.files during debugging. The idea is that it can
|
||||
provide a better error message than just a generic KeyError/BadRequest.
|
||||
|
|
@ -33,17 +29,18 @@ class DebugFilesKeyError(KeyError, AssertionError):
|
|||
def __init__(self, request, key):
|
||||
form_matches = request.form.getlist(key)
|
||||
buf = [
|
||||
'You tried to access the file "%s" in the request.files '
|
||||
"dictionary but it does not exist. The mimetype for the request "
|
||||
'is "%s" instead of "multipart/form-data" which means that no '
|
||||
"file contents were transmitted. To fix this error you should "
|
||||
'provide enctype="multipart/form-data" in your form.'
|
||||
% (key, request.mimetype)
|
||||
f"You tried to access the file {key!r} in the request.files"
|
||||
" dictionary but it does not exist. The mimetype for the"
|
||||
f" request is {request.mimetype!r} instead of"
|
||||
" 'multipart/form-data' which means that no file contents"
|
||||
" were transmitted. To fix this error you should provide"
|
||||
' enctype="multipart/form-data" in your form.'
|
||||
]
|
||||
if form_matches:
|
||||
names = ", ".join(repr(x) for x in form_matches)
|
||||
buf.append(
|
||||
"\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)
|
||||
|
||||
|
|
@ -60,24 +57,24 @@ class FormDataRoutingRedirect(AssertionError):
|
|||
def __init__(self, request):
|
||||
exc = request.routing_exception
|
||||
buf = [
|
||||
"A request was sent to this URL (%s) but a redirect was "
|
||||
'issued automatically by the routing system to "%s".'
|
||||
% (request.url, exc.new_url)
|
||||
f"A request was sent to this URL ({request.url}) but a"
|
||||
" redirect was issued automatically by the routing system"
|
||||
f" to {exc.new_url!r}."
|
||||
]
|
||||
|
||||
# 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(
|
||||
" The URL was defined with a trailing slash so "
|
||||
"Flask will automatically redirect to the URL "
|
||||
"with the trailing slash if it was accessed "
|
||||
"without one."
|
||||
" The URL was defined with a trailing slash so Flask"
|
||||
" will automatically redirect to the URL with the"
|
||||
" trailing slash if it was accessed without one."
|
||||
)
|
||||
|
||||
buf.append(
|
||||
" Make sure to directly send your %s-request to this URL "
|
||||
"since we can't make browsers or HTTP clients redirect "
|
||||
"with form data reliably or without user interaction." % request.method
|
||||
" Make sure to directly send your"
|
||||
f" {request.method}-request to this URL since we can't make"
|
||||
" 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")
|
||||
AssertionError.__init__(self, "".join(buf).encode("utf-8"))
|
||||
|
|
@ -105,25 +102,25 @@ def attach_enctype_error_multidict(request):
|
|||
|
||||
|
||||
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()):
|
||||
if key.startswith("_"):
|
||||
continue
|
||||
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
|
||||
yield "%s:" % key
|
||||
yield f"{key}:"
|
||||
for item in value:
|
||||
yield " - %s" % item
|
||||
yield f" - {item}"
|
||||
continue
|
||||
elif not isinstance(value, (str, text_type, int, float, bool)):
|
||||
elif not isinstance(value, (str, int, float, bool)):
|
||||
continue
|
||||
yield "%s: %r" % (key, value)
|
||||
yield f"{key}: {value!r}"
|
||||
|
||||
|
||||
def explain_template_loading_attempts(app, template, attempts):
|
||||
"""This should help developers understand what failed"""
|
||||
info = ['Locating template "%s":' % template]
|
||||
info = [f"Locating template {template!r}:"]
|
||||
total_found = 0
|
||||
blueprint = None
|
||||
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):
|
||||
if isinstance(srcobj, Flask):
|
||||
src_info = 'application "%s"' % srcobj.import_name
|
||||
src_info = f"application {srcobj.import_name!r}"
|
||||
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:
|
||||
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):
|
||||
info.append(" %s" % line)
|
||||
info.append(f" {line}")
|
||||
|
||||
if triple is None:
|
||||
detail = "no match"
|
||||
else:
|
||||
detail = "found (%r)" % (triple[1] or "<string>")
|
||||
detail = f"found ({triple[1] or '<string>'!r})"
|
||||
total_found += 1
|
||||
info.append(" -> %s" % detail)
|
||||
info.append(f" -> {detail}")
|
||||
|
||||
seems_fishy = False
|
||||
if total_found == 0:
|
||||
|
|
@ -160,8 +157,8 @@ def explain_template_loading_attempts(app, template, attempts):
|
|||
|
||||
if blueprint is not None and seems_fishy:
|
||||
info.append(
|
||||
" The template was looked up from an endpoint that "
|
||||
'belongs to the blueprint "%s".' % blueprint
|
||||
" The template was looked up from an endpoint that belongs"
|
||||
f" to the blueprint {blueprint!r}."
|
||||
)
|
||||
info.append(" Maybe you did not place a template in the right folder?")
|
||||
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":
|
||||
warn(
|
||||
Warning(
|
||||
"Silently ignoring app.run() because the "
|
||||
"application is run from the flask command line "
|
||||
"executable. Consider putting app.run() behind an "
|
||||
'if __name__ == "__main__" guard to silence this '
|
||||
"warning."
|
||||
"Silently ignoring app.run() because the application is"
|
||||
" run from the flask command line executable. Consider"
|
||||
' putting app.run() behind an if __name__ == "__main__"'
|
||||
" guard to silence this warning."
|
||||
),
|
||||
stacklevel=3,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flask.globals
|
||||
~~~~~~~~~~~~~
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flask.helpers
|
||||
~~~~~~~~~~~~~
|
||||
|
|
@ -30,10 +29,6 @@ from werkzeug.routing import BuildError
|
|||
from werkzeug.urls import url_quote
|
||||
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 _request_ctx_stack
|
||||
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
|
||||
# automatically.
|
||||
try:
|
||||
for item in gen:
|
||||
yield item
|
||||
yield from gen
|
||||
finally:
|
||||
if hasattr(gen, "close"):
|
||||
gen.close()
|
||||
|
|
@ -317,7 +311,7 @@ def url_for(endpoint, **values):
|
|||
|
||||
if endpoint[:1] == ".":
|
||||
if blueprint_name is not None:
|
||||
endpoint = blueprint_name + endpoint
|
||||
endpoint = f"{blueprint_name}{endpoint}"
|
||||
else:
|
||||
endpoint = endpoint[1:]
|
||||
|
||||
|
|
@ -370,7 +364,7 @@ def url_for(endpoint, **values):
|
|||
return appctx.app.handle_url_build_error(error, endpoint, values)
|
||||
|
||||
if anchor is not None:
|
||||
rv += "#" + url_quote(anchor)
|
||||
rv += f"#{url_quote(anchor)}"
|
||||
return rv
|
||||
|
||||
|
||||
|
|
@ -576,9 +570,9 @@ def send_file(
|
|||
fsize = None
|
||||
|
||||
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
|
||||
if not os.path.isabs(filename):
|
||||
filename = os.path.join(current_app.root_path, filename)
|
||||
|
|
@ -608,17 +602,18 @@ def send_file(
|
|||
if attachment_filename is None:
|
||||
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")
|
||||
|
||||
try:
|
||||
attachment_filename = attachment_filename.encode("ascii")
|
||||
except UnicodeEncodeError:
|
||||
quoted = url_quote(attachment_filename, safe="")
|
||||
filenames = {
|
||||
"filename": unicodedata.normalize("NFKD", attachment_filename).encode(
|
||||
"ascii", "ignore"
|
||||
),
|
||||
"filename*": "UTF-8''%s" % url_quote(attachment_filename, safe=b""),
|
||||
"filename*": f"UTF-8''{quoted}",
|
||||
}
|
||||
else:
|
||||
filenames = {"filename": attachment_filename}
|
||||
|
|
@ -638,11 +633,7 @@ def send_file(
|
|||
mtime = os.path.getmtime(filename)
|
||||
fsize = os.path.getsize(filename)
|
||||
elif isinstance(file, io.BytesIO):
|
||||
try:
|
||||
fsize = file.getbuffer().nbytes
|
||||
except AttributeError:
|
||||
# Python 2 doesn't have getbuffer
|
||||
fsize = len(file.getvalue())
|
||||
fsize = file.getbuffer().nbytes
|
||||
elif isinstance(file, io.TextIOBase):
|
||||
raise ValueError("Files must be opened in binary mode or use BytesIO.")
|
||||
|
||||
|
|
@ -671,23 +662,19 @@ def send_file(
|
|||
from warnings import warn
|
||||
|
||||
try:
|
||||
rv.set_etag(
|
||||
"%s-%s-%s"
|
||||
% (
|
||||
os.path.getmtime(filename),
|
||||
os.path.getsize(filename),
|
||||
adler32(
|
||||
filename.encode("utf-8")
|
||||
if isinstance(filename, text_type)
|
||||
else filename
|
||||
)
|
||||
& 0xFFFFFFFF,
|
||||
check = (
|
||||
adler32(
|
||||
filename.encode("utf-8") if isinstance(filename, str) else filename
|
||||
)
|
||||
& 0xFFFFFFFF
|
||||
)
|
||||
rv.set_etag(
|
||||
f"{os.path.getmtime(filename)}-{os.path.getsize(filename)}-{check}"
|
||||
)
|
||||
except OSError:
|
||||
warn(
|
||||
"Access %s failed, maybe it does not exist, so ignore etags in "
|
||||
"headers" % filename,
|
||||
f"Access {filename} failed, maybe it does not exist, so"
|
||||
" ignore etags in headers",
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
|
|
@ -769,8 +756,8 @@ def send_from_directory(directory, filename, **options):
|
|||
:param options: optional keyword arguments that are directly
|
||||
forwarded to :func:`send_file`.
|
||||
"""
|
||||
filename = fspath(filename)
|
||||
directory = fspath(directory)
|
||||
filename = os.fspath(filename)
|
||||
directory = os.fspath(directory)
|
||||
filename = safe_join(directory, filename)
|
||||
if not os.path.isabs(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__":
|
||||
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"):
|
||||
filepath = loader.get_filename(import_name)
|
||||
else:
|
||||
|
|
@ -818,13 +803,12 @@ def get_root_path(import_name):
|
|||
# first module that is contained in our package.
|
||||
if filepath is None:
|
||||
raise RuntimeError(
|
||||
"No root path can be found for the provided "
|
||||
'module "%s". This can happen because the '
|
||||
"module came from an import hook that does "
|
||||
"not provide file name information or because "
|
||||
"it's a namespace package. In this case "
|
||||
"the root path needs to be explicitly "
|
||||
"provided." % import_name
|
||||
"No root path can be found for the provided module"
|
||||
f" {import_name!r}. This can happen because the module"
|
||||
" came from an import hook that does not provide file"
|
||||
" name information or because it's a namespace package."
|
||||
" In this case the root path needs to be explicitly"
|
||||
" provided."
|
||||
)
|
||||
|
||||
# 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
|
||||
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
|
||||
# directly ask the loader.
|
||||
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
|
||||
# all the modules it loads are packages, so we can take advantage of
|
||||
# this information.
|
||||
elif (
|
||||
loader.__class__.__module__ == "_frozen_importlib"
|
||||
and loader.__class__.__name__ == "NamespaceLoader"
|
||||
):
|
||||
elif cls.__module__ == "_frozen_importlib" and cls.__name__ == "NamespaceLoader":
|
||||
return True
|
||||
# Otherwise we need to fail with an error that explains what went
|
||||
# wrong.
|
||||
raise AttributeError(
|
||||
(
|
||||
"%s.is_package() method is missing but is required by Flask of "
|
||||
"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__
|
||||
f"{cls.__name__}.is_package() method is missing but is required"
|
||||
" for PEP 302 import hooks."
|
||||
)
|
||||
|
||||
|
||||
def _find_package_path(root_mod_name):
|
||||
"""Find the path where the module's root exists in"""
|
||||
if sys.version_info >= (3, 4):
|
||||
import importlib.util
|
||||
import importlib.util
|
||||
|
||||
try:
|
||||
spec = importlib.util.find_spec(root_mod_name)
|
||||
if spec is None:
|
||||
raise ValueError("not found")
|
||||
# ImportError: the machinery told us it does not exist
|
||||
# ValueError:
|
||||
# - the module name was invalid
|
||||
# - the module name is __main__
|
||||
# - *we* raised `ValueError` due to `spec` being `None`
|
||||
except (ImportError, ValueError):
|
||||
pass # handled below
|
||||
try:
|
||||
spec = importlib.util.find_spec(root_mod_name)
|
||||
if spec is None:
|
||||
raise ValueError("not found")
|
||||
# ImportError: the machinery told us it does not exist
|
||||
# ValueError:
|
||||
# - the module name was invalid
|
||||
# - the module name is __main__
|
||||
# - *we* raised `ValueError` due to `spec` being `None`
|
||||
except (ImportError, ValueError):
|
||||
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:
|
||||
# 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:
|
||||
return os.path.dirname(spec.origin)
|
||||
return os.path.dirname(spec.origin)
|
||||
|
||||
# we were unable to find the `package_path` using PEP 451 loaders
|
||||
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
|
||||
return os.getcwd()
|
||||
else:
|
||||
# For .egg, zipimporter does not have get_filename until Python 2.7.
|
||||
if hasattr(loader, "get_filename"):
|
||||
filename = loader.get_filename(root_mod_name)
|
||||
elif hasattr(loader, "archive"):
|
||||
|
|
@ -909,8 +885,8 @@ def _find_package_path(root_mod_name):
|
|||
package_path = os.path.abspath(os.path.dirname(filename))
|
||||
|
||||
# In case the root module is a package we need to chop of the
|
||||
# rightmost part. This needs to go through a helper function
|
||||
# because of python 3.3 namespace packages.
|
||||
# rightmost part. This needs to go through a helper function
|
||||
# because of namespace packages.
|
||||
if _matching_loader_thinks_module_is_package(loader, root_mod_name):
|
||||
package_path = os.path.dirname(package_path)
|
||||
|
||||
|
|
@ -945,7 +921,7 @@ def find_package(import_name):
|
|||
return None, package_path
|
||||
|
||||
|
||||
class locked_cached_property(object):
|
||||
class locked_cached_property:
|
||||
"""A decorator that converts a function into a lazy property. The
|
||||
function wrapped is called the first time to retrieve the result
|
||||
and then that calculated result is used the next time you access
|
||||
|
|
@ -971,7 +947,7 @@ class locked_cached_property(object):
|
|||
return value
|
||||
|
||||
|
||||
class _PackageBoundObject(object):
|
||||
class _PackageBoundObject:
|
||||
#: The name of the package or module that this app belongs to. Do not
|
||||
#: change this once it is set by the constructor.
|
||||
import_name = None
|
||||
|
|
@ -1028,7 +1004,7 @@ class _PackageBoundObject(object):
|
|||
|
||||
if self.static_folder is not None:
|
||||
basename = os.path.basename(self.static_folder)
|
||||
return ("/" + basename).rstrip("/")
|
||||
return f"/{basename}".rstrip("/")
|
||||
|
||||
@static_url_path.setter
|
||||
def static_url_path(self, value):
|
||||
|
|
@ -1140,26 +1116,16 @@ def total_seconds(td):
|
|||
def is_ip(value):
|
||||
"""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
|
||||
:type value: str
|
||||
|
||||
:return: True if string is an IP address
|
||||
: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):
|
||||
try:
|
||||
socket.inet_pton(family, value)
|
||||
except socket.error:
|
||||
except OSError:
|
||||
pass
|
||||
else:
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flask.json
|
||||
~~~~~~~~~~
|
||||
|
|
@ -16,14 +15,13 @@ from itsdangerous import json as _json
|
|||
from jinja2 import Markup
|
||||
from werkzeug.http import http_date
|
||||
|
||||
from .._compat import PY2
|
||||
from .._compat import text_type
|
||||
from ..globals import current_app
|
||||
from ..globals import request
|
||||
|
||||
try:
|
||||
import dataclasses
|
||||
except ImportError:
|
||||
# Python < 3.7
|
||||
dataclasses = None
|
||||
|
||||
# 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):
|
||||
return dataclasses.asdict(o)
|
||||
if hasattr(o, "__html__"):
|
||||
return text_type(o.__html__())
|
||||
return str(o.__html__())
|
||||
return _json.JSONEncoder.default(self, o)
|
||||
|
||||
|
||||
|
|
@ -209,7 +207,7 @@ def dumps(obj, app=None, **kwargs):
|
|||
_dump_arg_defaults(kwargs, app=app)
|
||||
encoding = kwargs.pop("encoding", None)
|
||||
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)
|
||||
return rv
|
||||
|
||||
|
|
@ -256,8 +254,7 @@ def loads(s, app=None, **kwargs):
|
|||
def load(fp, app=None, **kwargs):
|
||||
"""Like :func:`loads` but reads from a file object."""
|
||||
_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)
|
||||
|
||||
|
||||
|
|
@ -288,10 +285,10 @@ def htmlsafe_dumps(obj, **kwargs):
|
|||
"""
|
||||
rv = (
|
||||
dumps(obj, **kwargs)
|
||||
.replace(u"<", u"\\u003c")
|
||||
.replace(u">", u"\\u003e")
|
||||
.replace(u"&", u"\\u0026")
|
||||
.replace(u"'", u"\\u0027")
|
||||
.replace("<", "\\u003c")
|
||||
.replace(">", "\\u003e")
|
||||
.replace("&", "\\u0026")
|
||||
.replace("'", "\\u0027")
|
||||
)
|
||||
if not _slash_escape:
|
||||
rv = rv.replace("\\/", "/")
|
||||
|
|
@ -300,7 +297,7 @@ def htmlsafe_dumps(obj, **kwargs):
|
|||
|
||||
def htmlsafe_dump(obj, fp, **kwargs):
|
||||
"""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):
|
||||
|
|
@ -367,7 +364,7 @@ def jsonify(*args, **kwargs):
|
|||
data = args or kwargs
|
||||
|
||||
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"],
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Tagged JSON
|
||||
~~~~~~~~~~~
|
||||
|
|
@ -50,13 +49,11 @@ from jinja2 import Markup
|
|||
from werkzeug.http import http_date
|
||||
from werkzeug.http import parse_date
|
||||
|
||||
from .._compat import iteritems
|
||||
from .._compat import text_type
|
||||
from ..json import dumps
|
||||
from ..json import loads
|
||||
|
||||
|
||||
class JSONTag(object):
|
||||
class JSONTag:
|
||||
"""Base class for defining type tags for :class:`TaggedJSONSerializer`."""
|
||||
|
||||
__slots__ = ("serializer",)
|
||||
|
|
@ -108,7 +105,7 @@ class TagDict(JSONTag):
|
|||
|
||||
def to_json(self, 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):
|
||||
key = next(iter(value))
|
||||
|
|
@ -124,7 +121,7 @@ class PassDict(JSONTag):
|
|||
def to_json(self, value):
|
||||
# JSON objects may only have string keys, so don't bother tagging the
|
||||
# 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
|
||||
|
||||
|
|
@ -181,7 +178,7 @@ class TagMarkup(JSONTag):
|
|||
return callable(getattr(value, "__html__", None))
|
||||
|
||||
def to_json(self, value):
|
||||
return text_type(value.__html__())
|
||||
return str(value.__html__())
|
||||
|
||||
def to_python(self, value):
|
||||
return Markup(value)
|
||||
|
|
@ -215,7 +212,7 @@ class TagDateTime(JSONTag):
|
|||
return parse_date(value)
|
||||
|
||||
|
||||
class TaggedJSONSerializer(object):
|
||||
class TaggedJSONSerializer:
|
||||
"""Serializer that uses a tag system to compactly represent objects that
|
||||
are not JSON types. Passed as the intermediate serializer to
|
||||
:class:`itsdangerous.Serializer`.
|
||||
|
|
@ -271,7 +268,7 @@ class TaggedJSONSerializer(object):
|
|||
|
||||
if key is not None:
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flask.logging
|
||||
~~~~~~~~~~~~~
|
||||
|
|
@ -6,11 +5,8 @@ flask.logging
|
|||
:copyright: 2010 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
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):
|
||||
"""Get the Flask app's logger and configure it if needed.
|
||||
|
||||
|
|
@ -86,20 +68,6 @@ def create_logger(app):
|
|||
"""
|
||||
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:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flask.sessions
|
||||
~~~~~~~~~~~~~~
|
||||
|
|
@ -10,19 +9,19 @@
|
|||
"""
|
||||
import hashlib
|
||||
import warnings
|
||||
from collections.abc import MutableMapping
|
||||
from datetime import datetime
|
||||
|
||||
from itsdangerous import BadSignature
|
||||
from itsdangerous import URLSafeTimedSerializer
|
||||
from werkzeug.datastructures import CallbackDict
|
||||
|
||||
from ._compat import collections_abc
|
||||
from .helpers import is_ip
|
||||
from .helpers import total_seconds
|
||||
from .json.tag import TaggedJSONSerializer
|
||||
|
||||
|
||||
class SessionMixin(collections_abc.MutableMapping):
|
||||
class SessionMixin(MutableMapping):
|
||||
"""Expands a basic dictionary with session attributes."""
|
||||
|
||||
@property
|
||||
|
|
@ -77,19 +76,19 @@ class SecureCookieSession(CallbackDict, SessionMixin):
|
|||
self.modified = True
|
||||
self.accessed = True
|
||||
|
||||
super(SecureCookieSession, self).__init__(initial, on_update)
|
||||
super().__init__(initial, on_update)
|
||||
|
||||
def __getitem__(self, key):
|
||||
self.accessed = True
|
||||
return super(SecureCookieSession, self).__getitem__(key)
|
||||
return super().__getitem__(key)
|
||||
|
||||
def get(self, key, default=None):
|
||||
self.accessed = True
|
||||
return super(SecureCookieSession, self).get(key, default)
|
||||
return super().get(key, default)
|
||||
|
||||
def setdefault(self, key, default=None):
|
||||
self.accessed = True
|
||||
return super(SecureCookieSession, self).setdefault(key, default)
|
||||
return super().setdefault(key, default)
|
||||
|
||||
|
||||
class NullSession(SecureCookieSession):
|
||||
|
|
@ -109,7 +108,7 @@ class NullSession(SecureCookieSession):
|
|||
del _fail
|
||||
|
||||
|
||||
class SessionInterface(object):
|
||||
class SessionInterface:
|
||||
"""The basic interface you have to implement in order to replace the
|
||||
default session interface which uses werkzeug's securecookie
|
||||
implementation. The only methods you have to implement are
|
||||
|
|
@ -209,13 +208,13 @@ class SessionInterface(object):
|
|||
rv = rv.rsplit(":", 1)[0].lstrip(".")
|
||||
|
||||
if "." not in rv:
|
||||
# Chrome doesn't allow names without a '.'
|
||||
# this should only come up with localhost
|
||||
# hack around this by not setting the name, and show a warning
|
||||
# Chrome doesn't allow names without a '.'. This should only
|
||||
# come up with localhost. Hack around this by not setting
|
||||
# the name, and show a warning.
|
||||
warnings.warn(
|
||||
'"{rv}" is not a valid cookie domain, it must contain a ".".'
|
||||
" Add an entry to your hosts file, for example"
|
||||
' "{rv}.localdomain", and use that instead.'.format(rv=rv)
|
||||
f"{rv!r} is not a valid cookie domain, it must contain"
|
||||
" a '.'. Add an entry to your hosts file, for example"
|
||||
f" '{rv}.localdomain', and use that instead."
|
||||
)
|
||||
app.config["SESSION_COOKIE_DOMAIN"] = False
|
||||
return None
|
||||
|
|
@ -233,7 +232,7 @@ class SessionInterface(object):
|
|||
# if this is not an ip and app is mounted at the root, allow subdomain
|
||||
# matching by adding a '.' prefix
|
||||
if self.get_cookie_path(app) == "/" and not ip:
|
||||
rv = "." + rv
|
||||
rv = f".{rv}"
|
||||
|
||||
app.config["SESSION_COOKIE_DOMAIN"] = rv
|
||||
return rv
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flask.signals
|
||||
~~~~~~~~~~~~~
|
||||
|
|
@ -16,11 +15,11 @@ try:
|
|||
except ImportError:
|
||||
signals_available = False
|
||||
|
||||
class Namespace(object):
|
||||
class Namespace:
|
||||
def signal(self, name, doc=None):
|
||||
return _FakeSignal(name, doc)
|
||||
|
||||
class _FakeSignal(object):
|
||||
class _FakeSignal:
|
||||
"""If blinker is unavailable, create a fake class with the same
|
||||
interface that allows sending of signals but will fail with an
|
||||
error on anything else. Instead of doing anything on send, it
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flask.templating
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flask.testing
|
||||
~~~~~~~~~~~~~
|
||||
|
|
@ -9,7 +8,6 @@
|
|||
:copyright: 2010 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
||||
import warnings
|
||||
from contextlib import contextmanager
|
||||
|
||||
import werkzeug.test
|
||||
|
|
@ -52,7 +50,7 @@ class EnvironBuilder(werkzeug.test.EnvironBuilder):
|
|||
subdomain=None,
|
||||
url_scheme=None,
|
||||
*args,
|
||||
**kwargs
|
||||
**kwargs,
|
||||
):
|
||||
assert not (base_url or subdomain or url_scheme) or (
|
||||
base_url is not None
|
||||
|
|
@ -65,16 +63,15 @@ class EnvironBuilder(werkzeug.test.EnvironBuilder):
|
|||
app_root = app.config["APPLICATION_ROOT"]
|
||||
|
||||
if subdomain:
|
||||
http_host = "{0}.{1}".format(subdomain, http_host)
|
||||
http_host = f"{subdomain}.{http_host}"
|
||||
|
||||
if url_scheme is None:
|
||||
url_scheme = app.config["PREFERRED_URL_SCHEME"]
|
||||
|
||||
url = url_parse(path)
|
||||
base_url = "{scheme}://{netloc}/{path}".format(
|
||||
scheme=url.scheme or url_scheme,
|
||||
netloc=url.netloc or http_host,
|
||||
path=app_root.lstrip("/"),
|
||||
base_url = (
|
||||
f"{url.scheme or url_scheme}://{url.netloc or http_host}"
|
||||
f"/{app_root.lstrip('/')}"
|
||||
)
|
||||
path = url.path
|
||||
|
||||
|
|
@ -83,7 +80,7 @@ class EnvironBuilder(werkzeug.test.EnvironBuilder):
|
|||
path += sep + url.query
|
||||
|
||||
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):
|
||||
"""Serialize ``obj`` to a JSON-formatted string.
|
||||
|
|
@ -95,23 +92,6 @@ class EnvironBuilder(werkzeug.test.EnvironBuilder):
|
|||
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):
|
||||
"""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
|
||||
|
|
@ -130,10 +110,10 @@ class FlaskClient(Client):
|
|||
preserve_context = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(FlaskClient, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.environ_base = {
|
||||
"REMOTE_ADDR": "127.0.0.1",
|
||||
"HTTP_USER_AGENT": "werkzeug/" + werkzeug.__version__,
|
||||
"HTTP_USER_AGENT": f"werkzeug/{werkzeug.__version__}",
|
||||
}
|
||||
|
||||
@contextmanager
|
||||
|
|
@ -257,7 +237,7 @@ class FlaskCliRunner(CliRunner):
|
|||
|
||||
def __init__(self, app, **kwargs):
|
||||
self.app = app
|
||||
super(FlaskCliRunner, self).__init__(**kwargs)
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def invoke(self, cli=None, args=None, **kwargs):
|
||||
"""Invokes a CLI command in an isolated environment. See
|
||||
|
|
@ -280,4 +260,4 @@ class FlaskCliRunner(CliRunner):
|
|||
if "obj" not in kwargs:
|
||||
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
|
||||
~~~~~~~~~~~
|
||||
|
|
@ -8,7 +7,6 @@
|
|||
:copyright: 2010 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
||||
from ._compat import with_metaclass
|
||||
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
|
||||
:meth:`dispatch_request` which is called with the view arguments from
|
||||
the URL routing system. If :attr:`methods` is provided the methods
|
||||
|
|
@ -28,7 +26,7 @@ class View(object):
|
|||
methods = ['GET']
|
||||
|
||||
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'))
|
||||
|
||||
|
|
@ -114,7 +112,7 @@ class MethodViewType(type):
|
|||
"""
|
||||
|
||||
def __init__(cls, name, bases, d):
|
||||
super(MethodViewType, cls).__init__(name, bases, d)
|
||||
super().__init__(name, bases, d)
|
||||
|
||||
if "methods" not in d:
|
||||
methods = set()
|
||||
|
|
@ -135,7 +133,7 @@ class MethodViewType(type):
|
|||
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
|
||||
class methods. For example, if you implement a ``get`` method, it will be
|
||||
used to handle ``GET`` requests. ::
|
||||
|
|
@ -159,5 +157,5 @@ class MethodView(with_metaclass(MethodViewType, View)):
|
|||
if meth is None and request.method == "HEAD":
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flask.wrappers
|
||||
~~~~~~~~~~~~~~
|
||||
|
|
@ -22,7 +21,7 @@ class JSONMixin(_JSONMixin):
|
|||
|
||||
def on_json_loading_failed(self, e):
|
||||
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()
|
||||
|
||||
|
|
@ -134,4 +133,4 @@ class Response(ResponseBase, JSONMixin):
|
|||
return current_app.config["MAX_COOKIE_SIZE"]
|
||||
|
||||
# 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
|
||||
~~~~~~~~~~~~~~
|
||||
|
|
@ -112,14 +111,13 @@ def limit_loader(request, monkeypatch):
|
|||
if not request.param:
|
||||
return
|
||||
|
||||
class LimitedLoader(object):
|
||||
class LimitedLoader:
|
||||
def __init__(self, loader):
|
||||
self.loader = loader
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name in ("archive", "get_filename"):
|
||||
msg = "Mocking a loader which does not have `%s.`" % name
|
||||
raise AttributeError(msg)
|
||||
if name in {"archive", "get_filename"}:
|
||||
raise AttributeError(f"Mocking a loader which does not have {name!r}.")
|
||||
return getattr(self.loader, name)
|
||||
|
||||
old_get_loader = pkgutil.get_loader
|
||||
|
|
@ -149,7 +147,7 @@ def site_packages(modules_tmpdir, monkeypatch):
|
|||
"""Create a fake site-packages."""
|
||||
rv = (
|
||||
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")
|
||||
)
|
||||
monkeypatch.syspath_prepend(str(rv))
|
||||
|
|
@ -162,23 +160,21 @@ def install_egg(modules_tmpdir, monkeypatch):
|
|||
sys.path."""
|
||||
|
||||
def inner(name, base=modules_tmpdir):
|
||||
if not isinstance(name, str):
|
||||
raise ValueError(name)
|
||||
base.join(name).ensure_dir()
|
||||
base.join(name).join("__init__.py").ensure()
|
||||
|
||||
egg_setup = base.join("setup.py")
|
||||
egg_setup.write(
|
||||
textwrap.dedent(
|
||||
"""
|
||||
from setuptools import setup
|
||||
setup(name='{0}',
|
||||
version='1.0',
|
||||
packages=['site_egg'],
|
||||
zip_safe=True)
|
||||
""".format(
|
||||
name
|
||||
f"""
|
||||
from setuptools import setup
|
||||
setup(
|
||||
name="{name}",
|
||||
version="1.0",
|
||||
packages=["site_egg"],
|
||||
zip_safe=True,
|
||||
)
|
||||
"""
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -187,7 +183,7 @@ def install_egg(modules_tmpdir, monkeypatch):
|
|||
subprocess.check_call(
|
||||
[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))
|
||||
return egg_path
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
tests.appctx
|
||||
~~~~~~~~~~~~
|
||||
|
|
@ -163,7 +162,7 @@ def test_app_ctx_globals_methods(app, app_ctx):
|
|||
|
||||
|
||||
def test_custom_app_ctx_globals_class(app):
|
||||
class CustomRequestGlobals(object):
|
||||
class CustomRequestGlobals:
|
||||
def __init__(self):
|
||||
self.spam = "eggs"
|
||||
|
||||
|
|
@ -190,7 +189,7 @@ def test_context_refcounts(app, client):
|
|||
pass
|
||||
env = flask._request_ctx_stack.top.request.environ
|
||||
assert env["werkzeug.request"] is not None
|
||||
return u""
|
||||
return ""
|
||||
|
||||
res = client.get("/")
|
||||
assert res.status_code == 200
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
|
||||
from flask import Flask
|
||||
|
||||
testapp = Flask("testapp")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
|
||||
from flask import Flask
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
|
||||
from flask import Flask
|
||||
|
||||
raise ImportError()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
|
||||
from flask import Flask
|
||||
|
||||
app1 = Flask("app1")
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
tests.basic
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
@ -24,7 +23,6 @@ from werkzeug.http import parse_date
|
|||
from werkzeug.routing import BuildError
|
||||
|
||||
import flask
|
||||
from flask._compat import text_type
|
||||
|
||||
|
||||
def test_options_work(app, client):
|
||||
|
|
@ -92,12 +90,7 @@ def test_provide_automatic_options_kwarg(app, client):
|
|||
assert rv.status_code == 405
|
||||
assert sorted(rv.allow) == ["GET", "HEAD"]
|
||||
|
||||
# Older versions of Werkzeug.test.Client don't have an options method
|
||||
if hasattr(client, "options"):
|
||||
rv = client.options("/")
|
||||
else:
|
||||
rv = client.open("/", method="OPTIONS")
|
||||
|
||||
rv = client.open("/", method="OPTIONS")
|
||||
assert rv.status_code == 405
|
||||
|
||||
rv = client.head("/")
|
||||
|
|
@ -110,11 +103,7 @@ def test_provide_automatic_options_kwarg(app, client):
|
|||
assert rv.status_code == 405
|
||||
assert sorted(rv.allow) == ["GET", "HEAD", "POST"]
|
||||
|
||||
if hasattr(client, "options"):
|
||||
rv = client.options("/more")
|
||||
else:
|
||||
rv = client.open("/more", method="OPTIONS")
|
||||
|
||||
rv = client.open("/more", method="OPTIONS")
|
||||
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):
|
||||
class PrefixPathMiddleware(object):
|
||||
class PrefixPathMiddleware:
|
||||
def __init__(self, app, prefix):
|
||||
self.app = app
|
||||
self.prefix = prefix
|
||||
|
|
@ -372,7 +361,7 @@ def test_session_localhost_warning(recwarn, app, client):
|
|||
rv = client.get("/", "http://localhost:5000/")
|
||||
assert "domain" not in rv.headers["set-cookie"].lower()
|
||||
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):
|
||||
|
|
@ -413,7 +402,7 @@ def test_session_expiration(app, client):
|
|||
|
||||
@app.route("/test")
|
||||
def test():
|
||||
return text_type(flask.session.permanent)
|
||||
return str(flask.session.permanent)
|
||||
|
||||
rv = client.get("/")
|
||||
assert "set-cookie" in rv.headers
|
||||
|
|
@ -593,18 +582,18 @@ def test_extended_flashing(app):
|
|||
|
||||
@app.route("/")
|
||||
def index():
|
||||
flask.flash(u"Hello World")
|
||||
flask.flash(u"Hello World", "error")
|
||||
flask.flash(flask.Markup(u"<em>Testing</em>"), "warning")
|
||||
flask.flash("Hello World")
|
||||
flask.flash("Hello World", "error")
|
||||
flask.flash(flask.Markup("<em>Testing</em>"), "warning")
|
||||
return ""
|
||||
|
||||
@app.route("/test/")
|
||||
def test():
|
||||
messages = flask.get_flashed_messages()
|
||||
assert list(messages) == [
|
||||
u"Hello World",
|
||||
u"Hello World",
|
||||
flask.Markup(u"<em>Testing</em>"),
|
||||
"Hello World",
|
||||
"Hello World",
|
||||
flask.Markup("<em>Testing</em>"),
|
||||
]
|
||||
return ""
|
||||
|
||||
|
|
@ -613,9 +602,9 @@ def test_extended_flashing(app):
|
|||
messages = flask.get_flashed_messages(with_categories=True)
|
||||
assert len(messages) == 3
|
||||
assert list(messages) == [
|
||||
("message", u"Hello World"),
|
||||
("error", u"Hello World"),
|
||||
("warning", flask.Markup(u"<em>Testing</em>")),
|
||||
("message", "Hello World"),
|
||||
("error", "Hello World"),
|
||||
("warning", flask.Markup("<em>Testing</em>")),
|
||||
]
|
||||
return ""
|
||||
|
||||
|
|
@ -624,7 +613,7 @@ def test_extended_flashing(app):
|
|||
messages = flask.get_flashed_messages(
|
||||
category_filter=["message"], with_categories=True
|
||||
)
|
||||
assert list(messages) == [("message", u"Hello World")]
|
||||
assert list(messages) == [("message", "Hello World")]
|
||||
return ""
|
||||
|
||||
@app.route("/test_filters/")
|
||||
|
|
@ -633,8 +622,8 @@ def test_extended_flashing(app):
|
|||
category_filter=["message", "warning"], with_categories=True
|
||||
)
|
||||
assert list(messages) == [
|
||||
("message", u"Hello World"),
|
||||
("warning", flask.Markup(u"<em>Testing</em>")),
|
||||
("message", "Hello World"),
|
||||
("warning", flask.Markup("<em>Testing</em>")),
|
||||
]
|
||||
return ""
|
||||
|
||||
|
|
@ -642,8 +631,8 @@ def test_extended_flashing(app):
|
|||
def test_filters2():
|
||||
messages = flask.get_flashed_messages(category_filter=["message", "warning"])
|
||||
assert len(messages) == 2
|
||||
assert messages[0] == u"Hello World"
|
||||
assert messages[1] == flask.Markup(u"<em>Testing</em>")
|
||||
assert messages[0] == "Hello World"
|
||||
assert messages[1] == flask.Markup("<em>Testing</em>")
|
||||
return ""
|
||||
|
||||
# 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:
|
||||
client.post("/fail", data={"foo": "index.txt"})
|
||||
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):
|
||||
@app.route("/text")
|
||||
def from_text():
|
||||
return u"Hällo Wörld"
|
||||
return "Hällo Wörld"
|
||||
|
||||
@app.route("/bytes")
|
||||
def from_bytes():
|
||||
return u"Hällo Wörld".encode("utf-8")
|
||||
return "Hällo Wörld".encode()
|
||||
|
||||
@app.route("/full_tuple")
|
||||
def from_full_tuple():
|
||||
|
|
@ -1153,8 +1142,8 @@ def test_response_types(app, client):
|
|||
def from_dict():
|
||||
return {"foo": "bar"}, 201
|
||||
|
||||
assert client.get("/text").data == u"Hällo Wörld".encode("utf-8")
|
||||
assert client.get("/bytes").data == u"Hällo Wörld".encode("utf-8")
|
||||
assert client.get("/text").data == "Hällo Wörld".encode()
|
||||
assert client.get("/bytes").data == "Hällo Wörld".encode()
|
||||
|
||||
rv = client.get("/full_tuple")
|
||||
assert rv.data == b"Meh"
|
||||
|
|
@ -1621,11 +1610,11 @@ def test_inject_blueprint_url_defaults(app):
|
|||
|
||||
|
||||
def test_nonascii_pathinfo(app, client):
|
||||
@app.route(u"/киртест")
|
||||
@app.route("/киртест")
|
||||
def index():
|
||||
return "Hello World!"
|
||||
|
||||
rv = client.get(u"/киртест")
|
||||
rv = client.get("/киртест")
|
||||
assert rv.data == b"Hello World!"
|
||||
|
||||
|
||||
|
|
@ -1832,7 +1821,7 @@ def test_subdomain_matching():
|
|||
|
||||
@app.route("/", subdomain="<user>")
|
||||
def index(user):
|
||||
return "index for %s" % user
|
||||
return f"index for {user}"
|
||||
|
||||
rv = client.get("/", "http://mitsuhiko.localhost.localdomain/")
|
||||
assert rv.data == b"index for mitsuhiko"
|
||||
|
|
@ -1845,7 +1834,7 @@ def test_subdomain_matching_with_ports():
|
|||
|
||||
@app.route("/", subdomain="<user>")
|
||||
def index(user):
|
||||
return "index for %s" % user
|
||||
return f"index for {user}"
|
||||
|
||||
rv = client.get("/", "http://mitsuhiko.localhost.localdomain:3000/")
|
||||
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):
|
||||
class View(object):
|
||||
class View:
|
||||
def __init__(self, app):
|
||||
app.add_url_rule("/", "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
|
||||
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)
|
||||
hostname, port = "localhost", 8000
|
||||
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(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
tests.blueprints
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
|
@ -15,7 +14,6 @@ from jinja2 import TemplateNotFound
|
|||
from werkzeug.http import parse_cache_control_header
|
||||
|
||||
import flask
|
||||
from flask._compat import text_type
|
||||
|
||||
|
||||
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})
|
||||
def foo(bar, baz):
|
||||
return "%s/%d" % (bar, baz)
|
||||
return f"{bar}/{baz:d}"
|
||||
|
||||
@bp.route("/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="/2", url_defaults={"bar": 19})
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
tests.test_cli
|
||||
~~~~~~~~~~~~~~
|
||||
|
|
@ -8,8 +7,6 @@
|
|||
"""
|
||||
# This file was part of Flask-CLI and was modified under the terms of
|
||||
# its Revised BSD License. Copyright © 2015 CERN.
|
||||
from __future__ import absolute_import
|
||||
|
||||
import os
|
||||
import ssl
|
||||
import sys
|
||||
|
|
@ -261,7 +258,7 @@ def test_get_version(test_apps, capsys):
|
|||
from werkzeug import __version__ as werkzeug_version
|
||||
from platform import python_version
|
||||
|
||||
class MockCtx(object):
|
||||
class MockCtx:
|
||||
resilient_parsing = False
|
||||
color = None
|
||||
|
||||
|
|
@ -271,9 +268,9 @@ def test_get_version(test_apps, capsys):
|
|||
ctx = MockCtx()
|
||||
get_version(ctx, None, "test")
|
||||
out, err = capsys.readouterr()
|
||||
assert "Python " + python_version() in out
|
||||
assert "Flask " + flask_version in out
|
||||
assert "Werkzeug " + werkzeug_version in out
|
||||
assert f"Python {python_version()}" in out
|
||||
assert f"Flask {flask_version}" in out
|
||||
assert f"Werkzeug {werkzeug_version}" in out
|
||||
|
||||
|
||||
def test_scriptinfo(test_apps, monkeypatch):
|
||||
|
|
@ -291,7 +288,7 @@ def test_scriptinfo(test_apps, monkeypatch):
|
|||
app = obj.load_app()
|
||||
assert app.name == "testapp"
|
||||
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()
|
||||
assert app.name == "testapp"
|
||||
assert obj.load_app() is app
|
||||
|
|
@ -409,7 +406,7 @@ def test_flaskgroup_debug(runner, set_debug_flag):
|
|||
|
||||
result = runner.invoke(cli, ["test"])
|
||||
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):
|
||||
|
|
@ -589,16 +586,11 @@ def test_run_cert_import(monkeypatch):
|
|||
with pytest.raises(click.BadParameter):
|
||||
run_command.make_context("run", ["--cert", "not_here"])
|
||||
|
||||
# not an SSLContext
|
||||
if sys.version_info >= (2, 7, 9):
|
||||
with pytest.raises(click.BadParameter):
|
||||
run_command.make_context("run", ["--cert", "flask"])
|
||||
with pytest.raises(click.BadParameter):
|
||||
run_command.make_context("run", ["--cert", "flask"])
|
||||
|
||||
# SSLContext
|
||||
if sys.version_info < (2, 7, 9):
|
||||
ssl_context = object()
|
||||
else:
|
||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
|
||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
|
||||
monkeypatch.setitem(sys.modules, "ssl_context", ssl_context)
|
||||
ctx = run_command.make_context("run", ["--cert", "ssl_context"])
|
||||
|
|
@ -664,4 +656,4 @@ def test_cli_empty(app):
|
|||
app.register_blueprint(bp)
|
||||
|
||||
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
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
|
@ -14,7 +13,6 @@ from datetime import timedelta
|
|||
import pytest
|
||||
|
||||
import flask
|
||||
from flask._compat import PY2
|
||||
|
||||
|
||||
# config keys used for the TestConfig
|
||||
|
|
@ -30,7 +28,7 @@ def common_object_test(app):
|
|||
|
||||
def test_config_from_pyfile():
|
||||
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)
|
||||
|
||||
|
||||
|
|
@ -66,7 +64,7 @@ def test_config_from_mapping():
|
|||
|
||||
|
||||
def test_config_from_class():
|
||||
class Base(object):
|
||||
class Base:
|
||||
TEST_KEY = "foo"
|
||||
|
||||
class Test(Base):
|
||||
|
|
@ -86,7 +84,7 @@ def test_config_from_envvar(monkeypatch):
|
|||
assert not app.config.from_envvar("FOO_SETTINGS", silent=True)
|
||||
|
||||
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")
|
||||
common_object_test(app)
|
||||
|
|
@ -187,17 +185,13 @@ def test_from_pyfile_weird_encoding(tmpdir, encoding):
|
|||
f = tmpdir.join("my_config.py")
|
||||
f.write_binary(
|
||||
textwrap.dedent(
|
||||
u"""
|
||||
# -*- coding: {0} -*-
|
||||
TEST_VALUE = "föö"
|
||||
""".format(
|
||||
encoding
|
||||
)
|
||||
f"""
|
||||
# -*- coding: {encoding} -*-
|
||||
TEST_VALUE = "föö"
|
||||
"""
|
||||
).encode(encoding)
|
||||
)
|
||||
app = flask.Flask(__name__)
|
||||
app.config.from_pyfile(str(f))
|
||||
value = app.config["TEST_VALUE"]
|
||||
if PY2:
|
||||
value = value.decode(encoding)
|
||||
assert value == u"föö"
|
||||
assert value == "föö"
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ def test_custom_converters(app, client):
|
|||
return value.split(",")
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
@ -24,9 +23,6 @@ from werkzeug.http import parse_options_header
|
|||
|
||||
import flask
|
||||
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_env
|
||||
|
||||
|
|
@ -41,7 +37,7 @@ def has_encoding(name):
|
|||
return False
|
||||
|
||||
|
||||
class FakePath(object):
|
||||
class FakePath:
|
||||
"""Fake object to represent a ``PathLike object``.
|
||||
|
||||
This represents a ``pathlib.Path`` object in python 3.
|
||||
|
|
@ -76,9 +72,9 @@ class FixedOffset(datetime.tzinfo):
|
|||
return datetime.timedelta()
|
||||
|
||||
|
||||
class TestJSON(object):
|
||||
class TestJSON:
|
||||
@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(
|
||||
"encoding",
|
||||
|
|
@ -95,7 +91,6 @@ class TestJSON(object):
|
|||
)
|
||||
def test_detect_encoding(self, value, encoding):
|
||||
data = json.dumps(value).encode(encoding)
|
||||
assert json.detect_encoding(data) == encoding
|
||||
assert json.loads(data) == value
|
||||
|
||||
@pytest.mark.parametrize("debug", (True, False))
|
||||
|
|
@ -116,7 +111,7 @@ class TestJSON(object):
|
|||
def test_json_bad_requests(self, app, client):
|
||||
@app.route("/json", methods=["POST"])
|
||||
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")
|
||||
assert rv.status_code == 400
|
||||
|
|
@ -130,17 +125,17 @@ class TestJSON(object):
|
|||
assert rv.data == b"foo"
|
||||
|
||||
@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):
|
||||
|
||||
app.config["JSON_AS_ASCII"] = test_value
|
||||
rv = flask.json.dumps(u"\N{SNOWMAN}")
|
||||
rv = flask.json.dumps("\N{SNOWMAN}")
|
||||
assert rv == expected
|
||||
|
||||
def test_json_dump_to_file(self, app, app_ctx):
|
||||
test_data = {"name": "Flask"}
|
||||
out = StringIO()
|
||||
out = io.StringIO()
|
||||
|
||||
flask.json.dump(test_data, out)
|
||||
out.seek(0)
|
||||
|
|
@ -221,7 +216,7 @@ class TestJSON(object):
|
|||
)
|
||||
|
||||
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))
|
||||
rv = client.get(url)
|
||||
assert rv.mimetype == "application/json"
|
||||
|
|
@ -254,7 +249,7 @@ class TestJSON(object):
|
|||
@app.route("/add", methods=["POST"])
|
||||
def add():
|
||||
json = flask.request.get_json()
|
||||
return text_type(json["a"] + json["b"])
|
||||
return str(json["a"] + json["b"])
|
||||
|
||||
rv = client.post(
|
||||
"/add",
|
||||
|
|
@ -266,8 +261,8 @@ class TestJSON(object):
|
|||
def test_template_escaping(self, app, req_ctx):
|
||||
render = flask.render_template_string
|
||||
rv = flask.json.htmlsafe_dumps("</script>")
|
||||
assert rv == u'"\\u003c/script\\u003e"'
|
||||
assert type(rv) == text_type
|
||||
assert rv == '"\\u003c/script\\u003e"'
|
||||
assert type(rv) is str
|
||||
rv = render('{{ "</script>"|tojson }}')
|
||||
assert rv == '"\\u003c/script\\u003e"'
|
||||
rv = render('{{ "<\0/script>"|tojson }}')
|
||||
|
|
@ -284,14 +279,14 @@ class TestJSON(object):
|
|||
assert rv == '<a ng-data=\'{"x": ["foo", "bar", "baz\\u0027"]}\'></a>'
|
||||
|
||||
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):
|
||||
self.val = val
|
||||
|
||||
class MyEncoder(flask.json.JSONEncoder):
|
||||
def default(self, o):
|
||||
if isinstance(o, X):
|
||||
return "<%d>" % o.val
|
||||
return f"<{o.val}>"
|
||||
return flask.json.JSONEncoder.default(self, o)
|
||||
|
||||
class MyDecoder(flask.json.JSONDecoder):
|
||||
|
|
@ -319,14 +314,16 @@ class TestJSON(object):
|
|||
assert rv.data == b'"<42>"'
|
||||
|
||||
def test_blueprint_json_customization(self, app, client):
|
||||
class X(object): # noqa: B903, for Python2 compatibility
|
||||
class X:
|
||||
__slots__ = ("val",)
|
||||
|
||||
def __init__(self, val):
|
||||
self.val = val
|
||||
|
||||
class MyEncoder(flask.json.JSONEncoder):
|
||||
def default(self, o):
|
||||
if isinstance(o, X):
|
||||
return "<%d>" % o.val
|
||||
return f"<{o.val}>"
|
||||
|
||||
return flask.json.JSONEncoder.default(self, o)
|
||||
|
||||
|
|
@ -372,9 +369,9 @@ class TestJSON(object):
|
|||
def index():
|
||||
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.data == u"정상처리".encode("utf-8")
|
||||
assert rv.data == "정상처리".encode()
|
||||
|
||||
def test_json_key_sorting(self, app, client):
|
||||
app.debug = True
|
||||
|
|
@ -447,7 +444,7 @@ class TestJSON(object):
|
|||
assert lines == sorted_by_str
|
||||
|
||||
|
||||
class PyStringIO(object):
|
||||
class PyBytesIO:
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._io = io.BytesIO(*args, **kwargs)
|
||||
|
||||
|
|
@ -455,7 +452,7 @@ class PyStringIO(object):
|
|||
return getattr(self._io, name)
|
||||
|
||||
|
||||
class TestSendfile(object):
|
||||
class TestSendfile:
|
||||
def test_send_file_regular(self, app, req_ctx):
|
||||
rv = flask.send_file("static/index.html")
|
||||
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: io.BytesIO(b"Test"),
|
||||
pytest.param(
|
||||
lambda app: StringIO("Test"),
|
||||
marks=pytest.mark.skipif(not PY2, reason="Python 2 only"),
|
||||
),
|
||||
lambda app: PyStringIO(b"Test"),
|
||||
lambda app: PyBytesIO(b"Test"),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("req_ctx")
|
||||
|
|
@ -524,11 +517,8 @@ class TestSendfile(object):
|
|||
@pytest.mark.parametrize(
|
||||
"opener",
|
||||
[
|
||||
lambda app: io.StringIO(u"Test"),
|
||||
pytest.param(
|
||||
lambda app: open(os.path.join(app.static_folder, "index.html")),
|
||||
marks=pytest.mark.skipif(PY2, reason="Python 3 only"),
|
||||
),
|
||||
lambda app: io.StringIO("Test"),
|
||||
lambda app: open(os.path.join(app.static_folder, "index.html")),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("req_ctx")
|
||||
|
|
@ -684,15 +674,13 @@ class TestSendfile(object):
|
|||
(
|
||||
("index.html", "index.html", False),
|
||||
(
|
||||
u"Ñandú/pingüino.txt",
|
||||
"Ñandú/pingüino.txt",
|
||||
'"Nandu/pinguino.txt"',
|
||||
"%C3%91and%C3%BA%EF%BC%8Fping%C3%BCino.txt",
|
||||
),
|
||||
(u"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"),
|
||||
("Vögel.txt", "Vogel.txt", "V%C3%B6gel.txt"),
|
||||
# ":/" 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):
|
||||
|
|
@ -701,9 +689,9 @@ class TestSendfile(object):
|
|||
)
|
||||
rv.close()
|
||||
content_disposition = rv.headers["Content-Disposition"]
|
||||
assert "filename=%s" % ascii in content_disposition
|
||||
assert f"filename={ascii}" in content_disposition
|
||||
if utf8:
|
||||
assert "filename*=UTF-8''" + utf8 in content_disposition
|
||||
assert f"filename*=UTF-8''{utf8}" in content_disposition
|
||||
else:
|
||||
assert "filename*=UTF-8''" not in content_disposition
|
||||
|
||||
|
|
@ -788,7 +776,7 @@ class TestSendfile(object):
|
|||
flask.send_from_directory("static", "bad\x00")
|
||||
|
||||
|
||||
class TestUrlFor(object):
|
||||
class TestUrlFor:
|
||||
def test_url_for_with_anchor(self, app, req_ctx):
|
||||
@app.route("/")
|
||||
def index():
|
||||
|
|
@ -832,7 +820,7 @@ class TestUrlFor(object):
|
|||
def get(self, id=None):
|
||||
if id is None:
|
||||
return "List"
|
||||
return "Get %d" % id
|
||||
return f"Get {id:d}"
|
||||
|
||||
def post(self):
|
||||
return "Create"
|
||||
|
|
@ -847,7 +835,7 @@ class TestUrlFor(object):
|
|||
assert flask.url_for("myview", _method="POST") == "/myview/create"
|
||||
|
||||
|
||||
class TestNoImports(object):
|
||||
class TestNoImports:
|
||||
"""Test Flasks are created without import.
|
||||
|
||||
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.")
|
||||
|
||||
|
||||
class TestStreaming(object):
|
||||
class TestStreaming:
|
||||
def test_streaming_with_context(self, app, client):
|
||||
@app.route("/")
|
||||
def index():
|
||||
|
|
@ -897,7 +885,7 @@ class TestStreaming(object):
|
|||
def test_streaming_with_context_and_custom_close(self, app, client):
|
||||
called = []
|
||||
|
||||
class Wrapper(object):
|
||||
class Wrapper:
|
||||
def __init__(self, gen):
|
||||
self._gen = gen
|
||||
|
||||
|
|
@ -940,7 +928,7 @@ class TestStreaming(object):
|
|||
assert rv.data == b"flask"
|
||||
|
||||
|
||||
class TestSafeJoin(object):
|
||||
class TestSafeJoin:
|
||||
def test_safe_join(self):
|
||||
# Valid combinations of *args and expected joined paths.
|
||||
passing = (
|
||||
|
|
@ -981,7 +969,7 @@ class TestSafeJoin(object):
|
|||
print(flask.safe_join(*args))
|
||||
|
||||
|
||||
class TestHelpers(object):
|
||||
class TestHelpers:
|
||||
@pytest.mark.parametrize(
|
||||
"debug, expected_flag, expected_default_flag",
|
||||
[
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
tests.test_instance
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
|
@ -12,7 +11,6 @@ import sys
|
|||
import pytest
|
||||
|
||||
import flask
|
||||
from flask._compat import PY2
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@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):
|
||||
app = modules_tmpdir.join("main_app.py")
|
||||
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")
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason="weird interaction with tox")
|
||||
def test_uninstalled_module_paths(modules_tmpdir, purge_module):
|
||||
app = modules_tmpdir.join("config_module_app.py").write(
|
||||
"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"))
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason="weird interaction with tox")
|
||||
def test_uninstalled_package_paths(modules_tmpdir, purge_module):
|
||||
app = modules_tmpdir.mkdir("config_package_app")
|
||||
init = app.join("__init__.py")
|
||||
|
|
@ -126,19 +126,3 @@ def test_egg_installed_paths(install_egg, modules_tmpdir, modules_tmpdir_prefix)
|
|||
finally:
|
||||
if "site_egg" in sys.modules:
|
||||
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
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
|
@ -48,7 +47,7 @@ def test_duplicate_tag():
|
|||
|
||||
|
||||
def test_custom_tag():
|
||||
class Foo(object): # noqa: B903, for Python2 compatibility
|
||||
class Foo: # noqa: B903, for Python2 compatibility
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
tests.test_logging
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
|
@ -8,10 +7,10 @@ tests.test_logging
|
|||
"""
|
||||
import logging
|
||||
import sys
|
||||
from io import StringIO
|
||||
|
||||
import pytest
|
||||
|
||||
from flask._compat import StringIO
|
||||
from flask.logging import default_handler
|
||||
from flask.logging import has_level_handler
|
||||
from flask.logging import wsgi_errors_stream
|
||||
|
|
@ -104,12 +103,3 @@ def test_log_view_exception(app, client):
|
|||
err = stream.getvalue()
|
||||
assert "Exception on / [GET]" 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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
@ -9,7 +8,7 @@
|
|||
:license: BSD-3-Clause
|
||||
"""
|
||||
import gc
|
||||
import sys
|
||||
import platform
|
||||
import threading
|
||||
|
||||
import pytest
|
||||
|
|
@ -20,7 +19,7 @@ import flask
|
|||
_gc_lock = threading.Lock()
|
||||
|
||||
|
||||
class assert_no_leak(object):
|
||||
class assert_no_leak:
|
||||
def __enter__(self):
|
||||
gc.disable()
|
||||
_gc_lock.acquire()
|
||||
|
|
@ -44,6 +43,7 @@ class assert_no_leak(object):
|
|||
gc.enable()
|
||||
|
||||
|
||||
@pytest.mark.skipif(platform.python_implementation() == "PyPy", reason="CPython only")
|
||||
def test_memory_consumption():
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
|
|
@ -60,11 +60,9 @@ def test_memory_consumption():
|
|||
# Trigger caches
|
||||
fire()
|
||||
|
||||
# This test only works on CPython 2.7.
|
||||
if sys.version_info >= (2, 7) and not hasattr(sys, "pypy_translation_info"):
|
||||
with assert_no_leak():
|
||||
for _x in range(10):
|
||||
fire()
|
||||
with assert_no_leak():
|
||||
for _x in range(10):
|
||||
fire()
|
||||
|
||||
|
||||
def test_safe_join_toplevel_pardir():
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
tests.reqctx
|
||||
~~~~~~~~~~~~
|
||||
|
|
@ -111,7 +110,7 @@ def test_proper_test_request_context(app):
|
|||
def test_context_binding(app):
|
||||
@app.route("/")
|
||||
def index():
|
||||
return "Hello %s!" % flask.request.args["name"]
|
||||
return f"Hello {flask.request.args['name']}!"
|
||||
|
||||
@app.route("/meh")
|
||||
def meh():
|
||||
|
|
@ -139,7 +138,7 @@ def test_context_test(app):
|
|||
def test_manual_context_binding(app):
|
||||
@app.route("/")
|
||||
def index():
|
||||
return "Hello %s!" % flask.request.args["name"]
|
||||
return f"Hello {flask.request.args['name']}!"
|
||||
|
||||
ctx = app.test_request_context("/?name=World")
|
||||
ctx.push()
|
||||
|
|
@ -150,7 +149,7 @@ def test_manual_context_binding(app):
|
|||
|
||||
|
||||
@pytest.mark.skipif(greenlet is None, reason="greenlet not installed")
|
||||
class TestGreenletContextCopying(object):
|
||||
class TestGreenletContextCopying:
|
||||
def test_greenlet_context_copying(self, app, client):
|
||||
greenlets = []
|
||||
|
||||
|
|
@ -239,7 +238,7 @@ def test_session_dynamic_cookie_name():
|
|||
if flask.request.url.endswith("dynamic_cookie"):
|
||||
return "dynamic_cookie_name"
|
||||
else:
|
||||
return super(PathAwareSessionInterface, self).get_cookie_name(app)
|
||||
return super().get_cookie_name(app)
|
||||
|
||||
class CustomFlask(flask.Flask):
|
||||
session_interface = PathAwareSessionInterface()
|
||||
|
|
@ -285,17 +284,13 @@ def test_session_dynamic_cookie_name():
|
|||
def test_bad_environ_raises_bad_request():
|
||||
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
|
||||
|
||||
builder = EnvironBuilder(app)
|
||||
environ = builder.get_environ()
|
||||
|
||||
# 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):
|
||||
response = app.full_dispatch_request()
|
||||
|
|
@ -309,17 +304,13 @@ def test_environ_for_valid_idna_completes():
|
|||
def index():
|
||||
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
|
||||
|
||||
builder = EnvironBuilder(app)
|
||||
environ = builder.get_environ()
|
||||
|
||||
# these characters are all IDNA-compatible
|
||||
environ["HTTP_HOST"] = u"ąśźäüжŠßя.com"
|
||||
environ["HTTP_HOST"] = "ąśźäüжŠßя.com"
|
||||
|
||||
with app.request_context(environ):
|
||||
response = app.full_dispatch_request()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
tests.signals
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
tests.subclassing
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
|
@ -9,8 +8,9 @@
|
|||
:copyright: 2010 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
||||
from io import StringIO
|
||||
|
||||
import flask
|
||||
from flask._compat import StringIO
|
||||
|
||||
|
||||
def test_suppressed_exception_logging():
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
tests.templating
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
|
@ -415,16 +414,16 @@ def test_template_loader_debugging(test_apps, monkeypatch):
|
|||
def handle(self, record):
|
||||
called.append(True)
|
||||
text = str(record.msg)
|
||||
assert '1: trying loader of application "blueprintapp"' in text
|
||||
assert "1: trying loader of application 'blueprintapp'" in text
|
||||
assert (
|
||||
'2: trying loader of blueprint "admin" ' "(blueprintapp.apps.admin)"
|
||||
"2: trying loader of blueprint 'admin' (blueprintapp.apps.admin)"
|
||||
) in text
|
||||
assert (
|
||||
'trying loader of blueprint "frontend" ' "(blueprintapp.apps.frontend)"
|
||||
"trying loader of blueprint 'frontend' (blueprintapp.apps.frontend)"
|
||||
) in text
|
||||
assert "Error: the template could not be found" in text
|
||||
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
|
||||
assert "See https://flask.palletsprojects.com/blueprints/#templates" in text
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
tests.testing
|
||||
~~~~~~~~~~~~~
|
||||
|
|
@ -14,12 +13,10 @@ import werkzeug
|
|||
|
||||
import flask
|
||||
from flask import appcontext_popped
|
||||
from flask._compat import text_type
|
||||
from flask.cli import ScriptInfo
|
||||
from flask.json import jsonify
|
||||
from flask.testing import EnvironBuilder
|
||||
from flask.testing import FlaskCliRunner
|
||||
from flask.testing import make_test_environ_builder
|
||||
|
||||
try:
|
||||
import blinker
|
||||
|
|
@ -62,7 +59,7 @@ def test_environ_base_default(app, client, app_ctx):
|
|||
|
||||
rv = client.get("/")
|
||||
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):
|
||||
|
|
@ -121,20 +118,11 @@ def test_path_is_url(app):
|
|||
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):
|
||||
"""EnvironBuilder.json_dumps() takes settings from the app."""
|
||||
app.config["JSON_AS_ASCII"] = False
|
||||
eb = EnvironBuilder(app, json=u"\u20ac")
|
||||
assert eb.input_stream.read().decode("utf8") == u'"\u20ac"'
|
||||
eb = EnvironBuilder(app, json="\u20ac")
|
||||
assert eb.input_stream.read().decode("utf8") == '"\u20ac"'
|
||||
|
||||
|
||||
def test_blueprint_with_subdomain():
|
||||
|
|
@ -180,12 +168,10 @@ def test_redirect_keep_session(app, client, app_ctx):
|
|||
rv = client.get("/")
|
||||
assert rv.data == b"index"
|
||||
assert flask.session.get("data") == "foo"
|
||||
|
||||
rv = client.post("/", data={}, follow_redirects=True)
|
||||
assert rv.data == b"foo"
|
||||
|
||||
# This support requires a new Werkzeug version
|
||||
if not hasattr(client, "redirect_client"):
|
||||
assert flask.session.get("data") == "foo"
|
||||
assert flask.session.get("data") == "foo"
|
||||
|
||||
rv = client.get("/getsession")
|
||||
assert rv.data == b"foo"
|
||||
|
|
@ -194,7 +180,7 @@ def test_redirect_keep_session(app, client, app_ctx):
|
|||
def test_session_transactions(app, client):
|
||||
@app.route("/")
|
||||
def index():
|
||||
return text_type(flask.session["foo"])
|
||||
return str(flask.session["foo"])
|
||||
|
||||
with client:
|
||||
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):
|
||||
@app.route("/hello", methods=["POST"])
|
||||
def hello():
|
||||
return "Hello, {}!".format(flask.request.json["name"])
|
||||
return f"Hello, {flask.request.json['name']}!"
|
||||
|
||||
class Namespace(object):
|
||||
class Namespace:
|
||||
count = 0
|
||||
|
||||
def add(self, app):
|
||||
|
|
@ -415,7 +401,7 @@ def test_cli_invoke(app):
|
|||
|
||||
|
||||
def test_cli_custom_obj(app):
|
||||
class NS(object):
|
||||
class NS:
|
||||
called = False
|
||||
|
||||
def create_app():
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
tests.test_user_error_handler
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
@ -30,7 +29,7 @@ def test_error_handler_no_match(app, client):
|
|||
original = getattr(e, "original_exception", None)
|
||||
|
||||
if original is not None:
|
||||
return "wrapped " + type(original).__name__
|
||||
return f"wrapped {type(original).__name__}"
|
||||
|
||||
return "direct"
|
||||
|
||||
|
|
@ -208,7 +207,7 @@ def test_default_error_handler():
|
|||
assert c.get("/slash", follow_redirects=True).data == b"slash"
|
||||
|
||||
|
||||
class TestGenericHandlers(object):
|
||||
class TestGenericHandlers:
|
||||
"""Test how very generic handlers are dispatched to."""
|
||||
|
||||
class Custom(Exception):
|
||||
|
|
@ -239,9 +238,9 @@ class TestGenericHandlers(object):
|
|||
original = getattr(e, "original_exception", 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))
|
||||
def test_handle_class_or_code(self, app, client, to_handle):
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
tests.views
|
||||
~~~~~~~~~~~
|
||||
|
|
|
|||
35
tox.ini
35
tox.ini
|
|
@ -1,16 +1,14 @@
|
|||
[tox]
|
||||
envlist =
|
||||
py{38,37,36,35,27,py3,py}
|
||||
py{38,37,36,py3}
|
||||
py38-{simplejson,devel,lowest}
|
||||
style
|
||||
docs
|
||||
coverage
|
||||
skip_missing_interpreters = true
|
||||
|
||||
[testenv]
|
||||
passenv = LANG
|
||||
deps =
|
||||
pytest
|
||||
coverage
|
||||
greenlet
|
||||
blinker
|
||||
python-dotenv
|
||||
|
|
@ -29,18 +27,10 @@ deps =
|
|||
simplejson: simplejson
|
||||
|
||||
commands =
|
||||
# the examples need to be installed to test successfully
|
||||
pip install -q -e examples/tutorial[test]
|
||||
pip install -q -e examples/javascript[test]
|
||||
|
||||
# pytest-cov doesn't seem to play nice with -p
|
||||
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
|
||||
pytest --tb=short --basetemp={envtmpdir} {posargs:tests examples}
|
||||
|
||||
[testenv:style]
|
||||
deps = pre-commit
|
||||
|
|
@ -48,22 +38,5 @@ skip_install = true
|
|||
commands = pre-commit run --all-files --show-diff-on-failure
|
||||
|
||||
[testenv:docs]
|
||||
deps =
|
||||
-r docs/requirements.txt
|
||||
deps = -r docs/requirements.txt
|
||||
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