Merge pull request #3554 from pallets/drop-python2

Drop Python 2
This commit is contained in:
David Lord 2020-04-04 12:13:35 -07:00 committed by GitHub
commit cd8a374504
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
84 changed files with 585 additions and 2051 deletions

View file

@ -1,85 +1,44 @@
trigger: trigger:
- 'master' - master
- '*.x' - '*.x'
jobs: variables:
- job: Tests vmImage: ubuntu-latest
variables: python.version: '3.8'
vmImage: 'ubuntu-latest' TOXENV: py
python.version: '3.8'
TOXENV: 'py,coverage-ci'
hasTestResults: 'true'
strategy: strategy:
matrix: matrix:
Python 3.8 Linux: Linux:
vmImage: 'ubuntu-latest'
Python 3.8 Windows:
vmImage: 'windows-latest'
Python 3.8 Mac:
vmImage: 'macos-latest'
PyPy 3 Linux:
python.version: 'pypy3'
Python 3.7 Linux:
python.version: '3.7'
Python 3.6 Linux:
python.version: '3.6'
Python 3.5 Linux:
python.version: '3.5'
Python 2.7 Linux:
python.version: '2.7'
Python 2.7 Windows:
python.version: '2.7'
vmImage: 'windows-latest'
Docs:
TOXENV: 'docs'
hasTestResults: 'false'
Style:
TOXENV: 'style'
hasTestResults: 'false'
VersionRange:
TOXENV: 'devel,lowest,coverage-ci'
pool:
vmImage: $[ variables.vmImage ]
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: $(python.version)
displayName: Use Python $(python.version)
- script: pip --disable-pip-version-check install -U tox
displayName: Install tox
- script: tox -s false -- --junitxml=test-results.xml tests examples
displayName: Run tox
- task: PublishTestResults@2
inputs:
testResultsFiles: test-results.xml
testRunTitle: $(Agent.JobName)
condition: eq(variables['hasTestResults'], 'true')
displayName: Publish test results
- task: PublishCodeCoverageResults@1
inputs:
codeCoverageTool: Cobertura
summaryFileLocation: coverage.xml
condition: eq(variables['hasTestResults'], 'true')
displayName: Publish coverage results
# Test on the nightly version of Python.
# Use a container since Azure Pipelines may not have the latest build.
- job: FlaskOnNightly
pool:
vmImage: ubuntu-latest vmImage: ubuntu-latest
container: python:rc-stretch Windows:
steps: vmImage: windows-latest
- script: | Mac:
echo "##vso[task.prependPath]$HOME/.local/bin" vmImage: macos-latest
pip --disable-pip-version-check install --user -U tox Python 3.7:
displayName: Install tox python.version: '3.7'
Python 3.6:
python.version: '3.6'
PyPy 3:
python.version: pypy3
Version Range:
TOXENV: 'devel,lowest'
Docs:
TOXENV: docs
Style:
TOXENV: style
- script: tox -e nightly pool:
displayName: Run tox vmImage: $[ variables.vmImage ]
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: $(python.version)
displayName: Use Python $(python.version)
- script: pip --disable-pip-version-check install -U tox
displayName: Install tox
- script: tox
displayName: Run tox

View file

@ -1,22 +1,29 @@
repos: repos:
- repo: https://github.com/asottile/pyupgrade
rev: v2.1.0
hooks:
- id: pyupgrade
args: ["--py36-plus"]
- repo: https://github.com/asottile/reorder_python_imports - repo: https://github.com/asottile/reorder_python_imports
rev: v1.5.0 rev: v2.1.0
hooks: hooks:
- id: reorder-python-imports - id: reorder-python-imports
name: Reorder Python imports (src, tests) name: Reorder Python imports (src, tests)
files: "^(?!examples/)" files: "^(?!examples/)"
args: ["--application-directories", ".:src"] args: ["--application-directories", "src"]
- repo: https://github.com/python/black - repo: https://github.com/python/black
rev: 19.3b0 rev: 19.10b0
hooks: hooks:
- id: black - id: black
- repo: https://gitlab.com/pycqa/flake8 - repo: https://gitlab.com/pycqa/flake8
rev: 3.7.7 rev: 3.7.9
hooks: hooks:
- id: flake8 - id: flake8
additional_dependencies: [flake8-bugbear] additional_dependencies:
- flake8-bugbear
- flake8-implicit-str-concat
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.2.3 rev: v2.5.0
hooks: hooks:
- id: check-byte-order-marker - id: check-byte-order-marker
- id: trailing-whitespace - id: trailing-whitespace

View file

@ -1,10 +1,11 @@
.. currentmodule:: flask .. currentmodule:: flask
Version 1.2.0 Version 2.0.0
------------- -------------
Unreleased Unreleased
- Drop support for Python 2 and 3.5.
- Add :meth:`sessions.SessionInterface.get_cookie_name` to allow - Add :meth:`sessions.SessionInterface.get_cookie_name` to allow
setting the session cookie name dynamically. :pr:`3369` setting the session cookie name dynamically. :pr:`3369`
- Add :meth:`Config.from_file` to load config using arbitrary file - Add :meth:`Config.from_file` to load config using arbitrary file
@ -452,7 +453,7 @@ Released 2016-05-29, codename Absinthe
- Added support to serializing top-level arrays to - Added support to serializing top-level arrays to
:func:`flask.jsonify`. This introduces a security risk in ancient :func:`flask.jsonify`. This introduces a security risk in ancient
browsers. See :ref:`json-security` for details. browsers.
- Added before_render_template signal. - Added before_render_template signal.
- Added ``**kwargs`` to :meth:`flask.Test.test_client` to support - Added ``**kwargs`` to :meth:`flask.Test.test_client` to support
passing additional keyword arguments to the constructor of passing additional keyword arguments to the constructor of
@ -567,8 +568,7 @@ Version 0.10
Released 2013-06-13, codename Limoncello Released 2013-06-13, codename Limoncello
- Changed default cookie serialization format from pickle to JSON to - Changed default cookie serialization format from pickle to JSON to
limit the impact an attacker can do if the secret key leaks. See limit the impact an attacker can do if the secret key leaks.
:ref:`upgrading-to-010` for more information.
- Added ``template_test`` methods in addition to the already existing - Added ``template_test`` methods in addition to the already existing
``template_filter`` method family. ``template_filter`` method family.
- Added ``template_global`` methods in addition to the already - Added ``template_global`` methods in addition to the already
@ -597,8 +597,7 @@ Released 2013-06-13, codename Limoncello
- Added an option to generate non-ascii encoded JSON which should - Added an option to generate non-ascii encoded JSON which should
result in less bytes being transmitted over the network. It's result in less bytes being transmitted over the network. It's
disabled by default to not cause confusion with existing libraries disabled by default to not cause confusion with existing libraries
that might expect ``flask.json.dumps`` to return bytestrings by that might expect ``flask.json.dumps`` to return bytes by default.
default.
- ``flask.g`` is now stored on the app context instead of the request - ``flask.g`` is now stored on the app context instead of the request
context. context.
- ``flask.g`` now gained a ``get()`` method for not erroring out on - ``flask.g`` now gained a ``get()`` method for not erroring out on
@ -762,7 +761,7 @@ Released 2011-09-29, codename Rakija
designated place to drop files that are modified at runtime (uploads designated place to drop files that are modified at runtime (uploads
etc.). Also this is conceptually only instance depending and outside etc.). Also this is conceptually only instance depending and outside
version control so it's the perfect place to put configuration files version control so it's the perfect place to put configuration files
etc. For more information see :ref:`instance-folders`. etc.
- Added the ``APPLICATION_ROOT`` configuration variable. - Added the ``APPLICATION_ROOT`` configuration variable.
- Implemented :meth:`~flask.testing.TestClient.session_transaction` to - Implemented :meth:`~flask.testing.TestClient.session_transaction` to
easily modify sessions from the test environment. easily modify sessions from the test environment.
@ -842,8 +841,7 @@ Released 2011-06-28, codename Grappa
- Added ``teardown_request`` decorator, for functions that should run - Added ``teardown_request`` decorator, for functions that should run
at the end of a request regardless of whether an exception occurred. at the end of a request regardless of whether an exception occurred.
Also the behavior for ``after_request`` was changed. It's now no Also the behavior for ``after_request`` was changed. It's now no
longer executed when an exception is raised. See longer executed when an exception is raised.
:ref:`upgrading-to-new-teardown-handling`
- Implemented :func:`flask.has_request_context` - Implemented :func:`flask.has_request_context`
- Deprecated ``init_jinja_globals``. Override the - Deprecated ``init_jinja_globals``. Override the
:meth:`~flask.Flask.create_jinja_environment` method instead to :meth:`~flask.Flask.create_jinja_environment` method instead to
@ -860,7 +858,7 @@ Released 2011-06-28, codename Grappa
errors that might occur during request processing (for instance errors that might occur during request processing (for instance
database connection errors, timeouts from remote resources etc.). database connection errors, timeouts from remote resources etc.).
- Blueprints can provide blueprint specific error handlers. - Blueprints can provide blueprint specific error handlers.
- Implemented generic :ref:`views` (class-based views). - Implemented generic class-based views.
Version 0.6.1 Version 0.6.1

View file

@ -135,8 +135,9 @@ depends on which part of Flask you're working on. Travis-CI will run the full
suite when you submit your pull request. suite when you submit your pull request.
The full test suite takes a long time to run because it tests multiple The full test suite takes a long time to run because it tests multiple
combinations of Python and dependencies. You need to have Python 2.7, 3.4, combinations of Python and dependencies. If you don't have a Python
3.5, 3.6, 3.7 and PyPy 2.7 installed to run all of the environments. Then run:: version installed, it will be skipped with a warning message at the end.
Run::
tox tox

View file

@ -58,12 +58,12 @@ Incoming Request Data
the following: the following:
============= ====================================================== ============= ======================================================
`path` ``u'/π/page.html'`` `path` ``'/π/page.html'``
`full_path` ``u'/π/page.html?x=y'`` `full_path` ``'/π/page.html?x=y'``
`script_root` ``u'/myapplication'`` `script_root` ``'/myapplication'``
`base_url` ``u'http://www.example.com/myapplication/π/page.html'`` `base_url` ``'http://www.example.com/myapplication/π/page.html'``
`url` ``u'http://www.example.com/myapplication/π/page.html?x=y'`` `url` ``'http://www.example.com/myapplication/π/page.html?x=y'``
`url_root` ``u'http://www.example.com/myapplication/'`` `url_root` ``'http://www.example.com/myapplication/'``
============= ====================================================== ============= ======================================================

View file

