@@ -166,5 +170,12 @@ var quill = new Quill('#quill-care-guide', { theme: 'snow' });
document.getElementById('add-plant-form').onsubmit = function() {
document.getElementById('care_guide').value = quill.root.innerHTML;
};
+// Auto-open the add plant form if ?add=1 is in the URL
+if (window.location.search.includes('add=1')) {
+ document.getElementById('add-plant-form-card').classList.remove('hidden');
+ setTimeout(() => {
+ document.getElementById('add-plant-form-card').scrollIntoView({behavior: 'smooth'});
+ }, 100);
+}
{% endblock %}
\ No newline at end of file
diff --git a/venv/Lib/site-packages/dotenv/__pycache__/__init__.cpython-310.pyc b/venv/Lib/site-packages/dotenv/__pycache__/__init__.cpython-310.pyc
index 00bc84a..2a50231 100644
Binary files a/venv/Lib/site-packages/dotenv/__pycache__/__init__.cpython-310.pyc and b/venv/Lib/site-packages/dotenv/__pycache__/__init__.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/dotenv/__pycache__/__main__.cpython-310.pyc b/venv/Lib/site-packages/dotenv/__pycache__/__main__.cpython-310.pyc
index 2753ab2..698a187 100644
Binary files a/venv/Lib/site-packages/dotenv/__pycache__/__main__.cpython-310.pyc and b/venv/Lib/site-packages/dotenv/__pycache__/__main__.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/dotenv/__pycache__/cli.cpython-310.pyc b/venv/Lib/site-packages/dotenv/__pycache__/cli.cpython-310.pyc
index 5f35f8f..5fe24e3 100644
Binary files a/venv/Lib/site-packages/dotenv/__pycache__/cli.cpython-310.pyc and b/venv/Lib/site-packages/dotenv/__pycache__/cli.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/dotenv/__pycache__/ipython.cpython-310.pyc b/venv/Lib/site-packages/dotenv/__pycache__/ipython.cpython-310.pyc
index 098947e..27dfee3 100644
Binary files a/venv/Lib/site-packages/dotenv/__pycache__/ipython.cpython-310.pyc and b/venv/Lib/site-packages/dotenv/__pycache__/ipython.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/dotenv/__pycache__/main.cpython-310.pyc b/venv/Lib/site-packages/dotenv/__pycache__/main.cpython-310.pyc
index 8521fb1..80baef1 100644
Binary files a/venv/Lib/site-packages/dotenv/__pycache__/main.cpython-310.pyc and b/venv/Lib/site-packages/dotenv/__pycache__/main.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/dotenv/__pycache__/parser.cpython-310.pyc b/venv/Lib/site-packages/dotenv/__pycache__/parser.cpython-310.pyc
index 6a8cbff..124cb25 100644
Binary files a/venv/Lib/site-packages/dotenv/__pycache__/parser.cpython-310.pyc and b/venv/Lib/site-packages/dotenv/__pycache__/parser.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/dotenv/__pycache__/variables.cpython-310.pyc b/venv/Lib/site-packages/dotenv/__pycache__/variables.cpython-310.pyc
index daae918..4117168 100644
Binary files a/venv/Lib/site-packages/dotenv/__pycache__/variables.cpython-310.pyc and b/venv/Lib/site-packages/dotenv/__pycache__/variables.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/dotenv/__pycache__/version.cpython-310.pyc b/venv/Lib/site-packages/dotenv/__pycache__/version.cpython-310.pyc
index 3f8675e..5c4ed08 100644
Binary files a/venv/Lib/site-packages/dotenv/__pycache__/version.cpython-310.pyc and b/venv/Lib/site-packages/dotenv/__pycache__/version.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/dotenv/main.py b/venv/Lib/site-packages/dotenv/main.py
index f40c20e..7bc5428 100644
--- a/venv/Lib/site-packages/dotenv/main.py
+++ b/venv/Lib/site-packages/dotenv/main.py
@@ -1,6 +1,7 @@
import io
import logging
import os
+import pathlib
import shutil
import sys
import tempfile
@@ -131,17 +132,21 @@ def rewrite(
path: StrPath,
encoding: Optional[str],
) -> Iterator[Tuple[IO[str], IO[str]]]:
- if not os.path.isfile(path):
- with open(path, mode="w", encoding=encoding) as source:
- source.write("")
+ pathlib.Path(path).touch()
+
with tempfile.NamedTemporaryFile(mode="w", encoding=encoding, delete=False) as dest:
+ error = None
try:
with open(path, encoding=encoding) as source:
yield (source, dest)
- except BaseException:
- os.unlink(dest.name)
- raise
- shutil.move(dest.name, path)
+ except BaseException as err:
+ error = err
+
+ if error is None:
+ shutil.move(dest.name, path)
+ else:
+ os.unlink(dest.name)
+ raise error from None
def set_key(
@@ -280,7 +285,10 @@ def find_dotenv(
def _is_interactive():
""" Decide whether this is running in a REPL or IPython notebook """
- main = __import__('__main__', None, None, fromlist=['__file__'])
+ try:
+ main = __import__('__main__', None, None, fromlist=['__file__'])
+ except ModuleNotFoundError:
+ return False
return not hasattr(main, '__file__')
if usecwd or _is_interactive() or getattr(sys, 'frozen', False):
@@ -291,7 +299,9 @@ def find_dotenv(
frame = sys._getframe()
current_file = __file__
- while frame.f_code.co_filename == current_file:
+ while frame.f_code.co_filename == current_file or not os.path.exists(
+ frame.f_code.co_filename
+ ):
assert frame.f_back is not None
frame = frame.f_back
frame_filename = frame.f_code.co_filename
diff --git a/venv/Lib/site-packages/dotenv/version.py b/venv/Lib/site-packages/dotenv/version.py
index 5becc17..5c4105c 100644
--- a/venv/Lib/site-packages/dotenv/version.py
+++ b/venv/Lib/site-packages/dotenv/version.py
@@ -1 +1 @@
-__version__ = "1.0.0"
+__version__ = "1.0.1"
diff --git a/venv/Lib/site-packages/flask-2.3.3.dist-info/INSTALLER b/venv/Lib/site-packages/flask-2.3.3.dist-info/INSTALLER
deleted file mode 100644
index a1b589e..0000000
--- a/venv/Lib/site-packages/flask-2.3.3.dist-info/INSTALLER
+++ /dev/null
@@ -1 +0,0 @@
-pip
diff --git a/venv/Lib/site-packages/flask-2.3.3.dist-info/LICENSE.rst b/venv/Lib/site-packages/flask-2.3.3.dist-info/LICENSE.rst
deleted file mode 100644
index 9d227a0..0000000
--- a/venv/Lib/site-packages/flask-2.3.3.dist-info/LICENSE.rst
+++ /dev/null
@@ -1,28 +0,0 @@
-Copyright 2010 Pallets
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
-1. Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
-
-2. Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
-
-3. Neither the name of the copyright holder nor the names of its
- contributors may be used to endorse or promote products derived from
- this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
-PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
-TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
-PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/venv/Lib/site-packages/flask-2.3.3.dist-info/METADATA b/venv/Lib/site-packages/flask-2.3.3.dist-info/METADATA
deleted file mode 100644
index d7c3145..0000000
--- a/venv/Lib/site-packages/flask-2.3.3.dist-info/METADATA
+++ /dev/null
@@ -1,116 +0,0 @@
-Metadata-Version: 2.1
-Name: Flask
-Version: 2.3.3
-Summary: A simple framework for building complex web applications.
-Maintainer-email: Pallets
-Requires-Python: >=3.8
-Description-Content-Type: text/x-rst
-Classifier: Development Status :: 5 - Production/Stable
-Classifier: Environment :: Web Environment
-Classifier: Framework :: Flask
-Classifier: Intended Audience :: Developers
-Classifier: License :: OSI Approved :: BSD License
-Classifier: Operating System :: OS Independent
-Classifier: Programming Language :: Python
-Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
-Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
-Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
-Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
-Requires-Dist: Werkzeug>=2.3.7
-Requires-Dist: Jinja2>=3.1.2
-Requires-Dist: itsdangerous>=2.1.2
-Requires-Dist: click>=8.1.3
-Requires-Dist: blinker>=1.6.2
-Requires-Dist: importlib-metadata>=3.6.0; python_version < '3.10'
-Requires-Dist: asgiref>=3.2 ; extra == "async"
-Requires-Dist: python-dotenv ; extra == "dotenv"
-Project-URL: Changes, https://flask.palletsprojects.com/changes/
-Project-URL: Chat, https://discord.gg/pallets
-Project-URL: Documentation, https://flask.palletsprojects.com/
-Project-URL: Donate, https://palletsprojects.com/donate
-Project-URL: Issue Tracker, https://github.com/pallets/flask/issues/
-Project-URL: Source Code, https://github.com/pallets/flask/
-Provides-Extra: async
-Provides-Extra: dotenv
-
-Flask
-=====
-
-Flask is a lightweight `WSGI`_ web application framework. It is designed
-to make getting started quick and easy, with the ability to scale up to
-complex applications. It began as a simple wrapper around `Werkzeug`_
-and `Jinja`_ and has become one of the most popular Python web
-application frameworks.
-
-Flask offers suggestions, but doesn't enforce any dependencies or
-project layout. It is up to the developer to choose the tools and
-libraries they want to use. There are many extensions provided by the
-community that make adding new functionality easy.
-
-.. _WSGI: https://wsgi.readthedocs.io/
-.. _Werkzeug: https://werkzeug.palletsprojects.com/
-.. _Jinja: https://jinja.palletsprojects.com/
-
-
-Installing
-----------
-
-Install and update using `pip`_:
-
-.. code-block:: text
-
- $ pip install -U Flask
-
-.. _pip: https://pip.pypa.io/en/stable/getting-started/
-
-
-A Simple Example
-----------------
-
-.. code-block:: python
-
- # save this as app.py
- from flask import Flask
-
- app = Flask(__name__)
-
- @app.route("/")
- def hello():
- return "Hello, World!"
-
-.. code-block:: text
-
- $ flask run
- * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
-
-
-Contributing
-------------
-
-For guidance on setting up a development environment and how to make a
-contribution to Flask, see the `contributing guidelines`_.
-
-.. _contributing guidelines: https://github.com/pallets/flask/blob/main/CONTRIBUTING.rst
-
-
-Donate
-------
-
-The Pallets organization develops and supports Flask and the libraries
-it uses. In order to grow the community of contributors and users, and
-allow the maintainers to devote more time to the projects, `please
-donate today`_.
-
-.. _please donate today: https://palletsprojects.com/donate
-
-
-Links
------
-
-- Documentation: https://flask.palletsprojects.com/
-- Changes: https://flask.palletsprojects.com/changes/
-- PyPI Releases: https://pypi.org/project/Flask/
-- Source Code: https://github.com/pallets/flask/
-- Issue Tracker: https://github.com/pallets/flask/issues/
-- Chat: https://discord.gg/pallets
-
diff --git a/venv/Lib/site-packages/flask-2.3.3.dist-info/RECORD b/venv/Lib/site-packages/flask-2.3.3.dist-info/RECORD
deleted file mode 100644
index 1c9ad9a..0000000
--- a/venv/Lib/site-packages/flask-2.3.3.dist-info/RECORD
+++ /dev/null
@@ -1,53 +0,0 @@
-../../Scripts/flask.exe,sha256=nDhArXCv__edrUjuqzazd1Exlw2mkUQN3KYEzVfMJzs,108403
-flask-2.3.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
-flask-2.3.3.dist-info/LICENSE.rst,sha256=SJqOEQhQntmKN7uYPhHg9-HTHwvY-Zp5yESOf_N9B-o,1475
-flask-2.3.3.dist-info/METADATA,sha256=-BtXVsnPe7lNA3mcFZHJfsVIiVin1A8LUstChm8qiHo,3588
-flask-2.3.3.dist-info/RECORD,,
-flask-2.3.3.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-flask-2.3.3.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
-flask-2.3.3.dist-info/entry_points.txt,sha256=bBP7hTOS5fz9zLtC7sPofBZAlMkEvBxu7KqS6l5lvc4,40
-flask/__init__.py,sha256=xq09XNKP-Y-fdv6BeGH7RlFaY006tUA3o_llGcl-dno,3731
-flask/__main__.py,sha256=bYt9eEaoRQWdejEHFD8REx9jxVEdZptECFsV7F49Ink,30
-flask/__pycache__/__init__.cpython-310.pyc,,
-flask/__pycache__/__main__.cpython-310.pyc,,
-flask/__pycache__/app.cpython-310.pyc,,
-flask/__pycache__/blueprints.cpython-310.pyc,,
-flask/__pycache__/cli.cpython-310.pyc,,
-flask/__pycache__/config.cpython-310.pyc,,
-flask/__pycache__/ctx.cpython-310.pyc,,
-flask/__pycache__/debughelpers.cpython-310.pyc,,
-flask/__pycache__/globals.cpython-310.pyc,,
-flask/__pycache__/helpers.cpython-310.pyc,,
-flask/__pycache__/logging.cpython-310.pyc,,
-flask/__pycache__/scaffold.cpython-310.pyc,,
-flask/__pycache__/sessions.cpython-310.pyc,,
-flask/__pycache__/signals.cpython-310.pyc,,
-flask/__pycache__/templating.cpython-310.pyc,,
-flask/__pycache__/testing.cpython-310.pyc,,
-flask/__pycache__/typing.cpython-310.pyc,,
-flask/__pycache__/views.cpython-310.pyc,,
-flask/__pycache__/wrappers.cpython-310.pyc,,
-flask/app.py,sha256=ht3Qx9U9z0I1qUfLoS7bYhJcubdpk-i54eHq37LDlN8,87620
-flask/blueprints.py,sha256=ZpVrwa8UY-YnVDsX_1K10XQjDwCUp7Qn2hmKln5icEQ,24332
-flask/cli.py,sha256=PDwZCfPagi5GUzb-D6dEN7y20gWiVAg3ejRnxBKNHPA,33821
-flask/config.py,sha256=YZSZ-xpFj1iW1B1Kj1iDhpc5s7pHncloiRLqXhsU7Hs,12856
-flask/ctx.py,sha256=x2kGzUXtPzVyi2YSKrU_PV1AvtxTmh2iRdriJRTSPGM,14841
-flask/debughelpers.py,sha256=BR0xkd-sAyFuFW07D6NfrqNwSZxk1IrkG5n8zem-3sw,5547
-flask/globals.py,sha256=KUzVvSPh8v28kUasVDi_aQKB9hI2jZSYQHqaDU2P414,2945
-flask/helpers.py,sha256=uVhMwhhfwgjBt8b--zIZTjkfBRK28yPpmNhgVzhP444,25106
-flask/json/__init__.py,sha256=pdtpoK2b0b1u7Sxbx3feM7VWhsI20l1yGAvbYWxaxvc,5572
-flask/json/__pycache__/__init__.cpython-310.pyc,,
-flask/json/__pycache__/provider.cpython-310.pyc,,
-flask/json/__pycache__/tag.cpython-310.pyc,,
-flask/json/provider.py,sha256=Os0frb8oGfyWKL-TDxb0Uy-MY6gDhPdJkRaUl5xAOXI,7637
-flask/json/tag.py,sha256=ihb7QWrNEr0YC3KD4TolZbftgSPCuLk7FAvK49huYC0,8871
-flask/logging.py,sha256=lArx2Bq9oTtUJ-DnZL9t88xU2zytzp4UWSM9Bd72NDQ,2327
-flask/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-flask/scaffold.py,sha256=ALGHLcy2qSbJ7ENd1H8dOnq5VDgH5XSFsOkDelcOKV8,33217
-flask/sessions.py,sha256=rFH2QKXG24dEazkKGxAHqUpAUh_30hDHrddhVYgAcY0,14169
-flask/signals.py,sha256=s1H4yKjf3c5dgVr41V6sJpE9dLJvmTJMYuK0rkqx3sw,1146
-flask/templating.py,sha256=XdP2hMFnZ5FCZOG7HUaLjC2VC-b4uHSWlDjwv_1p3qc,7503
-flask/testing.py,sha256=h7AinggrMgGzKlDN66VfB0JjWW4Z1U_OD6FyjqBNiYM,10017
-flask/typing.py,sha256=4Lj-YTxUoYvPYofC9GKu-1o0Ht8lyjp9z3I336J13_o,3005
-flask/views.py,sha256=V5hOGZLx0Bn99QGcM6mh5x_uM-MypVT0-RysEFU84jc,6789
-flask/wrappers.py,sha256=PhMp3teK3SnEmIdog59cO_DHiZ9Btn0qI1EifrTdwP8,5709
diff --git a/venv/Lib/site-packages/flask-2.3.3.dist-info/REQUESTED b/venv/Lib/site-packages/flask-2.3.3.dist-info/REQUESTED
deleted file mode 100644
index e69de29..0000000
diff --git a/venv/Lib/site-packages/flask-2.3.3.dist-info/WHEEL b/venv/Lib/site-packages/flask-2.3.3.dist-info/WHEEL
deleted file mode 100644
index 3b5e64b..0000000
--- a/venv/Lib/site-packages/flask-2.3.3.dist-info/WHEEL
+++ /dev/null
@@ -1,4 +0,0 @@
-Wheel-Version: 1.0
-Generator: flit 3.9.0
-Root-Is-Purelib: true
-Tag: py3-none-any
diff --git a/venv/Lib/site-packages/flask-2.3.3.dist-info/entry_points.txt b/venv/Lib/site-packages/flask-2.3.3.dist-info/entry_points.txt
deleted file mode 100644
index eec6733..0000000
--- a/venv/Lib/site-packages/flask-2.3.3.dist-info/entry_points.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-[console_scripts]
-flask=flask.cli:main
-
diff --git a/venv/Lib/site-packages/flask/__init__.py b/venv/Lib/site-packages/flask/__init__.py
index bdca1b0..e86eb43 100644
--- a/venv/Lib/site-packages/flask/__init__.py
+++ b/venv/Lib/site-packages/flask/__init__.py
@@ -1,7 +1,9 @@
+from __future__ import annotations
+
+import typing as t
+
from . import json as json
from .app import Flask as Flask
-from .app import Request as Request
-from .app import Response as Response
from .blueprints import Blueprint as Blueprint
from .config import Config as Config
from .ctx import after_this_request as after_this_request
@@ -37,66 +39,22 @@ from .templating import render_template as render_template
from .templating import render_template_string as render_template_string
from .templating import stream_template as stream_template
from .templating import stream_template_string as stream_template_string
-
-__version__ = "2.3.3"
+from .wrappers import Request as Request
+from .wrappers import Response as Response
-def __getattr__(name):
- if name == "_app_ctx_stack":
- import warnings
- from .globals import __app_ctx_stack
-
- warnings.warn(
- "'_app_ctx_stack' is deprecated and will be removed in Flask 2.4.",
- DeprecationWarning,
- stacklevel=2,
- )
- return __app_ctx_stack
-
- if name == "_request_ctx_stack":
- import warnings
- from .globals import __request_ctx_stack
-
- warnings.warn(
- "'_request_ctx_stack' is deprecated and will be removed in Flask 2.4.",
- DeprecationWarning,
- stacklevel=2,
- )
- return __request_ctx_stack
-
- if name == "escape":
- import warnings
- from markupsafe import escape
-
- warnings.warn(
- "'flask.escape' is deprecated and will be removed in Flask 2.4. Import"
- " 'markupsafe.escape' instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- return escape
-
- if name == "Markup":
- import warnings
- from markupsafe import Markup
-
- warnings.warn(
- "'flask.Markup' is deprecated and will be removed in Flask 2.4. Import"
- " 'markupsafe.Markup' instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- return Markup
-
- if name == "signals_available":
+def __getattr__(name: str) -> t.Any:
+ if name == "__version__":
+ import importlib.metadata
import warnings
warnings.warn(
- "'signals_available' is deprecated and will be removed in Flask 2.4."
- " Signals are always available",
+ "The '__version__' attribute is deprecated and will be removed in"
+ " Flask 3.1. Use feature detection or"
+ " 'importlib.metadata.version(\"flask\")' instead.",
DeprecationWarning,
stacklevel=2,
)
- return True
+ return importlib.metadata.version("flask")
raise AttributeError(name)
diff --git a/venv/Lib/site-packages/flask/__pycache__/__init__.cpython-310.pyc b/venv/Lib/site-packages/flask/__pycache__/__init__.cpython-310.pyc
index 5148109..83dc559 100644
Binary files a/venv/Lib/site-packages/flask/__pycache__/__init__.cpython-310.pyc and b/venv/Lib/site-packages/flask/__pycache__/__init__.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/__main__.cpython-310.pyc b/venv/Lib/site-packages/flask/__pycache__/__main__.cpython-310.pyc
index 148abbb..19f7d08 100644
Binary files a/venv/Lib/site-packages/flask/__pycache__/__main__.cpython-310.pyc and b/venv/Lib/site-packages/flask/__pycache__/__main__.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/app.cpython-310.pyc b/venv/Lib/site-packages/flask/__pycache__/app.cpython-310.pyc
index 3730d59..a3b1e1f 100644
Binary files a/venv/Lib/site-packages/flask/__pycache__/app.cpython-310.pyc and b/venv/Lib/site-packages/flask/__pycache__/app.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/blueprints.cpython-310.pyc b/venv/Lib/site-packages/flask/__pycache__/blueprints.cpython-310.pyc
index 733b1dc..723369e 100644
Binary files a/venv/Lib/site-packages/flask/__pycache__/blueprints.cpython-310.pyc and b/venv/Lib/site-packages/flask/__pycache__/blueprints.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/cli.cpython-310.pyc b/venv/Lib/site-packages/flask/__pycache__/cli.cpython-310.pyc
index 7c9375c..418ac2c 100644
Binary files a/venv/Lib/site-packages/flask/__pycache__/cli.cpython-310.pyc and b/venv/Lib/site-packages/flask/__pycache__/cli.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/config.cpython-310.pyc b/venv/Lib/site-packages/flask/__pycache__/config.cpython-310.pyc
index f89c609..4c81114 100644
Binary files a/venv/Lib/site-packages/flask/__pycache__/config.cpython-310.pyc and b/venv/Lib/site-packages/flask/__pycache__/config.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/ctx.cpython-310.pyc b/venv/Lib/site-packages/flask/__pycache__/ctx.cpython-310.pyc
index 099a428..d1e26b9 100644
Binary files a/venv/Lib/site-packages/flask/__pycache__/ctx.cpython-310.pyc and b/venv/Lib/site-packages/flask/__pycache__/ctx.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/debughelpers.cpython-310.pyc b/venv/Lib/site-packages/flask/__pycache__/debughelpers.cpython-310.pyc
index 79e1428..891d00d 100644
Binary files a/venv/Lib/site-packages/flask/__pycache__/debughelpers.cpython-310.pyc and b/venv/Lib/site-packages/flask/__pycache__/debughelpers.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/globals.cpython-310.pyc b/venv/Lib/site-packages/flask/__pycache__/globals.cpython-310.pyc
index fb2f809..0219a4d 100644
Binary files a/venv/Lib/site-packages/flask/__pycache__/globals.cpython-310.pyc and b/venv/Lib/site-packages/flask/__pycache__/globals.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/helpers.cpython-310.pyc b/venv/Lib/site-packages/flask/__pycache__/helpers.cpython-310.pyc
index 4d32224..41a858d 100644
Binary files a/venv/Lib/site-packages/flask/__pycache__/helpers.cpython-310.pyc and b/venv/Lib/site-packages/flask/__pycache__/helpers.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/logging.cpython-310.pyc b/venv/Lib/site-packages/flask/__pycache__/logging.cpython-310.pyc
index 3d9072c..84db3c6 100644
Binary files a/venv/Lib/site-packages/flask/__pycache__/logging.cpython-310.pyc and b/venv/Lib/site-packages/flask/__pycache__/logging.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/scaffold.cpython-310.pyc b/venv/Lib/site-packages/flask/__pycache__/scaffold.cpython-310.pyc
deleted file mode 100644
index 5f5fd51..0000000
Binary files a/venv/Lib/site-packages/flask/__pycache__/scaffold.cpython-310.pyc and /dev/null differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/sessions.cpython-310.pyc b/venv/Lib/site-packages/flask/__pycache__/sessions.cpython-310.pyc
index e8305d6..54201f6 100644
Binary files a/venv/Lib/site-packages/flask/__pycache__/sessions.cpython-310.pyc and b/venv/Lib/site-packages/flask/__pycache__/sessions.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/signals.cpython-310.pyc b/venv/Lib/site-packages/flask/__pycache__/signals.cpython-310.pyc
index a535e1d..b1f6cb4 100644
Binary files a/venv/Lib/site-packages/flask/__pycache__/signals.cpython-310.pyc and b/venv/Lib/site-packages/flask/__pycache__/signals.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/templating.cpython-310.pyc b/venv/Lib/site-packages/flask/__pycache__/templating.cpython-310.pyc
index d8152aa..a7c3de0 100644
Binary files a/venv/Lib/site-packages/flask/__pycache__/templating.cpython-310.pyc and b/venv/Lib/site-packages/flask/__pycache__/templating.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/testing.cpython-310.pyc b/venv/Lib/site-packages/flask/__pycache__/testing.cpython-310.pyc
index 00686e9..d5c5c42 100644
Binary files a/venv/Lib/site-packages/flask/__pycache__/testing.cpython-310.pyc and b/venv/Lib/site-packages/flask/__pycache__/testing.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/typing.cpython-310.pyc b/venv/Lib/site-packages/flask/__pycache__/typing.cpython-310.pyc
index c8a8748..6b541df 100644
Binary files a/venv/Lib/site-packages/flask/__pycache__/typing.cpython-310.pyc and b/venv/Lib/site-packages/flask/__pycache__/typing.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/views.cpython-310.pyc b/venv/Lib/site-packages/flask/__pycache__/views.cpython-310.pyc
index b1e4f08..aa0ab72 100644
Binary files a/venv/Lib/site-packages/flask/__pycache__/views.cpython-310.pyc and b/venv/Lib/site-packages/flask/__pycache__/views.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/wrappers.cpython-310.pyc b/venv/Lib/site-packages/flask/__pycache__/wrappers.cpython-310.pyc
index 4a27636..da1aee7 100644
Binary files a/venv/Lib/site-packages/flask/__pycache__/wrappers.cpython-310.pyc and b/venv/Lib/site-packages/flask/__pycache__/wrappers.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/flask/app.py b/venv/Lib/site-packages/flask/app.py
index 3b6b38d..12ac50d 100644
--- a/venv/Lib/site-packages/flask/app.py
+++ b/venv/Lib/site-packages/flask/app.py
@@ -1,11 +1,10 @@
from __future__ import annotations
-import logging
+import collections.abc as cabc
import os
import sys
import typing as t
import weakref
-from collections.abc import Iterator as _abc_Iterator
from datetime import timedelta
from inspect import iscoroutinefunction
from itertools import chain
@@ -15,47 +14,34 @@ from urllib.parse import quote as _url_quote
import click
from werkzeug.datastructures import Headers
from werkzeug.datastructures import ImmutableDict
-from werkzeug.exceptions import Aborter
-from werkzeug.exceptions import BadRequest
from werkzeug.exceptions import BadRequestKeyError
from werkzeug.exceptions import HTTPException
from werkzeug.exceptions import InternalServerError
from werkzeug.routing import BuildError
-from werkzeug.routing import Map
from werkzeug.routing import MapAdapter
from werkzeug.routing import RequestRedirect
from werkzeug.routing import RoutingException
from werkzeug.routing import Rule
from werkzeug.serving import is_running_from_reloader
-from werkzeug.utils import cached_property
-from werkzeug.utils import redirect as _wz_redirect
from werkzeug.wrappers import Response as BaseResponse
from . import cli
from . import typing as ft
-from .config import Config
-from .config import ConfigAttribute
-from .ctx import _AppCtxGlobals
from .ctx import AppContext
from .ctx import RequestContext
from .globals import _cv_app
from .globals import _cv_request
+from .globals import current_app
from .globals import g
from .globals import request
from .globals import request_ctx
from .globals import session
-from .helpers import _split_blueprint_path
from .helpers import get_debug_flag
from .helpers import get_flashed_messages
from .helpers import get_load_dotenv
-from .json.provider import DefaultJSONProvider
-from .json.provider import JSONProvider
-from .logging import create_logger
-from .scaffold import _endpoint_from_view_func
-from .scaffold import _sentinel
-from .scaffold import find_package
-from .scaffold import Scaffold
-from .scaffold import setupmethod
+from .helpers import send_from_directory
+from .sansio.app import App
+from .sansio.scaffold import _sentinel
from .sessions import SecureCookieSessionInterface
from .sessions import SessionInterface
from .signals import appcontext_tearing_down
@@ -63,13 +49,14 @@ from .signals import got_request_exception
from .signals import request_finished
from .signals import request_started
from .signals import request_tearing_down
-from .templating import DispatchingJinjaLoader
from .templating import Environment
from .wrappers import Request
from .wrappers import Response
if t.TYPE_CHECKING: # pragma: no cover
- from .blueprints import Blueprint
+ from _typeshed.wsgi import StartResponse
+ from _typeshed.wsgi import WSGIEnvironment
+
from .testing import FlaskClient
from .testing import FlaskCliRunner
@@ -89,7 +76,7 @@ def _make_timedelta(value: timedelta | int | None) -> timedelta | None:
return timedelta(seconds=value)
-class Flask(Scaffold):
+class Flask(App):
"""The flask object implements a WSGI application and acts as the central
object. It is passed the name of the module or package of the
application. Once it is created it will act as a central registry for
@@ -186,111 +173,6 @@ class Flask(Scaffold):
automatically, such as for namespace packages.
"""
- #: The class that is used for request objects. See :class:`~flask.Request`
- #: for more information.
- request_class = Request
-
- #: The class that is used for response objects. See
- #: :class:`~flask.Response` for more information.
- response_class = Response
-
- #: The class of the object assigned to :attr:`aborter`, created by
- #: :meth:`create_aborter`. That object is called by
- #: :func:`flask.abort` to raise HTTP errors, and can be
- #: called directly as well.
- #:
- #: Defaults to :class:`werkzeug.exceptions.Aborter`.
- #:
- #: .. versionadded:: 2.2
- aborter_class = Aborter
-
- #: The class that is used for the Jinja environment.
- #:
- #: .. versionadded:: 0.11
- jinja_environment = Environment
-
- #: The class that is used for the :data:`~flask.g` instance.
- #:
- #: Example use cases for a custom class:
- #:
- #: 1. Store arbitrary attributes on flask.g.
- #: 2. Add a property for lazy per-request database connectors.
- #: 3. Return None instead of AttributeError on unexpected attributes.
- #: 4. Raise exception if an unexpected attr is set, a "controlled" flask.g.
- #:
- #: In Flask 0.9 this property was called `request_globals_class` but it
- #: was changed in 0.10 to :attr:`app_ctx_globals_class` because the
- #: flask.g object is now application context scoped.
- #:
- #: .. versionadded:: 0.10
- app_ctx_globals_class = _AppCtxGlobals
-
- #: The class that is used for the ``config`` attribute of this app.
- #: Defaults to :class:`~flask.Config`.
- #:
- #: Example use cases for a custom class:
- #:
- #: 1. Default values for certain config options.
- #: 2. Access to config values through attributes in addition to keys.
- #:
- #: .. versionadded:: 0.11
- config_class = Config
-
- #: The testing flag. Set this to ``True`` to enable the test mode of
- #: Flask extensions (and in the future probably also Flask itself).
- #: For example this might activate test helpers that have an
- #: additional runtime cost which should not be enabled by default.
- #:
- #: If this is enabled and PROPAGATE_EXCEPTIONS is not changed from the
- #: default it's implicitly enabled.
- #:
- #: This attribute can also be configured from the config with the
- #: ``TESTING`` configuration key. Defaults to ``False``.
- testing = ConfigAttribute("TESTING")
-
- #: If a secret key is set, cryptographic components can use this to
- #: sign cookies and other things. Set this to a complex random value
- #: when you want to use the secure cookie for instance.
- #:
- #: This attribute can also be configured from the config with the
- #: :data:`SECRET_KEY` configuration key. Defaults to ``None``.
- secret_key = ConfigAttribute("SECRET_KEY")
-
- #: A :class:`~datetime.timedelta` which is used to set the expiration
- #: date of a permanent session. The default is 31 days which makes a
- #: permanent session survive for roughly one month.
- #:
- #: This attribute can also be configured from the config with the
- #: ``PERMANENT_SESSION_LIFETIME`` configuration key. Defaults to
- #: ``timedelta(days=31)``
- permanent_session_lifetime = ConfigAttribute(
- "PERMANENT_SESSION_LIFETIME", get_converter=_make_timedelta
- )
-
- json_provider_class: type[JSONProvider] = DefaultJSONProvider
- """A subclass of :class:`~flask.json.provider.JSONProvider`. An
- instance is created and assigned to :attr:`app.json` when creating
- the app.
-
- The default, :class:`~flask.json.provider.DefaultJSONProvider`, uses
- Python's built-in :mod:`json` library. A different provider can use
- a different JSON library.
-
- .. versionadded:: 2.2
- """
-
- #: Options that are passed to the Jinja environment in
- #: :meth:`create_jinja_environment`. Changing these options after
- #: the environment is created (accessing :attr:`jinja_env`) will
- #: have no effect.
- #:
- #: .. versionchanged:: 1.1.0
- #: This is a ``dict`` instead of an ``ImmutableDict`` to allow
- #: easier configuration.
- #:
- jinja_options: dict = {}
-
- #: Default configuration parameters.
default_config = ImmutableDict(
{
"DEBUG": None,
@@ -319,31 +201,13 @@ class Flask(Scaffold):
}
)
- #: The rule object to use for URL rules created. This is used by
- #: :meth:`add_url_rule`. Defaults to :class:`werkzeug.routing.Rule`.
- #:
- #: .. versionadded:: 0.7
- url_rule_class = Rule
+ #: The class that is used for request objects. See :class:`~flask.Request`
+ #: for more information.
+ request_class: type[Request] = Request
- #: The map object to use for storing the URL rules and routing
- #: configuration parameters. Defaults to :class:`werkzeug.routing.Map`.
- #:
- #: .. versionadded:: 1.1.0
- url_map_class = Map
-
- #: The :meth:`test_client` method creates an instance of this test
- #: client class. Defaults to :class:`~flask.testing.FlaskClient`.
- #:
- #: .. versionadded:: 0.7
- test_client_class: type[FlaskClient] | None = None
-
- #: The :class:`~click.testing.CliRunner` subclass, by default
- #: :class:`~flask.testing.FlaskCliRunner` that is used by
- #: :meth:`test_cli_runner`. Its ``__init__`` method should take a
- #: Flask app object as the first argument.
- #:
- #: .. versionadded:: 1.0
- test_cli_runner_class: type[FlaskCliRunner] | None = None
+ #: The class that is used for response objects. See
+ #: :class:`~flask.Response` for more information.
+ response_class: type[Response] = Response
#: the session interface to use. By default an instance of
#: :class:`~flask.sessions.SecureCookieSessionInterface` is used here.
@@ -355,134 +219,28 @@ class Flask(Scaffold):
self,
import_name: str,
static_url_path: str | None = None,
- static_folder: str | os.PathLike | None = "static",
+ static_folder: str | os.PathLike[str] | None = "static",
static_host: str | None = None,
host_matching: bool = False,
subdomain_matching: bool = False,
- template_folder: str | os.PathLike | None = "templates",
+ template_folder: str | os.PathLike[str] | None = "templates",
instance_path: str | None = None,
instance_relative_config: bool = False,
root_path: str | None = None,
):
super().__init__(
import_name=import_name,
- static_folder=static_folder,
static_url_path=static_url_path,
+ static_folder=static_folder,
+ static_host=static_host,
+ host_matching=host_matching,
+ subdomain_matching=subdomain_matching,
template_folder=template_folder,
+ instance_path=instance_path,
+ instance_relative_config=instance_relative_config,
root_path=root_path,
)
- if instance_path is None:
- instance_path = self.auto_find_instance_path()
- elif not os.path.isabs(instance_path):
- raise ValueError(
- "If an instance path is provided it must be absolute."
- " A relative path was given instead."
- )
-
- #: Holds the path to the instance folder.
- #:
- #: .. versionadded:: 0.8
- self.instance_path = instance_path
-
- #: The configuration dictionary as :class:`Config`. This behaves
- #: exactly like a regular dictionary but supports additional methods
- #: to load a config from files.
- self.config = self.make_config(instance_relative_config)
-
- #: An instance of :attr:`aborter_class` created by
- #: :meth:`make_aborter`. This is called by :func:`flask.abort`
- #: to raise HTTP errors, and can be called directly as well.
- #:
- #: .. versionadded:: 2.2
- #: Moved from ``flask.abort``, which calls this object.
- self.aborter = self.make_aborter()
-
- self.json: JSONProvider = self.json_provider_class(self)
- """Provides access to JSON methods. Functions in ``flask.json``
- will call methods on this provider when the application context
- is active. Used for handling JSON requests and responses.
-
- An instance of :attr:`json_provider_class`. Can be customized by
- changing that attribute on a subclass, or by assigning to this
- attribute afterwards.
-
- The default, :class:`~flask.json.provider.DefaultJSONProvider`,
- uses Python's built-in :mod:`json` library. A different provider
- can use a different JSON library.
-
- .. versionadded:: 2.2
- """
-
- #: A list of functions that are called by
- #: :meth:`handle_url_build_error` when :meth:`.url_for` raises a
- #: :exc:`~werkzeug.routing.BuildError`. Each function is called
- #: with ``error``, ``endpoint`` and ``values``. If a function
- #: returns ``None`` or raises a ``BuildError``, it is skipped.
- #: Otherwise, its return value is returned by ``url_for``.
- #:
- #: .. versionadded:: 0.9
- self.url_build_error_handlers: list[
- t.Callable[[Exception, str, dict[str, t.Any]], str]
- ] = []
-
- #: A list of functions that are called when the application context
- #: is destroyed. Since the application context is also torn down
- #: if the request ends this is the place to store code that disconnects
- #: from databases.
- #:
- #: .. versionadded:: 0.9
- self.teardown_appcontext_funcs: list[ft.TeardownCallable] = []
-
- #: A list of shell context processor functions that should be run
- #: when a shell context is created.
- #:
- #: .. versionadded:: 0.11
- self.shell_context_processors: list[ft.ShellContextProcessorCallable] = []
-
- #: Maps registered blueprint names to blueprint objects. The
- #: dict retains the order the blueprints were registered in.
- #: Blueprints can be registered multiple times, this dict does
- #: not track how often they were attached.
- #:
- #: .. versionadded:: 0.7
- self.blueprints: dict[str, Blueprint] = {}
-
- #: a place where extensions can store application specific state. For
- #: example this is where an extension could store database engines and
- #: similar things.
- #:
- #: The key must match the name of the extension module. For example in
- #: case of a "Flask-Foo" extension in `flask_foo`, the key would be
- #: ``'foo'``.
- #:
- #: .. versionadded:: 0.7
- self.extensions: dict = {}
-
- #: The :class:`~werkzeug.routing.Map` for this instance. You can use
- #: this to change the routing converters after the class was created
- #: but before any routes are connected. Example::
- #:
- #: from werkzeug.routing import BaseConverter
- #:
- #: class ListConverter(BaseConverter):
- #: def to_python(self, value):
- #: return value.split(',')
- #: def to_url(self, values):
- #: return ','.join(super(ListConverter, self).to_url(value)
- #: for value in values)
- #:
- #: app = Flask(__name__)
- #: app.url_map.converters['list'] = ListConverter
- self.url_map = self.url_map_class()
-
- self.url_map.host_matching = host_matching
- self.subdomain_matching = subdomain_matching
-
- # tracks internally if the application already handled at least one
- # request.
- self._got_first_request = False
-
# Add a static route using the provided static_url_path, static_host,
# and static_folder if there is a configured static_folder.
# Note we do this without checking if static_folder exists.
@@ -502,135 +260,81 @@ class Flask(Scaffold):
view_func=lambda **kw: self_ref().send_static_file(**kw), # type: ignore # noqa: B950
)
- # Set the name of the Click group in case someone wants to add
- # the app's commands to another CLI tool.
- self.cli.name = self.name
+ def get_send_file_max_age(self, filename: str | None) -> int | None:
+ """Used by :func:`send_file` to determine the ``max_age`` cache
+ value for a given file path if it wasn't passed.
- def _check_setup_finished(self, f_name: str) -> None:
- if self._got_first_request:
- raise AssertionError(
- f"The setup method '{f_name}' can no longer be called"
- " on the application. It has already handled its first"
- " request, any changes will not be applied"
- " consistently.\n"
- "Make sure all imports, decorators, functions, etc."
- " needed to set up the application are done before"
- " running it."
- )
+ By default, this returns :data:`SEND_FILE_MAX_AGE_DEFAULT` from
+ the configuration of :data:`~flask.current_app`. This defaults
+ to ``None``, which tells the browser to use conditional requests
+ instead of a timed cache, which is usually preferable.
- @cached_property
- def name(self) -> str: # type: ignore
- """The name of the application. This is usually the import name
- with the difference that it's guessed from the run file if the
- import name is main. This name is used as a display name when
- Flask needs the name of the application. It can be set and overridden
- to change the value.
+ Note this is a duplicate of the same method in the Flask
+ class.
- .. versionadded:: 0.8
+ .. versionchanged:: 2.0
+ The default configuration is ``None`` instead of 12 hours.
+
+ .. versionadded:: 0.9
"""
- if self.import_name == "__main__":
- fn = getattr(sys.modules["__main__"], "__file__", None)
- if fn is None:
- return "__main__"
- return os.path.splitext(os.path.basename(fn))[0]
- return self.import_name
+ value = current_app.config["SEND_FILE_MAX_AGE_DEFAULT"]
- @cached_property
- def logger(self) -> logging.Logger:
- """A standard Python :class:`~logging.Logger` for the app, with
- the same name as :attr:`name`.
+ if value is None:
+ return None
- In debug mode, the logger's :attr:`~logging.Logger.level` will
- be set to :data:`~logging.DEBUG`.
+ if isinstance(value, timedelta):
+ return int(value.total_seconds())
- If there are no handlers configured, a default handler will be
- added. See :doc:`/logging` for more information.
+ return value # type: ignore[no-any-return]
- .. versionchanged:: 1.1.0
- The logger takes the same name as :attr:`name` rather than
- hard-coding ``"flask.app"``.
+ def send_static_file(self, filename: str) -> Response:
+ """The view function used to serve files from
+ :attr:`static_folder`. A route is automatically registered for
+ this view at :attr:`static_url_path` if :attr:`static_folder` is
+ set.
- .. versionchanged:: 1.0.0
- Behavior was simplified. The logger is always named
- ``"flask.app"``. The level is only set during configuration,
- it doesn't check ``app.debug`` each time. Only one format is
- used, not different ones depending on ``app.debug``. No
- handlers are removed, and a handler is only added if no
- handlers are already configured.
+ Note this is a duplicate of the same method in the Flask
+ class.
+
+ .. versionadded:: 0.5
- .. versionadded:: 0.3
"""
- return create_logger(self)
+ if not self.has_static_folder:
+ raise RuntimeError("'static_folder' must be set to serve static_files.")
- @cached_property
- def jinja_env(self) -> Environment:
- """The Jinja environment used to load templates.
-
- The environment is created the first time this property is
- accessed. Changing :attr:`jinja_options` after that will have no
- effect.
- """
- return self.create_jinja_environment()
-
- @property
- def got_first_request(self) -> bool:
- """This attribute is set to ``True`` if the application started
- handling the first request.
-
- .. deprecated:: 2.3
- Will be removed in Flask 2.4.
-
- .. versionadded:: 0.8
- """
- import warnings
-
- warnings.warn(
- "'got_first_request' is deprecated and will be removed in Flask 2.4.",
- DeprecationWarning,
- stacklevel=2,
+ # send_file only knows to call get_send_file_max_age on the app,
+ # call it here so it works for blueprints too.
+ max_age = self.get_send_file_max_age(filename)
+ return send_from_directory(
+ t.cast(str, self.static_folder), filename, max_age=max_age
)
- return self._got_first_request
- def make_config(self, instance_relative: bool = False) -> Config:
- """Used to create the config attribute by the Flask constructor.
- The `instance_relative` parameter is passed in from the constructor
- of Flask (there named `instance_relative_config`) and indicates if
- the config should be relative to the instance path or the root path
- of the application.
+ def open_resource(self, resource: str, mode: str = "rb") -> t.IO[t.AnyStr]:
+ """Open a resource file relative to :attr:`root_path` for
+ reading.
+
+ For example, if the file ``schema.sql`` is next to the file
+ ``app.py`` where the ``Flask`` app is defined, it can be opened
+ with:
+
+ .. code-block:: python
+
+ with app.open_resource("schema.sql") as f:
+ conn.executescript(f.read())
+
+ :param resource: Path to the resource relative to
+ :attr:`root_path`.
+ :param mode: Open the file in this mode. Only reading is
+ supported, valid values are "r" (or "rt") and "rb".
+
+ Note this is a duplicate of the same method in the Flask
+ class.
- .. versionadded:: 0.8
"""
- root_path = self.root_path
- if instance_relative:
- root_path = self.instance_path
- defaults = dict(self.default_config)
- defaults["DEBUG"] = get_debug_flag()
- return self.config_class(root_path, defaults)
+ if mode not in {"r", "rt", "rb"}:
+ raise ValueError("Resources can only be opened for reading.")
- def make_aborter(self) -> Aborter:
- """Create the object to assign to :attr:`aborter`. That object
- is called by :func:`flask.abort` to raise HTTP errors, and can
- be called directly as well.
-
- By default, this creates an instance of :attr:`aborter_class`,
- which defaults to :class:`werkzeug.exceptions.Aborter`.
-
- .. versionadded:: 2.2
- """
- return self.aborter_class()
-
- def auto_find_instance_path(self) -> str:
- """Tries to locate the instance path if it was not provided to the
- constructor of the application class. It will basically calculate
- the path to a folder named ``instance`` next to your main file or
- the package.
-
- .. versionadded:: 0.8
- """
- prefix, package_path = find_package(self.import_name)
- if prefix is None:
- return os.path.join(package_path, "instance")
- return os.path.join(prefix, "var", f"{self.name}-instance")
+ return open(os.path.join(self.root_path, resource), mode)
def open_instance_resource(self, resource: str, mode: str = "rb") -> t.IO[t.AnyStr]:
"""Opens a resource from the application's instance folder
@@ -684,33 +388,75 @@ class Flask(Scaffold):
rv.policies["json.dumps_function"] = self.json.dumps
return rv
- def create_global_jinja_loader(self) -> DispatchingJinjaLoader:
- """Creates the loader for the Jinja2 environment. Can be used to
- override just the loader and keeping the rest unchanged. It's
- discouraged to override this function. Instead one should override
- the :meth:`jinja_loader` function instead.
+ def create_url_adapter(self, request: Request | None) -> MapAdapter | None:
+ """Creates a URL adapter for the given request. The URL adapter
+ is created at a point where the request context is not yet set
+ up so the request is passed explicitly.
- The global loader dispatches between the loaders of the application
- and the individual blueprints.
+ .. versionadded:: 0.6
- .. versionadded:: 0.7
+ .. versionchanged:: 0.9
+ This can now also be called without a request object when the
+ URL adapter is created for the application context.
+
+ .. versionchanged:: 1.0
+ :data:`SERVER_NAME` no longer implicitly enables subdomain
+ matching. Use :attr:`subdomain_matching` instead.
"""
- return DispatchingJinjaLoader(self)
+ if request is not None:
+ # If subdomain matching is disabled (the default), use the
+ # default subdomain in all cases. This should be the default
+ # in Werkzeug but it currently does not have that feature.
+ if not self.subdomain_matching:
+ subdomain = self.url_map.default_subdomain or None
+ else:
+ subdomain = None
- def select_jinja_autoescape(self, filename: str) -> bool:
- """Returns ``True`` if autoescaping should be active for the given
- template name. If no template name is given, returns `True`.
+ return self.url_map.bind_to_environ(
+ request.environ,
+ server_name=self.config["SERVER_NAME"],
+ subdomain=subdomain,
+ )
+ # We need at the very least the server name to be set for this
+ # to work.
+ if self.config["SERVER_NAME"] is not None:
+ return self.url_map.bind(
+ self.config["SERVER_NAME"],
+ script_name=self.config["APPLICATION_ROOT"],
+ url_scheme=self.config["PREFERRED_URL_SCHEME"],
+ )
- .. versionchanged:: 2.2
- Autoescaping is now enabled by default for ``.svg`` files.
+ return None
- .. versionadded:: 0.5
+ def raise_routing_exception(self, request: Request) -> t.NoReturn:
+ """Intercept routing exceptions and possibly do something else.
+
+ In debug mode, intercept a routing redirect and replace it with
+ an error if the body will be discarded.
+
+ With modern Werkzeug this shouldn't occur, since it now uses a
+ 308 status which tells the browser to resend the method and
+ body.
+
+ .. versionchanged:: 2.1
+ Don't intercept 307 and 308 redirects.
+
+ :meta private:
+ :internal:
"""
- if filename is None:
- return True
- return filename.endswith((".html", ".htm", ".xml", ".xhtml", ".svg"))
+ if (
+ not self.debug
+ or not isinstance(request.routing_exception, RequestRedirect)
+ or request.routing_exception.code in {307, 308}
+ or request.method in {"GET", "HEAD", "OPTIONS"}
+ ):
+ raise request.routing_exception # type: ignore[misc]
- def update_template_context(self, context: dict) -> None:
+ from .debughelpers import FormDataRoutingRedirect
+
+ raise FormDataRoutingRedirect(request)
+
+ def update_template_context(self, context: dict[str, t.Any]) -> None:
"""Update the template context with some commonly used variables.
This injects request, session, config and g into the template
context as well as everything template context processors want
@@ -734,11 +480,11 @@ class Flask(Scaffold):
for name in names:
if name in self.template_context_processors:
for func in self.template_context_processors[name]:
- context.update(func())
+ context.update(self.ensure_sync(func)())
context.update(orig_ctx)
- def make_shell_context(self) -> dict:
+ def make_shell_context(self) -> dict[str, t.Any]:
"""Returns the shell context for an interactive shell for this
application. This runs all the registered shell context
processors.
@@ -750,26 +496,6 @@ class Flask(Scaffold):
rv.update(processor())
return rv
- @property
- def debug(self) -> bool:
- """Whether debug mode is enabled. When using ``flask run`` to start the
- development server, an interactive debugger will be shown for unhandled
- exceptions, and the server will be reloaded when code changes. This maps to the
- :data:`DEBUG` config key. It may not behave as expected if set late.
-
- **Do not enable debug mode when deploying in production.**
-
- Default: ``False``
- """
- return self.config["DEBUG"]
-
- @debug.setter
- def debug(self, value: bool) -> None:
- self.config["DEBUG"] = value
-
- if self.config["TEMPLATES_AUTO_RELOAD"] is None:
- self.jinja_env.auto_reload = value
-
def run(
self,
host: str | None = None,
@@ -968,283 +694,6 @@ class Flask(Scaffold):
return cls(self, **kwargs) # type: ignore
- @setupmethod
- def register_blueprint(self, blueprint: Blueprint, **options: t.Any) -> None:
- """Register a :class:`~flask.Blueprint` on the application. Keyword
- arguments passed to this method will override the defaults set on the
- blueprint.
-
- Calls the blueprint's :meth:`~flask.Blueprint.register` method after
- recording the blueprint in the application's :attr:`blueprints`.
-
- :param blueprint: The blueprint to register.
- :param url_prefix: Blueprint routes will be prefixed with this.
- :param subdomain: Blueprint routes will match on this subdomain.
- :param url_defaults: Blueprint routes will use these default values for
- view arguments.
- :param options: Additional keyword arguments are passed to
- :class:`~flask.blueprints.BlueprintSetupState`. They can be
- accessed in :meth:`~flask.Blueprint.record` callbacks.
-
- .. versionchanged:: 2.0.1
- The ``name`` option can be used to change the (pre-dotted)
- name the blueprint is registered with. This allows the same
- blueprint to be registered multiple times with unique names
- for ``url_for``.
-
- .. versionadded:: 0.7
- """
- blueprint.register(self, options)
-
- def iter_blueprints(self) -> t.ValuesView[Blueprint]:
- """Iterates over all blueprints by the order they were registered.
-
- .. versionadded:: 0.11
- """
- return self.blueprints.values()
-
- @setupmethod
- def add_url_rule(
- self,
- rule: str,
- endpoint: str | None = None,
- view_func: ft.RouteCallable | None = None,
- provide_automatic_options: bool | None = None,
- **options: t.Any,
- ) -> None:
- if endpoint is None:
- endpoint = _endpoint_from_view_func(view_func) # type: ignore
- options["endpoint"] = endpoint
- methods = options.pop("methods", None)
-
- # if the methods are not given and the view_func object knows its
- # methods we can use that instead. If neither exists, we go with
- # a tuple of only ``GET`` as default.
- if methods is None:
- methods = getattr(view_func, "methods", None) or ("GET",)
- if isinstance(methods, str):
- raise TypeError(
- "Allowed methods must be a list of strings, for"
- ' example: @app.route(..., methods=["POST"])'
- )
- methods = {item.upper() for item in methods}
-
- # Methods that should always be added
- required_methods = set(getattr(view_func, "required_methods", ()))
-
- # starting with Flask 0.8 the view_func object can disable and
- # force-enable the automatic options handling.
- if provide_automatic_options is None:
- provide_automatic_options = getattr(
- view_func, "provide_automatic_options", None
- )
-
- if provide_automatic_options is None:
- if "OPTIONS" not in methods:
- provide_automatic_options = True
- required_methods.add("OPTIONS")
- else:
- provide_automatic_options = False
-
- # Add the required methods now.
- methods |= required_methods
-
- rule = self.url_rule_class(rule, methods=methods, **options)
- rule.provide_automatic_options = provide_automatic_options # type: ignore
-
- self.url_map.add(rule)
- if view_func is not None:
- old_func = self.view_functions.get(endpoint)
- if old_func is not None and old_func != view_func:
- raise AssertionError(
- "View function mapping is overwriting an existing"
- f" endpoint function: {endpoint}"
- )
- self.view_functions[endpoint] = view_func
-
- @setupmethod
- def template_filter(
- self, name: str | None = None
- ) -> t.Callable[[T_template_filter], T_template_filter]:
- """A decorator that is used to register custom template filter.
- You can specify a name for the filter, otherwise the function
- name will be used. Example::
-
- @app.template_filter()
- def reverse(s):
- return s[::-1]
-
- :param name: the optional name of the filter, otherwise the
- function name will be used.
- """
-
- def decorator(f: T_template_filter) -> T_template_filter:
- self.add_template_filter(f, name=name)
- return f
-
- return decorator
-
- @setupmethod
- def add_template_filter(
- self, f: ft.TemplateFilterCallable, name: str | None = None
- ) -> None:
- """Register a custom template filter. Works exactly like the
- :meth:`template_filter` decorator.
-
- :param name: the optional name of the filter, otherwise the
- function name will be used.
- """
- self.jinja_env.filters[name or f.__name__] = f
-
- @setupmethod
- def template_test(
- self, name: str | None = None
- ) -> t.Callable[[T_template_test], T_template_test]:
- """A decorator that is used to register custom template test.
- You can specify a name for the test, otherwise the function
- name will be used. Example::
-
- @app.template_test()
- def is_prime(n):
- if n == 2:
- return True
- for i in range(2, int(math.ceil(math.sqrt(n))) + 1):
- if n % i == 0:
- return False
- return True
-
- .. versionadded:: 0.10
-
- :param name: the optional name of the test, otherwise the
- function name will be used.
- """
-
- def decorator(f: T_template_test) -> T_template_test:
- self.add_template_test(f, name=name)
- return f
-
- return decorator
-
- @setupmethod
- def add_template_test(
- self, f: ft.TemplateTestCallable, name: str | None = None
- ) -> None:
- """Register a custom template test. Works exactly like the
- :meth:`template_test` decorator.
-
- .. versionadded:: 0.10
-
- :param name: the optional name of the test, otherwise the
- function name will be used.
- """
- self.jinja_env.tests[name or f.__name__] = f
-
- @setupmethod
- def template_global(
- self, name: str | None = None
- ) -> t.Callable[[T_template_global], T_template_global]:
- """A decorator that is used to register a custom template global function.
- You can specify a name for the global function, otherwise the function
- name will be used. Example::
-
- @app.template_global()
- def double(n):
- return 2 * n
-
- .. versionadded:: 0.10
-
- :param name: the optional name of the global function, otherwise the
- function name will be used.
- """
-
- def decorator(f: T_template_global) -> T_template_global:
- self.add_template_global(f, name=name)
- return f
-
- return decorator
-
- @setupmethod
- def add_template_global(
- self, f: ft.TemplateGlobalCallable, name: str | None = None
- ) -> None:
- """Register a custom template global function. Works exactly like the
- :meth:`template_global` decorator.
-
- .. versionadded:: 0.10
-
- :param name: the optional name of the global function, otherwise the
- function name will be used.
- """
- self.jinja_env.globals[name or f.__name__] = f
-
- @setupmethod
- def teardown_appcontext(self, f: T_teardown) -> T_teardown:
- """Registers a function to be called when the application
- context is popped. The application context is typically popped
- after the request context for each request, at the end of CLI
- commands, or after a manually pushed context ends.
-
- .. code-block:: python
-
- with app.app_context():
- ...
-
- When the ``with`` block exits (or ``ctx.pop()`` is called), the
- teardown functions are called just before the app context is
- made inactive. Since a request context typically also manages an
- application context it would also be called when you pop a
- request context.
-
- When a teardown function was called because of an unhandled
- exception it will be passed an error object. If an
- :meth:`errorhandler` is registered, it will handle the exception
- and the teardown will not receive it.
-
- Teardown functions must avoid raising exceptions. If they
- execute code that might fail they must surround that code with a
- ``try``/``except`` block and log any errors.
-
- The return values of teardown functions are ignored.
-
- .. versionadded:: 0.9
- """
- self.teardown_appcontext_funcs.append(f)
- return f
-
- @setupmethod
- def shell_context_processor(
- self, f: T_shell_context_processor
- ) -> T_shell_context_processor:
- """Registers a shell context processor function.
-
- .. versionadded:: 0.11
- """
- self.shell_context_processors.append(f)
- return f
-
- def _find_error_handler(self, e: Exception) -> ft.ErrorHandlerCallable | None:
- """Return a registered error handler for an exception in this order:
- blueprint handler for a specific code, app handler for a specific code,
- blueprint handler for an exception class, app handler for an exception
- class, or ``None`` if a suitable handler is not found.
- """
- exc_class, code = self._get_exc_class_and_code(type(e))
- names = (*request.blueprints, None)
-
- for c in (code, None) if code is not None else (None,):
- for name in names:
- handler_map = self.error_handler_spec[name][c]
-
- if not handler_map:
- continue
-
- for cls in exc_class.__mro__:
- handler = handler_map.get(cls)
-
- if handler is not None:
- return handler
- return None
-
def handle_http_exception(
self, e: HTTPException
) -> HTTPException | ft.ResponseReturnValue:
@@ -1275,45 +724,10 @@ class Flask(Scaffold):
if isinstance(e, RoutingException):
return e
- handler = self._find_error_handler(e)
+ handler = self._find_error_handler(e, request.blueprints)
if handler is None:
return e
- return self.ensure_sync(handler)(e)
-
- def trap_http_exception(self, e: Exception) -> bool:
- """Checks if an HTTP exception should be trapped or not. By default
- this will return ``False`` for all exceptions except for a bad request
- key error if ``TRAP_BAD_REQUEST_ERRORS`` is set to ``True``. It
- also returns ``True`` if ``TRAP_HTTP_EXCEPTIONS`` is set to ``True``.
-
- This is called for all HTTP exceptions raised by a view function.
- If it returns ``True`` for any exception the error handler for this
- exception is not called and it shows up as regular exception in the
- traceback. This is helpful for debugging implicitly raised HTTP
- exceptions.
-
- .. versionchanged:: 1.0
- Bad request errors are not trapped by default in debug mode.
-
- .. versionadded:: 0.8
- """
- if self.config["TRAP_HTTP_EXCEPTIONS"]:
- return True
-
- trap_bad_request = self.config["TRAP_BAD_REQUEST_ERRORS"]
-
- # if unset, trap key errors in debug mode
- if (
- trap_bad_request is None
- and self.debug
- and isinstance(e, BadRequestKeyError)
- ):
- return True
-
- if trap_bad_request:
- return isinstance(e, BadRequest)
-
- return False
+ return self.ensure_sync(handler)(e) # type: ignore[no-any-return]
def handle_user_exception(
self, e: Exception
@@ -1340,12 +754,12 @@ class Flask(Scaffold):
if isinstance(e, HTTPException) and not self.trap_http_exception(e):
return self.handle_http_exception(e)
- handler = self._find_error_handler(e)
+ handler = self._find_error_handler(e, request.blueprints)
if handler is None:
raise
- return self.ensure_sync(handler)(e)
+ return self.ensure_sync(handler)(e) # type: ignore[no-any-return]
def handle_exception(self, e: Exception) -> Response:
"""Handle an exception that did not have an error handler
@@ -1393,7 +807,7 @@ class Flask(Scaffold):
self.log_exception(exc_info)
server_error: InternalServerError | ft.ResponseReturnValue
server_error = InternalServerError(original_exception=e)
- handler = self._find_error_handler(server_error)
+ handler = self._find_error_handler(server_error, request.blueprints)
if handler is not None:
server_error = self.ensure_sync(handler)(server_error)
@@ -1415,34 +829,6 @@ class Flask(Scaffold):
f"Exception on {request.path} [{request.method}]", exc_info=exc_info
)
- def raise_routing_exception(self, request: Request) -> t.NoReturn:
- """Intercept routing exceptions and possibly do something else.
-
- In debug mode, intercept a routing redirect and replace it with
- an error if the body will be discarded.
-
- With modern Werkzeug this shouldn't occur, since it now uses a
- 308 status which tells the browser to resend the method and
- body.
-
- .. versionchanged:: 2.1
- Don't intercept 307 and 308 redirects.
-
- :meta private:
- :internal:
- """
- if (
- not self.debug
- or not isinstance(request.routing_exception, RequestRedirect)
- or request.routing_exception.code in {307, 308}
- or request.method in {"GET", "HEAD", "OPTIONS"}
- ):
- raise request.routing_exception # type: ignore
-
- from .debughelpers import FormDataRoutingRedirect
-
- raise FormDataRoutingRedirect(request)
-
def dispatch_request(self) -> ft.ResponseReturnValue:
"""Does the request dispatching. Matches the URL and returns the
return value of the view or error handler. This does not have to
@@ -1466,7 +852,7 @@ class Flask(Scaffold):
return self.make_default_options_response()
# otherwise dispatch to the handler for that endpoint
view_args: dict[str, t.Any] = req.view_args # type: ignore[assignment]
- return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
+ return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args) # type: ignore[no-any-return]
def full_dispatch_request(self) -> Response:
"""Dispatches the request and on top of that performs request
@@ -1530,17 +916,7 @@ class Flask(Scaffold):
rv.allow.update(methods)
return rv
- def should_ignore_error(self, error: BaseException | None) -> bool:
- """This is called to figure out if an error should be ignored
- or not as far as the teardown system is concerned. If this
- function returns ``True`` then the teardown handlers will not be
- passed the error.
-
- .. versionadded:: 0.10
- """
- return False
-
- def ensure_sync(self, func: t.Callable) -> t.Callable:
+ def ensure_sync(self, func: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]:
"""Ensure that the function is synchronous for WSGI workers.
Plain ``def`` functions are returned as-is. ``async def``
functions are wrapped to run and wait for the response.
@@ -1555,7 +931,7 @@ class Flask(Scaffold):
return func
def async_to_sync(
- self, func: t.Callable[..., t.Coroutine]
+ self, func: t.Callable[..., t.Coroutine[t.Any, t.Any, t.Any]]
) -> t.Callable[..., t.Any]:
"""Return a sync function that will run the coroutine function.
@@ -1579,6 +955,7 @@ class Flask(Scaffold):
def url_for(
self,
+ /,
endpoint: str,
*,
_anchor: str | None = None,
@@ -1702,20 +1079,6 @@ class Flask(Scaffold):
return rv
- def redirect(self, location: str, code: int = 302) -> BaseResponse:
- """Create a redirect response object.
-
- This is called by :func:`flask.redirect`, and can be called
- directly as well.
-
- :param location: The URL to redirect to.
- :param code: The status code for the redirect.
-
- .. versionadded:: 2.2
- Moved from ``flask.redirect``, which calls this method.
- """
- return _wz_redirect(location, code=code, Response=self.response_class)
-
def make_response(self, rv: ft.ResponseReturnValue) -> Response:
"""Convert the return value from a view function to an instance of
:attr:`response_class`.
@@ -1806,7 +1169,7 @@ class Flask(Scaffold):
# make sure the body is an instance of the response class
if not isinstance(rv, self.response_class):
- if isinstance(rv, (str, bytes, bytearray)) or isinstance(rv, _abc_Iterator):
+ if isinstance(rv, (str, bytes, bytearray)) or isinstance(rv, cabc.Iterator):
# let the response class set the status and headers instead of
# waiting to do it manually, so that the class can handle any
# special logic
@@ -1823,7 +1186,8 @@ class Flask(Scaffold):
# class to the correct type
try:
rv = self.response_class.force_type(
- rv, request.environ # type: ignore[arg-type]
+ rv, # type: ignore[arg-type]
+ request.environ,
)
except TypeError as e:
raise TypeError(
@@ -1856,101 +1220,6 @@ class Flask(Scaffold):
return rv
- def create_url_adapter(self, request: Request | None) -> MapAdapter | None:
- """Creates a URL adapter for the given request. The URL adapter
- is created at a point where the request context is not yet set
- up so the request is passed explicitly.
-
- .. versionadded:: 0.6
-
- .. versionchanged:: 0.9
- This can now also be called without a request object when the
- URL adapter is created for the application context.
-
- .. versionchanged:: 1.0
- :data:`SERVER_NAME` no longer implicitly enables subdomain
- matching. Use :attr:`subdomain_matching` instead.
- """
- if request is not None:
- # If subdomain matching is disabled (the default), use the
- # default subdomain in all cases. This should be the default
- # in Werkzeug but it currently does not have that feature.
- if not self.subdomain_matching:
- subdomain = self.url_map.default_subdomain or None
- else:
- subdomain = None
-
- return self.url_map.bind_to_environ(
- request.environ,
- server_name=self.config["SERVER_NAME"],
- subdomain=subdomain,
- )
- # We need at the very least the server name to be set for this
- # to work.
- if self.config["SERVER_NAME"] is not None:
- return self.url_map.bind(
- self.config["SERVER_NAME"],
- script_name=self.config["APPLICATION_ROOT"],
- url_scheme=self.config["PREFERRED_URL_SCHEME"],
- )
-
- return None
-
- def inject_url_defaults(self, endpoint: str, values: dict) -> None:
- """Injects the URL defaults for the given endpoint directly into
- the values dictionary passed. This is used internally and
- automatically called on URL building.
-
- .. versionadded:: 0.7
- """
- names: t.Iterable[str | None] = (None,)
-
- # url_for may be called outside a request context, parse the
- # passed endpoint instead of using request.blueprints.
- if "." in endpoint:
- names = chain(
- names, reversed(_split_blueprint_path(endpoint.rpartition(".")[0]))
- )
-
- for name in names:
- if name in self.url_default_functions:
- for func in self.url_default_functions[name]:
- func(endpoint, values)
-
- def handle_url_build_error(
- self, error: BuildError, endpoint: str, values: dict[str, t.Any]
- ) -> str:
- """Called by :meth:`.url_for` if a
- :exc:`~werkzeug.routing.BuildError` was raised. If this returns
- a value, it will be returned by ``url_for``, otherwise the error
- will be re-raised.
-
- Each function in :attr:`url_build_error_handlers` is called with
- ``error``, ``endpoint`` and ``values``. If a function returns
- ``None`` or raises a ``BuildError``, it is skipped. Otherwise,
- its return value is returned by ``url_for``.
-
- :param error: The active ``BuildError`` being handled.
- :param endpoint: The endpoint being built.
- :param values: The keyword arguments passed to ``url_for``.
- """
- for handler in self.url_build_error_handlers:
- try:
- rv = handler(error, endpoint, values)
- except BuildError as e:
- # make error available outside except block
- error = e
- else:
- if rv is not None:
- return rv
-
- # Re-raise if called with an active exception, otherwise raise
- # the passed in exception.
- if error is sys.exc_info()[1]:
- raise
-
- raise error
-
def preprocess_request(self) -> ft.ResponseReturnValue | None:
"""Called before the request is dispatched. Calls
:attr:`url_value_preprocessors` registered with the app and the
@@ -1974,7 +1243,7 @@ class Flask(Scaffold):
rv = self.ensure_sync(before_func)()
if rv is not None:
- return rv
+ return rv # type: ignore[no-any-return]
return None
@@ -2007,7 +1276,8 @@ class Flask(Scaffold):
return response
def do_teardown_request(
- self, exc: BaseException | None = _sentinel # type: ignore
+ self,
+ exc: BaseException | None = _sentinel, # type: ignore[assignment]
) -> None:
"""Called after the request is dispatched and the response is
returned, right before the request context is popped.
@@ -2040,7 +1310,8 @@ class Flask(Scaffold):
request_tearing_down.send(self, _async_wrapper=self.ensure_sync, exc=exc)
def do_teardown_appcontext(
- self, exc: BaseException | None = _sentinel # type: ignore
+ self,
+ exc: BaseException | None = _sentinel, # type: ignore[assignment]
) -> None:
"""Called right before the application context is popped.
@@ -2085,7 +1356,7 @@ class Flask(Scaffold):
"""
return AppContext(self)
- def request_context(self, environ: dict) -> RequestContext:
+ def request_context(self, environ: WSGIEnvironment) -> RequestContext:
"""Create a :class:`~flask.ctx.RequestContext` representing a
WSGI environment. Use a ``with`` block to push the context,
which will make :data:`request` point at this request.
@@ -2157,7 +1428,9 @@ class Flask(Scaffold):
finally:
builder.close()
- def wsgi_app(self, environ: dict, start_response: t.Callable) -> t.Any:
+ def wsgi_app(
+ self, environ: WSGIEnvironment, start_response: StartResponse
+ ) -> cabc.Iterable[bytes]:
"""The actual WSGI application. This is not implemented in
:meth:`__call__` so that middlewares can be applied without
losing a reference to the app object. Instead of doing this::
@@ -2205,7 +1478,9 @@ class Flask(Scaffold):
ctx.pop(error)
- def __call__(self, environ: dict, start_response: t.Callable) -> t.Any:
+ def __call__(
+ self, environ: WSGIEnvironment, start_response: StartResponse
+ ) -> cabc.Iterable[bytes]:
"""The WSGI server calls the Flask application object as the
WSGI application. This calls :meth:`wsgi_app`, which can be
wrapped to apply middleware.
diff --git a/venv/Lib/site-packages/flask/blueprints.py b/venv/Lib/site-packages/flask/blueprints.py
index 0407f86..52859b8 100644
--- a/venv/Lib/site-packages/flask/blueprints.py
+++ b/venv/Lib/site-packages/flask/blueprints.py
@@ -2,625 +2,90 @@ from __future__ import annotations
import os
import typing as t
-from collections import defaultdict
-from functools import update_wrapper
+from datetime import timedelta
-from . import typing as ft
-from .scaffold import _endpoint_from_view_func
-from .scaffold import _sentinel
-from .scaffold import Scaffold
-from .scaffold import setupmethod
+from .globals import current_app
+from .helpers import send_from_directory
+from .sansio.blueprints import Blueprint as SansioBlueprint
+from .sansio.blueprints import BlueprintSetupState as BlueprintSetupState # noqa
if t.TYPE_CHECKING: # pragma: no cover
- from .app import Flask
-
-DeferredSetupFunction = t.Callable[["BlueprintSetupState"], t.Callable]
-T_after_request = t.TypeVar("T_after_request", bound=ft.AfterRequestCallable)
-T_before_request = t.TypeVar("T_before_request", bound=ft.BeforeRequestCallable)
-T_error_handler = t.TypeVar("T_error_handler", bound=ft.ErrorHandlerCallable)
-T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable)
-T_template_context_processor = t.TypeVar(
- "T_template_context_processor", bound=ft.TemplateContextProcessorCallable
-)
-T_template_filter = t.TypeVar("T_template_filter", bound=ft.TemplateFilterCallable)
-T_template_global = t.TypeVar("T_template_global", bound=ft.TemplateGlobalCallable)
-T_template_test = t.TypeVar("T_template_test", bound=ft.TemplateTestCallable)
-T_url_defaults = t.TypeVar("T_url_defaults", bound=ft.URLDefaultCallable)
-T_url_value_preprocessor = t.TypeVar(
- "T_url_value_preprocessor", bound=ft.URLValuePreprocessorCallable
-)
+ from .wrappers import Response
-class BlueprintSetupState:
- """Temporary holder object for registering a blueprint with the
- application. An instance of this class is created by the
- :meth:`~flask.Blueprint.make_setup_state` method and later passed
- to all register callback functions.
- """
+class Blueprint(SansioBlueprint):
+ def get_send_file_max_age(self, filename: str | None) -> int | None:
+ """Used by :func:`send_file` to determine the ``max_age`` cache
+ value for a given file path if it wasn't passed.
- def __init__(
- self,
- blueprint: Blueprint,
- app: Flask,
- options: t.Any,
- first_registration: bool,
- ) -> None:
- #: a reference to the current application
- self.app = app
+ By default, this returns :data:`SEND_FILE_MAX_AGE_DEFAULT` from
+ the configuration of :data:`~flask.current_app`. This defaults
+ to ``None``, which tells the browser to use conditional requests
+ instead of a timed cache, which is usually preferable.
- #: a reference to the blueprint that created this setup state.
- self.blueprint = blueprint
+ Note this is a duplicate of the same method in the Flask
+ class.
- #: a dictionary with all options that were passed to the
- #: :meth:`~flask.Flask.register_blueprint` method.
- self.options = options
+ .. versionchanged:: 2.0
+ The default configuration is ``None`` instead of 12 hours.
- #: as blueprints can be registered multiple times with the
- #: application and not everything wants to be registered
- #: multiple times on it, this attribute can be used to figure
- #: out if the blueprint was registered in the past already.
- self.first_registration = first_registration
-
- subdomain = self.options.get("subdomain")
- if subdomain is None:
- subdomain = self.blueprint.subdomain
-
- #: The subdomain that the blueprint should be active for, ``None``
- #: otherwise.
- self.subdomain = subdomain
-
- url_prefix = self.options.get("url_prefix")
- if url_prefix is None:
- url_prefix = self.blueprint.url_prefix
- #: The prefix that should be used for all URLs defined on the
- #: blueprint.
- self.url_prefix = url_prefix
-
- self.name = self.options.get("name", blueprint.name)
- self.name_prefix = self.options.get("name_prefix", "")
-
- #: A dictionary with URL defaults that is added to each and every
- #: URL that was defined with the blueprint.
- self.url_defaults = dict(self.blueprint.url_values_defaults)
- self.url_defaults.update(self.options.get("url_defaults", ()))
-
- def add_url_rule(
- self,
- rule: str,
- endpoint: str | None = None,
- view_func: t.Callable | None = None,
- **options: t.Any,
- ) -> None:
- """A helper method to register a rule (and optionally a view function)
- to the application. The endpoint is automatically prefixed with the
- blueprint's name.
+ .. versionadded:: 0.9
"""
- if self.url_prefix is not None:
- if rule:
- rule = "/".join((self.url_prefix.rstrip("/"), rule.lstrip("/")))
- else:
- rule = self.url_prefix
- options.setdefault("subdomain", self.subdomain)
- if endpoint is None:
- endpoint = _endpoint_from_view_func(view_func) # type: ignore
- defaults = self.url_defaults
- if "defaults" in options:
- defaults = dict(defaults, **options.pop("defaults"))
+ value = current_app.config["SEND_FILE_MAX_AGE_DEFAULT"]
- self.app.add_url_rule(
- rule,
- f"{self.name_prefix}.{self.name}.{endpoint}".lstrip("."),
- view_func,
- defaults=defaults,
- **options,
+ if value is None:
+ return None
+
+ if isinstance(value, timedelta):
+ return int(value.total_seconds())
+
+ return value # type: ignore[no-any-return]
+
+ def send_static_file(self, filename: str) -> Response:
+ """The view function used to serve files from
+ :attr:`static_folder`. A route is automatically registered for
+ this view at :attr:`static_url_path` if :attr:`static_folder` is
+ set.
+
+ Note this is a duplicate of the same method in the Flask
+ class.
+
+ .. versionadded:: 0.5
+
+ """
+ if not self.has_static_folder:
+ raise RuntimeError("'static_folder' must be set to serve static_files.")
+
+ # send_file only knows to call get_send_file_max_age on the app,
+ # call it here so it works for blueprints too.
+ max_age = self.get_send_file_max_age(filename)
+ return send_from_directory(
+ t.cast(str, self.static_folder), filename, max_age=max_age
)
+ def open_resource(self, resource: str, mode: str = "rb") -> t.IO[t.AnyStr]:
+ """Open a resource file relative to :attr:`root_path` for
+ reading.
-class Blueprint(Scaffold):
- """Represents a blueprint, a collection of routes and other
- app-related functions that can be registered on a real application
- later.
+ For example, if the file ``schema.sql`` is next to the file
+ ``app.py`` where the ``Flask`` app is defined, it can be opened
+ with:
- A blueprint is an object that allows defining application functions
- without requiring an application object ahead of time. It uses the
- same decorators as :class:`~flask.Flask`, but defers the need for an
- application by recording them for later registration.
+ .. code-block:: python
- Decorating a function with a blueprint creates a deferred function
- that is called with :class:`~flask.blueprints.BlueprintSetupState`
- when the blueprint is registered on an application.
+ with app.open_resource("schema.sql") as f:
+ conn.executescript(f.read())
- See :doc:`/blueprints` for more information.
+ :param resource: Path to the resource relative to
+ :attr:`root_path`.
+ :param mode: Open the file in this mode. Only reading is
+ supported, valid values are "r" (or "rt") and "rb".
- :param name: The name of the blueprint. Will be prepended to each
- endpoint name.
- :param import_name: The name of the blueprint package, usually
- ``__name__``. This helps locate the ``root_path`` for the
- blueprint.
- :param static_folder: A folder with static files that should be
- served by the blueprint's static route. The path is relative to
- the blueprint's root path. Blueprint static files are disabled
- by default.
- :param static_url_path: The url to serve static files from.
- Defaults to ``static_folder``. If the blueprint does not have
- a ``url_prefix``, the app's static route will take precedence,
- and the blueprint's static files won't be accessible.
- :param template_folder: A folder with templates that should be added
- to the app's template search path. The path is relative to the
- blueprint's root path. Blueprint templates are disabled by
- default. Blueprint templates have a lower precedence than those
- in the app's templates folder.
- :param url_prefix: A path to prepend to all of the blueprint's URLs,
- to make them distinct from the rest of the app's routes.
- :param subdomain: A subdomain that blueprint routes will match on by
- default.
- :param url_defaults: A dict of default values that blueprint routes
- will receive by default.
- :param root_path: By default, the blueprint will automatically set
- this based on ``import_name``. In certain situations this
- automatic detection can fail, so the path can be specified
- manually instead.
+ Note this is a duplicate of the same method in the Flask
+ class.
- .. versionchanged:: 1.1.0
- Blueprints have a ``cli`` group to register nested CLI commands.
- The ``cli_group`` parameter controls the name of the group under
- the ``flask`` command.
-
- .. versionadded:: 0.7
- """
-
- _got_registered_once = False
-
- def __init__(
- self,
- name: str,
- import_name: str,
- static_folder: str | os.PathLike | None = None,
- static_url_path: str | None = None,
- template_folder: str | os.PathLike | None = None,
- url_prefix: str | None = None,
- subdomain: str | None = None,
- url_defaults: dict | None = None,
- root_path: str | None = None,
- cli_group: str | None = _sentinel, # type: ignore
- ):
- super().__init__(
- import_name=import_name,
- static_folder=static_folder,
- static_url_path=static_url_path,
- template_folder=template_folder,
- root_path=root_path,
- )
-
- if not name:
- raise ValueError("'name' may not be empty.")
-
- if "." in name:
- raise ValueError("'name' may not contain a dot '.' character.")
-
- self.name = name
- self.url_prefix = url_prefix
- self.subdomain = subdomain
- self.deferred_functions: list[DeferredSetupFunction] = []
-
- if url_defaults is None:
- url_defaults = {}
-
- self.url_values_defaults = url_defaults
- self.cli_group = cli_group
- self._blueprints: list[tuple[Blueprint, dict]] = []
-
- def _check_setup_finished(self, f_name: str) -> None:
- if self._got_registered_once:
- raise AssertionError(
- f"The setup method '{f_name}' can no longer be called on the blueprint"
- f" '{self.name}'. It has already been registered at least once, any"
- " changes will not be applied consistently.\n"
- "Make sure all imports, decorators, functions, etc. needed to set up"
- " the blueprint are done before registering it."
- )
-
- @setupmethod
- def record(self, func: t.Callable) -> None:
- """Registers a function that is called when the blueprint is
- registered on the application. This function is called with the
- state as argument as returned by the :meth:`make_setup_state`
- method.
"""
- self.deferred_functions.append(func)
+ if mode not in {"r", "rt", "rb"}:
+ raise ValueError("Resources can only be opened for reading.")
- @setupmethod
- def record_once(self, func: t.Callable) -> None:
- """Works like :meth:`record` but wraps the function in another
- function that will ensure the function is only called once. If the
- blueprint is registered a second time on the application, the
- function passed is not called.
- """
-
- def wrapper(state: BlueprintSetupState) -> None:
- if state.first_registration:
- func(state)
-
- self.record(update_wrapper(wrapper, func))
-
- def make_setup_state(
- self, app: Flask, options: dict, first_registration: bool = False
- ) -> BlueprintSetupState:
- """Creates an instance of :meth:`~flask.blueprints.BlueprintSetupState`
- object that is later passed to the register callback functions.
- Subclasses can override this to return a subclass of the setup state.
- """
- return BlueprintSetupState(self, app, options, first_registration)
-
- @setupmethod
- def register_blueprint(self, blueprint: Blueprint, **options: t.Any) -> None:
- """Register a :class:`~flask.Blueprint` on this blueprint. Keyword
- arguments passed to this method will override the defaults set
- on the blueprint.
-
- .. versionchanged:: 2.0.1
- The ``name`` option can be used to change the (pre-dotted)
- name the blueprint is registered with. This allows the same
- blueprint to be registered multiple times with unique names
- for ``url_for``.
-
- .. versionadded:: 2.0
- """
- if blueprint is self:
- raise ValueError("Cannot register a blueprint on itself")
- self._blueprints.append((blueprint, options))
-
- def register(self, app: Flask, options: dict) -> None:
- """Called by :meth:`Flask.register_blueprint` to register all
- views and callbacks registered on the blueprint with the
- application. Creates a :class:`.BlueprintSetupState` and calls
- each :meth:`record` callback with it.
-
- :param app: The application this blueprint is being registered
- with.
- :param options: Keyword arguments forwarded from
- :meth:`~Flask.register_blueprint`.
-
- .. versionchanged:: 2.3
- Nested blueprints now correctly apply subdomains.
-
- .. versionchanged:: 2.1
- Registering the same blueprint with the same name multiple
- times is an error.
-
- .. versionchanged:: 2.0.1
- Nested blueprints are registered with their dotted name.
- This allows different blueprints with the same name to be
- nested at different locations.
-
- .. versionchanged:: 2.0.1
- The ``name`` option can be used to change the (pre-dotted)
- name the blueprint is registered with. This allows the same
- blueprint to be registered multiple times with unique names
- for ``url_for``.
- """
- name_prefix = options.get("name_prefix", "")
- self_name = options.get("name", self.name)
- name = f"{name_prefix}.{self_name}".lstrip(".")
-
- if name in app.blueprints:
- bp_desc = "this" if app.blueprints[name] is self else "a different"
- existing_at = f" '{name}'" if self_name != name else ""
-
- raise ValueError(
- f"The name '{self_name}' is already registered for"
- f" {bp_desc} blueprint{existing_at}. Use 'name=' to"
- f" provide a unique name."
- )
-
- first_bp_registration = not any(bp is self for bp in app.blueprints.values())
- first_name_registration = name not in app.blueprints
-
- app.blueprints[name] = self
- self._got_registered_once = True
- state = self.make_setup_state(app, options, first_bp_registration)
-
- if self.has_static_folder:
- state.add_url_rule(
- f"{self.static_url_path}/",
- view_func=self.send_static_file,
- endpoint="static",
- )
-
- # Merge blueprint data into parent.
- if first_bp_registration or first_name_registration:
-
- def extend(bp_dict, parent_dict):
- for key, values in bp_dict.items():
- key = name if key is None else f"{name}.{key}"
- parent_dict[key].extend(values)
-
- for key, value in self.error_handler_spec.items():
- key = name if key is None else f"{name}.{key}"
- value = defaultdict(
- dict,
- {
- code: {
- exc_class: func for exc_class, func in code_values.items()
- }
- for code, code_values in value.items()
- },
- )
- app.error_handler_spec[key] = value
-
- for endpoint, func in self.view_functions.items():
- app.view_functions[endpoint] = func
-
- extend(self.before_request_funcs, app.before_request_funcs)
- extend(self.after_request_funcs, app.after_request_funcs)
- extend(
- self.teardown_request_funcs,
- app.teardown_request_funcs,
- )
- extend(self.url_default_functions, app.url_default_functions)
- extend(self.url_value_preprocessors, app.url_value_preprocessors)
- extend(self.template_context_processors, app.template_context_processors)
-
- for deferred in self.deferred_functions:
- deferred(state)
-
- cli_resolved_group = options.get("cli_group", self.cli_group)
-
- if self.cli.commands:
- if cli_resolved_group is None:
- app.cli.commands.update(self.cli.commands)
- elif cli_resolved_group is _sentinel:
- self.cli.name = name
- app.cli.add_command(self.cli)
- else:
- self.cli.name = cli_resolved_group
- app.cli.add_command(self.cli)
-
- for blueprint, bp_options in self._blueprints:
- bp_options = bp_options.copy()
- bp_url_prefix = bp_options.get("url_prefix")
- bp_subdomain = bp_options.get("subdomain")
-
- if bp_subdomain is None:
- bp_subdomain = blueprint.subdomain
-
- if state.subdomain is not None and bp_subdomain is not None:
- bp_options["subdomain"] = bp_subdomain + "." + state.subdomain
- elif bp_subdomain is not None:
- bp_options["subdomain"] = bp_subdomain
- elif state.subdomain is not None:
- bp_options["subdomain"] = state.subdomain
-
- if bp_url_prefix is None:
- bp_url_prefix = blueprint.url_prefix
-
- if state.url_prefix is not None and bp_url_prefix is not None:
- bp_options["url_prefix"] = (
- state.url_prefix.rstrip("/") + "/" + bp_url_prefix.lstrip("/")
- )
- elif bp_url_prefix is not None:
- bp_options["url_prefix"] = bp_url_prefix
- elif state.url_prefix is not None:
- bp_options["url_prefix"] = state.url_prefix
-
- bp_options["name_prefix"] = name
- blueprint.register(app, bp_options)
-
- @setupmethod
- def add_url_rule(
- self,
- rule: str,
- endpoint: str | None = None,
- view_func: ft.RouteCallable | None = None,
- provide_automatic_options: bool | None = None,
- **options: t.Any,
- ) -> None:
- """Register a URL rule with the blueprint. See :meth:`.Flask.add_url_rule` for
- full documentation.
-
- The URL rule is prefixed with the blueprint's URL prefix. The endpoint name,
- used with :func:`url_for`, is prefixed with the blueprint's name.
- """
- if endpoint and "." in endpoint:
- raise ValueError("'endpoint' may not contain a dot '.' character.")
-
- if view_func and hasattr(view_func, "__name__") and "." in view_func.__name__:
- raise ValueError("'view_func' name may not contain a dot '.' character.")
-
- self.record(
- lambda s: s.add_url_rule(
- rule,
- endpoint,
- view_func,
- provide_automatic_options=provide_automatic_options,
- **options,
- )
- )
-
- @setupmethod
- def app_template_filter(
- self, name: str | None = None
- ) -> t.Callable[[T_template_filter], T_template_filter]:
- """Register a template filter, available in any template rendered by the
- application. Equivalent to :meth:`.Flask.template_filter`.
-
- :param name: the optional name of the filter, otherwise the
- function name will be used.
- """
-
- def decorator(f: T_template_filter) -> T_template_filter:
- self.add_app_template_filter(f, name=name)
- return f
-
- return decorator
-
- @setupmethod
- def add_app_template_filter(
- self, f: ft.TemplateFilterCallable, name: str | None = None
- ) -> None:
- """Register a template filter, available in any template rendered by the
- application. Works like the :meth:`app_template_filter` decorator. Equivalent to
- :meth:`.Flask.add_template_filter`.
-
- :param name: the optional name of the filter, otherwise the
- function name will be used.
- """
-
- def register_template(state: BlueprintSetupState) -> None:
- state.app.jinja_env.filters[name or f.__name__] = f
-
- self.record_once(register_template)
-
- @setupmethod
- def app_template_test(
- self, name: str | None = None
- ) -> t.Callable[[T_template_test], T_template_test]:
- """Register a template test, available in any template rendered by the
- application. Equivalent to :meth:`.Flask.template_test`.
-
- .. versionadded:: 0.10
-
- :param name: the optional name of the test, otherwise the
- function name will be used.
- """
-
- def decorator(f: T_template_test) -> T_template_test:
- self.add_app_template_test(f, name=name)
- return f
-
- return decorator
-
- @setupmethod
- def add_app_template_test(
- self, f: ft.TemplateTestCallable, name: str | None = None
- ) -> None:
- """Register a template test, available in any template rendered by the
- application. Works like the :meth:`app_template_test` decorator. Equivalent to
- :meth:`.Flask.add_template_test`.
-
- .. versionadded:: 0.10
-
- :param name: the optional name of the test, otherwise the
- function name will be used.
- """
-
- def register_template(state: BlueprintSetupState) -> None:
- state.app.jinja_env.tests[name or f.__name__] = f
-
- self.record_once(register_template)
-
- @setupmethod
- def app_template_global(
- self, name: str | None = None
- ) -> t.Callable[[T_template_global], T_template_global]:
- """Register a template global, available in any template rendered by the
- application. Equivalent to :meth:`.Flask.template_global`.
-
- .. versionadded:: 0.10
-
- :param name: the optional name of the global, otherwise the
- function name will be used.
- """
-
- def decorator(f: T_template_global) -> T_template_global:
- self.add_app_template_global(f, name=name)
- return f
-
- return decorator
-
- @setupmethod
- def add_app_template_global(
- self, f: ft.TemplateGlobalCallable, name: str | None = None
- ) -> None:
- """Register a template global, available in any template rendered by the
- application. Works like the :meth:`app_template_global` decorator. Equivalent to
- :meth:`.Flask.add_template_global`.
-
- .. versionadded:: 0.10
-
- :param name: the optional name of the global, otherwise the
- function name will be used.
- """
-
- def register_template(state: BlueprintSetupState) -> None:
- state.app.jinja_env.globals[name or f.__name__] = f
-
- self.record_once(register_template)
-
- @setupmethod
- def before_app_request(self, f: T_before_request) -> T_before_request:
- """Like :meth:`before_request`, but before every request, not only those handled
- by the blueprint. Equivalent to :meth:`.Flask.before_request`.
- """
- self.record_once(
- lambda s: s.app.before_request_funcs.setdefault(None, []).append(f)
- )
- return f
-
- @setupmethod
- def after_app_request(self, f: T_after_request) -> T_after_request:
- """Like :meth:`after_request`, but after every request, not only those handled
- by the blueprint. Equivalent to :meth:`.Flask.after_request`.
- """
- self.record_once(
- lambda s: s.app.after_request_funcs.setdefault(None, []).append(f)
- )
- return f
-
- @setupmethod
- def teardown_app_request(self, f: T_teardown) -> T_teardown:
- """Like :meth:`teardown_request`, but after every request, not only those
- handled by the blueprint. Equivalent to :meth:`.Flask.teardown_request`.
- """
- self.record_once(
- lambda s: s.app.teardown_request_funcs.setdefault(None, []).append(f)
- )
- return f
-
- @setupmethod
- def app_context_processor(
- self, f: T_template_context_processor
- ) -> T_template_context_processor:
- """Like :meth:`context_processor`, but for templates rendered by every view, not
- only by the blueprint. Equivalent to :meth:`.Flask.context_processor`.
- """
- self.record_once(
- lambda s: s.app.template_context_processors.setdefault(None, []).append(f)
- )
- return f
-
- @setupmethod
- def app_errorhandler(
- self, code: type[Exception] | int
- ) -> t.Callable[[T_error_handler], T_error_handler]:
- """Like :meth:`errorhandler`, but for every request, not only those handled by
- the blueprint. Equivalent to :meth:`.Flask.errorhandler`.
- """
-
- def decorator(f: T_error_handler) -> T_error_handler:
- self.record_once(lambda s: s.app.errorhandler(code)(f))
- return f
-
- return decorator
-
- @setupmethod
- def app_url_value_preprocessor(
- self, f: T_url_value_preprocessor
- ) -> T_url_value_preprocessor:
- """Like :meth:`url_value_preprocessor`, but for every request, not only those
- handled by the blueprint. Equivalent to :meth:`.Flask.url_value_preprocessor`.
- """
- self.record_once(
- lambda s: s.app.url_value_preprocessors.setdefault(None, []).append(f)
- )
- return f
-
- @setupmethod
- def app_url_defaults(self, f: T_url_defaults) -> T_url_defaults:
- """Like :meth:`url_defaults`, but for every request, not only those handled by
- the blueprint. Equivalent to :meth:`.Flask.url_defaults`.
- """
- self.record_once(
- lambda s: s.app.url_default_functions.setdefault(None, []).append(f)
- )
- return f
+ return open(os.path.join(self.root_path, resource), mode)
diff --git a/venv/Lib/site-packages/flask/cli.py b/venv/Lib/site-packages/flask/cli.py
index dda266b..d4df280 100644
--- a/venv/Lib/site-packages/flask/cli.py
+++ b/venv/Lib/site-packages/flask/cli.py
@@ -1,6 +1,7 @@
from __future__ import annotations
import ast
+import collections.abc as cabc
import importlib.metadata
import inspect
import os
@@ -11,6 +12,7 @@ import traceback
import typing as t
from functools import update_wrapper
from operator import itemgetter
+from types import ModuleType
import click
from click.core import ParameterSource
@@ -23,6 +25,12 @@ from .helpers import get_debug_flag
from .helpers import get_load_dotenv
if t.TYPE_CHECKING:
+ import ssl
+
+ from _typeshed.wsgi import StartResponse
+ from _typeshed.wsgi import WSGIApplication
+ from _typeshed.wsgi import WSGIEnvironment
+
from .app import Flask
@@ -30,7 +38,7 @@ class NoAppException(click.UsageError):
"""Raised if an application cannot be found or loaded."""
-def find_best_app(module):
+def find_best_app(module: ModuleType) -> Flask:
"""Given a module instance this tries to find the best possible
application in the module or raises an exception.
"""
@@ -83,7 +91,7 @@ def find_best_app(module):
)
-def _called_with_wrong_args(f):
+def _called_with_wrong_args(f: t.Callable[..., Flask]) -> bool:
"""Check whether calling a function raised a ``TypeError`` because
the call failed or because something in the factory raised the
error.
@@ -109,7 +117,7 @@ def _called_with_wrong_args(f):
del tb
-def find_app_by_string(module, app_name):
+def find_app_by_string(module: ModuleType, app_name: str) -> Flask:
"""Check if the given string is a variable name or a function. Call
a function to get the app instance, or return the variable directly.
"""
@@ -140,7 +148,11 @@ def find_app_by_string(module, app_name):
# Parse the positional and keyword arguments as literals.
try:
args = [ast.literal_eval(arg) for arg in expr.args]
- kwargs = {kw.arg: ast.literal_eval(kw.value) for kw in expr.keywords}
+ kwargs = {
+ kw.arg: ast.literal_eval(kw.value)
+ for kw in expr.keywords
+ if kw.arg is not None
+ }
except ValueError:
# literal_eval gives cryptic error messages, show a generic
# message with the full expression instead.
@@ -185,7 +197,7 @@ def find_app_by_string(module, app_name):
)
-def prepare_import(path):
+def prepare_import(path: str) -> str:
"""Given a filename this will try to calculate the python path, add it
to the search path and return the actual module name that is expected.
"""
@@ -214,13 +226,29 @@ def prepare_import(path):
return ".".join(module_name[::-1])
-def locate_app(module_name, app_name, raise_if_not_found=True):
+@t.overload
+def locate_app(
+ module_name: str, app_name: str | None, raise_if_not_found: t.Literal[True] = True
+) -> Flask:
+ ...
+
+
+@t.overload
+def locate_app(
+ module_name: str, app_name: str | None, raise_if_not_found: t.Literal[False] = ...
+) -> Flask | None:
+ ...
+
+
+def locate_app(
+ module_name: str, app_name: str | None, raise_if_not_found: bool = True
+) -> Flask | None:
try:
__import__(module_name)
except ImportError:
# Reraise the ImportError if it occurred within the imported module.
# Determine this by checking whether the trace has a depth > 1.
- if sys.exc_info()[2].tb_next:
+ if sys.exc_info()[2].tb_next: # type: ignore[union-attr]
raise NoAppException(
f"While importing {module_name!r}, an ImportError was"
f" raised:\n\n{traceback.format_exc()}"
@@ -228,7 +256,7 @@ def locate_app(module_name, app_name, raise_if_not_found=True):
elif raise_if_not_found:
raise NoAppException(f"Could not import {module_name!r}.") from None
else:
- return
+ return None
module = sys.modules[module_name]
@@ -238,7 +266,7 @@ def locate_app(module_name, app_name, raise_if_not_found=True):
return find_app_by_string(module, app_name)
-def get_version(ctx, param, value):
+def get_version(ctx: click.Context, param: click.Parameter, value: t.Any) -> None:
if not value or ctx.resilient_parsing:
return
@@ -299,7 +327,7 @@ class ScriptInfo:
return self._loaded_app
if self.create_app is not None:
- app = self.create_app()
+ app: Flask | None = self.create_app()
else:
if self.app_import_path:
path, name = (
@@ -312,10 +340,10 @@ class ScriptInfo:
import_name = prepare_import(path)
app = locate_app(import_name, None, raise_if_not_found=False)
- if app:
+ if app is not None:
break
- if not app:
+ if app is None:
raise NoAppException(
"Could not locate a Flask application. Use the"
" 'flask --app' option, 'FLASK_APP' environment"
@@ -334,8 +362,10 @@ class ScriptInfo:
pass_script_info = click.make_pass_decorator(ScriptInfo, ensure=True)
+F = t.TypeVar("F", bound=t.Callable[..., t.Any])
-def with_appcontext(f):
+
+def with_appcontext(f: F) -> F:
"""Wraps a callback so that it's guaranteed to be executed with the
script's application context.
@@ -350,14 +380,14 @@ def with_appcontext(f):
"""
@click.pass_context
- def decorator(__ctx, *args, **kwargs):
+ def decorator(ctx: click.Context, /, *args: t.Any, **kwargs: t.Any) -> t.Any:
if not current_app:
- app = __ctx.ensure_object(ScriptInfo).load_app()
- __ctx.with_resource(app.app_context())
+ app = ctx.ensure_object(ScriptInfo).load_app()
+ ctx.with_resource(app.app_context())
- return __ctx.invoke(f, *args, **kwargs)
+ return ctx.invoke(f, *args, **kwargs)
- return update_wrapper(decorator, f)
+ return update_wrapper(decorator, f) # type: ignore[return-value]
class AppGroup(click.Group):
@@ -368,27 +398,31 @@ class AppGroup(click.Group):
Not to be confused with :class:`FlaskGroup`.
"""
- def command(self, *args, **kwargs):
+ def command( # type: ignore[override]
+ self, *args: t.Any, **kwargs: t.Any
+ ) -> t.Callable[[t.Callable[..., t.Any]], click.Command]:
"""This works exactly like the method of the same name on a regular
:class:`click.Group` but it wraps callbacks in :func:`with_appcontext`
unless it's disabled by passing ``with_appcontext=False``.
"""
wrap_for_ctx = kwargs.pop("with_appcontext", True)
- def decorator(f):
+ def decorator(f: t.Callable[..., t.Any]) -> click.Command:
if wrap_for_ctx:
f = with_appcontext(f)
- return click.Group.command(self, *args, **kwargs)(f)
+ return super(AppGroup, self).command(*args, **kwargs)(f) # type: ignore[no-any-return]
return decorator
- def group(self, *args, **kwargs):
+ def group( # type: ignore[override]
+ self, *args: t.Any, **kwargs: t.Any
+ ) -> t.Callable[[t.Callable[..., t.Any]], click.Group]:
"""This works exactly like the method of the same name on a regular
:class:`click.Group` but it defaults the group class to
:class:`AppGroup`.
"""
kwargs.setdefault("cls", AppGroup)
- return click.Group.group(self, *args, **kwargs)
+ return super().group(*args, **kwargs) # type: ignore[no-any-return]
def _set_app(ctx: click.Context, param: click.Option, value: str | None) -> str | None:
@@ -545,7 +579,7 @@ class FlaskGroup(AppGroup):
self._loaded_plugin_commands = False
- def _load_plugin_commands(self):
+ def _load_plugin_commands(self) -> None:
if self._loaded_plugin_commands:
return
@@ -562,7 +596,7 @@ class FlaskGroup(AppGroup):
self._loaded_plugin_commands = True
- def get_command(self, ctx, name):
+ def get_command(self, ctx: click.Context, name: str) -> click.Command | None:
self._load_plugin_commands()
# Look up built-in and plugin commands, which should be
# available even if the app fails to load.
@@ -584,12 +618,12 @@ class FlaskGroup(AppGroup):
# Push an app context for the loaded app unless it is already
# active somehow. This makes the context available to parameter
# and command callbacks without needing @with_appcontext.
- if not current_app or current_app._get_current_object() is not app:
+ if not current_app or current_app._get_current_object() is not app: # type: ignore[attr-defined]
ctx.with_resource(app.app_context())
return app.cli.get_command(ctx, name)
- def list_commands(self, ctx):
+ def list_commands(self, ctx: click.Context) -> list[str]:
self._load_plugin_commands()
# Start with the built-in and plugin commands.
rv = set(super().list_commands(ctx))
@@ -645,14 +679,14 @@ class FlaskGroup(AppGroup):
return super().parse_args(ctx, args)
-def _path_is_ancestor(path, other):
+def _path_is_ancestor(path: str, other: str) -> bool:
"""Take ``other`` and remove the length of ``path`` from it. Then join it
to ``path``. If it is the original value, ``path`` is an ancestor of
``other``."""
return os.path.join(path, other[len(path) :].lstrip(os.sep)) == other
-def load_dotenv(path: str | os.PathLike | None = None) -> bool:
+def load_dotenv(path: str | os.PathLike[str] | None = None) -> bool:
"""Load "dotenv" files in order of precedence to set environment variables.
If an env var is already set it is not overwritten, so earlier files in the
@@ -713,7 +747,7 @@ def load_dotenv(path: str | os.PathLike | None = None) -> bool:
return loaded # True if at least one file was located and loaded.
-def show_server_banner(debug, app_import_path):
+def show_server_banner(debug: bool, app_import_path: str | None) -> None:
"""Show extra startup messages the first time the server is run,
ignoring the reloader.
"""
@@ -735,10 +769,12 @@ class CertParamType(click.ParamType):
name = "path"
- def __init__(self):
+ def __init__(self) -> None:
self.path_type = click.Path(exists=True, dir_okay=False, resolve_path=True)
- def convert(self, value, param, ctx):
+ def convert(
+ self, value: t.Any, param: click.Parameter | None, ctx: click.Context | None
+ ) -> t.Any:
try:
import ssl
except ImportError:
@@ -773,7 +809,7 @@ class CertParamType(click.ParamType):
raise
-def _validate_key(ctx, param, value):
+def _validate_key(ctx: click.Context, param: click.Parameter, value: t.Any) -> t.Any:
"""The ``--key`` option must be specified when ``--cert`` is a file.
Modifies the ``cert`` param to be a ``(cert, key)`` pair if needed.
"""
@@ -795,7 +831,9 @@ def _validate_key(ctx, param, value):
if is_context:
raise click.BadParameter(
- 'When "--cert" is an SSLContext object, "--key is not used.', ctx, param
+ 'When "--cert" is an SSLContext object, "--key" is not used.',
+ ctx,
+ param,
)
if not cert:
@@ -816,8 +854,11 @@ class SeparatedPathType(click.Path):
validated as a :class:`click.Path` type.
"""
- def convert(self, value, param, ctx):
+ def convert(
+ self, value: t.Any, param: click.Parameter | None, ctx: click.Context | None
+ ) -> t.Any:
items = self.split_envvar_value(value)
+ # can't call no-arg super() inside list comprehension until Python 3.12
super_convert = super().convert
return [super_convert(item, param, ctx) for item in items]
@@ -876,16 +917,16 @@ class SeparatedPathType(click.Path):
)
@pass_script_info
def run_command(
- info,
- host,
- port,
- reload,
- debugger,
- with_threads,
- cert,
- extra_files,
- exclude_patterns,
-):
+ info: ScriptInfo,
+ host: str,
+ port: int,
+ reload: bool,
+ debugger: bool,
+ with_threads: bool,
+ cert: ssl.SSLContext | tuple[str, str | None] | t.Literal["adhoc"] | None,
+ extra_files: list[str] | None,
+ exclude_patterns: list[str] | None,
+) -> None:
"""Run a local development server.
This server is for development purposes only. It does not provide
@@ -895,7 +936,7 @@ def run_command(
option.
"""
try:
- app = info.load_app()
+ app: WSGIApplication = info.load_app()
except Exception as e:
if is_running_from_reloader():
# When reloading, print out the error immediately, but raise
@@ -903,7 +944,9 @@ def run_command(
traceback.print_exc()
err = e
- def app(environ, start_response):
+ def app(
+ environ: WSGIEnvironment, start_response: StartResponse
+ ) -> cabc.Iterable[bytes]:
raise err from None
else:
@@ -954,7 +997,7 @@ def shell_command() -> None:
f"App: {current_app.import_name}\n"
f"Instance: {current_app.instance_path}"
)
- ctx: dict = {}
+ ctx: dict[str, t.Any] = {}
# Support the regular Python interpreter startup script if someone
# is using it.
diff --git a/venv/Lib/site-packages/flask/config.py b/venv/Lib/site-packages/flask/config.py
index 5f921b4..f2f4147 100644
--- a/venv/Lib/site-packages/flask/config.py
+++ b/venv/Lib/site-packages/flask/config.py
@@ -8,27 +8,48 @@ import typing as t
from werkzeug.utils import import_string
+if t.TYPE_CHECKING:
+ import typing_extensions as te
-class ConfigAttribute:
+ from .sansio.app import App
+
+
+T = t.TypeVar("T")
+
+
+class ConfigAttribute(t.Generic[T]):
"""Makes an attribute forward to the config"""
- def __init__(self, name: str, get_converter: t.Callable | None = None) -> None:
+ def __init__(
+ self, name: str, get_converter: t.Callable[[t.Any], T] | None = None
+ ) -> None:
self.__name__ = name
self.get_converter = get_converter
- def __get__(self, obj: t.Any, owner: t.Any = None) -> t.Any:
+ @t.overload
+ def __get__(self, obj: None, owner: None) -> te.Self:
+ ...
+
+ @t.overload
+ def __get__(self, obj: App, owner: type[App]) -> T:
+ ...
+
+ def __get__(self, obj: App | None, owner: type[App] | None = None) -> T | te.Self:
if obj is None:
return self
+
rv = obj.config[self.__name__]
+
if self.get_converter is not None:
rv = self.get_converter(rv)
- return rv
- def __set__(self, obj: t.Any, value: t.Any) -> None:
+ return rv # type: ignore[no-any-return]
+
+ def __set__(self, obj: App, value: t.Any) -> None:
obj.config[self.__name__] = value
-class Config(dict):
+class Config(dict): # type: ignore[type-arg]
"""Works exactly like a dict but provides ways to fill it from files
or special dictionaries. There are two common patterns to populate the
config.
@@ -73,7 +94,9 @@ class Config(dict):
"""
def __init__(
- self, root_path: str | os.PathLike, defaults: dict | None = None
+ self,
+ root_path: str | os.PathLike[str],
+ defaults: dict[str, t.Any] | None = None,
) -> None:
super().__init__(defaults or {})
self.root_path = root_path
@@ -166,7 +189,9 @@ class Config(dict):
return True
- def from_pyfile(self, filename: str | os.PathLike, silent: bool = False) -> bool:
+ def from_pyfile(
+ self, filename: str | os.PathLike[str], silent: bool = False
+ ) -> bool:
"""Updates the values in the config from a Python file. This function
behaves as if the file was imported as module with the
:meth:`from_object` function.
@@ -235,8 +260,8 @@ class Config(dict):
def from_file(
self,
- filename: str | os.PathLike,
- load: t.Callable[[t.IO[t.Any]], t.Mapping],
+ filename: str | os.PathLike[str],
+ load: t.Callable[[t.IO[t.Any]], t.Mapping[str, t.Any]],
silent: bool = False,
text: bool = True,
) -> bool:
diff --git a/venv/Lib/site-packages/flask/ctx.py b/venv/Lib/site-packages/flask/ctx.py
index b37e4e0..9b164d3 100644
--- a/venv/Lib/site-packages/flask/ctx.py
+++ b/venv/Lib/site-packages/flask/ctx.py
@@ -15,6 +15,8 @@ from .signals import appcontext_popped
from .signals import appcontext_pushed
if t.TYPE_CHECKING: # pragma: no cover
+ from _typeshed.wsgi import WSGIEnvironment
+
from .app import Flask
from .sessions import SessionMixin
from .wrappers import Request
@@ -112,7 +114,9 @@ class _AppCtxGlobals:
return object.__repr__(self)
-def after_this_request(f: ft.AfterRequestCallable) -> ft.AfterRequestCallable:
+def after_this_request(
+ f: ft.AfterRequestCallable[t.Any],
+) -> ft.AfterRequestCallable[t.Any]:
"""Executes a function after this request. This is useful to modify
response objects. The function is passed the response object and has
to return the same or a new one.
@@ -145,7 +149,10 @@ def after_this_request(f: ft.AfterRequestCallable) -> ft.AfterRequestCallable:
return f
-def copy_current_request_context(f: t.Callable) -> t.Callable:
+F = t.TypeVar("F", bound=t.Callable[..., t.Any])
+
+
+def copy_current_request_context(f: F) -> F:
"""A helper function that decorates a function to retain the current
request context. This is useful when working with greenlets. The moment
the function is decorated a copy of the request context is created and
@@ -179,11 +186,11 @@ def copy_current_request_context(f: t.Callable) -> t.Callable:
ctx = ctx.copy()
- def wrapper(*args, **kwargs):
- with ctx:
- return ctx.app.ensure_sync(f)(*args, **kwargs)
+ def wrapper(*args: t.Any, **kwargs: t.Any) -> t.Any:
+ with ctx: # type: ignore[union-attr]
+ return ctx.app.ensure_sync(f)(*args, **kwargs) # type: ignore[union-attr]
- return update_wrapper(wrapper, f)
+ return update_wrapper(wrapper, f) # type: ignore[return-value]
def has_request_context() -> bool:
@@ -239,7 +246,7 @@ class AppContext:
self.app = app
self.url_adapter = app.create_url_adapter(None)
self.g: _AppCtxGlobals = app.app_ctx_globals_class()
- self._cv_tokens: list[contextvars.Token] = []
+ self._cv_tokens: list[contextvars.Token[AppContext]] = []
def push(self) -> None:
"""Binds the app context to the current context."""
@@ -302,7 +309,7 @@ class RequestContext:
def __init__(
self,
app: Flask,
- environ: dict,
+ environ: WSGIEnvironment,
request: Request | None = None,
session: SessionMixin | None = None,
) -> None:
@@ -321,9 +328,11 @@ class RequestContext:
# Functions that should be executed after the request on the response
# object. These will be called before the regular "after_request"
# functions.
- self._after_request_functions: list[ft.AfterRequestCallable] = []
+ self._after_request_functions: list[ft.AfterRequestCallable[t.Any]] = []
- self._cv_tokens: list[tuple[contextvars.Token, AppContext | None]] = []
+ self._cv_tokens: list[
+ tuple[contextvars.Token[RequestContext], AppContext | None]
+ ] = []
def copy(self) -> RequestContext:
"""Creates a copy of this request context with the same request object.
diff --git a/venv/Lib/site-packages/flask/debughelpers.py b/venv/Lib/site-packages/flask/debughelpers.py
index 6061441..2c8c4c4 100644
--- a/venv/Lib/site-packages/flask/debughelpers.py
+++ b/venv/Lib/site-packages/flask/debughelpers.py
@@ -2,9 +2,16 @@ from __future__ import annotations
import typing as t
-from .app import Flask
+from jinja2.loaders import BaseLoader
+from werkzeug.routing import RequestRedirect
+
from .blueprints import Blueprint
from .globals import request_ctx
+from .sansio.app import App
+
+if t.TYPE_CHECKING:
+ from .sansio.scaffold import Scaffold
+ from .wrappers import Request
class UnexpectedUnicodeError(AssertionError, UnicodeError):
@@ -18,7 +25,7 @@ class DebugFilesKeyError(KeyError, AssertionError):
provide a better error message than just a generic KeyError/BadRequest.
"""
- def __init__(self, request, key):
+ def __init__(self, request: Request, key: str) -> None:
form_matches = request.form.getlist(key)
buf = [
f"You tried to access the file {key!r} in the request.files"
@@ -36,7 +43,7 @@ class DebugFilesKeyError(KeyError, AssertionError):
)
self.msg = "".join(buf)
- def __str__(self):
+ def __str__(self) -> str:
return self.msg
@@ -47,8 +54,9 @@ class FormDataRoutingRedirect(AssertionError):
307 or 308.
"""
- def __init__(self, request):
+ def __init__(self, request: Request) -> None:
exc = request.routing_exception
+ assert isinstance(exc, RequestRedirect)
buf = [
f"A request was sent to '{request.url}', but routing issued"
f" a redirect to the canonical URL '{exc.new_url}'."
@@ -70,7 +78,7 @@ class FormDataRoutingRedirect(AssertionError):
super().__init__("".join(buf))
-def attach_enctype_error_multidict(request):
+def attach_enctype_error_multidict(request: Request) -> None:
"""Patch ``request.files.__getitem__`` to raise a descriptive error
about ``enctype=multipart/form-data``.
@@ -79,8 +87,8 @@ def attach_enctype_error_multidict(request):
"""
oldcls = request.files.__class__
- class newcls(oldcls):
- def __getitem__(self, key):
+ class newcls(oldcls): # type: ignore[valid-type, misc]
+ def __getitem__(self, key: str) -> t.Any:
try:
return super().__getitem__(key)
except KeyError as e:
@@ -96,7 +104,7 @@ def attach_enctype_error_multidict(request):
request.files.__class__ = newcls
-def _dump_loader_info(loader) -> t.Generator:
+def _dump_loader_info(loader: BaseLoader) -> t.Iterator[str]:
yield f"class: {type(loader).__module__}.{type(loader).__name__}"
for key, value in sorted(loader.__dict__.items()):
if key.startswith("_"):
@@ -113,7 +121,17 @@ def _dump_loader_info(loader) -> t.Generator:
yield f"{key}: {value!r}"
-def explain_template_loading_attempts(app: Flask, template, attempts) -> None:
+def explain_template_loading_attempts(
+ app: App,
+ template: str,
+ attempts: list[
+ tuple[
+ BaseLoader,
+ Scaffold,
+ tuple[str, str | None, t.Callable[[], bool] | None] | None,
+ ]
+ ],
+) -> None:
"""This should help developers understand what failed"""
info = [f"Locating template {template!r}:"]
total_found = 0
@@ -122,7 +140,7 @@ def explain_template_loading_attempts(app: Flask, template, attempts) -> None:
blueprint = request_ctx.request.blueprint
for idx, (loader, srcobj, triple) in enumerate(attempts):
- if isinstance(srcobj, Flask):
+ if isinstance(srcobj, App):
src_info = f"application {srcobj.import_name!r}"
elif isinstance(srcobj, Blueprint):
src_info = f"blueprint {srcobj.name!r} ({srcobj.import_name})"
diff --git a/venv/Lib/site-packages/flask/globals.py b/venv/Lib/site-packages/flask/globals.py
index e9cd4ac..e2c410c 100644
--- a/venv/Lib/site-packages/flask/globals.py
+++ b/venv/Lib/site-packages/flask/globals.py
@@ -14,25 +14,6 @@ if t.TYPE_CHECKING: # pragma: no cover
from .wrappers import Request
-class _FakeStack:
- def __init__(self, name: str, cv: ContextVar[t.Any]) -> None:
- self.name = name
- self.cv = cv
-
- @property
- def top(self) -> t.Any | None:
- import warnings
-
- warnings.warn(
- f"'_{self.name}_ctx_stack' is deprecated and will be removed in Flask 2.4."
- f" Use 'g' to store data, or '{self.name}_ctx' to access the current"
- " context.",
- DeprecationWarning,
- stacklevel=2,
- )
- return self.cv.get(None)
-
-
_no_app_msg = """\
Working outside of application context.
@@ -41,7 +22,6 @@ the current application. To solve this, set up an application context
with app.app_context(). See the documentation for more information.\
"""
_cv_app: ContextVar[AppContext] = ContextVar("flask.app_ctx")
-__app_ctx_stack = _FakeStack("app", _cv_app)
app_ctx: AppContext = LocalProxy( # type: ignore[assignment]
_cv_app, unbound_message=_no_app_msg
)
@@ -60,7 +40,6 @@ an active HTTP request. Consult the documentation on testing for
information about how to avoid this problem.\
"""
_cv_request: ContextVar[RequestContext] = ContextVar("flask.request_ctx")
-__request_ctx_stack = _FakeStack("request", _cv_request)
request_ctx: RequestContext = LocalProxy( # type: ignore[assignment]
_cv_request, unbound_message=_no_req_msg
)
@@ -70,27 +49,3 @@ request: Request = LocalProxy( # type: ignore[assignment]
session: SessionMixin = LocalProxy( # type: ignore[assignment]
_cv_request, "session", unbound_message=_no_req_msg
)
-
-
-def __getattr__(name: str) -> t.Any:
- if name == "_app_ctx_stack":
- import warnings
-
- warnings.warn(
- "'_app_ctx_stack' is deprecated and will be removed in Flask 2.4.",
- DeprecationWarning,
- stacklevel=2,
- )
- return __app_ctx_stack
-
- if name == "_request_ctx_stack":
- import warnings
-
- warnings.warn(
- "'_request_ctx_stack' is deprecated and will be removed in Flask 2.4.",
- DeprecationWarning,
- stacklevel=2,
- )
- return __request_ctx_stack
-
- raise AttributeError(name)
diff --git a/venv/Lib/site-packages/flask/helpers.py b/venv/Lib/site-packages/flask/helpers.py
index 284c369..359a842 100644
--- a/venv/Lib/site-packages/flask/helpers.py
+++ b/venv/Lib/site-packages/flask/helpers.py
@@ -2,18 +2,16 @@ from __future__ import annotations
import importlib.util
import os
-import socket
import sys
import typing as t
-import warnings
from datetime import datetime
from functools import lru_cache
from functools import update_wrapper
-from threading import RLock
import werkzeug.utils
from werkzeug.exceptions import abort as _wz_abort
from werkzeug.utils import redirect as _wz_redirect
+from werkzeug.wrappers import Response as BaseResponse
from .globals import _cv_request
from .globals import current_app
@@ -23,7 +21,6 @@ from .globals import session
from .signals import message_flashed
if t.TYPE_CHECKING: # pragma: no cover
- from werkzeug.wrappers import Response as BaseResponse
from .wrappers import Response
@@ -51,9 +48,7 @@ def get_load_dotenv(default: bool = True) -> bool:
def stream_with_context(
- generator_or_function: (
- t.Iterator[t.AnyStr] | t.Callable[..., t.Iterator[t.AnyStr]]
- )
+ generator_or_function: t.Iterator[t.AnyStr] | t.Callable[..., t.Iterator[t.AnyStr]],
) -> t.Iterator[t.AnyStr]:
"""Request contexts disappear when the response is started on the server.
This is done for efficiency reasons and to make it less likely to encounter
@@ -89,16 +84,16 @@ def stream_with_context(
.. versionadded:: 0.9
"""
try:
- gen = iter(generator_or_function) # type: ignore
+ gen = iter(generator_or_function) # type: ignore[arg-type]
except TypeError:
def decorator(*args: t.Any, **kwargs: t.Any) -> t.Any:
- gen = generator_or_function(*args, **kwargs) # type: ignore
+ gen = generator_or_function(*args, **kwargs) # type: ignore[operator]
return stream_with_context(gen)
- return update_wrapper(decorator, generator_or_function) # type: ignore
+ return update_wrapper(decorator, generator_or_function) # type: ignore[arg-type]
- def generator() -> t.Generator:
+ def generator() -> t.Iterator[t.AnyStr | None]:
ctx = _cv_request.get(None)
if ctx is None:
raise RuntimeError(
@@ -126,7 +121,7 @@ def stream_with_context(
# real generator is executed.
wrapped_g = generator()
next(wrapped_g)
- return wrapped_g
+ return wrapped_g # type: ignore[return-value]
def make_response(*args: t.Any) -> Response:
@@ -175,7 +170,7 @@ def make_response(*args: t.Any) -> Response:
return current_app.response_class()
if len(args) == 1:
args = args[0]
- return current_app.make_response(args) # type: ignore
+ return current_app.make_response(args)
def url_for(
@@ -391,7 +386,7 @@ def _prepare_send_file_kwargs(**kwargs: t.Any) -> dict[str, t.Any]:
def send_file(
- path_or_file: os.PathLike | str | t.BinaryIO,
+ path_or_file: os.PathLike[t.AnyStr] | str | t.BinaryIO,
mimetype: str | None = None,
as_attachment: bool = False,
download_name: str | None = None,
@@ -492,7 +487,7 @@ def send_file(
.. versionchanged:: 0.7
MIME guessing and etag support for file-like objects was
- deprecated because it was unreliable. Pass a filename if you are
+ removed because it was unreliable. Pass a filename if you are
able to, otherwise attach an etag yourself.
.. versionchanged:: 0.5
@@ -517,8 +512,8 @@ def send_file(
def send_from_directory(
- directory: os.PathLike | str,
- path: os.PathLike | str,
+ directory: os.PathLike[str] | str,
+ path: os.PathLike[str] | str,
**kwargs: t.Any,
) -> Response:
"""Send a file from within a directory using :func:`send_file`.
@@ -613,82 +608,7 @@ def get_root_path(import_name: str) -> str:
)
# filepath is import_name.py for a module, or __init__.py for a package.
- return os.path.dirname(os.path.abspath(filepath))
-
-
-class locked_cached_property(werkzeug.utils.cached_property):
- """A :func:`property` that is only evaluated once. Like
- :class:`werkzeug.utils.cached_property` except access uses a lock
- for thread safety.
-
- .. deprecated:: 2.3
- Will be removed in Flask 2.4. Use a lock inside the decorated function if
- locking is needed.
-
- .. versionchanged:: 2.0
- Inherits from Werkzeug's ``cached_property`` (and ``property``).
- """
-
- def __init__(
- self,
- fget: t.Callable[[t.Any], t.Any],
- name: str | None = None,
- doc: str | None = None,
- ) -> None:
- import warnings
-
- warnings.warn(
- "'locked_cached_property' is deprecated and will be removed in Flask 2.4."
- " Use a lock inside the decorated function if locking is needed.",
- DeprecationWarning,
- stacklevel=2,
- )
- super().__init__(fget, name=name, doc=doc)
- self.lock = RLock()
-
- def __get__(self, obj: object, type: type = None) -> t.Any: # type: ignore
- if obj is None:
- return self
-
- with self.lock:
- return super().__get__(obj, type=type)
-
- def __set__(self, obj: object, value: t.Any) -> None:
- with self.lock:
- super().__set__(obj, value)
-
- def __delete__(self, obj: object) -> None:
- with self.lock:
- super().__delete__(obj)
-
-
-def is_ip(value: str) -> bool:
- """Determine if the given string is an IP address.
-
- :param value: value to check
- :type value: str
-
- :return: True if string is an IP address
- :rtype: bool
-
- .. deprecated:: 2.3
- Will be removed in Flask 2.4.
- """
- warnings.warn(
- "The 'is_ip' function is deprecated and will be removed in Flask 2.4.",
- DeprecationWarning,
- stacklevel=2,
- )
-
- for family in (socket.AF_INET, socket.AF_INET6):
- try:
- socket.inet_pton(family, value)
- except OSError:
- pass
- else:
- return True
-
- return False
+ return os.path.dirname(os.path.abspath(filepath)) # type: ignore[no-any-return]
@lru_cache(maxsize=None)
diff --git a/venv/Lib/site-packages/flask/json/__init__.py b/venv/Lib/site-packages/flask/json/__init__.py
index f15296f..c0941d0 100644
--- a/venv/Lib/site-packages/flask/json/__init__.py
+++ b/venv/Lib/site-packages/flask/json/__init__.py
@@ -167,4 +167,4 @@ def jsonify(*args: t.Any, **kwargs: t.Any) -> Response:
.. versionadded:: 0.2
"""
- return current_app.json.response(*args, **kwargs)
+ return current_app.json.response(*args, **kwargs) # type: ignore[return-value]
diff --git a/venv/Lib/site-packages/flask/json/__pycache__/__init__.cpython-310.pyc b/venv/Lib/site-packages/flask/json/__pycache__/__init__.cpython-310.pyc
index acc0475..bdeb7c9 100644
Binary files a/venv/Lib/site-packages/flask/json/__pycache__/__init__.cpython-310.pyc and b/venv/Lib/site-packages/flask/json/__pycache__/__init__.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/flask/json/__pycache__/provider.cpython-310.pyc b/venv/Lib/site-packages/flask/json/__pycache__/provider.cpython-310.pyc
index 22b2156..3ba9936 100644
Binary files a/venv/Lib/site-packages/flask/json/__pycache__/provider.cpython-310.pyc and b/venv/Lib/site-packages/flask/json/__pycache__/provider.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/flask/json/__pycache__/tag.cpython-310.pyc b/venv/Lib/site-packages/flask/json/__pycache__/tag.cpython-310.pyc
index 47be684..f5b36fa 100644
Binary files a/venv/Lib/site-packages/flask/json/__pycache__/tag.cpython-310.pyc and b/venv/Lib/site-packages/flask/json/__pycache__/tag.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/flask/json/provider.py b/venv/Lib/site-packages/flask/json/provider.py
index 0edd3d5..f9b2e8f 100644
--- a/venv/Lib/site-packages/flask/json/provider.py
+++ b/venv/Lib/site-packages/flask/json/provider.py
@@ -11,8 +11,9 @@ from datetime import date
from werkzeug.http import http_date
if t.TYPE_CHECKING: # pragma: no cover
- from ..app import Flask
- from ..wrappers import Response
+ from werkzeug.sansio.response import Response
+
+ from ..sansio.app import App
class JSONProvider:
@@ -34,8 +35,8 @@ class JSONProvider:
.. versionadded:: 2.2
"""
- def __init__(self, app: Flask) -> None:
- self._app = weakref.proxy(app)
+ def __init__(self, app: App) -> None:
+ self._app: App = weakref.proxy(app)
def dumps(self, obj: t.Any, **kwargs: t.Any) -> str:
"""Serialize data as JSON.
@@ -134,9 +135,7 @@ class DefaultJSONProvider(JSONProvider):
method) will call the ``__html__`` method to get a string.
"""
- default: t.Callable[[t.Any], t.Any] = staticmethod(
- _default
- ) # type: ignore[assignment]
+ default: t.Callable[[t.Any], t.Any] = staticmethod(_default) # type: ignore[assignment]
"""Apply this function to any object that :meth:`json.dumps` does
not know how to serialize. It should return a valid JSON type or
raise a ``TypeError``.
diff --git a/venv/Lib/site-packages/flask/json/tag.py b/venv/Lib/site-packages/flask/json/tag.py
index 91cc441..2bb986b 100644
--- a/venv/Lib/site-packages/flask/json/tag.py
+++ b/venv/Lib/site-packages/flask/json/tag.py
@@ -61,9 +61,9 @@ class JSONTag:
__slots__ = ("serializer",)
- #: The tag to mark the serialized object with. If ``None``, this tag is
+ #: The tag to mark the serialized object with. If empty, this tag is
#: only used as an intermediate step during tagging.
- key: str | None = None
+ key: str = ""
def __init__(self, serializer: TaggedJSONSerializer) -> None:
"""Create a tagger for the given serializer."""
@@ -83,7 +83,7 @@ class JSONTag:
will already be removed."""
raise NotImplementedError
- def tag(self, value: t.Any) -> t.Any:
+ def tag(self, value: t.Any) -> dict[str, t.Any]:
"""Convert the value to a valid JSON type and add the tag structure
around it."""
return {self.key: self.to_json(value)}
@@ -274,7 +274,7 @@ class TaggedJSONSerializer:
tag = tag_class(self)
key = tag.key
- if key is not None:
+ if key:
if not force and key in self.tags:
raise KeyError(f"Tag '{key}' is already registered.")
@@ -285,7 +285,7 @@ class TaggedJSONSerializer:
else:
self.order.insert(index, tag)
- def tag(self, value: t.Any) -> dict[str, t.Any]:
+ def tag(self, value: t.Any) -> t.Any:
"""Convert a value to a tagged representation if necessary."""
for tag in self.order:
if tag.check(value):
@@ -305,10 +305,22 @@ class TaggedJSONSerializer:
return self.tags[key].to_python(value[key])
+ def _untag_scan(self, value: t.Any) -> t.Any:
+ if isinstance(value, dict):
+ # untag each item recursively
+ value = {k: self._untag_scan(v) for k, v in value.items()}
+ # untag the dict itself
+ value = self.untag(value)
+ elif isinstance(value, list):
+ # untag each item recursively
+ value = [self._untag_scan(item) for item in value]
+
+ return value
+
def dumps(self, value: t.Any) -> str:
"""Tag the value and dump it to a compact JSON string."""
return dumps(self.tag(value), separators=(",", ":"))
def loads(self, value: str) -> t.Any:
"""Load data from a JSON string and deserialized any tagged objects."""
- return loads(value, object_hook=self.untag)
+ return self._untag_scan(loads(value))
diff --git a/venv/Lib/site-packages/flask/logging.py b/venv/Lib/site-packages/flask/logging.py
index 99f6be8..0cb8f43 100644
--- a/venv/Lib/site-packages/flask/logging.py
+++ b/venv/Lib/site-packages/flask/logging.py
@@ -9,7 +9,7 @@ from werkzeug.local import LocalProxy
from .globals import request
if t.TYPE_CHECKING: # pragma: no cover
- from .app import Flask
+ from .sansio.app import App
@LocalProxy
@@ -22,7 +22,10 @@ def wsgi_errors_stream() -> t.TextIO:
can't import this directly, you can refer to it as
``ext://flask.logging.wsgi_errors_stream``.
"""
- return request.environ["wsgi.errors"] if request else sys.stderr
+ if request:
+ return request.environ["wsgi.errors"] # type: ignore[no-any-return]
+
+ return sys.stderr
def has_level_handler(logger: logging.Logger) -> bool:
@@ -52,7 +55,7 @@ default_handler.setFormatter(
)
-def create_logger(app: Flask) -> logging.Logger:
+def create_logger(app: App) -> logging.Logger:
"""Get the Flask app's logger and configure it if needed.
The logger name will be the same as
diff --git a/venv/Lib/site-packages/flask/scaffold.py b/venv/Lib/site-packages/flask/scaffold.py
deleted file mode 100644
index d15b873..0000000
--- a/venv/Lib/site-packages/flask/scaffold.py
+++ /dev/null
@@ -1,873 +0,0 @@
-from __future__ import annotations
-
-import importlib.util
-import os
-import pathlib
-import sys
-import typing as t
-from collections import defaultdict
-from datetime import timedelta
-from functools import update_wrapper
-
-from jinja2 import FileSystemLoader
-from werkzeug.exceptions import default_exceptions
-from werkzeug.exceptions import HTTPException
-from werkzeug.utils import cached_property
-
-from . import typing as ft
-from .cli import AppGroup
-from .globals import current_app
-from .helpers import get_root_path
-from .helpers import send_from_directory
-from .templating import _default_template_ctx_processor
-
-if t.TYPE_CHECKING: # pragma: no cover
- from .wrappers import Response
-
-# a singleton sentinel value for parameter defaults
-_sentinel = object()
-
-F = t.TypeVar("F", bound=t.Callable[..., t.Any])
-T_after_request = t.TypeVar("T_after_request", bound=ft.AfterRequestCallable)
-T_before_request = t.TypeVar("T_before_request", bound=ft.BeforeRequestCallable)
-T_error_handler = t.TypeVar("T_error_handler", bound=ft.ErrorHandlerCallable)
-T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable)
-T_template_context_processor = t.TypeVar(
- "T_template_context_processor", bound=ft.TemplateContextProcessorCallable
-)
-T_url_defaults = t.TypeVar("T_url_defaults", bound=ft.URLDefaultCallable)
-T_url_value_preprocessor = t.TypeVar(
- "T_url_value_preprocessor", bound=ft.URLValuePreprocessorCallable
-)
-T_route = t.TypeVar("T_route", bound=ft.RouteCallable)
-
-
-def setupmethod(f: F) -> F:
- f_name = f.__name__
-
- def wrapper_func(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
- self._check_setup_finished(f_name)
- return f(self, *args, **kwargs)
-
- return t.cast(F, update_wrapper(wrapper_func, f))
-
-
-class Scaffold:
- """Common behavior shared between :class:`~flask.Flask` and
- :class:`~flask.blueprints.Blueprint`.
-
- :param import_name: The import name of the module where this object
- is defined. Usually :attr:`__name__` should be used.
- :param static_folder: Path to a folder of static files to serve.
- If this is set, a static route will be added.
- :param static_url_path: URL prefix for the static route.
- :param template_folder: Path to a folder containing template files.
- for rendering. If this is set, a Jinja loader will be added.
- :param root_path: The path that static, template, and resource files
- are relative to. Typically not set, it is discovered based on
- the ``import_name``.
-
- .. versionadded:: 2.0
- """
-
- name: str
- _static_folder: str | None = None
- _static_url_path: str | None = None
-
- def __init__(
- self,
- import_name: str,
- static_folder: str | os.PathLike | None = None,
- static_url_path: str | None = None,
- template_folder: str | os.PathLike | None = None,
- root_path: str | None = None,
- ):
- #: The name of the package or module that this object belongs
- #: to. Do not change this once it is set by the constructor.
- self.import_name = import_name
-
- self.static_folder = static_folder # type: ignore
- self.static_url_path = static_url_path
-
- #: The path to the templates folder, relative to
- #: :attr:`root_path`, to add to the template loader. ``None`` if
- #: templates should not be added.
- self.template_folder = template_folder
-
- if root_path is None:
- root_path = get_root_path(self.import_name)
-
- #: Absolute path to the package on the filesystem. Used to look
- #: up resources contained in the package.
- self.root_path = root_path
-
- #: The Click command group for registering CLI commands for this
- #: object. The commands are available from the ``flask`` command
- #: once the application has been discovered and blueprints have
- #: been registered.
- self.cli = AppGroup()
-
- #: A dictionary mapping endpoint names to view functions.
- #:
- #: To register a view function, use the :meth:`route` decorator.
- #:
- #: This data structure is internal. It should not be modified
- #: directly and its format may change at any time.
- self.view_functions: dict[str, t.Callable] = {}
-
- #: A data structure of registered error handlers, in the format
- #: ``{scope: {code: {class: handler}}}``. The ``scope`` key is
- #: the name of a blueprint the handlers are active for, or
- #: ``None`` for all requests. The ``code`` key is the HTTP
- #: status code for ``HTTPException``, or ``None`` for
- #: other exceptions. The innermost dictionary maps exception
- #: classes to handler functions.
- #:
- #: To register an error handler, use the :meth:`errorhandler`
- #: decorator.
- #:
- #: This data structure is internal. It should not be modified
- #: directly and its format may change at any time.
- self.error_handler_spec: dict[
- ft.AppOrBlueprintKey,
- dict[int | None, dict[type[Exception], ft.ErrorHandlerCallable]],
- ] = defaultdict(lambda: defaultdict(dict))
-
- #: A data structure of functions to call at the beginning of
- #: each request, in the format ``{scope: [functions]}``. The
- #: ``scope`` key is the name of a blueprint the functions are
- #: active for, or ``None`` for all requests.
- #:
- #: To register a function, use the :meth:`before_request`
- #: decorator.
- #:
- #: This data structure is internal. It should not be modified
- #: directly and its format may change at any time.
- self.before_request_funcs: dict[
- ft.AppOrBlueprintKey, list[ft.BeforeRequestCallable]
- ] = defaultdict(list)
-
- #: A data structure of functions to call at the end of each
- #: request, in the format ``{scope: [functions]}``. The
- #: ``scope`` key is the name of a blueprint the functions are
- #: active for, or ``None`` for all requests.
- #:
- #: To register a function, use the :meth:`after_request`
- #: decorator.
- #:
- #: This data structure is internal. It should not be modified
- #: directly and its format may change at any time.
- self.after_request_funcs: dict[
- ft.AppOrBlueprintKey, list[ft.AfterRequestCallable]
- ] = defaultdict(list)
-
- #: A data structure of functions to call at the end of each
- #: request even if an exception is raised, in the format
- #: ``{scope: [functions]}``. The ``scope`` key is the name of a
- #: blueprint the functions are active for, or ``None`` for all
- #: requests.
- #:
- #: To register a function, use the :meth:`teardown_request`
- #: decorator.
- #:
- #: This data structure is internal. It should not be modified
- #: directly and its format may change at any time.
- self.teardown_request_funcs: dict[
- ft.AppOrBlueprintKey, list[ft.TeardownCallable]
- ] = defaultdict(list)
-
- #: A data structure of functions to call to pass extra context
- #: values when rendering templates, in the format
- #: ``{scope: [functions]}``. The ``scope`` key is the name of a
- #: blueprint the functions are active for, or ``None`` for all
- #: requests.
- #:
- #: To register a function, use the :meth:`context_processor`
- #: decorator.
- #:
- #: This data structure is internal. It should not be modified
- #: directly and its format may change at any time.
- self.template_context_processors: dict[
- ft.AppOrBlueprintKey, list[ft.TemplateContextProcessorCallable]
- ] = defaultdict(list, {None: [_default_template_ctx_processor]})
-
- #: A data structure of functions to call to modify the keyword
- #: arguments passed to the view function, in the format
- #: ``{scope: [functions]}``. The ``scope`` key is the name of a
- #: blueprint the functions are active for, or ``None`` for all
- #: requests.
- #:
- #: To register a function, use the
- #: :meth:`url_value_preprocessor` decorator.
- #:
- #: This data structure is internal. It should not be modified
- #: directly and its format may change at any time.
- self.url_value_preprocessors: dict[
- ft.AppOrBlueprintKey,
- list[ft.URLValuePreprocessorCallable],
- ] = defaultdict(list)
-
- #: A data structure of functions to call to modify the keyword
- #: arguments when generating URLs, in the format
- #: ``{scope: [functions]}``. The ``scope`` key is the name of a
- #: blueprint the functions are active for, or ``None`` for all
- #: requests.
- #:
- #: To register a function, use the :meth:`url_defaults`
- #: decorator.
- #:
- #: This data structure is internal. It should not be modified
- #: directly and its format may change at any time.
- self.url_default_functions: dict[
- ft.AppOrBlueprintKey, list[ft.URLDefaultCallable]
- ] = defaultdict(list)
-
- def __repr__(self) -> str:
- return f"<{type(self).__name__} {self.name!r}>"
-
- def _check_setup_finished(self, f_name: str) -> None:
- raise NotImplementedError
-
- @property
- def static_folder(self) -> str | None:
- """The absolute path to the configured static folder. ``None``
- if no static folder is set.
- """
- if self._static_folder is not None:
- return os.path.join(self.root_path, self._static_folder)
- else:
- return None
-
- @static_folder.setter
- def static_folder(self, value: str | os.PathLike | None) -> None:
- if value is not None:
- value = os.fspath(value).rstrip(r"\/")
-
- self._static_folder = value
-
- @property
- def has_static_folder(self) -> bool:
- """``True`` if :attr:`static_folder` is set.
-
- .. versionadded:: 0.5
- """
- return self.static_folder is not None
-
- @property
- def static_url_path(self) -> str | None:
- """The URL prefix that the static route will be accessible from.
-
- If it was not configured during init, it is derived from
- :attr:`static_folder`.
- """
- if self._static_url_path is not None:
- return self._static_url_path
-
- if self.static_folder is not None:
- basename = os.path.basename(self.static_folder)
- return f"/{basename}".rstrip("/")
-
- return None
-
- @static_url_path.setter
- def static_url_path(self, value: str | None) -> None:
- if value is not None:
- value = value.rstrip("/")
-
- self._static_url_path = value
-
- def get_send_file_max_age(self, filename: str | None) -> int | None:
- """Used by :func:`send_file` to determine the ``max_age`` cache
- value for a given file path if it wasn't passed.
-
- By default, this returns :data:`SEND_FILE_MAX_AGE_DEFAULT` from
- the configuration of :data:`~flask.current_app`. This defaults
- to ``None``, which tells the browser to use conditional requests
- instead of a timed cache, which is usually preferable.
-
- .. versionchanged:: 2.0
- The default configuration is ``None`` instead of 12 hours.
-
- .. versionadded:: 0.9
- """
- value = current_app.config["SEND_FILE_MAX_AGE_DEFAULT"]
-
- if value is None:
- return None
-
- if isinstance(value, timedelta):
- return int(value.total_seconds())
-
- return value
-
- def send_static_file(self, filename: str) -> Response:
- """The view function used to serve files from
- :attr:`static_folder`. A route is automatically registered for
- this view at :attr:`static_url_path` if :attr:`static_folder` is
- set.
-
- .. versionadded:: 0.5
- """
- if not self.has_static_folder:
- raise RuntimeError("'static_folder' must be set to serve static_files.")
-
- # send_file only knows to call get_send_file_max_age on the app,
- # call it here so it works for blueprints too.
- max_age = self.get_send_file_max_age(filename)
- return send_from_directory(
- t.cast(str, self.static_folder), filename, max_age=max_age
- )
-
- @cached_property
- def jinja_loader(self) -> FileSystemLoader | None:
- """The Jinja loader for this object's templates. By default this
- is a class :class:`jinja2.loaders.FileSystemLoader` to
- :attr:`template_folder` if it is set.
-
- .. versionadded:: 0.5
- """
- if self.template_folder is not None:
- return FileSystemLoader(os.path.join(self.root_path, self.template_folder))
- else:
- return None
-
- def open_resource(self, resource: str, mode: str = "rb") -> t.IO[t.AnyStr]:
- """Open a resource file relative to :attr:`root_path` for
- reading.
-
- For example, if the file ``schema.sql`` is next to the file
- ``app.py`` where the ``Flask`` app is defined, it can be opened
- with:
-
- .. code-block:: python
-
- with app.open_resource("schema.sql") as f:
- conn.executescript(f.read())
-
- :param resource: Path to the resource relative to
- :attr:`root_path`.
- :param mode: Open the file in this mode. Only reading is
- supported, valid values are "r" (or "rt") and "rb".
- """
- if mode not in {"r", "rt", "rb"}:
- raise ValueError("Resources can only be opened for reading.")
-
- return open(os.path.join(self.root_path, resource), mode)
-
- def _method_route(
- self,
- method: str,
- rule: str,
- options: dict,
- ) -> t.Callable[[T_route], T_route]:
- if "methods" in options:
- raise TypeError("Use the 'route' decorator to use the 'methods' argument.")
-
- return self.route(rule, methods=[method], **options)
-
- @setupmethod
- def get(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
- """Shortcut for :meth:`route` with ``methods=["GET"]``.
-
- .. versionadded:: 2.0
- """
- return self._method_route("GET", rule, options)
-
- @setupmethod
- def post(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
- """Shortcut for :meth:`route` with ``methods=["POST"]``.
-
- .. versionadded:: 2.0
- """
- return self._method_route("POST", rule, options)
-
- @setupmethod
- def put(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
- """Shortcut for :meth:`route` with ``methods=["PUT"]``.
-
- .. versionadded:: 2.0
- """
- return self._method_route("PUT", rule, options)
-
- @setupmethod
- def delete(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
- """Shortcut for :meth:`route` with ``methods=["DELETE"]``.
-
- .. versionadded:: 2.0
- """
- return self._method_route("DELETE", rule, options)
-
- @setupmethod
- def patch(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
- """Shortcut for :meth:`route` with ``methods=["PATCH"]``.
-
- .. versionadded:: 2.0
- """
- return self._method_route("PATCH", rule, options)
-
- @setupmethod
- def route(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
- """Decorate a view function to register it with the given URL
- rule and options. Calls :meth:`add_url_rule`, which has more
- details about the implementation.
-
- .. code-block:: python
-
- @app.route("/")
- def index():
- return "Hello, World!"
-
- See :ref:`url-route-registrations`.
-
- The endpoint name for the route defaults to the name of the view
- function if the ``endpoint`` parameter isn't passed.
-
- The ``methods`` parameter defaults to ``["GET"]``. ``HEAD`` and
- ``OPTIONS`` are added automatically.
-
- :param rule: The URL rule string.
- :param options: Extra options passed to the
- :class:`~werkzeug.routing.Rule` object.
- """
-
- def decorator(f: T_route) -> T_route:
- endpoint = options.pop("endpoint", None)
- self.add_url_rule(rule, endpoint, f, **options)
- return f
-
- return decorator
-
- @setupmethod
- def add_url_rule(
- self,
- rule: str,
- endpoint: str | None = None,
- view_func: ft.RouteCallable | None = None,
- provide_automatic_options: bool | None = None,
- **options: t.Any,
- ) -> None:
- """Register a rule for routing incoming requests and building
- URLs. The :meth:`route` decorator is a shortcut to call this
- with the ``view_func`` argument. These are equivalent:
-
- .. code-block:: python
-
- @app.route("/")
- def index():
- ...
-
- .. code-block:: python
-
- def index():
- ...
-
- app.add_url_rule("/", view_func=index)
-
- See :ref:`url-route-registrations`.
-
- The endpoint name for the route defaults to the name of the view
- function if the ``endpoint`` parameter isn't passed. An error
- will be raised if a function has already been registered for the
- endpoint.
-
- The ``methods`` parameter defaults to ``["GET"]``. ``HEAD`` is
- always added automatically, and ``OPTIONS`` is added
- automatically by default.
-
- ``view_func`` does not necessarily need to be passed, but if the
- rule should participate in routing an endpoint name must be
- associated with a view function at some point with the
- :meth:`endpoint` decorator.
-
- .. code-block:: python
-
- app.add_url_rule("/", endpoint="index")
-
- @app.endpoint("index")
- def index():
- ...
-
- If ``view_func`` has a ``required_methods`` attribute, those
- methods are added to the passed and automatic methods. If it
- has a ``provide_automatic_methods`` attribute, it is used as the
- default if the parameter is not passed.
-
- :param rule: The URL rule string.
- :param endpoint: The endpoint name to associate with the rule
- and view function. Used when routing and building URLs.
- Defaults to ``view_func.__name__``.
- :param view_func: The view function to associate with the
- endpoint name.
- :param provide_automatic_options: Add the ``OPTIONS`` method and
- respond to ``OPTIONS`` requests automatically.
- :param options: Extra options passed to the
- :class:`~werkzeug.routing.Rule` object.
- """
- raise NotImplementedError
-
- @setupmethod
- def endpoint(self, endpoint: str) -> t.Callable[[F], F]:
- """Decorate a view function to register it for the given
- endpoint. Used if a rule is added without a ``view_func`` with
- :meth:`add_url_rule`.
-
- .. code-block:: python
-
- app.add_url_rule("/ex", endpoint="example")
-
- @app.endpoint("example")
- def example():
- ...
-
- :param endpoint: The endpoint name to associate with the view
- function.
- """
-
- def decorator(f: F) -> F:
- self.view_functions[endpoint] = f
- return f
-
- return decorator
-
- @setupmethod
- def before_request(self, f: T_before_request) -> T_before_request:
- """Register a function to run before each request.
-
- For example, this can be used to open a database connection, or
- to load the logged in user from the session.
-
- .. code-block:: python
-
- @app.before_request
- def load_user():
- if "user_id" in session:
- g.user = db.session.get(session["user_id"])
-
- The function will be called without any arguments. If it returns
- a non-``None`` value, the value is handled as if it was the
- return value from the view, and further request handling is
- stopped.
-
- This is available on both app and blueprint objects. When used on an app, this
- executes before every request. When used on a blueprint, this executes before
- every request that the blueprint handles. To register with a blueprint and
- execute before every request, use :meth:`.Blueprint.before_app_request`.
- """
- self.before_request_funcs.setdefault(None, []).append(f)
- return f
-
- @setupmethod
- def after_request(self, f: T_after_request) -> T_after_request:
- """Register a function to run after each request to this object.
-
- The function is called with the response object, and must return
- a response object. This allows the functions to modify or
- replace the response before it is sent.
-
- If a function raises an exception, any remaining
- ``after_request`` functions will not be called. Therefore, this
- should not be used for actions that must execute, such as to
- close resources. Use :meth:`teardown_request` for that.
-
- This is available on both app and blueprint objects. When used on an app, this
- executes after every request. When used on a blueprint, this executes after
- every request that the blueprint handles. To register with a blueprint and
- execute after every request, use :meth:`.Blueprint.after_app_request`.
- """
- self.after_request_funcs.setdefault(None, []).append(f)
- return f
-
- @setupmethod
- def teardown_request(self, f: T_teardown) -> T_teardown:
- """Register a function to be called when the request context is
- popped. Typically this happens at the end of each request, but
- contexts may be pushed manually as well during testing.
-
- .. code-block:: python
-
- with app.test_request_context():
- ...
-
- When the ``with`` block exits (or ``ctx.pop()`` is called), the
- teardown functions are called just before the request context is
- made inactive.
-
- When a teardown function was called because of an unhandled
- exception it will be passed an error object. If an
- :meth:`errorhandler` is registered, it will handle the exception
- and the teardown will not receive it.
-
- Teardown functions must avoid raising exceptions. If they
- execute code that might fail they must surround that code with a
- ``try``/``except`` block and log any errors.
-
- The return values of teardown functions are ignored.
-
- This is available on both app and blueprint objects. When used on an app, this
- executes after every request. When used on a blueprint, this executes after
- every request that the blueprint handles. To register with a blueprint and
- execute after every request, use :meth:`.Blueprint.teardown_app_request`.
- """
- self.teardown_request_funcs.setdefault(None, []).append(f)
- return f
-
- @setupmethod
- def context_processor(
- self,
- f: T_template_context_processor,
- ) -> T_template_context_processor:
- """Registers a template context processor function. These functions run before
- rendering a template. The keys of the returned dict are added as variables
- available in the template.
-
- This is available on both app and blueprint objects. When used on an app, this
- is called for every rendered template. When used on a blueprint, this is called
- for templates rendered from the blueprint's views. To register with a blueprint
- and affect every template, use :meth:`.Blueprint.app_context_processor`.
- """
- self.template_context_processors[None].append(f)
- return f
-
- @setupmethod
- def url_value_preprocessor(
- self,
- f: T_url_value_preprocessor,
- ) -> T_url_value_preprocessor:
- """Register a URL value preprocessor function for all view
- functions in the application. These functions will be called before the
- :meth:`before_request` functions.
-
- The function can modify the values captured from the matched url before
- they are passed to the view. For example, this can be used to pop a
- common language code value and place it in ``g`` rather than pass it to
- every view.
-
- The function is passed the endpoint name and values dict. The return
- value is ignored.
-
- This is available on both app and blueprint objects. When used on an app, this
- is called for every request. When used on a blueprint, this is called for
- requests that the blueprint handles. To register with a blueprint and affect
- every request, use :meth:`.Blueprint.app_url_value_preprocessor`.
- """
- self.url_value_preprocessors[None].append(f)
- return f
-
- @setupmethod
- def url_defaults(self, f: T_url_defaults) -> T_url_defaults:
- """Callback function for URL defaults for all view functions of the
- application. It's called with the endpoint and values and should
- update the values passed in place.
-
- This is available on both app and blueprint objects. When used on an app, this
- is called for every request. When used on a blueprint, this is called for
- requests that the blueprint handles. To register with a blueprint and affect
- every request, use :meth:`.Blueprint.app_url_defaults`.
- """
- self.url_default_functions[None].append(f)
- return f
-
- @setupmethod
- def errorhandler(
- self, code_or_exception: type[Exception] | int
- ) -> t.Callable[[T_error_handler], T_error_handler]:
- """Register a function to handle errors by code or exception class.
-
- A decorator that is used to register a function given an
- error code. Example::
-
- @app.errorhandler(404)
- def page_not_found(error):
- return 'This page does not exist', 404
-
- You can also register handlers for arbitrary exceptions::
-
- @app.errorhandler(DatabaseError)
- def special_exception_handler(error):
- return 'Database connection failed', 500
-
- This is available on both app and blueprint objects. When used on an app, this
- can handle errors from every request. When used on a blueprint, this can handle
- errors from requests that the blueprint handles. To register with a blueprint
- and affect every request, use :meth:`.Blueprint.app_errorhandler`.
-
- .. versionadded:: 0.7
- Use :meth:`register_error_handler` instead of modifying
- :attr:`error_handler_spec` directly, for application wide error
- handlers.
-
- .. versionadded:: 0.7
- One can now additionally also register custom exception types
- that do not necessarily have to be a subclass of the
- :class:`~werkzeug.exceptions.HTTPException` class.
-
- :param code_or_exception: the code as integer for the handler, or
- an arbitrary exception
- """
-
- def decorator(f: T_error_handler) -> T_error_handler:
- self.register_error_handler(code_or_exception, f)
- return f
-
- return decorator
-
- @setupmethod
- def register_error_handler(
- self,
- code_or_exception: type[Exception] | int,
- f: ft.ErrorHandlerCallable,
- ) -> None:
- """Alternative error attach function to the :meth:`errorhandler`
- decorator that is more straightforward to use for non decorator
- usage.
-
- .. versionadded:: 0.7
- """
- exc_class, code = self._get_exc_class_and_code(code_or_exception)
- self.error_handler_spec[None][code][exc_class] = f
-
- @staticmethod
- def _get_exc_class_and_code(
- exc_class_or_code: type[Exception] | int,
- ) -> tuple[type[Exception], int | None]:
- """Get the exception class being handled. For HTTP status codes
- or ``HTTPException`` subclasses, return both the exception and
- status code.
-
- :param exc_class_or_code: Any exception class, or an HTTP status
- code as an integer.
- """
- exc_class: type[Exception]
-
- if isinstance(exc_class_or_code, int):
- try:
- exc_class = default_exceptions[exc_class_or_code]
- except KeyError:
- raise ValueError(
- f"'{exc_class_or_code}' is not a recognized HTTP"
- " error code. Use a subclass of HTTPException with"
- " that code instead."
- ) from None
- else:
- exc_class = exc_class_or_code
-
- if isinstance(exc_class, Exception):
- raise TypeError(
- f"{exc_class!r} is an instance, not a class. Handlers"
- " can only be registered for Exception classes or HTTP"
- " error codes."
- )
-
- if not issubclass(exc_class, Exception):
- raise ValueError(
- f"'{exc_class.__name__}' is not a subclass of Exception."
- " Handlers can only be registered for Exception classes"
- " or HTTP error codes."
- )
-
- if issubclass(exc_class, HTTPException):
- return exc_class, exc_class.code
- else:
- return exc_class, None
-
-
-def _endpoint_from_view_func(view_func: t.Callable) -> str:
- """Internal helper that returns the default endpoint for a given
- function. This always is the function name.
- """
- assert view_func is not None, "expected view func if endpoint is not provided."
- return view_func.__name__
-
-
-def _path_is_relative_to(path: pathlib.PurePath, base: str) -> bool:
- # Path.is_relative_to doesn't exist until Python 3.9
- try:
- path.relative_to(base)
- return True
- except ValueError:
- return False
-
-
-def _find_package_path(import_name):
- """Find the path that contains the package or module."""
- root_mod_name, _, _ = import_name.partition(".")
-
- try:
- root_spec = importlib.util.find_spec(root_mod_name)
-
- if root_spec is None:
- raise ValueError("not found")
- except (ImportError, ValueError):
- # ImportError: the machinery told us it does not exist
- # ValueError:
- # - the module name was invalid
- # - the module name is __main__
- # - we raised `ValueError` due to `root_spec` being `None`
- return os.getcwd()
-
- if root_spec.origin in {"namespace", None}:
- # namespace package
- package_spec = importlib.util.find_spec(import_name)
-
- if package_spec is not None and package_spec.submodule_search_locations:
- # Pick the path in the namespace that contains the submodule.
- package_path = pathlib.Path(
- os.path.commonpath(package_spec.submodule_search_locations)
- )
- search_location = next(
- location
- for location in root_spec.submodule_search_locations
- if _path_is_relative_to(package_path, location)
- )
- else:
- # Pick the first path.
- search_location = root_spec.submodule_search_locations[0]
-
- return os.path.dirname(search_location)
- elif root_spec.submodule_search_locations:
- # package with __init__.py
- return os.path.dirname(os.path.dirname(root_spec.origin))
- else:
- # module
- return os.path.dirname(root_spec.origin)
-
-
-def find_package(import_name: str):
- """Find the prefix that a package is installed under, and the path
- that it would be imported from.
-
- The prefix is the directory containing the standard directory
- hierarchy (lib, bin, etc.). If the package is not installed to the
- system (:attr:`sys.prefix`) or a virtualenv (``site-packages``),
- ``None`` is returned.
-
- The path is the entry in :attr:`sys.path` that contains the package
- for import. If the package is not installed, it's assumed that the
- package was imported from the current working directory.
- """
- package_path = _find_package_path(import_name)
- py_prefix = os.path.abspath(sys.prefix)
-
- # installed to the system
- if _path_is_relative_to(pathlib.PurePath(package_path), py_prefix):
- return py_prefix, package_path
-
- site_parent, site_folder = os.path.split(package_path)
-
- # installed to a virtualenv
- if site_folder.lower() == "site-packages":
- parent, folder = os.path.split(site_parent)
-
- # Windows (prefix/lib/site-packages)
- if folder.lower() == "lib":
- return parent, package_path
-
- # Unix (prefix/lib/pythonX.Y/site-packages)
- if os.path.basename(parent).lower() == "lib":
- return os.path.dirname(parent), package_path
-
- # something else (prefix/site-packages)
- return site_parent, package_path
-
- # not installed
- return None, package_path
diff --git a/venv/Lib/site-packages/flask/sessions.py b/venv/Lib/site-packages/flask/sessions.py
index e5650d6..bb753eb 100644
--- a/venv/Lib/site-packages/flask/sessions.py
+++ b/venv/Lib/site-packages/flask/sessions.py
@@ -13,11 +13,15 @@ from werkzeug.datastructures import CallbackDict
from .json.tag import TaggedJSONSerializer
if t.TYPE_CHECKING: # pragma: no cover
+ import typing_extensions as te
+
from .app import Flask
- from .wrappers import Request, Response
+ from .wrappers import Request
+ from .wrappers import Response
-class SessionMixin(MutableMapping):
+# TODO generic when Python > 3.8
+class SessionMixin(MutableMapping): # type: ignore[type-arg]
"""Expands a basic dictionary with session attributes."""
@property
@@ -45,7 +49,8 @@ class SessionMixin(MutableMapping):
accessed = True
-class SecureCookieSession(CallbackDict, SessionMixin):
+# TODO generic when Python > 3.8
+class SecureCookieSession(CallbackDict, SessionMixin): # type: ignore[type-arg]
"""Base class for sessions based on signed cookies.
This session backend will set the :attr:`modified` and
@@ -68,7 +73,7 @@ class SecureCookieSession(CallbackDict, SessionMixin):
accessed = False
def __init__(self, initial: t.Any = None) -> None:
- def on_update(self) -> None:
+ def on_update(self: te.Self) -> None:
self.modified = True
self.accessed = True
@@ -177,7 +182,7 @@ class SessionInterface:
def get_cookie_name(self, app: Flask) -> str:
"""The name of the session cookie. Uses``app.config["SESSION_COOKIE_NAME"]``."""
- return app.config["SESSION_COOKIE_NAME"]
+ return app.config["SESSION_COOKIE_NAME"] # type: ignore[no-any-return]
def get_cookie_domain(self, app: Flask) -> str | None:
"""The value of the ``Domain`` parameter on the session cookie. If not set,
@@ -189,8 +194,7 @@ class SessionInterface:
.. versionchanged:: 2.3
Not set by default, does not fall back to ``SERVER_NAME``.
"""
- rv = app.config["SESSION_COOKIE_DOMAIN"]
- return rv if rv else None
+ return app.config["SESSION_COOKIE_DOMAIN"] # type: ignore[no-any-return]
def get_cookie_path(self, app: Flask) -> str:
"""Returns the path for which the cookie should be valid. The
@@ -198,27 +202,27 @@ class SessionInterface:
config var if it's set, and falls back to ``APPLICATION_ROOT`` or
uses ``/`` if it's ``None``.
"""
- return app.config["SESSION_COOKIE_PATH"] or app.config["APPLICATION_ROOT"]
+ return app.config["SESSION_COOKIE_PATH"] or app.config["APPLICATION_ROOT"] # type: ignore[no-any-return]
def get_cookie_httponly(self, app: Flask) -> bool:
"""Returns True if the session cookie should be httponly. This
currently just returns the value of the ``SESSION_COOKIE_HTTPONLY``
config var.
"""
- return app.config["SESSION_COOKIE_HTTPONLY"]
+ return app.config["SESSION_COOKIE_HTTPONLY"] # type: ignore[no-any-return]
def get_cookie_secure(self, app: Flask) -> bool:
"""Returns True if the cookie should be secure. This currently
just returns the value of the ``SESSION_COOKIE_SECURE`` setting.
"""
- return app.config["SESSION_COOKIE_SECURE"]
+ return app.config["SESSION_COOKIE_SECURE"] # type: ignore[no-any-return]
- def get_cookie_samesite(self, app: Flask) -> str:
+ def get_cookie_samesite(self, app: Flask) -> str | None:
"""Return ``'Strict'`` or ``'Lax'`` if the cookie should use the
``SameSite`` attribute. This currently just returns the value of
the :data:`SESSION_COOKIE_SAMESITE` setting.
"""
- return app.config["SESSION_COOKIE_SAMESITE"]
+ return app.config["SESSION_COOKIE_SAMESITE"] # type: ignore[no-any-return]
def get_expiration_time(self, app: Flask, session: SessionMixin) -> datetime | None:
"""A helper method that returns an expiration date for the session
diff --git a/venv/Lib/site-packages/flask/signals.py b/venv/Lib/site-packages/flask/signals.py
index d79f21f..444fda9 100644
--- a/venv/Lib/site-packages/flask/signals.py
+++ b/venv/Lib/site-packages/flask/signals.py
@@ -1,8 +1,5 @@
from __future__ import annotations
-import typing as t
-import warnings
-
from blinker import Namespace
# This namespace is only for signals provided by Flask itself.
@@ -18,16 +15,3 @@ appcontext_tearing_down = _signals.signal("appcontext-tearing-down")
appcontext_pushed = _signals.signal("appcontext-pushed")
appcontext_popped = _signals.signal("appcontext-popped")
message_flashed = _signals.signal("message-flashed")
-
-
-def __getattr__(name: str) -> t.Any:
- if name == "signals_available":
- warnings.warn(
- "The 'signals_available' attribute is deprecated and will be removed in"
- " Flask 2.4. Signals are always available.",
- DeprecationWarning,
- stacklevel=2,
- )
- return True
-
- raise AttributeError(name)
diff --git a/venv/Lib/site-packages/flask/templating.py b/venv/Lib/site-packages/flask/templating.py
index 769108f..618a3b3 100644
--- a/venv/Lib/site-packages/flask/templating.py
+++ b/venv/Lib/site-packages/flask/templating.py
@@ -17,7 +17,8 @@ from .signals import template_rendered
if t.TYPE_CHECKING: # pragma: no cover
from .app import Flask
- from .scaffold import Scaffold
+ from .sansio.app import App
+ from .sansio.scaffold import Scaffold
def _default_template_ctx_processor() -> dict[str, t.Any]:
@@ -41,7 +42,7 @@ class Environment(BaseEnvironment):
name of the blueprint to referenced templates if necessary.
"""
- def __init__(self, app: Flask, **options: t.Any) -> None:
+ def __init__(self, app: App, **options: t.Any) -> None:
if "loader" not in options:
options["loader"] = app.create_global_jinja_loader()
BaseEnvironment.__init__(self, **options)
@@ -53,19 +54,19 @@ class DispatchingJinjaLoader(BaseLoader):
the blueprint folders.
"""
- def __init__(self, app: Flask) -> None:
+ def __init__(self, app: App) -> None:
self.app = app
- def get_source( # type: ignore
- self, environment: Environment, template: str
- ) -> tuple[str, str | None, t.Callable | None]:
+ def get_source(
+ self, environment: BaseEnvironment, template: str
+ ) -> tuple[str, str | None, t.Callable[[], bool] | None]:
if self.app.config["EXPLAIN_TEMPLATE_LOADING"]:
return self._get_source_explained(environment, template)
return self._get_source_fast(environment, template)
def _get_source_explained(
- self, environment: Environment, template: str
- ) -> tuple[str, str | None, t.Callable | None]:
+ self, environment: BaseEnvironment, template: str
+ ) -> tuple[str, str | None, t.Callable[[], bool] | None]:
attempts = []
rv: tuple[str, str | None, t.Callable[[], bool] | None] | None
trv: None | (tuple[str, str | None, t.Callable[[], bool] | None]) = None
@@ -88,8 +89,8 @@ class DispatchingJinjaLoader(BaseLoader):
raise TemplateNotFound(template)
def _get_source_fast(
- self, environment: Environment, template: str
- ) -> tuple[str, str | None, t.Callable | None]:
+ self, environment: BaseEnvironment, template: str
+ ) -> tuple[str, str | None, t.Callable[[], bool] | None]:
for _srcobj, loader in self._iter_loaders(template):
try:
return loader.get_source(environment, template)
@@ -97,9 +98,7 @@ class DispatchingJinjaLoader(BaseLoader):
continue
raise TemplateNotFound(template)
- def _iter_loaders(
- self, template: str
- ) -> t.Generator[tuple[Scaffold, BaseLoader], None, None]:
+ def _iter_loaders(self, template: str) -> t.Iterator[tuple[Scaffold, BaseLoader]]:
loader = self.app.jinja_loader
if loader is not None:
yield self.app, loader
diff --git a/venv/Lib/site-packages/flask/testing.py b/venv/Lib/site-packages/flask/testing.py
index 69aa785..a27b7c8 100644
--- a/venv/Lib/site-packages/flask/testing.py
+++ b/venv/Lib/site-packages/flask/testing.py
@@ -17,6 +17,7 @@ from .cli import ScriptInfo
from .sessions import SessionMixin
if t.TYPE_CHECKING: # pragma: no cover
+ from _typeshed.wsgi import WSGIEnvironment
from werkzeug.test import TestResponse
from .app import Flask
@@ -134,7 +135,7 @@ class FlaskClient(Client):
@contextmanager
def session_transaction(
self, *args: t.Any, **kwargs: t.Any
- ) -> t.Generator[SessionMixin, None, None]:
+ ) -> t.Iterator[SessionMixin]:
"""When used in combination with a ``with`` statement this opens a
session transaction. This can be used to modify the session that
the test client uses. Once the ``with`` block is left the session is
@@ -181,7 +182,7 @@ class FlaskClient(Client):
resp.headers.getlist("Set-Cookie"),
)
- def _copy_environ(self, other):
+ def _copy_environ(self, other: WSGIEnvironment) -> WSGIEnvironment:
out = {**self.environ_base, **other}
if self.preserve_context:
@@ -189,7 +190,9 @@ class FlaskClient(Client):
return out
- def _request_from_builder_args(self, args, kwargs):
+ def _request_from_builder_args(
+ self, args: tuple[t.Any, ...], kwargs: dict[str, t.Any]
+ ) -> BaseRequest:
kwargs["environ_base"] = self._copy_environ(kwargs.get("environ_base", {}))
builder = EnvironBuilder(self.application, *args, **kwargs)
@@ -210,7 +213,7 @@ class FlaskClient(Client):
):
if isinstance(args[0], werkzeug.test.EnvironBuilder):
builder = copy(args[0])
- builder.environ_base = self._copy_environ(builder.environ_base or {})
+ builder.environ_base = self._copy_environ(builder.environ_base or {}) # type: ignore[arg-type]
request = builder.get_request()
elif isinstance(args[0], dict):
request = EnvironBuilder.from_environ(
@@ -287,7 +290,7 @@ class FlaskCliRunner(CliRunner):
:return: a :class:`~click.testing.Result` object.
"""
if cli is None:
- cli = self.app.cli # type: ignore
+ cli = self.app.cli
if "obj" not in kwargs:
kwargs["obj"] = ScriptInfo(create_app=lambda: self.app)
diff --git a/venv/Lib/site-packages/flask/typing.py b/venv/Lib/site-packages/flask/typing.py
index 50aef7f..cf6d4ae 100644
--- a/venv/Lib/site-packages/flask/typing.py
+++ b/venv/Lib/site-packages/flask/typing.py
@@ -5,7 +5,7 @@ import typing as t
if t.TYPE_CHECKING: # pragma: no cover
from _typeshed.wsgi import WSGIApplication # noqa: F401
from werkzeug.datastructures import Headers # noqa: F401
- from werkzeug.wrappers import Response # noqa: F401
+ from werkzeug.sansio.response import Response # noqa: F401
# The possible types that are directly convertible or are a Response object.
ResponseValue = t.Union[
@@ -61,12 +61,17 @@ TeardownCallable = t.Union[
t.Callable[[t.Optional[BaseException]], None],
t.Callable[[t.Optional[BaseException]], t.Awaitable[None]],
]
-TemplateContextProcessorCallable = t.Callable[[], t.Dict[str, t.Any]]
+TemplateContextProcessorCallable = t.Union[
+ t.Callable[[], t.Dict[str, t.Any]],
+ t.Callable[[], t.Awaitable[t.Dict[str, t.Any]]],
+]
TemplateFilterCallable = t.Callable[..., t.Any]
TemplateGlobalCallable = t.Callable[..., t.Any]
TemplateTestCallable = t.Callable[..., bool]
-URLDefaultCallable = t.Callable[[str, dict], None]
-URLValuePreprocessorCallable = t.Callable[[t.Optional[str], t.Optional[dict]], None]
+URLDefaultCallable = t.Callable[[str, t.Dict[str, t.Any]], None]
+URLValuePreprocessorCallable = t.Callable[
+ [t.Optional[str], t.Optional[t.Dict[str, t.Any]]], None
+]
# This should take Exception, but that either breaks typing the argument
# with a specific exception, or decorating multiple times with different
@@ -74,7 +79,10 @@ URLValuePreprocessorCallable = t.Callable[[t.Optional[str], t.Optional[dict]], N
# https://github.com/pallets/flask/issues/4095
# https://github.com/pallets/flask/issues/4295
# https://github.com/pallets/flask/issues/4297
-ErrorHandlerCallable = t.Callable[[t.Any], ResponseReturnValue]
+ErrorHandlerCallable = t.Union[
+ t.Callable[[t.Any], ResponseReturnValue],
+ t.Callable[[t.Any], t.Awaitable[ResponseReturnValue]],
+]
RouteCallable = t.Union[
t.Callable[..., ResponseReturnValue],
diff --git a/venv/Lib/site-packages/flask/views.py b/venv/Lib/site-packages/flask/views.py
index c7a2b62..794fdc0 100644
--- a/venv/Lib/site-packages/flask/views.py
+++ b/venv/Lib/site-packages/flask/views.py
@@ -6,6 +6,7 @@ from . import typing as ft
from .globals import current_app
from .globals import request
+F = t.TypeVar("F", bound=t.Callable[..., t.Any])
http_method_funcs = frozenset(
["get", "post", "head", "options", "delete", "put", "trace", "patch"]
@@ -60,7 +61,7 @@ class View:
#: decorator.
#:
#: .. versionadded:: 0.8
- decorators: t.ClassVar[list[t.Callable]] = []
+ decorators: t.ClassVar[list[t.Callable[[F], F]]] = []
#: Create a new instance of this view class for every request by
#: default. If a view subclass sets this to ``False``, the same
@@ -106,13 +107,13 @@ class View:
self = view.view_class( # type: ignore[attr-defined]
*class_args, **class_kwargs
)
- return current_app.ensure_sync(self.dispatch_request)(**kwargs)
+ return current_app.ensure_sync(self.dispatch_request)(**kwargs) # type: ignore[no-any-return]
else:
self = cls(*class_args, **class_kwargs)
def view(**kwargs: t.Any) -> ft.ResponseReturnValue:
- return current_app.ensure_sync(self.dispatch_request)(**kwargs)
+ return current_app.ensure_sync(self.dispatch_request)(**kwargs) # type: ignore[no-any-return]
if cls.decorators:
view.__name__ = name
@@ -187,4 +188,4 @@ class MethodView(View):
meth = getattr(self, "get", None)
assert meth is not None, f"Unimplemented method {request.method!r}"
- return current_app.ensure_sync(meth)(**kwargs)
+ return current_app.ensure_sync(meth)(**kwargs) # type: ignore[no-any-return]
diff --git a/venv/Lib/site-packages/flask/wrappers.py b/venv/Lib/site-packages/flask/wrappers.py
index ef7aa38..c1eca80 100644
--- a/venv/Lib/site-packages/flask/wrappers.py
+++ b/venv/Lib/site-packages/flask/wrappers.py
@@ -3,6 +3,7 @@ from __future__ import annotations
import typing as t
from werkzeug.exceptions import BadRequest
+from werkzeug.exceptions import HTTPException
from werkzeug.wrappers import Request as RequestBase
from werkzeug.wrappers import Response as ResponseBase
@@ -49,13 +50,13 @@ class Request(RequestBase):
#: raised / was raised as part of the request handling. This is
#: usually a :exc:`~werkzeug.exceptions.NotFound` exception or
#: something similar.
- routing_exception: Exception | None = None
+ routing_exception: HTTPException | None = None
@property
- def max_content_length(self) -> int | None: # type: ignore
+ def max_content_length(self) -> int | None: # type: ignore[override]
"""Read-only view of the ``MAX_CONTENT_LENGTH`` config key."""
if current_app:
- return current_app.config["MAX_CONTENT_LENGTH"]
+ return current_app.config["MAX_CONTENT_LENGTH"] # type: ignore[no-any-return]
else:
return None
@@ -167,7 +168,7 @@ class Response(ResponseBase):
Werkzeug's docs.
"""
if current_app:
- return current_app.config["MAX_COOKIE_SIZE"]
+ return current_app.config["MAX_COOKIE_SIZE"] # type: ignore[no-any-return]
# return Werkzeug's default when not in an app context
return super().max_cookie_size
diff --git a/venv/Lib/site-packages/python_dotenv-1.0.0.dist-info/INSTALLER b/venv/Lib/site-packages/python_dotenv-1.0.0.dist-info/INSTALLER
deleted file mode 100644
index a1b589e..0000000
--- a/venv/Lib/site-packages/python_dotenv-1.0.0.dist-info/INSTALLER
+++ /dev/null
@@ -1 +0,0 @@
-pip
diff --git a/venv/Lib/site-packages/python_dotenv-1.0.0.dist-info/LICENSE b/venv/Lib/site-packages/python_dotenv-1.0.0.dist-info/LICENSE
deleted file mode 100644
index 3a97119..0000000
--- a/venv/Lib/site-packages/python_dotenv-1.0.0.dist-info/LICENSE
+++ /dev/null
@@ -1,27 +0,0 @@
-Copyright (c) 2014, Saurabh Kumar (python-dotenv), 2013, Ted Tieken (django-dotenv-rw), 2013, Jacob Kaplan-Moss (django-dotenv)
-
-Redistribution and use in source and binary forms, with or without modification,
-are permitted provided that the following conditions are met:
-
-- Redistributions of source code must retain the above copyright notice,
- this list of conditions and the following disclaimer.
-
-- Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
-
-- Neither the name of django-dotenv nor the names of its contributors
- may be used to endorse or promote products derived from this software
- without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
-CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
-PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/venv/Lib/site-packages/python_dotenv-1.0.0.dist-info/METADATA b/venv/Lib/site-packages/python_dotenv-1.0.0.dist-info/METADATA
deleted file mode 100644
index a85c4f9..0000000
--- a/venv/Lib/site-packages/python_dotenv-1.0.0.dist-info/METADATA
+++ /dev/null
@@ -1,667 +0,0 @@
-Metadata-Version: 2.1
-Name: python-dotenv
-Version: 1.0.0
-Summary: Read key-value pairs from a .env file and set them as environment variables
-Home-page: https://github.com/theskumar/python-dotenv
-Author: Saurabh Kumar
-Author-email: me+github@saurabh-kumar.com
-License: BSD-3-Clause
-Keywords: environment variables,deployments,settings,env,dotenv,configurations,python
-Classifier: Development Status :: 5 - Production/Stable
-Classifier: Programming Language :: Python
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.8
-Classifier: Programming Language :: Python :: 3.9
-Classifier: Programming Language :: Python :: 3.10
-Classifier: Programming Language :: Python :: 3.11
-Classifier: Programming Language :: Python :: 3.12
-Classifier: Programming Language :: Python :: Implementation :: PyPy
-Classifier: Intended Audience :: Developers
-Classifier: Intended Audience :: System Administrators
-Classifier: License :: OSI Approved :: BSD License
-Classifier: Operating System :: OS Independent
-Classifier: Topic :: System :: Systems Administration
-Classifier: Topic :: Utilities
-Classifier: Environment :: Web Environment
-Requires-Python: >=3.8
-Description-Content-Type: text/markdown
-License-File: LICENSE
-Provides-Extra: cli
-Requires-Dist: click (>=5.0) ; extra == 'cli'
-
-# python-dotenv
-
-[![Build Status][build_status_badge]][build_status_link]
-[![PyPI version][pypi_badge]][pypi_link]
-
-Python-dotenv reads key-value pairs from a `.env` file and can set them as environment
-variables. It helps in the development of applications following the
-[12-factor](http://12factor.net/) principles.
-
-- [Getting Started](#getting-started)
-- [Other Use Cases](#other-use-cases)
- * [Load configuration without altering the environment](#load-configuration-without-altering-the-environment)
- * [Parse configuration as a stream](#parse-configuration-as-a-stream)
- * [Load .env files in IPython](#load-env-files-in-ipython)
-- [Command-line Interface](#command-line-interface)
-- [File format](#file-format)
- * [Multiline values](#multiline-values)
- * [Variable expansion](#variable-expansion)
-- [Related Projects](#related-projects)
-- [Acknowledgements](#acknowledgements)
-
-## Getting Started
-
-```shell
-pip install python-dotenv
-```
-
-If your application takes its configuration from environment variables, like a 12-factor
-application, launching it in development is not very practical because you have to set
-those environment variables yourself.
-
-To help you with that, you can add Python-dotenv to your application to make it load the
-configuration from a `.env` file when it is present (e.g. in development) while remaining
-configurable via the environment:
-
-```python
-from dotenv import load_dotenv
-
-load_dotenv() # take environment variables from .env.
-
-# Code of your application, which uses environment variables (e.g. from `os.environ` or
-# `os.getenv`) as if they came from the actual environment.
-```
-
-By default, `load_dotenv` doesn't override existing environment variables.
-
-To configure the development environment, add a `.env` in the root directory of your
-project:
-
-```
-.
-├── .env
-└── foo.py
-```
-
-The syntax of `.env` files supported by python-dotenv is similar to that of Bash:
-
-```bash
-# Development settings
-DOMAIN=example.org
-ADMIN_EMAIL=admin@${DOMAIN}
-ROOT_URL=${DOMAIN}/app
-```
-
-If you use variables in values, ensure they are surrounded with `{` and `}`, like
-`${DOMAIN}`, as bare variables such as `$DOMAIN` are not expanded.
-
-You will probably want to add `.env` to your `.gitignore`, especially if it contains
-secrets like a password.
-
-See the section "File format" below for more information about what you can write in a
-`.env` file.
-
-## Other Use Cases
-
-### Load configuration without altering the environment
-
-The function `dotenv_values` works more or less the same way as `load_dotenv`, except it
-doesn't touch the environment, it just returns a `dict` with the values parsed from the
-`.env` file.
-
-```python
-from dotenv import dotenv_values
-
-config = dotenv_values(".env") # config = {"USER": "foo", "EMAIL": "foo@example.org"}
-```
-
-This notably enables advanced configuration management:
-
-```python
-import os
-from dotenv import dotenv_values
-
-config = {
- **dotenv_values(".env.shared"), # load shared development variables
- **dotenv_values(".env.secret"), # load sensitive variables
- **os.environ, # override loaded values with environment variables
-}
-```
-
-### Parse configuration as a stream
-
-`load_dotenv` and `dotenv_values` accept [streams][python_streams] via their `stream`
-argument. It is thus possible to load the variables from sources other than the
-filesystem (e.g. the network).
-
-```python
-from io import StringIO
-
-from dotenv import load_dotenv
-
-config = StringIO("USER=foo\nEMAIL=foo@example.org")
-load_dotenv(stream=config)
-```
-
-### Load .env files in IPython
-
-You can use dotenv in IPython. By default, it will use `find_dotenv` to search for a
-`.env` file:
-
-```python
-%load_ext dotenv
-%dotenv
-```
-
-You can also specify a path:
-
-```python
-%dotenv relative/or/absolute/path/to/.env
-```
-
-Optional flags:
-
-- `-o` to override existing variables.
-- `-v` for increased verbosity.
-
-## Command-line Interface
-
-A CLI interface `dotenv` is also included, which helps you manipulate the `.env` file
-without manually opening it.
-
-```shell
-$ pip install "python-dotenv[cli]"
-$ dotenv set USER foo
-$ dotenv set EMAIL foo@example.org
-$ dotenv list
-USER=foo
-EMAIL=foo@example.org
-$ dotenv list --format=json
-{
- "USER": "foo",
- "EMAIL": "foo@example.org"
-}
-$ dotenv run -- python foo.py
-```
-
-Run `dotenv --help` for more information about the options and subcommands.
-
-## File format
-
-The format is not formally specified and still improves over time. That being said,
-`.env` files should mostly look like Bash files.
-
-Keys can be unquoted or single-quoted. Values can be unquoted, single- or double-quoted.
-Spaces before and after keys, equal signs, and values are ignored. Values can be followed
-by a comment. Lines can start with the `export` directive, which does not affect their
-interpretation.
-
-Allowed escape sequences:
-
-- in single-quoted values: `\\`, `\'`
-- in double-quoted values: `\\`, `\'`, `\"`, `\a`, `\b`, `\f`, `\n`, `\r`, `\t`, `\v`
-
-### Multiline values
-
-It is possible for single- or double-quoted values to span multiple lines. The following
-examples are equivalent:
-
-```bash
-FOO="first line
-second line"
-```
-
-```bash
-FOO="first line\nsecond line"
-```
-
-### Variable without a value
-
-A variable can have no value:
-
-```bash
-FOO
-```
-
-It results in `dotenv_values` associating that variable name with the value `None` (e.g.
-`{"FOO": None}`. `load_dotenv`, on the other hand, simply ignores such variables.
-
-This shouldn't be confused with `FOO=`, in which case the variable is associated with the
-empty string.
-
-### Variable expansion
-
-Python-dotenv can interpolate variables using POSIX variable expansion.
-
-With `load_dotenv(override=True)` or `dotenv_values()`, the value of a variable is the
-first of the values defined in the following list:
-
-- Value of that variable in the `.env` file.
-- Value of that variable in the environment.
-- Default value, if provided.
-- Empty string.
-
-With `load_dotenv(override=False)`, the value of a variable is the first of the values
-defined in the following list:
-
-- Value of that variable in the environment.
-- Value of that variable in the `.env` file.
-- Default value, if provided.
-- Empty string.
-
-## Related Projects
-
-- [Honcho](https://github.com/nickstenning/honcho) - For managing
- Procfile-based applications.
-- [django-dotenv](https://github.com/jpadilla/django-dotenv)
-- [django-environ](https://github.com/joke2k/django-environ)
-- [django-environ-2](https://github.com/sergeyklay/django-environ-2)
-- [django-configuration](https://github.com/jezdez/django-configurations)
-- [dump-env](https://github.com/sobolevn/dump-env)
-- [environs](https://github.com/sloria/environs)
-- [dynaconf](https://github.com/rochacbruno/dynaconf)
-- [parse_it](https://github.com/naorlivne/parse_it)
-- [python-decouple](https://github.com/HBNetwork/python-decouple)
-
-## Acknowledgements
-
-This project is currently maintained by [Saurabh Kumar](https://saurabh-kumar.com) and
-[Bertrand Bonnefoy-Claudet](https://github.com/bbc2) and would not have been possible
-without the support of these [awesome
-people](https://github.com/theskumar/python-dotenv/graphs/contributors).
-
-[build_status_badge]: https://github.com/theskumar/python-dotenv/actions/workflows/test.yml/badge.svg
-[build_status_link]: https://github.com/theskumar/python-dotenv/actions/workflows/test.yml
-[pypi_badge]: https://badge.fury.io/py/python-dotenv.svg
-[pypi_link]: http://badge.fury.io/py/python-dotenv
-[python_streams]: https://docs.python.org/3/library/io.html
-
-# Changelog
-
-All notable changes to this project will be documented in this file.
-
-The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this
-project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-
-## [1.0.0]
-
-**Fixed**
-
-* Drop support for python 3.7, add python 3.12-dev (#449 by [@theskumar])
-* Handle situations where the cwd does not exist. (#446 by [@jctanner])
-
-## [0.21.1] - 2022-01-21
-
-**Added**
-
-* Use Python 3.11 non-beta in CI (#438 by [@bbc2])
-* Modernize variables code (#434 by [@Nougat-Waffle])
-* Modernize main.py and parser.py code (#435 by [@Nougat-Waffle])
-* Improve conciseness of cli.py and __init__.py (#439 by [@Nougat-Waffle])
-* Improve error message for `get` and `list` commands when env file can't be opened (#441 by [@bbc2])
-* Updated License to align with BSD OSI template (#433 by [@lsmith77])
-
-
-**Fixed**
-
-* Fix Out-of-scope error when "dest" variable is undefined (#413 by [@theGOTOguy])
-* Fix IPython test warning about deprecated `magic` (#440 by [@bbc2])
-* Fix type hint for dotenv_path var, add StrPath alias (#432 by [@eaf])
-
-## [0.21.0] - 2022-09-03
-
-**Added**
-
-* CLI: add support for invocations via 'python -m'. (#395 by [@theskumar])
-* `load_dotenv` function now returns `False`. (#388 by [@larsks])
-* CLI: add --format= option to list command. (#407 by [@sammck])
-
-**Fixed**
-
-* Drop Python 3.5 and 3.6 and upgrade GA (#393 by [@eggplants])
-* Use `open` instead of `io.open`. (#389 by [@rabinadk1])
-* Improve documentation for variables without a value (#390 by [@bbc2])
-* Add `parse_it` to Related Projects (#410 by [@naorlivne])
-* Update README.md (#415 by [@harveer07])
-* Improve documentation with direct use of MkDocs (#398 by [@bbc2])
-
-## [0.20.0] - 2022-03-24
-
-**Added**
-
-- Add `encoding` (`Optional[str]`) parameter to `get_key`, `set_key` and `unset_key`.
- (#379 by [@bbc2])
-
-**Fixed**
-
-- Use dict to specify the `entry_points` parameter of `setuptools.setup` (#376 by
- [@mgorny]).
-- Don't build universal wheels (#387 by [@bbc2]).
-
-## [0.19.2] - 2021-11-11
-
-**Fixed**
-
-- In `set_key`, add missing newline character before new entry if necessary. (#361 by
- [@bbc2])
-
-## [0.19.1] - 2021-08-09
-
-**Added**
-
-- Add support for Python 3.10. (#359 by [@theskumar])
-
-## [0.19.0] - 2021-07-24
-
-**Changed**
-
-- Require Python 3.5 or a later version. Python 2 and 3.4 are no longer supported. (#341
- by [@bbc2]).
-
-**Added**
-
-- The `dotenv_path` argument of `set_key` and `unset_key` now has a type of `Union[str,
- os.PathLike]` instead of just `os.PathLike` (#347 by [@bbc2]).
-- The `stream` argument of `load_dotenv` and `dotenv_values` can now be a text stream
- (`IO[str]`), which includes values like `io.StringIO("foo")` and `open("file.env",
- "r")` (#348 by [@bbc2]).
-
-## [0.18.0] - 2021-06-20
-
-**Changed**
-
-- Raise `ValueError` if `quote_mode` isn't one of `always`, `auto` or `never` in
- `set_key` (#330 by [@bbc2]).
-- When writing a value to a .env file with `set_key` or `dotenv set ` (#330
- by [@bbc2]):
- - Use single quotes instead of double quotes.
- - Don't strip surrounding quotes.
- - In `auto` mode, don't add quotes if the value is only made of alphanumeric characters
- (as determined by `string.isalnum`).
-
-## [0.17.1] - 2021-04-29
-
-**Fixed**
-
-- Fixed tests for build environments relying on `PYTHONPATH` (#318 by [@befeleme]).
-
-## [0.17.0] - 2021-04-02
-
-**Changed**
-
-- Make `dotenv get ` only show the value, not `key=value` (#313 by [@bbc2]).
-
-**Added**
-
-- Add `--override`/`--no-override` option to `dotenv run` (#312 by [@zueve] and [@bbc2]).
-
-## [0.16.0] - 2021-03-27
-
-**Changed**
-
-- The default value of the `encoding` parameter for `load_dotenv` and `dotenv_values` is
- now `"utf-8"` instead of `None` (#306 by [@bbc2]).
-- Fix resolution order in variable expansion with `override=False` (#287 by [@bbc2]).
-
-## [0.15.0] - 2020-10-28
-
-**Added**
-
-- Add `--export` option to `set` to make it prepend the binding with `export` (#270 by
- [@jadutter]).
-
-**Changed**
-
-- Make `set` command create the `.env` file in the current directory if no `.env` file was
- found (#270 by [@jadutter]).
-
-**Fixed**
-
-- Fix potentially empty expanded value for duplicate key (#260 by [@bbc2]).
-- Fix import error on Python 3.5.0 and 3.5.1 (#267 by [@gongqingkui]).
-- Fix parsing of unquoted values containing several adjacent space or tab characters
- (#277 by [@bbc2], review by [@x-yuri]).
-
-## [0.14.0] - 2020-07-03
-
-**Changed**
-
-- Privilege definition in file over the environment in variable expansion (#256 by
- [@elbehery95]).
-
-**Fixed**
-
-- Improve error message for when file isn't found (#245 by [@snobu]).
-- Use HTTPS URL in package meta data (#251 by [@ekohl]).
-
-## [0.13.0] - 2020-04-16
-
-**Added**
-
-- Add support for a Bash-like default value in variable expansion (#248 by [@bbc2]).
-
-## [0.12.0] - 2020-02-28
-
-**Changed**
-
-- Use current working directory to find `.env` when bundled by PyInstaller (#213 by
- [@gergelyk]).
-
-**Fixed**
-
-- Fix escaping of quoted values written by `set_key` (#236 by [@bbc2]).
-- Fix `dotenv run` crashing on environment variables without values (#237 by [@yannham]).
-- Remove warning when last line is empty (#238 by [@bbc2]).
-
-## [0.11.0] - 2020-02-07
-
-**Added**
-
-- Add `interpolate` argument to `load_dotenv` and `dotenv_values` to disable interpolation
- (#232 by [@ulyssessouza]).
-
-**Changed**
-
-- Use logging instead of warnings (#231 by [@bbc2]).
-
-**Fixed**
-
-- Fix installation in non-UTF-8 environments (#225 by [@altendky]).
-- Fix PyPI classifiers (#228 by [@bbc2]).
-
-## [0.10.5] - 2020-01-19
-
-**Fixed**
-
-- Fix handling of malformed lines and lines without a value (#222 by [@bbc2]):
- - Don't print warning when key has no value.
- - Reject more malformed lines (e.g. "A: B", "a='b',c").
-- Fix handling of lines with just a comment (#224 by [@bbc2]).
-
-## [0.10.4] - 2020-01-17
-
-**Added**
-
-- Make typing optional (#179 by [@techalchemy]).
-- Print a warning on malformed line (#211 by [@bbc2]).
-- Support keys without a value (#220 by [@ulyssessouza]).
-
-## 0.10.3
-
-- Improve interactive mode detection ([@andrewsmith])([#183]).
-- Refactor parser to fix parsing inconsistencies ([@bbc2])([#170]).
- - Interpret escapes as control characters only in double-quoted strings.
- - Interpret `#` as start of comment only if preceded by whitespace.
-
-## 0.10.2
-
-- Add type hints and expose them to users ([@qnighy])([#172])
-- `load_dotenv` and `dotenv_values` now accept an `encoding` parameter, defaults to `None`
- ([@theskumar])([@earlbread])([#161])
-- Fix `str`/`unicode` inconsistency in Python 2: values are always `str` now. ([@bbc2])([#121])
-- Fix Unicode error in Python 2, introduced in 0.10.0. ([@bbc2])([#176])
-
-## 0.10.1
-- Fix parsing of variable without a value ([@asyncee])([@bbc2])([#158])
-
-## 0.10.0
-
-- Add support for UTF-8 in unquoted values ([@bbc2])([#148])
-- Add support for trailing comments ([@bbc2])([#148])
-- Add backslashes support in values ([@bbc2])([#148])
-- Add support for newlines in values ([@bbc2])([#148])
-- Force environment variables to str with Python2 on Windows ([@greyli])
-- Drop Python 3.3 support ([@greyli])
-- Fix stderr/-out/-in redirection ([@venthur])
-
-
-## 0.9.0
-
-- Add `--version` parameter to cli ([@venthur])
-- Enable loading from current directory ([@cjauvin])
-- Add 'dotenv run' command for calling arbitrary shell script with .env ([@venthur])
-
-## 0.8.1
-
-- Add tests for docs ([@Flimm])
-- Make 'cli' support optional. Use `pip install python-dotenv[cli]`. ([@theskumar])
-
-## 0.8.0
-
-- `set_key` and `unset_key` only modified the affected file instead of
- parsing and re-writing file, this causes comments and other file
- entact as it is.
-- Add support for `export` prefix in the line.
-- Internal refractoring ([@theskumar])
-- Allow `load_dotenv` and `dotenv_values` to work with `StringIO())` ([@alanjds])([@theskumar])([#78])
-
-## 0.7.1
-
-- Remove hard dependency on iPython ([@theskumar])
-
-## 0.7.0
-
-- Add support to override system environment variable via .env.
- ([@milonimrod](https://github.com/milonimrod))
- ([\#63](https://github.com/theskumar/python-dotenv/issues/63))
-- Disable ".env not found" warning by default
- ([@maxkoryukov](https://github.com/maxkoryukov))
- ([\#57](https://github.com/theskumar/python-dotenv/issues/57))
-
-## 0.6.5
-
-- Add support for special characters `\`.
- ([@pjona](https://github.com/pjona))
- ([\#60](https://github.com/theskumar/python-dotenv/issues/60))
-
-## 0.6.4
-
-- Fix issue with single quotes ([@Flimm])
- ([\#52](https://github.com/theskumar/python-dotenv/issues/52))
-
-## 0.6.3
-
-- Handle unicode exception in setup.py
- ([\#46](https://github.com/theskumar/python-dotenv/issues/46))
-
-## 0.6.2
-
-- Fix dotenv list command ([@ticosax](https://github.com/ticosax))
-- Add iPython Support
- ([@tillahoffmann](https://github.com/tillahoffmann))
-
-## 0.6.0
-
-- Drop support for Python 2.6
-- Handle escaped characters and newlines in quoted values. (Thanks
- [@iameugenejo](https://github.com/iameugenejo))
-- Remove any spaces around unquoted key/value. (Thanks
- [@paulochf](https://github.com/paulochf))
-- Added POSIX variable expansion. (Thanks
- [@hugochinchilla](https://github.com/hugochinchilla))
-
-## 0.5.1
-
-- Fix find\_dotenv - it now start search from the file where this
- function is called from.
-
-## 0.5.0
-
-- Add `find_dotenv` method that will try to find a `.env` file.
- (Thanks [@isms](https://github.com/isms))
-
-## 0.4.0
-
-- cli: Added `-q/--quote` option to control the behaviour of quotes
- around values in `.env`. (Thanks
- [@hugochinchilla](https://github.com/hugochinchilla)).
-- Improved test coverage.
-
-[#78]: https://github.com/theskumar/python-dotenv/issues/78
-[#121]: https://github.com/theskumar/python-dotenv/issues/121
-[#148]: https://github.com/theskumar/python-dotenv/issues/148
-[#158]: https://github.com/theskumar/python-dotenv/issues/158
-[#170]: https://github.com/theskumar/python-dotenv/issues/170
-[#172]: https://github.com/theskumar/python-dotenv/issues/172
-[#176]: https://github.com/theskumar/python-dotenv/issues/176
-[#183]: https://github.com/theskumar/python-dotenv/issues/183
-[#359]: https://github.com/theskumar/python-dotenv/issues/359
-
-[@alanjds]: https://github.com/alanjds
-[@altendky]: https://github.com/altendky
-[@andrewsmith]: https://github.com/andrewsmith
-[@asyncee]: https://github.com/asyncee
-[@bbc2]: https://github.com/bbc2
-[@befeleme]: https://github.com/befeleme
-[@cjauvin]: https://github.com/cjauvin
-[@eaf]: https://github.com/eaf
-[@earlbread]: https://github.com/earlbread
-[@eggplants]: https://github.com/@eggplants
-[@ekohl]: https://github.com/ekohl
-[@elbehery95]: https://github.com/elbehery95
-[@Flimm]: https://github.com/Flimm
-[@gergelyk]: https://github.com/gergelyk
-[@gongqingkui]: https://github.com/gongqingkui
-[@greyli]: https://github.com/greyli
-[@harveer07]: https://github.com/@harveer07
-[@jadutter]: https://github.com/jadutter
-[@jctanner]: https://github.com/jctanner
-[@larsks]: https://github.com/@larsks
-[@lsmith77]: https://github.com/lsmith77
-[@mgorny]: https://github.com/mgorny
-[@naorlivne]: https://github.com/@naorlivne
-[@Nougat-Waffle]: https://github.com/Nougat-Waffle
-[@qnighy]: https://github.com/qnighy
-[@rabinadk1]: https://github.com/@rabinadk1
-[@sammck]: https://github.com/@sammck
-[@snobu]: https://github.com/snobu
-[@techalchemy]: https://github.com/techalchemy
-[@theGOTOguy]: https://github.com/theGOTOguy
-[@theskumar]: https://github.com/theskumar
-[@ulyssessouza]: https://github.com/ulyssessouza
-[@venthur]: https://github.com/venthur
-[@x-yuri]: https://github.com/x-yuri
-[@yannham]: https://github.com/yannham
-[@zueve]: https://github.com/zueve
-
-
-[Unreleased]: https://github.com/theskumar/python-dotenv/compare/v1.0.0...HEAD
-[1.0.0]: https://github.com/theskumar/python-dotenv/compare/v0.21.0...v1.0.0
-[0.21.1]: https://github.com/theskumar/python-dotenv/compare/v0.21.0...v0.21.1
-[0.21.0]: https://github.com/theskumar/python-dotenv/compare/v0.20.0...v0.21.0
-[0.20.0]: https://github.com/theskumar/python-dotenv/compare/v0.19.2...v0.20.0
-[0.19.2]: https://github.com/theskumar/python-dotenv/compare/v0.19.1...v0.19.2
-[0.19.1]: https://github.com/theskumar/python-dotenv/compare/v0.19.0...v0.19.1
-[0.19.0]: https://github.com/theskumar/python-dotenv/compare/v0.18.0...v0.19.0
-[0.18.0]: https://github.com/theskumar/python-dotenv/compare/v0.17.1...v0.18.0
-[0.17.1]: https://github.com/theskumar/python-dotenv/compare/v0.17.0...v0.17.1
-[0.17.0]: https://github.com/theskumar/python-dotenv/compare/v0.16.0...v0.17.0
-[0.16.0]: https://github.com/theskumar/python-dotenv/compare/v0.15.0...v0.16.0
-[0.15.0]: https://github.com/theskumar/python-dotenv/compare/v0.14.0...v0.15.0
-[0.14.0]: https://github.com/theskumar/python-dotenv/compare/v0.13.0...v0.14.0
-[0.13.0]: https://github.com/theskumar/python-dotenv/compare/v0.12.0...v0.13.0
-[0.12.0]: https://github.com/theskumar/python-dotenv/compare/v0.11.0...v0.12.0
-[0.11.0]: https://github.com/theskumar/python-dotenv/compare/v0.10.5...v0.11.0
-[0.10.5]: https://github.com/theskumar/python-dotenv/compare/v0.10.4...v0.10.5
-[0.10.4]: https://github.com/theskumar/python-dotenv/compare/v0.10.3...v0.10.4
diff --git a/venv/Lib/site-packages/python_dotenv-1.0.0.dist-info/RECORD b/venv/Lib/site-packages/python_dotenv-1.0.0.dist-info/RECORD
deleted file mode 100644
index 9e0d4b2..0000000
--- a/venv/Lib/site-packages/python_dotenv-1.0.0.dist-info/RECORD
+++ /dev/null
@@ -1,26 +0,0 @@
-../../Scripts/dotenv.exe,sha256=jHYKxcvUoEg8RKYCiB_ufWqcZf7w-n2_fs2qoqFnCVg,108407
-dotenv/__init__.py,sha256=WBU5SfSiKAhS3hzu17ykNuuwbuwyDCX91Szv4vUeOuM,1292
-dotenv/__main__.py,sha256=N0RhLG7nHIqtlJHwwepIo-zbJPNx9sewCCRGY528h_4,129
-dotenv/__pycache__/__init__.cpython-310.pyc,,
-dotenv/__pycache__/__main__.cpython-310.pyc,,
-dotenv/__pycache__/cli.cpython-310.pyc,,
-dotenv/__pycache__/ipython.cpython-310.pyc,,
-dotenv/__pycache__/main.cpython-310.pyc,,
-dotenv/__pycache__/parser.cpython-310.pyc,,
-dotenv/__pycache__/variables.cpython-310.pyc,,
-dotenv/__pycache__/version.cpython-310.pyc,,
-dotenv/cli.py,sha256=_ttQuR9Yl4k1PT53ByISkDjJ3kO_N_LzIDZzZ95uXEk,5809
-dotenv/ipython.py,sha256=avI6aez_RxnBptYgchIquF2TSgKI-GOhY3ppiu3VuWE,1303
-dotenv/main.py,sha256=6j1GW8kNeZAooqffdajLne_dq_TJLi2Mk63DRNJjXLk,11932
-dotenv/parser.py,sha256=QgU5HwMwM2wMqt0vz6dHTJ4nzPmwqRqvi4MSyeVifgU,5186
-dotenv/py.typed,sha256=8PjyZ1aVoQpRVvt71muvuq5qE-jTFZkK-GLHkhdebmc,26
-dotenv/variables.py,sha256=CD0qXOvvpB3q5RpBQMD9qX6vHX7SyW-SuiwGMFSlt08,2348
-dotenv/version.py,sha256=J-j-u0itpEFT6irdmWmixQqYMadNl1X91TxUmoiLHMI,22
-python_dotenv-1.0.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
-python_dotenv-1.0.0.dist-info/LICENSE,sha256=gGGbcEnwjIFoOtDgHwjyV6hAZS3XHugxRtNmWMfSwrk,1556
-python_dotenv-1.0.0.dist-info/METADATA,sha256=0oze1EyeRIUTg91jCTJGbnxQR6mz_FkOW73CmeueUak,21991
-python_dotenv-1.0.0.dist-info/RECORD,,
-python_dotenv-1.0.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-python_dotenv-1.0.0.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92
-python_dotenv-1.0.0.dist-info/entry_points.txt,sha256=yRl1rCbswb1nQTQ_gZRlCw5QfabztUGnfGWLhlXFNdI,47
-python_dotenv-1.0.0.dist-info/top_level.txt,sha256=eyqUH4SHJNr6ahOYlxIunTr4XinE8Z5ajWLdrK3r0D8,7
diff --git a/venv/Lib/site-packages/python_dotenv-1.0.0.dist-info/REQUESTED b/venv/Lib/site-packages/python_dotenv-1.0.0.dist-info/REQUESTED
deleted file mode 100644
index e69de29..0000000
diff --git a/venv/Lib/site-packages/python_dotenv-1.0.0.dist-info/WHEEL b/venv/Lib/site-packages/python_dotenv-1.0.0.dist-info/WHEEL
deleted file mode 100644
index 57e3d84..0000000
--- a/venv/Lib/site-packages/python_dotenv-1.0.0.dist-info/WHEEL
+++ /dev/null
@@ -1,5 +0,0 @@
-Wheel-Version: 1.0
-Generator: bdist_wheel (0.38.4)
-Root-Is-Purelib: true
-Tag: py3-none-any
-
diff --git a/venv/Lib/site-packages/python_dotenv-1.0.0.dist-info/entry_points.txt b/venv/Lib/site-packages/python_dotenv-1.0.0.dist-info/entry_points.txt
deleted file mode 100644
index 0a86823..0000000
--- a/venv/Lib/site-packages/python_dotenv-1.0.0.dist-info/entry_points.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-[console_scripts]
-dotenv = dotenv.__main__:cli
diff --git a/venv/Lib/site-packages/python_dotenv-1.0.0.dist-info/top_level.txt b/venv/Lib/site-packages/python_dotenv-1.0.0.dist-info/top_level.txt
deleted file mode 100644
index fe7c01a..0000000
--- a/venv/Lib/site-packages/python_dotenv-1.0.0.dist-info/top_level.txt
+++ /dev/null
@@ -1 +0,0 @@
-dotenv
diff --git a/venv/Lib/site-packages/werkzeug-2.3.7.dist-info/INSTALLER b/venv/Lib/site-packages/werkzeug-2.3.7.dist-info/INSTALLER
deleted file mode 100644
index a1b589e..0000000
--- a/venv/Lib/site-packages/werkzeug-2.3.7.dist-info/INSTALLER
+++ /dev/null
@@ -1 +0,0 @@
-pip
diff --git a/venv/Lib/site-packages/werkzeug-2.3.7.dist-info/LICENSE.rst b/venv/Lib/site-packages/werkzeug-2.3.7.dist-info/LICENSE.rst
deleted file mode 100644
index c37cae4..0000000
--- a/venv/Lib/site-packages/werkzeug-2.3.7.dist-info/LICENSE.rst
+++ /dev/null
@@ -1,28 +0,0 @@
-Copyright 2007 Pallets
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
-1. Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
-
-2. Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
-
-3. Neither the name of the copyright holder nor the names of its
- contributors may be used to endorse or promote products derived from
- this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
-PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
-TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
-PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/venv/Lib/site-packages/werkzeug-2.3.7.dist-info/METADATA b/venv/Lib/site-packages/werkzeug-2.3.7.dist-info/METADATA
deleted file mode 100644
index 7719139..0000000
--- a/venv/Lib/site-packages/werkzeug-2.3.7.dist-info/METADATA
+++ /dev/null
@@ -1,118 +0,0 @@
-Metadata-Version: 2.1
-Name: Werkzeug
-Version: 2.3.7
-Summary: The comprehensive WSGI web application library.
-Maintainer-email: Pallets
-Requires-Python: >=3.8
-Description-Content-Type: text/x-rst
-Classifier: Development Status :: 5 - Production/Stable
-Classifier: Environment :: Web Environment
-Classifier: Intended Audience :: Developers
-Classifier: License :: OSI Approved :: BSD License
-Classifier: Operating System :: OS Independent
-Classifier: Programming Language :: Python
-Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
-Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
-Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
-Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware
-Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
-Requires-Dist: MarkupSafe>=2.1.1
-Requires-Dist: watchdog>=2.3 ; extra == "watchdog"
-Project-URL: Changes, https://werkzeug.palletsprojects.com/changes/
-Project-URL: Chat, https://discord.gg/pallets
-Project-URL: Documentation, https://werkzeug.palletsprojects.com/
-Project-URL: Donate, https://palletsprojects.com/donate
-Project-URL: Issue Tracker, https://github.com/pallets/werkzeug/issues/
-Project-URL: Source Code, https://github.com/pallets/werkzeug/
-Provides-Extra: watchdog
-
-Werkzeug
-========
-
-*werkzeug* German noun: "tool". Etymology: *werk* ("work"), *zeug* ("stuff")
-
-Werkzeug is a comprehensive `WSGI`_ web application library. It began as
-a simple collection of various utilities for WSGI applications and has
-become one of the most advanced WSGI utility libraries.
-
-It includes:
-
-- An interactive debugger that allows inspecting stack traces and
- source code in the browser with an interactive interpreter for any
- frame in the stack.
-- A full-featured request object with objects to interact with
- headers, query args, form data, files, and cookies.
-- A response object that can wrap other WSGI applications and handle
- streaming data.
-- A routing system for matching URLs to endpoints and generating URLs
- for endpoints, with an extensible system for capturing variables
- from URLs.
-- HTTP utilities to handle entity tags, cache control, dates, user
- agents, cookies, files, and more.
-- A threaded WSGI server for use while developing applications
- locally.
-- A test client for simulating HTTP requests during testing without
- requiring running a server.
-
-Werkzeug doesn't enforce any dependencies. It is up to the developer to
-choose a template engine, database adapter, and even how to handle
-requests. It can be used to build all sorts of end user applications
-such as blogs, wikis, or bulletin boards.
-
-`Flask`_ wraps Werkzeug, using it to handle the details of WSGI while
-providing more structure and patterns for defining powerful
-applications.
-
-.. _WSGI: https://wsgi.readthedocs.io/en/latest/
-.. _Flask: https://www.palletsprojects.com/p/flask/
-
-
-Installing
-----------
-
-Install and update using `pip`_:
-
-.. code-block:: text
-
- pip install -U Werkzeug
-
-.. _pip: https://pip.pypa.io/en/stable/getting-started/
-
-
-A Simple Example
-----------------
-
-.. code-block:: python
-
- from werkzeug.wrappers import Request, Response
-
- @Request.application
- def application(request):
- return Response('Hello, World!')
-
- if __name__ == '__main__':
- from werkzeug.serving import run_simple
- run_simple('localhost', 4000, application)
-
-
-Donate
-------
-
-The Pallets organization develops and supports Werkzeug and other
-popular packages. In order to grow the community of contributors and
-users, and allow the maintainers to devote more time to the projects,
-`please donate today`_.
-
-.. _please donate today: https://palletsprojects.com/donate
-
-
-Links
------
-
-- Documentation: https://werkzeug.palletsprojects.com/
-- Changes: https://werkzeug.palletsprojects.com/changes/
-- PyPI Releases: https://pypi.org/project/Werkzeug/
-- Source Code: https://github.com/pallets/werkzeug/
-- Issue Tracker: https://github.com/pallets/werkzeug/issues/
-- Chat: https://discord.gg/pallets
-
diff --git a/venv/Lib/site-packages/werkzeug-2.3.7.dist-info/RECORD b/venv/Lib/site-packages/werkzeug-2.3.7.dist-info/RECORD
deleted file mode 100644
index 3a5ed08..0000000
--- a/venv/Lib/site-packages/werkzeug-2.3.7.dist-info/RECORD
+++ /dev/null
@@ -1,126 +0,0 @@
-werkzeug-2.3.7.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
-werkzeug-2.3.7.dist-info/LICENSE.rst,sha256=O0nc7kEF6ze6wQ-vG-JgQI_oXSUrjp3y4JefweCUQ3s,1475
-werkzeug-2.3.7.dist-info/METADATA,sha256=H_HVr2PWAD0AW95k1Q4-4s4m3vcM693t6Vf4YCh2FPk,4093
-werkzeug-2.3.7.dist-info/RECORD,,
-werkzeug-2.3.7.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-werkzeug-2.3.7.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
-werkzeug/__init__.py,sha256=HjWydmi120RYL45Z8FRvqOcMGhuaKCGi80tfS7zF9GE,188
-werkzeug/__pycache__/__init__.cpython-310.pyc,,
-werkzeug/__pycache__/_internal.cpython-310.pyc,,
-werkzeug/__pycache__/_reloader.cpython-310.pyc,,
-werkzeug/__pycache__/exceptions.cpython-310.pyc,,
-werkzeug/__pycache__/formparser.cpython-310.pyc,,
-werkzeug/__pycache__/http.cpython-310.pyc,,
-werkzeug/__pycache__/local.cpython-310.pyc,,
-werkzeug/__pycache__/security.cpython-310.pyc,,
-werkzeug/__pycache__/serving.cpython-310.pyc,,
-werkzeug/__pycache__/test.cpython-310.pyc,,
-werkzeug/__pycache__/testapp.cpython-310.pyc,,
-werkzeug/__pycache__/urls.cpython-310.pyc,,
-werkzeug/__pycache__/user_agent.cpython-310.pyc,,
-werkzeug/__pycache__/utils.cpython-310.pyc,,
-werkzeug/__pycache__/wsgi.cpython-310.pyc,,
-werkzeug/_internal.py,sha256=2AlGqWeqlbt7SlK03s2dg4SamrfsJDyIiVKpz4Og69w,8303
-werkzeug/_reloader.py,sha256=1O1DDWlqVwYIX8kgJwH5B4a_Uh6acQnw3sQf01JpXtM,14745
-werkzeug/datastructures/__init__.py,sha256=yzBdOT9DdK3nraNG49pA3bVsvtPPLx2-t2N8ZmuAd9w,1900
-werkzeug/datastructures/__pycache__/__init__.cpython-310.pyc,,
-werkzeug/datastructures/__pycache__/accept.cpython-310.pyc,,
-werkzeug/datastructures/__pycache__/auth.cpython-310.pyc,,
-werkzeug/datastructures/__pycache__/cache_control.cpython-310.pyc,,
-werkzeug/datastructures/__pycache__/csp.cpython-310.pyc,,
-werkzeug/datastructures/__pycache__/etag.cpython-310.pyc,,
-werkzeug/datastructures/__pycache__/file_storage.cpython-310.pyc,,
-werkzeug/datastructures/__pycache__/headers.cpython-310.pyc,,
-werkzeug/datastructures/__pycache__/mixins.cpython-310.pyc,,
-werkzeug/datastructures/__pycache__/range.cpython-310.pyc,,
-werkzeug/datastructures/__pycache__/structures.cpython-310.pyc,,
-werkzeug/datastructures/accept.py,sha256=CuCvBAxNzbt4QUb17rH986vvOVGURFUjo0DX2PQy_yI,10670
-werkzeug/datastructures/accept.pyi,sha256=6P114gncjZoy-i_n_3OQy2nJVwjEAIe7PcBxKYqCEfc,1917
-werkzeug/datastructures/auth.py,sha256=WGcJjnFmbDprqlDr54kXaD3zhK0E4NOoQVa4JHYl9iM,16043
-werkzeug/datastructures/cache_control.py,sha256=RTUipZev50s-1TAn2rYGZrytm_6IOIxQd67fkR5bNF0,6043
-werkzeug/datastructures/cache_control.pyi,sha256=6Q93jRysAKMPWRA72OMksyn7d3ZysuxwGlHp_iwF9pA,3756
-werkzeug/datastructures/csp.py,sha256=DAOAO266LK0JKbvlG80bbkAgfrNsnU9HBoz-FdIYNdo,3244
-werkzeug/datastructures/csp.pyi,sha256=AmDWiZU4rrJA4SZmyMNI1L5PLdIfJsI5Li9r5lE1q6M,5765
-werkzeug/datastructures/etag.py,sha256=JsyI-yXayF-hQu26MyFzbHFIZsaQ6odj3RZO_jF-_cc,2913
-werkzeug/datastructures/etag.pyi,sha256=N9cuUBrZnxHmsbW0BBmjKW-djNY7WKbI6t_WopB8Zo0,1047
-werkzeug/datastructures/file_storage.py,sha256=ePeMtr65s_1_sunXMv_SBOiFof5CX5BepYv5_W16fZk,6184
-werkzeug/datastructures/file_storage.pyi,sha256=2sdbKHhvbQF5FjrJuO6l_m1yZvZ4oPCUTspmdmjQlSU,1433
-werkzeug/datastructures/headers.py,sha256=V08N4VTcaA11fRq1WK5v28QomGd-A1S9CmiwugixhWo,18882
-werkzeug/datastructures/headers.pyi,sha256=66Gh9DbD8QNpLRBOuer4DMCj12csddHrcgxiJPLE5n8,4237
-werkzeug/datastructures/mixins.py,sha256=-IQSQ70UOMQlqtJEIyyhplOd4obaTOfzGvka-cunCtM,5337
-werkzeug/datastructures/mixins.pyi,sha256=y92tClxVslJBEGgAwDRsQLExfin2p0x7NfnP_b8w6xc,4191
-werkzeug/datastructures/range.py,sha256=JXSDPseG7iH5giJp3R1SnQC_SqQp634M8Iv6QTsbTxM,5669
-werkzeug/datastructures/range.pyi,sha256=bsM61iNp86gT2lyN0F_Dqg8xsnfPerdmElipuHppiJQ,1792
-werkzeug/datastructures/structures.py,sha256=_bhAf0adEk6WU2uy8jdmuxFMTFcuClY1p7jQ-3wYXj4,31761
-werkzeug/datastructures/structures.pyi,sha256=MRg-RubT3UPjh62i9-7Xht8DVL0zTApRzjs52Hfz_j4,8148
-werkzeug/debug/__init__.py,sha256=WRTLJSvnuK6jlBuQLllTnN57th0HKPjxbS7-d8QJZIc,18760
-werkzeug/debug/__pycache__/__init__.cpython-310.pyc,,
-werkzeug/debug/__pycache__/console.cpython-310.pyc,,
-werkzeug/debug/__pycache__/repr.cpython-310.pyc,,
-werkzeug/debug/__pycache__/tbtools.cpython-310.pyc,,
-werkzeug/debug/console.py,sha256=FIO8gDX2eQ1_4MtpJ4s0i2gR4fFCJZTPwhSVByF4kbo,6068
-werkzeug/debug/repr.py,sha256=ECmIpNVlCppTfCuIuEgrJVfuhr8iDqPSWeVJyxt1QOM,9328
-werkzeug/debug/shared/ICON_LICENSE.md,sha256=DhA6Y1gUl5Jwfg0NFN9Rj4VWITt8tUx0IvdGf0ux9-s,222
-werkzeug/debug/shared/console.png,sha256=bxax6RXXlvOij_KeqvSNX0ojJf83YbnZ7my-3Gx9w2A,507
-werkzeug/debug/shared/debugger.js,sha256=FVBBUirz4kKedIbM08QQCYeEoicoSbnm4BnBF4dCYfA,10562
-werkzeug/debug/shared/less.png,sha256=-4-kNRaXJSONVLahrQKUxMwXGm9R4OnZ9SxDGpHlIR4,191
-werkzeug/debug/shared/more.png,sha256=GngN7CioHQoV58rH6ojnkYi8c_qED2Aka5FO5UXrReY,200
-werkzeug/debug/shared/style.css,sha256=-xSxzUEZGw_IqlDR5iZxitNl8LQUjBM-_Y4UAvXVH8g,6078
-werkzeug/debug/tbtools.py,sha256=8Xg7p2JzCC1AMWuse5HYc594OdzC5ToeJbNk49_zZCc,13271
-werkzeug/exceptions.py,sha256=d6VNzGcVgLazIpfwRD8pN_d3yAJNyngBDFvlXQbR-38,26062
-werkzeug/formparser.py,sha256=DZ9BeiHAah3_CuBORNOEipRwE74lHRFX1eK2_3XKcL4,19574
-werkzeug/http.py,sha256=morM5oaClzgpBEjShgUVPlj4asZCvYuZ8WqjVaZzXtQ,48754
-werkzeug/local.py,sha256=Jawgwa7Q7orExtyzVlhbh-4mGWg3v63bspIU5Nnl-DU,22003
-werkzeug/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-werkzeug/middleware/__pycache__/__init__.cpython-310.pyc,,
-werkzeug/middleware/__pycache__/dispatcher.cpython-310.pyc,,
-werkzeug/middleware/__pycache__/http_proxy.cpython-310.pyc,,
-werkzeug/middleware/__pycache__/lint.cpython-310.pyc,,
-werkzeug/middleware/__pycache__/profiler.cpython-310.pyc,,
-werkzeug/middleware/__pycache__/proxy_fix.cpython-310.pyc,,
-werkzeug/middleware/__pycache__/shared_data.cpython-310.pyc,,
-werkzeug/middleware/dispatcher.py,sha256=6ltzPtDsIdLTY_T1GW6kxBJL0KZftbipa_WVdKtpVQ8,2601
-werkzeug/middleware/http_proxy.py,sha256=vsSvt84m656x3mV_Fj78y7O2eYHmurWngErTcjeiz8U,7833
-werkzeug/middleware/lint.py,sha256=6CqcwMWro1p-GRUGPgQ1n21KFnTTqc6-81CGTzpcK74,13916
-werkzeug/middleware/profiler.py,sha256=KKr8nAiF9dr9pNd3G0D3xs7mUba9gvWkyK7X9ceke70,4906
-werkzeug/middleware/proxy_fix.py,sha256=dcOOSjSok2QsSh1VSNsw-a0Vy_Jn5DunlO6PRbXBq0A,6754
-werkzeug/middleware/shared_data.py,sha256=DeM8OouhfhZs8w5T7Wxw-uKuOHXoH0x5RopzxR2RRjI,9513
-werkzeug/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-werkzeug/routing/__init__.py,sha256=HpvahY7WwkLdV4Cq3Bsc3GrqNon4u6t8-vhbb9E5o00,4819
-werkzeug/routing/__pycache__/__init__.cpython-310.pyc,,
-werkzeug/routing/__pycache__/converters.cpython-310.pyc,,
-werkzeug/routing/__pycache__/exceptions.cpython-310.pyc,,
-werkzeug/routing/__pycache__/map.cpython-310.pyc,,
-werkzeug/routing/__pycache__/matcher.cpython-310.pyc,,
-werkzeug/routing/__pycache__/rules.cpython-310.pyc,,
-werkzeug/routing/converters.py,sha256=V8e_wMRop6WG4Kymu4pBIR8OrJl-ZUQUZlinUXfw7WE,7602
-werkzeug/routing/exceptions.py,sha256=yGZ5AUL-buHp-vK8AJbZ0bLIbSckh1UyiGKgRg4ZjaA,4698
-werkzeug/routing/map.py,sha256=2tirw9j5wypzsUT6WBcBNcBTqNp0_iBXnF_1vhY9HjI,37403
-werkzeug/routing/matcher.py,sha256=FyPG45iqR1XwxFujejSqfNEKV7IgbR2td7Jp-ocSASY,7817
-werkzeug/routing/rules.py,sha256=THxBzPRlK87nyf8qoBH96MuHk8G5KiSJkQEay4zZmzY,32058
-werkzeug/sansio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
-werkzeug/sansio/__pycache__/__init__.cpython-310.pyc,,
-werkzeug/sansio/__pycache__/http.cpython-310.pyc,,
-werkzeug/sansio/__pycache__/multipart.cpython-310.pyc,,
-werkzeug/sansio/__pycache__/request.cpython-310.pyc,,
-werkzeug/sansio/__pycache__/response.cpython-310.pyc,,
-werkzeug/sansio/__pycache__/utils.cpython-310.pyc,,
-werkzeug/sansio/http.py,sha256=mKTbXo_squCAZKjt9yzfPFV8ZqQbfa6mjdc6XoeLNZ0,6234
-werkzeug/sansio/multipart.py,sha256=XM53Ud4YXicPEqm0HgcS3loMfMCt3NoAx8DmImxaW0g,11113
-werkzeug/sansio/request.py,sha256=wEeVGySwlOfJT5xlgQzjJOe2ksky70CJT75QTzkvfqM,24243
-werkzeug/sansio/response.py,sha256=6DgROSXWG_0XzOrJi_U8PRMNGKX8YV-uHPu0cLrOKsk,29010
-werkzeug/sansio/utils.py,sha256=LYgmrN7yr04ZDVk5flPcUJLo1rDnTzhF04OH3-ujCWQ,4950
-werkzeug/security.py,sha256=gEH8qD5Ykgn6W6PgMx2CQx-iNqJFenXXqOGiWDi_3eE,5814
-werkzeug/serving.py,sha256=Ql_SUZxsmQzN8OZ-hDvKFQ5nRgKh6FEIYwcXVEmD6qU,39224
-werkzeug/test.py,sha256=xOnp3B6V2MQ0Qn3jL7eMimJna2-zqI04wD_IazDKCto,55733
-werkzeug/testapp.py,sha256=Q7SXVDXeXnnXo7-TWVoAJCTF2GnXxoH-v5_pvjUyTWc,6135
-werkzeug/urls.py,sha256=Uq_cu8TmZFHkQ7t2pp9DNwDvs6wG76jzWPstQIssPVk,45683
-werkzeug/user_agent.py,sha256=lSlLYKCcbzCUSkbdAoO8zPk2UR-8Mdn6iu_iA2kYPBA,1416
-werkzeug/utils.py,sha256=DYkOtfDR_Wc3ro3_peReo9KkUC-6yhOvz27_PUAckbA,24654
-werkzeug/wrappers/__init__.py,sha256=kGyK7rOud3qCxll_jFyW15YarJhj1xtdf3ocx9ZheB8,120
-werkzeug/wrappers/__pycache__/__init__.cpython-310.pyc,,
-werkzeug/wrappers/__pycache__/request.cpython-310.pyc,,
-werkzeug/wrappers/__pycache__/response.cpython-310.pyc,,
-werkzeug/wrappers/request.py,sha256=_PIbgCZ9xfQXC9HEjm-j1R-F4gSPcx5q-QT983mMzbs,24848
-werkzeug/wrappers/response.py,sha256=FfGesquK6cSdPTFZvzV42CM__Ohta2cxNqLBDRkAuKA,32664
-werkzeug/wsgi.py,sha256=PGkhajtHnJj2NqYpYW_T8w17JJbaH8iI0wHHNkPvJKs,29153
diff --git a/venv/Lib/site-packages/werkzeug-2.3.7.dist-info/REQUESTED b/venv/Lib/site-packages/werkzeug-2.3.7.dist-info/REQUESTED
deleted file mode 100644
index e69de29..0000000
diff --git a/venv/Lib/site-packages/werkzeug-2.3.7.dist-info/WHEEL b/venv/Lib/site-packages/werkzeug-2.3.7.dist-info/WHEEL
deleted file mode 100644
index 3b5e64b..0000000
--- a/venv/Lib/site-packages/werkzeug-2.3.7.dist-info/WHEEL
+++ /dev/null
@@ -1,4 +0,0 @@
-Wheel-Version: 1.0
-Generator: flit 3.9.0
-Root-Is-Purelib: true
-Tag: py3-none-any
diff --git a/venv/Lib/site-packages/werkzeug/__init__.py b/venv/Lib/site-packages/werkzeug/__init__.py
index d34bfe6..57cb753 100644
--- a/venv/Lib/site-packages/werkzeug/__init__.py
+++ b/venv/Lib/site-packages/werkzeug/__init__.py
@@ -1,6 +1,25 @@
+from __future__ import annotations
+
+import typing as t
+
from .serving import run_simple as run_simple
from .test import Client as Client
from .wrappers import Request as Request
from .wrappers import Response as Response
-__version__ = "2.3.7"
+
+def __getattr__(name: str) -> t.Any:
+ if name == "__version__":
+ import importlib.metadata
+ import warnings
+
+ warnings.warn(
+ "The '__version__' attribute is deprecated and will be removed in"
+ " Werkzeug 3.1. Use feature detection or"
+ " 'importlib.metadata.version(\"werkzeug\")' instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return importlib.metadata.version("werkzeug")
+
+ raise AttributeError(name)
diff --git a/venv/Lib/site-packages/werkzeug/__pycache__/__init__.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/__pycache__/__init__.cpython-310.pyc
index a4799d5..36646d1 100644
Binary files a/venv/Lib/site-packages/werkzeug/__pycache__/__init__.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/__pycache__/__init__.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/__pycache__/_internal.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/__pycache__/_internal.cpython-310.pyc
index fedfcac..c99fc5f 100644
Binary files a/venv/Lib/site-packages/werkzeug/__pycache__/_internal.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/__pycache__/_internal.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/__pycache__/_reloader.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/__pycache__/_reloader.cpython-310.pyc
index f89c957..832bbcb 100644
Binary files a/venv/Lib/site-packages/werkzeug/__pycache__/_reloader.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/__pycache__/_reloader.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/__pycache__/exceptions.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/__pycache__/exceptions.cpython-310.pyc
index 84cc65f..274fb4e 100644
Binary files a/venv/Lib/site-packages/werkzeug/__pycache__/exceptions.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/__pycache__/exceptions.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/__pycache__/formparser.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/__pycache__/formparser.cpython-310.pyc
index 1991ddf..8d5fe94 100644
Binary files a/venv/Lib/site-packages/werkzeug/__pycache__/formparser.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/__pycache__/formparser.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/__pycache__/http.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/__pycache__/http.cpython-310.pyc
index ffb95b0..cb7d8eb 100644
Binary files a/venv/Lib/site-packages/werkzeug/__pycache__/http.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/__pycache__/http.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/__pycache__/local.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/__pycache__/local.cpython-310.pyc
index aaa75be..365f5b4 100644
Binary files a/venv/Lib/site-packages/werkzeug/__pycache__/local.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/__pycache__/local.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/__pycache__/security.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/__pycache__/security.cpython-310.pyc
index 4707bed..942b1e7 100644
Binary files a/venv/Lib/site-packages/werkzeug/__pycache__/security.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/__pycache__/security.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/__pycache__/serving.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/__pycache__/serving.cpython-310.pyc
index ca6c2b9..01f2b38 100644
Binary files a/venv/Lib/site-packages/werkzeug/__pycache__/serving.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/__pycache__/serving.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/__pycache__/test.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/__pycache__/test.cpython-310.pyc
index a55045f..209efc8 100644
Binary files a/venv/Lib/site-packages/werkzeug/__pycache__/test.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/__pycache__/test.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/__pycache__/testapp.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/__pycache__/testapp.cpython-310.pyc
index 47479ce..0f4cc95 100644
Binary files a/venv/Lib/site-packages/werkzeug/__pycache__/testapp.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/__pycache__/testapp.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/__pycache__/urls.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/__pycache__/urls.cpython-310.pyc
index ed37dd9..e1856c5 100644
Binary files a/venv/Lib/site-packages/werkzeug/__pycache__/urls.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/__pycache__/urls.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/__pycache__/user_agent.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/__pycache__/user_agent.cpython-310.pyc
index 5025ac2..acb5fc3 100644
Binary files a/venv/Lib/site-packages/werkzeug/__pycache__/user_agent.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/__pycache__/user_agent.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/__pycache__/utils.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/__pycache__/utils.cpython-310.pyc
index 1211226..50d9f48 100644
Binary files a/venv/Lib/site-packages/werkzeug/__pycache__/utils.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/__pycache__/utils.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/__pycache__/wsgi.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/__pycache__/wsgi.cpython-310.pyc
index c8eb750..979abed 100644
Binary files a/venv/Lib/site-packages/werkzeug/__pycache__/wsgi.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/__pycache__/wsgi.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/_internal.py b/venv/Lib/site-packages/werkzeug/_internal.py
index 6ed4d30..70ab687 100644
--- a/venv/Lib/site-packages/werkzeug/_internal.py
+++ b/venv/Lib/site-packages/werkzeug/_internal.py
@@ -1,7 +1,6 @@
from __future__ import annotations
import logging
-import operator
import re
import sys
import typing as t
@@ -26,102 +25,12 @@ class _Missing:
_missing = _Missing()
-@t.overload
-def _make_encode_wrapper(reference: str) -> t.Callable[[str], str]:
- ...
+def _wsgi_decoding_dance(s: str) -> str:
+ return s.encode("latin1").decode(errors="replace")
-@t.overload
-def _make_encode_wrapper(reference: bytes) -> t.Callable[[str], bytes]:
- ...
-
-
-def _make_encode_wrapper(reference: t.AnyStr) -> t.Callable[[str], t.AnyStr]:
- """Create a function that will be called with a string argument. If
- the reference is bytes, values will be encoded to bytes.
- """
- if isinstance(reference, str):
- return lambda x: x
-
- return operator.methodcaller("encode", "latin1")
-
-
-def _check_str_tuple(value: tuple[t.AnyStr, ...]) -> None:
- """Ensure tuple items are all strings or all bytes."""
- if not value:
- return
-
- item_type = str if isinstance(value[0], str) else bytes
-
- if any(not isinstance(item, item_type) for item in value):
- raise TypeError(f"Cannot mix str and bytes arguments (got {value!r})")
-
-
-_default_encoding = sys.getdefaultencoding()
-
-
-def _to_bytes(
- x: str | bytes, charset: str = _default_encoding, errors: str = "strict"
-) -> bytes:
- if x is None or isinstance(x, bytes):
- return x
-
- if isinstance(x, (bytearray, memoryview)):
- return bytes(x)
-
- if isinstance(x, str):
- return x.encode(charset, errors)
-
- raise TypeError("Expected bytes")
-
-
-@t.overload
-def _to_str( # type: ignore
- x: None,
- charset: str | None = ...,
- errors: str = ...,
- allow_none_charset: bool = ...,
-) -> None:
- ...
-
-
-@t.overload
-def _to_str(
- x: t.Any,
- charset: str | None = ...,
- errors: str = ...,
- allow_none_charset: bool = ...,
-) -> str:
- ...
-
-
-def _to_str(
- x: t.Any | None,
- charset: str | None = _default_encoding,
- errors: str = "strict",
- allow_none_charset: bool = False,
-) -> str | bytes | None:
- if x is None or isinstance(x, str):
- return x
-
- if not isinstance(x, (bytes, bytearray)):
- return str(x)
-
- if charset is None:
- if allow_none_charset:
- return x
-
- return x.decode(charset, errors) # type: ignore
-
-
-def _wsgi_decoding_dance(
- s: str, charset: str = "utf-8", errors: str = "replace"
-) -> str:
- return s.encode("latin1").decode(charset, errors)
-
-
-def _wsgi_encoding_dance(s: str, charset: str = "utf-8", errors: str = "strict") -> str:
- return s.encode(charset).decode("latin1", errors)
+def _wsgi_encoding_dance(s: str) -> str:
+ return s.encode().decode("latin1")
def _get_environ(obj: WSGIEnvironment | Request) -> WSGIEnvironment:
@@ -287,31 +196,6 @@ class _DictAccessorProperty(t.Generic[_TAccessorValue]):
return f"<{type(self).__name__} {self.name}>"
-def _decode_idna(domain: str) -> str:
- try:
- data = domain.encode("ascii")
- except UnicodeEncodeError:
- # If the domain is not ASCII, it's decoded already.
- return domain
-
- try:
- # Try decoding in one shot.
- return data.decode("idna")
- except UnicodeDecodeError:
- pass
-
- # Decode each part separately, leaving invalid parts as punycode.
- parts = []
-
- for part in data.split(b"."):
- try:
- parts.append(part.decode("idna"))
- except UnicodeDecodeError:
- parts.append(part.decode("ascii"))
-
- return ".".join(parts)
-
-
_plain_int_re = re.compile(r"-?\d+", re.ASCII)
diff --git a/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/__init__.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/__init__.cpython-310.pyc
index e226e28..58774ff 100644
Binary files a/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/__init__.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/__init__.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/accept.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/accept.cpython-310.pyc
index aa73ba6..97b10dc 100644
Binary files a/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/accept.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/accept.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/auth.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/auth.cpython-310.pyc
index 04f8663..f2b498e 100644
Binary files a/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/auth.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/auth.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/cache_control.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/cache_control.cpython-310.pyc
index fa43a3d..24f51c0 100644
Binary files a/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/cache_control.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/cache_control.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/csp.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/csp.cpython-310.pyc
index 140fa68..13aae06 100644
Binary files a/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/csp.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/csp.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/etag.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/etag.cpython-310.pyc
index f266fba..c3ea201 100644
Binary files a/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/etag.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/etag.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/file_storage.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/file_storage.cpython-310.pyc
index 9b09483..0c9fbe7 100644
Binary files a/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/file_storage.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/file_storage.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/headers.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/headers.cpython-310.pyc
index a28dd82..05b4fbb 100644
Binary files a/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/headers.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/headers.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/mixins.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/mixins.cpython-310.pyc
index 9148b1c..2d5839f 100644
Binary files a/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/mixins.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/mixins.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/range.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/range.cpython-310.pyc
index c3fd66d..d87eb31 100644
Binary files a/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/range.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/range.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/structures.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/structures.cpython-310.pyc
index f3d0b41..effac82 100644
Binary files a/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/structures.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/structures.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/datastructures/auth.py b/venv/Lib/site-packages/werkzeug/datastructures/auth.py
index 2f25150..494576d 100644
--- a/venv/Lib/site-packages/werkzeug/datastructures/auth.py
+++ b/venv/Lib/site-packages/werkzeug/datastructures/auth.py
@@ -3,15 +3,11 @@ from __future__ import annotations
import base64
import binascii
import typing as t
-import warnings
-from functools import wraps
from ..http import dump_header
from ..http import parse_dict_header
-from ..http import parse_set_header
from ..http import quote_header_value
from .structures import CallbackDict
-from .structures import HeaderSet
if t.TYPE_CHECKING:
import typing_extensions as te
@@ -46,7 +42,7 @@ class Authorization:
def __init__(
self,
auth_type: str,
- data: dict[str, str] | None = None,
+ data: dict[str, str | None] | None = None,
token: str | None = None,
) -> None:
self.type = auth_type
@@ -143,31 +139,6 @@ class Authorization:
return f"<{type(self).__name__} {self.to_header()}>"
-def auth_property(name: str, doc: str | None = None) -> property:
- """A static helper function for Authentication subclasses to add
- extra authentication system properties onto a class::
-
- class FooAuthenticate(WWWAuthenticate):
- special_realm = auth_property('special_realm')
-
- .. deprecated:: 2.3
- Will be removed in Werkzeug 3.0.
- """
- warnings.warn(
- "'auth_property' is deprecated and will be removed in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
-
- def _set_value(self, value): # type: ignore[no-untyped-def]
- if value is None:
- self.pop(name, None)
- else:
- self[name] = str(value)
-
- return property(lambda x: x.get(name), _set_value, doc=doc)
-
-
class WWWAuthenticate:
"""Represents the parts of a ``WWW-Authenticate`` response header.
@@ -196,21 +167,12 @@ class WWWAuthenticate:
def __init__(
self,
- auth_type: str | None = None,
- values: dict[str, str] | None = None,
+ auth_type: str,
+ values: dict[str, str | None] | None = None,
token: str | None = None,
):
- if auth_type is None:
- warnings.warn(
- "An auth type must be given as the first parameter. Assuming 'basic' is"
- " deprecated and will be removed in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- auth_type = "basic"
-
self._type = auth_type.lower()
- self._parameters: dict[str, str] = CallbackDict( # type: ignore[misc]
+ self._parameters: dict[str, str | None] = CallbackDict( # type: ignore[misc]
values, lambda _: self._trigger_on_update()
)
self._token = token
@@ -231,7 +193,7 @@ class WWWAuthenticate:
self._trigger_on_update()
@property
- def parameters(self) -> dict[str, str]:
+ def parameters(self) -> dict[str, str | None]:
"""A dict of parameters for the header. Only one of this or :attr:`token` should
have a value for a given scheme.
"""
@@ -261,62 +223,6 @@ class WWWAuthenticate:
self._token = value
self._trigger_on_update()
- def set_basic(self, realm: str = "authentication required") -> None:
- """Clear any existing data and set a ``Basic`` challenge.
-
- .. deprecated:: 2.3
- Will be removed in Werkzeug 3.0. Create and assign an instance instead.
- """
- warnings.warn(
- "The 'set_basic' method is deprecated and will be removed in Werkzeug 3.0."
- " Create and assign an instance instead."
- )
- self._type = "basic"
- dict.clear(self.parameters) # type: ignore[arg-type]
- dict.update(
- self.parameters, # type: ignore[arg-type]
- {"realm": realm}, # type: ignore[dict-item]
- )
- self._token = None
- self._trigger_on_update()
-
- def set_digest(
- self,
- realm: str,
- nonce: str,
- qop: t.Sequence[str] = ("auth",),
- opaque: str | None = None,
- algorithm: str | None = None,
- stale: bool = False,
- ) -> None:
- """Clear any existing data and set a ``Digest`` challenge.
-
- .. deprecated:: 2.3
- Will be removed in Werkzeug 3.0. Create and assign an instance instead.
- """
- warnings.warn(
- "The 'set_digest' method is deprecated and will be removed in Werkzeug 3.0."
- " Create and assign an instance instead."
- )
- self._type = "digest"
- dict.clear(self.parameters) # type: ignore[arg-type]
- parameters = {
- "realm": realm,
- "nonce": nonce,
- "qop": ", ".join(qop),
- "stale": "TRUE" if stale else "FALSE",
- }
-
- if opaque is not None:
- parameters["opaque"] = opaque
-
- if algorithm is not None:
- parameters["algorithm"] = algorithm
-
- dict.update(self.parameters, parameters) # type: ignore[arg-type]
- self._token = None
- self._trigger_on_update()
-
def __getitem__(self, key: str) -> str | None:
return self.parameters.get(key)
@@ -410,101 +316,3 @@ class WWWAuthenticate:
def __repr__(self) -> str:
return f"<{type(self).__name__} {self.to_header()}>"
-
- @property
- def qop(self) -> set[str]:
- """The ``qop`` parameter as a set.
-
- .. deprecated:: 2.3
- Will be removed in Werkzeug 3.0. It will become the same as other
- parameters, returning a string.
- """
- warnings.warn(
- "The 'qop' property is deprecated and will be removed in Werkzeug 3.0."
- " It will become the same as other parameters, returning a string.",
- DeprecationWarning,
- stacklevel=2,
- )
-
- def on_update(value: HeaderSet) -> None:
- if not value:
- if "qop" in self:
- del self["qop"]
-
- return
-
- self.parameters["qop"] = value.to_header()
-
- return parse_set_header(self.parameters.get("qop"), on_update)
-
- @property
- def stale(self) -> bool | None:
- """The ``stale`` parameter as a boolean.
-
- .. deprecated:: 2.3
- Will be removed in Werkzeug 3.0. It will become the same as other
- parameters, returning a string.
- """
- warnings.warn(
- "The 'stale' property is deprecated and will be removed in Werkzeug 3.0."
- " It will become the same as other parameters, returning a string.",
- DeprecationWarning,
- stacklevel=2,
- )
-
- if "stale" in self.parameters:
- return self.parameters["stale"].lower() == "true"
-
- return None
-
- @stale.setter
- def stale(self, value: bool | str | None) -> None:
- if value is None:
- if "stale" in self.parameters:
- del self.parameters["stale"]
-
- return
-
- if isinstance(value, bool):
- warnings.warn(
- "Setting the 'stale' property to a boolean is deprecated and will be"
- " removed in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- self.parameters["stale"] = "TRUE" if value else "FALSE"
- else:
- self.parameters["stale"] = value
-
- auth_property = staticmethod(auth_property)
-
-
-def _deprecated_dict_method(f): # type: ignore[no-untyped-def]
- @wraps(f)
- def wrapper(*args, **kwargs): # type: ignore[no-untyped-def]
- warnings.warn(
- "Treating 'Authorization' and 'WWWAuthenticate' as a dict is deprecated and"
- " will be removed in Werkzeug 3.0. Use the 'parameters' attribute instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- return f(*args, **kwargs)
-
- return wrapper
-
-
-for name in (
- "__iter__",
- "clear",
- "copy",
- "items",
- "keys",
- "pop",
- "popitem",
- "setdefault",
- "update",
- "values",
-):
- f = _deprecated_dict_method(getattr(dict, name))
- setattr(Authorization, name, f)
- setattr(WWWAuthenticate, name, f)
diff --git a/venv/Lib/site-packages/werkzeug/datastructures/headers.py b/venv/Lib/site-packages/werkzeug/datastructures/headers.py
index dc060c4..d9dd655 100644
--- a/venv/Lib/site-packages/werkzeug/datastructures/headers.py
+++ b/venv/Lib/site-packages/werkzeug/datastructures/headers.py
@@ -2,7 +2,6 @@ from __future__ import annotations
import re
import typing as t
-import warnings
from .._internal import _missing
from ..exceptions import BadRequestKeyError
@@ -82,7 +81,7 @@ class Headers:
__hash__ = None
- def get(self, key, default=None, type=None, as_bytes=None):
+ def get(self, key, default=None, type=None):
"""Return the default value if the requested data doesn't exist.
If `type` is provided and is a callable it should convert the value,
return it or raise a :exc:`ValueError` if that is not possible. In
@@ -101,27 +100,16 @@ class Headers:
:class:`Headers`. If a :exc:`ValueError` is raised
by this callable the default value is returned.
- .. versionchanged:: 2.3
- The ``as_bytes`` parameter is deprecated and will be removed
- in Werkzeug 3.0.
+ .. versionchanged:: 3.0
+ The ``as_bytes`` parameter was removed.
.. versionchanged:: 0.9
The ``as_bytes`` parameter was added.
"""
- if as_bytes is not None:
- warnings.warn(
- "The 'as_bytes' parameter is deprecated and will be"
- " removed in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
-
try:
rv = self.__getitem__(key, _get_mode=True)
except KeyError:
return default
- if as_bytes:
- rv = rv.encode("latin1")
if type is None:
return rv
try:
@@ -129,7 +117,7 @@ class Headers:
except ValueError:
return default
- def getlist(self, key, type=None, as_bytes=None):
+ def getlist(self, key, type=None):
"""Return the list of items for a given key. If that key is not in the
:class:`Headers`, the return value will be an empty list. Just like
:meth:`get`, :meth:`getlist` accepts a `type` parameter. All items will
@@ -141,27 +129,16 @@ class Headers:
by this callable the value will be removed from the list.
:return: a :class:`list` of all the values for the key.
- .. versionchanged:: 2.3
- The ``as_bytes`` parameter is deprecated and will be removed
- in Werkzeug 3.0.
+ .. versionchanged:: 3.0
+ The ``as_bytes`` parameter was removed.
.. versionchanged:: 0.9
The ``as_bytes`` parameter was added.
"""
- if as_bytes is not None:
- warnings.warn(
- "The 'as_bytes' parameter is deprecated and will be"
- " removed in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
-
ikey = key.lower()
result = []
for k, v in self:
if k.lower() == ikey:
- if as_bytes:
- v = v.encode("latin1")
if type is not None:
try:
v = type(v)
@@ -293,7 +270,6 @@ class Headers:
"""
if kw:
_value = _options_header_vkw(_value, kw)
- _key = _str_header_key(_key)
_value = _str_header_value(_value)
self._list.append((_key, _value))
@@ -326,7 +302,6 @@ class Headers:
"""
if kw:
_value = _options_header_vkw(_value, kw)
- _key = _str_header_key(_key)
_value = _str_header_value(_value)
if not self._list:
self._list.append((_key, _value))
@@ -399,7 +374,7 @@ class Headers:
if isinstance(key, (slice, int)):
if isinstance(key, int):
value = [value]
- value = [(_str_header_key(k), _str_header_value(v)) for (k, v) in value]
+ value = [(k, _str_header_value(v)) for (k, v) in value]
if isinstance(key, int):
self._list[key] = value[0]
else:
@@ -476,36 +451,10 @@ def _options_header_vkw(value: str, kw: dict[str, t.Any]):
)
-def _str_header_key(key: t.Any) -> str:
- if not isinstance(key, str):
- warnings.warn(
- "Header keys must be strings. Passing other types is deprecated and will"
- " not be supported in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
-
- if isinstance(key, bytes):
- key = key.decode("latin-1")
- else:
- key = str(key)
-
- return key
-
-
_newline_re = re.compile(r"[\r\n]")
def _str_header_value(value: t.Any) -> str:
- if isinstance(value, bytes):
- warnings.warn(
- "Passing bytes as a header value is deprecated and will not be supported in"
- " Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- value = value.decode("latin-1")
-
if not isinstance(value, str):
value = str(value)
diff --git a/venv/Lib/site-packages/werkzeug/debug/__pycache__/__init__.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/debug/__pycache__/__init__.cpython-310.pyc
index 9b81433..1d30898 100644
Binary files a/venv/Lib/site-packages/werkzeug/debug/__pycache__/__init__.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/debug/__pycache__/__init__.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/debug/__pycache__/console.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/debug/__pycache__/console.cpython-310.pyc
index e220ea5..b9a4f50 100644
Binary files a/venv/Lib/site-packages/werkzeug/debug/__pycache__/console.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/debug/__pycache__/console.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/debug/__pycache__/repr.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/debug/__pycache__/repr.cpython-310.pyc
index a085830..5314e34 100644
Binary files a/venv/Lib/site-packages/werkzeug/debug/__pycache__/repr.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/debug/__pycache__/repr.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/debug/__pycache__/tbtools.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/debug/__pycache__/tbtools.cpython-310.pyc
index b96fb81..3e2f8ea 100644
Binary files a/venv/Lib/site-packages/werkzeug/debug/__pycache__/tbtools.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/debug/__pycache__/tbtools.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/formparser.py b/venv/Lib/site-packages/werkzeug/formparser.py
index 25ef0d6..ee30666 100644
--- a/venv/Lib/site-packages/werkzeug/formparser.py
+++ b/venv/Lib/site-packages/werkzeug/formparser.py
@@ -1,7 +1,6 @@
from __future__ import annotations
import typing as t
-import warnings
from io import BytesIO
from urllib.parse import parse_qsl
@@ -68,8 +67,6 @@ def default_stream_factory(
def parse_form_data(
environ: WSGIEnvironment,
stream_factory: TStreamFactory | None = None,
- charset: str | None = None,
- errors: str | None = None,
max_form_memory_size: int | None = None,
max_content_length: int | None = None,
cls: type[MultiDict] | None = None,
@@ -108,12 +105,11 @@ def parse_form_data(
is exceeded, a :exc:`~exceptions.RequestEntityTooLarge` exception is raised.
:return: A tuple in the form ``(stream, form, files)``.
- .. versionchanged:: 2.3
- Added the ``max_form_parts`` parameter.
+ .. versionchanged:: 3.0
+ The ``charset`` and ``errors`` parameters were removed.
.. versionchanged:: 2.3
- The ``charset`` and ``errors`` parameters are deprecated and will be removed in
- Werkzeug 3.0.
+ Added the ``max_form_parts`` parameter.
.. versionadded:: 0.5.1
Added the ``silent`` parameter.
@@ -124,8 +120,6 @@ def parse_form_data(
"""
return FormDataParser(
stream_factory=stream_factory,
- charset=charset,
- errors=errors,
max_form_memory_size=max_form_memory_size,
max_content_length=max_content_length,
max_form_parts=max_form_parts,
@@ -159,13 +153,11 @@ class FormDataParser:
:param max_form_parts: The maximum number of multipart parts to be parsed. If this
is exceeded, a :exc:`~exceptions.RequestEntityTooLarge` exception is raised.
- .. versionchanged:: 2.3
- The ``charset`` and ``errors`` parameters are deprecated and will be removed in
- Werkzeug 3.0.
+ .. versionchanged:: 3.0
+ The ``charset`` and ``errors`` parameters were removed.
- .. versionchanged:: 2.3
- The ``parse_functions`` attribute and ``get_parse_func`` methods are deprecated
- and will be removed in Werkzeug 3.0.
+ .. versionchanged:: 3.0
+ The ``parse_functions`` attribute and ``get_parse_func`` methods were removed.
.. versionchanged:: 2.2.3
Added the ``max_form_parts`` parameter.
@@ -176,8 +168,6 @@ class FormDataParser:
def __init__(
self,
stream_factory: TStreamFactory | None = None,
- charset: str | None = None,
- errors: str | None = None,
max_form_memory_size: int | None = None,
max_content_length: int | None = None,
cls: type[MultiDict] | None = None,
@@ -189,30 +179,6 @@ class FormDataParser:
stream_factory = default_stream_factory
self.stream_factory = stream_factory
-
- if charset is not None:
- warnings.warn(
- "The 'charset' parameter is deprecated and will be"
- " removed in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- else:
- charset = "utf-8"
-
- self.charset = charset
-
- if errors is not None:
- warnings.warn(
- "The 'errors' parameter is deprecated and will be"
- " removed in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- else:
- errors = "replace"
-
- self.errors = errors
self.max_form_memory_size = max_form_memory_size
self.max_content_length = max_content_length
self.max_form_parts = max_form_parts
@@ -223,44 +189,6 @@ class FormDataParser:
self.cls = cls
self.silent = silent
- def get_parse_func(
- self, mimetype: str, options: dict[str, str]
- ) -> None | (
- t.Callable[
- [FormDataParser, t.IO[bytes], str, int | None, dict[str, str]],
- t_parse_result,
- ]
- ):
- warnings.warn(
- "The 'get_parse_func' method is deprecated and will be"
- " removed in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
-
- if mimetype == "multipart/form-data":
- return type(self)._parse_multipart
- elif mimetype == "application/x-www-form-urlencoded":
- return type(self)._parse_urlencoded
- elif mimetype == "application/x-url-encoded":
- warnings.warn(
- "The 'application/x-url-encoded' mimetype is invalid, and will not be"
- " treated as 'application/x-www-form-urlencoded' in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- return type(self)._parse_urlencoded
- elif mimetype in self.parse_functions:
- warnings.warn(
- "The 'parse_functions' attribute is deprecated and will be removed in"
- " Werkzeug 3.0. Override 'parse' instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- return self.parse_functions[mimetype]
-
- return None
-
def parse_from_environ(self, environ: WSGIEnvironment) -> t_parse_result:
"""Parses the information from the environment as form data.
@@ -294,30 +222,14 @@ class FormDataParser:
the multipart boundary for instance)
:return: A tuple in the form ``(stream, form, files)``.
- .. versionchanged:: 2.3
- The ``application/x-url-encoded`` content type is deprecated and will not be
- treated as ``application/x-www-form-urlencoded`` in Werkzeug 3.0.
+ .. versionchanged:: 3.0
+ The invalid ``application/x-url-encoded`` content type is not
+ treated as ``application/x-www-form-urlencoded``.
"""
if mimetype == "multipart/form-data":
parse_func = self._parse_multipart
elif mimetype == "application/x-www-form-urlencoded":
parse_func = self._parse_urlencoded
- elif mimetype == "application/x-url-encoded":
- warnings.warn(
- "The 'application/x-url-encoded' mimetype is invalid, and will not be"
- " treated as 'application/x-www-form-urlencoded' in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- parse_func = self._parse_urlencoded
- elif mimetype in self.parse_functions:
- warnings.warn(
- "The 'parse_functions' attribute is deprecated and will be removed in"
- " Werkzeug 3.0. Override 'parse' instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- parse_func = self.parse_functions[mimetype].__get__(self, type(self))
else:
return stream, self.cls(), self.cls()
@@ -339,12 +251,8 @@ class FormDataParser:
content_length: int | None,
options: dict[str, str],
) -> t_parse_result:
- charset = self.charset if self.charset != "utf-8" else None
- errors = self.errors if self.errors != "replace" else None
parser = MultiPartParser(
stream_factory=self.stream_factory,
- charset=charset,
- errors=errors,
max_form_memory_size=self.max_form_memory_size,
max_form_parts=self.max_form_parts,
cls=self.cls,
@@ -375,7 +283,6 @@ class FormDataParser:
items = parse_qsl(
stream.read().decode(),
keep_blank_values=True,
- encoding=self.charset,
errors="werkzeug.url_quote",
)
except ValueError as e:
@@ -383,49 +290,16 @@ class FormDataParser:
return stream, self.cls(items), self.cls()
- parse_functions: dict[
- str,
- t.Callable[
- [FormDataParser, t.IO[bytes], str, int | None, dict[str, str]],
- t_parse_result,
- ],
- ] = {}
-
class MultiPartParser:
def __init__(
self,
stream_factory: TStreamFactory | None = None,
- charset: str | None = None,
- errors: str | None = None,
max_form_memory_size: int | None = None,
cls: type[MultiDict] | None = None,
buffer_size: int = 64 * 1024,
max_form_parts: int | None = None,
) -> None:
- if charset is not None:
- warnings.warn(
- "The 'charset' parameter is deprecated and will be"
- " removed in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- else:
- charset = "utf-8"
-
- self.charset = charset
-
- if errors is not None:
- warnings.warn(
- "The 'errors' parameter is deprecated and will be"
- " removed in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- else:
- errors = "replace"
-
- self.errors = errors
self.max_form_memory_size = max_form_memory_size
self.max_form_parts = max_form_parts
@@ -456,7 +330,7 @@ class MultiPartParser:
if ct_charset in {"ascii", "us-ascii", "utf-8", "iso-8859-1"}:
return ct_charset
- return self.charset
+ return "utf-8"
def start_file_streaming(
self, event: File, total_content_length: int | None
@@ -509,7 +383,7 @@ class MultiPartParser:
if not event.more_data:
if isinstance(current_part, Field):
value = b"".join(container).decode(
- self.get_part_charset(current_part.headers), self.errors
+ self.get_part_charset(current_part.headers), "replace"
)
fields.append((current_part.name, value))
else:
diff --git a/venv/Lib/site-packages/werkzeug/http.py b/venv/Lib/site-packages/werkzeug/http.py
index 07d1fd4..8280f51 100644
--- a/venv/Lib/site-packages/werkzeug/http.py
+++ b/venv/Lib/site-packages/werkzeug/http.py
@@ -136,11 +136,7 @@ class COOP(Enum):
SAME_ORIGIN = "same-origin"
-def quote_header_value(
- value: t.Any,
- extra_chars: str | None = None,
- allow_token: bool = True,
-) -> str:
+def quote_header_value(value: t.Any, allow_token: bool = True) -> str:
"""Add double quotes around a header value. If the header contains only ASCII token
characters, it will be returned unchanged. If the header contains ``"`` or ``\\``
characters, they will be escaped with an additional ``\\`` character.
@@ -150,33 +146,17 @@ def quote_header_value(
:param value: The value to quote. Will be converted to a string.
:param allow_token: Disable to quote the value even if it only has token characters.
+ .. versionchanged:: 3.0
+ Passing bytes is not supported.
+
+ .. versionchanged:: 3.0
+ The ``extra_chars`` parameter is removed.
+
.. versionchanged:: 2.3
The value is quoted if it is the empty string.
- .. versionchanged:: 2.3
- Passing bytes is deprecated and will not be supported in Werkzeug 3.0.
-
- .. versionchanged:: 2.3
- The ``extra_chars`` parameter is deprecated and will be removed in Werkzeug 3.0.
-
.. versionadded:: 0.5
"""
- if isinstance(value, bytes):
- warnings.warn(
- "Passing bytes is deprecated and will not be supported in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- value = value.decode("latin1")
-
- if extra_chars is not None:
- warnings.warn(
- "The 'extra_chars' parameter is deprecated and will be"
- " removed in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
-
value = str(value)
if not value:
@@ -185,9 +165,6 @@ def quote_header_value(
if allow_token:
token_chars = _token_chars
- if extra_chars:
- token_chars |= set(extra_chars)
-
if token_chars.issuperset(value):
return value
@@ -195,7 +172,7 @@ def quote_header_value(
return f'"{value}"'
-def unquote_header_value(value: str, is_filename: bool | None = None) -> str:
+def unquote_header_value(value: str) -> str:
"""Remove double quotes and decode slash-escaped ``"`` and ``\\`` characters in a
header value.
@@ -203,22 +180,12 @@ def unquote_header_value(value: str, is_filename: bool | None = None) -> str:
:param value: The header value to unquote.
- .. versionchanged:: 2.3
- The ``is_filename`` parameter is deprecated and will be removed in Werkzeug 3.0.
+ .. versionchanged:: 3.0
+ The ``is_filename`` parameter is removed.
"""
- if is_filename is not None:
- warnings.warn(
- "The 'is_filename' parameter is deprecated and will be"
- " removed in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
-
if len(value) >= 2 and value[0] == value[-1] == '"':
value = value[1:-1]
-
- if not is_filename:
- return value.replace("\\\\", "\\").replace('\\"', '"')
+ return value.replace("\\\\", "\\").replace('\\"', '"')
return value
@@ -269,10 +236,7 @@ def dump_options_header(header: str | None, options: t.Mapping[str, t.Any]) -> s
return "; ".join(segments)
-def dump_header(
- iterable: dict[str, t.Any] | t.Iterable[t.Any],
- allow_token: bool | None = None,
-) -> str:
+def dump_header(iterable: dict[str, t.Any] | t.Iterable[t.Any]) -> str:
"""Produce a header value from a list of items or ``key=value`` pairs, separated by
commas ``,``.
@@ -298,22 +262,12 @@ def dump_header(
:param iterable: The items to create a header from.
- .. versionchanged:: 2.3
- The ``allow_token`` parameter is deprecated and will be removed in Werkzeug 3.0.
+ .. versionchanged:: 3.0
+ The ``allow_token`` parameter is removed.
.. versionchanged:: 2.2.3
If a key ends with ``*``, its value will not be quoted.
"""
- if allow_token is not None:
- warnings.warn(
- "'The 'allow_token' parameter is deprecated and will be"
- " removed in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- else:
- allow_token = True
-
if isinstance(iterable, dict):
items = []
@@ -323,11 +277,9 @@ def dump_header(
elif key[-1] == "*":
items.append(f"{key}={value}")
else:
- items.append(
- f"{key}={quote_header_value(value, allow_token=allow_token)}"
- )
+ items.append(f"{key}={quote_header_value(value)}")
else:
- items = [quote_header_value(x, allow_token=allow_token) for x in iterable]
+ items = [quote_header_value(x) for x in iterable]
return ", ".join(items)
@@ -372,7 +324,7 @@ def parse_list_header(value: str) -> list[str]:
return result
-def parse_dict_header(value: str, cls: type[dict] | None = None) -> dict[str, str]:
+def parse_dict_header(value: str) -> dict[str, str | None]:
"""Parse a list header using :func:`parse_list_header`, then parse each item as a
``key=value`` pair.
@@ -391,36 +343,19 @@ def parse_dict_header(value: str, cls: type[dict] | None = None) -> dict[str, st
:param value: The header value to parse.
+ .. versionchanged:: 3.0
+ Passing bytes is not supported.
+
+ .. versionchanged:: 3.0
+ The ``cls`` argument is removed.
+
.. versionchanged:: 2.3
Added support for ``key*=charset''value`` encoded items.
- .. versionchanged:: 2.3
- Passing bytes is deprecated, support will be removed in Werkzeug 3.0.
-
- .. versionchanged:: 2.3
- The ``cls`` argument is deprecated and will be removed in Werkzeug 3.0.
-
.. versionchanged:: 0.9
The ``cls`` argument was added.
"""
- if cls is None:
- cls = dict
- else:
- warnings.warn(
- "The 'cls' parameter is deprecated and will be removed in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
-
- result = cls()
-
- if isinstance(value, bytes):
- warnings.warn(
- "Passing bytes is deprecated and will be removed in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- value = value.decode("latin1")
+ result: dict[str, str | None] = {}
for item in parse_list_header(value):
key, has_value, value = item.partition("=")
@@ -815,65 +750,6 @@ def parse_set_header(
return ds.HeaderSet(parse_list_header(value), on_update)
-def parse_authorization_header(
- value: str | None,
-) -> ds.Authorization | None:
- """Parse an HTTP basic/digest authorization header transmitted by the web
- browser. The return value is either `None` if the header was invalid or
- not given, otherwise an :class:`~werkzeug.datastructures.Authorization`
- object.
-
- :param value: the authorization header to parse.
- :return: a :class:`~werkzeug.datastructures.Authorization` object or `None`.
-
- .. deprecated:: 2.3
- Will be removed in Werkzeug 3.0. Use :meth:`.Authorization.from_header` instead.
- """
- from .datastructures import Authorization
-
- warnings.warn(
- "'parse_authorization_header' is deprecated and will be removed in Werkzeug"
- " 2.4. Use 'Authorization.from_header' instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- return Authorization.from_header(value)
-
-
-def parse_www_authenticate_header(
- value: str | None,
- on_update: t.Callable[[ds.WWWAuthenticate], None] | None = None,
-) -> ds.WWWAuthenticate:
- """Parse an HTTP WWW-Authenticate header into a
- :class:`~werkzeug.datastructures.WWWAuthenticate` object.
-
- :param value: a WWW-Authenticate header to parse.
- :param on_update: an optional callable that is called every time a value
- on the :class:`~werkzeug.datastructures.WWWAuthenticate`
- object is changed.
- :return: a :class:`~werkzeug.datastructures.WWWAuthenticate` object.
-
- .. deprecated:: 2.3
- Will be removed in Werkzeug 3.0. Use :meth:`.WWWAuthenticate.from_header`
- instead.
- """
- from .datastructures.auth import WWWAuthenticate
-
- warnings.warn(
- "'parse_www_authenticate_header' is deprecated and will be removed in Werkzeug"
- " 2.4. Use 'WWWAuthenticate.from_header' instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- rv = WWWAuthenticate.from_header(value)
-
- if rv is None:
- rv = WWWAuthenticate("basic")
-
- rv._on_update = on_update
- return rv
-
-
def parse_if_range_header(value: str | None) -> ds.IfRange:
"""Parses an if-range header which can be an etag or a date. Returns
a :class:`~werkzeug.datastructures.IfRange` object.
@@ -1284,8 +1160,6 @@ def is_hop_by_hop_header(header: str) -> bool:
def parse_cookie(
header: WSGIEnvironment | str | None,
- charset: str | None = None,
- errors: str | None = None,
cls: type[ds.MultiDict] | None = None,
) -> ds.MultiDict[str, str]:
"""Parse a cookie from a string or WSGI environ.
@@ -1300,9 +1174,8 @@ def parse_cookie(
:param cls: A dict-like class to store the parsed cookies in.
Defaults to :class:`MultiDict`.
- .. versionchanged:: 2.3
- Passing bytes, and the ``charset`` and ``errors`` parameters, are deprecated and
- will be removed in Werkzeug 3.0.
+ .. versionchanged:: 3.0
+ Passing bytes, and the ``charset`` and ``errors`` parameters, were removed.
.. versionchanged:: 1.0
Returns a :class:`MultiDict` instead of a ``TypeConversionDict``.
@@ -1313,22 +1186,13 @@ def parse_cookie(
"""
if isinstance(header, dict):
cookie = header.get("HTTP_COOKIE")
- elif isinstance(header, bytes):
- warnings.warn(
- "Passing bytes is deprecated and will not be supported in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- cookie = header.decode()
else:
cookie = header
if cookie:
cookie = cookie.encode("latin1").decode()
- return _sansio_http.parse_cookie(
- cookie=cookie, charset=charset, errors=errors, cls=cls
- )
+ return _sansio_http.parse_cookie(cookie=cookie, cls=cls)
_cookie_no_quote_re = re.compile(r"[\w!#$%&'()*+\-./:<=>?@\[\]^`{|}~]*", re.A)
@@ -1349,7 +1213,6 @@ def dump_cookie(
domain: str | None = None,
secure: bool = False,
httponly: bool = False,
- charset: str | None = None,
sync_expires: bool = True,
max_size: int = 4093,
samesite: str | None = None,
@@ -1392,6 +1255,9 @@ def dump_cookie(
.. _`cookie`: http://browsercookielimits.squawky.net/
+ .. versionchanged:: 3.0
+ Passing bytes, and the ``charset`` parameter, were removed.
+
.. versionchanged:: 2.3.3
The ``path`` parameter is ``/`` by default.
@@ -1405,46 +1271,14 @@ def dump_cookie(
.. versionchanged:: 2.3
The ``path`` parameter is ``None`` by default.
- .. versionchanged:: 2.3
- Passing bytes, and the ``charset`` parameter, are deprecated and will be removed
- in Werkzeug 3.0.
-
.. versionchanged:: 1.0.0
The string ``'None'`` is accepted for ``samesite``.
"""
- if charset is not None:
- warnings.warn(
- "The 'charset' parameter is deprecated and will be removed"
- " in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- else:
- charset = "utf-8"
-
- if isinstance(key, bytes):
- warnings.warn(
- "The 'key' parameter must be a string. Bytes are deprecated"
- " and will not be supported in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- key = key.decode()
-
- if isinstance(value, bytes):
- warnings.warn(
- "The 'value' parameter must be a string. Bytes are"
- " deprecated and will not be supported in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- value = value.decode()
-
if path is not None:
# safe = https://url.spec.whatwg.org/#url-path-segment-string
# as well as percent for things that are already quoted
# excluding semicolon since it's part of the header syntax
- path = quote(path, safe="%!$&'()*+,/:=@", encoding=charset)
+ path = quote(path, safe="%!$&'()*+,/:=@")
if domain:
domain = domain.partition(":")[0].lstrip(".").encode("idna").decode("ascii")
@@ -1469,7 +1303,7 @@ def dump_cookie(
if not _cookie_no_quote_re.fullmatch(value):
# Work with bytes here, since a UTF-8 character could be multiple bytes.
value = _cookie_slash_re.sub(
- lambda m: _cookie_slash_map[m.group()], value.encode(charset)
+ lambda m: _cookie_slash_map[m.group()], value.encode()
).decode("ascii")
value = f'"{value}"'
diff --git a/venv/Lib/site-packages/werkzeug/middleware/__pycache__/__init__.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/middleware/__pycache__/__init__.cpython-310.pyc
index 616a1b1..866cbf9 100644
Binary files a/venv/Lib/site-packages/werkzeug/middleware/__pycache__/__init__.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/middleware/__pycache__/__init__.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/middleware/__pycache__/dispatcher.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/middleware/__pycache__/dispatcher.cpython-310.pyc
index 2196943..cd6367b 100644
Binary files a/venv/Lib/site-packages/werkzeug/middleware/__pycache__/dispatcher.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/middleware/__pycache__/dispatcher.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/middleware/__pycache__/http_proxy.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/middleware/__pycache__/http_proxy.cpython-310.pyc
index bc33fec..7e629c3 100644
Binary files a/venv/Lib/site-packages/werkzeug/middleware/__pycache__/http_proxy.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/middleware/__pycache__/http_proxy.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/middleware/__pycache__/lint.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/middleware/__pycache__/lint.cpython-310.pyc
index fabd600..3732125 100644
Binary files a/venv/Lib/site-packages/werkzeug/middleware/__pycache__/lint.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/middleware/__pycache__/lint.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/middleware/__pycache__/profiler.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/middleware/__pycache__/profiler.cpython-310.pyc
index 786f03c..dd5f28d 100644
Binary files a/venv/Lib/site-packages/werkzeug/middleware/__pycache__/profiler.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/middleware/__pycache__/profiler.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/middleware/__pycache__/proxy_fix.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/middleware/__pycache__/proxy_fix.cpython-310.pyc
index 5a984e1..effae8c 100644
Binary files a/venv/Lib/site-packages/werkzeug/middleware/__pycache__/proxy_fix.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/middleware/__pycache__/proxy_fix.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/middleware/__pycache__/shared_data.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/middleware/__pycache__/shared_data.cpython-310.pyc
index 14961f2..d50cf2f 100644
Binary files a/venv/Lib/site-packages/werkzeug/middleware/__pycache__/shared_data.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/middleware/__pycache__/shared_data.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/middleware/profiler.py b/venv/Lib/site-packages/werkzeug/middleware/profiler.py
index 2d80615..1120c83 100644
--- a/venv/Lib/site-packages/werkzeug/middleware/profiler.py
+++ b/venv/Lib/site-packages/werkzeug/middleware/profiler.py
@@ -44,11 +44,16 @@ class ProfilerMiddleware:
- ``{method}`` - The request method; GET, POST, etc.
- ``{path}`` - The request path or 'root' should one not exist.
- - ``{elapsed}`` - The elapsed time of the request.
+ - ``{elapsed}`` - The elapsed time of the request in milliseconds.
- ``{time}`` - The time of the request.
- If it is a callable, it will be called with the WSGI ``environ``
- dict and should return a filename.
+ If it is a callable, it will be called with the WSGI ``environ`` and
+ be expected to return a filename string. The ``environ`` dictionary
+ will also have the ``"werkzeug.profiler"`` key populated with a
+ dictionary containing the following fields (more may be added in the
+ future):
+ - ``{elapsed}`` - The elapsed time of the request in milliseconds.
+ - ``{time}`` - The time of the request.
:param app: The WSGI application to wrap.
:param stream: Write stats to this stream. Disable with ``None``.
@@ -65,6 +70,10 @@ class ProfilerMiddleware:
from werkzeug.middleware.profiler import ProfilerMiddleware
app = ProfilerMiddleware(app)
+ .. versionchanged:: 3.0
+ Added the ``"werkzeug.profiler"`` key to the ``filename_format(environ)``
+ parameter with the ``elapsed`` and ``time`` fields.
+
.. versionchanged:: 0.15
Stats are written even if ``profile_dir`` is given, and can be
disable by passing ``stream=None``.
@@ -118,6 +127,10 @@ class ProfilerMiddleware:
if self._profile_dir is not None:
if callable(self._filename_format):
+ environ["werkzeug.profiler"] = {
+ "elapsed": elapsed * 1000.0,
+ "time": time.time(),
+ }
filename = self._filename_format(environ)
else:
filename = self._filename_format.format(
diff --git a/venv/Lib/site-packages/werkzeug/routing/__pycache__/__init__.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/routing/__pycache__/__init__.cpython-310.pyc
index 5438b63..b9b4d28 100644
Binary files a/venv/Lib/site-packages/werkzeug/routing/__pycache__/__init__.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/routing/__pycache__/__init__.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/routing/__pycache__/converters.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/routing/__pycache__/converters.cpython-310.pyc
index e884406..0664108 100644
Binary files a/venv/Lib/site-packages/werkzeug/routing/__pycache__/converters.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/routing/__pycache__/converters.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/routing/__pycache__/exceptions.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/routing/__pycache__/exceptions.cpython-310.pyc
index fd1806a..8677301 100644
Binary files a/venv/Lib/site-packages/werkzeug/routing/__pycache__/exceptions.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/routing/__pycache__/exceptions.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/routing/__pycache__/map.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/routing/__pycache__/map.cpython-310.pyc
index beb8ef1..438e18f 100644
Binary files a/venv/Lib/site-packages/werkzeug/routing/__pycache__/map.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/routing/__pycache__/map.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/routing/__pycache__/matcher.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/routing/__pycache__/matcher.cpython-310.pyc
index b7b8e15..8aa3d9f 100644
Binary files a/venv/Lib/site-packages/werkzeug/routing/__pycache__/matcher.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/routing/__pycache__/matcher.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/routing/__pycache__/rules.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/routing/__pycache__/rules.cpython-310.pyc
index 51b0945..fb200ec 100644
Binary files a/venv/Lib/site-packages/werkzeug/routing/__pycache__/rules.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/routing/__pycache__/rules.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/routing/converters.py b/venv/Lib/site-packages/werkzeug/routing/converters.py
index c59e2ab..ce01dd1 100644
--- a/venv/Lib/site-packages/werkzeug/routing/converters.py
+++ b/venv/Lib/site-packages/werkzeug/routing/converters.py
@@ -3,7 +3,6 @@ from __future__ import annotations
import re
import typing as t
import uuid
-import warnings
from urllib.parse import quote
if t.TYPE_CHECKING:
@@ -42,17 +41,8 @@ class BaseConverter:
return value
def to_url(self, value: t.Any) -> str:
- if isinstance(value, (bytes, bytearray)):
- warnings.warn(
- "Passing bytes as a URL value is deprecated and will not be supported"
- " in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=7,
- )
- return quote(value, safe="!$&'()*+,/:;=@")
-
# safe = https://url.spec.whatwg.org/#url-path-segment-string
- return quote(str(value), encoding=self.map.charset, safe="!$&'()*+,/:;=@")
+ return quote(str(value), safe="!$&'()*+,/:;=@")
class UnicodeConverter(BaseConverter):
@@ -129,6 +119,7 @@ class PathConverter(BaseConverter):
:param map: the :class:`Map`.
"""
+ part_isolating = False
regex = "[^/].*?"
weight = 200
diff --git a/venv/Lib/site-packages/werkzeug/routing/map.py b/venv/Lib/site-packages/werkzeug/routing/map.py
index 0d02bb8..76bbe2f 100644
--- a/venv/Lib/site-packages/werkzeug/routing/map.py
+++ b/venv/Lib/site-packages/werkzeug/routing/map.py
@@ -47,7 +47,6 @@ class Map:
:param rules: sequence of url rules for this map.
:param default_subdomain: The default subdomain for rules without a
subdomain defined.
- :param charset: charset of the url. defaults to ``"utf-8"``
:param strict_slashes: If a rule ends with a slash but the matched
URL does not, redirect to the URL with a trailing slash.
:param merge_slashes: Merge consecutive slashes when matching or
@@ -62,15 +61,13 @@ class Map:
:param sort_parameters: If set to `True` the url parameters are sorted.
See `url_encode` for more details.
:param sort_key: The sort key function for `url_encode`.
- :param encoding_errors: the error method to use for decoding
:param host_matching: if set to `True` it enables the host matching
feature and disables the subdomain one. If
enabled the `host` parameter to rules is used
instead of the `subdomain` one.
- .. versionchanged:: 2.3
- The ``charset`` and ``encoding_errors`` parameters are deprecated and will be
- removed in Werkzeug 3.0.
+ .. versionchanged:: 3.0
+ The ``charset`` and ``encoding_errors`` parameters were removed.
.. versionchanged:: 1.0
If ``url_scheme`` is ``ws`` or ``wss``, only WebSocket rules will match.
@@ -97,14 +94,12 @@ class Map:
self,
rules: t.Iterable[RuleFactory] | None = None,
default_subdomain: str = "",
- charset: str | None = None,
strict_slashes: bool = True,
merge_slashes: bool = True,
redirect_defaults: bool = True,
converters: t.Mapping[str, type[BaseConverter]] | None = None,
sort_parameters: bool = False,
sort_key: t.Callable[[t.Any], t.Any] | None = None,
- encoding_errors: str | None = None,
host_matching: bool = False,
) -> None:
self._matcher = StateMachineMatcher(merge_slashes)
@@ -113,30 +108,6 @@ class Map:
self._remap_lock = self.lock_class()
self.default_subdomain = default_subdomain
-
- if charset is not None:
- warnings.warn(
- "The 'charset' parameter is deprecated and will be"
- " removed in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- else:
- charset = "utf-8"
-
- self.charset = charset
-
- if encoding_errors is not None:
- warnings.warn(
- "The 'encoding_errors' parameter is deprecated and will be"
- " removed in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- else:
- encoding_errors = "replace"
-
- self.encoding_errors = encoding_errors
self.strict_slashes = strict_slashes
self.merge_slashes = merge_slashes
self.redirect_defaults = redirect_defaults
@@ -362,7 +333,7 @@ class Map:
def _get_wsgi_string(name: str) -> str | None:
val = env.get(name)
if val is not None:
- return _wsgi_decoding_dance(val, self.charset)
+ return _wsgi_decoding_dance(val)
return None
script_name = _get_wsgi_string("SCRIPT_NAME")
@@ -629,9 +600,7 @@ class MapAdapter:
result = self.map._matcher.match(domain_part, path_part, method, websocket)
except RequestPath as e:
# safe = https://url.spec.whatwg.org/#url-path-segment-string
- new_path = quote(
- e.path_info, safe="!$&'()*+,/:;=@", encoding=self.map.charset
- )
+ new_path = quote(e.path_info, safe="!$&'()*+,/:;=@")
raise RequestRedirect(
self.make_redirect_url(new_path, query_args)
) from None
@@ -767,7 +736,7 @@ class MapAdapter:
def encode_query_args(self, query_args: t.Mapping[str, t.Any] | str) -> str:
if not isinstance(query_args, str):
- return _urlencode(query_args, encoding=self.map.charset)
+ return _urlencode(query_args)
return query_args
def make_redirect_url(
diff --git a/venv/Lib/site-packages/werkzeug/routing/rules.py b/venv/Lib/site-packages/werkzeug/routing/rules.py
index 904a022..5c8184c 100644
--- a/venv/Lib/site-packages/werkzeug/routing/rules.py
+++ b/venv/Lib/site-packages/werkzeug/routing/rules.py
@@ -583,7 +583,7 @@ class Rule(RuleFactory):
if self.map.sort_parameters:
items = sorted(items, key=self.map.sort_key)
- return _urlencode(items, encoding=self.map.charset)
+ return _urlencode(items)
def _parse_rule(self, rule: str) -> t.Iterable[RulePart]:
content = ""
@@ -739,12 +739,7 @@ class Rule(RuleFactory):
opl.append((False, data))
elif not is_dynamic:
# safe = https://url.spec.whatwg.org/#url-path-segment-string
- opl.append(
- (
- False,
- quote(data, safe="!$&'()*+,/:;=@", encoding=self.map.charset),
- )
- )
+ opl.append((False, quote(data, safe="!$&'()*+,/:;=@")))
else:
opl.append((True, data))
diff --git a/venv/Lib/site-packages/werkzeug/sansio/__pycache__/__init__.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/sansio/__pycache__/__init__.cpython-310.pyc
index b215a4b..fc28f52 100644
Binary files a/venv/Lib/site-packages/werkzeug/sansio/__pycache__/__init__.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/sansio/__pycache__/__init__.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/sansio/__pycache__/http.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/sansio/__pycache__/http.cpython-310.pyc
index 46b4c60..f56dd24 100644
Binary files a/venv/Lib/site-packages/werkzeug/sansio/__pycache__/http.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/sansio/__pycache__/http.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/sansio/__pycache__/multipart.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/sansio/__pycache__/multipart.cpython-310.pyc
index de3b57a..b246de8 100644
Binary files a/venv/Lib/site-packages/werkzeug/sansio/__pycache__/multipart.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/sansio/__pycache__/multipart.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/sansio/__pycache__/request.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/sansio/__pycache__/request.cpython-310.pyc
index d21f7eb..0e29edf 100644
Binary files a/venv/Lib/site-packages/werkzeug/sansio/__pycache__/request.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/sansio/__pycache__/request.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/sansio/__pycache__/response.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/sansio/__pycache__/response.cpython-310.pyc
index 425a3a9..d597bd9 100644
Binary files a/venv/Lib/site-packages/werkzeug/sansio/__pycache__/response.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/sansio/__pycache__/response.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/sansio/__pycache__/utils.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/sansio/__pycache__/utils.cpython-310.pyc
index 2c154f4..11b4e80 100644
Binary files a/venv/Lib/site-packages/werkzeug/sansio/__pycache__/utils.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/sansio/__pycache__/utils.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/sansio/http.py b/venv/Lib/site-packages/werkzeug/sansio/http.py
index 21a6197..e3cd333 100644
--- a/venv/Lib/site-packages/werkzeug/sansio/http.py
+++ b/venv/Lib/site-packages/werkzeug/sansio/http.py
@@ -2,7 +2,6 @@ from __future__ import annotations
import re
import typing as t
-import warnings
from datetime import datetime
from .._internal import _dt_as_utc
@@ -123,8 +122,6 @@ def _cookie_unslash_replace(m: t.Match[bytes]) -> bytes:
def parse_cookie(
cookie: str | None = None,
- charset: str | None = None,
- errors: str | None = None,
cls: type[ds.MultiDict] | None = None,
) -> ds.MultiDict[str, str]:
"""Parse a cookie from a string.
@@ -138,42 +135,14 @@ def parse_cookie(
:param cls: A dict-like class to store the parsed cookies in.
Defaults to :class:`MultiDict`.
- .. versionchanged:: 2.3
- Passing bytes, and the ``charset`` and ``errors`` parameters, are deprecated and
- will be removed in Werkzeug 3.0.
+ .. versionchanged:: 3.0
+ Passing bytes, and the ``charset`` and ``errors`` parameters, were removed.
.. versionadded:: 2.2
"""
if cls is None:
cls = ds.MultiDict
- if isinstance(cookie, bytes):
- warnings.warn(
- "The 'cookie' parameter must be a string. Passing bytes is deprecated and"
- " will not be supported in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- cookie = cookie.decode()
-
- if charset is not None:
- warnings.warn(
- "The 'charset' parameter is deprecated and will be removed in Werkzeug 3.0",
- DeprecationWarning,
- stacklevel=2,
- )
- else:
- charset = "utf-8"
-
- if errors is not None:
- warnings.warn(
- "The 'errors' parameter is deprecated and will be removed in Werkzeug 3.0",
- DeprecationWarning,
- stacklevel=2,
- )
- else:
- errors = "replace"
-
if not cookie:
return cls()
@@ -191,7 +160,7 @@ def parse_cookie(
# Work with bytes here, since a UTF-8 character could be multiple bytes.
cv = _cookie_unslash_re.sub(
_cookie_unslash_replace, cv[1:-1].encode()
- ).decode(charset, errors)
+ ).decode(errors="replace")
out.append((ck, cv))
diff --git a/venv/Lib/site-packages/werkzeug/sansio/multipart.py b/venv/Lib/site-packages/werkzeug/sansio/multipart.py
index 380993a..fc87353 100644
--- a/venv/Lib/site-packages/werkzeug/sansio/multipart.py
+++ b/venv/Lib/site-packages/werkzeug/sansio/multipart.py
@@ -251,12 +251,20 @@ class MultipartDecoder:
else:
data_start = 0
- if self.buffer.find(b"--" + self.boundary) == -1:
+ boundary = b"--" + self.boundary
+
+ if self.buffer.find(boundary) == -1:
# No complete boundary in the buffer, but there may be
# a partial boundary at the end. As the boundary
# starts with either a nl or cr find the earliest and
# return up to that as data.
data_end = del_index = self.last_newline(data[data_start:]) + data_start
+ # If amount of data after last newline is far from
+ # possible length of partial boundary, we should
+ # assume that there is no partial boundary in the buffer
+ # and return all pending data.
+ if (len(data) - data_end) > len(b"\n" + boundary):
+ data_end = del_index = len(data)
more_data = True
else:
match = self.boundary_re.search(data)
diff --git a/venv/Lib/site-packages/werkzeug/sansio/request.py b/venv/Lib/site-packages/werkzeug/sansio/request.py
index 0bcda90..b59bd5b 100644
--- a/venv/Lib/site-packages/werkzeug/sansio/request.py
+++ b/venv/Lib/site-packages/werkzeug/sansio/request.py
@@ -1,7 +1,6 @@
from __future__ import annotations
import typing as t
-import warnings
from datetime import datetime
from urllib.parse import parse_qsl
@@ -59,95 +58,13 @@ class Request:
:param headers: The headers received with the request.
:param remote_addr: The address of the client sending the request.
+ .. versionchanged:: 3.0
+ The ``charset``, ``url_charset``, and ``encoding_errors`` attributes
+ were removed.
+
.. versionadded:: 2.0
"""
- _charset: str
-
- @property
- def charset(self) -> str:
- """The charset used to decode body, form, and cookie data. Defaults to UTF-8.
-
- .. deprecated:: 2.3
- Will be removed in Werkzeug 3.0. Request data must always be UTF-8.
- """
- warnings.warn(
- "The 'charset' attribute is deprecated and will not be used in Werkzeug"
- " 2.4. Interpreting bytes as text in body, form, and cookie data will"
- " always use UTF-8.",
- DeprecationWarning,
- stacklevel=2,
- )
- return self._charset
-
- @charset.setter
- def charset(self, value: str) -> None:
- warnings.warn(
- "The 'charset' attribute is deprecated and will not be used in Werkzeug"
- " 2.4. Interpreting bytes as text in body, form, and cookie data will"
- " always use UTF-8.",
- DeprecationWarning,
- stacklevel=2,
- )
- self._charset = value
-
- _encoding_errors: str
-
- @property
- def encoding_errors(self) -> str:
- """How errors when decoding bytes are handled. Defaults to "replace".
-
- .. deprecated:: 2.3
- Will be removed in Werkzeug 3.0.
- """
- warnings.warn(
- "The 'encoding_errors' attribute is deprecated and will not be used in"
- " Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- return self._encoding_errors
-
- @encoding_errors.setter
- def encoding_errors(self, value: str) -> None:
- warnings.warn(
- "The 'encoding_errors' attribute is deprecated and will not be used in"
- " Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- self._encoding_errors = value
-
- _url_charset: str
-
- @property
- def url_charset(self) -> str:
- """The charset to use when decoding percent-encoded bytes in :attr:`args`.
- Defaults to the value of :attr:`charset`, which defaults to UTF-8.
-
- .. deprecated:: 2.3
- Will be removed in Werkzeug 3.0. Percent-encoded bytes must always be UTF-8.
-
- .. versionadded:: 0.6
- """
- warnings.warn(
- "The 'url_charset' attribute is deprecated and will not be used in"
- " Werkzeug 3.0. Percent-encoded bytes must always be UTF-8.",
- DeprecationWarning,
- stacklevel=2,
- )
- return self._url_charset
-
- @url_charset.setter
- def url_charset(self, value: str) -> None:
- warnings.warn(
- "The 'url_charset' attribute is deprecated and will not be used in"
- " Werkzeug 3.0. Percent-encoded bytes must always be UTF-8.",
- DeprecationWarning,
- stacklevel=2,
- )
- self._url_charset = value
-
#: the class to use for `args` and `form`. The default is an
#: :class:`~werkzeug.datastructures.ImmutableMultiDict` which supports
#: multiple values per key. alternatively it makes sense to use an
@@ -209,40 +126,6 @@ class Request:
headers: Headers,
remote_addr: str | None,
) -> None:
- if not isinstance(type(self).charset, property):
- warnings.warn(
- "The 'charset' attribute is deprecated and will not be used in Werkzeug"
- " 2.4. Interpreting bytes as text in body, form, and cookie data will"
- " always use UTF-8.",
- DeprecationWarning,
- stacklevel=2,
- )
- self._charset = self.charset
- else:
- self._charset = "utf-8"
-
- if not isinstance(type(self).encoding_errors, property):
- warnings.warn(
- "The 'encoding_errors' attribute is deprecated and will not be used in"
- " Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- self._encoding_errors = self.encoding_errors
- else:
- self._encoding_errors = "replace"
-
- if not isinstance(type(self).url_charset, property):
- warnings.warn(
- "The 'url_charset' attribute is deprecated and will not be used in"
- " Werkzeug 3.0. Percent-encoded bytes must always be UTF-8.",
- DeprecationWarning,
- stacklevel=2,
- )
- self._url_charset = self.url_charset
- else:
- self._url_charset = self._charset
-
#: The method the request was made with, such as ``GET``.
self.method = method.upper()
#: The URL scheme of the protocol the request used, such as
@@ -291,7 +174,6 @@ class Request:
parse_qsl(
self.query_string.decode(),
keep_blank_values=True,
- encoding=self._url_charset,
errors="werkzeug.url_quote",
)
)
@@ -360,13 +242,8 @@ class Request:
"""A :class:`dict` with the contents of all cookies transmitted with
the request."""
wsgi_combined_cookie = ";".join(self.headers.getlist("Cookie"))
- charset = self._charset if self._charset != "utf-8" else None
- errors = self._encoding_errors if self._encoding_errors != "replace" else None
return parse_cookie( # type: ignore
- wsgi_combined_cookie,
- charset=charset,
- errors=errors,
- cls=self.dict_storage_class,
+ wsgi_combined_cookie, cls=self.dict_storage_class
)
# Common Descriptors
diff --git a/venv/Lib/site-packages/werkzeug/sansio/response.py b/venv/Lib/site-packages/werkzeug/sansio/response.py
index e5c1df7..271974e 100644
--- a/venv/Lib/site-packages/werkzeug/sansio/response.py
+++ b/venv/Lib/site-packages/werkzeug/sansio/response.py
@@ -1,7 +1,6 @@
from __future__ import annotations
import typing as t
-import warnings
from datetime import datetime
from datetime import timedelta
from datetime import timezone
@@ -81,36 +80,12 @@ class Response:
:param content_type: The full content type of the response.
Overrides building the value from ``mimetype``.
+ .. versionchanged:: 3.0
+ The ``charset`` attribute was removed.
+
.. versionadded:: 2.0
"""
- _charset: str
-
- @property
- def charset(self) -> str:
- """The charset used to encode body and cookie data. Defaults to UTF-8.
-
- .. deprecated:: 2.3
- Will be removed in Werkzeug 3.0. Response data must always be UTF-8.
- """
- warnings.warn(
- "The 'charset' attribute is deprecated and will not be used in Werkzeug"
- " 2.4. Text in body and cookie data will always use UTF-8.",
- DeprecationWarning,
- stacklevel=2,
- )
- return self._charset
-
- @charset.setter
- def charset(self, value: str) -> None:
- warnings.warn(
- "The 'charset' attribute is deprecated and will not be used in Werkzeug"
- " 2.4. Text in body and cookie data will always use UTF-8.",
- DeprecationWarning,
- stacklevel=2,
- )
- self._charset = value
-
#: the default status if none is provided.
default_status = 200
@@ -139,17 +114,6 @@ class Response:
mimetype: str | None = None,
content_type: str | None = None,
) -> None:
- if not isinstance(type(self).charset, property):
- warnings.warn(
- "The 'charset' attribute is deprecated and will not be used in Werkzeug"
- " 2.4. Text in body and cookie data will always use UTF-8.",
- DeprecationWarning,
- stacklevel=2,
- )
- self._charset = self.charset
- else:
- self._charset = "utf-8"
-
if isinstance(headers, Headers):
self.headers = headers
elif not headers:
@@ -161,7 +125,7 @@ class Response:
if mimetype is None and "content-type" not in self.headers:
mimetype = self.default_mimetype
if mimetype is not None:
- mimetype = get_content_type(mimetype, self._charset)
+ mimetype = get_content_type(mimetype, "utf-8")
content_type = mimetype
if content_type is not None:
self.headers["Content-Type"] = content_type
@@ -255,7 +219,6 @@ class Response:
:param samesite: Limit the scope of the cookie to only be
attached to requests that are "same-site".
"""
- charset = self._charset if self._charset != "utf-8" else None
self.headers.add(
"Set-Cookie",
dump_cookie(
@@ -267,7 +230,6 @@ class Response:
domain=domain,
secure=secure,
httponly=httponly,
- charset=charset,
max_size=self.max_cookie_size,
samesite=samesite,
),
@@ -332,7 +294,7 @@ class Response:
@mimetype.setter
def mimetype(self, value: str) -> None:
- self.headers["Content-Type"] = get_content_type(value, self._charset)
+ self.headers["Content-Type"] = get_content_type(value, "utf-8")
@property
def mimetype_params(self) -> dict[str, str]:
diff --git a/venv/Lib/site-packages/werkzeug/security.py b/venv/Lib/site-packages/werkzeug/security.py
index 282c4fd..578caf7 100644
--- a/venv/Lib/site-packages/werkzeug/security.py
+++ b/venv/Lib/site-packages/werkzeug/security.py
@@ -5,7 +5,6 @@ import hmac
import os
import posixpath
import secrets
-import warnings
SALT_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
DEFAULT_PBKDF2_ITERATIONS = 600000
@@ -24,14 +23,6 @@ def gen_salt(length: int) -> str:
def _hash_internal(method: str, salt: str, password: str) -> tuple[str, str]:
- if method == "plain":
- warnings.warn(
- "The 'plain' password method is deprecated and will be removed in"
- " Werkzeug 3.0. Migrate to the 'scrypt' method.",
- stacklevel=3,
- )
- return password, method
-
method, *args = method.split(":")
salt = salt.encode("utf-8")
password = password.encode("utf-8")
@@ -72,26 +63,20 @@ def _hash_internal(method: str, salt: str, password: str) -> tuple[str, str]:
f"pbkdf2:{hash_name}:{iterations}",
)
else:
- warnings.warn(
- f"The '{method}' password method is deprecated and will be removed in"
- " Werkzeug 3.0. Migrate to the 'scrypt' method.",
- stacklevel=3,
- )
- return hmac.new(salt, password, method).hexdigest(), method
+ raise ValueError(f"Invalid hash method '{method}'.")
def generate_password_hash(
- password: str, method: str = "pbkdf2", salt_length: int = 16
+ password: str, method: str = "scrypt", salt_length: int = 16
) -> str:
"""Securely hash a password for storage. A password can be compared to a stored hash
using :func:`check_password_hash`.
The following methods are supported:
- - ``scrypt``, more secure but not available on PyPy. The parameters are ``n``,
- ``r``, and ``p``, the default is ``scrypt:32768:8:1``. See
- :func:`hashlib.scrypt`.
- - ``pbkdf2``, the default. The parameters are ``hash_method`` and ``iterations``,
+ - ``scrypt``, the default. The parameters are ``n``, ``r``, and ``p``, the default
+ is ``scrypt:32768:8:1``. See :func:`hashlib.scrypt`.
+ - ``pbkdf2``, less secure. The parameters are ``hash_method`` and ``iterations``,
the default is ``pbkdf2:sha256:600000``. See :func:`hashlib.pbkdf2_hmac`.
Default parameters may be updated to reflect current guidelines, and methods may be
diff --git a/venv/Lib/site-packages/werkzeug/serving.py b/venv/Lib/site-packages/werkzeug/serving.py
index c031dc4..ff5eb8c 100644
--- a/venv/Lib/site-packages/werkzeug/serving.py
+++ b/venv/Lib/site-packages/werkzeug/serving.py
@@ -154,9 +154,7 @@ class WSGIRequestHandler(BaseHTTPRequestHandler):
@property
def server_version(self) -> str: # type: ignore
- from . import __version__
-
- return f"Werkzeug/{__version__}"
+ return self.server._server_version
def make_environ(self) -> WSGIEnvironment:
request_url = urlsplit(self.path)
@@ -764,7 +762,7 @@ class BaseWSGIServer(HTTPServer):
if sys.platform == "darwin" and port == 5000:
print(
"On macOS, try disabling the 'AirPlay Receiver' service"
- " from System Preferences -> Sharing.",
+ " from System Preferences -> General -> AirDrop & Handoff.",
file=sys.stderr,
)
@@ -796,6 +794,10 @@ class BaseWSGIServer(HTTPServer):
else:
self.ssl_context = None
+ import importlib.metadata
+
+ self._server_version = f"Werkzeug/{importlib.metadata.version('werkzeug')}"
+
def log(self, type: str, message: str, *args: t.Any) -> None:
_log(type, message, *args)
diff --git a/venv/Lib/site-packages/werkzeug/test.py b/venv/Lib/site-packages/werkzeug/test.py
index 968553f..7b5899a 100644
--- a/venv/Lib/site-packages/werkzeug/test.py
+++ b/venv/Lib/site-packages/werkzeug/test.py
@@ -4,7 +4,6 @@ import dataclasses
import mimetypes
import sys
import typing as t
-import warnings
from collections import defaultdict
from datetime import datetime
from io import BytesIO
@@ -17,7 +16,6 @@ from urllib.parse import urlsplit
from urllib.parse import urlunsplit
from ._internal import _get_environ
-from ._internal import _make_encode_wrapper
from ._internal import _wsgi_decoding_dance
from ._internal import _wsgi_encoding_dance
from .datastructures import Authorization
@@ -58,24 +56,14 @@ def stream_encode_multipart(
use_tempfile: bool = True,
threshold: int = 1024 * 500,
boundary: str | None = None,
- charset: str | None = None,
) -> tuple[t.IO[bytes], int, str]:
"""Encode a dict of values (either strings or file descriptors or
:class:`FileStorage` objects.) into a multipart encoded string stored
in a file descriptor.
- .. versionchanged:: 2.3
- The ``charset`` parameter is deprecated and will be removed in Werkzeug 3.0
+ .. versionchanged:: 3.0
+ The ``charset`` parameter was removed.
"""
- if charset is not None:
- warnings.warn(
- "The 'charset' parameter is deprecated and will be removed in Werkzeug 3.0",
- DeprecationWarning,
- stacklevel=2,
- )
- else:
- charset = "utf-8"
-
if boundary is None:
boundary = f"---------------WerkzeugFormPart_{time()}{random()}"
@@ -144,9 +132,7 @@ def stream_encode_multipart(
if not isinstance(value, str):
value = str(value)
write_binary(encoder.send_event(Field(name=key, headers=Headers())))
- write_binary(
- encoder.send_event(Data(data=value.encode(charset), more_data=False))
- )
+ write_binary(encoder.send_event(Data(data=value.encode(), more_data=False)))
write_binary(encoder.send_event(Epilogue(data=b"")))
@@ -156,18 +142,16 @@ def stream_encode_multipart(
def encode_multipart(
- values: t.Mapping[str, t.Any],
- boundary: str | None = None,
- charset: str | None = None,
+ values: t.Mapping[str, t.Any], boundary: str | None = None
) -> tuple[str, bytes]:
"""Like `stream_encode_multipart` but returns a tuple in the form
(``boundary``, ``data``) where data is bytes.
- .. versionchanged:: 2.3
- The ``charset`` parameter is deprecated and will be removed in Werkzeug 3.0
+ .. versionchanged:: 3.0
+ The ``charset`` parameter was removed.
"""
stream, length, boundary = stream_encode_multipart(
- values, use_tempfile=False, boundary=boundary, charset=charset
+ values, use_tempfile=False, boundary=boundary
)
return boundary, stream.read()
@@ -259,8 +243,8 @@ class EnvironBuilder:
``Authorization`` header value. A ``(username, password)`` tuple
is a shortcut for ``Basic`` authorization.
- .. versionchanged:: 2.3
- The ``charset`` parameter is deprecated and will be removed in Werkzeug 3.0
+ .. versionchanged:: 3.0
+ The ``charset`` parameter was removed.
.. versionchanged:: 2.1
``CONTENT_TYPE`` and ``CONTENT_LENGTH`` are not duplicated as
@@ -328,35 +312,20 @@ class EnvironBuilder:
data: None | (t.IO[bytes] | str | bytes | t.Mapping[str, t.Any]) = None,
environ_base: t.Mapping[str, t.Any] | None = None,
environ_overrides: t.Mapping[str, t.Any] | None = None,
- charset: str | None = None,
mimetype: str | None = None,
json: t.Mapping[str, t.Any] | None = None,
auth: Authorization | tuple[str, str] | None = None,
) -> None:
- path_s = _make_encode_wrapper(path)
- if query_string is not None and path_s("?") in path:
+ if query_string is not None and "?" in path:
raise ValueError("Query string is defined in the path and as an argument")
request_uri = urlsplit(path)
- if query_string is None and path_s("?") in path:
+ if query_string is None and "?" in path:
query_string = request_uri.query
- if charset is not None:
- warnings.warn(
- "The 'charset' parameter is deprecated and will be"
- " removed in Werkzeug 3.0",
- DeprecationWarning,
- stacklevel=2,
- )
- else:
- charset = "utf-8"
-
- self.charset = charset
self.path = iri_to_uri(request_uri.path)
self.request_uri = path
if base_url is not None:
- base_url = iri_to_uri(
- base_url, charset=charset if charset != "utf-8" else None
- )
+ base_url = iri_to_uri(base_url)
self.base_url = base_url # type: ignore
if isinstance(query_string, str):
self.query_string = query_string
@@ -409,7 +378,7 @@ class EnvironBuilder:
if hasattr(data, "read"):
data = data.read()
if isinstance(data, str):
- data = data.encode(self.charset)
+ data = data.encode()
if isinstance(data, bytes):
self.input_stream = BytesIO(data)
if self.content_length is None:
@@ -526,7 +495,7 @@ class EnvironBuilder:
@mimetype.setter
def mimetype(self, value: str) -> None:
- self.content_type = get_content_type(value, self.charset)
+ self.content_type = get_content_type(value, "utf-8")
@property
def mimetype_params(self) -> t.Mapping[str, str]:
@@ -628,7 +597,7 @@ class EnvironBuilder:
"""
if self._query_string is None:
if self._args is not None:
- return _urlencode(self._args, encoding=self.charset)
+ return _urlencode(self._args)
return ""
return self._query_string
@@ -716,13 +685,12 @@ class EnvironBuilder:
input_stream.seek(start_pos)
content_length = end_pos - start_pos
elif mimetype == "multipart/form-data":
- charset = self.charset if self.charset != "utf-8" else None
input_stream, content_length, boundary = stream_encode_multipart(
- CombinedMultiDict([self.form, self.files]), charset=charset
+ CombinedMultiDict([self.form, self.files])
)
content_type = f'{mimetype}; boundary="{boundary}"'
elif mimetype == "application/x-www-form-urlencoded":
- form_encoded = _urlencode(self.form, encoding=self.charset).encode("ascii")
+ form_encoded = _urlencode(self.form).encode("ascii")
content_length = len(form_encoded)
input_stream = BytesIO(form_encoded)
else:
@@ -733,15 +701,15 @@ class EnvironBuilder:
result.update(self.environ_base)
def _path_encode(x: str) -> str:
- return _wsgi_encoding_dance(unquote(x, encoding=self.charset), self.charset)
+ return _wsgi_encoding_dance(unquote(x))
- raw_uri = _wsgi_encoding_dance(self.request_uri, self.charset)
+ raw_uri = _wsgi_encoding_dance(self.request_uri)
result.update(
{
"REQUEST_METHOD": self.method,
"SCRIPT_NAME": _path_encode(self.script_root),
"PATH_INFO": _path_encode(self.path),
- "QUERY_STRING": _wsgi_encoding_dance(self.query_string, self.charset),
+ "QUERY_STRING": _wsgi_encoding_dance(self.query_string),
# Non-standard, added by mod_wsgi, uWSGI
"REQUEST_URI": raw_uri,
# Non-standard, added by gunicorn
@@ -857,20 +825,6 @@ class Client:
self.allow_subdomain_redirects = allow_subdomain_redirects
- @property
- def cookie_jar(self) -> t.Iterable[Cookie] | None:
- warnings.warn(
- "The 'cookie_jar' attribute is a private API and will be removed in"
- " Werkzeug 3.0. Use the 'get_cookie' method instead.",
- DeprecationWarning,
- stacklevel=2,
- )
-
- if self._cookies is None:
- return None
-
- return self._cookies.values()
-
def get_cookie(
self, key: str, domain: str = "localhost", path: str = "/"
) -> Cookie | None:
@@ -894,7 +848,7 @@ class Client:
self,
key: str,
value: str = "",
- *args: t.Any,
+ *,
domain: str = "localhost",
origin_only: bool = True,
path: str = "/",
@@ -920,34 +874,21 @@ class Client:
or as a prefix.
:param kwargs: Passed to :func:`.dump_cookie`.
+ .. versionchanged:: 3.0
+ The parameter ``server_name`` is removed. The first parameter is
+ ``key``. Use the ``domain`` and ``origin_only`` parameters instead.
+
.. versionchanged:: 2.3
The ``origin_only`` parameter was added.
.. versionchanged:: 2.3
The ``domain`` parameter defaults to ``localhost``.
-
- .. versionchanged:: 2.3
- The first parameter ``server_name`` is deprecated and will be removed in
- Werkzeug 3.0. The first parameter is ``key``. Use the ``domain`` and
- ``origin_only`` parameters instead.
"""
if self._cookies is None:
raise TypeError(
"Cookies are disabled. Create a client with 'use_cookies=True'."
)
- if args:
- warnings.warn(
- "The first parameter 'server_name' is no longer used, and will be"
- " removed in Werkzeug 3.0. The positional parameters are 'key' and"
- " 'value'. Use the 'domain' and 'origin_only' parameters instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- domain = key
- key = value
- value = args[0]
-
cookie = Cookie._from_response_header(
domain, "/", dump_cookie(key, value, domain=domain, path=path, **kwargs)
)
@@ -961,10 +902,9 @@ class Client:
def delete_cookie(
self,
key: str,
- *args: t.Any,
+ *,
domain: str = "localhost",
path: str = "/",
- **kwargs: t.Any,
) -> None:
"""Delete a cookie if it exists. Cookies are uniquely identified by
``(domain, path, key)``.
@@ -973,44 +913,21 @@ class Client:
:param domain: The domain the cookie was set for.
:param path: The path the cookie was set for.
+ .. versionchanged:: 3.0
+ The ``server_name`` parameter is removed. The first parameter is
+ ``key``. Use the ``domain`` parameter instead.
+
+ .. versionchanged:: 3.0
+ The ``secure``, ``httponly`` and ``samesite`` parameters are removed.
+
.. versionchanged:: 2.3
The ``domain`` parameter defaults to ``localhost``.
-
- .. versionchanged:: 2.3
- The first parameter ``server_name`` is deprecated and will be removed in
- Werkzeug 3.0. The first parameter is ``key``. Use the ``domain`` parameter
- instead.
-
- .. versionchanged:: 2.3
- The ``secure``, ``httponly`` and ``samesite`` parameters are deprecated and
- will be removed in Werkzeug 2.4.
"""
if self._cookies is None:
raise TypeError(
"Cookies are disabled. Create a client with 'use_cookies=True'."
)
- if args:
- warnings.warn(
- "The first parameter 'server_name' is no longer used, and will be"
- " removed in Werkzeug 2.4. The first parameter is 'key'. Use the"
- " 'domain' parameter instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- domain = key
- key = args[0]
-
- if kwargs:
- kwargs_keys = ", ".join(f"'{k}'" for k in kwargs)
- plural = "parameters are" if len(kwargs) > 1 else "parameter is"
- warnings.warn(
- f"The {kwargs_keys} {plural} deprecated and will be"
- f" removed in Werkzeug 2.4.",
- DeprecationWarning,
- stacklevel=2,
- )
-
self._cookies.pop((domain, path, key), None)
def _add_cookies_to_wsgi(self, environ: WSGIEnvironment) -> None:
diff --git a/venv/Lib/site-packages/werkzeug/urls.py b/venv/Lib/site-packages/werkzeug/urls.py
index f5760eb..4d61e60 100644
--- a/venv/Lib/site-packages/werkzeug/urls.py
+++ b/venv/Lib/site-packages/werkzeug/urls.py
@@ -1,796 +1,16 @@
-"""Functions for working with URLs.
-
-Contains implementations of functions from :mod:`urllib.parse` that
-handle bytes and strings.
-"""
from __future__ import annotations
import codecs
-import os
import re
import typing as t
-import warnings
from urllib.parse import quote
from urllib.parse import unquote
from urllib.parse import urlencode
from urllib.parse import urlsplit
from urllib.parse import urlunsplit
-from ._internal import _check_str_tuple
-from ._internal import _decode_idna
-from ._internal import _make_encode_wrapper
-from ._internal import _to_str
from .datastructures import iter_multi_items
-if t.TYPE_CHECKING:
- from . import datastructures as ds
-
-# A regular expression for what a valid schema looks like
-_scheme_re = re.compile(r"^[a-zA-Z0-9+-.]+$")
-
-# Characters that are safe in any part of an URL.
-_always_safe_chars = (
- "abcdefghijklmnopqrstuvwxyz"
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- "0123456789"
- "-._~"
- "$!'()*+,;" # RFC3986 sub-delims set, not including query string delimiters &=
-)
-_always_safe = frozenset(_always_safe_chars.encode("ascii"))
-
-_hexdigits = "0123456789ABCDEFabcdef"
-_hextobyte = {
- f"{a}{b}".encode("ascii"): int(f"{a}{b}", 16)
- for a in _hexdigits
- for b in _hexdigits
-}
-_bytetohex = [f"%{char:02X}".encode("ascii") for char in range(256)]
-
-
-class _URLTuple(t.NamedTuple):
- scheme: str
- netloc: str
- path: str
- query: str
- fragment: str
-
-
-class BaseURL(_URLTuple):
- """Superclass of :py:class:`URL` and :py:class:`BytesURL`.
-
- .. deprecated:: 2.3
- Will be removed in Werkzeug 3.0. Use the ``urllib.parse`` library instead.
- """
-
- __slots__ = ()
- _at: str
- _colon: str
- _lbracket: str
- _rbracket: str
-
- def __new__(cls, *args: t.Any, **kwargs: t.Any) -> BaseURL:
- warnings.warn(
- f"'werkzeug.urls.{cls.__name__}' is deprecated and will be removed in"
- " Werkzeug 3.0. Use the 'urllib.parse' library instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- return super().__new__(cls, *args, **kwargs)
-
- def __str__(self) -> str:
- return self.to_url()
-
- def replace(self, **kwargs: t.Any) -> BaseURL:
- """Return an URL with the same values, except for those parameters
- given new values by whichever keyword arguments are specified."""
- return self._replace(**kwargs)
-
- @property
- def host(self) -> str | None:
- """The host part of the URL if available, otherwise `None`. The
- host is either the hostname or the IP address mentioned in the
- URL. It will not contain the port.
- """
- return self._split_host()[0]
-
- @property
- def ascii_host(self) -> str | None:
- """Works exactly like :attr:`host` but will return a result that
- is restricted to ASCII. If it finds a netloc that is not ASCII
- it will attempt to idna decode it. This is useful for socket
- operations when the URL might include internationalized characters.
- """
- rv = self.host
- if rv is not None and isinstance(rv, str):
- try:
- rv = rv.encode("idna").decode("ascii")
- except UnicodeError:
- pass
- return rv
-
- @property
- def port(self) -> int | None:
- """The port in the URL as an integer if it was present, `None`
- otherwise. This does not fill in default ports.
- """
- try:
- rv = int(_to_str(self._split_host()[1]))
- if 0 <= rv <= 65535:
- return rv
- except (ValueError, TypeError):
- pass
- return None
-
- @property
- def auth(self) -> str | None:
- """The authentication part in the URL if available, `None`
- otherwise.
- """
- return self._split_netloc()[0]
-
- @property
- def username(self) -> str | None:
- """The username if it was part of the URL, `None` otherwise.
- This undergoes URL decoding and will always be a string.
- """
- rv = self._split_auth()[0]
- if rv is not None:
- return _url_unquote_legacy(rv)
- return None
-
- @property
- def raw_username(self) -> str | None:
- """The username if it was part of the URL, `None` otherwise.
- Unlike :attr:`username` this one is not being decoded.
- """
- return self._split_auth()[0]
-
- @property
- def password(self) -> str | None:
- """The password if it was part of the URL, `None` otherwise.
- This undergoes URL decoding and will always be a string.
- """
- rv = self._split_auth()[1]
- if rv is not None:
- return _url_unquote_legacy(rv)
- return None
-
- @property
- def raw_password(self) -> str | None:
- """The password if it was part of the URL, `None` otherwise.
- Unlike :attr:`password` this one is not being decoded.
- """
- return self._split_auth()[1]
-
- def decode_query(self, *args: t.Any, **kwargs: t.Any) -> ds.MultiDict[str, str]:
- """Decodes the query part of the URL. Ths is a shortcut for
- calling :func:`url_decode` on the query argument. The arguments and
- keyword arguments are forwarded to :func:`url_decode` unchanged.
- """
- return url_decode(self.query, *args, **kwargs)
-
- def join(self, *args: t.Any, **kwargs: t.Any) -> BaseURL:
- """Joins this URL with another one. This is just a convenience
- function for calling into :meth:`url_join` and then parsing the
- return value again.
- """
- return url_parse(url_join(self, *args, **kwargs))
-
- def to_url(self) -> str:
- """Returns a URL string or bytes depending on the type of the
- information stored. This is just a convenience function
- for calling :meth:`url_unparse` for this URL.
- """
- return url_unparse(self)
-
- def encode_netloc(self) -> str:
- """Encodes the netloc part to an ASCII safe URL as bytes."""
- rv = self.ascii_host or ""
- if ":" in rv:
- rv = f"[{rv}]"
- port = self.port
- if port is not None:
- rv = f"{rv}:{port}"
- auth = ":".join(
- filter(
- None,
- [
- url_quote(self.raw_username or "", "utf-8", "strict", "/:%"),
- url_quote(self.raw_password or "", "utf-8", "strict", "/:%"),
- ],
- )
- )
- if auth:
- rv = f"{auth}@{rv}"
- return rv
-
- def decode_netloc(self) -> str:
- """Decodes the netloc part into a string."""
- host = self.host or ""
-
- if isinstance(host, bytes):
- host = host.decode()
-
- rv = _decode_idna(host)
-
- if ":" in rv:
- rv = f"[{rv}]"
- port = self.port
- if port is not None:
- rv = f"{rv}:{port}"
- auth = ":".join(
- filter(
- None,
- [
- _url_unquote_legacy(self.raw_username or "", "/:%@"),
- _url_unquote_legacy(self.raw_password or "", "/:%@"),
- ],
- )
- )
- if auth:
- rv = f"{auth}@{rv}"
- return rv
-
- def to_uri_tuple(self) -> BaseURL:
- """Returns a :class:`BytesURL` tuple that holds a URI. This will
- encode all the information in the URL properly to ASCII using the
- rules a web browser would follow.
-
- It's usually more interesting to directly call :meth:`iri_to_uri` which
- will return a string.
- """
- return url_parse(iri_to_uri(self))
-
- def to_iri_tuple(self) -> BaseURL:
- """Returns a :class:`URL` tuple that holds a IRI. This will try
- to decode as much information as possible in the URL without
- losing information similar to how a web browser does it for the
- URL bar.
-
- It's usually more interesting to directly call :meth:`uri_to_iri` which
- will return a string.
- """
- return url_parse(uri_to_iri(self))
-
- def get_file_location(
- self, pathformat: str | None = None
- ) -> tuple[str | None, str | None]:
- """Returns a tuple with the location of the file in the form
- ``(server, location)``. If the netloc is empty in the URL or
- points to localhost, it's represented as ``None``.
-
- The `pathformat` by default is autodetection but needs to be set
- when working with URLs of a specific system. The supported values
- are ``'windows'`` when working with Windows or DOS paths and
- ``'posix'`` when working with posix paths.
-
- If the URL does not point to a local file, the server and location
- are both represented as ``None``.
-
- :param pathformat: The expected format of the path component.
- Currently ``'windows'`` and ``'posix'`` are
- supported. Defaults to ``None`` which is
- autodetect.
- """
- if self.scheme != "file":
- return None, None
-
- path = url_unquote(self.path)
- host = self.netloc or None
-
- if pathformat is None:
- if os.name == "nt":
- pathformat = "windows"
- else:
- pathformat = "posix"
-
- if pathformat == "windows":
- if path[:1] == "/" and path[1:2].isalpha() and path[2:3] in "|:":
- path = f"{path[1:2]}:{path[3:]}"
- windows_share = path[:3] in ("\\" * 3, "/" * 3)
- import ntpath
-
- path = ntpath.normpath(path)
- # Windows shared drives are represented as ``\\host\\directory``.
- # That results in a URL like ``file://///host/directory``, and a
- # path like ``///host/directory``. We need to special-case this
- # because the path contains the hostname.
- if windows_share and host is None:
- parts = path.lstrip("\\").split("\\", 1)
- if len(parts) == 2:
- host, path = parts
- else:
- host = parts[0]
- path = ""
- elif pathformat == "posix":
- import posixpath
-
- path = posixpath.normpath(path)
- else:
- raise TypeError(f"Invalid path format {pathformat!r}")
-
- if host in ("127.0.0.1", "::1", "localhost"):
- host = None
-
- return host, path
-
- def _split_netloc(self) -> tuple[str | None, str]:
- if self._at in self.netloc:
- auth, _, netloc = self.netloc.partition(self._at)
- return auth, netloc
- return None, self.netloc
-
- def _split_auth(self) -> tuple[str | None, str | None]:
- auth = self._split_netloc()[0]
- if not auth:
- return None, None
- if self._colon not in auth:
- return auth, None
-
- username, _, password = auth.partition(self._colon)
- return username, password
-
- def _split_host(self) -> tuple[str | None, str | None]:
- rv = self._split_netloc()[1]
- if not rv:
- return None, None
-
- if not rv.startswith(self._lbracket):
- if self._colon in rv:
- host, _, port = rv.partition(self._colon)
- return host, port
- return rv, None
-
- idx = rv.find(self._rbracket)
- if idx < 0:
- return rv, None
-
- host = rv[1:idx]
- rest = rv[idx + 1 :]
- if rest.startswith(self._colon):
- return host, rest[1:]
- return host, None
-
-
-class URL(BaseURL):
- """Represents a parsed URL. This behaves like a regular tuple but
- also has some extra attributes that give further insight into the
- URL.
-
- .. deprecated:: 2.3
- Will be removed in Werkzeug 3.0. Use the ``urllib.parse`` library instead.
- """
-
- __slots__ = ()
- _at = "@"
- _colon = ":"
- _lbracket = "["
- _rbracket = "]"
-
- def encode(self, charset: str = "utf-8", errors: str = "replace") -> BytesURL:
- """Encodes the URL to a tuple made out of bytes. The charset is
- only being used for the path, query and fragment.
- """
- return BytesURL(
- self.scheme.encode("ascii"),
- self.encode_netloc(),
- self.path.encode(charset, errors),
- self.query.encode(charset, errors),
- self.fragment.encode(charset, errors),
- )
-
-
-class BytesURL(BaseURL):
- """Represents a parsed URL in bytes.
-
- .. deprecated:: 2.3
- Will be removed in Werkzeug 3.0. Use the ``urllib.parse`` library instead.
- """
-
- __slots__ = ()
- _at = b"@" # type: ignore
- _colon = b":" # type: ignore
- _lbracket = b"[" # type: ignore
- _rbracket = b"]" # type: ignore
-
- def __str__(self) -> str:
- return self.to_url().decode("utf-8", "replace") # type: ignore
-
- def encode_netloc(self) -> bytes: # type: ignore
- """Returns the netloc unchanged as bytes."""
- return self.netloc # type: ignore
-
- def decode(self, charset: str = "utf-8", errors: str = "replace") -> URL:
- """Decodes the URL to a tuple made out of strings. The charset is
- only being used for the path, query and fragment.
- """
- return URL(
- self.scheme.decode("ascii"), # type: ignore
- self.decode_netloc(),
- self.path.decode(charset, errors), # type: ignore
- self.query.decode(charset, errors), # type: ignore
- self.fragment.decode(charset, errors), # type: ignore
- )
-
-
-_unquote_maps: dict[frozenset[int], dict[bytes, int]] = {frozenset(): _hextobyte}
-
-
-def _unquote_to_bytes(string: str | bytes, unsafe: str | bytes = "") -> bytes:
- if isinstance(string, str):
- string = string.encode("utf-8")
-
- if isinstance(unsafe, str):
- unsafe = unsafe.encode("utf-8")
-
- unsafe = frozenset(bytearray(unsafe))
- groups = iter(string.split(b"%"))
- result = bytearray(next(groups, b""))
-
- try:
- hex_to_byte = _unquote_maps[unsafe]
- except KeyError:
- hex_to_byte = _unquote_maps[unsafe] = {
- h: b for h, b in _hextobyte.items() if b not in unsafe
- }
-
- for group in groups:
- code = group[:2]
-
- if code in hex_to_byte:
- result.append(hex_to_byte[code])
- result.extend(group[2:])
- else:
- result.append(37) # %
- result.extend(group)
-
- return bytes(result)
-
-
-def _url_encode_impl(
- obj: t.Mapping[str, str] | t.Iterable[tuple[str, str]],
- charset: str,
- sort: bool,
- key: t.Callable[[tuple[str, str]], t.Any] | None,
-) -> t.Iterator[str]:
- from .datastructures import iter_multi_items
-
- iterable: t.Iterable[tuple[str, str]] = iter_multi_items(obj)
-
- if sort:
- iterable = sorted(iterable, key=key)
-
- for key_str, value_str in iterable:
- if value_str is None:
- continue
-
- if not isinstance(key_str, bytes):
- key_bytes = str(key_str).encode(charset)
- else:
- key_bytes = key_str
-
- if not isinstance(value_str, bytes):
- value_bytes = str(value_str).encode(charset)
- else:
- value_bytes = value_str
-
- yield f"{_fast_url_quote_plus(key_bytes)}={_fast_url_quote_plus(value_bytes)}"
-
-
-def _url_unquote_legacy(value: str, unsafe: str = "") -> str:
- try:
- return url_unquote(value, charset="utf-8", errors="strict", unsafe=unsafe)
- except UnicodeError:
- return url_unquote(value, charset="latin1", unsafe=unsafe)
-
-
-def url_parse(
- url: str, scheme: str | None = None, allow_fragments: bool = True
-) -> BaseURL:
- """Parses a URL from a string into a :class:`URL` tuple. If the URL
- is lacking a scheme it can be provided as second argument. Otherwise,
- it is ignored. Optionally fragments can be stripped from the URL
- by setting `allow_fragments` to `False`.
-
- The inverse of this function is :func:`url_unparse`.
-
- :param url: the URL to parse.
- :param scheme: the default schema to use if the URL is schemaless.
- :param allow_fragments: if set to `False` a fragment will be removed
- from the URL.
-
- .. deprecated:: 2.3
- Will be removed in Werkzeug 3.0. Use ``urllib.parse.urlsplit`` instead.
- """
- warnings.warn(
- "'werkzeug.urls.url_parse' is deprecated and will be removed in Werkzeug 3.0."
- " Use 'urllib.parse.urlsplit' instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- s = _make_encode_wrapper(url)
- is_text_based = isinstance(url, str)
-
- if scheme is None:
- scheme = s("")
- netloc = query = fragment = s("")
- i = url.find(s(":"))
- if i > 0 and _scheme_re.match(_to_str(url[:i], errors="replace")):
- # make sure "iri" is not actually a port number (in which case
- # "scheme" is really part of the path)
- rest = url[i + 1 :]
- if not rest or any(c not in s("0123456789") for c in rest):
- # not a port number
- scheme, url = url[:i].lower(), rest
-
- if url[:2] == s("//"):
- delim = len(url)
- for c in s("/?#"):
- wdelim = url.find(c, 2)
- if wdelim >= 0:
- delim = min(delim, wdelim)
- netloc, url = url[2:delim], url[delim:]
- if (s("[") in netloc and s("]") not in netloc) or (
- s("]") in netloc and s("[") not in netloc
- ):
- raise ValueError("Invalid IPv6 URL")
-
- if allow_fragments and s("#") in url:
- url, fragment = url.split(s("#"), 1)
- if s("?") in url:
- url, query = url.split(s("?"), 1)
-
- result_type = URL if is_text_based else BytesURL
-
- return result_type(scheme, netloc, url, query, fragment)
-
-
-def _make_fast_url_quote(
- charset: str = "utf-8",
- errors: str = "strict",
- safe: str | bytes = "/:",
- unsafe: str | bytes = "",
-) -> t.Callable[[bytes], str]:
- """Precompile the translation table for a URL encoding function.
-
- Unlike :func:`url_quote`, the generated function only takes the
- string to quote.
-
- :param charset: The charset to encode the result with.
- :param errors: How to handle encoding errors.
- :param safe: An optional sequence of safe characters to never encode.
- :param unsafe: An optional sequence of unsafe characters to always encode.
- """
- if isinstance(safe, str):
- safe = safe.encode(charset, errors)
-
- if isinstance(unsafe, str):
- unsafe = unsafe.encode(charset, errors)
-
- safe = (frozenset(bytearray(safe)) | _always_safe) - frozenset(bytearray(unsafe))
- table = [chr(c) if c in safe else f"%{c:02X}" for c in range(256)]
-
- def quote(string: bytes) -> str:
- return "".join([table[c] for c in string])
-
- return quote
-
-
-_fast_url_quote = _make_fast_url_quote()
-_fast_quote_plus = _make_fast_url_quote(safe=" ", unsafe="+")
-
-
-def _fast_url_quote_plus(string: bytes) -> str:
- return _fast_quote_plus(string).replace(" ", "+")
-
-
-def url_quote(
- string: str | bytes,
- charset: str = "utf-8",
- errors: str = "strict",
- safe: str | bytes = "/:",
- unsafe: str | bytes = "",
-) -> str:
- """URL encode a single string with a given encoding.
-
- :param s: the string to quote.
- :param charset: the charset to be used.
- :param safe: an optional sequence of safe characters.
- :param unsafe: an optional sequence of unsafe characters.
-
- .. deprecated:: 2.3
- Will be removed in Werkzeug 3.0. Use ``urllib.parse.quote`` instead.
-
- .. versionadded:: 0.9.2
- The `unsafe` parameter was added.
- """
- warnings.warn(
- "'werkzeug.urls.url_quote' is deprecated and will be removed in Werkzeug 3.0."
- " Use 'urllib.parse.quote' instead.",
- DeprecationWarning,
- stacklevel=2,
- )
-
- if not isinstance(string, (str, bytes, bytearray)):
- string = str(string)
- if isinstance(string, str):
- string = string.encode(charset, errors)
- if isinstance(safe, str):
- safe = safe.encode(charset, errors)
- if isinstance(unsafe, str):
- unsafe = unsafe.encode(charset, errors)
- safe = (frozenset(bytearray(safe)) | _always_safe) - frozenset(bytearray(unsafe))
- rv = bytearray()
- for char in bytearray(string):
- if char in safe:
- rv.append(char)
- else:
- rv.extend(_bytetohex[char])
- return bytes(rv).decode(charset)
-
-
-def url_quote_plus(
- string: str, charset: str = "utf-8", errors: str = "strict", safe: str = ""
-) -> str:
- """URL encode a single string with the given encoding and convert
- whitespace to "+".
-
- :param s: The string to quote.
- :param charset: The charset to be used.
- :param safe: An optional sequence of safe characters.
-
- .. deprecated:: 2.3
- Will be removed in Werkzeug 3.0. Use ``urllib.parse.quote_plus`` instead.
- """
- warnings.warn(
- "'werkzeug.urls.url_quote_plus' is deprecated and will be removed in Werkzeug"
- " 2.4. Use 'urllib.parse.quote_plus' instead.",
- DeprecationWarning,
- stacklevel=2,
- )
-
- return url_quote(string, charset, errors, safe + " ", "+").replace(" ", "+")
-
-
-def url_unparse(components: tuple[str, str, str, str, str]) -> str:
- """The reverse operation to :meth:`url_parse`. This accepts arbitrary
- as well as :class:`URL` tuples and returns a URL as a string.
-
- :param components: the parsed URL as tuple which should be converted
- into a URL string.
-
- .. deprecated:: 2.3
- Will be removed in Werkzeug 3.0. Use ``urllib.parse.urlunsplit`` instead.
- """
- warnings.warn(
- "'werkzeug.urls.url_unparse' is deprecated and will be removed in Werkzeug 3.0."
- " Use 'urllib.parse.urlunsplit' instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- _check_str_tuple(components)
- scheme, netloc, path, query, fragment = components
- s = _make_encode_wrapper(scheme)
- url = s("")
-
- # We generally treat file:///x and file:/x the same which is also
- # what browsers seem to do. This also allows us to ignore a schema
- # register for netloc utilization or having to differentiate between
- # empty and missing netloc.
- if netloc or (scheme and path.startswith(s("/"))):
- if path and path[:1] != s("/"):
- path = s("/") + path
- url = s("//") + (netloc or s("")) + path
- elif path:
- url += path
- if scheme:
- url = scheme + s(":") + url
- if query:
- url = url + s("?") + query
- if fragment:
- url = url + s("#") + fragment
- return url
-
-
-def url_unquote(
- s: str | bytes,
- charset: str = "utf-8",
- errors: str = "replace",
- unsafe: str = "",
-) -> str:
- """URL decode a single string with a given encoding. If the charset
- is set to `None` no decoding is performed and raw bytes are
- returned.
-
- :param s: the string to unquote.
- :param charset: the charset of the query string. If set to `None`
- no decoding will take place.
- :param errors: the error handling for the charset decoding.
-
- .. deprecated:: 2.3
- Will be removed in Werkzeug 3.0. Use ``urllib.parse.unquote`` instead.
- """
- warnings.warn(
- "'werkzeug.urls.url_unquote' is deprecated and will be removed in Werkzeug 3.0."
- " Use 'urllib.parse.unquote' instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- rv = _unquote_to_bytes(s, unsafe)
- if charset is None:
- return rv
- return rv.decode(charset, errors)
-
-
-def url_unquote_plus(
- s: str | bytes, charset: str = "utf-8", errors: str = "replace"
-) -> str:
- """URL decode a single string with the given `charset` and decode "+" to
- whitespace.
-
- Per default encoding errors are ignored. If you want a different behavior
- you can set `errors` to ``'replace'`` or ``'strict'``.
-
- :param s: The string to unquote.
- :param charset: the charset of the query string. If set to `None`
- no decoding will take place.
- :param errors: The error handling for the `charset` decoding.
-
- .. deprecated:: 2.3
- Will be removed in Werkzeug 3.0. Use ``urllib.parse.unquote_plus`` instead.
- """
- warnings.warn(
- "'werkzeug.urls.url_unquote_plus' is deprecated and will be removed in Werkzeug"
- " 2.4. Use 'urllib.parse.unquote_plus' instead.",
- DeprecationWarning,
- stacklevel=2,
- )
-
- if isinstance(s, str):
- s = s.replace("+", " ")
- else:
- s = s.replace(b"+", b" ")
-
- return url_unquote(s, charset, errors)
-
-
-def url_fix(s: str, charset: str = "utf-8") -> str:
- r"""Sometimes you get an URL by a user that just isn't a real URL because
- it contains unsafe characters like ' ' and so on. This function can fix
- some of the problems in a similar way browsers handle data entered by the
- user:
-
- >>> url_fix('http://de.wikipedia.org/wiki/Elf (Begriffskl\xe4rung)')
- 'http://de.wikipedia.org/wiki/Elf%20(Begriffskl%C3%A4rung)'
-
- :param s: the string with the URL to fix.
- :param charset: The target charset for the URL if the url was given
- as a string.
-
- .. deprecated:: 2.3
- Will be removed in Werkzeug 3.0.
- """
- warnings.warn(
- "'werkzeug.urls.url_fix' is deprecated and will be removed in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- # First step is to switch to text processing and to convert
- # backslashes (which are invalid in URLs anyways) to slashes. This is
- # consistent with what Chrome does.
- s = _to_str(s, charset, "replace").replace("\\", "/")
-
- # For the specific case that we look like a malformed windows URL
- # we want to fix this up manually:
- if s.startswith("file://") and s[7:8].isalpha() and s[8:10] in (":/", "|/"):
- s = f"file:///{s[7:]}"
-
- url = url_parse(s)
- path = url_quote(url.path, charset, safe="/%+$!*'(),")
- qs = url_quote_plus(url.query, charset, safe=":&%=+$!*'(),")
- anchor = url_quote_plus(url.fragment, charset, safe=":&%=+$!*'(),")
- return url_unparse((url.scheme, url.encode_netloc(), path, qs, anchor))
-
def _codec_error_url_quote(e: UnicodeError) -> tuple[str, int]:
"""Used in :func:`uri_to_iri` after unquoting to re-quote any
@@ -805,7 +25,7 @@ def _codec_error_url_quote(e: UnicodeError) -> tuple[str, int]:
codecs.register_error("werkzeug.url_quote", _codec_error_url_quote)
-def _make_unquote_part(name: str, chars: str) -> t.Callable[[str, str, str], str]:
+def _make_unquote_part(name: str, chars: str) -> t.Callable[[str], str]:
"""Create a function that unquotes all percent encoded characters except those
given. This allows working with unquoted characters if possible while not changing
the meaning of a given part of a URL.
@@ -813,12 +33,12 @@ def _make_unquote_part(name: str, chars: str) -> t.Callable[[str, str, str], str
choices = "|".join(f"{ord(c):02X}" for c in sorted(chars))
pattern = re.compile(f"((?:%(?:{choices}))+)", re.I)
- def _unquote_partial(value: str, encoding: str, errors: str) -> str:
+ def _unquote_partial(value: str) -> str:
parts = iter(pattern.split(value))
out = []
for part in parts:
- out.append(unquote(part, encoding, errors))
+ out.append(unquote(part, "utf-8", "werkzeug.url_quote"))
out.append(next(parts, ""))
return "".join(out)
@@ -837,11 +57,7 @@ _unquote_path = _make_unquote_part("path", _always_unsafe + "/?#")
_unquote_user = _make_unquote_part("user", _always_unsafe + ":@/?#")
-def uri_to_iri(
- uri: str | tuple[str, str, str, str, str],
- charset: str | None = None,
- errors: str | None = None,
-) -> str:
+def uri_to_iri(uri: str) -> str:
"""Convert a URI to an IRI. All valid UTF-8 characters are unquoted,
leaving all reserved and invalid characters quoted. If the URL has
a domain, it is decoded from Punycode.
@@ -850,13 +66,10 @@ def uri_to_iri(
'http://\\u2603.net/p\\xe5th?q=\\xe8ry%DF'
:param uri: The URI to convert.
- :param charset: The encoding to encode unquoted bytes with.
- :param errors: Error handler to use during ``bytes.encode``. By
- default, invalid bytes are left quoted.
- .. versionchanged:: 2.3
- Passing a tuple or bytes, and the ``charset`` and ``errors`` parameters, are
- deprecated and will be removed in Werkzeug 3.0.
+ .. versionchanged:: 3.0
+ Passing a tuple or bytes, and the ``charset`` and ``errors`` parameters,
+ are removed.
.. versionchanged:: 2.3
Which characters remain quoted is specific to each part of the URL.
@@ -868,45 +81,10 @@ def uri_to_iri(
.. versionadded:: 0.6
"""
- if isinstance(uri, tuple):
- warnings.warn(
- "Passing a tuple is deprecated and will not be supported in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- uri = urlunsplit(uri)
-
- if isinstance(uri, bytes):
- warnings.warn(
- "Passing bytes is deprecated and will not be supported in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- uri = uri.decode()
-
- if charset is not None:
- warnings.warn(
- "The 'charset' parameter is deprecated and will be removed"
- " in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- else:
- charset = "utf-8"
-
- if errors is not None:
- warnings.warn(
- "The 'errors' parameter is deprecated and will be removed in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- else:
- errors = "werkzeug.url_quote"
-
parts = urlsplit(uri)
- path = _unquote_path(parts.path, charset, errors)
- query = _unquote_query(parts.query, charset, errors)
- fragment = _unquote_fragment(parts.fragment, charset, errors)
+ path = _unquote_path(parts.path)
+ query = _unquote_query(parts.query)
+ fragment = _unquote_fragment(parts.fragment)
if parts.hostname:
netloc = _decode_idna(parts.hostname)
@@ -920,22 +98,18 @@ def uri_to_iri(
netloc = f"{netloc}:{parts.port}"
if parts.username:
- auth = _unquote_user(parts.username, charset, errors)
+ auth = _unquote_user(parts.username)
if parts.password:
- auth = f"{auth}:{_unquote_user(parts.password, charset, errors)}"
+ password = _unquote_user(parts.password)
+ auth = f"{auth}:{password}"
netloc = f"{auth}@{netloc}"
return urlunsplit((parts.scheme, netloc, path, query, fragment))
-def iri_to_uri(
- iri: str | tuple[str, str, str, str, str],
- charset: str | None = None,
- errors: str | None = None,
- safe_conversion: bool | None = None,
-) -> str:
+def iri_to_uri(iri: str) -> str:
"""Convert an IRI to a URI. All non-ASCII and unsafe characters are
quoted. If the URL has a domain, it is encoded to Punycode.
@@ -943,20 +117,14 @@ def iri_to_uri(
'http://xn--n3h.net/p%C3%A5th?q=%C3%A8ry%DF'
:param iri: The IRI to convert.
- :param charset: The encoding of the IRI.
- :param errors: Error handler to use during ``bytes.encode``.
- .. versionchanged:: 2.3
- Passing a tuple or bytes, and the ``charset`` and ``errors`` parameters, are
- deprecated and will be removed in Werkzeug 3.0.
+ .. versionchanged:: 3.0
+ Passing a tuple or bytes, the ``charset`` and ``errors`` parameters,
+ and the ``safe_conversion`` parameter, are removed.
.. versionchanged:: 2.3
Which characters remain unquoted is specific to each part of the URL.
- .. versionchanged:: 2.3
- The ``safe_conversion`` parameter is deprecated and will be removed in Werkzeug
- 2.4.
-
.. versionchanged:: 0.15
All reserved characters remain unquoted. Previously, only some reserved
characters were left unquoted.
@@ -966,69 +134,12 @@ def iri_to_uri(
.. versionadded:: 0.6
"""
- if charset is not None:
- warnings.warn(
- "The 'charset' parameter is deprecated and will be removed"
- " in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- else:
- charset = "utf-8"
-
- if isinstance(iri, tuple):
- warnings.warn(
- "Passing a tuple is deprecated and will not be supported in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- iri = urlunsplit(iri)
-
- if isinstance(iri, bytes):
- warnings.warn(
- "Passing bytes is deprecated and will not be supported in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- iri = iri.decode(charset)
-
- if errors is not None:
- warnings.warn(
- "The 'errors' parameter is deprecated and will be removed in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- else:
- errors = "strict"
-
- if safe_conversion is not None:
- warnings.warn(
- "The 'safe_conversion' parameter is deprecated and will be removed in"
- " Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
-
- if safe_conversion:
- # If we're not sure if it's safe to normalize the URL, and it only contains
- # ASCII characters, return it as-is.
- try:
- ascii_iri = iri.encode("ascii")
-
- # Only return if it doesn't have whitespace. (Why?)
- if len(ascii_iri.split()) == 1:
- return iri
- except UnicodeError:
- pass
-
parts = urlsplit(iri)
# safe = https://url.spec.whatwg.org/#url-path-segment-string
# as well as percent for things that are already quoted
- path = quote(parts.path, safe="%!$&'()*+,/:;=@", encoding=charset, errors=errors)
- query = quote(parts.query, safe="%!$&'()*+,/:;=?@", encoding=charset, errors=errors)
- fragment = quote(
- parts.fragment, safe="%!#$&'()*+,/:;=?@", encoding=charset, errors=errors
- )
+ path = quote(parts.path, safe="%!$&'()*+,/:;=@")
+ query = quote(parts.query, safe="%!$&'()*+,/:;=?@")
+ fragment = quote(parts.fragment, safe="%!#$&'()*+,/:;=?@")
if parts.hostname:
netloc = parts.hostname.encode("idna").decode("ascii")
@@ -1045,8 +156,8 @@ def iri_to_uri(
auth = quote(parts.username, safe="%!$&'()*+,;=")
if parts.password:
- pass_quoted = quote(parts.password, safe="%!$&'()*+,;=")
- auth = f"{auth}:{pass_quoted}"
+ password = quote(parts.password, safe="%!$&'()*+,;=")
+ auth = f"{auth}:{password}"
netloc = f"{auth}@{netloc}"
@@ -1074,304 +185,32 @@ def _invalid_iri_to_uri(iri: str) -> str:
return iri_to_uri(iri)
-def url_decode(
- s: t.AnyStr,
- charset: str = "utf-8",
- include_empty: bool = True,
- errors: str = "replace",
- separator: str = "&",
- cls: type[ds.MultiDict] | None = None,
-) -> ds.MultiDict[str, str]:
- """Parse a query string and return it as a :class:`MultiDict`.
+def _decode_idna(domain: str) -> str:
+ try:
+ data = domain.encode("ascii")
+ except UnicodeEncodeError:
+ # If the domain is not ASCII, it's decoded already.
+ return domain
- :param s: The query string to parse.
- :param charset: Decode bytes to string with this charset. If not
- given, bytes are returned as-is.
- :param include_empty: Include keys with empty values in the dict.
- :param errors: Error handling behavior when decoding bytes.
- :param separator: Separator character between pairs.
- :param cls: Container to hold result instead of :class:`MultiDict`.
+ try:
+ # Try decoding in one shot.
+ return data.decode("idna")
+ except UnicodeDecodeError:
+ pass
- .. deprecated:: 2.3
- Will be removed in Werkzeug 3.0. Use ``urllib.parse.parse_qs`` instead.
+ # Decode each part separately, leaving invalid parts as punycode.
+ parts = []
- .. versionchanged:: 2.1
- The ``decode_keys`` parameter was removed.
+ for part in data.split(b"."):
+ try:
+ parts.append(part.decode("idna"))
+ except UnicodeDecodeError:
+ parts.append(part.decode("ascii"))
- .. versionchanged:: 0.5
- In previous versions ";" and "&" could be used for url decoding.
- Now only "&" is supported. If you want to use ";", a different
- ``separator`` can be provided.
-
- .. versionchanged:: 0.5
- The ``cls`` parameter was added.
- """
- warnings.warn(
- "'werkzeug.urls.url_decode' is deprecated and will be removed in Werkzeug 2.4."
- " Use 'urllib.parse.parse_qs' instead.",
- DeprecationWarning,
- stacklevel=2,
- )
-
- if cls is None:
- from .datastructures import MultiDict # noqa: F811
-
- cls = MultiDict
- if isinstance(s, str) and not isinstance(separator, str):
- separator = separator.decode(charset or "ascii")
- elif isinstance(s, bytes) and not isinstance(separator, bytes):
- separator = separator.encode(charset or "ascii") # type: ignore
- return cls(
- _url_decode_impl(
- s.split(separator), charset, include_empty, errors # type: ignore
- )
- )
+ return ".".join(parts)
-def url_decode_stream(
- stream: t.IO[bytes],
- charset: str = "utf-8",
- include_empty: bool = True,
- errors: str = "replace",
- separator: bytes = b"&",
- cls: type[ds.MultiDict] | None = None,
- limit: int | None = None,
-) -> ds.MultiDict[str, str]:
- """Works like :func:`url_decode` but decodes a stream. The behavior
- of stream and limit follows functions like
- :func:`~werkzeug.wsgi.make_line_iter`. The generator of pairs is
- directly fed to the `cls` so you can consume the data while it's
- parsed.
-
- :param stream: a stream with the encoded querystring
- :param charset: the charset of the query string. If set to `None`
- no decoding will take place.
- :param include_empty: Set to `False` if you don't want empty values to
- appear in the dict.
- :param errors: the decoding error behavior.
- :param separator: the pair separator to be used, defaults to ``&``
- :param cls: an optional dict class to use. If this is not specified
- or `None` the default :class:`MultiDict` is used.
- :param limit: the content length of the URL data. Not necessary if
- a limited stream is provided.
-
- .. deprecated:: 2.3
- Will be removed in Werkzeug 2.4. Use ``urllib.parse.parse_qs`` instead.
-
- .. versionchanged:: 2.1
- The ``decode_keys`` and ``return_iterator`` parameters were removed.
-
- .. versionadded:: 0.8
- """
- warnings.warn(
- "'werkzeug.urls.url_decode_stream' is deprecated and will be removed in"
- " Werkzeug 2.4. Use 'urllib.parse.parse_qs' instead.",
- DeprecationWarning,
- stacklevel=2,
- )
-
- from .wsgi import make_chunk_iter
-
- pair_iter = make_chunk_iter(stream, separator, limit)
- decoder = _url_decode_impl(pair_iter, charset, include_empty, errors)
-
- if cls is None:
- from .datastructures import MultiDict # noqa: F811
-
- cls = MultiDict
-
- return cls(decoder)
-
-
-def _url_decode_impl(
- pair_iter: t.Iterable[t.AnyStr], charset: str, include_empty: bool, errors: str
-) -> t.Iterator[tuple[str, str]]:
- for pair in pair_iter:
- if not pair:
- continue
- s = _make_encode_wrapper(pair)
- equal = s("=")
- if equal in pair:
- key, value = pair.split(equal, 1)
- else:
- if not include_empty:
- continue
- key = pair
- value = s("")
- yield (
- url_unquote_plus(key, charset, errors),
- url_unquote_plus(value, charset, errors),
- )
-
-
-def url_encode(
- obj: t.Mapping[str, str] | t.Iterable[tuple[str, str]],
- charset: str = "utf-8",
- sort: bool = False,
- key: t.Callable[[tuple[str, str]], t.Any] | None = None,
- separator: str = "&",
-) -> str:
- """URL encode a dict/`MultiDict`. If a value is `None` it will not appear
- in the result string. Per default only values are encoded into the target
- charset strings.
-
- :param obj: the object to encode into a query string.
- :param charset: the charset of the query string.
- :param sort: set to `True` if you want parameters to be sorted by `key`.
- :param separator: the separator to be used for the pairs.
- :param key: an optional function to be used for sorting. For more details
- check out the :func:`sorted` documentation.
-
- .. deprecated:: 2.3
- Will be removed in Werkzeug 2.4. Use ``urllib.parse.urlencode`` instead.
-
- .. versionchanged:: 2.1
- The ``encode_keys`` parameter was removed.
-
- .. versionchanged:: 0.5
- Added the ``sort``, ``key``, and ``separator`` parameters.
- """
- warnings.warn(
- "'werkzeug.urls.url_encode' is deprecated and will be removed in Werkzeug 2.4."
- " Use 'urllib.parse.urlencode' instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- separator = _to_str(separator, "ascii")
- return separator.join(_url_encode_impl(obj, charset, sort, key))
-
-
-def url_encode_stream(
- obj: t.Mapping[str, str] | t.Iterable[tuple[str, str]],
- stream: t.IO[str] | None = None,
- charset: str = "utf-8",
- sort: bool = False,
- key: t.Callable[[tuple[str, str]], t.Any] | None = None,
- separator: str = "&",
-) -> None:
- """Like :meth:`url_encode` but writes the results to a stream
- object. If the stream is `None` a generator over all encoded
- pairs is returned.
-
- :param obj: the object to encode into a query string.
- :param stream: a stream to write the encoded object into or `None` if
- an iterator over the encoded pairs should be returned. In
- that case the separator argument is ignored.
- :param charset: the charset of the query string.
- :param sort: set to `True` if you want parameters to be sorted by `key`.
- :param separator: the separator to be used for the pairs.
- :param key: an optional function to be used for sorting. For more details
- check out the :func:`sorted` documentation.
-
- .. deprecated:: 2.3
- Will be removed in Werkzeug 2.4. Use ``urllib.parse.urlencode`` instead.
-
- .. versionchanged:: 2.1
- The ``encode_keys`` parameter was removed.
-
- .. versionadded:: 0.8
- """
- warnings.warn(
- "'werkzeug.urls.url_encode_stream' is deprecated and will be removed in"
- " Werkzeug 2.4. Use 'urllib.parse.urlencode' instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- separator = _to_str(separator, "ascii")
- gen = _url_encode_impl(obj, charset, sort, key)
- if stream is None:
- return gen # type: ignore
- for idx, chunk in enumerate(gen):
- if idx:
- stream.write(separator)
- stream.write(chunk)
- return None
-
-
-def url_join(
- base: str | tuple[str, str, str, str, str],
- url: str | tuple[str, str, str, str, str],
- allow_fragments: bool = True,
-) -> str:
- """Join a base URL and a possibly relative URL to form an absolute
- interpretation of the latter.
-
- :param base: the base URL for the join operation.
- :param url: the URL to join.
- :param allow_fragments: indicates whether fragments should be allowed.
-
- .. deprecated:: 2.3
- Will be removed in Werkzeug 2.4. Use ``urllib.parse.urljoin`` instead.
- """
- warnings.warn(
- "'werkzeug.urls.url_join' is deprecated and will be removed in Werkzeug 2.4."
- " Use 'urllib.parse.urljoin' instead.",
- DeprecationWarning,
- stacklevel=2,
- )
-
- if isinstance(base, tuple):
- base = url_unparse(base)
- if isinstance(url, tuple):
- url = url_unparse(url)
-
- _check_str_tuple((base, url))
- s = _make_encode_wrapper(base)
-
- if not base:
- return url
- if not url:
- return base
-
- bscheme, bnetloc, bpath, bquery, bfragment = url_parse(
- base, allow_fragments=allow_fragments
- )
- scheme, netloc, path, query, fragment = url_parse(url, bscheme, allow_fragments)
- if scheme != bscheme:
- return url
- if netloc:
- return url_unparse((scheme, netloc, path, query, fragment))
- netloc = bnetloc
-
- if path[:1] == s("/"):
- segments = path.split(s("/"))
- elif not path:
- segments = bpath.split(s("/"))
- if not query:
- query = bquery
- else:
- segments = bpath.split(s("/"))[:-1] + path.split(s("/"))
-
- # If the rightmost part is "./" we want to keep the slash but
- # remove the dot.
- if segments[-1] == s("."):
- segments[-1] = s("")
-
- # Resolve ".." and "."
- segments = [segment for segment in segments if segment != s(".")]
- while True:
- i = 1
- n = len(segments) - 1
- while i < n:
- if segments[i] == s("..") and segments[i - 1] not in (s(""), s("..")):
- del segments[i - 1 : i + 1]
- break
- i += 1
- else:
- break
-
- # Remove trailing ".." if the URL is absolute
- unwanted_marker = [s(""), s("..")]
- while segments[:2] == unwanted_marker:
- del segments[1]
-
- path = s("/").join(segments)
- return url_unparse((scheme, netloc, path, query, fragment))
-
-
-def _urlencode(
- query: t.Mapping[str, str] | t.Iterable[tuple[str, str]], encoding: str = "utf-8"
-) -> str:
+def _urlencode(query: t.Mapping[str, str] | t.Iterable[tuple[str, str]]) -> str:
items = [x for x in iter_multi_items(query) if x[1] is not None]
# safe = https://url.spec.whatwg.org/#percent-encoded-bytes
- return urlencode(items, safe="!$'()*,/:;?@", encoding=encoding)
+ return urlencode(items, safe="!$'()*,/:;?@")
diff --git a/venv/Lib/site-packages/werkzeug/wrappers/__pycache__/__init__.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/wrappers/__pycache__/__init__.cpython-310.pyc
index e3a5301..9094ee9 100644
Binary files a/venv/Lib/site-packages/werkzeug/wrappers/__pycache__/__init__.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/wrappers/__pycache__/__init__.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/wrappers/__pycache__/request.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/wrappers/__pycache__/request.cpython-310.pyc
index b5d8ef2..28d73a7 100644
Binary files a/venv/Lib/site-packages/werkzeug/wrappers/__pycache__/request.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/wrappers/__pycache__/request.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/wrappers/__pycache__/response.cpython-310.pyc b/venv/Lib/site-packages/werkzeug/wrappers/__pycache__/response.cpython-310.pyc
index 8f5b987..c1e05ae 100644
Binary files a/venv/Lib/site-packages/werkzeug/wrappers/__pycache__/response.cpython-310.pyc and b/venv/Lib/site-packages/werkzeug/wrappers/__pycache__/response.cpython-310.pyc differ
diff --git a/venv/Lib/site-packages/werkzeug/wrappers/request.py b/venv/Lib/site-packages/werkzeug/wrappers/request.py
index f4f51b1..25b0916 100644
--- a/venv/Lib/site-packages/werkzeug/wrappers/request.py
+++ b/venv/Lib/site-packages/werkzeug/wrappers/request.py
@@ -50,6 +50,10 @@ class Request(_SansIORequest):
prevent consuming the form data in middleware, which would make
it unavailable to the final application.
+ .. versionchanged:: 3.0
+ The ``charset``, ``url_charset``, and ``encoding_errors`` parameters
+ were removed.
+
.. versionchanged:: 2.1
Old ``BaseRequest`` and mixin classes were removed.
@@ -145,9 +149,6 @@ class Request(_SansIORequest):
"""
from ..test import EnvironBuilder
- kwargs.setdefault(
- "charset", cls.charset if not isinstance(cls.charset, property) else None
- )
builder = EnvironBuilder(*args, **kwargs)
try:
return builder.get_request(cls)
@@ -240,12 +241,8 @@ class Request(_SansIORequest):
.. versionadded:: 0.8
"""
- charset = self._charset if self._charset != "utf-8" else None
- errors = self._encoding_errors if self._encoding_errors != "replace" else None
return self.form_data_parser_class(
stream_factory=self._get_file_stream,
- charset=charset,
- errors=errors,
max_form_memory_size=self.max_form_memory_size,
max_content_length=self.max_content_length,
max_form_parts=self.max_form_parts,
@@ -424,7 +421,7 @@ class Request(_SansIORequest):
if cache:
self._cached_data = rv
if as_text:
- rv = rv.decode(self._charset, self._encoding_errors)
+ rv = rv.decode(errors="replace")
return rv
@cached_property
diff --git a/venv/Lib/site-packages/werkzeug/wrappers/response.py b/venv/Lib/site-packages/werkzeug/wrappers/response.py
index c848809..ee5c694 100644
--- a/venv/Lib/site-packages/werkzeug/wrappers/response.py
+++ b/venv/Lib/site-packages/werkzeug/wrappers/response.py
@@ -28,10 +28,10 @@ if t.TYPE_CHECKING:
from .request import Request
-def _iter_encoded(iterable: t.Iterable[str | bytes], charset: str) -> t.Iterator[bytes]:
+def _iter_encoded(iterable: t.Iterable[str | bytes]) -> t.Iterator[bytes]:
for item in iterable:
if isinstance(item, str):
- yield item.encode(charset)
+ yield item.encode()
else:
yield item
@@ -284,7 +284,7 @@ class Response(_SansIOResponse):
rv = b"".join(self.iter_encoded())
if as_text:
- return rv.decode(self._charset)
+ return rv.decode()
return rv
@@ -296,7 +296,7 @@ class Response(_SansIOResponse):
.. versionadded:: 0.9
"""
if isinstance(value, str):
- value = value.encode(self._charset)
+ value = value.encode()
self.response = [value]
if self.automatically_set_content_length:
self.headers["Content-Length"] = str(len(value))
@@ -366,7 +366,7 @@ class Response(_SansIOResponse):
# Encode in a separate function so that self.response is fetched
# early. This allows us to wrap the response with the return
# value from get_app_iter or iter_encoded.
- return _iter_encoded(self.response, self._charset)
+ return _iter_encoded(self.response)
@property
def is_streamed(self) -> bool:
@@ -832,4 +832,4 @@ class ResponseStream:
@property
def encoding(self) -> str:
- return self.response._charset
+ return "utf-8"
diff --git a/venv/Lib/site-packages/werkzeug/wsgi.py b/venv/Lib/site-packages/werkzeug/wsgi.py
index 6061e11..01d40af 100644
--- a/venv/Lib/site-packages/werkzeug/wsgi.py
+++ b/venv/Lib/site-packages/werkzeug/wsgi.py
@@ -1,16 +1,10 @@
from __future__ import annotations
import io
-import re
import typing as t
-import warnings
from functools import partial
from functools import update_wrapper
-from itertools import chain
-from ._internal import _make_encode_wrapper
-from ._internal import _to_bytes
-from ._internal import _to_str
from .exceptions import ClientDisconnected
from .exceptions import RequestEntityTooLarge
from .sansio import utils as _sansio_utils
@@ -200,45 +194,18 @@ def get_input_stream(
return t.cast(t.IO[bytes], LimitedStream(stream, content_length))
-def get_path_info(
- environ: WSGIEnvironment,
- charset: t.Any = ...,
- errors: str | None = None,
-) -> str:
+def get_path_info(environ: WSGIEnvironment) -> str:
"""Return ``PATH_INFO`` from the WSGI environment.
:param environ: WSGI environment to get the path from.
- .. versionchanged:: 2.3
- The ``charset`` and ``errors`` parameters are deprecated and will be removed in
- Werkzeug 3.0.
+ .. versionchanged:: 3.0
+ The ``charset`` and ``errors`` parameters were removed.
.. versionadded:: 0.9
"""
- if charset is not ...:
- warnings.warn(
- "The 'charset' parameter is deprecated and will be removed"
- " in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
-
- if charset is None:
- charset = "utf-8"
- else:
- charset = "utf-8"
-
- if errors is not None:
- warnings.warn(
- "The 'errors' parameter is deprecated and will be removed in Werkzeug 3.0",
- DeprecationWarning,
- stacklevel=2,
- )
- else:
- errors = "replace"
-
- path = environ.get("PATH_INFO", "").encode("latin1")
- return path.decode(charset, errors) # type: ignore[no-any-return]
+ path: bytes = environ.get("PATH_INFO", "").encode("latin1")
+ return path.decode(errors="replace")
class ClosingIterator:
@@ -455,225 +422,6 @@ class _RangeWrapper:
self.iterable.close()
-def _make_chunk_iter(
- stream: t.Iterable[bytes] | t.IO[bytes],
- limit: int | None,
- buffer_size: int,
-) -> t.Iterator[bytes]:
- """Helper for the line and chunk iter functions."""
- warnings.warn(
- "'_make_chunk_iter' is deprecated and will be removed in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
-
- if isinstance(stream, (bytes, bytearray, str)):
- raise TypeError(
- "Passed a string or byte object instead of true iterator or stream."
- )
- if not hasattr(stream, "read"):
- for item in stream:
- if item:
- yield item
- return
- stream = t.cast(t.IO[bytes], stream)
- if not isinstance(stream, LimitedStream) and limit is not None:
- stream = t.cast(t.IO[bytes], LimitedStream(stream, limit))
- _read = stream.read
- while True:
- item = _read(buffer_size)
- if not item:
- break
- yield item
-
-
-def make_line_iter(
- stream: t.Iterable[bytes] | t.IO[bytes],
- limit: int | None = None,
- buffer_size: int = 10 * 1024,
- cap_at_buffer: bool = False,
-) -> t.Iterator[bytes]:
- """Safely iterates line-based over an input stream. If the input stream
- is not a :class:`LimitedStream` the `limit` parameter is mandatory.
-
- This uses the stream's :meth:`~file.read` method internally as opposite
- to the :meth:`~file.readline` method that is unsafe and can only be used
- in violation of the WSGI specification. The same problem applies to the
- `__iter__` function of the input stream which calls :meth:`~file.readline`
- without arguments.
-
- If you need line-by-line processing it's strongly recommended to iterate
- over the input stream using this helper function.
-
- .. deprecated:: 2.3
- Will be removed in Werkzeug 3.0.
-
- .. versionadded:: 0.11
- added support for the `cap_at_buffer` parameter.
-
- .. versionadded:: 0.9
- added support for iterators as input stream.
-
- .. versionchanged:: 0.8
- This function now ensures that the limit was reached.
-
- :param stream: the stream or iterate to iterate over.
- :param limit: the limit in bytes for the stream. (Usually
- content length. Not necessary if the `stream`
- is a :class:`LimitedStream`.
- :param buffer_size: The optional buffer size.
- :param cap_at_buffer: if this is set chunks are split if they are longer
- than the buffer size. Internally this is implemented
- that the buffer size might be exhausted by a factor
- of two however.
- """
- warnings.warn(
- "'make_line_iter' is deprecated and will be removed in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- _iter = _make_chunk_iter(stream, limit, buffer_size)
-
- first_item = next(_iter, "")
-
- if not first_item:
- return
-
- s = _make_encode_wrapper(first_item)
- empty = t.cast(bytes, s(""))
- cr = t.cast(bytes, s("\r"))
- lf = t.cast(bytes, s("\n"))
- crlf = t.cast(bytes, s("\r\n"))
-
- _iter = t.cast(t.Iterator[bytes], chain((first_item,), _iter))
-
- def _iter_basic_lines() -> t.Iterator[bytes]:
- _join = empty.join
- buffer: list[bytes] = []
- while True:
- new_data = next(_iter, "")
- if not new_data:
- break
- new_buf: list[bytes] = []
- buf_size = 0
- for item in t.cast(
- t.Iterator[bytes], chain(buffer, new_data.splitlines(True))
- ):
- new_buf.append(item)
- buf_size += len(item)
- if item and item[-1:] in crlf:
- yield _join(new_buf)
- new_buf = []
- elif cap_at_buffer and buf_size >= buffer_size:
- rv = _join(new_buf)
- while len(rv) >= buffer_size:
- yield rv[:buffer_size]
- rv = rv[buffer_size:]
- new_buf = [rv]
- buffer = new_buf
- if buffer:
- yield _join(buffer)
-
- # This hackery is necessary to merge 'foo\r' and '\n' into one item
- # of 'foo\r\n' if we were unlucky and we hit a chunk boundary.
- previous = empty
- for item in _iter_basic_lines():
- if item == lf and previous[-1:] == cr:
- previous += item
- item = empty
- if previous:
- yield previous
- previous = item
- if previous:
- yield previous
-
-
-def make_chunk_iter(
- stream: t.Iterable[bytes] | t.IO[bytes],
- separator: bytes,
- limit: int | None = None,
- buffer_size: int = 10 * 1024,
- cap_at_buffer: bool = False,
-) -> t.Iterator[bytes]:
- """Works like :func:`make_line_iter` but accepts a separator
- which divides chunks. If you want newline based processing
- you should use :func:`make_line_iter` instead as it
- supports arbitrary newline markers.
-
- .. deprecated:: 2.3
- Will be removed in Werkzeug 3.0.
-
- .. versionchanged:: 0.11
- added support for the `cap_at_buffer` parameter.
-
- .. versionchanged:: 0.9
- added support for iterators as input stream.
-
- .. versionadded:: 0.8
-
- :param stream: the stream or iterate to iterate over.
- :param separator: the separator that divides chunks.
- :param limit: the limit in bytes for the stream. (Usually
- content length. Not necessary if the `stream`
- is otherwise already limited).
- :param buffer_size: The optional buffer size.
- :param cap_at_buffer: if this is set chunks are split if they are longer
- than the buffer size. Internally this is implemented
- that the buffer size might be exhausted by a factor
- of two however.
- """
- warnings.warn(
- "'make_chunk_iter' is deprecated and will be removed in Werkzeug 3.0.",
- DeprecationWarning,
- stacklevel=2,
- )
- _iter = _make_chunk_iter(stream, limit, buffer_size)
-
- first_item = next(_iter, b"")
-
- if not first_item:
- return
-
- _iter = t.cast(t.Iterator[bytes], chain((first_item,), _iter))
- if isinstance(first_item, str):
- separator = _to_str(separator)
- _split = re.compile(f"({re.escape(separator)})").split
- _join = "".join
- else:
- separator = _to_bytes(separator)
- _split = re.compile(b"(" + re.escape(separator) + b")").split
- _join = b"".join
-
- buffer: list[bytes] = []
- while True:
- new_data = next(_iter, b"")
- if not new_data:
- break
- chunks = _split(new_data)
- new_buf: list[bytes] = []
- buf_size = 0
- for item in chain(buffer, chunks):
- if item == separator:
- yield _join(new_buf)
- new_buf = []
- buf_size = 0
- else:
- buf_size += len(item)
- new_buf.append(item)
-
- if cap_at_buffer and buf_size >= buffer_size:
- rv = _join(new_buf)
- while len(rv) >= buffer_size:
- yield rv[:buffer_size]
- rv = rv[buffer_size:]
- new_buf = [rv]
- buf_size = len(rv)
-
- buffer = new_buf
- if buffer:
- yield _join(buffer)
-
-
class LimitedStream(io.RawIOBase):
"""Wrap a stream so that it doesn't read more than a given limit. This is used to
limit ``wsgi.input`` to the ``Content-Length`` header value or
diff --git a/venv/Scripts/dotenv.exe b/venv/Scripts/dotenv.exe
index f5e3de2..754b95c 100644
Binary files a/venv/Scripts/dotenv.exe and b/venv/Scripts/dotenv.exe differ
diff --git a/venv/Scripts/flask.exe b/venv/Scripts/flask.exe
index aba7c84..5385a88 100644
Binary files a/venv/Scripts/flask.exe and b/venv/Scripts/flask.exe differ