@ -52,14 +52,12 @@ singlehtml_sidebars = {"index": ["project.html", "localtoc.html"]}
html_static_path = ["_static"] html_static_path = ["_static"]
html_favicon = "_static/flask-icon.png" html_favicon = "_static/flask-icon.png"
html_logo = "_static/flask-icon.png" html_logo = "_static/flask-icon.png"
html_title = "Flask Documentation ({})".format(version) html_title = f"Flask Documentation ({version})"
html_show_sourcelink = False html_show_sourcelink = False
# LaTeX ---------------------------------------------------------------- # LaTeX ----------------------------------------------------------------
latex_documents = [ latex_documents = [(master_doc, f"Flask-{version}.tex", html_title, author, "manual")]
(master_doc, "Flask-{}.tex".format(version), html_title, author, "manual")
]
# Local Extensions ----------------------------------------------------- # Local Extensions -----------------------------------------------------
@ -76,9 +74,9 @@ def github_link(name, rawtext, text, lineno, inliner, options=None, content=None
words = None words = None
if packaging.version.parse(release).is_devrelease: if packaging.version.parse(release).is_devrelease:
url = "{0}master/{1}".format(base_url, text) url = f"{base_url}master/{text}"
else: else:
url = "{0}{1}/{2}".format(base_url, release, text) url = f"{base_url}{release}/{text}"
if words is None: if words is None:
words = url words = url

View file

@ -161,8 +161,8 @@ The following configuration values are used internally by Flask:
A secret key that will be used for securely signing the session cookie A secret key that will be used for securely signing the session cookie
and can be used for any other security related needs by extensions or your and can be used for any other security related needs by extensions or your
application. It should be a long random string of bytes, although unicode application. It should be a long random ``bytes`` or ``str``. For
is accepted too. For example, copy the output of this to your config:: example, copy the output of this to your config::
$ python -c 'import os; print(os.urandom(16))' $ python -c 'import os; print(os.urandom(16))'
b'_5#y2L"F4Q8z\n\xec]/' b'_5#y2L"F4Q8z\n\xec]/'
@ -302,10 +302,10 @@ The following configuration values are used internally by Flask:
.. py:data:: JSON_AS_ASCII .. py:data:: JSON_AS_ASCII
Serialize objects to ASCII-encoded JSON. If this is disabled, the JSON Serialize objects to ASCII-encoded JSON. If this is disabled, the
will be returned as a Unicode string, or encoded as ``UTF-8`` by JSON returned from ``jsonify`` will contain Unicode characters. This
``jsonify``. This has security implications when rendering the JSON into has security implications when rendering the JSON into JavaScript in
JavaScript in templates, and should typically remain enabled. templates, and should typically remain enabled.
Default: ``True`` Default: ``True``
@ -596,8 +596,8 @@ your configuration classes::
DB_SERVER = '192.168.1.56' DB_SERVER = '192.168.1.56'
@property @property
def DATABASE_URI(self): # Note: all caps def DATABASE_URI(self): # Note: all caps
return 'mysql://user@{}/foo'.format(self.DB_SERVER) return f"mysql://user@{self.DB_SERVER}/foo"
class ProductionConfig(Config): class ProductionConfig(Config):
"""Uses production database server.""" """Uses production database server."""
@ -678,7 +678,7 @@ locations are used:
- Installed module or package:: - Installed module or package::
$PREFIX/lib/python2.X/site-packages/myapp $PREFIX/lib/pythonX.Y/site-packages/myapp
$PREFIX/var/myapp-instance $PREFIX/var/myapp-instance
``$PREFIX`` is the prefix of your Python installation. This can be ``$PREFIX`` is the prefix of your Python installation. This can be

View file

@ -210,11 +210,6 @@ you have to modify your ``.wsgi`` file slightly.
Add the following lines to the top of your ``.wsgi`` file:: Add the following lines to the top of your ``.wsgi`` file::
activate_this = '/path/to/env/bin/activate_this.py'
execfile(activate_this, dict(__file__=activate_this))
For Python 3 add the following lines to the top of your ``.wsgi`` file::
activate_this = '/path/to/env/bin/activate_this.py' activate_this = '/path/to/env/bin/activate_this.py'
with open(activate_this) as file_: with open(activate_this) as file_:
exec(file_.read(), dict(__file__=activate_this)) exec(file_.read(), dict(__file__=activate_this))

View file

@ -109,14 +109,14 @@ has a certain understanding about how things work. On the surface they
all work the same: you tell the engine to evaluate a template with a set all work the same: you tell the engine to evaluate a template with a set
of variables and take the return value as string. of variables and take the return value as string.
But that's about where similarities end. Jinja2 for example has an But that's about where similarities end. Jinja2 for example has an
extensive filter system, a certain way to do template inheritance, support extensive filter system, a certain way to do template inheritance,
for reusable blocks (macros) that can be used from inside templates and support for reusable blocks (macros) that can be used from inside
also from Python code, uses Unicode for all operations, supports templates and also from Python code, supports iterative template
iterative template rendering, configurable syntax and more. On the other rendering, configurable syntax and more. On the other hand an engine
hand an engine like Genshi is based on XML stream evaluation, template like Genshi is based on XML stream evaluation, template inheritance by
inheritance by taking the availability of XPath into account and more. taking the availability of XPath into account and more. Mako on the
Mako on the other hand treats templates similar to Python modules. other hand treats templates similar to Python modules.
When it comes to connecting a template engine with an application or When it comes to connecting a template engine with an application or
framework there is more than just rendering templates. For instance, framework there is more than just rendering templates. For instance,

View file

@ -84,10 +84,7 @@ Design notes, legal information and changelog are here for the interested.
design design
htmlfaq htmlfaq
security security
unicode
extensiondev extensiondev
styleguide
upgrading
changelog changelog
license license
contributing contributing

View file

@ -3,11 +3,13 @@
Installation Installation
============ ============
Python Version Python Version
-------------- --------------
We recommend using the latest version of Python 3. Flask supports Python 3.5 We recommend using the latest version of Python. Flask supports Python
and newer, Python 2.7, and PyPy. 3.6 and newer.
Dependencies Dependencies
------------ ------------
@ -31,6 +33,7 @@ These distributions will be installed automatically when installing Flask.
.. _ItsDangerous: https://palletsprojects.com/p/itsdangerous/ .. _ItsDangerous: https://palletsprojects.com/p/itsdangerous/
.. _Click: https://palletsprojects.com/p/click/ .. _Click: https://palletsprojects.com/p/click/
Optional dependencies Optional dependencies
~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~
@ -51,6 +54,7 @@ use them if you install them.
.. _python-dotenv: https://github.com/theskumar/python-dotenv#readme .. _python-dotenv: https://github.com/theskumar/python-dotenv#readme
.. _watchdog: https://pythonhosted.org/watchdog/ .. _watchdog: https://pythonhosted.org/watchdog/
Virtual environments Virtual environments
-------------------- --------------------
@ -66,13 +70,9 @@ Virtual environments are independent groups of Python libraries, one for each
project. Packages installed for one project will not affect other projects or project. Packages installed for one project will not affect other projects or
the operating system's packages. the operating system's packages.
Python 3 comes bundled with the :mod:`venv` module to create virtual Python comes bundled with the :mod:`venv` module to create virtual
environments. If you're using a modern version of Python, you can continue on environments.
to the next section.
If you're using Python 2, see :ref:`install-install-virtualenv` first.
.. _install-create-env:
Create an environment Create an environment
~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~
@ -91,18 +91,6 @@ On Windows:
$ py -3 -m venv venv $ py -3 -m venv venv
If you needed to install virtualenv because you are using Python 2, use
the following command instead:
.. code-block:: sh
$ python2 -m virtualenv venv
On Windows:
.. code-block:: bat
> \Python27\Scripts\virtualenv.exe venv
.. _install-activate-env: .. _install-activate-env:
@ -121,12 +109,15 @@ On Windows:
> venv\Scripts\activate > venv\Scripts\activate
Your shell prompt will change to show the name of the activated environment. Your shell prompt will change to show the name of the activated
environment.
Install Flask Install Flask
------------- -------------
Within the activated environment, use the following command to install Flask: Within the activated environment, use the following command to install
Flask:
.. code-block:: sh .. code-block:: sh
@ -134,53 +125,3 @@ Within the activated environment, use the following command to install Flask:
Flask is now installed. Check out the :doc:`/quickstart` or go to the Flask is now installed. Check out the :doc:`/quickstart` or go to the
:doc:`Documentation Overview </index>`. :doc:`Documentation Overview </index>`.
Living on the edge
~~~~~~~~~~~~~~~~~~
If you want to work with the latest Flask code before it's released, install or
update the code from the master branch:
.. code-block:: sh
$ pip install -U https://github.com/pallets/flask/archive/master.tar.gz
.. _install-install-virtualenv:
Install virtualenv
------------------
If you are using Python 2, the venv module is not available. Instead,
install `virtualenv`_.
On Linux, virtualenv is provided by your package manager:
.. code-block:: sh
# Debian, Ubuntu
$ sudo apt-get install python-virtualenv
# CentOS, Fedora
$ sudo yum install python-virtualenv
# Arch
$ sudo pacman -S python-virtualenv
If you are on Mac OS X or Windows, download `get-pip.py`_, then:
.. code-block:: sh
$ sudo python2 Downloads/get-pip.py
$ sudo python2 -m pip install virtualenv
On Windows, as an administrator:
.. code-block:: bat
> \Python27\python.exe Downloads\get-pip.py
> \Python27\python.exe -m pip install virtualenv
Now you can return above and :ref:`install-create-env`.
.. _virtualenv: https://virtualenv.pypa.io/
.. _get-pip.py: https://bootstrap.pypa.io/get-pip.py

View file

@ -19,10 +19,6 @@ complex constructs that make larger applications easier to distribute:
other package. other package.
- **installation manager**: :command:`pip` can install other libraries for you. - **installation manager**: :command:`pip` can install other libraries for you.
If you have Python 2 (>=2.7.9) or Python 3 (>=3.4) installed from python.org,
you will already have pip and setuptools on your system. Otherwise, you
will need to install them yourself.
Flask itself, and all the libraries you can find on PyPI are distributed with Flask itself, and all the libraries you can find on PyPI are distributed with
either setuptools or distutils. either setuptools or distutils.

View file

@ -103,7 +103,7 @@ error messages could be displayed with a red background.
To flash a message with a different category, just use the second argument To flash a message with a different category, just use the second argument
to the :func:`~flask.flash` function:: to the :func:`~flask.flash` function::
flash(u'Invalid password provided', 'error') flash('Invalid password provided', 'error')
Inside the template you then have to tell the Inside the template you then have to tell the
:func:`~flask.get_flashed_messages` function to also return the :func:`~flask.get_flashed_messages` function to also return the

View file

@ -93,7 +93,7 @@ write this by having a function that calls into
name and a dot, and by wrapping `view_func` in a `LazyView` as needed. :: name and a dot, and by wrapping `view_func` in a `LazyView` as needed. ::
def url(import_name, url_rules=[], **options): def url(import_name, url_rules=[], **options):
view = LazyView('yourapplication.' + import_name) view = LazyView(f"yourapplication.{import_name}")
for url_rule in url_rules: for url_rule in url_rules:
app.add_url_rule(url_rule, view_func=view, **options) app.add_url_rule(url_rule, view_func=view, **options)

View file

@ -52,4 +52,4 @@ Example usage::
files = request.files files = request.files
# At this point the hash is fully constructed. # At this point the hash is fully constructed.
checksum = hash.hexdigest() checksum = hash.hexdigest()
return 'Hash was: %s' % checksum return f"Hash was: {checksum}"

View file

@ -39,7 +39,7 @@ Here's the example :file:`database.py` module for your application::
from sqlalchemy.orm import scoped_session, sessionmaker from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
engine = create_engine('sqlite:////tmp/test.db', convert_unicode=True) engine = create_engine('sqlite:////tmp/test.db')
db_session = scoped_session(sessionmaker(autocommit=False, db_session = scoped_session(sessionmaker(autocommit=False,
autoflush=False, autoflush=False,
bind=engine)) bind=engine))
@ -104,9 +104,9 @@ You can insert entries into the database like this:
Querying is simple as well: Querying is simple as well:
>>> User.query.all() >>> User.query.all()
[<User u'admin'>] [<User 'admin'>]
>>> User.query.filter(User.name == 'admin').first() >>> User.query.filter(User.name == 'admin').first()
<User u'admin'> <User 'admin'>
.. _SQLAlchemy: https://www.sqlalchemy.org/ .. _SQLAlchemy: https://www.sqlalchemy.org/
.. _declarative: .. _declarative:
@ -127,7 +127,7 @@ Here is an example :file:`database.py` module for your application::
from sqlalchemy import create_engine, MetaData from sqlalchemy import create_engine, MetaData
from sqlalchemy.orm import scoped_session, sessionmaker from sqlalchemy.orm import scoped_session, sessionmaker
engine = create_engine('sqlite:////tmp/test.db', convert_unicode=True) engine = create_engine('sqlite:////tmp/test.db')
metadata = MetaData() metadata = MetaData()
db_session = scoped_session(sessionmaker(autocommit=False, db_session = scoped_session(sessionmaker(autocommit=False,
autoflush=False, autoflush=False,
@ -179,7 +179,7 @@ you basically only need the engine::
from sqlalchemy import create_engine, MetaData, Table from sqlalchemy import create_engine, MetaData, Table
engine = create_engine('sqlite:////tmp/test.db', convert_unicode=True) engine = create_engine('sqlite:////tmp/test.db')
metadata = MetaData(bind=engine) metadata = MetaData(bind=engine)
Then you can either declare the tables in your code like in the examples Then you can either declare the tables in your code like in the examples
@ -200,19 +200,19 @@ SQLAlchemy will automatically commit for us.
To query your database, you use the engine directly or use a connection: To query your database, you use the engine directly or use a connection:
>>> users.select(users.c.id == 1).execute().first() >>> users.select(users.c.id == 1).execute().first()
(1, u'admin', u'admin@localhost') (1, 'admin', 'admin@localhost')
These results are also dict-like tuples: These results are also dict-like tuples:
>>> r = users.select(users.c.id == 1).execute().first() >>> r = users.select(users.c.id == 1).execute().first()
>>> r['name'] >>> r['name']
u'admin' 'admin'
You can also pass strings of SQL statements to the You can also pass strings of SQL statements to the
:meth:`~sqlalchemy.engine.base.Connection.execute` method: :meth:`~sqlalchemy.engine.base.Connection.execute` method:
>>> engine.execute('select * from users where id = :1', [1]).first() >>> engine.execute('select * from users where id = :1', [1]).first()
(1, u'admin', u'admin@localhost') (1, 'admin', 'admin@localhost')
For more information about SQLAlchemy, head over to the For more information about SQLAlchemy, head over to the
`website <https://www.sqlalchemy.org/>`_. `website <https://www.sqlalchemy.org/>`_.

View file

@ -21,7 +21,7 @@ data and to then invoke that function and pass it to a response object::
def generate_large_csv(): def generate_large_csv():
def generate(): def generate():
for row in iter_all_rows(): for row in iter_all_rows():
yield ','.join(row) + '\n' yield f"{','.join(row)}\n"
return Response(generate(), mimetype='text/csv') return Response(generate(), mimetype='text/csv')
Each ``yield`` expression is directly sent to the browser. Note though Each ``yield`` expression is directly sent to the browser. Note though

View file

@ -142,8 +142,7 @@ Here is the code for that decorator::
def decorated_function(*args, **kwargs): def decorated_function(*args, **kwargs):
template_name = template template_name = template
if template_name is None: if template_name is None:
template_name = request.endpoint \ template_name = f"'{request.endpoint.replace('.', '/')}.html'"
.replace('.', '/') + '.html'
ctx = f(*args, **kwargs) ctx = f(*args, **kwargs)
if ctx is None: if ctx is None:
ctx = {} ctx = {}

View file

@ -98,9 +98,9 @@ This macro accepts a couple of keyword arguments that are forwarded to
WTForm's field function, which renders the field for us. The keyword WTForm's field function, which renders the field for us. The keyword
arguments will be inserted as HTML attributes. So, for example, you can arguments will be inserted as HTML attributes. So, for example, you can
call ``render_field(form.username, class='username')`` to add a class to call ``render_field(form.username, class='username')`` to add a class to
the input element. Note that WTForms returns standard Python unicode the input element. Note that WTForms returns standard Python strings,
strings, so we have to tell Jinja2 that this data is already HTML-escaped so we have to tell Jinja2 that this data is already HTML-escaped with
with the ``|safe`` filter. the ``|safe`` filter.
Here is the :file:`register.html` template for the function we used above, which Here is the :file:`register.html` template for the function we used above, which
takes advantage of the :file:`_formhelpers.html` template: takes advantage of the :file:`_formhelpers.html` template:

View file

@ -293,8 +293,7 @@ Why would you want to build URLs using the URL reversing function
1. Reversing is often more descriptive than hard-coding the URLs. 1. Reversing is often more descriptive than hard-coding the URLs.
2. You can change your URLs in one go instead of needing to remember to 2. You can change your URLs in one go instead of needing to remember to
manually change hard-coded URLs. manually change hard-coded URLs.
3. URL building handles escaping of special characters and Unicode data 3. URL building handles escaping of special characters transparently.
transparently.
4. The generated paths are always absolute, avoiding unexpected behavior 4. The generated paths are always absolute, avoiding unexpected behavior
of relative paths in browsers. of relative paths in browsers.
5. If your application is placed outside the URL root, for example, in 5. If your application is placed outside the URL root, for example, in
@ -448,11 +447,11 @@ Here is a basic introduction to how the :class:`~markupsafe.Markup` class works:
>>> from markupsafe import Markup >>> from markupsafe import Markup
>>> Markup('<strong>Hello %s!</strong>') % '<blink>hacker</blink>' >>> Markup('<strong>Hello %s!</strong>') % '<blink>hacker</blink>'
Markup(u'<strong>Hello &lt;blink&gt;hacker&lt;/blink&gt;!</strong>') Markup('<strong>Hello &lt;blink&gt;hacker&lt;/blink&gt;!</strong>')
>>> Markup.escape('<blink>hacker</blink>') >>> Markup.escape('<blink>hacker</blink>')
Markup(u'&lt;blink&gt;hacker&lt;/blink&gt;') Markup('&lt;blink&gt;hacker&lt;/blink&gt;')
>>> Markup('<em>Marked up</em> &raquo; HTML').striptags() >>> Markup('<em>Marked up</em> &raquo; HTML').striptags()
u'Marked up \xbb HTML' 'Marked up \xbb HTML'
.. versionchanged:: 0.5 .. versionchanged:: 0.5
@ -610,8 +609,8 @@ Werkzeug provides for you::
@app.route('/upload', methods=['GET', 'POST']) @app.route('/upload', methods=['GET', 'POST'])
def upload_file(): def upload_file():
if request.method == 'POST': if request.method == 'POST':
f = request.files['the_file'] file = request.files['the_file']
f.save('/var/www/uploads/' + secure_filename(f.filename)) file.save(f"/var/www/uploads/{secure_filename(f.filename)}")
... ...
For some better examples, checkout the :ref:`uploading-files` pattern. For some better examples, checkout the :ref:`uploading-files` pattern.

View file

@ -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)

View file

@ -222,8 +222,8 @@ functions)::
@app.context_processor @app.context_processor
def utility_processor(): def utility_processor():
def format_price(amount, currency=u'€'): def format_price(amount, currency="€"):
return u'{0:.2f}{1}'.format(amount, currency) return f"{amount:.2f}{currency}"
return dict(format_price=format_price) return dict(format_price=format_price)
The context processor above makes the `format_price` function available to all The context processor above makes the `format_price` function available to all

View file

@ -165,16 +165,19 @@ invalid credentials. Add this new test function::
def test_login_logout(client): def test_login_logout(client):
"""Make sure login and logout works.""" """Make sure login and logout works."""
rv = login(client, flaskr.app.config['USERNAME'], flaskr.app.config['PASSWORD']) username = flaskr.app.config["USERNAME"]
password = flaskr.app.config["PASSWORD"]
rv = login(client, username, password)
assert b'You were logged in' in rv.data assert b'You were logged in' in rv.data
rv = logout(client) rv = logout(client)
assert b'You were logged out' in rv.data assert b'You were logged out' in rv.data
rv = login(client, flaskr.app.config['USERNAME'] + 'x', flaskr.app.config['PASSWORD']) rv = login(client, f"{username}x", password)
assert b'Invalid username' in rv.data assert b'Invalid username' in rv.data
rv = login(client, flaskr.app.config['USERNAME'], flaskr.app.config['PASSWORD'] + 'x') rv = login(client, username, f'{password}x')
assert b'Invalid password' in rv.data assert b'Invalid password' in rv.data
Test Adding Messages Test Adding Messages

View file

@ -207,7 +207,7 @@ it from each view.
).fetchone() ).fetchone()
if post is None: if post is None:
abort(404, "Post id {0} doesn't exist.".format(id)) abort(404, f"Post id {id} doesn't exist.")
if check_author and post['author_id'] != g.user['id']: if check_author and post['author_id'] != g.user['id']:
abort(403) abort(403)

View file

@ -301,8 +301,8 @@ URL when the register view redirects to the login view.
:attr:`~Response.data` contains the body of the response as bytes. If :attr:`~Response.data` contains the body of the response as bytes. If
you expect a certain value to render on the page, check that it's in you expect a certain value to render on the page, check that it's in
``data``. Bytes must be compared to bytes. If you want to compare ``data``. Bytes must be compared to bytes. If you want to compare text,
Unicode text, use :meth:`get_data(as_text=True) <werkzeug.wrappers.BaseResponse.get_data>` use :meth:`get_data(as_text=True) <werkzeug.wrappers.BaseResponse.get_data>`
instead. instead.
``pytest.mark.parametrize`` tells Pytest to run the same test function ``pytest.mark.parametrize`` tells Pytest to run the same test function

View file

@ -94,7 +94,7 @@ write templates to generate the HTML form.
elif db.execute( elif db.execute(
'SELECT id FROM user WHERE username = ?', (username,) 'SELECT id FROM user WHERE username = ?', (username,)
).fetchone() is not None: ).fetchone() is not None:
error = 'User {} is already registered.'.format(username) error = f"User {username} is already registered."
if error is None: if error is None:
db.execute( db.execute(

View file

@ -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.

View file

@ -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.

View file

@ -2,4 +2,4 @@ from flask import Flask
app = Flask(__name__) app = Flask(__name__)
from js_example import views from js_example import views # noqa: F401

View file

@ -8,7 +8,7 @@ from js_example import app
@app.route("/", defaults={"js": "plain"}) @app.route("/", defaults={"js": "plain"})
@app.route("/<any(plain, jquery, fetch):js>") @app.route("/<any(plain, jquery, fetch):js>")
def index(js): def index(js):
return render_template("{0}.html".format(js), js=js) return render_template(f"{js}.html", js=js)
@app.route("/add", methods=["POST"]) @app.route("/add", methods=["POST"])

View file

@ -1,9 +1,7 @@
import io
from setuptools import find_packages from setuptools import find_packages
from setuptools import setup from setuptools import setup
with io.open("README.rst", "rt", encoding="utf8") as f: with open("README.rst", encoding="utf8") as f:
readme = f.read() readme = f.read()
setup( setup(

View file

@ -64,7 +64,7 @@ def register():
db.execute("SELECT id FROM user WHERE username = ?", (username,)).fetchone() db.execute("SELECT id FROM user WHERE username = ?", (username,)).fetchone()
is not None is not None
): ):
error = "User {0} is already registered.".format(username) error = f"User {username} is already registered."
if error is None: if error is None:
# the name is available, store it in the database and go to # the name is available, store it in the database and go to

View file

@ -49,7 +49,7 @@ def get_post(id, check_author=True):
) )
if post is None: if post is None:
abort(404, "Post id {0} doesn't exist.".format(id)) abort(404, f"Post id {id} doesn't exist.")
if check_author and post["author_id"] != g.user["id"]: if check_author and post["author_id"] != g.user["id"]:
abort(403) abort(403)

View file

@ -1,9 +1,7 @@
import io
from setuptools import find_packages from setuptools import find_packages
from setuptools import setup from setuptools import setup
with io.open("README.rst", "rt", encoding="utf8") as f: with open("README.rst", encoding="utf8") as f:
readme = f.read() readme = f.read()
setup( setup(

View file

@ -44,7 +44,7 @@ def runner(app):
return app.test_cli_runner() return app.test_cli_runner()
class AuthActions(object): class AuthActions:
def __init__(self, client): def __init__(self, client):
self._client = client self._client = client

View file

@ -17,7 +17,7 @@ def test_get_close_db(app):
def test_init_db_command(runner, monkeypatch): def test_init_db_command(runner, monkeypatch):
class Recorder(object): class Recorder:
called = False called = False
def fake_init_db(): def fake_init_db():

View file

@ -1,8 +1,7 @@
[bdist_wheel]
universal = true
[tool:pytest] [tool:pytest]
testpaths = tests testpaths = tests
filterwarnings =
error
[coverage:run] [coverage:run]
branch = True branch = True
@ -12,9 +11,8 @@ source =
[coverage:paths] [coverage:paths]
source = source =
src/flask src
.tox/*/lib/python*/site-packages/flask */site-packages
.tox/*/site-packages/flask
[flake8] [flake8]
# B = bugbear # B = bugbear
@ -22,7 +20,8 @@ source =
# F = flake8 pyflakes # F = flake8 pyflakes
# W = pycodestyle warnings # W = pycodestyle warnings
# B9 = bugbear opinions # B9 = bugbear opinions
select = B, E, F, W, B9 # ISC = implicit-str-concat
select = B, E, F, W, B9, ISC
ignore = ignore =
# slice notation whitespace, invalid # slice notation whitespace, invalid
E203 E203
@ -37,6 +36,5 @@ ignore =
# up to 88 allowed by bugbear B950 # up to 88 allowed by bugbear B950
max-line-length = 80 max-line-length = 80
per-file-ignores = per-file-ignores =
# __init__ modules export names # __init__ module exports names
**/__init__.py: F401 src/flask/__init__.py: F401
src/flask/_compat.py: E731, B301, F401

View file

@ -1,13 +1,12 @@
import io
import re import re
from setuptools import find_packages from setuptools import find_packages
from setuptools import setup from setuptools import setup
with io.open("README.rst", "rt", encoding="utf8") as f: with open("README.rst", encoding="utf8") as f:
readme = f.read() readme = f.read()
with io.open("src/flask/__init__.py", "rt", encoding="utf8") as f: with open("src/flask/__init__.py", encoding="utf8") as f:
version = re.search(r'__version__ = "(.*?)"', f.read()).group(1) version = re.search(r'__version__ = "(.*?)"', f.read()).group(1)
setup( setup(
@ -34,15 +33,6 @@ setup(
"License :: OSI Approved :: BSD License", "License :: OSI Approved :: BSD License",
"Operating System :: OS Independent", "Operating System :: OS Independent",
"Programming Language :: Python", "Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
"Topic :: Internet :: WWW/HTTP :: WSGI :: Application", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
"Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Application Frameworks",
@ -51,7 +41,7 @@ setup(
packages=find_packages("src"), packages=find_packages("src"),
package_dir={"": "src"}, package_dir={"": "src"},
include_package_data=True, include_package_data=True,
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", python_requires=">=3.6",
install_requires=[ install_requires=[
"Werkzeug>=0.15", "Werkzeug>=0.15",
"Jinja2>=2.10.1", "Jinja2>=2.10.1",

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
flask flask
~~~~~ ~~~~~
@ -17,7 +16,6 @@ from werkzeug.exceptions import abort
from werkzeug.utils import redirect from werkzeug.utils import redirect
from . import json from . import json
from ._compat import json_available
from .app import Flask from .app import Flask
from .app import Request from .app import Request
from .app import Response from .app import Response
@ -57,4 +55,4 @@ from .signals import template_rendered
from .templating import render_template from .templating import render_template
from .templating import render_template_string from .templating import render_template_string
__version__ = "1.2.0.dev" __version__ = "2.0.0.dev"

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
flask.__main__ flask.__main__
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~

View file

@ -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)

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
flask.app flask.app
~~~~~~~~~ ~~~~~~~~~
@ -10,7 +9,6 @@
""" """
import os import os
import sys import sys
import warnings
from datetime import timedelta from datetime import timedelta
from functools import update_wrapper from functools import update_wrapper
from itertools import chain from itertools import chain
@ -32,10 +30,6 @@ from werkzeug.wrappers import BaseResponse
from . import cli from . import cli
from . import json from . import json
from ._compat import integer_types
from ._compat import reraise
from ._compat import string_types
from ._compat import text_type
from .config import Config from .config import Config
from .config import ConfigAttribute from .config import ConfigAttribute
from .ctx import _AppCtxGlobals from .ctx import _AppCtxGlobals
@ -191,11 +185,9 @@ class Flask(_PackageBoundObject):
for loading the config are assumed to for loading the config are assumed to
be relative to the instance path instead be relative to the instance path instead
of the application root. of the application root.
:param root_path: Flask by default will automatically calculate the path :param root_path: The path to the root of the application files.
to the root of the application. In certain situations This should only be set manually when it can't be detected
this cannot be achieved (for instance if the package automatically, such as for namespace packages.
is a Python 3 namespace package) and needs to be
manually defined.
""" """
#: The class that is used for request objects. See :class:`~flask.Request` #: The class that is used for request objects. See :class:`~flask.Request`
@ -593,7 +585,7 @@ class Flask(_PackageBoundObject):
bool(static_host) == host_matching bool(static_host) == host_matching
), "Invalid static_host/host_matching combination" ), "Invalid static_host/host_matching combination"
self.add_url_rule( self.add_url_rule(
self.static_url_path + "/<path:filename>", f"{self.static_url_path}/<path:filename>",
endpoint="static", endpoint="static",
host=static_host, host=static_host,
view_func=self.send_static_file, view_func=self.send_static_file,
@ -719,7 +711,7 @@ class Flask(_PackageBoundObject):
prefix, package_path = find_package(self.import_name) prefix, package_path = find_package(self.import_name)
if prefix is None: if prefix is None:
return os.path.join(package_path, "instance") return os.path.join(package_path, "instance")
return os.path.join(prefix, "var", self.name + "-instance") return os.path.join(prefix, "var", f"{self.name}-instance")
def open_instance_resource(self, resource, mode="rb"): def open_instance_resource(self, resource, mode="rb"):
"""Opens a resource from the application's instance folder """Opens a resource from the application's instance folder
@ -1068,70 +1060,6 @@ class Flask(_PackageBoundObject):
return cls(self, **kwargs) return cls(self, **kwargs)
def open_session(self, request):
"""Creates or opens a new session. Default implementation stores all
session data in a signed cookie. This requires that the
:attr:`secret_key` is set. Instead of overriding this method
we recommend replacing the :class:`session_interface`.
.. deprecated: 1.0
Will be removed in 2.0. Use
``session_interface.open_session`` instead.
:param request: an instance of :attr:`request_class`.
"""
warnings.warn(
DeprecationWarning(
'"open_session" is deprecated and will be removed in'
' 2.0. Use "session_interface.open_session" instead.'
)
)
return self.session_interface.open_session(self, request)
def save_session(self, session, response):
"""Saves the session if it needs updates. For the default
implementation, check :meth:`open_session`. Instead of overriding this
method we recommend replacing the :class:`session_interface`.
.. deprecated: 1.0
Will be removed in 2.0. Use
``session_interface.save_session`` instead.
:param session: the session to be saved (a
:class:`~werkzeug.contrib.securecookie.SecureCookie`
object)
:param response: an instance of :attr:`response_class`
"""
warnings.warn(
DeprecationWarning(
'"save_session" is deprecated and will be removed in'
' 2.0. Use "session_interface.save_session" instead.'
)
)
return self.session_interface.save_session(self, session, response)
def make_null_session(self):
"""Creates a new instance of a missing session. Instead of overriding
this method we recommend replacing the :class:`session_interface`.
.. deprecated: 1.0
Will be removed in 2.0. Use
``session_interface.make_null_session`` instead.
.. versionadded:: 0.7
"""
warnings.warn(
DeprecationWarning(
'"make_null_session" is deprecated and will be removed'
' in 2.0. Use "session_interface.make_null_session"'
" instead."
)
)
return self.session_interface.make_null_session(self)
@setupmethod @setupmethod
def register_blueprint(self, blueprint, **options): def register_blueprint(self, blueprint, **options):
"""Register a :class:`~flask.Blueprint` on the application. Keyword """Register a :class:`~flask.Blueprint` on the application. Keyword
@ -1156,10 +1084,11 @@ class Flask(_PackageBoundObject):
if blueprint.name in self.blueprints: if blueprint.name in self.blueprints:
assert self.blueprints[blueprint.name] is blueprint, ( assert self.blueprints[blueprint.name] is blueprint, (
"A name collision occurred between blueprints %r and %r. Both" "A name collision occurred between blueprints"
' share the same name "%s". Blueprints that are created on the' f" {blueprint!r} and {self.blueprints[blueprint.name]!r}."
" fly need unique names." f" Both share the same name {blueprint.name!r}."
% (blueprint, self.blueprints[blueprint.name], blueprint.name) f" Blueprints that are created on the fly need unique"
f" names."
) )
else: else:
self.blueprints[blueprint.name] = blueprint self.blueprints[blueprint.name] = blueprint
@ -1182,7 +1111,7 @@ class Flask(_PackageBoundObject):
endpoint=None, endpoint=None,
view_func=None, view_func=None,
provide_automatic_options=None, provide_automatic_options=None,
**options **options,
): ):
"""Connects a URL rule. Works exactly like the :meth:`route` """Connects a URL rule. Works exactly like the :meth:`route`
decorator. If a view_func is provided it will be registered with the decorator. If a view_func is provided it will be registered with the
@ -1246,12 +1175,12 @@ class Flask(_PackageBoundObject):
# a tuple of only ``GET`` as default. # a tuple of only ``GET`` as default.
if methods is None: if methods is None:
methods = getattr(view_func, "methods", None) or ("GET",) methods = getattr(view_func, "methods", None) or ("GET",)
if isinstance(methods, string_types): if isinstance(methods, str):
raise TypeError( raise TypeError(
"Allowed methods have to be iterables of strings, " "Allowed methods must be a list of strings, for"
'for example: @app.route(..., methods=["POST"])' ' example: @app.route(..., methods=["POST"])'
) )
methods = set(item.upper() for item in methods) methods = {item.upper() for item in methods}
# Methods that should always be added # Methods that should always be added
required_methods = set(getattr(view_func, "required_methods", ())) required_methods = set(getattr(view_func, "required_methods", ()))
@ -1281,8 +1210,8 @@ class Flask(_PackageBoundObject):
old_func = self.view_functions.get(endpoint) old_func = self.view_functions.get(endpoint)
if old_func is not None and old_func != view_func: if old_func is not None and old_func != view_func:
raise AssertionError( raise AssertionError(
"View function mapping is overwriting an " "View function mapping is overwriting an existing"
"existing endpoint function: %s" % endpoint f" endpoint function: {endpoint}"
) )
self.view_functions[endpoint] = view_func self.view_functions[endpoint] = view_func
@ -1345,7 +1274,7 @@ class Flask(_PackageBoundObject):
:param exc_class_or_code: Any exception class, or an HTTP status :param exc_class_or_code: Any exception class, or an HTTP status
code as an integer. code as an integer.
""" """
if isinstance(exc_class_or_code, integer_types): if isinstance(exc_class_or_code, int):
exc_class = default_exceptions[exc_class_or_code] exc_class = default_exceptions[exc_class_or_code]
else: else:
exc_class = exc_class_or_code exc_class = exc_class_or_code
@ -1413,17 +1342,18 @@ class Flask(_PackageBoundObject):
""" """
if isinstance(code_or_exception, HTTPException): # old broken behavior if isinstance(code_or_exception, HTTPException): # old broken behavior
raise ValueError( raise ValueError(
"Tried to register a handler for an exception instance {0!r}." "Tried to register a handler for an exception instance"
" Handlers can only be registered for exception classes or" f" {code_or_exception!r}. Handlers can only be"
" HTTP error codes.".format(code_or_exception) " registered for exception classes or HTTP error codes."
) )
try: try:
exc_class, code = self._get_exc_class_and_code(code_or_exception) exc_class, code = self._get_exc_class_and_code(code_or_exception)
except KeyError: except KeyError:
raise KeyError( raise KeyError(
"'{0}' is not a recognized HTTP error code. Use a subclass of" f"'{code_or_exception}' is not a recognized HTTP error"
" HTTPException with that code instead.".format(code_or_exception) " code. Use a subclass of HTTPException with that code"
" instead."
) )
handlers = self.error_handler_spec.setdefault(key, {}).setdefault(code, {}) handlers = self.error_handler_spec.setdefault(key, {}).setdefault(code, {})
@ -1794,13 +1724,6 @@ class Flask(_PackageBoundObject):
.. versionadded:: 0.7 .. versionadded:: 0.7
""" """
exc_type, exc_value, tb = sys.exc_info()
assert exc_value is e
# ensure not to trash sys.exc_info() at that point in case someone
# wants the traceback preserved in handle_http_exception. Of course
# we cannot prevent users from trashing it themselves in a custom
# trap_http_exception method so that's their fault then.
if isinstance(e, BadRequestKeyError): if isinstance(e, BadRequestKeyError):
if self.debug or self.config["TRAP_BAD_REQUEST_ERRORS"]: if self.debug or self.config["TRAP_BAD_REQUEST_ERRORS"]:
e.show_exception = True e.show_exception = True
@ -1809,7 +1732,7 @@ class Flask(_PackageBoundObject):
# message, add it in manually. # message, add it in manually.
# TODO: clean up once Werkzeug >= 0.15.5 is required # TODO: clean up once Werkzeug >= 0.15.5 is required
if e.args[0] not in e.get_description(): if e.args[0] not in e.get_description():
e.description = "KeyError: '{}'".format(*e.args) e.description = f"KeyError: {e.args[0]!r}"
elif not hasattr(BadRequestKeyError, "show_exception"): elif not hasattr(BadRequestKeyError, "show_exception"):
e.args = () e.args = ()
@ -1819,7 +1742,8 @@ class Flask(_PackageBoundObject):
handler = self._find_error_handler(e) handler = self._find_error_handler(e)
if handler is None: if handler is None:
reraise(exc_type, exc_value, tb) raise
return handler(e) return handler(e)
def handle_exception(self, e): def handle_exception(self, e):
@ -1856,20 +1780,18 @@ class Flask(_PackageBoundObject):
.. versionadded:: 0.3 .. versionadded:: 0.3
""" """
exc_type, exc_value, tb = sys.exc_info() exc_info = sys.exc_info()
got_request_exception.send(self, exception=e) got_request_exception.send(self, exception=e)
if self.propagate_exceptions: if self.propagate_exceptions:
# if we want to repropagate the exception, we can attempt to # Re-raise if called with an active exception, otherwise
# raise it with the whole traceback in case we can do that # raise the passed in exception.
# (the function was actually called from the except part) if exc_info[1] is e:
# otherwise, we just raise the error again raise
if exc_value is e:
reraise(exc_type, exc_value, tb)
else:
raise e
self.log_exception((exc_type, exc_value, tb)) raise e
self.log_exception(exc_info)
server_error = InternalServerError() server_error = InternalServerError()
# TODO: pass as param when Werkzeug>=1.0.0 is required # TODO: pass as param when Werkzeug>=1.0.0 is required
# TODO: also remove note about this from docstring and docs # TODO: also remove note about this from docstring and docs
@ -1890,7 +1812,7 @@ class Flask(_PackageBoundObject):
.. versionadded:: 0.8 .. versionadded:: 0.8
""" """
self.logger.error( self.logger.error(
"Exception on %s [%s]" % (request.path, request.method), exc_info=exc_info f"Exception on {request.path} [{request.method}]", exc_info=exc_info
) )
def raise_routing_exception(self, request): def raise_routing_exception(self, request):
@ -2026,11 +1948,11 @@ class Flask(_PackageBoundObject):
without returning, is not allowed. The following types are allowed without returning, is not allowed. The following types are allowed
for ``view_rv``: for ``view_rv``:
``str`` (``unicode`` in Python 2) ``str``
A response object is created with the string encoded to UTF-8 A response object is created with the string encoded to UTF-8
as the body. as the body.
``bytes`` (``str`` in Python 2) ``bytes``
A response object is created with the bytes as the body. A response object is created with the bytes as the body.
``dict`` ``dict``
@ -2086,14 +2008,14 @@ class Flask(_PackageBoundObject):
# the body must not be None # the body must not be None
if rv is None: if rv is None:
raise TypeError( raise TypeError(
'The view function for "{}" did not return a valid response. The' f"The view function for {request.endpoint!r} did not"
" function either returned None or ended without a return" " return a valid response. The function either returned"
" statement.".format(request.endpoint) " None or ended without a return statement."
) )
# make sure the body is an instance of the response class # make sure the body is an instance of the response class
if not isinstance(rv, self.response_class): if not isinstance(rv, self.response_class):
if isinstance(rv, (text_type, bytes, bytearray)): if isinstance(rv, (str, bytes, bytearray)):
# let the response class set the status and headers instead of # let the response class set the status and headers instead of
# waiting to do it manually, so that the class can handle any # waiting to do it manually, so that the class can handle any
# special logic # special logic
@ -2107,24 +2029,23 @@ class Flask(_PackageBoundObject):
try: try:
rv = self.response_class.force_type(rv, request.environ) rv = self.response_class.force_type(rv, request.environ)
except TypeError as e: except TypeError as e:
new_error = TypeError( raise TypeError(
"{e}\nThe view function did not return a valid" f"{e}\nThe view function did not return a valid"
" response. The return type must be a string, dict, tuple," " response. The return type must be a string,"
" Response instance, or WSGI callable, but it was a" " dict, tuple, Response instance, or WSGI"
" {rv.__class__.__name__}.".format(e=e, rv=rv) f" callable, but it was a {type(rv).__name__}."
) ).with_traceback(sys.exc_info()[2])
reraise(TypeError, new_error, sys.exc_info()[2])
else: else:
raise TypeError( raise TypeError(
"The view function did not return a valid" "The view function did not return a valid"
" response. The return type must be a string, dict, tuple," " response. The return type must be a string,"
" Response instance, or WSGI callable, but it was a" " dict, tuple, Response instance, or WSGI"
" {rv.__class__.__name__}.".format(rv=rv) f" callable, but it was a {type(rv).__name__}."
) )
# prefer the status if it was provided # prefer the status if it was provided
if status is not None: if status is not None:
if isinstance(status, (text_type, bytes, bytearray)): if isinstance(status, (str, bytes, bytearray)):
rv.status = status rv.status = status
else: else:
rv.status_code = status rv.status_code = status
@ -2188,23 +2109,24 @@ class Flask(_PackageBoundObject):
func(endpoint, values) func(endpoint, values)
def handle_url_build_error(self, error, endpoint, values): def handle_url_build_error(self, error, endpoint, values):
"""Handle :class:`~werkzeug.routing.BuildError` on :meth:`url_for`. """Handle :class:`~werkzeug.routing.BuildError` on
:meth:`url_for`.
""" """
exc_type, exc_value, tb = sys.exc_info()
for handler in self.url_build_error_handlers: for handler in self.url_build_error_handlers:
try: try:
rv = handler(error, endpoint, values) rv = handler(error, endpoint, values)
except BuildError as e:
# make error available outside except block
error = e
else:
if rv is not None: if rv is not None:
return rv return rv
except BuildError as e:
# make error available outside except block (py3)
error = e
# At this point we want to reraise the exception. If the error is # Re-raise if called with an active exception, otherwise raise
# still the same one we can reraise it with the original traceback, # the passed in exception.
# otherwise we raise it from here. if error is sys.exc_info()[1]:
if error is exc_value: raise
reraise(exc_type, exc_value, tb)
raise error raise error
def preprocess_request(self): def preprocess_request(self):
@ -2455,4 +2377,4 @@ class Flask(_PackageBoundObject):
return self.wsgi_app(environ, start_response) return self.wsgi_app(environ, start_response)
def __repr__(self): def __repr__(self):
return "<%s %r>" % (self.__class__.__name__, self.name) return f"<{type(self).__name__} {self.name!r}>"

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
flask.blueprints flask.blueprints
~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~
@ -18,7 +17,7 @@ from .helpers import _PackageBoundObject
_sentinel = object() _sentinel = object()
class BlueprintSetupState(object): class BlueprintSetupState:
"""Temporary holder object for registering a blueprint with the """Temporary holder object for registering a blueprint with the
application. An instance of this class is created by the application. An instance of this class is created by the
:meth:`~flask.Blueprint.make_setup_state` method and later passed :meth:`~flask.Blueprint.make_setup_state` method and later passed
@ -80,10 +79,10 @@ class BlueprintSetupState(object):
defaults = dict(defaults, **options.pop("defaults")) defaults = dict(defaults, **options.pop("defaults"))
self.app.add_url_rule( self.app.add_url_rule(
rule, rule,
"%s.%s" % (self.blueprint.name, endpoint), f"{self.blueprint.name}.{endpoint}",
view_func, view_func,
defaults=defaults, defaults=defaults,
**options **options,
) )
@ -247,7 +246,7 @@ class Blueprint(_PackageBoundObject):
if self.has_static_folder: if self.has_static_folder:
state.add_url_rule( state.add_url_rule(
self.static_url_path + "/<path:filename>", f"{self.static_url_path}/<path:filename>",
view_func=self.send_static_file, view_func=self.send_static_file,
endpoint="static", endpoint="static",
) )

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
flask.cli flask.cli
~~~~~~~~~ ~~~~~~~~~
@ -8,8 +7,6 @@
:copyright: 2010 Pallets :copyright: 2010 Pallets
:license: BSD-3-Clause :license: BSD-3-Clause
""" """
from __future__ import print_function
import ast import ast
import inspect import inspect
import os import os
@ -25,10 +22,6 @@ from threading import Thread
import click import click
from werkzeug.utils import import_string from werkzeug.utils import import_string
from ._compat import getargspec
from ._compat import itervalues
from ._compat import reraise
from ._compat import text_type
from .globals import current_app from .globals import current_app
from .helpers import get_debug_flag from .helpers import get_debug_flag
from .helpers import get_env from .helpers import get_env
@ -63,19 +56,19 @@ def find_best_app(script_info, module):
return app return app
# Otherwise find the only object that is a Flask instance. # Otherwise find the only object that is a Flask instance.
matches = [v for v in itervalues(module.__dict__) if isinstance(v, Flask)] matches = [v for v in module.__dict__.values() if isinstance(v, Flask)]
if len(matches) == 1: if len(matches) == 1:
return matches[0] return matches[0]
elif len(matches) > 1: elif len(matches) > 1:
raise NoAppException( raise NoAppException(
'Detected multiple Flask applications in module "{module}". Use ' "Detected multiple Flask applications in module"
'"FLASK_APP={module}:name" to specify the correct ' f" {module.__name__!r}. Use 'FLASK_APP={module.__name__}:name'"
"one.".format(module=module.__name__) f" to specify the correct one."
) )
# Search for app factory functions. # Search for app factory functions.
for attr_name in ("create_app", "make_app"): for attr_name in {"create_app", "make_app"}:
app_factory = getattr(module, attr_name, None) app_factory = getattr(module, attr_name, None)
if inspect.isfunction(app_factory): if inspect.isfunction(app_factory):
@ -88,15 +81,16 @@ def find_best_app(script_info, module):
if not _called_with_wrong_args(app_factory): if not _called_with_wrong_args(app_factory):
raise raise
raise NoAppException( raise NoAppException(
'Detected factory "{factory}" in module "{module}", but ' f"Detected factory {attr_name!r} in module {module.__name__!r},"
"could not call it without arguments. Use " " but could not call it without arguments. Use"
"\"FLASK_APP='{module}:{factory}(args)'\" to specify " f" \"FLASK_APP='{module.__name__}:{attr_name}(args)'\""
"arguments.".format(factory=attr_name, module=module.__name__) " to specify arguments."
) )
raise NoAppException( raise NoAppException(
'Failed to find Flask application or factory in module "{module}". ' "Failed to find Flask application or factory in module"
'Use "FLASK_APP={module}:name to specify one.'.format(module=module.__name__) f" {module.__name__!r}. Use 'FLASK_APP={module.__name__}:name'"
" to specify one."
) )
@ -105,7 +99,7 @@ def call_factory(script_info, app_factory, arguments=()):
of arguments. Checks for the existence of a script_info argument and calls of arguments. Checks for the existence of a script_info argument and calls
the app_factory depending on that and the arguments provided. the app_factory depending on that and the arguments provided.
""" """
args_spec = getargspec(app_factory) args_spec = inspect.getfullargspec(app_factory)
arg_names = args_spec.args arg_names = args_spec.args
arg_defaults = args_spec.defaults arg_defaults = args_spec.defaults
@ -157,8 +151,7 @@ def find_app_by_string(script_info, module, app_name):
if not match: if not match:
raise NoAppException( raise NoAppException(
'"{name}" is not a valid variable name or function ' f"{app_name!r} is not a valid variable name or function expression."
"expression.".format(name=app_name)
) )
name, args = match.groups() name, args = match.groups()
@ -171,12 +164,9 @@ def find_app_by_string(script_info, module, app_name):
if inspect.isfunction(attr): if inspect.isfunction(attr):
if args: if args:
try: try:
args = ast.literal_eval("({args},)".format(args=args)) args = ast.literal_eval(f"({args},)")
except (ValueError, SyntaxError) as e: except (ValueError, SyntaxError):
raise NoAppException( raise NoAppException(f"Could not parse the arguments in {app_name!r}.")
"Could not parse the arguments in "
'"{app_name}".'.format(e=e, app_name=app_name)
)
else: else:
args = () args = ()
@ -187,10 +177,9 @@ def find_app_by_string(script_info, module, app_name):
raise raise
raise NoAppException( raise NoAppException(
'{e}\nThe factory "{app_name}" in module "{module}" could not ' f"{e}\nThe factory {app_name!r} in module"
"be called with the specified arguments.".format( f" {module.__name__!r} could not be called with the"
e=e, app_name=app_name, module=module.__name__ " specified arguments."
)
) )
else: else:
app = attr app = attr
@ -199,8 +188,8 @@ def find_app_by_string(script_info, module, app_name):
return app return app
raise NoAppException( raise NoAppException(
"A valid Flask application was not obtained from " "A valid Flask application was not obtained from"
'"{module}:{app_name}".'.format(module=module.__name__, app_name=app_name) f" '{module.__name__}:{app_name}'."
) )
@ -241,13 +230,13 @@ def locate_app(script_info, module_name, app_name, raise_if_not_found=True):
except ImportError: except ImportError:
# Reraise the ImportError if it occurred within the imported module. # Reraise the ImportError if it occurred within the imported module.
# Determine this by checking whether the trace has a depth > 1. # Determine this by checking whether the trace has a depth > 1.
if sys.exc_info()[-1].tb_next: if sys.exc_info()[2].tb_next:
raise NoAppException( raise NoAppException(
'While importing "{name}", an ImportError was raised:' f"While importing {module_name!r}, an ImportError was"
"\n\n{tb}".format(name=module_name, tb=traceback.format_exc()) f" raised:\n\n{traceback.format_exc()}"
) )
elif raise_if_not_found: elif raise_if_not_found:
raise NoAppException('Could not import "{name}".'.format(name=module_name)) raise NoAppException(f"Could not import {module_name!r}.")
else: else:
return return
@ -266,14 +255,10 @@ def get_version(ctx, param, value):
import werkzeug import werkzeug
from . import __version__ from . import __version__
message = "Python %(python)s\nFlask %(flask)s\nWerkzeug %(werkzeug)s"
click.echo( click.echo(
message f"Python {platform.python_version()}\n"
% { f"Flask {__version__}\n"
"python": platform.python_version(), f"Werkzeug {werkzeug.__version__}",
"flask": __version__,
"werkzeug": werkzeug.__version__,
},
color=ctx.color, color=ctx.color,
) )
ctx.exit() ctx.exit()
@ -289,7 +274,7 @@ version_option = click.Option(
) )
class DispatchingApp(object): class DispatchingApp:
"""Special application that dispatches to a Flask application which """Special application that dispatches to a Flask application which
is imported by name in a background thread. If an error happens is imported by name in a background thread. If an error happens
it is recorded and shown as part of the WSGI handling which in case it is recorded and shown as part of the WSGI handling which in case
@ -327,7 +312,7 @@ class DispatchingApp(object):
exc_info = self._bg_loading_exc_info exc_info = self._bg_loading_exc_info
if exc_info is not None: if exc_info is not None:
self._bg_loading_exc_info = None self._bg_loading_exc_info = None
reraise(*exc_info) raise exc_info
def _load_unlocked(self): def _load_unlocked(self):
__traceback_hide__ = True # noqa: F841 __traceback_hide__ = True # noqa: F841
@ -348,7 +333,7 @@ class DispatchingApp(object):
return rv(environ, start_response) return rv(environ, start_response)
class ScriptInfo(object): class ScriptInfo:
"""Helper object to deal with Flask applications. This is usually not """Helper object to deal with Flask applications. This is usually not
necessary to interface with as it's used internally in the dispatching necessary to interface with as it's used internally in the dispatching
to click. In future versions of Flask this object will most likely play to click. In future versions of Flask this object will most likely play
@ -495,7 +480,7 @@ class FlaskGroup(AppGroup):
add_version_option=True, add_version_option=True,
load_dotenv=True, load_dotenv=True,
set_debug_flag=True, set_debug_flag=True,
**extra **extra,
): ):
params = list(extra.pop("params", None) or ()) params = list(extra.pop("params", None) or ())
@ -587,7 +572,7 @@ class FlaskGroup(AppGroup):
kwargs["obj"] = obj kwargs["obj"] = obj
kwargs.setdefault("auto_envvar_prefix", "FLASK") kwargs.setdefault("auto_envvar_prefix", "FLASK")
return super(FlaskGroup, self).main(*args, **kwargs) return super().main(*args, **kwargs)
def _path_is_ancestor(path, other): def _path_is_ancestor(path, other):
@ -666,25 +651,25 @@ def show_server_banner(env, debug, app_import_path, eager_loading):
return return
if app_import_path is not None: if app_import_path is not None:
message = ' * Serving Flask app "{0}"'.format(app_import_path) message = f" * Serving Flask app {app_import_path!r}"
if not eager_loading: if not eager_loading:
message += " (lazy loading)" message += " (lazy loading)"
click.echo(message) click.echo(message)
click.echo(" * Environment: {0}".format(env)) click.echo(f" * Environment: {env}")
if env == "production": if env == "production":
click.secho( click.secho(
" WARNING: This is a development server. " " WARNING: This is a development server. Do not use it in"
"Do not use it in a production deployment.", " a production deployment.",
fg="red", fg="red",
) )
click.secho(" Use a production WSGI server instead.", dim=True) click.secho(" Use a production WSGI server instead.", dim=True)
if debug is not None: if debug is not None:
click.echo(" * Debug mode: {0}".format("on" if debug else "off")) click.echo(f" * Debug mode: {'on' if debug else 'off'}")
class CertParamType(click.ParamType): class CertParamType(click.ParamType):
@ -725,12 +710,8 @@ class CertParamType(click.ParamType):
obj = import_string(value, silent=True) obj = import_string(value, silent=True)
if sys.version_info < (2, 7, 9): if isinstance(obj, ssl.SSLContext):
if obj: return obj
return obj
else:
if isinstance(obj, ssl.SSLContext):
return obj
raise raise
@ -741,11 +722,7 @@ def _validate_key(ctx, param, value):
""" """
cert = ctx.params.get("cert") cert = ctx.params.get("cert")
is_adhoc = cert == "adhoc" is_adhoc = cert == "adhoc"
is_context = ssl and isinstance(cert, ssl.SSLContext)
if sys.version_info < (2, 7, 9):
is_context = cert and not isinstance(cert, (text_type, bytes))
else:
is_context = ssl and isinstance(cert, ssl.SSLContext)
if value is not None: if value is not None:
if is_adhoc: if is_adhoc:
@ -778,7 +755,7 @@ class SeparatedPathType(click.Path):
def convert(self, value, param, ctx): def convert(self, value, param, ctx):
items = self.split_envvar_value(value) items = self.split_envvar_value(value)
super_convert = super(SeparatedPathType, self).convert super_convert = super().convert
return [super_convert(item, param, ctx) for item in items] return [super_convert(item, param, ctx) for item in items]
@ -824,7 +801,7 @@ class SeparatedPathType(click.Path):
type=SeparatedPathType(), type=SeparatedPathType(),
help=( help=(
"Extra files that trigger a reload on change. Multiple paths" "Extra files that trigger a reload on change. Multiple paths"
" are separated by '{}'.".format(os.path.pathsep) f" are separated by {os.path.pathsep!r}."
), ),
) )
@pass_script_info @pass_script_info
@ -878,12 +855,10 @@ def shell_command():
from .globals import _app_ctx_stack from .globals import _app_ctx_stack
app = _app_ctx_stack.top.app app = _app_ctx_stack.top.app
banner = "Python %s on %s\nApp: %s [%s]\nInstance: %s" % ( banner = (
sys.version, f"Python {sys.version} on {sys.platform}\n"
sys.platform, f"App: {app.import_name} [{app.env}]\n"
app.import_name, f"Instance: {app.instance_path}"
app.env,
app.instance_path,
) )
ctx = {} ctx = {}
@ -891,7 +866,7 @@ def shell_command():
# is using it. # is using it.
startup = os.environ.get("PYTHONSTARTUP") startup = os.environ.get("PYTHONSTARTUP")
if startup and os.path.isfile(startup): if startup and os.path.isfile(startup):
with open(startup, "r") as f: with open(startup) as f:
eval(compile(f.read(), startup, "exec"), ctx) eval(compile(f.read(), startup, "exec"), ctx)
ctx.update(app.make_shell_context()) ctx.update(app.make_shell_context())

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
flask.config flask.config
~~~~~~~~~~~~ ~~~~~~~~~~~~
@ -11,15 +10,11 @@
import errno import errno
import os import os
import types import types
import warnings
from werkzeug.utils import import_string from werkzeug.utils import import_string
from ._compat import iteritems
from ._compat import string_types
class ConfigAttribute:
class ConfigAttribute(object):
"""Makes an attribute forward to the config""" """Makes an attribute forward to the config"""
def __init__(self, name, get_converter=None): def __init__(self, name, get_converter=None):
@ -103,10 +98,10 @@ class Config(dict):
if silent: if silent:
return False return False
raise RuntimeError( raise RuntimeError(
"The environment variable %r is not set " f"The environment variable {variable_name!r} is not set"
"and as such configuration could not be " " and as such configuration could not be loaded. Set"
"loaded. Set this variable and make it " " this variable and make it point to a configuration"
"point to a configuration file" % variable_name " file"
) )
return self.from_pyfile(rv, silent=silent) return self.from_pyfile(rv, silent=silent)
@ -130,10 +125,10 @@ class Config(dict):
try: try:
with open(filename, mode="rb") as config_file: with open(filename, mode="rb") as config_file:
exec(compile(config_file.read(), filename, "exec"), d.__dict__) exec(compile(config_file.read(), filename, "exec"), d.__dict__)
except IOError as e: except OSError as e:
if silent and e.errno in (errno.ENOENT, errno.EISDIR, errno.ENOTDIR): if silent and e.errno in (errno.ENOENT, errno.EISDIR, errno.ENOTDIR):
return False return False
e.strerror = "Unable to load configuration file (%s)" % e.strerror e.strerror = f"Unable to load configuration file ({e.strerror})"
raise raise
self.from_object(d) self.from_object(d)
return True return True
@ -170,7 +165,7 @@ class Config(dict):
:param obj: an import name or object :param obj: an import name or object
""" """
if isinstance(obj, string_types): if isinstance(obj, str):
obj = import_string(obj) obj = import_string(obj)
for key in dir(obj): for key in dir(obj):
if key.isupper(): if key.isupper():
@ -201,38 +196,15 @@ class Config(dict):
try: try:
with open(filename) as f: with open(filename) as f:
obj = load(f) obj = load(f)
except IOError as e: except OSError as e:
if silent and e.errno in (errno.ENOENT, errno.EISDIR): if silent and e.errno in (errno.ENOENT, errno.EISDIR):
return False return False
e.strerror = "Unable to load configuration file (%s)" % e.strerror e.strerror = f"Unable to load configuration file ({e.strerror})"
raise raise
return self.from_mapping(obj) return self.from_mapping(obj)
def from_json(self, filename, silent=False):
"""Update the values in the config from a JSON file. The loaded
data is passed to the :meth:`from_mapping` method.
:param filename: The path to the JSON file. This can be an
absolute path or relative to the config root path.
:param silent: Ignore the file if it doesn't exist.
.. deprecated:: 2.0
Use :meth:`from_file` with :meth:`json.load` instead.
.. versionadded:: 0.11
"""
warnings.warn(
"'from_json' is deprecated and will be removed in 2.0."
" Use 'from_file(filename, load=json.load)' instead.",
DeprecationWarning,
stacklevel=2,
)
from .json import load
return self.from_file(filename, load, silent=silent)
def from_mapping(self, *mapping, **kwargs): def from_mapping(self, *mapping, **kwargs):
"""Updates the config like :meth:`update` ignoring items with non-upper """Updates the config like :meth:`update` ignoring items with non-upper
keys. keys.
@ -247,7 +219,7 @@ class Config(dict):
mappings.append(mapping[0]) mappings.append(mapping[0])
elif len(mapping) > 1: elif len(mapping) > 1:
raise TypeError( raise TypeError(
"expected at most 1 positional argument, got %d" % len(mapping) f"expected at most 1 positional argument, got {len(mapping)}"
) )
mappings.append(kwargs.items()) mappings.append(kwargs.items())
for mapping in mappings: for mapping in mappings:
@ -285,7 +257,7 @@ class Config(dict):
.. versionadded:: 0.11 .. versionadded:: 0.11
""" """
rv = {} rv = {}
for k, v in iteritems(self): for k, v in self.items():
if not k.startswith(namespace): if not k.startswith(namespace):
continue continue
if trim_namespace: if trim_namespace:
@ -298,4 +270,4 @@ class Config(dict):
return rv return rv
def __repr__(self): def __repr__(self):
return "<%s %s>" % (self.__class__.__name__, dict.__repr__(self)) return f"<{type(self).__name__} {dict.__repr__(self)}>"

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
flask.ctx flask.ctx
~~~~~~~~~ ~~~~~~~~~
@ -13,8 +12,6 @@ from functools import update_wrapper
from werkzeug.exceptions import HTTPException from werkzeug.exceptions import HTTPException
from ._compat import BROKEN_PYPY_CTXMGR_EXIT
from ._compat import reraise
from .globals import _app_ctx_stack from .globals import _app_ctx_stack
from .globals import _request_ctx_stack from .globals import _request_ctx_stack
from .signals import appcontext_popped from .signals import appcontext_popped
@ -25,7 +22,7 @@ from .signals import appcontext_pushed
_sentinel = object() _sentinel = object()
class _AppCtxGlobals(object): class _AppCtxGlobals:
"""A plain object. Used as a namespace for storing data during an """A plain object. Used as a namespace for storing data during an
application context. application context.
@ -91,7 +88,7 @@ class _AppCtxGlobals(object):
def __repr__(self): def __repr__(self):
top = _app_ctx_stack.top top = _app_ctx_stack.top
if top is not None: if top is not None:
return "<flask.g of %r>" % top.app.name return f"<flask.g of {top.app.name!r}>"
return object.__repr__(self) return object.__repr__(self)
@ -202,7 +199,7 @@ def has_app_context():
return _app_ctx_stack.top is not None return _app_ctx_stack.top is not None
class AppContext(object): class AppContext:
"""The application context binds an application object implicitly """The application context binds an application object implicitly
to the current thread or greenlet, similar to how the to the current thread or greenlet, similar to how the
:class:`RequestContext` binds request information. The application :class:`RequestContext` binds request information. The application
@ -223,8 +220,6 @@ class AppContext(object):
def push(self): def push(self):
"""Binds the app context to the current context.""" """Binds the app context to the current context."""
self._refcnt += 1 self._refcnt += 1
if hasattr(sys, "exc_clear"):
sys.exc_clear()
_app_ctx_stack.push(self) _app_ctx_stack.push(self)
appcontext_pushed.send(self.app) appcontext_pushed.send(self.app)
@ -238,7 +233,7 @@ class AppContext(object):
self.app.do_teardown_appcontext(exc) self.app.do_teardown_appcontext(exc)
finally: finally:
rv = _app_ctx_stack.pop() rv = _app_ctx_stack.pop()
assert rv is self, "Popped wrong app context. (%r instead of %r)" % (rv, self) assert rv is self, f"Popped wrong app context. ({rv!r} instead of {self!r})"
appcontext_popped.send(self.app) appcontext_popped.send(self.app)
def __enter__(self): def __enter__(self):
@ -248,11 +243,8 @@ class AppContext(object):
def __exit__(self, exc_type, exc_value, tb): def __exit__(self, exc_type, exc_value, tb):
self.pop(exc_value) self.pop(exc_value)
if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:
reraise(exc_type, exc_value, tb)
class RequestContext:
class RequestContext(object):
"""The request context contains all request relevant information. It is """The request context contains all request relevant information. It is
created at the beginning of the request and pushed to the created at the beginning of the request and pushed to the
`_request_ctx_stack` and removed at the end of it. It will create the `_request_ctx_stack` and removed at the end of it. It will create the
@ -376,9 +368,6 @@ class RequestContext(object):
else: else:
self._implicit_app_ctx_stack.append(None) self._implicit_app_ctx_stack.append(None)
if hasattr(sys, "exc_clear"):
sys.exc_clear()
_request_ctx_stack.push(self) _request_ctx_stack.push(self)
# Open the session at the moment that the request context is available. # Open the session at the moment that the request context is available.
@ -404,9 +393,9 @@ class RequestContext(object):
Added the `exc` argument. Added the `exc` argument.
""" """
app_ctx = self._implicit_app_ctx_stack.pop() app_ctx = self._implicit_app_ctx_stack.pop()
clear_request = False
try: try:
clear_request = False
if not self._implicit_app_ctx_stack: if not self._implicit_app_ctx_stack:
self.preserved = False self.preserved = False
self._preserved_exc = None self._preserved_exc = None
@ -414,13 +403,6 @@ class RequestContext(object):
exc = sys.exc_info()[1] exc = sys.exc_info()[1]
self.app.do_teardown_request(exc) self.app.do_teardown_request(exc)
# If this interpreter supports clearing the exception information
# we do that now. This will only go into effect on Python 2.x,
# on 3.x it disappears automatically at the end of the exception
# stack.
if hasattr(sys, "exc_clear"):
sys.exc_clear()
request_close = getattr(self.request, "close", None) request_close = getattr(self.request, "close", None)
if request_close is not None: if request_close is not None:
request_close() request_close()
@ -437,10 +419,9 @@ class RequestContext(object):
if app_ctx is not None: if app_ctx is not None:
app_ctx.pop(exc) app_ctx.pop(exc)
assert rv is self, "Popped wrong request context. (%r instead of %r)" % ( assert (
rv, rv is self
self, ), f"Popped wrong request context. ({rv!r} instead of {self!r})"
)
def auto_pop(self, exc): def auto_pop(self, exc):
if self.request.environ.get("flask._preserve_context") or ( if self.request.environ.get("flask._preserve_context") or (
@ -463,13 +444,8 @@ class RequestContext(object):
# See flask.testing for how this works. # See flask.testing for how this works.
self.auto_pop(exc_value) self.auto_pop(exc_value)
if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:
reraise(exc_type, exc_value, tb)
def __repr__(self): def __repr__(self):
return "<%s '%s' [%s] of %s>" % ( return (
self.__class__.__name__, f"<{type(self).__name__} {self.request.url!r}"
self.request.url, f" [{self.request.method}] of {self.app.name}>"
self.request.method,
self.app.name,
) )

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
flask.debughelpers flask.debughelpers
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
@ -11,8 +10,6 @@
import os import os
from warnings import warn from warnings import warn
from ._compat import implements_to_string
from ._compat import text_type
from .app import Flask from .app import Flask
from .blueprints import Blueprint from .blueprints import Blueprint
from .globals import _request_ctx_stack from .globals import _request_ctx_stack
@ -24,7 +21,6 @@ class UnexpectedUnicodeError(AssertionError, UnicodeError):
""" """
@implements_to_string
class DebugFilesKeyError(KeyError, AssertionError): class DebugFilesKeyError(KeyError, AssertionError):
"""Raised from request.files during debugging. The idea is that it can """Raised from request.files during debugging. The idea is that it can
provide a better error message than just a generic KeyError/BadRequest. provide a better error message than just a generic KeyError/BadRequest.
@ -33,17 +29,18 @@ class DebugFilesKeyError(KeyError, AssertionError):
def __init__(self, request, key): def __init__(self, request, key):
form_matches = request.form.getlist(key) form_matches = request.form.getlist(key)
buf = [ buf = [
'You tried to access the file "%s" in the request.files ' f"You tried to access the file {key!r} in the request.files"
"dictionary but it does not exist. The mimetype for the request " " dictionary but it does not exist. The mimetype for the"
'is "%s" instead of "multipart/form-data" which means that no ' f" request is {request.mimetype!r} instead of"
"file contents were transmitted. To fix this error you should " " 'multipart/form-data' which means that no file contents"
'provide enctype="multipart/form-data" in your form.' " were transmitted. To fix this error you should provide"
% (key, request.mimetype) ' enctype="multipart/form-data" in your form.'
] ]
if form_matches: if form_matches:
names = ", ".join(repr(x) for x in form_matches)
buf.append( buf.append(
"\n\nThe browser instead transmitted some file names. " "\n\nThe browser instead transmitted some file names. "
"This was submitted: %s" % ", ".join('"%s"' % x for x in form_matches) f"This was submitted: {names}"
) )
self.msg = "".join(buf) self.msg = "".join(buf)
@ -60,24 +57,24 @@ class FormDataRoutingRedirect(AssertionError):
def __init__(self, request): def __init__(self, request):
exc = request.routing_exception exc = request.routing_exception
buf = [ buf = [
"A request was sent to this URL (%s) but a redirect was " f"A request was sent to this URL ({request.url}) but a"
'issued automatically by the routing system to "%s".' " redirect was issued automatically by the routing system"
% (request.url, exc.new_url) f" to {exc.new_url!r}."
] ]
# In case just a slash was appended we can be extra helpful # In case just a slash was appended we can be extra helpful
if request.base_url + "/" == exc.new_url.split("?")[0]: if f"{request.base_url}/" == exc.new_url.split("?")[0]:
buf.append( buf.append(
" The URL was defined with a trailing slash so " " The URL was defined with a trailing slash so Flask"
"Flask will automatically redirect to the URL " " will automatically redirect to the URL with the"
"with the trailing slash if it was accessed " " trailing slash if it was accessed without one."
"without one."
) )
buf.append( buf.append(
" Make sure to directly send your %s-request to this URL " " Make sure to directly send your"
"since we can't make browsers or HTTP clients redirect " f" {request.method}-request to this URL since we can't make"
"with form data reliably or without user interaction." % request.method " browsers or HTTP clients redirect with form data reliably"
" or without user interaction."
) )
buf.append("\n\nNote: this exception is only raised in debug mode") buf.append("\n\nNote: this exception is only raised in debug mode")
AssertionError.__init__(self, "".join(buf).encode("utf-8")) AssertionError.__init__(self, "".join(buf).encode("utf-8"))
@ -105,25 +102,25 @@ def attach_enctype_error_multidict(request):
def _dump_loader_info(loader): def _dump_loader_info(loader):
yield "class: %s.%s" % (type(loader).__module__, type(loader).__name__) yield f"class: {type(loader).__module__}.{type(loader).__name__}"
for key, value in sorted(loader.__dict__.items()): for key, value in sorted(loader.__dict__.items()):
if key.startswith("_"): if key.startswith("_"):
continue continue
if isinstance(value, (tuple, list)): if isinstance(value, (tuple, list)):
if not all(isinstance(x, (str, text_type)) for x in value): if not all(isinstance(x, str) for x in value):
continue continue
yield "%s:" % key yield f"{key}:"
for item in value: for item in value:
yield " - %s" % item yield f" - {item}"
continue continue
elif not isinstance(value, (str, text_type, int, float, bool)): elif not isinstance(value, (str, int, float, bool)):
continue continue
yield "%s: %r" % (key, value) yield f"{key}: {value!r}"
def explain_template_loading_attempts(app, template, attempts): def explain_template_loading_attempts(app, template, attempts):
"""This should help developers understand what failed""" """This should help developers understand what failed"""
info = ['Locating template "%s":' % template] info = [f"Locating template {template!r}:"]
total_found = 0 total_found = 0
blueprint = None blueprint = None
reqctx = _request_ctx_stack.top reqctx = _request_ctx_stack.top
@ -132,23 +129,23 @@ def explain_template_loading_attempts(app, template, attempts):
for idx, (loader, srcobj, triple) in enumerate(attempts): for idx, (loader, srcobj, triple) in enumerate(attempts):
if isinstance(srcobj, Flask): if isinstance(srcobj, Flask):
src_info = 'application "%s"' % srcobj.import_name src_info = f"application {srcobj.import_name!r}"
elif isinstance(srcobj, Blueprint): elif isinstance(srcobj, Blueprint):
src_info = 'blueprint "%s" (%s)' % (srcobj.name, srcobj.import_name) src_info = f"blueprint {srcobj.name!r} ({srcobj.import_name})"
else: else:
src_info = repr(srcobj) src_info = repr(srcobj)
info.append("% 5d: trying loader of %s" % (idx + 1, src_info)) info.append(f"{idx + 1:5}: trying loader of {src_info}")
for line in _dump_loader_info(loader): for line in _dump_loader_info(loader):
info.append(" %s" % line) info.append(f" {line}")
if triple is None: if triple is None:
detail = "no match" detail = "no match"
else: else:
detail = "found (%r)" % (triple[1] or "<string>") detail = f"found ({triple[1] or '<string>'!r})"
total_found += 1 total_found += 1
info.append(" -> %s" % detail) info.append(f" -> {detail}")
seems_fishy = False seems_fishy = False
if total_found == 0: if total_found == 0:
@ -160,8 +157,8 @@ def explain_template_loading_attempts(app, template, attempts):
if blueprint is not None and seems_fishy: if blueprint is not None and seems_fishy:
info.append( info.append(
" The template was looked up from an endpoint that " " The template was looked up from an endpoint that belongs"
'belongs to the blueprint "%s".' % blueprint f" to the blueprint {blueprint!r}."
) )
info.append(" Maybe you did not place a template in the right folder?") info.append(" Maybe you did not place a template in the right folder?")
info.append(" See https://flask.palletsprojects.com/blueprints/#templates") info.append(" See https://flask.palletsprojects.com/blueprints/#templates")
@ -173,11 +170,10 @@ def explain_ignored_app_run():
if os.environ.get("WERKZEUG_RUN_MAIN") != "true": if os.environ.get("WERKZEUG_RUN_MAIN") != "true":
warn( warn(
Warning( Warning(
"Silently ignoring app.run() because the " "Silently ignoring app.run() because the application is"
"application is run from the flask command line " " run from the flask command line executable. Consider"
"executable. Consider putting app.run() behind an " ' putting app.run() behind an if __name__ == "__main__"'
'if __name__ == "__main__" guard to silence this ' " guard to silence this warning."
"warning."
), ),
stacklevel=3, stacklevel=3,
) )

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
flask.globals flask.globals
~~~~~~~~~~~~~ ~~~~~~~~~~~~~

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
flask.helpers flask.helpers
~~~~~~~~~~~~~ ~~~~~~~~~~~~~
@ -30,10 +29,6 @@ from werkzeug.routing import BuildError
from werkzeug.urls import url_quote from werkzeug.urls import url_quote
from werkzeug.wsgi import wrap_file from werkzeug.wsgi import wrap_file
from ._compat import fspath
from ._compat import PY2
from ._compat import string_types
from ._compat import text_type
from .globals import _app_ctx_stack from .globals import _app_ctx_stack
from .globals import _request_ctx_stack from .globals import _request_ctx_stack
from .globals import current_app from .globals import current_app
@ -159,8 +154,7 @@ def stream_with_context(generator_or_function):
# don't need that because they are closed on their destruction # don't need that because they are closed on their destruction
# automatically. # automatically.
try: try:
for item in gen: yield from gen
yield item
finally: finally:
if hasattr(gen, "close"): if hasattr(gen, "close"):
gen.close() gen.close()
@ -317,7 +311,7 @@ def url_for(endpoint, **values):
if endpoint[:1] == ".": if endpoint[:1] == ".":
if blueprint_name is not None: if blueprint_name is not None:
endpoint = blueprint_name + endpoint endpoint = f"{blueprint_name}{endpoint}"
else: else:
endpoint = endpoint[1:] endpoint = endpoint[1:]
@ -370,7 +364,7 @@ def url_for(endpoint, **values):
return appctx.app.handle_url_build_error(error, endpoint, values) return appctx.app.handle_url_build_error(error, endpoint, values)
if anchor is not None: if anchor is not None:
rv += "#" + url_quote(anchor) rv += f"#{url_quote(anchor)}"
return rv return rv
@ -576,9 +570,9 @@ def send_file(
fsize = None fsize = None
if hasattr(filename_or_fp, "__fspath__"): if hasattr(filename_or_fp, "__fspath__"):
filename_or_fp = fspath(filename_or_fp) filename_or_fp = os.fspath(filename_or_fp)
if isinstance(filename_or_fp, string_types): if isinstance(filename_or_fp, str):
filename = filename_or_fp filename = filename_or_fp
if not os.path.isabs(filename): if not os.path.isabs(filename):
filename = os.path.join(current_app.root_path, filename) filename = os.path.join(current_app.root_path, filename)
@ -608,17 +602,18 @@ def send_file(
if attachment_filename is None: if attachment_filename is None:
raise TypeError("filename unavailable, required for sending as attachment") raise TypeError("filename unavailable, required for sending as attachment")
if not isinstance(attachment_filename, text_type): if not isinstance(attachment_filename, str):
attachment_filename = attachment_filename.decode("utf-8") attachment_filename = attachment_filename.decode("utf-8")
try: try:
attachment_filename = attachment_filename.encode("ascii") attachment_filename = attachment_filename.encode("ascii")
except UnicodeEncodeError: except UnicodeEncodeError:
quoted = url_quote(attachment_filename, safe="")
filenames = { filenames = {
"filename": unicodedata.normalize("NFKD", attachment_filename).encode( "filename": unicodedata.normalize("NFKD", attachment_filename).encode(
"ascii", "ignore" "ascii", "ignore"
), ),
"filename*": "UTF-8''%s" % url_quote(attachment_filename, safe=b""), "filename*": f"UTF-8''{quoted}",
} }
else: else:
filenames = {"filename": attachment_filename} filenames = {"filename": attachment_filename}
@ -638,11 +633,7 @@ def send_file(
mtime = os.path.getmtime(filename) mtime = os.path.getmtime(filename)
fsize = os.path.getsize(filename) fsize = os.path.getsize(filename)
elif isinstance(file, io.BytesIO): elif isinstance(file, io.BytesIO):
try: fsize = file.getbuffer().nbytes
fsize = file.getbuffer().nbytes
except AttributeError:
# Python 2 doesn't have getbuffer
fsize = len(file.getvalue())
elif isinstance(file, io.TextIOBase): elif isinstance(file, io.TextIOBase):
raise ValueError("Files must be opened in binary mode or use BytesIO.") raise ValueError("Files must be opened in binary mode or use BytesIO.")
@ -671,23 +662,19 @@ def send_file(
from warnings import warn from warnings import warn
try: try:
rv.set_etag( check = (
"%s-%s-%s" adler32(
% ( filename.encode("utf-8") if isinstance(filename, str) else filename
os.path.getmtime(filename),
os.path.getsize(filename),
adler32(
filename.encode("utf-8")
if isinstance(filename, text_type)
else filename
)
& 0xFFFFFFFF,
) )
& 0xFFFFFFFF
)
rv.set_etag(
f"{os.path.getmtime(filename)}-{os.path.getsize(filename)}-{check}"
) )
except OSError: except OSError:
warn( warn(
"Access %s failed, maybe it does not exist, so ignore etags in " f"Access {filename} failed, maybe it does not exist, so"
"headers" % filename, " ignore etags in headers",
stacklevel=2, stacklevel=2,
) )
@ -769,8 +756,8 @@ def send_from_directory(directory, filename, **options):
:param options: optional keyword arguments that are directly :param options: optional keyword arguments that are directly
forwarded to :func:`send_file`. forwarded to :func:`send_file`.
""" """
filename = fspath(filename) filename = os.fspath(filename)
directory = fspath(directory) directory = os.fspath(directory)
filename = safe_join(directory, filename) filename = safe_join(directory, filename)
if not os.path.isabs(filename): if not os.path.isabs(filename):
filename = os.path.join(current_app.root_path, filename) filename = os.path.join(current_app.root_path, filename)
@ -803,8 +790,6 @@ def get_root_path(import_name):
if loader is None or import_name == "__main__": if loader is None or import_name == "__main__":
return os.getcwd() return os.getcwd()
# For .egg, zipimporter does not have get_filename until Python 2.7.
# Some other loaders might exhibit the same behavior.
if hasattr(loader, "get_filename"): if hasattr(loader, "get_filename"):
filepath = loader.get_filename(import_name) filepath = loader.get_filename(import_name)
else: else:
@ -818,13 +803,12 @@ def get_root_path(import_name):
# first module that is contained in our package. # first module that is contained in our package.
if filepath is None: if filepath is None:
raise RuntimeError( raise RuntimeError(
"No root path can be found for the provided " "No root path can be found for the provided module"
'module "%s". This can happen because the ' f" {import_name!r}. This can happen because the module"
"module came from an import hook that does " " came from an import hook that does not provide file"
"not provide file name information or because " " name information or because it's a namespace package."
"it's a namespace package. In this case " " In this case the root path needs to be explicitly"
"the root path needs to be explicitly " " provided."
"provided." % import_name
) )
# filepath is import_name.py for a module, or __init__.py for a package. # filepath is import_name.py for a module, or __init__.py for a package.
@ -835,6 +819,7 @@ def _matching_loader_thinks_module_is_package(loader, mod_name):
"""Given the loader that loaded a module and the module this function """Given the loader that loaded a module and the module this function
attempts to figure out if the given module is actually a package. attempts to figure out if the given module is actually a package.
""" """
cls = type(loader)
# If the loader can tell us if something is a package, we can # If the loader can tell us if something is a package, we can
# directly ask the loader. # directly ask the loader.
if hasattr(loader, "is_package"): if hasattr(loader, "is_package"):
@ -842,49 +827,41 @@ def _matching_loader_thinks_module_is_package(loader, mod_name):
# importlib's namespace loaders do not have this functionality but # importlib's namespace loaders do not have this functionality but
# all the modules it loads are packages, so we can take advantage of # all the modules it loads are packages, so we can take advantage of
# this information. # this information.
elif ( elif cls.__module__ == "_frozen_importlib" and cls.__name__ == "NamespaceLoader":
loader.__class__.__module__ == "_frozen_importlib"
and loader.__class__.__name__ == "NamespaceLoader"
):
return True return True
# Otherwise we need to fail with an error that explains what went # Otherwise we need to fail with an error that explains what went
# wrong. # wrong.
raise AttributeError( raise AttributeError(
( f"{cls.__name__}.is_package() method is missing but is required"
"%s.is_package() method is missing but is required by Flask of " " for PEP 302 import hooks."
"PEP 302 import hooks. If you do not use import hooks and "
"you encounter this error please file a bug against Flask."
)
% loader.__class__.__name__
) )
def _find_package_path(root_mod_name): def _find_package_path(root_mod_name):
"""Find the path where the module's root exists in""" """Find the path where the module's root exists in"""
if sys.version_info >= (3, 4): import importlib.util
import importlib.util
try: try:
spec = importlib.util.find_spec(root_mod_name) spec = importlib.util.find_spec(root_mod_name)
if spec is None: if spec is None:
raise ValueError("not found") raise ValueError("not found")
# ImportError: the machinery told us it does not exist # ImportError: the machinery told us it does not exist
# ValueError: # ValueError:
# - the module name was invalid # - the module name was invalid
# - the module name is __main__ # - the module name is __main__
# - *we* raised `ValueError` due to `spec` being `None` # - *we* raised `ValueError` due to `spec` being `None`
except (ImportError, ValueError): except (ImportError, ValueError):
pass # handled below pass # handled below
else:
# namespace package
if spec.origin in {"namespace", None}:
return os.path.dirname(next(iter(spec.submodule_search_locations)))
# a package (with __init__.py)
elif spec.submodule_search_locations:
return os.path.dirname(os.path.dirname(spec.origin))
# just a normal module
else: else:
# namespace package return os.path.dirname(spec.origin)
if spec.origin in {"namespace", None}:
return os.path.dirname(next(iter(spec.submodule_search_locations)))
# a package (with __init__.py)
elif spec.submodule_search_locations:
return os.path.dirname(os.path.dirname(spec.origin))
# just a normal module
else:
return os.path.dirname(spec.origin)
# we were unable to find the `package_path` using PEP 451 loaders # we were unable to find the `package_path` using PEP 451 loaders
loader = pkgutil.get_loader(root_mod_name) loader = pkgutil.get_loader(root_mod_name)
@ -892,7 +869,6 @@ def _find_package_path(root_mod_name):
# import name is not found, or interactive/main module # import name is not found, or interactive/main module
return os.getcwd() return os.getcwd()
else: else:
# For .egg, zipimporter does not have get_filename until Python 2.7.
if hasattr(loader, "get_filename"): if hasattr(loader, "get_filename"):
filename = loader.get_filename(root_mod_name) filename = loader.get_filename(root_mod_name)
elif hasattr(loader, "archive"): elif hasattr(loader, "archive"):
@ -909,8 +885,8 @@ def _find_package_path(root_mod_name):
package_path = os.path.abspath(os.path.dirname(filename)) package_path = os.path.abspath(os.path.dirname(filename))
# In case the root module is a package we need to chop of the # In case the root module is a package we need to chop of the
# rightmost part. This needs to go through a helper function # rightmost part. This needs to go through a helper function
# because of python 3.3 namespace packages. # because of namespace packages.
if _matching_loader_thinks_module_is_package(loader, root_mod_name): if _matching_loader_thinks_module_is_package(loader, root_mod_name):
package_path = os.path.dirname(package_path) package_path = os.path.dirname(package_path)
@ -945,7 +921,7 @@ def find_package(import_name):
return None, package_path return None, package_path
class locked_cached_property(object): class locked_cached_property:
"""A decorator that converts a function into a lazy property. The """A decorator that converts a function into a lazy property. The
function wrapped is called the first time to retrieve the result function wrapped is called the first time to retrieve the result
and then that calculated result is used the next time you access and then that calculated result is used the next time you access
@ -971,7 +947,7 @@ class locked_cached_property(object):
return value return value
class _PackageBoundObject(object): class _PackageBoundObject:
#: The name of the package or module that this app belongs to. Do not #: The name of the package or module that this app belongs to. Do not
#: change this once it is set by the constructor. #: change this once it is set by the constructor.
import_name = None import_name = None
@ -1028,7 +1004,7 @@ class _PackageBoundObject(object):
if self.static_folder is not None: if self.static_folder is not None:
basename = os.path.basename(self.static_folder) basename = os.path.basename(self.static_folder)
return ("/" + basename).rstrip("/") return f"/{basename}".rstrip("/")
@static_url_path.setter @static_url_path.setter
def static_url_path(self, value): def static_url_path(self, value):
@ -1140,26 +1116,16 @@ def total_seconds(td):
def is_ip(value): def is_ip(value):
"""Determine if the given string is an IP address. """Determine if the given string is an IP address.
Python 2 on Windows doesn't provide ``inet_pton``, so this only
checks IPv4 addresses in that environment.
:param value: value to check :param value: value to check
:type value: str :type value: str
:return: True if string is an IP address :return: True if string is an IP address
:rtype: bool :rtype: bool
""" """
if PY2 and os.name == "nt":
try:
socket.inet_aton(value)
return True
except socket.error:
return False
for family in (socket.AF_INET, socket.AF_INET6): for family in (socket.AF_INET, socket.AF_INET6):
try: try:
socket.inet_pton(family, value) socket.inet_pton(family, value)
except socket.error: except OSError:
pass pass
else: else:
return True return True

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
flask.json flask.json
~~~~~~~~~~ ~~~~~~~~~~
@ -16,14 +15,13 @@ from itsdangerous import json as _json
from jinja2 import Markup from jinja2 import Markup
from werkzeug.http import http_date from werkzeug.http import http_date
from .._compat import PY2
from .._compat import text_type
from ..globals import current_app from ..globals import current_app
from ..globals import request from ..globals import request
try: try:
import dataclasses import dataclasses
except ImportError: except ImportError:
# Python < 3.7
dataclasses = None dataclasses = None
# Figure out if simplejson escapes slashes. This behavior was changed # Figure out if simplejson escapes slashes. This behavior was changed
@ -96,7 +94,7 @@ class JSONEncoder(_json.JSONEncoder):
if dataclasses and dataclasses.is_dataclass(o): if dataclasses and dataclasses.is_dataclass(o):
return dataclasses.asdict(o) return dataclasses.asdict(o)
if hasattr(o, "__html__"): if hasattr(o, "__html__"):
return text_type(o.__html__()) return str(o.__html__())
return _json.JSONEncoder.default(self, o) return _json.JSONEncoder.default(self, o)
@ -209,7 +207,7 @@ def dumps(obj, app=None, **kwargs):
_dump_arg_defaults(kwargs, app=app) _dump_arg_defaults(kwargs, app=app)
encoding = kwargs.pop("encoding", None) encoding = kwargs.pop("encoding", None)
rv = _json.dumps(obj, **kwargs) rv = _json.dumps(obj, **kwargs)
if encoding is not None and isinstance(rv, text_type): if encoding is not None and isinstance(rv, str):
rv = rv.encode(encoding) rv = rv.encode(encoding)
return rv return rv
@ -256,8 +254,7 @@ def loads(s, app=None, **kwargs):
def load(fp, app=None, **kwargs): def load(fp, app=None, **kwargs):
"""Like :func:`loads` but reads from a file object.""" """Like :func:`loads` but reads from a file object."""
_load_arg_defaults(kwargs, app=app) _load_arg_defaults(kwargs, app=app)
if not PY2: fp = _wrap_reader_for_text(fp, kwargs.pop("encoding", None) or "utf-8")
fp = _wrap_reader_for_text(fp, kwargs.pop("encoding", None) or "utf-8")
return _json.load(fp, **kwargs) return _json.load(fp, **kwargs)
@ -288,10 +285,10 @@ def htmlsafe_dumps(obj, **kwargs):
""" """
rv = ( rv = (
dumps(obj, **kwargs) dumps(obj, **kwargs)
.replace(u"<", u"\\u003c") .replace("<", "\\u003c")
.replace(u">", u"\\u003e") .replace(">", "\\u003e")
.replace(u"&", u"\\u0026") .replace("&", "\\u0026")
.replace(u"'", u"\\u0027") .replace("'", "\\u0027")
) )
if not _slash_escape: if not _slash_escape:
rv = rv.replace("\\/", "/") rv = rv.replace("\\/", "/")
@ -300,7 +297,7 @@ def htmlsafe_dumps(obj, **kwargs):
def htmlsafe_dump(obj, fp, **kwargs): def htmlsafe_dump(obj, fp, **kwargs):
"""Like :func:`htmlsafe_dumps` but writes into a file object.""" """Like :func:`htmlsafe_dumps` but writes into a file object."""
fp.write(text_type(htmlsafe_dumps(obj, **kwargs))) fp.write(str(htmlsafe_dumps(obj, **kwargs)))
def jsonify(*args, **kwargs): def jsonify(*args, **kwargs):
@ -367,7 +364,7 @@ def jsonify(*args, **kwargs):
data = args or kwargs data = args or kwargs
return current_app.response_class( return current_app.response_class(
dumps(data, indent=indent, separators=separators) + "\n", f"{dumps(data, indent=indent, separators=separators)}\n",
mimetype=current_app.config["JSONIFY_MIMETYPE"], mimetype=current_app.config["JSONIFY_MIMETYPE"],
) )

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
Tagged JSON Tagged JSON
~~~~~~~~~~~ ~~~~~~~~~~~
@ -50,13 +49,11 @@ from jinja2 import Markup
from werkzeug.http import http_date from werkzeug.http import http_date
from werkzeug.http import parse_date from werkzeug.http import parse_date
from .._compat import iteritems
from .._compat import text_type
from ..json import dumps from ..json import dumps
from ..json import loads from ..json import loads
class JSONTag(object): class JSONTag:
"""Base class for defining type tags for :class:`TaggedJSONSerializer`.""" """Base class for defining type tags for :class:`TaggedJSONSerializer`."""
__slots__ = ("serializer",) __slots__ = ("serializer",)
@ -108,7 +105,7 @@ class TagDict(JSONTag):
def to_json(self, value): def to_json(self, value):
key = next(iter(value)) key = next(iter(value))
return {key + "__": self.serializer.tag(value[key])} return {f"{key}__": self.serializer.tag(value[key])}
def to_python(self, value): def to_python(self, value):
key = next(iter(value)) key = next(iter(value))
@ -124,7 +121,7 @@ class PassDict(JSONTag):
def to_json(self, value): def to_json(self, value):
# JSON objects may only have string keys, so don't bother tagging the # JSON objects may only have string keys, so don't bother tagging the
# key here. # key here.
return dict((k, self.serializer.tag(v)) for k, v in iteritems(value)) return {k: self.serializer.tag(v) for k, v in value.items()}
tag = to_json tag = to_json
@ -181,7 +178,7 @@ class TagMarkup(JSONTag):
return callable(getattr(value, "__html__", None)) return callable(getattr(value, "__html__", None))
def to_json(self, value): def to_json(self, value):
return text_type(value.__html__()) return str(value.__html__())
def to_python(self, value): def to_python(self, value):
return Markup(value) return Markup(value)
@ -215,7 +212,7 @@ class TagDateTime(JSONTag):
return parse_date(value) return parse_date(value)
class TaggedJSONSerializer(object): class TaggedJSONSerializer:
"""Serializer that uses a tag system to compactly represent objects that """Serializer that uses a tag system to compactly represent objects that
are not JSON types. Passed as the intermediate serializer to are not JSON types. Passed as the intermediate serializer to
:class:`itsdangerous.Serializer`. :class:`itsdangerous.Serializer`.
@ -271,7 +268,7 @@ class TaggedJSONSerializer(object):
if key is not None: if key is not None:
if not force and key in self.tags: if not force and key in self.tags:
raise KeyError("Tag '{0}' is already registered.".format(key)) raise KeyError(f"Tag '{key}' is already registered.")
self.tags[key] = tag self.tags[key] = tag

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
flask.logging flask.logging
~~~~~~~~~~~~~ ~~~~~~~~~~~~~
@ -6,11 +5,8 @@ flask.logging
:copyright: 2010 Pallets :copyright: 2010 Pallets
:license: BSD-3-Clause :license: BSD-3-Clause
""" """
from __future__ import absolute_import
import logging import logging
import sys import sys
import warnings
from werkzeug.local import LocalProxy from werkzeug.local import LocalProxy
@ -57,20 +53,6 @@ default_handler.setFormatter(
) )
def _has_config(logger):
"""Decide if a logger has direct configuration applied by checking
its properties against the defaults.
:param logger: The :class:`~logging.Logger` to inspect.
"""
return (
logger.level != logging.NOTSET
or logger.handlers
or logger.filters
or not logger.propagate
)
def create_logger(app): def create_logger(app):
"""Get the Flask app's logger and configure it if needed. """Get the Flask app's logger and configure it if needed.
@ -86,20 +68,6 @@ def create_logger(app):
""" """
logger = logging.getLogger(app.name) logger = logging.getLogger(app.name)
# 1.1.0 changes name of logger, warn if config is detected for old
# name and not new name
for old_name in ("flask.app", "flask"):
old_logger = logging.getLogger(old_name)
if _has_config(old_logger) and not _has_config(logger):
warnings.warn(
"'app.logger' is named '{name}' for this application,"
" but configuration was found for '{old_name}', which"
" no longer has an effect. The logging configuration"
" should be moved to '{name}'.".format(name=app.name, old_name=old_name)
)
break
if app.debug and not logger.level: if app.debug and not logger.level:
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
flask.sessions flask.sessions
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
@ -10,19 +9,19 @@
""" """
import hashlib import hashlib
import warnings import warnings
from collections.abc import MutableMapping
from datetime import datetime from datetime import datetime
from itsdangerous import BadSignature from itsdangerous import BadSignature
from itsdangerous import URLSafeTimedSerializer from itsdangerous import URLSafeTimedSerializer
from werkzeug.datastructures import CallbackDict from werkzeug.datastructures import CallbackDict
from ._compat import collections_abc
from .helpers import is_ip from .helpers import is_ip
from .helpers import total_seconds from .helpers import total_seconds
from .json.tag import TaggedJSONSerializer from .json.tag import TaggedJSONSerializer
class SessionMixin(collections_abc.MutableMapping): class SessionMixin(MutableMapping):
"""Expands a basic dictionary with session attributes.""" """Expands a basic dictionary with session attributes."""
@property @property
@ -77,19 +76,19 @@ class SecureCookieSession(CallbackDict, SessionMixin):
self.modified = True self.modified = True
self.accessed = True self.accessed = True
super(SecureCookieSession, self).__init__(initial, on_update) super().__init__(initial, on_update)
def __getitem__(self, key): def __getitem__(self, key):
self.accessed = True self.accessed = True
return super(SecureCookieSession, self).__getitem__(key) return super().__getitem__(key)
def get(self, key, default=None): def get(self, key, default=None):
self.accessed = True self.accessed = True
return super(SecureCookieSession, self).get(key, default) return super().get(key, default)
def setdefault(self, key, default=None): def setdefault(self, key, default=None):
self.accessed = True self.accessed = True
return super(SecureCookieSession, self).setdefault(key, default) return super().setdefault(key, default)
class NullSession(SecureCookieSession): class NullSession(SecureCookieSession):
@ -109,7 +108,7 @@ class NullSession(SecureCookieSession):
del _fail del _fail
class SessionInterface(object): class SessionInterface:
"""The basic interface you have to implement in order to replace the """The basic interface you have to implement in order to replace the
default session interface which uses werkzeug's securecookie default session interface which uses werkzeug's securecookie
implementation. The only methods you have to implement are implementation. The only methods you have to implement are
@ -209,13 +208,13 @@ class SessionInterface(object):
rv = rv.rsplit(":", 1)[0].lstrip(".") rv = rv.rsplit(":", 1)[0].lstrip(".")
if "." not in rv: if "." not in rv:
# Chrome doesn't allow names without a '.' # Chrome doesn't allow names without a '.'. This should only
# this should only come up with localhost # come up with localhost. Hack around this by not setting
# hack around this by not setting the name, and show a warning # the name, and show a warning.
warnings.warn( warnings.warn(
'"{rv}" is not a valid cookie domain, it must contain a ".".' f"{rv!r} is not a valid cookie domain, it must contain"
" Add an entry to your hosts file, for example" " a '.'. Add an entry to your hosts file, for example"
' "{rv}.localdomain", and use that instead.'.format(rv=rv) f" '{rv}.localdomain', and use that instead."
) )
app.config["SESSION_COOKIE_DOMAIN"] = False app.config["SESSION_COOKIE_DOMAIN"] = False
return None return None
@ -233,7 +232,7 @@ class SessionInterface(object):
# if this is not an ip and app is mounted at the root, allow subdomain # if this is not an ip and app is mounted at the root, allow subdomain
# matching by adding a '.' prefix # matching by adding a '.' prefix
if self.get_cookie_path(app) == "/" and not ip: if self.get_cookie_path(app) == "/" and not ip:
rv = "." + rv rv = f".{rv}"
app.config["SESSION_COOKIE_DOMAIN"] = rv app.config["SESSION_COOKIE_DOMAIN"] = rv
return rv return rv

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
flask.signals flask.signals
~~~~~~~~~~~~~ ~~~~~~~~~~~~~
@ -16,11 +15,11 @@ try:
except ImportError: except ImportError:
signals_available = False signals_available = False
class Namespace(object): class Namespace:
def signal(self, name, doc=None): def signal(self, name, doc=None):
return _FakeSignal(name, doc) return _FakeSignal(name, doc)
class _FakeSignal(object): class _FakeSignal:
"""If blinker is unavailable, create a fake class with the same """If blinker is unavailable, create a fake class with the same
interface that allows sending of signals but will fail with an interface that allows sending of signals but will fail with an
error on anything else. Instead of doing anything on send, it error on anything else. Instead of doing anything on send, it

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
flask.templating flask.templating
~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
flask.testing flask.testing
~~~~~~~~~~~~~ ~~~~~~~~~~~~~
@ -9,7 +8,6 @@
:copyright: 2010 Pallets :copyright: 2010 Pallets
:license: BSD-3-Clause :license: BSD-3-Clause
""" """
import warnings
from contextlib import contextmanager from contextlib import contextmanager
import werkzeug.test import werkzeug.test
@ -52,7 +50,7 @@ class EnvironBuilder(werkzeug.test.EnvironBuilder):
subdomain=None, subdomain=None,
url_scheme=None, url_scheme=None,
*args, *args,
**kwargs **kwargs,
): ):
assert not (base_url or subdomain or url_scheme) or ( assert not (base_url or subdomain or url_scheme) or (
base_url is not None base_url is not None
@ -65,16 +63,15 @@ class EnvironBuilder(werkzeug.test.EnvironBuilder):
app_root = app.config["APPLICATION_ROOT"] app_root = app.config["APPLICATION_ROOT"]
if subdomain: if subdomain:
http_host = "{0}.{1}".format(subdomain, http_host) http_host = f"{subdomain}.{http_host}"
if url_scheme is None: if url_scheme is None:
url_scheme = app.config["PREFERRED_URL_SCHEME"] url_scheme = app.config["PREFERRED_URL_SCHEME"]
url = url_parse(path) url = url_parse(path)
base_url = "{scheme}://{netloc}/{path}".format( base_url = (
scheme=url.scheme or url_scheme, f"{url.scheme or url_scheme}://{url.netloc or http_host}"
netloc=url.netloc or http_host, f"/{app_root.lstrip('/')}"
path=app_root.lstrip("/"),
) )
path = url.path path = url.path
@ -83,7 +80,7 @@ class EnvironBuilder(werkzeug.test.EnvironBuilder):
path += sep + url.query path += sep + url.query
self.app = app self.app = app
super(EnvironBuilder, self).__init__(path, base_url, *args, **kwargs) super().__init__(path, base_url, *args, **kwargs)
def json_dumps(self, obj, **kwargs): def json_dumps(self, obj, **kwargs):
"""Serialize ``obj`` to a JSON-formatted string. """Serialize ``obj`` to a JSON-formatted string.
@ -95,23 +92,6 @@ class EnvironBuilder(werkzeug.test.EnvironBuilder):
return json_dumps(obj, **kwargs) return json_dumps(obj, **kwargs)
def make_test_environ_builder(*args, **kwargs):
"""Create a :class:`flask.testing.EnvironBuilder`.
.. deprecated: 1.1
Will be removed in 2.0. Construct
``flask.testing.EnvironBuilder`` directly instead.
"""
warnings.warn(
DeprecationWarning(
'"make_test_environ_builder()" is deprecated and will be'
' removed in 2.0. Construct "flask.testing.EnvironBuilder"'
" directly instead."
)
)
return EnvironBuilder(*args, **kwargs)
class FlaskClient(Client): class FlaskClient(Client):
"""Works like a regular Werkzeug test client but has some knowledge about """Works like a regular Werkzeug test client but has some knowledge about
how Flask works to defer the cleanup of the request context stack to the how Flask works to defer the cleanup of the request context stack to the
@ -130,10 +110,10 @@ class FlaskClient(Client):
preserve_context = False preserve_context = False
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(FlaskClient, self).__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.environ_base = { self.environ_base = {
"REMOTE_ADDR": "127.0.0.1", "REMOTE_ADDR": "127.0.0.1",
"HTTP_USER_AGENT": "werkzeug/" + werkzeug.__version__, "HTTP_USER_AGENT": f"werkzeug/{werkzeug.__version__}",
} }
@contextmanager @contextmanager
@ -257,7 +237,7 @@ class FlaskCliRunner(CliRunner):
def __init__(self, app, **kwargs): def __init__(self, app, **kwargs):
self.app = app self.app = app
super(FlaskCliRunner, self).__init__(**kwargs) super().__init__(**kwargs)
def invoke(self, cli=None, args=None, **kwargs): def invoke(self, cli=None, args=None, **kwargs):
"""Invokes a CLI command in an isolated environment. See """Invokes a CLI command in an isolated environment. See
@ -280,4 +260,4 @@ class FlaskCliRunner(CliRunner):
if "obj" not in kwargs: if "obj" not in kwargs:
kwargs["obj"] = ScriptInfo(create_app=lambda: self.app) kwargs["obj"] = ScriptInfo(create_app=lambda: self.app)
return super(FlaskCliRunner, self).invoke(cli, args, **kwargs) return super().invoke(cli, args, **kwargs)

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
flask.views flask.views
~~~~~~~~~~~ ~~~~~~~~~~~
@ -8,7 +7,6 @@
:copyright: 2010 Pallets :copyright: 2010 Pallets
:license: BSD-3-Clause :license: BSD-3-Clause
""" """
from ._compat import with_metaclass
from .globals import request from .globals import request
@ -17,7 +15,7 @@ http_method_funcs = frozenset(
) )
class View(object): class View:
"""Alternative way to use view functions. A subclass has to implement """Alternative way to use view functions. A subclass has to implement
:meth:`dispatch_request` which is called with the view arguments from :meth:`dispatch_request` which is called with the view arguments from
the URL routing system. If :attr:`methods` is provided the methods the URL routing system. If :attr:`methods` is provided the methods
@ -28,7 +26,7 @@ class View(object):
methods = ['GET'] methods = ['GET']
def dispatch_request(self, name): def dispatch_request(self, name):
return 'Hello %s!' % name return f"Hello {name}!"
app.add_url_rule('/hello/<name>', view_func=MyView.as_view('myview')) app.add_url_rule('/hello/<name>', view_func=MyView.as_view('myview'))
@ -114,7 +112,7 @@ class MethodViewType(type):
""" """
def __init__(cls, name, bases, d): def __init__(cls, name, bases, d):
super(MethodViewType, cls).__init__(name, bases, d) super().__init__(name, bases, d)
if "methods" not in d: if "methods" not in d:
methods = set() methods = set()
@ -135,7 +133,7 @@ class MethodViewType(type):
cls.methods = methods cls.methods = methods
class MethodView(with_metaclass(MethodViewType, View)): class MethodView(View, metaclass=MethodViewType):
"""A class-based view that dispatches request methods to the corresponding """A class-based view that dispatches request methods to the corresponding
class methods. For example, if you implement a ``get`` method, it will be class methods. For example, if you implement a ``get`` method, it will be
used to handle ``GET`` requests. :: used to handle ``GET`` requests. ::
@ -159,5 +157,5 @@ class MethodView(with_metaclass(MethodViewType, View)):
if meth is None and request.method == "HEAD": if meth is None and request.method == "HEAD":
meth = getattr(self, "get", None) meth = getattr(self, "get", None)
assert meth is not None, "Unimplemented method %r" % request.method assert meth is not None, f"Unimplemented method {request.method!r}"
return meth(*args, **kwargs) return meth(*args, **kwargs)

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
flask.wrappers flask.wrappers
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
@ -22,7 +21,7 @@ class JSONMixin(_JSONMixin):
def on_json_loading_failed(self, e): def on_json_loading_failed(self, e):
if current_app and current_app.debug: if current_app and current_app.debug:
raise BadRequest("Failed to decode JSON object: {0}".format(e)) raise BadRequest(f"Failed to decode JSON object: {e}")
raise BadRequest() raise BadRequest()
@ -134,4 +133,4 @@ class Response(ResponseBase, JSONMixin):
return current_app.config["MAX_COOKIE_SIZE"] return current_app.config["MAX_COOKIE_SIZE"]
# return Werkzeug's default when not in an app context # return Werkzeug's default when not in an app context
return super(Response, self).max_cookie_size return super().max_cookie_size

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
tests.conftest tests.conftest
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
@ -112,14 +111,13 @@ def limit_loader(request, monkeypatch):
if not request.param: if not request.param:
return return
class LimitedLoader(object): class LimitedLoader:
def __init__(self, loader): def __init__(self, loader):
self.loader = loader self.loader = loader
def __getattr__(self, name): def __getattr__(self, name):
if name in ("archive", "get_filename"): if name in {"archive", "get_filename"}:
msg = "Mocking a loader which does not have `%s.`" % name raise AttributeError(f"Mocking a loader which does not have {name!r}.")
raise AttributeError(msg)
return getattr(self.loader, name) return getattr(self.loader, name)
old_get_loader = pkgutil.get_loader old_get_loader = pkgutil.get_loader
@ -149,7 +147,7 @@ def site_packages(modules_tmpdir, monkeypatch):
"""Create a fake site-packages.""" """Create a fake site-packages."""
rv = ( rv = (
modules_tmpdir.mkdir("lib") modules_tmpdir.mkdir("lib")
.mkdir("python{x[0]}.{x[1]}".format(x=sys.version_info)) .mkdir(f"python{sys.version_info.major}.{sys.version_info.minor}")
.mkdir("site-packages") .mkdir("site-packages")
) )
monkeypatch.syspath_prepend(str(rv)) monkeypatch.syspath_prepend(str(rv))
@ -162,23 +160,21 @@ def install_egg(modules_tmpdir, monkeypatch):
sys.path.""" sys.path."""
def inner(name, base=modules_tmpdir): def inner(name, base=modules_tmpdir):
if not isinstance(name, str):
raise ValueError(name)
base.join(name).ensure_dir() base.join(name).ensure_dir()
base.join(name).join("__init__.py").ensure() base.join(name).join("__init__.py").ensure()
egg_setup = base.join("setup.py") egg_setup = base.join("setup.py")
egg_setup.write( egg_setup.write(
textwrap.dedent( textwrap.dedent(
""" f"""
from setuptools import setup from setuptools import setup
setup(name='{0}', setup(
version='1.0', name="{name}",
packages=['site_egg'], version="1.0",
zip_safe=True) packages=["site_egg"],
""".format( zip_safe=True,
name
) )
"""
) )
) )
@ -187,7 +183,7 @@ def install_egg(modules_tmpdir, monkeypatch):
subprocess.check_call( subprocess.check_call(
[sys.executable, "setup.py", "bdist_egg"], cwd=str(modules_tmpdir) [sys.executable, "setup.py", "bdist_egg"], cwd=str(modules_tmpdir)
) )
egg_path, = modules_tmpdir.join("dist/").listdir() (egg_path,) = modules_tmpdir.join("dist/").listdir()
monkeypatch.syspath_prepend(str(egg_path)) monkeypatch.syspath_prepend(str(egg_path))
return egg_path return egg_path

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
tests.appctx tests.appctx
~~~~~~~~~~~~ ~~~~~~~~~~~~
@ -163,7 +162,7 @@ def test_app_ctx_globals_methods(app, app_ctx):
def test_custom_app_ctx_globals_class(app): def test_custom_app_ctx_globals_class(app):
class CustomRequestGlobals(object): class CustomRequestGlobals:
def __init__(self): def __init__(self):
self.spam = "eggs" self.spam = "eggs"
@ -190,7 +189,7 @@ def test_context_refcounts(app, client):
pass pass
env = flask._request_ctx_stack.top.request.environ env = flask._request_ctx_stack.top.request.environ
assert env["werkzeug.request"] is not None assert env["werkzeug.request"] is not None
return u"" return ""
res = client.get("/") res = client.get("/")
assert res.status_code == 200 assert res.status_code == 200

View file

@ -1,6 +1,3 @@
from __future__ import absolute_import
from __future__ import print_function
from flask import Flask from flask import Flask
testapp = Flask("testapp") testapp = Flask("testapp")

View file

@ -1,6 +1,3 @@
from __future__ import absolute_import
from __future__ import print_function
from flask import Flask from flask import Flask

View file

@ -1,6 +1,3 @@
from __future__ import absolute_import
from __future__ import print_function
from flask import Flask from flask import Flask
raise ImportError() raise ImportError()

View file

@ -1,6 +1,3 @@
from __future__ import absolute_import
from __future__ import print_function
from flask import Flask from flask import Flask
app1 = Flask("app1") app1 = Flask("app1")

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
tests.basic tests.basic
~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~
@ -24,7 +23,6 @@ from werkzeug.http import parse_date
from werkzeug.routing import BuildError from werkzeug.routing import BuildError
import flask import flask
from flask._compat import text_type
def test_options_work(app, client): def test_options_work(app, client):
@ -92,12 +90,7 @@ def test_provide_automatic_options_kwarg(app, client):
assert rv.status_code == 405 assert rv.status_code == 405
assert sorted(rv.allow) == ["GET", "HEAD"] assert sorted(rv.allow) == ["GET", "HEAD"]
# Older versions of Werkzeug.test.Client don't have an options method rv = client.open("/", method="OPTIONS")
if hasattr(client, "options"):
rv = client.options("/")
else:
rv = client.open("/", method="OPTIONS")
assert rv.status_code == 405 assert rv.status_code == 405
rv = client.head("/") rv = client.head("/")
@ -110,11 +103,7 @@ def test_provide_automatic_options_kwarg(app, client):
assert rv.status_code == 405 assert rv.status_code == 405
assert sorted(rv.allow) == ["GET", "HEAD", "POST"] assert sorted(rv.allow) == ["GET", "HEAD", "POST"]
if hasattr(client, "options"): rv = client.open("/more", method="OPTIONS")
rv = client.options("/more")
else:
rv = client.open("/more", method="OPTIONS")
assert rv.status_code == 405 assert rv.status_code == 405
@ -288,7 +277,7 @@ def test_session_using_server_name_port_and_path(app, client):
def test_session_using_application_root(app, client): def test_session_using_application_root(app, client):
class PrefixPathMiddleware(object): class PrefixPathMiddleware:
def __init__(self, app, prefix): def __init__(self, app, prefix):
self.app = app self.app = app
self.prefix = prefix self.prefix = prefix
@ -372,7 +361,7 @@ def test_session_localhost_warning(recwarn, app, client):
rv = client.get("/", "http://localhost:5000/") rv = client.get("/", "http://localhost:5000/")
assert "domain" not in rv.headers["set-cookie"].lower() assert "domain" not in rv.headers["set-cookie"].lower()
w = recwarn.pop(UserWarning) w = recwarn.pop(UserWarning)
assert '"localhost" is not a valid cookie domain' in str(w.message) assert "'localhost' is not a valid cookie domain" in str(w.message)
def test_session_ip_warning(recwarn, app, client): def test_session_ip_warning(recwarn, app, client):
@ -413,7 +402,7 @@ def test_session_expiration(app, client):
@app.route("/test") @app.route("/test")
def test(): def test():
return text_type(flask.session.permanent) return str(flask.session.permanent)
rv = client.get("/") rv = client.get("/")
assert "set-cookie" in rv.headers assert "set-cookie" in rv.headers
@ -593,18 +582,18 @@ def test_extended_flashing(app):
@app.route("/") @app.route("/")
def index(): def index():
flask.flash(u"Hello World") flask.flash("Hello World")
flask.flash(u"Hello World", "error") flask.flash("Hello World", "error")
flask.flash(flask.Markup(u"<em>Testing</em>"), "warning") flask.flash(flask.Markup("<em>Testing</em>"), "warning")
return "" return ""
@app.route("/test/") @app.route("/test/")
def test(): def test():
messages = flask.get_flashed_messages() messages = flask.get_flashed_messages()
assert list(messages) == [ assert list(messages) == [
u"Hello World", "Hello World",
u"Hello World", "Hello World",
flask.Markup(u"<em>Testing</em>"), flask.Markup("<em>Testing</em>"),
] ]
return "" return ""
@ -613,9 +602,9 @@ def test_extended_flashing(app):
messages = flask.get_flashed_messages(with_categories=True) messages = flask.get_flashed_messages(with_categories=True)
assert len(messages) == 3 assert len(messages) == 3
assert list(messages) == [ assert list(messages) == [
("message", u"Hello World"), ("message", "Hello World"),
("error", u"Hello World"), ("error", "Hello World"),
("warning", flask.Markup(u"<em>Testing</em>")), ("warning", flask.Markup("<em>Testing</em>")),
] ]
return "" return ""
@ -624,7 +613,7 @@ def test_extended_flashing(app):
messages = flask.get_flashed_messages( messages = flask.get_flashed_messages(
category_filter=["message"], with_categories=True category_filter=["message"], with_categories=True
) )
assert list(messages) == [("message", u"Hello World")] assert list(messages) == [("message", "Hello World")]
return "" return ""
@app.route("/test_filters/") @app.route("/test_filters/")
@ -633,8 +622,8 @@ def test_extended_flashing(app):
category_filter=["message", "warning"], with_categories=True category_filter=["message", "warning"], with_categories=True
) )
assert list(messages) == [ assert list(messages) == [
("message", u"Hello World"), ("message", "Hello World"),
("warning", flask.Markup(u"<em>Testing</em>")), ("warning", flask.Markup("<em>Testing</em>")),
] ]
return "" return ""
@ -642,8 +631,8 @@ def test_extended_flashing(app):
def test_filters2(): def test_filters2():
messages = flask.get_flashed_messages(category_filter=["message", "warning"]) messages = flask.get_flashed_messages(category_filter=["message", "warning"])
assert len(messages) == 2 assert len(messages) == 2
assert messages[0] == u"Hello World" assert messages[0] == "Hello World"
assert messages[1] == flask.Markup(u"<em>Testing</em>") assert messages[1] == flask.Markup("<em>Testing</em>")
return "" return ""
# Create new test client on each test to clean flashed messages. # Create new test client on each test to clean flashed messages.
@ -1106,17 +1095,17 @@ def test_enctype_debug_helper(app, client):
with pytest.raises(DebugFilesKeyError) as e: with pytest.raises(DebugFilesKeyError) as e:
client.post("/fail", data={"foo": "index.txt"}) client.post("/fail", data={"foo": "index.txt"})
assert "no file contents were transmitted" in str(e.value) assert "no file contents were transmitted" in str(e.value)
assert 'This was submitted: "index.txt"' in str(e.value) assert "This was submitted: 'index.txt'" in str(e.value)
def test_response_types(app, client): def test_response_types(app, client):
@app.route("/text") @app.route("/text")
def from_text(): def from_text():
return u"Hällo Wörld" return "Hällo Wörld"
@app.route("/bytes") @app.route("/bytes")
def from_bytes(): def from_bytes():
return u"Hällo Wörld".encode("utf-8") return "Hällo Wörld".encode()
@app.route("/full_tuple") @app.route("/full_tuple")
def from_full_tuple(): def from_full_tuple():
@ -1153,8 +1142,8 @@ def test_response_types(app, client):
def from_dict(): def from_dict():
return {"foo": "bar"}, 201 return {"foo": "bar"}, 201
assert client.get("/text").data == u"Hällo Wörld".encode("utf-8") assert client.get("/text").data == "Hällo Wörld".encode()
assert client.get("/bytes").data == u"Hällo Wörld".encode("utf-8") assert client.get("/bytes").data == "Hällo Wörld".encode()
rv = client.get("/full_tuple") rv = client.get("/full_tuple")
assert rv.data == b"Meh" assert rv.data == b"Meh"
@ -1621,11 +1610,11 @@ def test_inject_blueprint_url_defaults(app):
def test_nonascii_pathinfo(app, client): def test_nonascii_pathinfo(app, client):
@app.route(u"/киртест") @app.route("/киртест")
def index(): def index():
return "Hello World!" return "Hello World!"
rv = client.get(u"/киртест") rv = client.get("/киртест")
assert rv.data == b"Hello World!" assert rv.data == b"Hello World!"
@ -1832,7 +1821,7 @@ def test_subdomain_matching():
@app.route("/", subdomain="<user>") @app.route("/", subdomain="<user>")
def index(user): def index(user):
return "index for %s" % user return f"index for {user}"
rv = client.get("/", "http://mitsuhiko.localhost.localdomain/") rv = client.get("/", "http://mitsuhiko.localhost.localdomain/")
assert rv.data == b"index for mitsuhiko" assert rv.data == b"index for mitsuhiko"
@ -1845,7 +1834,7 @@ def test_subdomain_matching_with_ports():
@app.route("/", subdomain="<user>") @app.route("/", subdomain="<user>")
def index(user): def index(user):
return "index for %s" % user return f"index for {user}"
rv = client.get("/", "http://mitsuhiko.localhost.localdomain:3000/") rv = client.get("/", "http://mitsuhiko.localhost.localdomain:3000/")
assert rv.data == b"index for mitsuhiko" assert rv.data == b"index for mitsuhiko"
@ -1885,7 +1874,7 @@ def test_multi_route_rules(app, client):
def test_multi_route_class_views(app, client): def test_multi_route_class_views(app, client):
class View(object): class View:
def __init__(self, app): def __init__(self, app):
app.add_url_rule("/", "index", self.index) app.add_url_rule("/", "index", self.index)
app.add_url_rule("/<test>/", "index", self.index) app.add_url_rule("/<test>/", "index", self.index)
@ -1917,12 +1906,12 @@ def test_run_server_port(monkeypatch, app):
# Mocks werkzeug.serving.run_simple method # Mocks werkzeug.serving.run_simple method
def run_simple_mock(hostname, port, application, *args, **kwargs): def run_simple_mock(hostname, port, application, *args, **kwargs):
rv["result"] = "running on %s:%s ..." % (hostname, port) rv["result"] = f"running on {hostname}:{port} ..."
monkeypatch.setattr(werkzeug.serving, "run_simple", run_simple_mock) monkeypatch.setattr(werkzeug.serving, "run_simple", run_simple_mock)
hostname, port = "localhost", 8000 hostname, port = "localhost", 8000
app.run(hostname, port, debug=True) app.run(hostname, port, debug=True)
assert rv["result"] == "running on %s:%s ..." % (hostname, port) assert rv["result"] == f"running on {hostname}:{port} ..."
@pytest.mark.parametrize( @pytest.mark.parametrize(

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
tests.blueprints tests.blueprints
~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~
@ -15,7 +14,6 @@ from jinja2 import TemplateNotFound
from werkzeug.http import parse_cache_control_header from werkzeug.http import parse_cache_control_header
import flask import flask
from flask._compat import text_type
def test_blueprint_specific_error_handling(app, client): def test_blueprint_specific_error_handling(app, client):
@ -146,11 +144,11 @@ def test_blueprint_url_defaults(app, client):
@bp.route("/foo", defaults={"baz": 42}) @bp.route("/foo", defaults={"baz": 42})
def foo(bar, baz): def foo(bar, baz):
return "%s/%d" % (bar, baz) return f"{bar}/{baz:d}"
@bp.route("/bar") @bp.route("/bar")
def bar(bar): def bar(bar):
return text_type(bar) return str(bar)
app.register_blueprint(bp, url_prefix="/1", url_defaults={"bar": 23}) app.register_blueprint(bp, url_prefix="/1", url_defaults={"bar": 23})
app.register_blueprint(bp, url_prefix="/2", url_defaults={"bar": 19}) app.register_blueprint(bp, url_prefix="/2", url_defaults={"bar": 19})

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
tests.test_cli tests.test_cli
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
@ -8,8 +7,6 @@
""" """
# This file was part of Flask-CLI and was modified under the terms of # This file was part of Flask-CLI and was modified under the terms of
# its Revised BSD License. Copyright © 2015 CERN. # its Revised BSD License. Copyright © 2015 CERN.
from __future__ import absolute_import
import os import os
import ssl import ssl
import sys import sys
@ -261,7 +258,7 @@ def test_get_version(test_apps, capsys):
from werkzeug import __version__ as werkzeug_version from werkzeug import __version__ as werkzeug_version
from platform import python_version from platform import python_version
class MockCtx(object): class MockCtx:
resilient_parsing = False resilient_parsing = False
color = None color = None
@ -271,9 +268,9 @@ def test_get_version(test_apps, capsys):
ctx = MockCtx() ctx = MockCtx()
get_version(ctx, None, "test") get_version(ctx, None, "test")
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert "Python " + python_version() in out assert f"Python {python_version()}" in out
assert "Flask " + flask_version in out assert f"Flask {flask_version}" in out
assert "Werkzeug " + werkzeug_version in out assert f"Werkzeug {werkzeug_version}" in out
def test_scriptinfo(test_apps, monkeypatch): def test_scriptinfo(test_apps, monkeypatch):
@ -291,7 +288,7 @@ def test_scriptinfo(test_apps, monkeypatch):
app = obj.load_app() app = obj.load_app()
assert app.name == "testapp" assert app.name == "testapp"
assert obj.load_app() is app assert obj.load_app() is app
obj = ScriptInfo(app_import_path=cli_app_path + ":testapp") obj = ScriptInfo(app_import_path=f"{cli_app_path}:testapp")
app = obj.load_app() app = obj.load_app()
assert app.name == "testapp" assert app.name == "testapp"
assert obj.load_app() is app assert obj.load_app() is app
@ -409,7 +406,7 @@ def test_flaskgroup_debug(runner, set_debug_flag):
result = runner.invoke(cli, ["test"]) result = runner.invoke(cli, ["test"])
assert result.exit_code == 0 assert result.exit_code == 0
assert result.output == "%s\n" % str(not set_debug_flag) assert result.output == f"{not set_debug_flag}\n"
def test_print_exceptions(runner): def test_print_exceptions(runner):
@ -589,16 +586,11 @@ def test_run_cert_import(monkeypatch):
with pytest.raises(click.BadParameter): with pytest.raises(click.BadParameter):
run_command.make_context("run", ["--cert", "not_here"]) run_command.make_context("run", ["--cert", "not_here"])
# not an SSLContext with pytest.raises(click.BadParameter):
if sys.version_info >= (2, 7, 9): run_command.make_context("run", ["--cert", "flask"])
with pytest.raises(click.BadParameter):
run_command.make_context("run", ["--cert", "flask"])
# SSLContext # SSLContext
if sys.version_info < (2, 7, 9): ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ssl_context = object()
else:
ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
monkeypatch.setitem(sys.modules, "ssl_context", ssl_context) monkeypatch.setitem(sys.modules, "ssl_context", ssl_context)
ctx = run_command.make_context("run", ["--cert", "ssl_context"]) ctx = run_command.make_context("run", ["--cert", "ssl_context"])
@ -664,4 +656,4 @@ def test_cli_empty(app):
app.register_blueprint(bp) app.register_blueprint(bp)
result = app.test_cli_runner().invoke(args=["blue", "--help"]) result = app.test_cli_runner().invoke(args=["blue", "--help"])
assert result.exit_code == 2, "Unexpected success:\n\n" + result.output assert result.exit_code == 2, f"Unexpected success:\n\n{result.output}"

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
tests.test_config tests.test_config
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
@ -14,7 +13,6 @@ from datetime import timedelta
import pytest import pytest
import flask import flask
from flask._compat import PY2
# config keys used for the TestConfig # config keys used for the TestConfig
@ -30,7 +28,7 @@ def common_object_test(app):
def test_config_from_pyfile(): def test_config_from_pyfile():
app = flask.Flask(__name__) app = flask.Flask(__name__)
app.config.from_pyfile(__file__.rsplit(".", 1)[0] + ".py") app.config.from_pyfile(f"{__file__.rsplit('.', 1)[0]}.py")
common_object_test(app) common_object_test(app)
@ -66,7 +64,7 @@ def test_config_from_mapping():
def test_config_from_class(): def test_config_from_class():
class Base(object): class Base:
TEST_KEY = "foo" TEST_KEY = "foo"
class Test(Base): class Test(Base):
@ -86,7 +84,7 @@ def test_config_from_envvar(monkeypatch):
assert not app.config.from_envvar("FOO_SETTINGS", silent=True) assert not app.config.from_envvar("FOO_SETTINGS", silent=True)
monkeypatch.setattr( monkeypatch.setattr(
"os.environ", {"FOO_SETTINGS": __file__.rsplit(".", 1)[0] + ".py"} "os.environ", {"FOO_SETTINGS": f"{__file__.rsplit('.', 1)[0]}.py"}
) )
assert app.config.from_envvar("FOO_SETTINGS") assert app.config.from_envvar("FOO_SETTINGS")
common_object_test(app) common_object_test(app)
@ -187,17 +185,13 @@ def test_from_pyfile_weird_encoding(tmpdir, encoding):
f = tmpdir.join("my_config.py") f = tmpdir.join("my_config.py")
f.write_binary( f.write_binary(
textwrap.dedent( textwrap.dedent(
u""" f"""
# -*- coding: {0} -*- # -*- coding: {encoding} -*-
TEST_VALUE = "föö" TEST_VALUE = "föö"
""".format( """
encoding
)
).encode(encoding) ).encode(encoding)
) )
app = flask.Flask(__name__) app = flask.Flask(__name__)
app.config.from_pyfile(str(f)) app.config.from_pyfile(str(f))
value = app.config["TEST_VALUE"] value = app.config["TEST_VALUE"]
if PY2: assert value == "föö"
value = value.decode(encoding)
assert value == u"föö"

View file

@ -10,7 +10,7 @@ def test_custom_converters(app, client):
return value.split(",") return value.split(",")
def to_url(self, value): def to_url(self, value):
base_to_url = super(ListConverter, self).to_url base_to_url = super().to_url
return ",".join(base_to_url(x) for x in value) return ",".join(base_to_url(x) for x in value)
app.url_map.converters["list"] = ListConverter app.url_map.converters["list"] = ListConverter

View file

@ -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

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
tests.helpers tests.helpers
~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~
@ -24,9 +23,6 @@ from werkzeug.http import parse_options_header
import flask import flask
from flask import json from flask import json
from flask._compat import PY2
from flask._compat import StringIO
from flask._compat import text_type
from flask.helpers import get_debug_flag from flask.helpers import get_debug_flag
from flask.helpers import get_env from flask.helpers import get_env
@ -41,7 +37,7 @@ def has_encoding(name):
return False return False
class FakePath(object): class FakePath:
"""Fake object to represent a ``PathLike object``. """Fake object to represent a ``PathLike object``.
This represents a ``pathlib.Path`` object in python 3. This represents a ``pathlib.Path`` object in python 3.
@ -76,9 +72,9 @@ class FixedOffset(datetime.tzinfo):
return datetime.timedelta() return datetime.timedelta()
class TestJSON(object): class TestJSON:
@pytest.mark.parametrize( @pytest.mark.parametrize(
"value", (1, "t", True, False, None, [], [1, 2, 3], {}, {"foo": u"🐍"}) "value", (1, "t", True, False, None, [], [1, 2, 3], {}, {"foo": "🐍"})
) )
@pytest.mark.parametrize( @pytest.mark.parametrize(
"encoding", "encoding",
@ -95,7 +91,6 @@ class TestJSON(object):
) )
def test_detect_encoding(self, value, encoding): def test_detect_encoding(self, value, encoding):
data = json.dumps(value).encode(encoding) data = json.dumps(value).encode(encoding)
assert json.detect_encoding(data) == encoding
assert json.loads(data) == value assert json.loads(data) == value
@pytest.mark.parametrize("debug", (True, False)) @pytest.mark.parametrize("debug", (True, False))
@ -116,7 +111,7 @@ class TestJSON(object):
def test_json_bad_requests(self, app, client): def test_json_bad_requests(self, app, client):
@app.route("/json", methods=["POST"]) @app.route("/json", methods=["POST"])
def return_json(): def return_json():
return flask.jsonify(foo=text_type(flask.request.get_json())) return flask.jsonify(foo=str(flask.request.get_json()))
rv = client.post("/json", data="malformed", content_type="application/json") rv = client.post("/json", data="malformed", content_type="application/json")
assert rv.status_code == 400 assert rv.status_code == 400
@ -130,17 +125,17 @@ class TestJSON(object):
assert rv.data == b"foo" assert rv.data == b"foo"
@pytest.mark.parametrize( @pytest.mark.parametrize(
"test_value,expected", [(True, '"\\u2603"'), (False, u'"\u2603"')] "test_value,expected", [(True, '"\\u2603"'), (False, '"\u2603"')]
) )
def test_json_as_unicode(self, test_value, expected, app, app_ctx): def test_json_as_unicode(self, test_value, expected, app, app_ctx):
app.config["JSON_AS_ASCII"] = test_value app.config["JSON_AS_ASCII"] = test_value
rv = flask.json.dumps(u"\N{SNOWMAN}") rv = flask.json.dumps("\N{SNOWMAN}")
assert rv == expected assert rv == expected
def test_json_dump_to_file(self, app, app_ctx): def test_json_dump_to_file(self, app, app_ctx):
test_data = {"name": "Flask"} test_data = {"name": "Flask"}
out = StringIO() out = io.StringIO()
flask.json.dump(test_data, out) flask.json.dump(test_data, out)
out.seek(0) out.seek(0)
@ -221,7 +216,7 @@ class TestJSON(object):
) )
for i, d in enumerate(test_dates): for i, d in enumerate(test_dates):
url = "/datetest{0}".format(i) url = f"/datetest{i}"
app.add_url_rule(url, str(i), lambda val=d: flask.jsonify(x=val)) app.add_url_rule(url, str(i), lambda val=d: flask.jsonify(x=val))
rv = client.get(url) rv = client.get(url)
assert rv.mimetype == "application/json" assert rv.mimetype == "application/json"
@ -254,7 +249,7 @@ class TestJSON(object):
@app.route("/add", methods=["POST"]) @app.route("/add", methods=["POST"])
def add(): def add():
json = flask.request.get_json() json = flask.request.get_json()
return text_type(json["a"] + json["b"]) return str(json["a"] + json["b"])
rv = client.post( rv = client.post(
"/add", "/add",
@ -266,8 +261,8 @@ class TestJSON(object):
def test_template_escaping(self, app, req_ctx): def test_template_escaping(self, app, req_ctx):
render = flask.render_template_string render = flask.render_template_string
rv = flask.json.htmlsafe_dumps("</script>") rv = flask.json.htmlsafe_dumps("</script>")
assert rv == u'"\\u003c/script\\u003e"' assert rv == '"\\u003c/script\\u003e"'
assert type(rv) == text_type assert type(rv) is str
rv = render('{{ "</script>"|tojson }}') rv = render('{{ "</script>"|tojson }}')
assert rv == '"\\u003c/script\\u003e"' assert rv == '"\\u003c/script\\u003e"'
rv = render('{{ "<\0/script>"|tojson }}') rv = render('{{ "<\0/script>"|tojson }}')
@ -284,14 +279,14 @@ class TestJSON(object):
assert rv == '<a ng-data=\'{"x": ["foo", "bar", "baz\\u0027"]}\'></a>' assert rv == '<a ng-data=\'{"x": ["foo", "bar", "baz\\u0027"]}\'></a>'
def test_json_customization(self, app, client): def test_json_customization(self, app, client):
class X(object): # noqa: B903, for Python2 compatibility class X: # noqa: B903, for Python2 compatibility
def __init__(self, val): def __init__(self, val):
self.val = val self.val = val
class MyEncoder(flask.json.JSONEncoder): class MyEncoder(flask.json.JSONEncoder):
def default(self, o): def default(self, o):
if isinstance(o, X): if isinstance(o, X):
return "<%d>" % o.val return f"<{o.val}>"
return flask.json.JSONEncoder.default(self, o) return flask.json.JSONEncoder.default(self, o)
class MyDecoder(flask.json.JSONDecoder): class MyDecoder(flask.json.JSONDecoder):
@ -319,14 +314,16 @@ class TestJSON(object):
assert rv.data == b'"<42>"' assert rv.data == b'"<42>"'
def test_blueprint_json_customization(self, app, client): def test_blueprint_json_customization(self, app, client):
class X(object): # noqa: B903, for Python2 compatibility class X:
__slots__ = ("val",)
def __init__(self, val): def __init__(self, val):
self.val = val self.val = val
class MyEncoder(flask.json.JSONEncoder): class MyEncoder(flask.json.JSONEncoder):
def default(self, o): def default(self, o):
if isinstance(o, X): if isinstance(o, X):
return "<%d>" % o.val return f"<{o.val}>"
return flask.json.JSONEncoder.default(self, o) return flask.json.JSONEncoder.default(self, o)
@ -372,9 +369,9 @@ class TestJSON(object):
def index(): def index():
return flask.request.args["foo"] return flask.request.args["foo"]
rv = client.get(u"/?foo=정상처리".encode("euc-kr")) rv = client.get("/?foo=정상처리".encode("euc-kr"))
assert rv.status_code == 200 assert rv.status_code == 200
assert rv.data == u"정상처리".encode("utf-8") assert rv.data == "정상처리".encode()
def test_json_key_sorting(self, app, client): def test_json_key_sorting(self, app, client):
app.debug = True app.debug = True
@ -447,7 +444,7 @@ class TestJSON(object):
assert lines == sorted_by_str assert lines == sorted_by_str
class PyStringIO(object): class PyBytesIO:
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self._io = io.BytesIO(*args, **kwargs) self._io = io.BytesIO(*args, **kwargs)
@ -455,7 +452,7 @@ class PyStringIO(object):
return getattr(self._io, name) return getattr(self._io, name)
class TestSendfile(object): class TestSendfile:
def test_send_file_regular(self, app, req_ctx): def test_send_file_regular(self, app, req_ctx):
rv = flask.send_file("static/index.html") rv = flask.send_file("static/index.html")
assert rv.direct_passthrough assert rv.direct_passthrough
@ -503,11 +500,7 @@ class TestSendfile(object):
[ [
lambda app: open(os.path.join(app.static_folder, "index.html"), "rb"), lambda app: open(os.path.join(app.static_folder, "index.html"), "rb"),
lambda app: io.BytesIO(b"Test"), lambda app: io.BytesIO(b"Test"),
pytest.param( lambda app: PyBytesIO(b"Test"),
lambda app: StringIO("Test"),
marks=pytest.mark.skipif(not PY2, reason="Python 2 only"),
),
lambda app: PyStringIO(b"Test"),
], ],
) )
@pytest.mark.usefixtures("req_ctx") @pytest.mark.usefixtures("req_ctx")
@ -524,11 +517,8 @@ class TestSendfile(object):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"opener", "opener",
[ [
lambda app: io.StringIO(u"Test"), lambda app: io.StringIO("Test"),
pytest.param( lambda app: open(os.path.join(app.static_folder, "index.html")),
lambda app: open(os.path.join(app.static_folder, "index.html")),
marks=pytest.mark.skipif(PY2, reason="Python 3 only"),
),
], ],
) )
@pytest.mark.usefixtures("req_ctx") @pytest.mark.usefixtures("req_ctx")
@ -684,15 +674,13 @@ class TestSendfile(object):
( (
("index.html", "index.html", False), ("index.html", "index.html", False),
( (
u"Ñandúpingüino.txt", "Ñandúpingüino.txt",
'"Nandu/pinguino.txt"', '"Nandu/pinguino.txt"',
"%C3%91and%C3%BA%EF%BC%8Fping%C3%BCino.txt", "%C3%91and%C3%BA%EF%BC%8Fping%C3%BCino.txt",
), ),
(u"Vögel.txt", "Vogel.txt", "V%C3%B6gel.txt"), ("Vögel.txt", "Vogel.txt", "V%C3%B6gel.txt"),
# Native string not marked as Unicode on Python 2
("tést.txt", "test.txt", "t%C3%A9st.txt"),
# ":/" are not safe in filename* value # ":/" are not safe in filename* value
(u"те:/ст", '":/"', "%D1%82%D0%B5%3A%2F%D1%81%D1%82"), ("те:/ст", '":/"', "%D1%82%D0%B5%3A%2F%D1%81%D1%82"),
), ),
) )
def test_attachment_filename_encoding(self, filename, ascii, utf8): def test_attachment_filename_encoding(self, filename, ascii, utf8):
@ -701,9 +689,9 @@ class TestSendfile(object):
) )
rv.close() rv.close()
content_disposition = rv.headers["Content-Disposition"] content_disposition = rv.headers["Content-Disposition"]
assert "filename=%s" % ascii in content_disposition assert f"filename={ascii}" in content_disposition
if utf8: if utf8:
assert "filename*=UTF-8''" + utf8 in content_disposition assert f"filename*=UTF-8''{utf8}" in content_disposition
else: else:
assert "filename*=UTF-8''" not in content_disposition assert "filename*=UTF-8''" not in content_disposition
@ -788,7 +776,7 @@ class TestSendfile(object):
flask.send_from_directory("static", "bad\x00") flask.send_from_directory("static", "bad\x00")
class TestUrlFor(object): class TestUrlFor:
def test_url_for_with_anchor(self, app, req_ctx): def test_url_for_with_anchor(self, app, req_ctx):
@app.route("/") @app.route("/")
def index(): def index():
@ -832,7 +820,7 @@ class TestUrlFor(object):
def get(self, id=None): def get(self, id=None):
if id is None: if id is None:
return "List" return "List"
return "Get %d" % id return f"Get {id:d}"
def post(self): def post(self):
return "Create" return "Create"
@ -847,7 +835,7 @@ class TestUrlFor(object):
assert flask.url_for("myview", _method="POST") == "/myview/create" assert flask.url_for("myview", _method="POST") == "/myview/create"
class TestNoImports(object): class TestNoImports:
"""Test Flasks are created without import. """Test Flasks are created without import.
Avoiding ``__import__`` helps create Flask instances where there are errors Avoiding ``__import__`` helps create Flask instances where there are errors
@ -866,7 +854,7 @@ class TestNoImports(object):
AssertionError("Flask(import_name) is importing import_name.") AssertionError("Flask(import_name) is importing import_name.")
class TestStreaming(object): class TestStreaming:
def test_streaming_with_context(self, app, client): def test_streaming_with_context(self, app, client):
@app.route("/") @app.route("/")
def index(): def index():
@ -897,7 +885,7 @@ class TestStreaming(object):
def test_streaming_with_context_and_custom_close(self, app, client): def test_streaming_with_context_and_custom_close(self, app, client):
called = [] called = []
class Wrapper(object): class Wrapper:
def __init__(self, gen): def __init__(self, gen):
self._gen = gen self._gen = gen
@ -940,7 +928,7 @@ class TestStreaming(object):
assert rv.data == b"flask" assert rv.data == b"flask"
class TestSafeJoin(object): class TestSafeJoin:
def test_safe_join(self): def test_safe_join(self):
# Valid combinations of *args and expected joined paths. # Valid combinations of *args and expected joined paths.
passing = ( passing = (
@ -981,7 +969,7 @@ class TestSafeJoin(object):
print(flask.safe_join(*args)) print(flask.safe_join(*args))
class TestHelpers(object): class TestHelpers:
@pytest.mark.parametrize( @pytest.mark.parametrize(
"debug, expected_flag, expected_default_flag", "debug, expected_flag, expected_default_flag",
[ [

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
tests.test_instance tests.test_instance
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
@ -12,7 +11,6 @@ import sys
import pytest import pytest
import flask import flask
from flask._compat import PY2
def test_explicit_instance_paths(modules_tmpdir): def test_explicit_instance_paths(modules_tmpdir):
@ -24,7 +22,7 @@ def test_explicit_instance_paths(modules_tmpdir):
assert app.instance_path == str(modules_tmpdir) assert app.instance_path == str(modules_tmpdir)
@pytest.mark.xfail(reason="TODO: weird interaction with tox") @pytest.mark.xfail(reason="weird interaction with tox")
def test_main_module_paths(modules_tmpdir, purge_module): def test_main_module_paths(modules_tmpdir, purge_module):
app = modules_tmpdir.join("main_app.py") app = modules_tmpdir.join("main_app.py")
app.write('import flask\n\napp = flask.Flask("__main__")') app.write('import flask\n\napp = flask.Flask("__main__")')
@ -36,6 +34,7 @@ def test_main_module_paths(modules_tmpdir, purge_module):
assert app.instance_path == os.path.join(here, "instance") assert app.instance_path == os.path.join(here, "instance")
@pytest.mark.xfail(reason="weird interaction with tox")
def test_uninstalled_module_paths(modules_tmpdir, purge_module): def test_uninstalled_module_paths(modules_tmpdir, purge_module):
app = modules_tmpdir.join("config_module_app.py").write( app = modules_tmpdir.join("config_module_app.py").write(
"import os\n" "import os\n"
@ -50,6 +49,7 @@ def test_uninstalled_module_paths(modules_tmpdir, purge_module):
assert app.instance_path == str(modules_tmpdir.join("instance")) assert app.instance_path == str(modules_tmpdir.join("instance"))
@pytest.mark.xfail(reason="weird interaction with tox")
def test_uninstalled_package_paths(modules_tmpdir, purge_module): def test_uninstalled_package_paths(modules_tmpdir, purge_module):
app = modules_tmpdir.mkdir("config_package_app") app = modules_tmpdir.mkdir("config_package_app")
init = app.join("__init__.py") init = app.join("__init__.py")
@ -126,19 +126,3 @@ def test_egg_installed_paths(install_egg, modules_tmpdir, modules_tmpdir_prefix)
finally: finally:
if "site_egg" in sys.modules: if "site_egg" in sys.modules:
del sys.modules["site_egg"] del sys.modules["site_egg"]
@pytest.mark.skipif(not PY2, reason="This only works under Python 2.")
def test_meta_path_loader_without_is_package(request, modules_tmpdir):
app = modules_tmpdir.join("unimportable.py")
app.write("import flask\napp = flask.Flask(__name__)")
class Loader(object):
def find_module(self, name, path=None):
return self
sys.meta_path.append(Loader())
request.addfinalizer(sys.meta_path.pop)
with pytest.raises(AttributeError):
import unimportable # noqa: F401

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
tests.test_json_tag tests.test_json_tag
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
@ -48,7 +47,7 @@ def test_duplicate_tag():
def test_custom_tag(): def test_custom_tag():
class Foo(object): # noqa: B903, for Python2 compatibility class Foo: # noqa: B903, for Python2 compatibility
def __init__(self, data): def __init__(self, data):
self.data = data self.data = data

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
tests.test_logging tests.test_logging
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
@ -8,10 +7,10 @@ tests.test_logging
""" """
import logging import logging
import sys import sys
from io import StringIO
import pytest import pytest
from flask._compat import StringIO
from flask.logging import default_handler from flask.logging import default_handler
from flask.logging import has_level_handler from flask.logging import has_level_handler
from flask.logging import wsgi_errors_stream from flask.logging import wsgi_errors_stream
@ -104,12 +103,3 @@ def test_log_view_exception(app, client):
err = stream.getvalue() err = stream.getvalue()
assert "Exception on / [GET]" in err assert "Exception on / [GET]" in err
assert "Exception: test" in err assert "Exception: test" in err
def test_warn_old_config(app, request):
old_logger = logging.getLogger("flask.app")
old_logger.setLevel(logging.DEBUG)
request.addfinalizer(lambda: old_logger.setLevel(logging.NOTSET))
with pytest.warns(UserWarning):
assert app.logger.getEffectiveLevel() == logging.WARNING

View file

@ -1,6 +0,0 @@
import io
def test_changelog_utf8_compatible():
with io.open("CHANGES.rst", encoding="UTF-8") as f:
f.read()

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
tests.regression tests.regression
~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -9,7 +8,7 @@
:license: BSD-3-Clause :license: BSD-3-Clause
""" """
import gc import gc
import sys import platform
import threading import threading
import pytest import pytest
@ -20,7 +19,7 @@ import flask
_gc_lock = threading.Lock() _gc_lock = threading.Lock()
class assert_no_leak(object): class assert_no_leak:
def __enter__(self): def __enter__(self):
gc.disable() gc.disable()
_gc_lock.acquire() _gc_lock.acquire()
@ -44,6 +43,7 @@ class assert_no_leak(object):
gc.enable() gc.enable()
@pytest.mark.skipif(platform.python_implementation() == "PyPy", reason="CPython only")
def test_memory_consumption(): def test_memory_consumption():
app = flask.Flask(__name__) app = flask.Flask(__name__)
@ -60,11 +60,9 @@ def test_memory_consumption():
# Trigger caches # Trigger caches
fire() fire()
# This test only works on CPython 2.7. with assert_no_leak():
if sys.version_info >= (2, 7) and not hasattr(sys, "pypy_translation_info"): for _x in range(10):
with assert_no_leak(): fire()
for _x in range(10):
fire()
def test_safe_join_toplevel_pardir(): def test_safe_join_toplevel_pardir():

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
tests.reqctx tests.reqctx
~~~~~~~~~~~~ ~~~~~~~~~~~~
@ -111,7 +110,7 @@ def test_proper_test_request_context(app):
def test_context_binding(app): def test_context_binding(app):
@app.route("/") @app.route("/")
def index(): def index():
return "Hello %s!" % flask.request.args["name"] return f"Hello {flask.request.args['name']}!"
@app.route("/meh") @app.route("/meh")
def meh(): def meh():
@ -139,7 +138,7 @@ def test_context_test(app):
def test_manual_context_binding(app): def test_manual_context_binding(app):
@app.route("/") @app.route("/")
def index(): def index():
return "Hello %s!" % flask.request.args["name"] return f"Hello {flask.request.args['name']}!"
ctx = app.test_request_context("/?name=World") ctx = app.test_request_context("/?name=World")
ctx.push() ctx.push()
@ -150,7 +149,7 @@ def test_manual_context_binding(app):
@pytest.mark.skipif(greenlet is None, reason="greenlet not installed") @pytest.mark.skipif(greenlet is None, reason="greenlet not installed")
class TestGreenletContextCopying(object): class TestGreenletContextCopying:
def test_greenlet_context_copying(self, app, client): def test_greenlet_context_copying(self, app, client):
greenlets = [] greenlets = []
@ -239,7 +238,7 @@ def test_session_dynamic_cookie_name():
if flask.request.url.endswith("dynamic_cookie"): if flask.request.url.endswith("dynamic_cookie"):
return "dynamic_cookie_name" return "dynamic_cookie_name"
else: else:
return super(PathAwareSessionInterface, self).get_cookie_name(app) return super().get_cookie_name(app)
class CustomFlask(flask.Flask): class CustomFlask(flask.Flask):
session_interface = PathAwareSessionInterface() session_interface = PathAwareSessionInterface()
@ -285,17 +284,13 @@ def test_session_dynamic_cookie_name():
def test_bad_environ_raises_bad_request(): def test_bad_environ_raises_bad_request():
app = flask.Flask(__name__) app = flask.Flask(__name__)
# We cannot use app.test_client() for the Unicode-rich Host header,
# because werkzeug enforces latin1 on Python 2.
# However it works when actually passed to the server.
from flask.testing import EnvironBuilder from flask.testing import EnvironBuilder
builder = EnvironBuilder(app) builder = EnvironBuilder(app)
environ = builder.get_environ() environ = builder.get_environ()
# use a non-printable character in the Host - this is key to this test # use a non-printable character in the Host - this is key to this test
environ["HTTP_HOST"] = u"\x8a" environ["HTTP_HOST"] = "\x8a"
with app.request_context(environ): with app.request_context(environ):
response = app.full_dispatch_request() response = app.full_dispatch_request()
@ -309,17 +304,13 @@ def test_environ_for_valid_idna_completes():
def index(): def index():
return "Hello World!" return "Hello World!"
# We cannot use app.test_client() for the Unicode-rich Host header,
# because werkzeug enforces latin1 on Python 2.
# However it works when actually passed to the server.
from flask.testing import EnvironBuilder from flask.testing import EnvironBuilder
builder = EnvironBuilder(app) builder = EnvironBuilder(app)
environ = builder.get_environ() environ = builder.get_environ()
# these characters are all IDNA-compatible # these characters are all IDNA-compatible
environ["HTTP_HOST"] = u"ąśźäüжŠßя.com" environ["HTTP_HOST"] = "ąśźäüжŠßя.com"
with app.request_context(environ): with app.request_context(environ):
response = app.full_dispatch_request() response = app.full_dispatch_request()

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
tests.signals tests.signals
~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
tests.subclassing tests.subclassing
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
@ -9,8 +8,9 @@
:copyright: 2010 Pallets :copyright: 2010 Pallets
:license: BSD-3-Clause :license: BSD-3-Clause
""" """
from io import StringIO
import flask import flask
from flask._compat import StringIO
def test_suppressed_exception_logging(): def test_suppressed_exception_logging():

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
tests.templating tests.templating
~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~
@ -415,16 +414,16 @@ def test_template_loader_debugging(test_apps, monkeypatch):
def handle(self, record): def handle(self, record):
called.append(True) called.append(True)
text = str(record.msg) text = str(record.msg)
assert '1: trying loader of application "blueprintapp"' in text assert "1: trying loader of application 'blueprintapp'" in text
assert ( assert (
'2: trying loader of blueprint "admin" ' "(blueprintapp.apps.admin)" "2: trying loader of blueprint 'admin' (blueprintapp.apps.admin)"
) in text ) in text
assert ( assert (
'trying loader of blueprint "frontend" ' "(blueprintapp.apps.frontend)" "trying loader of blueprint 'frontend' (blueprintapp.apps.frontend)"
) in text ) in text
assert "Error: the template could not be found" in text assert "Error: the template could not be found" in text
assert ( assert (
"looked up from an endpoint that belongs to " 'the blueprint "frontend"' "looked up from an endpoint that belongs to the blueprint 'frontend'"
) in text ) in text
assert "See https://flask.palletsprojects.com/blueprints/#templates" in text assert "See https://flask.palletsprojects.com/blueprints/#templates" in text

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
tests.testing tests.testing
~~~~~~~~~~~~~ ~~~~~~~~~~~~~
@ -14,12 +13,10 @@ import werkzeug
import flask import flask
from flask import appcontext_popped from flask import appcontext_popped
from flask._compat import text_type
from flask.cli import ScriptInfo from flask.cli import ScriptInfo
from flask.json import jsonify from flask.json import jsonify
from flask.testing import EnvironBuilder from flask.testing import EnvironBuilder
from flask.testing import FlaskCliRunner from flask.testing import FlaskCliRunner
from flask.testing import make_test_environ_builder
try: try:
import blinker import blinker
@ -62,7 +59,7 @@ def test_environ_base_default(app, client, app_ctx):
rv = client.get("/") rv = client.get("/")
assert rv.data == b"127.0.0.1" assert rv.data == b"127.0.0.1"
assert flask.g.user_agent == "werkzeug/" + werkzeug.__version__ assert flask.g.user_agent == f"werkzeug/{werkzeug.__version__}"
def test_environ_base_modified(app, client, app_ctx): def test_environ_base_modified(app, client, app_ctx):
@ -121,20 +118,11 @@ def test_path_is_url(app):
assert eb.path == "/" assert eb.path == "/"
def test_make_test_environ_builder(app):
with pytest.deprecated_call():
eb = make_test_environ_builder(app, "https://example.com/")
assert eb.url_scheme == "https"
assert eb.host == "example.com"
assert eb.script_root == ""
assert eb.path == "/"
def test_environbuilder_json_dumps(app): def test_environbuilder_json_dumps(app):
"""EnvironBuilder.json_dumps() takes settings from the app.""" """EnvironBuilder.json_dumps() takes settings from the app."""
app.config["JSON_AS_ASCII"] = False app.config["JSON_AS_ASCII"] = False
eb = EnvironBuilder(app, json=u"\u20ac") eb = EnvironBuilder(app, json="\u20ac")
assert eb.input_stream.read().decode("utf8") == u'"\u20ac"' assert eb.input_stream.read().decode("utf8") == '"\u20ac"'
def test_blueprint_with_subdomain(): def test_blueprint_with_subdomain():
@ -180,12 +168,10 @@ def test_redirect_keep_session(app, client, app_ctx):
rv = client.get("/") rv = client.get("/")
assert rv.data == b"index" assert rv.data == b"index"
assert flask.session.get("data") == "foo" assert flask.session.get("data") == "foo"
rv = client.post("/", data={}, follow_redirects=True) rv = client.post("/", data={}, follow_redirects=True)
assert rv.data == b"foo" assert rv.data == b"foo"
assert flask.session.get("data") == "foo"
# This support requires a new Werkzeug version
if not hasattr(client, "redirect_client"):
assert flask.session.get("data") == "foo"
rv = client.get("/getsession") rv = client.get("/getsession")
assert rv.data == b"foo" assert rv.data == b"foo"
@ -194,7 +180,7 @@ def test_redirect_keep_session(app, client, app_ctx):
def test_session_transactions(app, client): def test_session_transactions(app, client):
@app.route("/") @app.route("/")
def index(): def index():
return text_type(flask.session["foo"]) return str(flask.session["foo"])
with client: with client:
with client.session_transaction() as sess: with client.session_transaction() as sess:
@ -335,9 +321,9 @@ def test_json_request_and_response(app, client):
def test_client_json_no_app_context(app, client): def test_client_json_no_app_context(app, client):
@app.route("/hello", methods=["POST"]) @app.route("/hello", methods=["POST"])
def hello(): def hello():
return "Hello, {}!".format(flask.request.json["name"]) return f"Hello, {flask.request.json['name']}!"
class Namespace(object): class Namespace:
count = 0 count = 0
def add(self, app): def add(self, app):
@ -415,7 +401,7 @@ def test_cli_invoke(app):
def test_cli_custom_obj(app): def test_cli_custom_obj(app):
class NS(object): class NS:
called = False called = False
def create_app(): def create_app():

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
tests.test_user_error_handler tests.test_user_error_handler
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -30,7 +29,7 @@ def test_error_handler_no_match(app, client):
original = getattr(e, "original_exception", None) original = getattr(e, "original_exception", None)
if original is not None: if original is not None:
return "wrapped " + type(original).__name__ return f"wrapped {type(original).__name__}"
return "direct" return "direct"
@ -208,7 +207,7 @@ def test_default_error_handler():
assert c.get("/slash", follow_redirects=True).data == b"slash" assert c.get("/slash", follow_redirects=True).data == b"slash"
class TestGenericHandlers(object): class TestGenericHandlers:
"""Test how very generic handlers are dispatched to.""" """Test how very generic handlers are dispatched to."""
class Custom(Exception): class Custom(Exception):
@ -239,9 +238,9 @@ class TestGenericHandlers(object):
original = getattr(e, "original_exception", None) original = getattr(e, "original_exception", None)
if original is not None: if original is not None:
return "wrapped " + type(original).__name__ return f"wrapped {type(original).__name__}"
return "direct " + type(e).__name__ return f"direct {type(e).__name__}"
@pytest.mark.parametrize("to_handle", (InternalServerError, 500)) @pytest.mark.parametrize("to_handle", (InternalServerError, 500))
def test_handle_class_or_code(self, app, client, to_handle): def test_handle_class_or_code(self, app, client, to_handle):

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
tests.views tests.views
~~~~~~~~~~~ ~~~~~~~~~~~

35
tox.ini
View file

@ -1,16 +1,14 @@
[tox] [tox]
envlist = envlist =
py{38,37,36,35,27,py3,py} py{38,37,36,py3}
py38-{simplejson,devel,lowest} py38-{simplejson,devel,lowest}
style
docs docs
coverage
skip_missing_interpreters = true skip_missing_interpreters = true
[testenv] [testenv]
passenv = LANG
deps = deps =
pytest pytest
coverage
greenlet greenlet
blinker blinker
python-dotenv python-dotenv
@ -29,18 +27,10 @@ deps =
simplejson: simplejson simplejson: simplejson
commands = commands =
# the examples need to be installed to test successfully
pip install -q -e examples/tutorial[test] pip install -q -e examples/tutorial[test]
pip install -q -e examples/javascript[test] pip install -q -e examples/javascript[test]
# pytest-cov doesn't seem to play nice with -p pytest --tb=short --basetemp={envtmpdir} {posargs:tests examples}
coverage run -p -m pytest --tb=short -Werror {posargs:tests examples}
[testenv:nightly]
# courtesy Python nightly test, don't fail the build in CI
ignore_outcome = true
commands =
coverage run -p -m pytest --tb=short -Werror --junitxml=test-results.xml tests
[testenv:style] [testenv:style]
deps = pre-commit deps = pre-commit
@ -48,22 +38,5 @@ skip_install = true
commands = pre-commit run --all-files --show-diff-on-failure commands = pre-commit run --all-files --show-diff-on-failure
[testenv:docs] [testenv:docs]
deps = deps = -r docs/requirements.txt
-r docs/requirements.txt
commands = sphinx-build -W -b html -d {envtmpdir}/doctrees docs {envtmpdir}/html commands = sphinx-build -W -b html -d {envtmpdir}/doctrees docs {envtmpdir}/html
[testenv:coverage]
deps = coverage
skip_install = true
commands =
coverage combine
coverage html
coverage report
[testenv:coverage-ci]
deps = coverage
skip_install = true
commands =
coverage combine
coverage xml
coverage report