From a8fe898a732ec8f6211e7d1b60176b8b05cb7327 Mon Sep 17 00:00:00 2001 From: Richjerk Date: Fri, 24 May 2024 10:41:13 +0200 Subject: [PATCH] Add Flask app with form template --- runtime.txt | 2 +- township-small-business-bot | 1 + .../flask-3.0.3.dist-info/INSTALLER | 1 + .../flask-3.0.3.dist-info/LICENSE.txt | 28 + .../flask-3.0.3.dist-info/METADATA | 101 ++ .../flask-3.0.3.dist-info/RECORD | 58 + .../flask-3.0.3.dist-info/REQUESTED | 0 .../site-packages/flask-3.0.3.dist-info/WHEEL | 4 + .../flask-3.0.3.dist-info/entry_points.txt | 3 + venv/Lib/site-packages/flask/__init__.py | 60 + venv/Lib/site-packages/flask/__main__.py | 3 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 2513 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 274 bytes .../flask/__pycache__/app.cpython-312.pyc | Bin 0 -> 61209 bytes .../__pycache__/blueprints.cpython-312.pyc | Bin 0 -> 4928 bytes .../flask/__pycache__/cli.cpython-312.pyc | Bin 0 -> 42306 bytes .../flask/__pycache__/config.cpython-312.pyc | Bin 0 -> 16294 bytes .../flask/__pycache__/ctx.cpython-312.pyc | Bin 0 -> 19871 bytes .../__pycache__/debughelpers.cpython-312.pyc | Bin 0 -> 9183 bytes .../flask/__pycache__/globals.cpython-312.pyc | Bin 0 -> 1898 bytes .../flask/__pycache__/helpers.cpython-312.pyc | Bin 0 -> 24851 bytes .../flask/__pycache__/logging.cpython-312.pyc | Bin 0 -> 3300 bytes .../__pycache__/sessions.cpython-312.pyc | Bin 0 -> 16347 bytes .../flask/__pycache__/signals.cpython-312.pyc | Bin 0 -> 1255 bytes .../__pycache__/templating.cpython-312.pyc | Bin 0 -> 9965 bytes .../flask/__pycache__/testing.cpython-312.pyc | Bin 0 -> 13739 bytes .../flask/__pycache__/typing.cpython-312.pyc | Bin 0 -> 4310 bytes .../flask/__pycache__/views.cpython-312.pyc | Bin 0 -> 7050 bytes .../__pycache__/wrappers.cpython-312.pyc | Bin 0 -> 6156 bytes venv/Lib/site-packages/flask/app.py | 1498 +++++++++++++++++ venv/Lib/site-packages/flask/blueprints.py | 129 ++ venv/Lib/site-packages/flask/cli.py | 1109 ++++++++++++ venv/Lib/site-packages/flask/config.py | 370 ++++ venv/Lib/site-packages/flask/ctx.py | 449 +++++ venv/Lib/site-packages/flask/debughelpers.py | 178 ++ venv/Lib/site-packages/flask/globals.py | 51 + venv/Lib/site-packages/flask/helpers.py | 621 +++++++ venv/Lib/site-packages/flask/json/__init__.py | 170 ++ .../json/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 6726 bytes .../json/__pycache__/provider.cpython-312.pyc | Bin 0 -> 9293 bytes .../json/__pycache__/tag.cpython-312.pyc | Bin 0 -> 13988 bytes venv/Lib/site-packages/flask/json/provider.py | 215 +++ venv/Lib/site-packages/flask/json/tag.py | 327 ++++ venv/Lib/site-packages/flask/logging.py | 79 + venv/Lib/site-packages/flask/py.typed | 0 venv/Lib/site-packages/flask/sansio/README.md | 6 + .../sansio/__pycache__/app.cpython-312.pyc | Bin 0 -> 33646 bytes .../__pycache__/blueprints.cpython-312.pyc | Bin 0 -> 31246 bytes .../__pycache__/scaffold.cpython-312.pyc | Bin 0 -> 30524 bytes venv/Lib/site-packages/flask/sansio/app.py | 964 +++++++++++ .../site-packages/flask/sansio/blueprints.py | 632 +++++++ .../site-packages/flask/sansio/scaffold.py | 801 +++++++++ venv/Lib/site-packages/flask/sessions.py | 379 +++++ venv/Lib/site-packages/flask/signals.py | 17 + venv/Lib/site-packages/flask/templating.py | 219 +++ venv/Lib/site-packages/flask/testing.py | 298 ++++ venv/Lib/site-packages/flask/typing.py | 90 + venv/Lib/site-packages/flask/views.py | 191 +++ venv/Lib/site-packages/flask/wrappers.py | 174 ++ .../itsdangerous-2.2.0.dist-info/INSTALLER | 1 + .../itsdangerous-2.2.0.dist-info/LICENSE.txt | 28 + .../itsdangerous-2.2.0.dist-info/METADATA | 60 + .../itsdangerous-2.2.0.dist-info/RECORD | 22 + .../itsdangerous-2.2.0.dist-info/WHEEL | 4 + .../site-packages/itsdangerous/__init__.py | 38 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1665 bytes .../__pycache__/_json.cpython-312.pyc | Bin 0 -> 1219 bytes .../__pycache__/encoding.cpython-312.pyc | Bin 0 -> 2719 bytes .../__pycache__/exc.cpython-312.pyc | Bin 0 -> 3979 bytes .../__pycache__/serializer.cpython-312.pyc | Bin 0 -> 15460 bytes .../__pycache__/signer.cpython-312.pyc | Bin 0 -> 11325 bytes .../__pycache__/timed.cpython-312.pyc | Bin 0 -> 8773 bytes .../__pycache__/url_safe.cpython-312.pyc | Bin 0 -> 3569 bytes venv/Lib/site-packages/itsdangerous/_json.py | 18 + .../site-packages/itsdangerous/encoding.py | 54 + venv/Lib/site-packages/itsdangerous/exc.py | 106 ++ venv/Lib/site-packages/itsdangerous/py.typed | 0 .../site-packages/itsdangerous/serializer.py | 406 +++++ venv/Lib/site-packages/itsdangerous/signer.py | 266 +++ venv/Lib/site-packages/itsdangerous/timed.py | 228 +++ .../site-packages/itsdangerous/url_safe.py | 83 + .../werkzeug-3.0.3.dist-info/INSTALLER | 1 + .../werkzeug-3.0.3.dist-info/LICENSE.txt | 28 + .../werkzeug-3.0.3.dist-info/METADATA | 99 ++ .../werkzeug-3.0.3.dist-info/RECORD | 125 ++ .../werkzeug-3.0.3.dist-info/WHEEL | 4 + venv/Lib/site-packages/werkzeug/__init__.py | 25 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1124 bytes .../__pycache__/_internal.cpython-312.pyc | Bin 0 -> 9797 bytes .../__pycache__/_reloader.cpython-312.pyc | Bin 0 -> 20316 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 32980 bytes .../__pycache__/formparser.cpython-312.pyc | Bin 0 -> 16968 bytes .../werkzeug/__pycache__/http.cpython-312.pyc | Bin 0 -> 49219 bytes .../__pycache__/local.cpython-312.pyc | Bin 0 -> 28518 bytes .../__pycache__/security.cpython-312.pyc | Bin 0 -> 7018 bytes .../__pycache__/serving.cpython-312.pyc | Bin 0 -> 45922 bytes .../werkzeug/__pycache__/test.cpython-312.pyc | Bin 0 -> 59937 bytes .../__pycache__/testapp.cpython-312.pyc | Bin 0 -> 8928 bytes .../werkzeug/__pycache__/urls.cpython-312.pyc | Bin 0 -> 8307 bytes .../__pycache__/user_agent.cpython-312.pyc | Bin 0 -> 2190 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 28181 bytes .../werkzeug/__pycache__/wsgi.cpython-312.pyc | Bin 0 -> 25253 bytes venv/Lib/site-packages/werkzeug/_internal.py | 211 +++ venv/Lib/site-packages/werkzeug/_reloader.py | 460 +++++ .../werkzeug/datastructures/__init__.py | 34 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1639 bytes .../__pycache__/accept.cpython-312.pyc | Bin 0 -> 13856 bytes .../__pycache__/auth.cpython-312.pyc | Bin 0 -> 14453 bytes .../__pycache__/cache_control.cpython-312.pyc | Bin 0 -> 7926 bytes .../__pycache__/csp.cpython-312.pyc | Bin 0 -> 5297 bytes .../__pycache__/etag.cpython-312.pyc | Bin 0 -> 5081 bytes .../__pycache__/file_storage.cpython-312.pyc | Bin 0 -> 7957 bytes .../__pycache__/headers.cpython-312.pyc | Bin 0 -> 23303 bytes .../__pycache__/mixins.cpython-312.pyc | Bin 0 -> 11430 bytes .../__pycache__/range.cpython-312.pyc | Bin 0 -> 8287 bytes .../__pycache__/structures.cpython-312.pyc | Bin 0 -> 48546 bytes .../werkzeug/datastructures/accept.py | 326 ++++ .../werkzeug/datastructures/accept.pyi | 54 + .../werkzeug/datastructures/auth.py | 316 ++++ .../werkzeug/datastructures/cache_control.py | 175 ++ .../werkzeug/datastructures/cache_control.pyi | 115 ++ .../werkzeug/datastructures/csp.py | 94 ++ .../werkzeug/datastructures/csp.pyi | 169 ++ .../werkzeug/datastructures/etag.py | 95 ++ .../werkzeug/datastructures/etag.pyi | 30 + .../werkzeug/datastructures/file_storage.py | 196 +++ .../werkzeug/datastructures/file_storage.pyi | 49 + .../werkzeug/datastructures/headers.py | 515 ++++++ .../werkzeug/datastructures/headers.pyi | 109 ++ .../werkzeug/datastructures/mixins.py | 242 +++ .../werkzeug/datastructures/mixins.pyi | 97 ++ .../werkzeug/datastructures/range.py | 180 ++ .../werkzeug/datastructures/range.pyi | 57 + .../werkzeug/datastructures/structures.py | 1010 +++++++++++ .../werkzeug/datastructures/structures.pyi | 206 +++ .../site-packages/werkzeug/debug/__init__.py | 560 ++++++ .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 23153 bytes .../debug/__pycache__/console.cpython-312.pyc | Bin 0 -> 11674 bytes .../debug/__pycache__/repr.cpython-312.pyc | Bin 0 -> 13847 bytes .../debug/__pycache__/tbtools.cpython-312.pyc | Bin 0 -> 16704 bytes .../site-packages/werkzeug/debug/console.py | 219 +++ venv/Lib/site-packages/werkzeug/debug/repr.py | 282 ++++ .../werkzeug/debug/shared/ICON_LICENSE.md | 6 + .../werkzeug/debug/shared/console.png | Bin 0 -> 507 bytes .../werkzeug/debug/shared/debugger.js | 360 ++++ .../werkzeug/debug/shared/less.png | Bin 0 -> 191 bytes .../werkzeug/debug/shared/more.png | Bin 0 -> 200 bytes .../werkzeug/debug/shared/style.css | 150 ++ .../site-packages/werkzeug/debug/tbtools.py | 439 +++++ venv/Lib/site-packages/werkzeug/exceptions.py | 881 ++++++++++ venv/Lib/site-packages/werkzeug/formparser.py | 423 +++++ venv/Lib/site-packages/werkzeug/http.py | 1370 +++++++++++++++ venv/Lib/site-packages/werkzeug/local.py | 653 +++++++ .../werkzeug/middleware/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 234 bytes .../__pycache__/dispatcher.cpython-312.pyc | Bin 0 -> 3352 bytes .../__pycache__/http_proxy.cpython-312.pyc | Bin 0 -> 9444 bytes .../__pycache__/lint.cpython-312.pyc | Bin 0 -> 17840 bytes .../__pycache__/profiler.cpython-312.pyc | Bin 0 -> 7238 bytes .../__pycache__/proxy_fix.cpython-312.pyc | Bin 0 -> 7235 bytes .../__pycache__/shared_data.cpython-312.pyc | Bin 0 -> 12757 bytes .../werkzeug/middleware/dispatcher.py | 81 + .../werkzeug/middleware/http_proxy.py | 236 +++ .../site-packages/werkzeug/middleware/lint.py | 439 +++++ .../werkzeug/middleware/profiler.py | 155 ++ .../werkzeug/middleware/proxy_fix.py | 183 ++ .../werkzeug/middleware/shared_data.py | 282 ++++ venv/Lib/site-packages/werkzeug/py.typed | 0 .../werkzeug/routing/__init__.py | 134 ++ .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 4707 bytes .../__pycache__/converters.cpython-312.pyc | Bin 0 -> 10947 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 7950 bytes .../routing/__pycache__/map.cpython-312.pyc | Bin 0 -> 39875 bytes .../__pycache__/matcher.cpython-312.pyc | Bin 0 -> 8318 bytes .../routing/__pycache__/rules.cpython-312.pyc | Bin 0 -> 38674 bytes .../werkzeug/routing/converters.py | 261 +++ .../werkzeug/routing/exceptions.py | 152 ++ .../Lib/site-packages/werkzeug/routing/map.py | 951 +++++++++++ .../site-packages/werkzeug/routing/matcher.py | 202 +++ .../site-packages/werkzeug/routing/rules.py | 917 ++++++++++ .../site-packages/werkzeug/sansio/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 230 bytes .../sansio/__pycache__/http.cpython-312.pyc | Bin 0 -> 5759 bytes .../__pycache__/multipart.cpython-312.pyc | Bin 0 -> 14087 bytes .../__pycache__/request.cpython-312.pyc | Bin 0 -> 21923 bytes .../__pycache__/response.cpython-312.pyc | Bin 0 -> 31446 bytes .../sansio/__pycache__/utils.cpython-312.pyc | Bin 0 -> 6054 bytes .../Lib/site-packages/werkzeug/sansio/http.py | 171 ++ .../werkzeug/sansio/multipart.py | 321 ++++ .../site-packages/werkzeug/sansio/request.py | 536 ++++++ .../site-packages/werkzeug/sansio/response.py | 754 +++++++++ .../site-packages/werkzeug/sansio/utils.py | 159 ++ venv/Lib/site-packages/werkzeug/security.py | 161 ++ venv/Lib/site-packages/werkzeug/serving.py | 1116 ++++++++++++ venv/Lib/site-packages/werkzeug/test.py | 1464 ++++++++++++++++ venv/Lib/site-packages/werkzeug/testapp.py | 194 +++ venv/Lib/site-packages/werkzeug/urls.py | 203 +++ venv/Lib/site-packages/werkzeug/user_agent.py | 47 + venv/Lib/site-packages/werkzeug/utils.py | 691 ++++++++ .../werkzeug/wrappers/__init__.py | 3 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 354 bytes .../__pycache__/request.cpython-312.pyc | Bin 0 -> 26179 bytes .../__pycache__/response.cpython-312.pyc | Bin 0 -> 34609 bytes .../werkzeug/wrappers/request.py | 647 +++++++ .../werkzeug/wrappers/response.py | 831 +++++++++ venv/Lib/site-packages/werkzeug/wsgi.py | 595 +++++++ venv/Scripts/flask.exe | Bin 0 -> 108452 bytes venv/app.py | 15 + venv/flask_project_directory.txt | 6 + venv/form.html | 14 + 210 files changed, 32827 insertions(+), 1 deletion(-) create mode 160000 township-small-business-bot create mode 100644 venv/Lib/site-packages/flask-3.0.3.dist-info/INSTALLER create mode 100644 venv/Lib/site-packages/flask-3.0.3.dist-info/LICENSE.txt create mode 100644 venv/Lib/site-packages/flask-3.0.3.dist-info/METADATA create mode 100644 venv/Lib/site-packages/flask-3.0.3.dist-info/RECORD create mode 100644 venv/Lib/site-packages/flask-3.0.3.dist-info/REQUESTED create mode 100644 venv/Lib/site-packages/flask-3.0.3.dist-info/WHEEL create mode 100644 venv/Lib/site-packages/flask-3.0.3.dist-info/entry_points.txt create mode 100644 venv/Lib/site-packages/flask/__init__.py create mode 100644 venv/Lib/site-packages/flask/__main__.py create mode 100644 venv/Lib/site-packages/flask/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/flask/__pycache__/__main__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/flask/__pycache__/app.cpython-312.pyc create mode 100644 venv/Lib/site-packages/flask/__pycache__/blueprints.cpython-312.pyc create mode 100644 venv/Lib/site-packages/flask/__pycache__/cli.cpython-312.pyc create mode 100644 venv/Lib/site-packages/flask/__pycache__/config.cpython-312.pyc create mode 100644 venv/Lib/site-packages/flask/__pycache__/ctx.cpython-312.pyc create mode 100644 venv/Lib/site-packages/flask/__pycache__/debughelpers.cpython-312.pyc create mode 100644 venv/Lib/site-packages/flask/__pycache__/globals.cpython-312.pyc create mode 100644 venv/Lib/site-packages/flask/__pycache__/helpers.cpython-312.pyc create mode 100644 venv/Lib/site-packages/flask/__pycache__/logging.cpython-312.pyc create mode 100644 venv/Lib/site-packages/flask/__pycache__/sessions.cpython-312.pyc create mode 100644 venv/Lib/site-packages/flask/__pycache__/signals.cpython-312.pyc create mode 100644 venv/Lib/site-packages/flask/__pycache__/templating.cpython-312.pyc create mode 100644 venv/Lib/site-packages/flask/__pycache__/testing.cpython-312.pyc create mode 100644 venv/Lib/site-packages/flask/__pycache__/typing.cpython-312.pyc create mode 100644 venv/Lib/site-packages/flask/__pycache__/views.cpython-312.pyc create mode 100644 venv/Lib/site-packages/flask/__pycache__/wrappers.cpython-312.pyc create mode 100644 venv/Lib/site-packages/flask/app.py create mode 100644 venv/Lib/site-packages/flask/blueprints.py create mode 100644 venv/Lib/site-packages/flask/cli.py create mode 100644 venv/Lib/site-packages/flask/config.py create mode 100644 venv/Lib/site-packages/flask/ctx.py create mode 100644 venv/Lib/site-packages/flask/debughelpers.py create mode 100644 venv/Lib/site-packages/flask/globals.py create mode 100644 venv/Lib/site-packages/flask/helpers.py create mode 100644 venv/Lib/site-packages/flask/json/__init__.py create mode 100644 venv/Lib/site-packages/flask/json/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/flask/json/__pycache__/provider.cpython-312.pyc create mode 100644 venv/Lib/site-packages/flask/json/__pycache__/tag.cpython-312.pyc create mode 100644 venv/Lib/site-packages/flask/json/provider.py create mode 100644 venv/Lib/site-packages/flask/json/tag.py create mode 100644 venv/Lib/site-packages/flask/logging.py create mode 100644 venv/Lib/site-packages/flask/py.typed create mode 100644 venv/Lib/site-packages/flask/sansio/README.md create mode 100644 venv/Lib/site-packages/flask/sansio/__pycache__/app.cpython-312.pyc create mode 100644 venv/Lib/site-packages/flask/sansio/__pycache__/blueprints.cpython-312.pyc create mode 100644 venv/Lib/site-packages/flask/sansio/__pycache__/scaffold.cpython-312.pyc create mode 100644 venv/Lib/site-packages/flask/sansio/app.py create mode 100644 venv/Lib/site-packages/flask/sansio/blueprints.py create mode 100644 venv/Lib/site-packages/flask/sansio/scaffold.py create mode 100644 venv/Lib/site-packages/flask/sessions.py create mode 100644 venv/Lib/site-packages/flask/signals.py create mode 100644 venv/Lib/site-packages/flask/templating.py create mode 100644 venv/Lib/site-packages/flask/testing.py create mode 100644 venv/Lib/site-packages/flask/typing.py create mode 100644 venv/Lib/site-packages/flask/views.py create mode 100644 venv/Lib/site-packages/flask/wrappers.py create mode 100644 venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/INSTALLER create mode 100644 venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/LICENSE.txt create mode 100644 venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/METADATA create mode 100644 venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/RECORD create mode 100644 venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/WHEEL create mode 100644 venv/Lib/site-packages/itsdangerous/__init__.py create mode 100644 venv/Lib/site-packages/itsdangerous/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/itsdangerous/__pycache__/_json.cpython-312.pyc create mode 100644 venv/Lib/site-packages/itsdangerous/__pycache__/encoding.cpython-312.pyc create mode 100644 venv/Lib/site-packages/itsdangerous/__pycache__/exc.cpython-312.pyc create mode 100644 venv/Lib/site-packages/itsdangerous/__pycache__/serializer.cpython-312.pyc create mode 100644 venv/Lib/site-packages/itsdangerous/__pycache__/signer.cpython-312.pyc create mode 100644 venv/Lib/site-packages/itsdangerous/__pycache__/timed.cpython-312.pyc create mode 100644 venv/Lib/site-packages/itsdangerous/__pycache__/url_safe.cpython-312.pyc create mode 100644 venv/Lib/site-packages/itsdangerous/_json.py create mode 100644 venv/Lib/site-packages/itsdangerous/encoding.py create mode 100644 venv/Lib/site-packages/itsdangerous/exc.py create mode 100644 venv/Lib/site-packages/itsdangerous/py.typed create mode 100644 venv/Lib/site-packages/itsdangerous/serializer.py create mode 100644 venv/Lib/site-packages/itsdangerous/signer.py create mode 100644 venv/Lib/site-packages/itsdangerous/timed.py create mode 100644 venv/Lib/site-packages/itsdangerous/url_safe.py create mode 100644 venv/Lib/site-packages/werkzeug-3.0.3.dist-info/INSTALLER create mode 100644 venv/Lib/site-packages/werkzeug-3.0.3.dist-info/LICENSE.txt create mode 100644 venv/Lib/site-packages/werkzeug-3.0.3.dist-info/METADATA create mode 100644 venv/Lib/site-packages/werkzeug-3.0.3.dist-info/RECORD create mode 100644 venv/Lib/site-packages/werkzeug-3.0.3.dist-info/WHEEL create mode 100644 venv/Lib/site-packages/werkzeug/__init__.py create mode 100644 venv/Lib/site-packages/werkzeug/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/__pycache__/_internal.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/__pycache__/_reloader.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/__pycache__/exceptions.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/__pycache__/formparser.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/__pycache__/http.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/__pycache__/local.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/__pycache__/security.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/__pycache__/serving.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/__pycache__/test.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/__pycache__/testapp.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/__pycache__/urls.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/__pycache__/user_agent.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/__pycache__/utils.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/__pycache__/wsgi.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/_internal.py create mode 100644 venv/Lib/site-packages/werkzeug/_reloader.py create mode 100644 venv/Lib/site-packages/werkzeug/datastructures/__init__.py create mode 100644 venv/Lib/site-packages/werkzeug/datastructures/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/datastructures/__pycache__/accept.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/datastructures/__pycache__/auth.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/datastructures/__pycache__/cache_control.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/datastructures/__pycache__/csp.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/datastructures/__pycache__/etag.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/datastructures/__pycache__/file_storage.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/datastructures/__pycache__/headers.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/datastructures/__pycache__/mixins.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/datastructures/__pycache__/range.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/datastructures/__pycache__/structures.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/datastructures/accept.py create mode 100644 venv/Lib/site-packages/werkzeug/datastructures/accept.pyi create mode 100644 venv/Lib/site-packages/werkzeug/datastructures/auth.py create mode 100644 venv/Lib/site-packages/werkzeug/datastructures/cache_control.py create mode 100644 venv/Lib/site-packages/werkzeug/datastructures/cache_control.pyi create mode 100644 venv/Lib/site-packages/werkzeug/datastructures/csp.py create mode 100644 venv/Lib/site-packages/werkzeug/datastructures/csp.pyi create mode 100644 venv/Lib/site-packages/werkzeug/datastructures/etag.py create mode 100644 venv/Lib/site-packages/werkzeug/datastructures/etag.pyi create mode 100644 venv/Lib/site-packages/werkzeug/datastructures/file_storage.py create mode 100644 venv/Lib/site-packages/werkzeug/datastructures/file_storage.pyi create mode 100644 venv/Lib/site-packages/werkzeug/datastructures/headers.py create mode 100644 venv/Lib/site-packages/werkzeug/datastructures/headers.pyi create mode 100644 venv/Lib/site-packages/werkzeug/datastructures/mixins.py create mode 100644 venv/Lib/site-packages/werkzeug/datastructures/mixins.pyi create mode 100644 venv/Lib/site-packages/werkzeug/datastructures/range.py create mode 100644 venv/Lib/site-packages/werkzeug/datastructures/range.pyi create mode 100644 venv/Lib/site-packages/werkzeug/datastructures/structures.py create mode 100644 venv/Lib/site-packages/werkzeug/datastructures/structures.pyi create mode 100644 venv/Lib/site-packages/werkzeug/debug/__init__.py create mode 100644 venv/Lib/site-packages/werkzeug/debug/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/debug/__pycache__/console.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/debug/__pycache__/repr.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/debug/__pycache__/tbtools.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/debug/console.py create mode 100644 venv/Lib/site-packages/werkzeug/debug/repr.py create mode 100644 venv/Lib/site-packages/werkzeug/debug/shared/ICON_LICENSE.md create mode 100644 venv/Lib/site-packages/werkzeug/debug/shared/console.png create mode 100644 venv/Lib/site-packages/werkzeug/debug/shared/debugger.js create mode 100644 venv/Lib/site-packages/werkzeug/debug/shared/less.png create mode 100644 venv/Lib/site-packages/werkzeug/debug/shared/more.png create mode 100644 venv/Lib/site-packages/werkzeug/debug/shared/style.css create mode 100644 venv/Lib/site-packages/werkzeug/debug/tbtools.py create mode 100644 venv/Lib/site-packages/werkzeug/exceptions.py create mode 100644 venv/Lib/site-packages/werkzeug/formparser.py create mode 100644 venv/Lib/site-packages/werkzeug/http.py create mode 100644 venv/Lib/site-packages/werkzeug/local.py create mode 100644 venv/Lib/site-packages/werkzeug/middleware/__init__.py create mode 100644 venv/Lib/site-packages/werkzeug/middleware/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/middleware/__pycache__/dispatcher.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/middleware/__pycache__/http_proxy.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/middleware/__pycache__/lint.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/middleware/__pycache__/profiler.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/middleware/__pycache__/proxy_fix.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/middleware/__pycache__/shared_data.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/middleware/dispatcher.py create mode 100644 venv/Lib/site-packages/werkzeug/middleware/http_proxy.py create mode 100644 venv/Lib/site-packages/werkzeug/middleware/lint.py create mode 100644 venv/Lib/site-packages/werkzeug/middleware/profiler.py create mode 100644 venv/Lib/site-packages/werkzeug/middleware/proxy_fix.py create mode 100644 venv/Lib/site-packages/werkzeug/middleware/shared_data.py create mode 100644 venv/Lib/site-packages/werkzeug/py.typed create mode 100644 venv/Lib/site-packages/werkzeug/routing/__init__.py create mode 100644 venv/Lib/site-packages/werkzeug/routing/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/routing/__pycache__/converters.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/routing/__pycache__/exceptions.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/routing/__pycache__/map.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/routing/__pycache__/matcher.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/routing/__pycache__/rules.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/routing/converters.py create mode 100644 venv/Lib/site-packages/werkzeug/routing/exceptions.py create mode 100644 venv/Lib/site-packages/werkzeug/routing/map.py create mode 100644 venv/Lib/site-packages/werkzeug/routing/matcher.py create mode 100644 venv/Lib/site-packages/werkzeug/routing/rules.py create mode 100644 venv/Lib/site-packages/werkzeug/sansio/__init__.py create mode 100644 venv/Lib/site-packages/werkzeug/sansio/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/sansio/__pycache__/http.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/sansio/__pycache__/multipart.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/sansio/__pycache__/request.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/sansio/__pycache__/response.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/sansio/__pycache__/utils.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/sansio/http.py create mode 100644 venv/Lib/site-packages/werkzeug/sansio/multipart.py create mode 100644 venv/Lib/site-packages/werkzeug/sansio/request.py create mode 100644 venv/Lib/site-packages/werkzeug/sansio/response.py create mode 100644 venv/Lib/site-packages/werkzeug/sansio/utils.py create mode 100644 venv/Lib/site-packages/werkzeug/security.py create mode 100644 venv/Lib/site-packages/werkzeug/serving.py create mode 100644 venv/Lib/site-packages/werkzeug/test.py create mode 100644 venv/Lib/site-packages/werkzeug/testapp.py create mode 100644 venv/Lib/site-packages/werkzeug/urls.py create mode 100644 venv/Lib/site-packages/werkzeug/user_agent.py create mode 100644 venv/Lib/site-packages/werkzeug/utils.py create mode 100644 venv/Lib/site-packages/werkzeug/wrappers/__init__.py create mode 100644 venv/Lib/site-packages/werkzeug/wrappers/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/wrappers/__pycache__/request.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/wrappers/__pycache__/response.cpython-312.pyc create mode 100644 venv/Lib/site-packages/werkzeug/wrappers/request.py create mode 100644 venv/Lib/site-packages/werkzeug/wrappers/response.py create mode 100644 venv/Lib/site-packages/werkzeug/wsgi.py create mode 100644 venv/Scripts/flask.exe create mode 100644 venv/app.py create mode 100644 venv/flask_project_directory.txt create mode 100644 venv/form.html diff --git a/runtime.txt b/runtime.txt index 67ebc4e9..d2aca3a7 100644 --- a/runtime.txt +++ b/runtime.txt @@ -1 +1 @@ -python-3.11 +python-3.12 diff --git a/township-small-business-bot b/township-small-business-bot new file mode 160000 index 00000000..71baeb48 --- /dev/null +++ b/township-small-business-bot @@ -0,0 +1 @@ +Subproject commit 71baeb4832add85be9c8549c15521a56622def38 diff --git a/venv/Lib/site-packages/flask-3.0.3.dist-info/INSTALLER b/venv/Lib/site-packages/flask-3.0.3.dist-info/INSTALLER new file mode 100644 index 00000000..a1b589e3 --- /dev/null +++ b/venv/Lib/site-packages/flask-3.0.3.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/Lib/site-packages/flask-3.0.3.dist-info/LICENSE.txt b/venv/Lib/site-packages/flask-3.0.3.dist-info/LICENSE.txt new file mode 100644 index 00000000..9d227a0c --- /dev/null +++ b/venv/Lib/site-packages/flask-3.0.3.dist-info/LICENSE.txt @@ -0,0 +1,28 @@ +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-3.0.3.dist-info/METADATA b/venv/Lib/site-packages/flask-3.0.3.dist-info/METADATA new file mode 100644 index 00000000..5a021072 --- /dev/null +++ b/venv/Lib/site-packages/flask-3.0.3.dist-info/METADATA @@ -0,0 +1,101 @@ +Metadata-Version: 2.1 +Name: Flask +Version: 3.0.3 +Summary: A simple framework for building complex web applications. +Maintainer-email: Pallets +Requires-Python: >=3.8 +Description-Content-Type: text/markdown +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 +Classifier: Typing :: Typed +Requires-Dist: Werkzeug>=3.0.0 +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: Source, 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 from [PyPI][] using an installer such as [pip][]: + +``` +$ pip install -U Flask +``` + +[PyPI]: https://pypi.org/project/Flask/ +[pip]: https://pip.pypa.io/en/stable/getting-started/ + + +## A Simple Example + +```python +# save this as app.py +from flask import Flask + +app = Flask(__name__) + +@app.route("/") +def hello(): + return "Hello, World!" +``` + +``` +$ 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 + diff --git a/venv/Lib/site-packages/flask-3.0.3.dist-info/RECORD b/venv/Lib/site-packages/flask-3.0.3.dist-info/RECORD new file mode 100644 index 00000000..e34f1e4c --- /dev/null +++ b/venv/Lib/site-packages/flask-3.0.3.dist-info/RECORD @@ -0,0 +1,58 @@ +../../Scripts/flask.exe,sha256=Mv9UQWgZFoF-2Wfdv602DwQP59GKGfZPwMKeV0NGWcM,108452 +flask-3.0.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +flask-3.0.3.dist-info/LICENSE.txt,sha256=SJqOEQhQntmKN7uYPhHg9-HTHwvY-Zp5yESOf_N9B-o,1475 +flask-3.0.3.dist-info/METADATA,sha256=exPahy4aahjV-mYqd9qb5HNP8haB_IxTuaotoSvCtag,3177 +flask-3.0.3.dist-info/RECORD,, +flask-3.0.3.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +flask-3.0.3.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81 +flask-3.0.3.dist-info/entry_points.txt,sha256=bBP7hTOS5fz9zLtC7sPofBZAlMkEvBxu7KqS6l5lvc4,40 +flask/__init__.py,sha256=6xMqdVA0FIQ2U1KVaGX3lzNCdXPzoHPaa0hvQCNcfSk,2625 +flask/__main__.py,sha256=bYt9eEaoRQWdejEHFD8REx9jxVEdZptECFsV7F49Ink,30 +flask/__pycache__/__init__.cpython-312.pyc,, +flask/__pycache__/__main__.cpython-312.pyc,, +flask/__pycache__/app.cpython-312.pyc,, +flask/__pycache__/blueprints.cpython-312.pyc,, +flask/__pycache__/cli.cpython-312.pyc,, +flask/__pycache__/config.cpython-312.pyc,, +flask/__pycache__/ctx.cpython-312.pyc,, +flask/__pycache__/debughelpers.cpython-312.pyc,, +flask/__pycache__/globals.cpython-312.pyc,, +flask/__pycache__/helpers.cpython-312.pyc,, +flask/__pycache__/logging.cpython-312.pyc,, +flask/__pycache__/sessions.cpython-312.pyc,, +flask/__pycache__/signals.cpython-312.pyc,, +flask/__pycache__/templating.cpython-312.pyc,, +flask/__pycache__/testing.cpython-312.pyc,, +flask/__pycache__/typing.cpython-312.pyc,, +flask/__pycache__/views.cpython-312.pyc,, +flask/__pycache__/wrappers.cpython-312.pyc,, +flask/app.py,sha256=7-lh6cIj27riTE1Q18Ok1p5nOZ8qYiMux4Btc6o6mNc,60143 +flask/blueprints.py,sha256=7INXPwTkUxfOQXOOv1yu52NpHPmPGI5fMTMFZ-BG9yY,4430 +flask/cli.py,sha256=OOaf_Efqih1i2in58j-5ZZZmQnPpaSfiUFbEjlL9bzw,35825 +flask/config.py,sha256=bLzLVAj-cq-Xotu9erqOFte0xSFaVXyfz0AkP4GbwmY,13312 +flask/ctx.py,sha256=4atDhJJ_cpV1VMq4qsfU4E_61M1oN93jlS2H9gjrl58,15120 +flask/debughelpers.py,sha256=PGIDhStW_efRjpaa3zHIpo-htStJOR41Ip3OJWPYBwo,6080 +flask/globals.py,sha256=XdQZmStBmPIs8t93tjx6pO7Bm3gobAaONWkFcUHaGas,1713 +flask/helpers.py,sha256=tYrcQ_73GuSZVEgwFr-eMmV69UriFQDBmt8wZJIAqvg,23084 +flask/json/__init__.py,sha256=hLNR898paqoefdeAhraa5wyJy-bmRB2k2dV4EgVy2Z8,5602 +flask/json/__pycache__/__init__.cpython-312.pyc,, +flask/json/__pycache__/provider.cpython-312.pyc,, +flask/json/__pycache__/tag.cpython-312.pyc,, +flask/json/provider.py,sha256=q6iB83lSiopy80DZPrU-9mGcWwrD0mvLjiv9fHrRZgc,7646 +flask/json/tag.py,sha256=DhaNwuIOhdt2R74oOC9Y4Z8ZprxFYiRb5dUP5byyINw,9281 +flask/logging.py,sha256=8sM3WMTubi1cBb2c_lPkWpN0J8dMAqrgKRYLLi1dCVI,2377 +flask/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +flask/sansio/README.md,sha256=-0X1tECnilmz1cogx-YhNw5d7guK7GKrq_DEV2OzlU0,228 +flask/sansio/__pycache__/app.cpython-312.pyc,, +flask/sansio/__pycache__/blueprints.cpython-312.pyc,, +flask/sansio/__pycache__/scaffold.cpython-312.pyc,, +flask/sansio/app.py,sha256=YG5Gf7JVf1c0yccWDZ86q5VSfJUidOVp27HFxFNxC7U,38053 +flask/sansio/blueprints.py,sha256=Tqe-7EkZ-tbWchm8iDoCfD848f0_3nLv6NNjeIPvHwM,24637 +flask/sansio/scaffold.py,sha256=WLV9TRQMMhGlXz-1OKtQ3lv6mtIBQZxdW2HezYrGxoI,30633 +flask/sessions.py,sha256=RU4lzm9MQW9CtH8rVLRTDm8USMJyT4LbvYe7sxM2__k,14807 +flask/signals.py,sha256=V7lMUww7CqgJ2ThUBn1PiatZtQanOyt7OZpu2GZI-34,750 +flask/templating.py,sha256=2TcXLT85Asflm2W9WOSFxKCmYn5e49w_Jkg9-NaaJWo,7537 +flask/testing.py,sha256=3BFXb3bP7R5r-XLBuobhczbxDu8-1LWRzYuhbr-lwaE,10163 +flask/typing.py,sha256=ZavK-wV28Yv8CQB7u73qZp_jLalpbWdrXS37QR1ftN0,3190 +flask/views.py,sha256=B66bTvYBBcHMYk4dA1ScZD0oTRTBl0I5smp1lRm9riI,6939 +flask/wrappers.py,sha256=m1j5tIJxIu8_sPPgTAB_G4TTh52Q-HoDuw_qHV5J59g,5831 diff --git a/venv/Lib/site-packages/flask-3.0.3.dist-info/REQUESTED b/venv/Lib/site-packages/flask-3.0.3.dist-info/REQUESTED new file mode 100644 index 00000000..e69de29b diff --git a/venv/Lib/site-packages/flask-3.0.3.dist-info/WHEEL b/venv/Lib/site-packages/flask-3.0.3.dist-info/WHEEL new file mode 100644 index 00000000..3b5e64b5 --- /dev/null +++ b/venv/Lib/site-packages/flask-3.0.3.dist-info/WHEEL @@ -0,0 +1,4 @@ +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-3.0.3.dist-info/entry_points.txt b/venv/Lib/site-packages/flask-3.0.3.dist-info/entry_points.txt new file mode 100644 index 00000000..eec6733e --- /dev/null +++ b/venv/Lib/site-packages/flask-3.0.3.dist-info/entry_points.txt @@ -0,0 +1,3 @@ +[console_scripts] +flask=flask.cli:main + diff --git a/venv/Lib/site-packages/flask/__init__.py b/venv/Lib/site-packages/flask/__init__.py new file mode 100644 index 00000000..e86eb43e --- /dev/null +++ b/venv/Lib/site-packages/flask/__init__.py @@ -0,0 +1,60 @@ +from __future__ import annotations + +import typing as t + +from . import json as json +from .app import Flask as Flask +from .blueprints import Blueprint as Blueprint +from .config import Config as Config +from .ctx import after_this_request as after_this_request +from .ctx import copy_current_request_context as copy_current_request_context +from .ctx import has_app_context as has_app_context +from .ctx import has_request_context as has_request_context +from .globals import current_app as current_app +from .globals import g as g +from .globals import request as request +from .globals import session as session +from .helpers import abort as abort +from .helpers import flash as flash +from .helpers import get_flashed_messages as get_flashed_messages +from .helpers import get_template_attribute as get_template_attribute +from .helpers import make_response as make_response +from .helpers import redirect as redirect +from .helpers import send_file as send_file +from .helpers import send_from_directory as send_from_directory +from .helpers import stream_with_context as stream_with_context +from .helpers import url_for as url_for +from .json import jsonify as jsonify +from .signals import appcontext_popped as appcontext_popped +from .signals import appcontext_pushed as appcontext_pushed +from .signals import appcontext_tearing_down as appcontext_tearing_down +from .signals import before_render_template as before_render_template +from .signals import got_request_exception as got_request_exception +from .signals import message_flashed as message_flashed +from .signals import request_finished as request_finished +from .signals import request_started as request_started +from .signals import request_tearing_down as request_tearing_down +from .signals import template_rendered as template_rendered +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 +from .wrappers import Request as Request +from .wrappers import Response as Response + + +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" + " Flask 3.1. Use feature detection or" + " 'importlib.metadata.version(\"flask\")' instead.", + DeprecationWarning, + stacklevel=2, + ) + return importlib.metadata.version("flask") + + raise AttributeError(name) diff --git a/venv/Lib/site-packages/flask/__main__.py b/venv/Lib/site-packages/flask/__main__.py new file mode 100644 index 00000000..4e28416e --- /dev/null +++ b/venv/Lib/site-packages/flask/__main__.py @@ -0,0 +1,3 @@ +from .cli import main + +main() diff --git a/venv/Lib/site-packages/flask/__pycache__/__init__.cpython-312.pyc b/venv/Lib/site-packages/flask/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bf91b922a6b1d1f9e370ac78f4a7d215c7646615 GIT binary patch literal 2513 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 X+h_M8*#|L4QO+svXFewqkmUab;W8G2 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/flask/__pycache__/cli.cpython-312.pyc b/venv/Lib/site-packages/flask/__pycache__/cli.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e72cfd5b359699f9567b0798fd9ebeab1b248638 GIT binary patch literal 42306 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%0Ao(zYE}id>Le)!y{cmG%=%S;>VnsQ77-7&$ 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/flask/__pycache__/config.cpython-312.pyc b/venv/Lib/site-packages/flask/__pycache__/config.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b9598a6901390ad46406b7c35a4ad56182e8bbd0 GIT binary patch literal 16294 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/flask/__pycache__/ctx.cpython-312.pyc b/venv/Lib/site-packages/flask/__pycache__/ctx.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d4ec73890522157f474492ef988f0cf3fb1c93dd GIT binary patch literal 19871 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 q{NX&4q|R literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/flask/__pycache__/helpers.cpython-312.pyc b/venv/Lib/site-packages/flask/__pycache__/helpers.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..27cbdf551b3546839df6c039b7518df90b5c24e8 GIT binary patch literal 24851 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 K@jjIpH~udjf>SyG literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/flask/__pycache__/logging.cpython-312.pyc b/venv/Lib/site-packages/flask/__pycache__/logging.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e339ed57a7280b8bffc432ceecd67fc04998e6f3 GIT binary patch literal 3300 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 K+y~UCnee|OVDrTQ literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/flask/__pycache__/testing.cpython-312.pyc b/venv/Lib/site-packages/flask/__pycache__/testing.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bea65485026009a10b92604398b3fdbaeefdaca0 GIT binary patch literal 13739 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 X^?l4y`th=(n}6ooTc2=97>55JhB*j- literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/flask/__pycache__/typing.cpython-312.pyc b/venv/Lib/site-packages/flask/__pycache__/typing.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4cadabdbe2651413c624412e43522bb08342c185 GIT binary patch literal 4310 zcmbVNOH3PC7OirX@dq1YFvd7(FiA+le+U>76GDI_1V}m=J{^sPgbMrFu~XPiRT%=C z3A31xRqU1R6=sD|kA!D6nN>@@olQMy7FJ1-BK2x^j(XRtxmErlD43y3qEx=WbMCqC zKK^Ak8wq&+I{7lNX(EVUQiA=R8-DVx+)>t)sd0hty+yHWUnHt`&<8)A1Wx=enUX3bnno}^SpDm55Li0_qX~V zH6@SN2~neGqmZJs_v$&FP+^Zsjx$IiXO-!B5mflTTCSp>a;8cZyd9Up4#F;7gdCLB zP{H+|KrG|BE{6-zYWg{9{Xm3@7Uwa#s9vpRnQP|-wI8XdeBmyaYhH(%YCEag#W`$m z$V`Le#WT4$7rg)9yt*#KS5f_UDg!u|o8zT%&dm3wI;LL-c9mPaJi%MPE9>^TS>5Hj zJ?at4ZGGupgPO|e@b;clSADZ8{{z&vjtXCzhjLPDT>5~^Wlq1!6yO$gloUI+FZC?d z6OT^ZB;`5uUxp)`l?&M@XGvw#RB#8jHQlEF$k$3YDb+KPNK{~iP&C4S55Fz1mS*Rs zVlg)4Q;cBJJw=QkaXftL7ZD$WNW|4a`T(beH%G%uGFLn^EW^v8+VyBW;_n`mjB9Iw zxDe;i+8Rg6p>@J`EEEY!q#&7>|Mc?l+RW3(GYfOy&q@tz;6nUs!z1b~zqnkyKxjpk&i8p|^1q@gd1nR;Fa9LJ>h~uIR9c zfMulQ(Jez;KQJ7pll#}dh{Njk1!h zlC^xY&8C&bXOB>TiLoo>MPnB$_{QzqhPPhguDqj8{ImiLJG6=p&F zwmsunU4iq=ug2m$Vpj!dlz$zHt??TSyS5(ZLlID?YVP;FW`y;qP&3GJY=0%NU-oUH z$mZ&^(E2JL5>S7P@x5V!h+hrBX1p0J9sYsX_HX+88&Q9pMGrXn4kY1^pMza0QY1-! zA+G&GG<~J$50ihH{6Y_Wp$EUvL&ZA6+LY2Ibp^ViK*QTmptT>K>^|8W`sDuT-hKWR ze$MUA?fDWzDRpT-BPsZw5{JLgH`k1Tyzqa#!tbI!Gte8 zv_F~|&0f!p=g4E-kuFc=m-2di`<`gOhZ{Wa>w!a~_0!hJtpaV9>y_Zf#fElFcT@o0 z$^i*Jy&$&U!j{{Z?k;LI+TL#z-KcqQDmDdNke)t@BDY;{rBdtwwBaxK3A`j~i>T}Pr%OhGmm>!2wXeYi=lmkF)MLp5p zvHvRbs+_UMB}Vq%VcL=I-0#WsWEXSR<10s3^2@l}BX)VPb6ljyzfqcYNOpp1ZO(mN z24+^81_u-JBt2sL5N^IBTJB(a_@rw*Ii7yIzmwS!dnRzl1Kd0*S|&05P-Zku0;M*n z+5(h1uN@i>jJZ3xx5t}Dn|a^op`YCUbmzl(a7yf*!meqN_^!}wON}MRDxH!kW73$O zz>VFar6>1DwEO|nL+Z|{$$g;+BUZ(NySg$&weebsxOD-UPL~mS9rhWH@U*v>(`WRPK+*D@Q9i zBkml=_7RbG=Y`UUT|o6}(MCAhP98EcrA=!0wnRs-Xz9gtUrl1go+kI}Gxg$)G3>Z2 zTJB={p4>T{nDTKmR(HL;zoPAyXsN7-veAjD)IxGWbPZ$Mh-ewXv|BDQ1|_VTT{AZV HSIU0@dK-pL literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/flask/__pycache__/views.cpython-312.pyc b/venv/Lib/site-packages/flask/__pycache__/views.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eac10eeeecfc9a8e88929883dd3d8b91f5b85ef0 GIT binary patch literal 7050 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/flask/__pycache__/wrappers.cpython-312.pyc b/venv/Lib/site-packages/flask/__pycache__/wrappers.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9b9d38dae24a71fe3d90a161ab7593a76102f933 GIT binary patch literal 6156 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/flask/app.py b/venv/Lib/site-packages/flask/app.py new file mode 100644 index 00000000..7622b5e8 --- /dev/null +++ b/venv/Lib/site-packages/flask/app.py @@ -0,0 +1,1498 @@ +from __future__ import annotations + +import collections.abc as cabc +import os +import sys +import typing as t +import weakref +from datetime import timedelta +from inspect import iscoroutinefunction +from itertools import chain +from types import TracebackType +from urllib.parse import quote as _url_quote + +import click +from werkzeug.datastructures import Headers +from werkzeug.datastructures import ImmutableDict +from werkzeug.exceptions import BadRequestKeyError +from werkzeug.exceptions import HTTPException +from werkzeug.exceptions import InternalServerError +from werkzeug.routing import BuildError +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.wrappers import Response as BaseResponse + +from . import cli +from . import typing as ft +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 get_debug_flag +from .helpers import get_flashed_messages +from .helpers import get_load_dotenv +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 +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 Environment +from .wrappers import Request +from .wrappers import Response + +if t.TYPE_CHECKING: # pragma: no cover + from _typeshed.wsgi import StartResponse + from _typeshed.wsgi import WSGIEnvironment + + from .testing import FlaskClient + from .testing import FlaskCliRunner + +T_shell_context_processor = t.TypeVar( + "T_shell_context_processor", bound=ft.ShellContextProcessorCallable +) +T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable) +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) + + +def _make_timedelta(value: timedelta | int | None) -> timedelta | None: + if value is None or isinstance(value, timedelta): + return value + + return timedelta(seconds=value) + + +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 + the view functions, the URL rules, template configuration and much more. + + The name of the package is used to resolve resources from inside the + package or the folder the module is contained in depending on if the + package parameter resolves to an actual python package (a folder with + an :file:`__init__.py` file inside) or a standard module (just a ``.py`` file). + + For more information about resource loading, see :func:`open_resource`. + + Usually you create a :class:`Flask` instance in your main module or + in the :file:`__init__.py` file of your package like this:: + + from flask import Flask + app = Flask(__name__) + + .. admonition:: About the First Parameter + + The idea of the first parameter is to give Flask an idea of what + belongs to your application. This name is used to find resources + on the filesystem, can be used by extensions to improve debugging + information and a lot more. + + So it's important what you provide there. If you are using a single + module, `__name__` is always the correct value. If you however are + using a package, it's usually recommended to hardcode the name of + your package there. + + For example if your application is defined in :file:`yourapplication/app.py` + you should create it with one of the two versions below:: + + app = Flask('yourapplication') + app = Flask(__name__.split('.')[0]) + + Why is that? The application will work even with `__name__`, thanks + to how resources are looked up. However it will make debugging more + painful. Certain extensions can make assumptions based on the + import name of your application. For example the Flask-SQLAlchemy + extension will look for the code in your application that triggered + an SQL query in debug mode. If the import name is not properly set + up, that debugging information is lost. (For example it would only + pick up SQL queries in `yourapplication.app` and not + `yourapplication.views.frontend`) + + .. versionadded:: 0.7 + The `static_url_path`, `static_folder`, and `template_folder` + parameters were added. + + .. versionadded:: 0.8 + The `instance_path` and `instance_relative_config` parameters were + added. + + .. versionadded:: 0.11 + The `root_path` parameter was added. + + .. versionadded:: 1.0 + The ``host_matching`` and ``static_host`` parameters were added. + + .. versionadded:: 1.0 + The ``subdomain_matching`` parameter was added. Subdomain + matching needs to be enabled manually now. Setting + :data:`SERVER_NAME` does not implicitly enable it. + + :param import_name: the name of the application package + :param static_url_path: can be used to specify a different path for the + static files on the web. Defaults to the name + of the `static_folder` folder. + :param static_folder: The folder with static files that is served at + ``static_url_path``. Relative to the application ``root_path`` + or an absolute path. Defaults to ``'static'``. + :param static_host: the host to use when adding the static route. + Defaults to None. Required when using ``host_matching=True`` + with a ``static_folder`` configured. + :param host_matching: set ``url_map.host_matching`` attribute. + Defaults to False. + :param subdomain_matching: consider the subdomain relative to + :data:`SERVER_NAME` when matching routes. Defaults to False. + :param template_folder: the folder that contains the templates that should + be used by the application. Defaults to + ``'templates'`` folder in the root path of the + application. + :param instance_path: An alternative instance path for the application. + By default the folder ``'instance'`` next to the + package or module is assumed to be the instance + path. + :param instance_relative_config: if set to ``True`` relative filenames + for loading the config are assumed to + be relative to the instance path instead + of the application root. + :param root_path: The path to the root of the application files. + This should only be set manually when it can't be detected + automatically, such as for namespace packages. + """ + + default_config = ImmutableDict( + { + "DEBUG": None, + "TESTING": False, + "PROPAGATE_EXCEPTIONS": None, + "SECRET_KEY": None, + "PERMANENT_SESSION_LIFETIME": timedelta(days=31), + "USE_X_SENDFILE": False, + "SERVER_NAME": None, + "APPLICATION_ROOT": "/", + "SESSION_COOKIE_NAME": "session", + "SESSION_COOKIE_DOMAIN": None, + "SESSION_COOKIE_PATH": None, + "SESSION_COOKIE_HTTPONLY": True, + "SESSION_COOKIE_SECURE": False, + "SESSION_COOKIE_SAMESITE": None, + "SESSION_REFRESH_EACH_REQUEST": True, + "MAX_CONTENT_LENGTH": None, + "SEND_FILE_MAX_AGE_DEFAULT": None, + "TRAP_BAD_REQUEST_ERRORS": None, + "TRAP_HTTP_EXCEPTIONS": False, + "EXPLAIN_TEMPLATE_LOADING": False, + "PREFERRED_URL_SCHEME": "http", + "TEMPLATES_AUTO_RELOAD": None, + "MAX_COOKIE_SIZE": 4093, + } + ) + + #: The class that is used for request objects. See :class:`~flask.Request` + #: for more information. + request_class: type[Request] = Request + + #: 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. + #: + #: .. versionadded:: 0.8 + session_interface: SessionInterface = SecureCookieSessionInterface() + + def __init__( + self, + import_name: str, + static_url_path: str | None = None, + 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[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_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, + ) + + #: 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 = cli.AppGroup() + + # 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 + + # 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. + # For one, it might be created while the server is running (e.g. during + # development). Also, Google App Engine stores static files somewhere + if self.has_static_folder: + assert ( + bool(static_host) == host_matching + ), "Invalid static_host/host_matching combination" + # Use a weakref to avoid creating a reference cycle between the app + # and the view function (see #3761). + self_ref = weakref.ref(self) + self.add_url_rule( + f"{self.static_url_path}/", + endpoint="static", + host=static_host, + view_func=lambda **kw: self_ref().send_static_file(**kw), # type: ignore # noqa: B950 + ) + + 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. + + Note this is a duplicate of the same method in the Flask + class. + + .. 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 # 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. + + 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. + + """ + 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 open_instance_resource(self, resource: str, mode: str = "rb") -> t.IO[t.AnyStr]: + """Opens a resource from the application's instance folder + (:attr:`instance_path`). Otherwise works like + :meth:`open_resource`. Instance resources can also be opened for + writing. + + :param resource: the name of the resource. To access resources within + subfolders use forward slashes as separator. + :param mode: resource file opening mode, default is 'rb'. + """ + return open(os.path.join(self.instance_path, resource), mode) + + def create_jinja_environment(self) -> Environment: + """Create the Jinja environment based on :attr:`jinja_options` + and the various Jinja-related methods of the app. Changing + :attr:`jinja_options` after this will have no effect. Also adds + Flask-related globals and filters to the environment. + + .. versionchanged:: 0.11 + ``Environment.auto_reload`` set in accordance with + ``TEMPLATES_AUTO_RELOAD`` configuration option. + + .. versionadded:: 0.5 + """ + options = dict(self.jinja_options) + + if "autoescape" not in options: + options["autoescape"] = self.select_jinja_autoescape + + if "auto_reload" not in options: + auto_reload = self.config["TEMPLATES_AUTO_RELOAD"] + + if auto_reload is None: + auto_reload = self.debug + + options["auto_reload"] = auto_reload + + rv = self.jinja_environment(self, **options) + rv.globals.update( + url_for=self.url_for, + get_flashed_messages=get_flashed_messages, + config=self.config, + # request, session and g are normally added with the + # context processor for efficiency reasons but for imported + # templates we also want the proxies in there. + request=request, + session=session, + g=g, + ) + rv.policies["json.dumps_function"] = self.json.dumps + 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 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[misc] + + 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 + to inject. Note that the as of Flask 0.6, the original values + in the context will not be overridden if a context processor + decides to return a value with the same key. + + :param context: the context as a dictionary that is updated in place + to add extra variables. + """ + names: t.Iterable[str | None] = (None,) + + # A template may be rendered outside a request context. + if request: + names = chain(names, reversed(request.blueprints)) + + # The values passed to render_template take precedence. Keep a + # copy to re-apply after all context functions. + orig_ctx = context.copy() + + for name in names: + if name in self.template_context_processors: + for func in self.template_context_processors[name]: + context.update(self.ensure_sync(func)()) + + context.update(orig_ctx) + + 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. + + .. versionadded:: 0.11 + """ + rv = {"app": self, "g": g} + for processor in self.shell_context_processors: + rv.update(processor()) + return rv + + def run( + self, + host: str | None = None, + port: int | None = None, + debug: bool | None = None, + load_dotenv: bool = True, + **options: t.Any, + ) -> None: + """Runs the application on a local development server. + + Do not use ``run()`` in a production setting. It is not intended to + meet security and performance requirements for a production server. + Instead, see :doc:`/deploying/index` for WSGI server recommendations. + + If the :attr:`debug` flag is set the server will automatically reload + for code changes and show a debugger in case an exception happened. + + If you want to run the application in debug mode, but disable the + code execution on the interactive debugger, you can pass + ``use_evalex=False`` as parameter. This will keep the debugger's + traceback screen active, but disable code execution. + + It is not recommended to use this function for development with + automatic reloading as this is badly supported. Instead you should + be using the :command:`flask` command line script's ``run`` support. + + .. admonition:: Keep in Mind + + Flask will suppress any server error with a generic error page + unless it is in debug mode. As such to enable just the + interactive debugger without the code reloading, you have to + invoke :meth:`run` with ``debug=True`` and ``use_reloader=False``. + Setting ``use_debugger`` to ``True`` without being in debug mode + won't catch any exceptions because there won't be any to + catch. + + :param host: the hostname to listen on. Set this to ``'0.0.0.0'`` to + have the server available externally as well. Defaults to + ``'127.0.0.1'`` or the host in the ``SERVER_NAME`` config variable + if present. + :param port: the port of the webserver. Defaults to ``5000`` or the + port defined in the ``SERVER_NAME`` config variable if present. + :param debug: if given, enable or disable debug mode. See + :attr:`debug`. + :param load_dotenv: Load the nearest :file:`.env` and :file:`.flaskenv` + files to set environment variables. Will also change the working + directory to the directory containing the first file found. + :param options: the options to be forwarded to the underlying Werkzeug + server. See :func:`werkzeug.serving.run_simple` for more + information. + + .. versionchanged:: 1.0 + If installed, python-dotenv will be used to load environment + variables from :file:`.env` and :file:`.flaskenv` files. + + The :envvar:`FLASK_DEBUG` environment variable will override :attr:`debug`. + + Threaded mode is enabled by default. + + .. versionchanged:: 0.10 + The default port is now picked from the ``SERVER_NAME`` + variable. + """ + # Ignore this call so that it doesn't start another server if + # the 'flask run' command is used. + if os.environ.get("FLASK_RUN_FROM_CLI") == "true": + if not is_running_from_reloader(): + click.secho( + " * Ignoring a call to 'app.run()' that would block" + " the current 'flask' CLI command.\n" + " Only call 'app.run()' in an 'if __name__ ==" + ' "__main__"\' guard.', + fg="red", + ) + + return + + if get_load_dotenv(load_dotenv): + cli.load_dotenv() + + # if set, env var overrides existing value + if "FLASK_DEBUG" in os.environ: + self.debug = get_debug_flag() + + # debug passed to method overrides all other sources + if debug is not None: + self.debug = bool(debug) + + server_name = self.config.get("SERVER_NAME") + sn_host = sn_port = None + + if server_name: + sn_host, _, sn_port = server_name.partition(":") + + if not host: + if sn_host: + host = sn_host + else: + host = "127.0.0.1" + + if port or port == 0: + port = int(port) + elif sn_port: + port = int(sn_port) + else: + port = 5000 + + options.setdefault("use_reloader", self.debug) + options.setdefault("use_debugger", self.debug) + options.setdefault("threaded", True) + + cli.show_server_banner(self.debug, self.name) + + from werkzeug.serving import run_simple + + try: + run_simple(t.cast(str, host), port, self, **options) + finally: + # reset the first request information if the development server + # reset normally. This makes it possible to restart the server + # without reloader and that stuff from an interactive shell. + self._got_first_request = False + + def test_client(self, use_cookies: bool = True, **kwargs: t.Any) -> FlaskClient: + """Creates a test client for this application. For information + about unit testing head over to :doc:`/testing`. + + Note that if you are testing for assertions or exceptions in your + application code, you must set ``app.testing = True`` in order for the + exceptions to propagate to the test client. Otherwise, the exception + will be handled by the application (not visible to the test client) and + the only indication of an AssertionError or other exception will be a + 500 status code response to the test client. See the :attr:`testing` + attribute. For example:: + + app.testing = True + client = app.test_client() + + The test client can be used in a ``with`` block to defer the closing down + of the context until the end of the ``with`` block. This is useful if + you want to access the context locals for testing:: + + with app.test_client() as c: + rv = c.get('/?vodka=42') + assert request.args['vodka'] == '42' + + Additionally, you may pass optional keyword arguments that will then + be passed to the application's :attr:`test_client_class` constructor. + For example:: + + from flask.testing import FlaskClient + + class CustomClient(FlaskClient): + def __init__(self, *args, **kwargs): + self._authentication = kwargs.pop("authentication") + super(CustomClient,self).__init__( *args, **kwargs) + + app.test_client_class = CustomClient + client = app.test_client(authentication='Basic ....') + + See :class:`~flask.testing.FlaskClient` for more information. + + .. versionchanged:: 0.4 + added support for ``with`` block usage for the client. + + .. versionadded:: 0.7 + The `use_cookies` parameter was added as well as the ability + to override the client to be used by setting the + :attr:`test_client_class` attribute. + + .. versionchanged:: 0.11 + Added `**kwargs` to support passing additional keyword arguments to + the constructor of :attr:`test_client_class`. + """ + cls = self.test_client_class + if cls is None: + from .testing import FlaskClient as cls + return cls( # type: ignore + self, self.response_class, use_cookies=use_cookies, **kwargs + ) + + def test_cli_runner(self, **kwargs: t.Any) -> FlaskCliRunner: + """Create a CLI runner for testing CLI commands. + See :ref:`testing-cli`. + + Returns an instance of :attr:`test_cli_runner_class`, by default + :class:`~flask.testing.FlaskCliRunner`. The Flask app object is + passed as the first argument. + + .. versionadded:: 1.0 + """ + cls = self.test_cli_runner_class + + if cls is None: + from .testing import FlaskCliRunner as cls + + return cls(self, **kwargs) # type: ignore + + def handle_http_exception( + self, e: HTTPException + ) -> HTTPException | ft.ResponseReturnValue: + """Handles an HTTP exception. By default this will invoke the + registered error handlers and fall back to returning the + exception as response. + + .. versionchanged:: 1.0.3 + ``RoutingException``, used internally for actions such as + slash redirects during routing, is not passed to error + handlers. + + .. versionchanged:: 1.0 + Exceptions are looked up by code *and* by MRO, so + ``HTTPException`` subclasses can be handled with a catch-all + handler for the base ``HTTPException``. + + .. versionadded:: 0.3 + """ + # Proxy exceptions don't have error codes. We want to always return + # those unchanged as errors + if e.code is None: + return e + + # RoutingExceptions are used internally to trigger routing + # actions, such as slash redirects raising RequestRedirect. They + # are not raised or handled in user code. + if isinstance(e, RoutingException): + return e + + handler = self._find_error_handler(e, request.blueprints) + if handler is None: + return e + return self.ensure_sync(handler)(e) # type: ignore[no-any-return] + + def handle_user_exception( + self, e: Exception + ) -> HTTPException | ft.ResponseReturnValue: + """This method is called whenever an exception occurs that + should be handled. A special case is :class:`~werkzeug + .exceptions.HTTPException` which is forwarded to the + :meth:`handle_http_exception` method. This function will either + return a response value or reraise the exception with the same + traceback. + + .. versionchanged:: 1.0 + Key errors raised from request data like ``form`` show the + bad key in debug mode rather than a generic bad request + message. + + .. versionadded:: 0.7 + """ + if isinstance(e, BadRequestKeyError) and ( + self.debug or self.config["TRAP_BAD_REQUEST_ERRORS"] + ): + e.show_exception = True + + if isinstance(e, HTTPException) and not self.trap_http_exception(e): + return self.handle_http_exception(e) + + handler = self._find_error_handler(e, request.blueprints) + + if handler is None: + raise + + 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 + associated with it, or that was raised from an error handler. + This always causes a 500 ``InternalServerError``. + + Always sends the :data:`got_request_exception` signal. + + If :data:`PROPAGATE_EXCEPTIONS` is ``True``, such as in debug + mode, the error will be re-raised so that the debugger can + display it. Otherwise, the original exception is logged, and + an :exc:`~werkzeug.exceptions.InternalServerError` is returned. + + If an error handler is registered for ``InternalServerError`` or + ``500``, it will be used. For consistency, the handler will + always receive the ``InternalServerError``. The original + unhandled exception is available as ``e.original_exception``. + + .. versionchanged:: 1.1.0 + Always passes the ``InternalServerError`` instance to the + handler, setting ``original_exception`` to the unhandled + error. + + .. versionchanged:: 1.1.0 + ``after_request`` functions and other finalization is done + even for the default 500 response when there is no handler. + + .. versionadded:: 0.3 + """ + exc_info = sys.exc_info() + got_request_exception.send(self, _async_wrapper=self.ensure_sync, exception=e) + propagate = self.config["PROPAGATE_EXCEPTIONS"] + + if propagate is None: + propagate = self.testing or self.debug + + if propagate: + # Re-raise if called with an active exception, otherwise + # raise the passed in exception. + if exc_info[1] is e: + raise + + raise e + + self.log_exception(exc_info) + server_error: InternalServerError | ft.ResponseReturnValue + server_error = InternalServerError(original_exception=e) + handler = self._find_error_handler(server_error, request.blueprints) + + if handler is not None: + server_error = self.ensure_sync(handler)(server_error) + + return self.finalize_request(server_error, from_error_handler=True) + + def log_exception( + self, + exc_info: (tuple[type, BaseException, TracebackType] | tuple[None, None, None]), + ) -> None: + """Logs an exception. This is called by :meth:`handle_exception` + if debugging is disabled and right before the handler is called. + The default implementation logs the exception as error on the + :attr:`logger`. + + .. versionadded:: 0.8 + """ + self.logger.error( + f"Exception on {request.path} [{request.method}]", exc_info=exc_info + ) + + 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 + be a response object. In order to convert the return value to a + proper response object, call :func:`make_response`. + + .. versionchanged:: 0.7 + This no longer does the exception handling, this code was + moved to the new :meth:`full_dispatch_request`. + """ + req = request_ctx.request + if req.routing_exception is not None: + self.raise_routing_exception(req) + rule: Rule = req.url_rule # type: ignore[assignment] + # if we provide automatic options for this URL and the + # request came with the OPTIONS method, reply automatically + if ( + getattr(rule, "provide_automatic_options", False) + and req.method == "OPTIONS" + ): + 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) # type: ignore[no-any-return] + + def full_dispatch_request(self) -> Response: + """Dispatches the request and on top of that performs request + pre and postprocessing as well as HTTP exception catching and + error handling. + + .. versionadded:: 0.7 + """ + self._got_first_request = True + + try: + request_started.send(self, _async_wrapper=self.ensure_sync) + rv = self.preprocess_request() + if rv is None: + rv = self.dispatch_request() + except Exception as e: + rv = self.handle_user_exception(e) + return self.finalize_request(rv) + + def finalize_request( + self, + rv: ft.ResponseReturnValue | HTTPException, + from_error_handler: bool = False, + ) -> Response: + """Given the return value from a view function this finalizes + the request by converting it into a response and invoking the + postprocessing functions. This is invoked for both normal + request dispatching as well as error handlers. + + Because this means that it might be called as a result of a + failure a special safe mode is available which can be enabled + with the `from_error_handler` flag. If enabled, failures in + response processing will be logged and otherwise ignored. + + :internal: + """ + response = self.make_response(rv) + try: + response = self.process_response(response) + request_finished.send( + self, _async_wrapper=self.ensure_sync, response=response + ) + except Exception: + if not from_error_handler: + raise + self.logger.exception( + "Request finalizing failed with an error while handling an error" + ) + return response + + def make_default_options_response(self) -> Response: + """This method is called to create the default ``OPTIONS`` response. + This can be changed through subclassing to change the default + behavior of ``OPTIONS`` responses. + + .. versionadded:: 0.7 + """ + adapter = request_ctx.url_adapter + methods = adapter.allowed_methods() # type: ignore[union-attr] + rv = self.response_class() + rv.allow.update(methods) + return rv + + 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. + + Override this method to change how the app runs async views. + + .. versionadded:: 2.0 + """ + if iscoroutinefunction(func): + return self.async_to_sync(func) + + return func + + def async_to_sync( + 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. + + .. code-block:: python + + result = app.async_to_sync(func)(*args, **kwargs) + + Override this method to change how the app converts async code + to be synchronously callable. + + .. versionadded:: 2.0 + """ + try: + from asgiref.sync import async_to_sync as asgiref_async_to_sync + except ImportError: + raise RuntimeError( + "Install Flask with the 'async' extra in order to use async views." + ) from None + + return asgiref_async_to_sync(func) + + def url_for( + self, + /, + endpoint: str, + *, + _anchor: str | None = None, + _method: str | None = None, + _scheme: str | None = None, + _external: bool | None = None, + **values: t.Any, + ) -> str: + """Generate a URL to the given endpoint with the given values. + + This is called by :func:`flask.url_for`, and can be called + directly as well. + + An *endpoint* is the name of a URL rule, usually added with + :meth:`@app.route() `, and usually the same name as the + view function. A route defined in a :class:`~flask.Blueprint` + will prepend the blueprint's name separated by a ``.`` to the + endpoint. + + In some cases, such as email messages, you want URLs to include + the scheme and domain, like ``https://example.com/hello``. When + not in an active request, URLs will be external by default, but + this requires setting :data:`SERVER_NAME` so Flask knows what + domain to use. :data:`APPLICATION_ROOT` and + :data:`PREFERRED_URL_SCHEME` should also be configured as + needed. This config is only used when not in an active request. + + Functions can be decorated with :meth:`url_defaults` to modify + keyword arguments before the URL is built. + + If building fails for some reason, such as an unknown endpoint + or incorrect values, the app's :meth:`handle_url_build_error` + method is called. If that returns a string, that is returned, + otherwise a :exc:`~werkzeug.routing.BuildError` is raised. + + :param endpoint: The endpoint name associated with the URL to + generate. If this starts with a ``.``, the current blueprint + name (if any) will be used. + :param _anchor: If given, append this as ``#anchor`` to the URL. + :param _method: If given, generate the URL associated with this + method for the endpoint. + :param _scheme: If given, the URL will have this scheme if it + is external. + :param _external: If given, prefer the URL to be internal + (False) or require it to be external (True). External URLs + include the scheme and domain. When not in an active + request, URLs are external by default. + :param values: Values to use for the variable parts of the URL + rule. Unknown keys are appended as query string arguments, + like ``?a=b&c=d``. + + .. versionadded:: 2.2 + Moved from ``flask.url_for``, which calls this method. + """ + req_ctx = _cv_request.get(None) + + if req_ctx is not None: + url_adapter = req_ctx.url_adapter + blueprint_name = req_ctx.request.blueprint + + # If the endpoint starts with "." and the request matches a + # blueprint, the endpoint is relative to the blueprint. + if endpoint[:1] == ".": + if blueprint_name is not None: + endpoint = f"{blueprint_name}{endpoint}" + else: + endpoint = endpoint[1:] + + # When in a request, generate a URL without scheme and + # domain by default, unless a scheme is given. + if _external is None: + _external = _scheme is not None + else: + app_ctx = _cv_app.get(None) + + # If called by helpers.url_for, an app context is active, + # use its url_adapter. Otherwise, app.url_for was called + # directly, build an adapter. + if app_ctx is not None: + url_adapter = app_ctx.url_adapter + else: + url_adapter = self.create_url_adapter(None) + + if url_adapter is None: + raise RuntimeError( + "Unable to build URLs outside an active request" + " without 'SERVER_NAME' configured. Also configure" + " 'APPLICATION_ROOT' and 'PREFERRED_URL_SCHEME' as" + " needed." + ) + + # When outside a request, generate a URL with scheme and + # domain by default. + if _external is None: + _external = True + + # It is an error to set _scheme when _external=False, in order + # to avoid accidental insecure URLs. + if _scheme is not None and not _external: + raise ValueError("When specifying '_scheme', '_external' must be True.") + + self.inject_url_defaults(endpoint, values) + + try: + rv = url_adapter.build( # type: ignore[union-attr] + endpoint, + values, + method=_method, + url_scheme=_scheme, + force_external=_external, + ) + except BuildError as error: + values.update( + _anchor=_anchor, _method=_method, _scheme=_scheme, _external=_external + ) + return self.handle_url_build_error(error, endpoint, values) + + if _anchor is not None: + _anchor = _url_quote(_anchor, safe="%!#$&'()*+,/:;=?@") + rv = f"{rv}#{_anchor}" + + return rv + + def make_response(self, rv: ft.ResponseReturnValue) -> Response: + """Convert the return value from a view function to an instance of + :attr:`response_class`. + + :param rv: the return value from the view function. The view function + must return a response. Returning ``None``, or the view ending + without returning, is not allowed. The following types are allowed + for ``view_rv``: + + ``str`` + A response object is created with the string encoded to UTF-8 + as the body. + + ``bytes`` + A response object is created with the bytes as the body. + + ``dict`` + A dictionary that will be jsonify'd before being returned. + + ``list`` + A list that will be jsonify'd before being returned. + + ``generator`` or ``iterator`` + A generator that returns ``str`` or ``bytes`` to be + streamed as the response. + + ``tuple`` + Either ``(body, status, headers)``, ``(body, status)``, or + ``(body, headers)``, where ``body`` is any of the other types + allowed here, ``status`` is a string or an integer, and + ``headers`` is a dictionary or a list of ``(key, value)`` + tuples. If ``body`` is a :attr:`response_class` instance, + ``status`` overwrites the exiting value and ``headers`` are + extended. + + :attr:`response_class` + The object is returned unchanged. + + other :class:`~werkzeug.wrappers.Response` class + The object is coerced to :attr:`response_class`. + + :func:`callable` + The function is called as a WSGI application. The result is + used to create a response object. + + .. versionchanged:: 2.2 + A generator will be converted to a streaming response. + A list will be converted to a JSON response. + + .. versionchanged:: 1.1 + A dict will be converted to a JSON response. + + .. versionchanged:: 0.9 + Previously a tuple was interpreted as the arguments for the + response object. + """ + + status = headers = None + + # unpack tuple returns + if isinstance(rv, tuple): + len_rv = len(rv) + + # a 3-tuple is unpacked directly + if len_rv == 3: + rv, status, headers = rv # type: ignore[misc] + # decide if a 2-tuple has status or headers + elif len_rv == 2: + if isinstance(rv[1], (Headers, dict, tuple, list)): + rv, headers = rv + else: + rv, status = rv # type: ignore[assignment,misc] + # other sized tuples are not allowed + else: + raise TypeError( + "The view function did not return a valid response tuple." + " The tuple must have the form (body, status, headers)," + " (body, status), or (body, headers)." + ) + + # the body must not be None + if rv is None: + raise TypeError( + f"The view function for {request.endpoint!r} did not" + " return a valid response. The function either returned" + " None or ended without a return statement." + ) + + # make sure the body is an instance of the response class + if not isinstance(rv, self.response_class): + if isinstance(rv, (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 + rv = self.response_class( + rv, + status=status, + headers=headers, # type: ignore[arg-type] + ) + status = headers = None + elif isinstance(rv, (dict, list)): + rv = self.json.response(rv) + elif isinstance(rv, BaseResponse) or callable(rv): + # evaluate a WSGI callable, or coerce a different response + # class to the correct type + try: + rv = self.response_class.force_type( + rv, # type: ignore[arg-type] + request.environ, + ) + except TypeError as e: + raise TypeError( + f"{e}\nThe view function did not return a valid" + " response. The return type must be a string," + " dict, list, tuple with headers or status," + " Response instance, or WSGI callable, but it" + f" was a {type(rv).__name__}." + ).with_traceback(sys.exc_info()[2]) from None + else: + raise TypeError( + "The view function did not return a valid" + " response. The return type must be a string," + " dict, list, tuple with headers or status," + " Response instance, or WSGI callable, but it was a" + f" {type(rv).__name__}." + ) + + rv = t.cast(Response, rv) + # prefer the status if it was provided + if status is not None: + if isinstance(status, (str, bytes, bytearray)): + rv.status = status + else: + rv.status_code = status + + # extend existing headers with provided headers + if headers: + rv.headers.update(headers) # type: ignore[arg-type] + + return rv + + def preprocess_request(self) -> ft.ResponseReturnValue | None: + """Called before the request is dispatched. Calls + :attr:`url_value_preprocessors` registered with the app and the + current blueprint (if any). Then calls :attr:`before_request_funcs` + registered with the app and the blueprint. + + If any :meth:`before_request` handler 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. + """ + names = (None, *reversed(request.blueprints)) + + for name in names: + if name in self.url_value_preprocessors: + for url_func in self.url_value_preprocessors[name]: + url_func(request.endpoint, request.view_args) + + for name in names: + if name in self.before_request_funcs: + for before_func in self.before_request_funcs[name]: + rv = self.ensure_sync(before_func)() + + if rv is not None: + return rv # type: ignore[no-any-return] + + return None + + def process_response(self, response: Response) -> Response: + """Can be overridden in order to modify the response object + before it's sent to the WSGI server. By default this will + call all the :meth:`after_request` decorated functions. + + .. versionchanged:: 0.5 + As of Flask 0.5 the functions registered for after request + execution are called in reverse order of registration. + + :param response: a :attr:`response_class` object. + :return: a new response object or the same, has to be an + instance of :attr:`response_class`. + """ + ctx = request_ctx._get_current_object() # type: ignore[attr-defined] + + for func in ctx._after_request_functions: + response = self.ensure_sync(func)(response) + + for name in chain(request.blueprints, (None,)): + if name in self.after_request_funcs: + for func in reversed(self.after_request_funcs[name]): + response = self.ensure_sync(func)(response) + + if not self.session_interface.is_null_session(ctx.session): + self.session_interface.save_session(self, ctx.session, response) + + return response + + def do_teardown_request( + 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. + + This calls all functions decorated with + :meth:`teardown_request`, and :meth:`Blueprint.teardown_request` + if a blueprint handled the request. Finally, the + :data:`request_tearing_down` signal is sent. + + This is called by + :meth:`RequestContext.pop() `, + which may be delayed during testing to maintain access to + resources. + + :param exc: An unhandled exception raised while dispatching the + request. Detected from the current exception information if + not passed. Passed to each teardown function. + + .. versionchanged:: 0.9 + Added the ``exc`` argument. + """ + if exc is _sentinel: + exc = sys.exc_info()[1] + + for name in chain(request.blueprints, (None,)): + if name in self.teardown_request_funcs: + for func in reversed(self.teardown_request_funcs[name]): + self.ensure_sync(func)(exc) + + request_tearing_down.send(self, _async_wrapper=self.ensure_sync, exc=exc) + + def do_teardown_appcontext( + self, + exc: BaseException | None = _sentinel, # type: ignore[assignment] + ) -> None: + """Called right before the application context is popped. + + When handling a request, the application context is popped + after the request context. See :meth:`do_teardown_request`. + + This calls all functions decorated with + :meth:`teardown_appcontext`. Then the + :data:`appcontext_tearing_down` signal is sent. + + This is called by + :meth:`AppContext.pop() `. + + .. versionadded:: 0.9 + """ + if exc is _sentinel: + exc = sys.exc_info()[1] + + for func in reversed(self.teardown_appcontext_funcs): + self.ensure_sync(func)(exc) + + appcontext_tearing_down.send(self, _async_wrapper=self.ensure_sync, exc=exc) + + def app_context(self) -> AppContext: + """Create an :class:`~flask.ctx.AppContext`. Use as a ``with`` + block to push the context, which will make :data:`current_app` + point at this application. + + An application context is automatically pushed by + :meth:`RequestContext.push() ` + when handling a request, and when running a CLI command. Use + this to manually create a context outside of these situations. + + :: + + with app.app_context(): + init_db() + + See :doc:`/appcontext`. + + .. versionadded:: 0.9 + """ + return AppContext(self) + + 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. + + See :doc:`/reqcontext`. + + Typically you should not call this from your own code. A request + context is automatically pushed by the :meth:`wsgi_app` when + handling a request. Use :meth:`test_request_context` to create + an environment and context instead of this method. + + :param environ: a WSGI environment + """ + return RequestContext(self, environ) + + def test_request_context(self, *args: t.Any, **kwargs: t.Any) -> RequestContext: + """Create a :class:`~flask.ctx.RequestContext` for a WSGI + environment created from the given values. This is mostly useful + during testing, where you may want to run a function that uses + request data without dispatching a full request. + + See :doc:`/reqcontext`. + + Use a ``with`` block to push the context, which will make + :data:`request` point at the request for the created + environment. :: + + with app.test_request_context(...): + generate_report() + + When using the shell, it may be easier to push and pop the + context manually to avoid indentation. :: + + ctx = app.test_request_context(...) + ctx.push() + ... + ctx.pop() + + Takes the same arguments as Werkzeug's + :class:`~werkzeug.test.EnvironBuilder`, with some defaults from + the application. See the linked Werkzeug docs for most of the + available arguments. Flask-specific behavior is listed here. + + :param path: URL path being requested. + :param base_url: Base URL where the app is being served, which + ``path`` is relative to. If not given, built from + :data:`PREFERRED_URL_SCHEME`, ``subdomain``, + :data:`SERVER_NAME`, and :data:`APPLICATION_ROOT`. + :param subdomain: Subdomain name to append to + :data:`SERVER_NAME`. + :param url_scheme: Scheme to use instead of + :data:`PREFERRED_URL_SCHEME`. + :param data: The request body, either as a string or a dict of + form keys and values. + :param json: If given, this is serialized as JSON and passed as + ``data``. Also defaults ``content_type`` to + ``application/json``. + :param args: other positional arguments passed to + :class:`~werkzeug.test.EnvironBuilder`. + :param kwargs: other keyword arguments passed to + :class:`~werkzeug.test.EnvironBuilder`. + """ + from .testing import EnvironBuilder + + builder = EnvironBuilder(self, *args, **kwargs) + + try: + return self.request_context(builder.get_environ()) + finally: + builder.close() + + 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:: + + app = MyMiddleware(app) + + It's a better idea to do this instead:: + + app.wsgi_app = MyMiddleware(app.wsgi_app) + + Then you still have the original application object around and + can continue to call methods on it. + + .. versionchanged:: 0.7 + Teardown events for the request and app contexts are called + even if an unhandled error occurs. Other events may not be + called depending on when an error occurs during dispatch. + See :ref:`callbacks-and-errors`. + + :param environ: A WSGI environment. + :param start_response: A callable accepting a status code, + a list of headers, and an optional exception context to + start the response. + """ + ctx = self.request_context(environ) + error: BaseException | None = None + try: + try: + ctx.push() + response = self.full_dispatch_request() + except Exception as e: + error = e + response = self.handle_exception(e) + except: # noqa: B001 + error = sys.exc_info()[1] + raise + return response(environ, start_response) + finally: + if "werkzeug.debug.preserve_context" in environ: + environ["werkzeug.debug.preserve_context"](_cv_app.get()) + environ["werkzeug.debug.preserve_context"](_cv_request.get()) + + if error is not None and self.should_ignore_error(error): + error = None + + ctx.pop(error) + + 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. + """ + return self.wsgi_app(environ, start_response) diff --git a/venv/Lib/site-packages/flask/blueprints.py b/venv/Lib/site-packages/flask/blueprints.py new file mode 100644 index 00000000..aa9eacf2 --- /dev/null +++ b/venv/Lib/site-packages/flask/blueprints.py @@ -0,0 +1,129 @@ +from __future__ import annotations + +import os +import typing as t +from datetime import timedelta + +from .cli import AppGroup +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 +from .sansio.scaffold import _sentinel + +if t.TYPE_CHECKING: # pragma: no cover + from .wrappers import Response + + +class Blueprint(SansioBlueprint): + def __init__( + self, + name: str, + import_name: str, + static_folder: str | os.PathLike[str] | None = None, + static_url_path: str | None = None, + template_folder: str | os.PathLike[str] | None = None, + url_prefix: str | None = None, + subdomain: str | None = None, + url_defaults: dict[str, t.Any] | None = None, + root_path: str | None = None, + cli_group: str | None = _sentinel, # type: ignore + ) -> None: + super().__init__( + name, + import_name, + static_folder, + static_url_path, + template_folder, + url_prefix, + subdomain, + url_defaults, + root_path, + cli_group, + ) + + #: 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() + + # 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. + + 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. + + Note this is a duplicate of the same method in the Flask + class. + + .. 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 # 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. + + 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. + + """ + 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) diff --git a/venv/Lib/site-packages/flask/cli.py b/venv/Lib/site-packages/flask/cli.py new file mode 100644 index 00000000..ecb292a0 --- /dev/null +++ b/venv/Lib/site-packages/flask/cli.py @@ -0,0 +1,1109 @@ +from __future__ import annotations + +import ast +import collections.abc as cabc +import importlib.metadata +import inspect +import os +import platform +import re +import sys +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 +from werkzeug import run_simple +from werkzeug.serving import is_running_from_reloader +from werkzeug.utils import import_string + +from .globals import current_app +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 + + +class NoAppException(click.UsageError): + """Raised if an application cannot be found or loaded.""" + + +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. + """ + from . import Flask + + # Search for the most common names first. + for attr_name in ("app", "application"): + app = getattr(module, attr_name, None) + + if isinstance(app, Flask): + return app + + # Otherwise find the only object that is a Flask instance. + matches = [v for v in module.__dict__.values() if isinstance(v, Flask)] + + if len(matches) == 1: + return matches[0] + elif len(matches) > 1: + raise NoAppException( + "Detected multiple Flask applications in module" + f" '{module.__name__}'. Use '{module.__name__}:name'" + " to specify the correct one." + ) + + # Search for app factory functions. + for attr_name in ("create_app", "make_app"): + app_factory = getattr(module, attr_name, None) + + if inspect.isfunction(app_factory): + try: + app = app_factory() + + if isinstance(app, Flask): + return app + except TypeError as e: + if not _called_with_wrong_args(app_factory): + raise + + raise NoAppException( + f"Detected factory '{attr_name}' in module '{module.__name__}'," + " but could not call it without arguments. Use" + f" '{module.__name__}:{attr_name}(args)'" + " to specify arguments." + ) from e + + raise NoAppException( + "Failed to find Flask application or factory in module" + f" '{module.__name__}'. Use '{module.__name__}:name'" + " to specify one." + ) + + +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. + + :param f: The function that was called. + :return: ``True`` if the call failed. + """ + tb = sys.exc_info()[2] + + try: + while tb is not None: + if tb.tb_frame.f_code is f.__code__: + # In the function, it was called successfully. + return False + + tb = tb.tb_next + + # Didn't reach the function. + return True + finally: + # Delete tb to break a circular reference. + # https://docs.python.org/2/library/sys.html#sys.exc_info + del tb + + +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. + """ + from . import Flask + + # Parse app_name as a single expression to determine if it's a valid + # attribute name or function call. + try: + expr = ast.parse(app_name.strip(), mode="eval").body + except SyntaxError: + raise NoAppException( + f"Failed to parse {app_name!r} as an attribute name or function call." + ) from None + + if isinstance(expr, ast.Name): + name = expr.id + args = [] + kwargs = {} + elif isinstance(expr, ast.Call): + # Ensure the function name is an attribute name only. + if not isinstance(expr.func, ast.Name): + raise NoAppException( + f"Function reference must be a simple name: {app_name!r}." + ) + + name = expr.func.id + + # 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 + if kw.arg is not None + } + except ValueError: + # literal_eval gives cryptic error messages, show a generic + # message with the full expression instead. + raise NoAppException( + f"Failed to parse arguments as literal values: {app_name!r}." + ) from None + else: + raise NoAppException( + f"Failed to parse {app_name!r} as an attribute name or function call." + ) + + try: + attr = getattr(module, name) + except AttributeError as e: + raise NoAppException( + f"Failed to find attribute {name!r} in {module.__name__!r}." + ) from e + + # If the attribute is a function, call it with any args and kwargs + # to get the real application. + if inspect.isfunction(attr): + try: + app = attr(*args, **kwargs) + except TypeError as e: + if not _called_with_wrong_args(attr): + raise + + raise NoAppException( + f"The factory {app_name!r} in module" + f" {module.__name__!r} could not be called with the" + " specified arguments." + ) from e + else: + app = attr + + if isinstance(app, Flask): + return app + + raise NoAppException( + "A valid Flask application was not obtained from" + f" '{module.__name__}:{app_name}'." + ) + + +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. + """ + path = os.path.realpath(path) + + fname, ext = os.path.splitext(path) + if ext == ".py": + path = fname + + if os.path.basename(path) == "__init__": + path = os.path.dirname(path) + + module_name = [] + + # move up until outside package structure (no __init__.py) + while True: + path, name = os.path.split(path) + module_name.append(name) + + if not os.path.exists(os.path.join(path, "__init__.py")): + break + + if sys.path[0] != path: + sys.path.insert(0, path) + + return ".".join(module_name[::-1]) + + +@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: # type: ignore[union-attr] + raise NoAppException( + f"While importing {module_name!r}, an ImportError was" + f" raised:\n\n{traceback.format_exc()}" + ) from None + elif raise_if_not_found: + raise NoAppException(f"Could not import {module_name!r}.") from None + else: + return None + + module = sys.modules[module_name] + + if app_name is None: + return find_best_app(module) + else: + return find_app_by_string(module, app_name) + + +def get_version(ctx: click.Context, param: click.Parameter, value: t.Any) -> None: + if not value or ctx.resilient_parsing: + return + + flask_version = importlib.metadata.version("flask") + werkzeug_version = importlib.metadata.version("werkzeug") + + click.echo( + f"Python {platform.python_version()}\n" + f"Flask {flask_version}\n" + f"Werkzeug {werkzeug_version}", + color=ctx.color, + ) + ctx.exit() + + +version_option = click.Option( + ["--version"], + help="Show the Flask version.", + expose_value=False, + callback=get_version, + is_flag=True, + is_eager=True, +) + + +class ScriptInfo: + """Helper object to deal with Flask applications. This is usually not + necessary to interface with as it's used internally in the dispatching + to click. In future versions of Flask this object will most likely play + a bigger role. Typically it's created automatically by the + :class:`FlaskGroup` but you can also manually create it and pass it + onwards as click object. + """ + + def __init__( + self, + app_import_path: str | None = None, + create_app: t.Callable[..., Flask] | None = None, + set_debug_flag: bool = True, + ) -> None: + #: Optionally the import path for the Flask application. + self.app_import_path = app_import_path + #: Optionally a function that is passed the script info to create + #: the instance of the application. + self.create_app = create_app + #: A dictionary with arbitrary data that can be associated with + #: this script info. + self.data: dict[t.Any, t.Any] = {} + self.set_debug_flag = set_debug_flag + self._loaded_app: Flask | None = None + + def load_app(self) -> Flask: + """Loads the Flask app (if not yet loaded) and returns it. Calling + this multiple times will just result in the already loaded app to + be returned. + """ + if self._loaded_app is not None: + return self._loaded_app + + if self.create_app is not None: + app: Flask | None = self.create_app() + else: + if self.app_import_path: + path, name = ( + re.split(r":(?![\\/])", self.app_import_path, maxsplit=1) + [None] + )[:2] + import_name = prepare_import(path) + app = locate_app(import_name, name) + else: + for path in ("wsgi.py", "app.py"): + import_name = prepare_import(path) + app = locate_app(import_name, None, raise_if_not_found=False) + + if app is not None: + break + + if app is None: + raise NoAppException( + "Could not locate a Flask application. Use the" + " 'flask --app' option, 'FLASK_APP' environment" + " variable, or a 'wsgi.py' or 'app.py' file in the" + " current directory." + ) + + if self.set_debug_flag: + # Update the app's debug flag through the descriptor so that + # other values repopulate as well. + app.debug = get_debug_flag() + + self._loaded_app = app + return app + + +pass_script_info = click.make_pass_decorator(ScriptInfo, ensure=True) + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) + + +def with_appcontext(f: F) -> F: + """Wraps a callback so that it's guaranteed to be executed with the + script's application context. + + Custom commands (and their options) registered under ``app.cli`` or + ``blueprint.cli`` will always have an app context available, this + decorator is not required in that case. + + .. versionchanged:: 2.2 + The app context is active for subcommands as well as the + decorated callback. The app context is always available to + ``app.cli`` command and parameter callbacks. + """ + + @click.pass_context + 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()) + + return ctx.invoke(f, *args, **kwargs) + + return update_wrapper(decorator, f) # type: ignore[return-value] + + +class AppGroup(click.Group): + """This works similar to a regular click :class:`~click.Group` but it + changes the behavior of the :meth:`command` decorator so that it + automatically wraps the functions in :func:`with_appcontext`. + + Not to be confused with :class:`FlaskGroup`. + """ + + 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: t.Callable[..., t.Any]) -> click.Command: + if wrap_for_ctx: + f = with_appcontext(f) + return super(AppGroup, self).command(*args, **kwargs)(f) # type: ignore[no-any-return] + + return decorator + + 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 super().group(*args, **kwargs) # type: ignore[no-any-return] + + +def _set_app(ctx: click.Context, param: click.Option, value: str | None) -> str | None: + if value is None: + return None + + info = ctx.ensure_object(ScriptInfo) + info.app_import_path = value + return value + + +# This option is eager so the app will be available if --help is given. +# --help is also eager, so --app must be before it in the param list. +# no_args_is_help bypasses eager processing, so this option must be +# processed manually in that case to ensure FLASK_APP gets picked up. +_app_option = click.Option( + ["-A", "--app"], + metavar="IMPORT", + help=( + "The Flask application or factory function to load, in the form 'module:name'." + " Module can be a dotted import or file path. Name is not required if it is" + " 'app', 'application', 'create_app', or 'make_app', and can be 'name(args)' to" + " pass arguments." + ), + is_eager=True, + expose_value=False, + callback=_set_app, +) + + +def _set_debug(ctx: click.Context, param: click.Option, value: bool) -> bool | None: + # If the flag isn't provided, it will default to False. Don't use + # that, let debug be set by env in that case. + source = ctx.get_parameter_source(param.name) # type: ignore[arg-type] + + if source is not None and source in ( + ParameterSource.DEFAULT, + ParameterSource.DEFAULT_MAP, + ): + return None + + # Set with env var instead of ScriptInfo.load so that it can be + # accessed early during a factory function. + os.environ["FLASK_DEBUG"] = "1" if value else "0" + return value + + +_debug_option = click.Option( + ["--debug/--no-debug"], + help="Set debug mode.", + expose_value=False, + callback=_set_debug, +) + + +def _env_file_callback( + ctx: click.Context, param: click.Option, value: str | None +) -> str | None: + if value is None: + return None + + import importlib + + try: + importlib.import_module("dotenv") + except ImportError: + raise click.BadParameter( + "python-dotenv must be installed to load an env file.", + ctx=ctx, + param=param, + ) from None + + # Don't check FLASK_SKIP_DOTENV, that only disables automatically + # loading .env and .flaskenv files. + load_dotenv(value) + return value + + +# This option is eager so env vars are loaded as early as possible to be +# used by other options. +_env_file_option = click.Option( + ["-e", "--env-file"], + type=click.Path(exists=True, dir_okay=False), + help="Load environment variables from this file. python-dotenv must be installed.", + is_eager=True, + expose_value=False, + callback=_env_file_callback, +) + + +class FlaskGroup(AppGroup): + """Special subclass of the :class:`AppGroup` group that supports + loading more commands from the configured Flask app. Normally a + developer does not have to interface with this class but there are + some very advanced use cases for which it makes sense to create an + instance of this. see :ref:`custom-scripts`. + + :param add_default_commands: if this is True then the default run and + shell commands will be added. + :param add_version_option: adds the ``--version`` option. + :param create_app: an optional callback that is passed the script info and + returns the loaded app. + :param load_dotenv: Load the nearest :file:`.env` and :file:`.flaskenv` + files to set environment variables. Will also change the working + directory to the directory containing the first file found. + :param set_debug_flag: Set the app's debug flag. + + .. versionchanged:: 2.2 + Added the ``-A/--app``, ``--debug/--no-debug``, ``-e/--env-file`` options. + + .. versionchanged:: 2.2 + An app context is pushed when running ``app.cli`` commands, so + ``@with_appcontext`` is no longer required for those commands. + + .. versionchanged:: 1.0 + If installed, python-dotenv will be used to load environment variables + from :file:`.env` and :file:`.flaskenv` files. + """ + + def __init__( + self, + add_default_commands: bool = True, + create_app: t.Callable[..., Flask] | None = None, + add_version_option: bool = True, + load_dotenv: bool = True, + set_debug_flag: bool = True, + **extra: t.Any, + ) -> None: + params = list(extra.pop("params", None) or ()) + # Processing is done with option callbacks instead of a group + # callback. This allows users to make a custom group callback + # without losing the behavior. --env-file must come first so + # that it is eagerly evaluated before --app. + params.extend((_env_file_option, _app_option, _debug_option)) + + if add_version_option: + params.append(version_option) + + if "context_settings" not in extra: + extra["context_settings"] = {} + + extra["context_settings"].setdefault("auto_envvar_prefix", "FLASK") + + super().__init__(params=params, **extra) + + self.create_app = create_app + self.load_dotenv = load_dotenv + self.set_debug_flag = set_debug_flag + + if add_default_commands: + self.add_command(run_command) + self.add_command(shell_command) + self.add_command(routes_command) + + self._loaded_plugin_commands = False + + def _load_plugin_commands(self) -> None: + if self._loaded_plugin_commands: + return + + if sys.version_info >= (3, 10): + from importlib import metadata + else: + # Use a backport on Python < 3.10. We technically have + # importlib.metadata on 3.8+, but the API changed in 3.10, + # so use the backport for consistency. + import importlib_metadata as metadata + + for ep in metadata.entry_points(group="flask.commands"): + self.add_command(ep.load(), ep.name) + + self._loaded_plugin_commands = True + + 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. + rv = super().get_command(ctx, name) + + if rv is not None: + return rv + + info = ctx.ensure_object(ScriptInfo) + + # Look up commands provided by the app, showing an error and + # continuing if the app couldn't be loaded. + try: + app = info.load_app() + except NoAppException as e: + click.secho(f"Error: {e.format_message()}\n", err=True, fg="red") + return None + + # 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: # type: ignore[attr-defined] + ctx.with_resource(app.app_context()) + + return app.cli.get_command(ctx, name) + + 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)) + info = ctx.ensure_object(ScriptInfo) + + # Add commands provided by the app, showing an error and + # continuing if the app couldn't be loaded. + try: + rv.update(info.load_app().cli.list_commands(ctx)) + except NoAppException as e: + # When an app couldn't be loaded, show the error message + # without the traceback. + click.secho(f"Error: {e.format_message()}\n", err=True, fg="red") + except Exception: + # When any other errors occurred during loading, show the + # full traceback. + click.secho(f"{traceback.format_exc()}\n", err=True, fg="red") + + return sorted(rv) + + def make_context( + self, + info_name: str | None, + args: list[str], + parent: click.Context | None = None, + **extra: t.Any, + ) -> click.Context: + # Set a flag to tell app.run to become a no-op. If app.run was + # not in a __name__ == __main__ guard, it would start the server + # when importing, blocking whatever command is being called. + os.environ["FLASK_RUN_FROM_CLI"] = "true" + + # Attempt to load .env and .flask env files. The --env-file + # option can cause another file to be loaded. + if get_load_dotenv(self.load_dotenv): + load_dotenv() + + if "obj" not in extra and "obj" not in self.context_settings: + extra["obj"] = ScriptInfo( + create_app=self.create_app, set_debug_flag=self.set_debug_flag + ) + + return super().make_context(info_name, args, parent=parent, **extra) + + def parse_args(self, ctx: click.Context, args: list[str]) -> list[str]: + if not args and self.no_args_is_help: + # Attempt to load --env-file and --app early in case they + # were given as env vars. Otherwise no_args_is_help will not + # see commands from app.cli. + _env_file_option.handle_parse_result(ctx, {}, []) + _app_option.handle_parse_result(ctx, {}, []) + + return super().parse_args(ctx, args) + + +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[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 + list are preferred over later files. + + This is a no-op if `python-dotenv`_ is not installed. + + .. _python-dotenv: https://github.com/theskumar/python-dotenv#readme + + :param path: Load the file at this location instead of searching. + :return: ``True`` if a file was loaded. + + .. versionchanged:: 2.0 + The current directory is not changed to the location of the + loaded file. + + .. versionchanged:: 2.0 + When loading the env files, set the default encoding to UTF-8. + + .. versionchanged:: 1.1.0 + Returns ``False`` when python-dotenv is not installed, or when + the given path isn't a file. + + .. versionadded:: 1.0 + """ + try: + import dotenv + except ImportError: + if path or os.path.isfile(".env") or os.path.isfile(".flaskenv"): + click.secho( + " * Tip: There are .env or .flaskenv files present." + ' Do "pip install python-dotenv" to use them.', + fg="yellow", + err=True, + ) + + return False + + # Always return after attempting to load a given path, don't load + # the default files. + if path is not None: + if os.path.isfile(path): + return dotenv.load_dotenv(path, encoding="utf-8") + + return False + + loaded = False + + for name in (".env", ".flaskenv"): + path = dotenv.find_dotenv(name, usecwd=True) + + if not path: + continue + + dotenv.load_dotenv(path, encoding="utf-8") + loaded = True + + return loaded # True if at least one file was located and loaded. + + +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. + """ + if is_running_from_reloader(): + return + + if app_import_path is not None: + click.echo(f" * Serving Flask app '{app_import_path}'") + + if debug is not None: + click.echo(f" * Debug mode: {'on' if debug else 'off'}") + + +class CertParamType(click.ParamType): + """Click option type for the ``--cert`` option. Allows either an + existing file, the string ``'adhoc'``, or an import for a + :class:`~ssl.SSLContext` object. + """ + + name = "path" + + def __init__(self) -> None: + self.path_type = click.Path(exists=True, dir_okay=False, resolve_path=True) + + def convert( + self, value: t.Any, param: click.Parameter | None, ctx: click.Context | None + ) -> t.Any: + try: + import ssl + except ImportError: + raise click.BadParameter( + 'Using "--cert" requires Python to be compiled with SSL support.', + ctx, + param, + ) from None + + try: + return self.path_type(value, param, ctx) + except click.BadParameter: + value = click.STRING(value, param, ctx).lower() + + if value == "adhoc": + try: + import cryptography # noqa: F401 + except ImportError: + raise click.BadParameter( + "Using ad-hoc certificates requires the cryptography library.", + ctx, + param, + ) from None + + return value + + obj = import_string(value, silent=True) + + if isinstance(obj, ssl.SSLContext): + return obj + + raise + + +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. + """ + cert = ctx.params.get("cert") + is_adhoc = cert == "adhoc" + + try: + import ssl + except ImportError: + is_context = False + else: + is_context = isinstance(cert, ssl.SSLContext) + + if value is not None: + if is_adhoc: + raise click.BadParameter( + 'When "--cert" is "adhoc", "--key" is not used.', ctx, param + ) + + if is_context: + raise click.BadParameter( + 'When "--cert" is an SSLContext object, "--key" is not used.', + ctx, + param, + ) + + if not cert: + raise click.BadParameter('"--cert" must also be specified.', ctx, param) + + ctx.params["cert"] = cert, value + + else: + if cert and not (is_adhoc or is_context): + raise click.BadParameter('Required when using "--cert".', ctx, param) + + return value + + +class SeparatedPathType(click.Path): + """Click option type that accepts a list of values separated by the + OS's path separator (``:``, ``;`` on Windows). Each value is + validated as a :class:`click.Path` type. + """ + + 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] + + +@click.command("run", short_help="Run a development server.") +@click.option("--host", "-h", default="127.0.0.1", help="The interface to bind to.") +@click.option("--port", "-p", default=5000, help="The port to bind to.") +@click.option( + "--cert", + type=CertParamType(), + help="Specify a certificate file to use HTTPS.", + is_eager=True, +) +@click.option( + "--key", + type=click.Path(exists=True, dir_okay=False, resolve_path=True), + callback=_validate_key, + expose_value=False, + help="The key file to use when specifying a certificate.", +) +@click.option( + "--reload/--no-reload", + default=None, + help="Enable or disable the reloader. By default the reloader " + "is active if debug is enabled.", +) +@click.option( + "--debugger/--no-debugger", + default=None, + help="Enable or disable the debugger. By default the debugger " + "is active if debug is enabled.", +) +@click.option( + "--with-threads/--without-threads", + default=True, + help="Enable or disable multithreading.", +) +@click.option( + "--extra-files", + default=None, + type=SeparatedPathType(), + help=( + "Extra files that trigger a reload on change. Multiple paths" + f" are separated by {os.path.pathsep!r}." + ), +) +@click.option( + "--exclude-patterns", + default=None, + type=SeparatedPathType(), + help=( + "Files matching these fnmatch patterns will not trigger a reload" + " on change. Multiple patterns are separated by" + f" {os.path.pathsep!r}." + ), +) +@pass_script_info +def run_command( + 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 + the stability, security, or performance of production WSGI servers. + + The reloader and debugger are enabled by default with the '--debug' + option. + """ + try: + app: WSGIApplication = info.load_app() + except Exception as e: + if is_running_from_reloader(): + # When reloading, print out the error immediately, but raise + # it later so the debugger or server can handle it. + traceback.print_exc() + err = e + + def app( + environ: WSGIEnvironment, start_response: StartResponse + ) -> cabc.Iterable[bytes]: + raise err from None + + else: + # When not reloading, raise the error immediately so the + # command fails. + raise e from None + + debug = get_debug_flag() + + if reload is None: + reload = debug + + if debugger is None: + debugger = debug + + show_server_banner(debug, info.app_import_path) + + run_simple( + host, + port, + app, + use_reloader=reload, + use_debugger=debugger, + threaded=with_threads, + ssl_context=cert, + extra_files=extra_files, + exclude_patterns=exclude_patterns, + ) + + +run_command.params.insert(0, _debug_option) + + +@click.command("shell", short_help="Run a shell in the app context.") +@with_appcontext +def shell_command() -> None: + """Run an interactive Python shell in the context of a given + Flask application. The application will populate the default + namespace of this shell according to its configuration. + + This is useful for executing small snippets of management code + without having to manually configure the application. + """ + import code + + banner = ( + f"Python {sys.version} on {sys.platform}\n" + f"App: {current_app.import_name}\n" + f"Instance: {current_app.instance_path}" + ) + ctx: dict[str, t.Any] = {} + + # Support the regular Python interpreter startup script if someone + # is using it. + startup = os.environ.get("PYTHONSTARTUP") + if startup and os.path.isfile(startup): + with open(startup) as f: + eval(compile(f.read(), startup, "exec"), ctx) + + ctx.update(current_app.make_shell_context()) + + # Site, customize, or startup script can set a hook to call when + # entering interactive mode. The default one sets up readline with + # tab and history completion. + interactive_hook = getattr(sys, "__interactivehook__", None) + + if interactive_hook is not None: + try: + import readline + from rlcompleter import Completer + except ImportError: + pass + else: + # rlcompleter uses __main__.__dict__ by default, which is + # flask.__main__. Use the shell context instead. + readline.set_completer(Completer(ctx).complete) + + interactive_hook() + + code.interact(banner=banner, local=ctx) + + +@click.command("routes", short_help="Show the routes for the app.") +@click.option( + "--sort", + "-s", + type=click.Choice(("endpoint", "methods", "domain", "rule", "match")), + default="endpoint", + help=( + "Method to sort routes by. 'match' is the order that Flask will match routes" + " when dispatching a request." + ), +) +@click.option("--all-methods", is_flag=True, help="Show HEAD and OPTIONS methods.") +@with_appcontext +def routes_command(sort: str, all_methods: bool) -> None: + """Show all registered routes with endpoints and methods.""" + rules = list(current_app.url_map.iter_rules()) + + if not rules: + click.echo("No routes were registered.") + return + + ignored_methods = set() if all_methods else {"HEAD", "OPTIONS"} + host_matching = current_app.url_map.host_matching + has_domain = any(rule.host if host_matching else rule.subdomain for rule in rules) + rows = [] + + for rule in rules: + row = [ + rule.endpoint, + ", ".join(sorted((rule.methods or set()) - ignored_methods)), + ] + + if has_domain: + row.append((rule.host if host_matching else rule.subdomain) or "") + + row.append(rule.rule) + rows.append(row) + + headers = ["Endpoint", "Methods"] + sorts = ["endpoint", "methods"] + + if has_domain: + headers.append("Host" if host_matching else "Subdomain") + sorts.append("domain") + + headers.append("Rule") + sorts.append("rule") + + try: + rows.sort(key=itemgetter(sorts.index(sort))) + except ValueError: + pass + + rows.insert(0, headers) + widths = [max(len(row[i]) for row in rows) for i in range(len(headers))] + rows.insert(1, ["-" * w for w in widths]) + template = " ".join(f"{{{i}:<{w}}}" for i, w in enumerate(widths)) + + for row in rows: + click.echo(template.format(*row)) + + +cli = FlaskGroup( + name="flask", + help="""\ +A general utility script for Flask applications. + +An application to load must be given with the '--app' option, +'FLASK_APP' environment variable, or with a 'wsgi.py' or 'app.py' file +in the current directory. +""", +) + + +def main() -> None: + cli.main() + + +if __name__ == "__main__": + main() diff --git a/venv/Lib/site-packages/flask/config.py b/venv/Lib/site-packages/flask/config.py new file mode 100644 index 00000000..7e3ba179 --- /dev/null +++ b/venv/Lib/site-packages/flask/config.py @@ -0,0 +1,370 @@ +from __future__ import annotations + +import errno +import json +import os +import types +import typing as t + +from werkzeug.utils import import_string + +if t.TYPE_CHECKING: + import typing_extensions as te + + 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[[t.Any], T] | None = None + ) -> None: + self.__name__ = name + self.get_converter = get_converter + + @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 # type: ignore[no-any-return] + + def __set__(self, obj: App, value: t.Any) -> None: + obj.config[self.__name__] = value + + +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. + + Either you can fill the config from a config file:: + + app.config.from_pyfile('yourconfig.cfg') + + Or alternatively you can define the configuration options in the + module that calls :meth:`from_object` or provide an import path to + a module that should be loaded. It is also possible to tell it to + use the same module and with that provide the configuration values + just before the call:: + + DEBUG = True + SECRET_KEY = 'development key' + app.config.from_object(__name__) + + In both cases (loading from any Python file or loading from modules), + only uppercase keys are added to the config. This makes it possible to use + lowercase values in the config file for temporary values that are not added + to the config or to define the config keys in the same file that implements + the application. + + Probably the most interesting way to load configurations is from an + environment variable pointing to a file:: + + app.config.from_envvar('YOURAPPLICATION_SETTINGS') + + In this case before launching the application you have to set this + environment variable to the file you want to use. On Linux and OS X + use the export statement:: + + export YOURAPPLICATION_SETTINGS='/path/to/config/file' + + On windows use `set` instead. + + :param root_path: path to which files are read relative from. When the + config object is created by the application, this is + the application's :attr:`~flask.Flask.root_path`. + :param defaults: an optional dictionary of default values + """ + + def __init__( + self, + root_path: str | os.PathLike[str], + defaults: dict[str, t.Any] | None = None, + ) -> None: + super().__init__(defaults or {}) + self.root_path = root_path + + def from_envvar(self, variable_name: str, silent: bool = False) -> bool: + """Loads a configuration from an environment variable pointing to + a configuration file. This is basically just a shortcut with nicer + error messages for this line of code:: + + app.config.from_pyfile(os.environ['YOURAPPLICATION_SETTINGS']) + + :param variable_name: name of the environment variable + :param silent: set to ``True`` if you want silent failure for missing + files. + :return: ``True`` if the file was loaded successfully. + """ + rv = os.environ.get(variable_name) + if not rv: + if silent: + return False + raise RuntimeError( + f"The environment variable {variable_name!r} is not set" + " and as such configuration could not be loaded. Set" + " this variable and make it point to a configuration" + " file" + ) + return self.from_pyfile(rv, silent=silent) + + def from_prefixed_env( + self, prefix: str = "FLASK", *, loads: t.Callable[[str], t.Any] = json.loads + ) -> bool: + """Load any environment variables that start with ``FLASK_``, + dropping the prefix from the env key for the config key. Values + are passed through a loading function to attempt to convert them + to more specific types than strings. + + Keys are loaded in :func:`sorted` order. + + The default loading function attempts to parse values as any + valid JSON type, including dicts and lists. + + Specific items in nested dicts can be set by separating the + keys with double underscores (``__``). If an intermediate key + doesn't exist, it will be initialized to an empty dict. + + :param prefix: Load env vars that start with this prefix, + separated with an underscore (``_``). + :param loads: Pass each string value to this function and use + the returned value as the config value. If any error is + raised it is ignored and the value remains a string. The + default is :func:`json.loads`. + + .. versionadded:: 2.1 + """ + prefix = f"{prefix}_" + len_prefix = len(prefix) + + for key in sorted(os.environ): + if not key.startswith(prefix): + continue + + value = os.environ[key] + + try: + value = loads(value) + except Exception: + # Keep the value as a string if loading failed. + pass + + # Change to key.removeprefix(prefix) on Python >= 3.9. + key = key[len_prefix:] + + if "__" not in key: + # A non-nested key, set directly. + self[key] = value + continue + + # Traverse nested dictionaries with keys separated by "__". + current = self + *parts, tail = key.split("__") + + for part in parts: + # If an intermediate dict does not exist, create it. + if part not in current: + current[part] = {} + + current = current[part] + + current[tail] = value + + return True + + 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. + + :param filename: the filename of the config. This can either be an + absolute filename or a filename relative to the + root path. + :param silent: set to ``True`` if you want silent failure for missing + files. + :return: ``True`` if the file was loaded successfully. + + .. versionadded:: 0.7 + `silent` parameter. + """ + filename = os.path.join(self.root_path, filename) + d = types.ModuleType("config") + d.__file__ = filename + try: + with open(filename, mode="rb") as config_file: + exec(compile(config_file.read(), filename, "exec"), d.__dict__) + except OSError as e: + if silent and e.errno in (errno.ENOENT, errno.EISDIR, errno.ENOTDIR): + return False + e.strerror = f"Unable to load configuration file ({e.strerror})" + raise + self.from_object(d) + return True + + def from_object(self, obj: object | str) -> None: + """Updates the values from the given object. An object can be of one + of the following two types: + + - a string: in this case the object with that name will be imported + - an actual object reference: that object is used directly + + Objects are usually either modules or classes. :meth:`from_object` + loads only the uppercase attributes of the module/class. A ``dict`` + object will not work with :meth:`from_object` because the keys of a + ``dict`` are not attributes of the ``dict`` class. + + Example of module-based configuration:: + + app.config.from_object('yourapplication.default_config') + from yourapplication import default_config + app.config.from_object(default_config) + + Nothing is done to the object before loading. If the object is a + class and has ``@property`` attributes, it needs to be + instantiated before being passed to this method. + + You should not use this function to load the actual configuration but + rather configuration defaults. The actual config should be loaded + with :meth:`from_pyfile` and ideally from a location not within the + package because the package might be installed system wide. + + See :ref:`config-dev-prod` for an example of class-based configuration + using :meth:`from_object`. + + :param obj: an import name or object + """ + if isinstance(obj, str): + obj = import_string(obj) + for key in dir(obj): + if key.isupper(): + self[key] = getattr(obj, key) + + def from_file( + self, + filename: str | os.PathLike[str], + load: t.Callable[[t.IO[t.Any]], t.Mapping[str, t.Any]], + silent: bool = False, + text: bool = True, + ) -> bool: + """Update the values in the config from a file that is loaded + using the ``load`` parameter. The loaded data is passed to the + :meth:`from_mapping` method. + + .. code-block:: python + + import json + app.config.from_file("config.json", load=json.load) + + import tomllib + app.config.from_file("config.toml", load=tomllib.load, text=False) + + :param filename: The path to the data file. This can be an + absolute path or relative to the config root path. + :param load: A callable that takes a file handle and returns a + mapping of loaded data from the file. + :type load: ``Callable[[Reader], Mapping]`` where ``Reader`` + implements a ``read`` method. + :param silent: Ignore the file if it doesn't exist. + :param text: Open the file in text or binary mode. + :return: ``True`` if the file was loaded successfully. + + .. versionchanged:: 2.3 + The ``text`` parameter was added. + + .. versionadded:: 2.0 + """ + filename = os.path.join(self.root_path, filename) + + try: + with open(filename, "r" if text else "rb") as f: + obj = load(f) + except OSError as e: + if silent and e.errno in (errno.ENOENT, errno.EISDIR): + return False + + e.strerror = f"Unable to load configuration file ({e.strerror})" + raise + + return self.from_mapping(obj) + + def from_mapping( + self, mapping: t.Mapping[str, t.Any] | None = None, **kwargs: t.Any + ) -> bool: + """Updates the config like :meth:`update` ignoring items with + non-upper keys. + + :return: Always returns ``True``. + + .. versionadded:: 0.11 + """ + mappings: dict[str, t.Any] = {} + if mapping is not None: + mappings.update(mapping) + mappings.update(kwargs) + for key, value in mappings.items(): + if key.isupper(): + self[key] = value + return True + + def get_namespace( + self, namespace: str, lowercase: bool = True, trim_namespace: bool = True + ) -> dict[str, t.Any]: + """Returns a dictionary containing a subset of configuration options + that match the specified namespace/prefix. Example usage:: + + app.config['IMAGE_STORE_TYPE'] = 'fs' + app.config['IMAGE_STORE_PATH'] = '/var/app/images' + app.config['IMAGE_STORE_BASE_URL'] = 'http://img.website.com' + image_store_config = app.config.get_namespace('IMAGE_STORE_') + + The resulting dictionary `image_store_config` would look like:: + + { + 'type': 'fs', + 'path': '/var/app/images', + 'base_url': 'http://img.website.com' + } + + This is often useful when configuration options map directly to + keyword arguments in functions or class constructors. + + :param namespace: a configuration namespace + :param lowercase: a flag indicating if the keys of the resulting + dictionary should be lowercase + :param trim_namespace: a flag indicating if the keys of the resulting + dictionary should not include the namespace + + .. versionadded:: 0.11 + """ + rv = {} + for k, v in self.items(): + if not k.startswith(namespace): + continue + if trim_namespace: + key = k[len(namespace) :] + else: + key = k + if lowercase: + key = key.lower() + rv[key] = v + return rv + + def __repr__(self) -> str: + return f"<{type(self).__name__} {dict.__repr__(self)}>" diff --git a/venv/Lib/site-packages/flask/ctx.py b/venv/Lib/site-packages/flask/ctx.py new file mode 100644 index 00000000..9b164d39 --- /dev/null +++ b/venv/Lib/site-packages/flask/ctx.py @@ -0,0 +1,449 @@ +from __future__ import annotations + +import contextvars +import sys +import typing as t +from functools import update_wrapper +from types import TracebackType + +from werkzeug.exceptions import HTTPException + +from . import typing as ft +from .globals import _cv_app +from .globals import _cv_request +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 + + +# a singleton sentinel value for parameter defaults +_sentinel = object() + + +class _AppCtxGlobals: + """A plain object. Used as a namespace for storing data during an + application context. + + Creating an app context automatically creates this object, which is + made available as the :data:`g` proxy. + + .. describe:: 'key' in g + + Check whether an attribute is present. + + .. versionadded:: 0.10 + + .. describe:: iter(g) + + Return an iterator over the attribute names. + + .. versionadded:: 0.10 + """ + + # Define attr methods to let mypy know this is a namespace object + # that has arbitrary attributes. + + def __getattr__(self, name: str) -> t.Any: + try: + return self.__dict__[name] + except KeyError: + raise AttributeError(name) from None + + def __setattr__(self, name: str, value: t.Any) -> None: + self.__dict__[name] = value + + def __delattr__(self, name: str) -> None: + try: + del self.__dict__[name] + except KeyError: + raise AttributeError(name) from None + + def get(self, name: str, default: t.Any | None = None) -> t.Any: + """Get an attribute by name, or a default value. Like + :meth:`dict.get`. + + :param name: Name of attribute to get. + :param default: Value to return if the attribute is not present. + + .. versionadded:: 0.10 + """ + return self.__dict__.get(name, default) + + def pop(self, name: str, default: t.Any = _sentinel) -> t.Any: + """Get and remove an attribute by name. Like :meth:`dict.pop`. + + :param name: Name of attribute to pop. + :param default: Value to return if the attribute is not present, + instead of raising a ``KeyError``. + + .. versionadded:: 0.11 + """ + if default is _sentinel: + return self.__dict__.pop(name) + else: + return self.__dict__.pop(name, default) + + def setdefault(self, name: str, default: t.Any = None) -> t.Any: + """Get the value of an attribute if it is present, otherwise + set and return a default value. Like :meth:`dict.setdefault`. + + :param name: Name of attribute to get. + :param default: Value to set and return if the attribute is not + present. + + .. versionadded:: 0.11 + """ + return self.__dict__.setdefault(name, default) + + def __contains__(self, item: str) -> bool: + return item in self.__dict__ + + def __iter__(self) -> t.Iterator[str]: + return iter(self.__dict__) + + def __repr__(self) -> str: + ctx = _cv_app.get(None) + if ctx is not None: + return f"" + return object.__repr__(self) + + +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. + + Example:: + + @app.route('/') + def index(): + @after_this_request + def add_header(response): + response.headers['X-Foo'] = 'Parachute' + return response + return 'Hello World!' + + This is more useful if a function other than the view function wants to + modify a response. For instance think of a decorator that wants to add + some headers without converting the return value into a response object. + + .. versionadded:: 0.9 + """ + ctx = _cv_request.get(None) + + if ctx is None: + raise RuntimeError( + "'after_this_request' can only be used when a request" + " context is active, such as in a view function." + ) + + ctx._after_request_functions.append(f) + return f + + +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 + then pushed when the function is called. The current session is also + included in the copied request context. + + Example:: + + import gevent + from flask import copy_current_request_context + + @app.route('/') + def index(): + @copy_current_request_context + def do_some_work(): + # do some work here, it can access flask.request or + # flask.session like you would otherwise in the view function. + ... + gevent.spawn(do_some_work) + return 'Regular response' + + .. versionadded:: 0.10 + """ + ctx = _cv_request.get(None) + + if ctx is None: + raise RuntimeError( + "'copy_current_request_context' can only be used when a" + " request context is active, such as in a view function." + ) + + ctx = ctx.copy() + + 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) # type: ignore[return-value] + + +def has_request_context() -> bool: + """If you have code that wants to test if a request context is there or + not this function can be used. For instance, you may want to take advantage + of request information if the request object is available, but fail + silently if it is unavailable. + + :: + + class User(db.Model): + + def __init__(self, username, remote_addr=None): + self.username = username + if remote_addr is None and has_request_context(): + remote_addr = request.remote_addr + self.remote_addr = remote_addr + + Alternatively you can also just test any of the context bound objects + (such as :class:`request` or :class:`g`) for truthness:: + + class User(db.Model): + + def __init__(self, username, remote_addr=None): + self.username = username + if remote_addr is None and request: + remote_addr = request.remote_addr + self.remote_addr = remote_addr + + .. versionadded:: 0.7 + """ + return _cv_request.get(None) is not None + + +def has_app_context() -> bool: + """Works like :func:`has_request_context` but for the application + context. You can also just do a boolean check on the + :data:`current_app` object instead. + + .. versionadded:: 0.9 + """ + return _cv_app.get(None) is not None + + +class AppContext: + """The app context contains application-specific information. An app + context is created and pushed at the beginning of each request if + one is not already active. An app context is also pushed when + running CLI commands. + """ + + def __init__(self, app: Flask) -> None: + 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[AppContext]] = [] + + def push(self) -> None: + """Binds the app context to the current context.""" + self._cv_tokens.append(_cv_app.set(self)) + appcontext_pushed.send(self.app, _async_wrapper=self.app.ensure_sync) + + def pop(self, exc: BaseException | None = _sentinel) -> None: # type: ignore + """Pops the app context.""" + try: + if len(self._cv_tokens) == 1: + if exc is _sentinel: + exc = sys.exc_info()[1] + self.app.do_teardown_appcontext(exc) + finally: + ctx = _cv_app.get() + _cv_app.reset(self._cv_tokens.pop()) + + if ctx is not self: + raise AssertionError( + f"Popped wrong app context. ({ctx!r} instead of {self!r})" + ) + + appcontext_popped.send(self.app, _async_wrapper=self.app.ensure_sync) + + def __enter__(self) -> AppContext: + self.push() + return self + + def __exit__( + self, + exc_type: type | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> None: + self.pop(exc_value) + + +class RequestContext: + """The request context contains per-request information. The Flask + app creates and pushes it at the beginning of the request, then pops + it at the end of the request. It will create the URL adapter and + request object for the WSGI environment provided. + + Do not attempt to use this class directly, instead use + :meth:`~flask.Flask.test_request_context` and + :meth:`~flask.Flask.request_context` to create this object. + + When the request context is popped, it will evaluate all the + functions registered on the application for teardown execution + (:meth:`~flask.Flask.teardown_request`). + + The request context is automatically popped at the end of the + request. When using the interactive debugger, the context will be + restored so ``request`` is still accessible. Similarly, the test + client can preserve the context after the request ends. However, + teardown functions may already have closed some resources such as + database connections. + """ + + def __init__( + self, + app: Flask, + environ: WSGIEnvironment, + request: Request | None = None, + session: SessionMixin | None = None, + ) -> None: + self.app = app + if request is None: + request = app.request_class(environ) + request.json_module = app.json + self.request: Request = request + self.url_adapter = None + try: + self.url_adapter = app.create_url_adapter(self.request) + except HTTPException as e: + self.request.routing_exception = e + self.flashes: list[tuple[str, str]] | None = None + self.session: SessionMixin | None = session + # 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[t.Any]] = [] + + 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. + This can be used to move a request context to a different greenlet. + Because the actual request object is the same this cannot be used to + move a request context to a different thread unless access to the + request object is locked. + + .. versionadded:: 0.10 + + .. versionchanged:: 1.1 + The current session object is used instead of reloading the original + data. This prevents `flask.session` pointing to an out-of-date object. + """ + return self.__class__( + self.app, + environ=self.request.environ, + request=self.request, + session=self.session, + ) + + def match_request(self) -> None: + """Can be overridden by a subclass to hook into the matching + of the request. + """ + try: + result = self.url_adapter.match(return_rule=True) # type: ignore + self.request.url_rule, self.request.view_args = result # type: ignore + except HTTPException as e: + self.request.routing_exception = e + + def push(self) -> None: + # Before we push the request context we have to ensure that there + # is an application context. + app_ctx = _cv_app.get(None) + + if app_ctx is None or app_ctx.app is not self.app: + app_ctx = self.app.app_context() + app_ctx.push() + else: + app_ctx = None + + self._cv_tokens.append((_cv_request.set(self), app_ctx)) + + # Open the session at the moment that the request context is available. + # This allows a custom open_session method to use the request context. + # Only open a new session if this is the first time the request was + # pushed, otherwise stream_with_context loses the session. + if self.session is None: + session_interface = self.app.session_interface + self.session = session_interface.open_session(self.app, self.request) + + if self.session is None: + self.session = session_interface.make_null_session(self.app) + + # Match the request URL after loading the session, so that the + # session is available in custom URL converters. + if self.url_adapter is not None: + self.match_request() + + def pop(self, exc: BaseException | None = _sentinel) -> None: # type: ignore + """Pops the request context and unbinds it by doing that. This will + also trigger the execution of functions registered by the + :meth:`~flask.Flask.teardown_request` decorator. + + .. versionchanged:: 0.9 + Added the `exc` argument. + """ + clear_request = len(self._cv_tokens) == 1 + + try: + if clear_request: + if exc is _sentinel: + exc = sys.exc_info()[1] + self.app.do_teardown_request(exc) + + request_close = getattr(self.request, "close", None) + if request_close is not None: + request_close() + finally: + ctx = _cv_request.get() + token, app_ctx = self._cv_tokens.pop() + _cv_request.reset(token) + + # get rid of circular dependencies at the end of the request + # so that we don't require the GC to be active. + if clear_request: + ctx.request.environ["werkzeug.request"] = None + + if app_ctx is not None: + app_ctx.pop(exc) + + if ctx is not self: + raise AssertionError( + f"Popped wrong request context. ({ctx!r} instead of {self!r})" + ) + + def __enter__(self) -> RequestContext: + self.push() + return self + + def __exit__( + self, + exc_type: type | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> None: + self.pop(exc_value) + + def __repr__(self) -> str: + return ( + f"<{type(self).__name__} {self.request.url!r}" + f" [{self.request.method}] of {self.app.name}>" + ) diff --git a/venv/Lib/site-packages/flask/debughelpers.py b/venv/Lib/site-packages/flask/debughelpers.py new file mode 100644 index 00000000..2c8c4c48 --- /dev/null +++ b/venv/Lib/site-packages/flask/debughelpers.py @@ -0,0 +1,178 @@ +from __future__ import annotations + +import typing as t + +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): + """Raised in places where we want some better error reporting for + unexpected unicode or binary data. + """ + + +class DebugFilesKeyError(KeyError, AssertionError): + """Raised from request.files during debugging. The idea is that it can + provide a better error message than just a generic KeyError/BadRequest. + """ + + 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" + " dictionary but it does not exist. The mimetype for the" + f" request is {request.mimetype!r} instead of" + " 'multipart/form-data' which means that no file contents" + " were transmitted. To fix this error you should provide" + ' enctype="multipart/form-data" in your form.' + ] + if form_matches: + names = ", ".join(repr(x) for x in form_matches) + buf.append( + "\n\nThe browser instead transmitted some file names. " + f"This was submitted: {names}" + ) + self.msg = "".join(buf) + + def __str__(self) -> str: + return self.msg + + +class FormDataRoutingRedirect(AssertionError): + """This exception is raised in debug mode if a routing redirect + would cause the browser to drop the method or body. This happens + when method is not GET, HEAD or OPTIONS and the status code is not + 307 or 308. + """ + + 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}'." + ] + + if f"{request.base_url}/" == exc.new_url.partition("?")[0]: + buf.append( + " The URL was defined with a trailing slash. Flask" + " will redirect to the URL with a trailing slash if it" + " was accessed without one." + ) + + buf.append( + " Send requests to the canonical URL, or use 307 or 308 for" + " routing redirects. Otherwise, browsers will drop form" + " data.\n\n" + "This exception is only raised in debug mode." + ) + super().__init__("".join(buf)) + + +def attach_enctype_error_multidict(request: Request) -> None: + """Patch ``request.files.__getitem__`` to raise a descriptive error + about ``enctype=multipart/form-data``. + + :param request: The request to patch. + :meta private: + """ + oldcls = request.files.__class__ + + class newcls(oldcls): # type: ignore[valid-type, misc] + def __getitem__(self, key: str) -> t.Any: + try: + return super().__getitem__(key) + except KeyError as e: + if key not in request.form: + raise + + raise DebugFilesKeyError(request, key).with_traceback( + e.__traceback__ + ) from None + + newcls.__name__ = oldcls.__name__ + newcls.__module__ = oldcls.__module__ + request.files.__class__ = newcls + + +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("_"): + continue + if isinstance(value, (tuple, list)): + if not all(isinstance(x, str) for x in value): + continue + yield f"{key}:" + for item in value: + yield f" - {item}" + continue + elif not isinstance(value, (str, int, float, bool)): + continue + yield f"{key}: {value!r}" + + +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 + blueprint = None + if request_ctx and request_ctx.request.blueprint is not None: + blueprint = request_ctx.request.blueprint + + for idx, (loader, srcobj, triple) in enumerate(attempts): + 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})" + else: + src_info = repr(srcobj) + + info.append(f"{idx + 1:5}: trying loader of {src_info}") + + for line in _dump_loader_info(loader): + info.append(f" {line}") + + if triple is None: + detail = "no match" + else: + detail = f"found ({triple[1] or ''!r})" + total_found += 1 + info.append(f" -> {detail}") + + seems_fishy = False + if total_found == 0: + info.append("Error: the template could not be found.") + seems_fishy = True + elif total_found > 1: + info.append("Warning: multiple loaders returned a match for the template.") + seems_fishy = True + + if blueprint is not None and seems_fishy: + info.append( + " The template was looked up from an endpoint that belongs" + f" to the blueprint {blueprint!r}." + ) + info.append(" Maybe you did not place a template in the right folder?") + info.append(" See https://flask.palletsprojects.com/blueprints/#templates") + + app.logger.info("\n".join(info)) diff --git a/venv/Lib/site-packages/flask/globals.py b/venv/Lib/site-packages/flask/globals.py new file mode 100644 index 00000000..e2c410cc --- /dev/null +++ b/venv/Lib/site-packages/flask/globals.py @@ -0,0 +1,51 @@ +from __future__ import annotations + +import typing as t +from contextvars import ContextVar + +from werkzeug.local import LocalProxy + +if t.TYPE_CHECKING: # pragma: no cover + from .app import Flask + from .ctx import _AppCtxGlobals + from .ctx import AppContext + from .ctx import RequestContext + from .sessions import SessionMixin + from .wrappers import Request + + +_no_app_msg = """\ +Working outside of application context. + +This typically means that you attempted to use functionality that needed +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: AppContext = LocalProxy( # type: ignore[assignment] + _cv_app, unbound_message=_no_app_msg +) +current_app: Flask = LocalProxy( # type: ignore[assignment] + _cv_app, "app", unbound_message=_no_app_msg +) +g: _AppCtxGlobals = LocalProxy( # type: ignore[assignment] + _cv_app, "g", unbound_message=_no_app_msg +) + +_no_req_msg = """\ +Working outside of request context. + +This typically means that you attempted to use functionality that needed +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: RequestContext = LocalProxy( # type: ignore[assignment] + _cv_request, unbound_message=_no_req_msg +) +request: Request = LocalProxy( # type: ignore[assignment] + _cv_request, "request", unbound_message=_no_req_msg +) +session: SessionMixin = LocalProxy( # type: ignore[assignment] + _cv_request, "session", unbound_message=_no_req_msg +) diff --git a/venv/Lib/site-packages/flask/helpers.py b/venv/Lib/site-packages/flask/helpers.py new file mode 100644 index 00000000..359a842a --- /dev/null +++ b/venv/Lib/site-packages/flask/helpers.py @@ -0,0 +1,621 @@ +from __future__ import annotations + +import importlib.util +import os +import sys +import typing as t +from datetime import datetime +from functools import lru_cache +from functools import update_wrapper + +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 +from .globals import request +from .globals import request_ctx +from .globals import session +from .signals import message_flashed + +if t.TYPE_CHECKING: # pragma: no cover + from .wrappers import Response + + +def get_debug_flag() -> bool: + """Get whether debug mode should be enabled for the app, indicated by the + :envvar:`FLASK_DEBUG` environment variable. The default is ``False``. + """ + val = os.environ.get("FLASK_DEBUG") + return bool(val and val.lower() not in {"0", "false", "no"}) + + +def get_load_dotenv(default: bool = True) -> bool: + """Get whether the user has disabled loading default dotenv files by + setting :envvar:`FLASK_SKIP_DOTENV`. The default is ``True``, load + the files. + + :param default: What to return if the env var isn't set. + """ + val = os.environ.get("FLASK_SKIP_DOTENV") + + if not val: + return default + + return val.lower() in ("0", "false", "no") + + +def stream_with_context( + 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 + memory leaks with badly written WSGI middlewares. The downside is that if + you are using streamed responses, the generator cannot access request bound + information any more. + + This function however can help you keep the context around for longer:: + + from flask import stream_with_context, request, Response + + @app.route('/stream') + def streamed_response(): + @stream_with_context + def generate(): + yield 'Hello ' + yield request.args['name'] + yield '!' + return Response(generate()) + + Alternatively it can also be used around a specific generator:: + + from flask import stream_with_context, request, Response + + @app.route('/stream') + def streamed_response(): + def generate(): + yield 'Hello ' + yield request.args['name'] + yield '!' + return Response(stream_with_context(generate())) + + .. versionadded:: 0.9 + """ + try: + 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[operator] + return stream_with_context(gen) + + return update_wrapper(decorator, generator_or_function) # type: ignore[arg-type] + + def generator() -> t.Iterator[t.AnyStr | None]: + ctx = _cv_request.get(None) + if ctx is None: + raise RuntimeError( + "'stream_with_context' can only be used when a request" + " context is active, such as in a view function." + ) + with ctx: + # Dummy sentinel. Has to be inside the context block or we're + # not actually keeping the context around. + yield None + + # The try/finally is here so that if someone passes a WSGI level + # iterator in we're still running the cleanup logic. Generators + # don't need that because they are closed on their destruction + # automatically. + try: + yield from gen + finally: + if hasattr(gen, "close"): + gen.close() + + # The trick is to start the generator. Then the code execution runs until + # the first dummy None is yielded at which point the context was already + # pushed. This item is discarded. Then when the iteration continues the + # real generator is executed. + wrapped_g = generator() + next(wrapped_g) + return wrapped_g # type: ignore[return-value] + + +def make_response(*args: t.Any) -> Response: + """Sometimes it is necessary to set additional headers in a view. Because + views do not have to return response objects but can return a value that + is converted into a response object by Flask itself, it becomes tricky to + add headers to it. This function can be called instead of using a return + and you will get a response object which you can use to attach headers. + + If view looked like this and you want to add a new header:: + + def index(): + return render_template('index.html', foo=42) + + You can now do something like this:: + + def index(): + response = make_response(render_template('index.html', foo=42)) + response.headers['X-Parachutes'] = 'parachutes are cool' + return response + + This function accepts the very same arguments you can return from a + view function. This for example creates a response with a 404 error + code:: + + response = make_response(render_template('not_found.html'), 404) + + The other use case of this function is to force the return value of a + view function into a response which is helpful with view + decorators:: + + response = make_response(view_function()) + response.headers['X-Parachutes'] = 'parachutes are cool' + + Internally this function does the following things: + + - if no arguments are passed, it creates a new response argument + - if one argument is passed, :meth:`flask.Flask.make_response` + is invoked with it. + - if more than one argument is passed, the arguments are passed + to the :meth:`flask.Flask.make_response` function as tuple. + + .. versionadded:: 0.6 + """ + if not args: + return current_app.response_class() + if len(args) == 1: + args = args[0] + return current_app.make_response(args) + + +def url_for( + endpoint: str, + *, + _anchor: str | None = None, + _method: str | None = None, + _scheme: str | None = None, + _external: bool | None = None, + **values: t.Any, +) -> str: + """Generate a URL to the given endpoint with the given values. + + This requires an active request or application context, and calls + :meth:`current_app.url_for() `. See that method + for full documentation. + + :param endpoint: The endpoint name associated with the URL to + generate. If this starts with a ``.``, the current blueprint + name (if any) will be used. + :param _anchor: If given, append this as ``#anchor`` to the URL. + :param _method: If given, generate the URL associated with this + method for the endpoint. + :param _scheme: If given, the URL will have this scheme if it is + external. + :param _external: If given, prefer the URL to be internal (False) or + require it to be external (True). External URLs include the + scheme and domain. When not in an active request, URLs are + external by default. + :param values: Values to use for the variable parts of the URL rule. + Unknown keys are appended as query string arguments, like + ``?a=b&c=d``. + + .. versionchanged:: 2.2 + Calls ``current_app.url_for``, allowing an app to override the + behavior. + + .. versionchanged:: 0.10 + The ``_scheme`` parameter was added. + + .. versionchanged:: 0.9 + The ``_anchor`` and ``_method`` parameters were added. + + .. versionchanged:: 0.9 + Calls ``app.handle_url_build_error`` on build errors. + """ + return current_app.url_for( + endpoint, + _anchor=_anchor, + _method=_method, + _scheme=_scheme, + _external=_external, + **values, + ) + + +def redirect( + location: str, code: int = 302, Response: type[BaseResponse] | None = None +) -> BaseResponse: + """Create a redirect response object. + + If :data:`~flask.current_app` is available, it will use its + :meth:`~flask.Flask.redirect` method, otherwise it will use + :func:`werkzeug.utils.redirect`. + + :param location: The URL to redirect to. + :param code: The status code for the redirect. + :param Response: The response class to use. Not used when + ``current_app`` is active, which uses ``app.response_class``. + + .. versionadded:: 2.2 + Calls ``current_app.redirect`` if available instead of always + using Werkzeug's default ``redirect``. + """ + if current_app: + return current_app.redirect(location, code=code) + + return _wz_redirect(location, code=code, Response=Response) + + +def abort(code: int | BaseResponse, *args: t.Any, **kwargs: t.Any) -> t.NoReturn: + """Raise an :exc:`~werkzeug.exceptions.HTTPException` for the given + status code. + + If :data:`~flask.current_app` is available, it will call its + :attr:`~flask.Flask.aborter` object, otherwise it will use + :func:`werkzeug.exceptions.abort`. + + :param code: The status code for the exception, which must be + registered in ``app.aborter``. + :param args: Passed to the exception. + :param kwargs: Passed to the exception. + + .. versionadded:: 2.2 + Calls ``current_app.aborter`` if available instead of always + using Werkzeug's default ``abort``. + """ + if current_app: + current_app.aborter(code, *args, **kwargs) + + _wz_abort(code, *args, **kwargs) + + +def get_template_attribute(template_name: str, attribute: str) -> t.Any: + """Loads a macro (or variable) a template exports. This can be used to + invoke a macro from within Python code. If you for example have a + template named :file:`_cider.html` with the following contents: + + .. sourcecode:: html+jinja + + {% macro hello(name) %}Hello {{ name }}!{% endmacro %} + + You can access this from Python code like this:: + + hello = get_template_attribute('_cider.html', 'hello') + return hello('World') + + .. versionadded:: 0.2 + + :param template_name: the name of the template + :param attribute: the name of the variable of macro to access + """ + return getattr(current_app.jinja_env.get_template(template_name).module, attribute) + + +def flash(message: str, category: str = "message") -> None: + """Flashes a message to the next request. In order to remove the + flashed message from the session and to display it to the user, + the template has to call :func:`get_flashed_messages`. + + .. versionchanged:: 0.3 + `category` parameter added. + + :param message: the message to be flashed. + :param category: the category for the message. The following values + are recommended: ``'message'`` for any kind of message, + ``'error'`` for errors, ``'info'`` for information + messages and ``'warning'`` for warnings. However any + kind of string can be used as category. + """ + # Original implementation: + # + # session.setdefault('_flashes', []).append((category, message)) + # + # This assumed that changes made to mutable structures in the session are + # always in sync with the session object, which is not true for session + # implementations that use external storage for keeping their keys/values. + flashes = session.get("_flashes", []) + flashes.append((category, message)) + session["_flashes"] = flashes + app = current_app._get_current_object() # type: ignore + message_flashed.send( + app, + _async_wrapper=app.ensure_sync, + message=message, + category=category, + ) + + +def get_flashed_messages( + with_categories: bool = False, category_filter: t.Iterable[str] = () +) -> list[str] | list[tuple[str, str]]: + """Pulls all flashed messages from the session and returns them. + Further calls in the same request to the function will return + the same messages. By default just the messages are returned, + but when `with_categories` is set to ``True``, the return value will + be a list of tuples in the form ``(category, message)`` instead. + + Filter the flashed messages to one or more categories by providing those + categories in `category_filter`. This allows rendering categories in + separate html blocks. The `with_categories` and `category_filter` + arguments are distinct: + + * `with_categories` controls whether categories are returned with message + text (``True`` gives a tuple, where ``False`` gives just the message text). + * `category_filter` filters the messages down to only those matching the + provided categories. + + See :doc:`/patterns/flashing` for examples. + + .. versionchanged:: 0.3 + `with_categories` parameter added. + + .. versionchanged:: 0.9 + `category_filter` parameter added. + + :param with_categories: set to ``True`` to also receive categories. + :param category_filter: filter of categories to limit return values. Only + categories in the list will be returned. + """ + flashes = request_ctx.flashes + if flashes is None: + flashes = session.pop("_flashes") if "_flashes" in session else [] + request_ctx.flashes = flashes + if category_filter: + flashes = list(filter(lambda f: f[0] in category_filter, flashes)) + if not with_categories: + return [x[1] for x in flashes] + return flashes + + +def _prepare_send_file_kwargs(**kwargs: t.Any) -> dict[str, t.Any]: + if kwargs.get("max_age") is None: + kwargs["max_age"] = current_app.get_send_file_max_age + + kwargs.update( + environ=request.environ, + use_x_sendfile=current_app.config["USE_X_SENDFILE"], + response_class=current_app.response_class, + _root_path=current_app.root_path, # type: ignore + ) + return kwargs + + +def send_file( + path_or_file: os.PathLike[t.AnyStr] | str | t.BinaryIO, + mimetype: str | None = None, + as_attachment: bool = False, + download_name: str | None = None, + conditional: bool = True, + etag: bool | str = True, + last_modified: datetime | int | float | None = None, + max_age: None | (int | t.Callable[[str | None], int | None]) = None, +) -> Response: + """Send the contents of a file to the client. + + The first argument can be a file path or a file-like object. Paths + are preferred in most cases because Werkzeug can manage the file and + get extra information from the path. Passing a file-like object + requires that the file is opened in binary mode, and is mostly + useful when building a file in memory with :class:`io.BytesIO`. + + Never pass file paths provided by a user. The path is assumed to be + trusted, so a user could craft a path to access a file you didn't + intend. Use :func:`send_from_directory` to safely serve + user-requested paths from within a directory. + + If the WSGI server sets a ``file_wrapper`` in ``environ``, it is + used, otherwise Werkzeug's built-in wrapper is used. Alternatively, + if the HTTP server supports ``X-Sendfile``, configuring Flask with + ``USE_X_SENDFILE = True`` will tell the server to send the given + path, which is much more efficient than reading it in Python. + + :param path_or_file: The path to the file to send, relative to the + current working directory if a relative path is given. + Alternatively, a file-like object opened in binary mode. Make + sure the file pointer is seeked to the start of the data. + :param mimetype: The MIME type to send for the file. If not + provided, it will try to detect it from the file name. + :param as_attachment: Indicate to a browser that it should offer to + save the file instead of displaying it. + :param download_name: The default name browsers will use when saving + the file. Defaults to the passed file name. + :param conditional: Enable conditional and range responses based on + request headers. Requires passing a file path and ``environ``. + :param etag: Calculate an ETag for the file, which requires passing + a file path. Can also be a string to use instead. + :param last_modified: The last modified time to send for the file, + in seconds. If not provided, it will try to detect it from the + file path. + :param max_age: How long the client should cache the file, in + seconds. If set, ``Cache-Control`` will be ``public``, otherwise + it will be ``no-cache`` to prefer conditional caching. + + .. versionchanged:: 2.0 + ``download_name`` replaces the ``attachment_filename`` + parameter. If ``as_attachment=False``, it is passed with + ``Content-Disposition: inline`` instead. + + .. versionchanged:: 2.0 + ``max_age`` replaces the ``cache_timeout`` parameter. + ``conditional`` is enabled and ``max_age`` is not set by + default. + + .. versionchanged:: 2.0 + ``etag`` replaces the ``add_etags`` parameter. It can be a + string to use instead of generating one. + + .. versionchanged:: 2.0 + Passing a file-like object that inherits from + :class:`~io.TextIOBase` will raise a :exc:`ValueError` rather + than sending an empty file. + + .. versionadded:: 2.0 + Moved the implementation to Werkzeug. This is now a wrapper to + pass some Flask-specific arguments. + + .. versionchanged:: 1.1 + ``filename`` may be a :class:`~os.PathLike` object. + + .. versionchanged:: 1.1 + Passing a :class:`~io.BytesIO` object supports range requests. + + .. versionchanged:: 1.0.3 + Filenames are encoded with ASCII instead of Latin-1 for broader + compatibility with WSGI servers. + + .. versionchanged:: 1.0 + UTF-8 filenames as specified in :rfc:`2231` are supported. + + .. versionchanged:: 0.12 + The filename is no longer automatically inferred from file + objects. If you want to use automatic MIME and etag support, + pass a filename via ``filename_or_fp`` or + ``attachment_filename``. + + .. versionchanged:: 0.12 + ``attachment_filename`` is preferred over ``filename`` for MIME + detection. + + .. versionchanged:: 0.9 + ``cache_timeout`` defaults to + :meth:`Flask.get_send_file_max_age`. + + .. versionchanged:: 0.7 + MIME guessing and etag support for file-like objects was + removed because it was unreliable. Pass a filename if you are + able to, otherwise attach an etag yourself. + + .. versionchanged:: 0.5 + The ``add_etags``, ``cache_timeout`` and ``conditional`` + parameters were added. The default behavior is to add etags. + + .. versionadded:: 0.2 + """ + return werkzeug.utils.send_file( # type: ignore[return-value] + **_prepare_send_file_kwargs( + path_or_file=path_or_file, + environ=request.environ, + mimetype=mimetype, + as_attachment=as_attachment, + download_name=download_name, + conditional=conditional, + etag=etag, + last_modified=last_modified, + max_age=max_age, + ) + ) + + +def send_from_directory( + 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`. + + .. code-block:: python + + @app.route("/uploads/") + def download_file(name): + return send_from_directory( + app.config['UPLOAD_FOLDER'], name, as_attachment=True + ) + + This is a secure way to serve files from a folder, such as static + files or uploads. Uses :func:`~werkzeug.security.safe_join` to + ensure the path coming from the client is not maliciously crafted to + point outside the specified directory. + + If the final path does not point to an existing regular file, + raises a 404 :exc:`~werkzeug.exceptions.NotFound` error. + + :param directory: The directory that ``path`` must be located under, + relative to the current application's root path. + :param path: The path to the file to send, relative to + ``directory``. + :param kwargs: Arguments to pass to :func:`send_file`. + + .. versionchanged:: 2.0 + ``path`` replaces the ``filename`` parameter. + + .. versionadded:: 2.0 + Moved the implementation to Werkzeug. This is now a wrapper to + pass some Flask-specific arguments. + + .. versionadded:: 0.5 + """ + return werkzeug.utils.send_from_directory( # type: ignore[return-value] + directory, path, **_prepare_send_file_kwargs(**kwargs) + ) + + +def get_root_path(import_name: str) -> str: + """Find the root path of a package, or the path that contains a + module. If it cannot be found, returns the current working + directory. + + Not to be confused with the value returned by :func:`find_package`. + + :meta private: + """ + # Module already imported and has a file attribute. Use that first. + mod = sys.modules.get(import_name) + + if mod is not None and hasattr(mod, "__file__") and mod.__file__ is not None: + return os.path.dirname(os.path.abspath(mod.__file__)) + + # Next attempt: check the loader. + try: + spec = importlib.util.find_spec(import_name) + + if spec is None: + raise ValueError + except (ImportError, ValueError): + loader = None + else: + loader = spec.loader + + # Loader does not exist or we're referring to an unloaded main + # module or a main module without path (interactive sessions), go + # with the current working directory. + if loader is None: + return os.getcwd() + + if hasattr(loader, "get_filename"): + filepath = loader.get_filename(import_name) + else: + # Fall back to imports. + __import__(import_name) + mod = sys.modules[import_name] + filepath = getattr(mod, "__file__", None) + + # If we don't have a file path it might be because it is a + # namespace package. In this case pick the root path from the + # first module that is contained in the package. + if filepath is None: + raise RuntimeError( + "No root path can be found for the provided module" + f" {import_name!r}. This can happen because the module" + " came from an import hook that does not provide file" + " name information or because it's a namespace package." + " In this case the root path needs to be explicitly" + " provided." + ) + + # filepath is import_name.py for a module, or __init__.py for a package. + return os.path.dirname(os.path.abspath(filepath)) # type: ignore[no-any-return] + + +@lru_cache(maxsize=None) +def _split_blueprint_path(name: str) -> list[str]: + out: list[str] = [name] + + if "." in name: + out.extend(_split_blueprint_path(name.rpartition(".")[0])) + + return out diff --git a/venv/Lib/site-packages/flask/json/__init__.py b/venv/Lib/site-packages/flask/json/__init__.py new file mode 100644 index 00000000..c0941d04 --- /dev/null +++ b/venv/Lib/site-packages/flask/json/__init__.py @@ -0,0 +1,170 @@ +from __future__ import annotations + +import json as _json +import typing as t + +from ..globals import current_app +from .provider import _default + +if t.TYPE_CHECKING: # pragma: no cover + from ..wrappers import Response + + +def dumps(obj: t.Any, **kwargs: t.Any) -> str: + """Serialize data as JSON. + + If :data:`~flask.current_app` is available, it will use its + :meth:`app.json.dumps() ` + method, otherwise it will use :func:`json.dumps`. + + :param obj: The data to serialize. + :param kwargs: Arguments passed to the ``dumps`` implementation. + + .. versionchanged:: 2.3 + The ``app`` parameter was removed. + + .. versionchanged:: 2.2 + Calls ``current_app.json.dumps``, allowing an app to override + the behavior. + + .. versionchanged:: 2.0.2 + :class:`decimal.Decimal` is supported by converting to a string. + + .. versionchanged:: 2.0 + ``encoding`` will be removed in Flask 2.1. + + .. versionchanged:: 1.0.3 + ``app`` can be passed directly, rather than requiring an app + context for configuration. + """ + if current_app: + return current_app.json.dumps(obj, **kwargs) + + kwargs.setdefault("default", _default) + return _json.dumps(obj, **kwargs) + + +def dump(obj: t.Any, fp: t.IO[str], **kwargs: t.Any) -> None: + """Serialize data as JSON and write to a file. + + If :data:`~flask.current_app` is available, it will use its + :meth:`app.json.dump() ` + method, otherwise it will use :func:`json.dump`. + + :param obj: The data to serialize. + :param fp: A file opened for writing text. Should use the UTF-8 + encoding to be valid JSON. + :param kwargs: Arguments passed to the ``dump`` implementation. + + .. versionchanged:: 2.3 + The ``app`` parameter was removed. + + .. versionchanged:: 2.2 + Calls ``current_app.json.dump``, allowing an app to override + the behavior. + + .. versionchanged:: 2.0 + Writing to a binary file, and the ``encoding`` argument, will be + removed in Flask 2.1. + """ + if current_app: + current_app.json.dump(obj, fp, **kwargs) + else: + kwargs.setdefault("default", _default) + _json.dump(obj, fp, **kwargs) + + +def loads(s: str | bytes, **kwargs: t.Any) -> t.Any: + """Deserialize data as JSON. + + If :data:`~flask.current_app` is available, it will use its + :meth:`app.json.loads() ` + method, otherwise it will use :func:`json.loads`. + + :param s: Text or UTF-8 bytes. + :param kwargs: Arguments passed to the ``loads`` implementation. + + .. versionchanged:: 2.3 + The ``app`` parameter was removed. + + .. versionchanged:: 2.2 + Calls ``current_app.json.loads``, allowing an app to override + the behavior. + + .. versionchanged:: 2.0 + ``encoding`` will be removed in Flask 2.1. The data must be a + string or UTF-8 bytes. + + .. versionchanged:: 1.0.3 + ``app`` can be passed directly, rather than requiring an app + context for configuration. + """ + if current_app: + return current_app.json.loads(s, **kwargs) + + return _json.loads(s, **kwargs) + + +def load(fp: t.IO[t.AnyStr], **kwargs: t.Any) -> t.Any: + """Deserialize data as JSON read from a file. + + If :data:`~flask.current_app` is available, it will use its + :meth:`app.json.load() ` + method, otherwise it will use :func:`json.load`. + + :param fp: A file opened for reading text or UTF-8 bytes. + :param kwargs: Arguments passed to the ``load`` implementation. + + .. versionchanged:: 2.3 + The ``app`` parameter was removed. + + .. versionchanged:: 2.2 + Calls ``current_app.json.load``, allowing an app to override + the behavior. + + .. versionchanged:: 2.2 + The ``app`` parameter will be removed in Flask 2.3. + + .. versionchanged:: 2.0 + ``encoding`` will be removed in Flask 2.1. The file must be text + mode, or binary mode with UTF-8 bytes. + """ + if current_app: + return current_app.json.load(fp, **kwargs) + + return _json.load(fp, **kwargs) + + +def jsonify(*args: t.Any, **kwargs: t.Any) -> Response: + """Serialize the given arguments as JSON, and return a + :class:`~flask.Response` object with the ``application/json`` + mimetype. A dict or list returned from a view will be converted to a + JSON response automatically without needing to call this. + + This requires an active request or application context, and calls + :meth:`app.json.response() `. + + In debug mode, the output is formatted with indentation to make it + easier to read. This may also be controlled by the provider. + + Either positional or keyword arguments can be given, not both. + If no arguments are given, ``None`` is serialized. + + :param args: A single value to serialize, or multiple values to + treat as a list to serialize. + :param kwargs: Treat as a dict to serialize. + + .. versionchanged:: 2.2 + Calls ``current_app.json.response``, allowing an app to override + the behavior. + + .. versionchanged:: 2.0.2 + :class:`decimal.Decimal` is supported by converting to a string. + + .. versionchanged:: 0.11 + Added support for serializing top-level arrays. This was a + security risk in ancient browsers. See :ref:`security-json`. + + .. versionadded:: 0.2 + """ + return current_app.json.response(*args, **kwargs) # type: ignore[return-value] diff --git a/venv/Lib/site-packages/flask/json/__pycache__/__init__.cpython-312.pyc b/venv/Lib/site-packages/flask/json/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6f6af886163dcb728383f387d1d446fed2fd0161 GIT binary patch literal 6726 zcmd5=&2JmW72l;OX)Ri!?Zk~^*YPBli?Ws0vK+esW7skLk-8s_5h?vpRggpOP+EDp z%gipt(l8362oSi3#<%$BgAuqW(0`;C5eXoGwE?4P555&pdhw~hH?zCcS5( zed?K=>38~^tT#{}oEb!0zcb)j^<6W&Ohfc9yqt*ZJ>>xU4|=)!?wQ@_KkQf^WoL$+ z5oZ^kBTn8K!ZYveadLR>arQd9@!Z?a6!twKAqBIWTg!b5jEcQBD7oYi?D$KqDqa)uOF41VVEnQq4KE_CEpFnqO{*aY>TuJm4)o; zH(kGJ)J)g7Y2G*gWKQ9^Fu1XgV@6i;^)HEyjR-pE-~X0d$neX3hA}0i%RTpoU=EKs z<01RW)O%O$p&^Ffl^QG3=VJNyHIIka?N!DpGdE;>k-HwRcw&sX5o@`g$C{x)Jyhe1 zbrH=M%P^(A5C*>OH0zD<`vrDJOHaWq z-dx36)2|lGJ^AHW`C@}hUS~mNp~$A^>vAF>7y zL*X!tz`U$nRw|XT?0Ul!)J1ttJeh5?MIl4DQgxpDbHXVW*`$3^%|MnU1 zNJ!R#xuvKFi^ADb&SWa*0{6TSl2&cRD$|zBV+3Fd`i=IcH%cQQvx= zRJC$h_|?F{09Z;nR7E5G$9W4B-VOm`?0 zQQR)+EO+}$)Lh7P`{1430quJs9b?9x-nvkn{QyoH&NgtCyxAyd`Qg0VD21?ksnQHx zUxZ=$c^nQGRf4GZf%0$oha{fpse`Gm-tdl=>uiDAK`QR5QmaI1)XNQ)v z`#ahFcij62zR3RIVQ$YSZ{L3VPT0vEyIVuCls&crQATw&45q;OSG24F&NrS1IB%M@ z44^YZ&}ka&%+1Wsf#-VJg+6Y6Er7y*9M+Pswmt(j*AubFeiNX&ZA^VGNPRv;tu=tA zj26y!E5NF4r;d}9vMDSE`oSH770OZ&NIo3l);|+!oiSQY@mlph@a<5pso#tNY*;{i&x8y`UqFZl<5uqkerDz(q zVINfcX7u&jXHkW$B2OAa`ju5RbY!|_s6j(>B#jxb-~>x5evW_mBNV`l$xUbq@8l;R zoJO%^P5v{cB_DQjNAF%nv6MafBBR8?El>hiArsu?{mf zrw+8*ggR_+W4ej9%?L=(7nTud<#rfMdSTr3=8-6c?wdziC%1^SChdt7U!D=A6fsOt ziejr&P(5&vPr!W<^A&mrABpBSULb1_BTJTx0Yf4WWo(3ugY=aO5B~%*Ce}j6XeU2< z@BMX<^ z@H%fJgA=NNhZZ9P&x0&vRcM+tSEh|~31}(1m-mZLon(iG)R+PT*iJjhQQ!z;0uDuu$MDS@>DU@Vj$(r7;+MGi1QGlHC4cy zvRZYH>qwotNSf*P5KB(adW5w{79?qR?VKA&7Du9P1q++q=r53IPjY|FQ+2!#IEWJ4I1N~SQH+%Uh;MrdmrT^R}n^m zETbE>nRJnIL+*|HRTtjODl%w68b(bC!HQDUie)6kRp)WaCM74Ow-cPyK~6MDC?Xmp zz|>UFN(_&TSe9IB18{c-NvvCI%hxdKkNAhLqqt>!^^R%meq}j#sFORioEz=rM(;UG zxl<32a5}Y=J*Ai(LQ{%7pY})gdB3e>%Om*E9nfIb?E^I~SgMn1P?yJzU6Fm|z2Bhe zx4j{f(YP!f3`jkImm4|sU^jJCv#z5OhJJJx-562glnPTDWhPw@qpQ||Vq{%oRHJNv zVPAKsRH`+B^rBRfV20X&>dxy90I#vPT{G$q&UrxvFrssjRstJ3`8YiN>uZ-v7k+%{ z!cVVUeYb11BoGw4six4mKNXEDdaW)@viYA4WlP1ppIT=CeN9iCl|Mui-E`qwDE{4N zn&ur3o3q2uKM*si- literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/flask/json/__pycache__/provider.cpython-312.pyc b/venv/Lib/site-packages/flask/json/__pycache__/provider.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..326e24898babb09e2b2e8ba28828a89547bb22d9 GIT binary patch literal 9293 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/flask/json/__pycache__/tag.cpython-312.pyc b/venv/Lib/site-packages/flask/json/__pycache__/tag.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c5735cecdd1dd2a2bc1e9550ebdf577af0b11a66 GIT binary patch literal 13988 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 to an instance of the class. + + :param app: An application instance. This will be stored as a + :class:`weakref.proxy` on the :attr:`_app` attribute. + + .. versionadded:: 2.2 + """ + + 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. + + :param obj: The data to serialize. + :param kwargs: May be passed to the underlying JSON library. + """ + raise NotImplementedError + + def dump(self, obj: t.Any, fp: t.IO[str], **kwargs: t.Any) -> None: + """Serialize data as JSON and write to a file. + + :param obj: The data to serialize. + :param fp: A file opened for writing text. Should use the UTF-8 + encoding to be valid JSON. + :param kwargs: May be passed to the underlying JSON library. + """ + fp.write(self.dumps(obj, **kwargs)) + + def loads(self, s: str | bytes, **kwargs: t.Any) -> t.Any: + """Deserialize data as JSON. + + :param s: Text or UTF-8 bytes. + :param kwargs: May be passed to the underlying JSON library. + """ + raise NotImplementedError + + def load(self, fp: t.IO[t.AnyStr], **kwargs: t.Any) -> t.Any: + """Deserialize data as JSON read from a file. + + :param fp: A file opened for reading text or UTF-8 bytes. + :param kwargs: May be passed to the underlying JSON library. + """ + return self.loads(fp.read(), **kwargs) + + def _prepare_response_obj( + self, args: tuple[t.Any, ...], kwargs: dict[str, t.Any] + ) -> t.Any: + if args and kwargs: + raise TypeError("app.json.response() takes either args or kwargs, not both") + + if not args and not kwargs: + return None + + if len(args) == 1: + return args[0] + + return args or kwargs + + def response(self, *args: t.Any, **kwargs: t.Any) -> Response: + """Serialize the given arguments as JSON, and return a + :class:`~flask.Response` object with the ``application/json`` + mimetype. + + The :func:`~flask.json.jsonify` function calls this method for + the current application. + + Either positional or keyword arguments can be given, not both. + If no arguments are given, ``None`` is serialized. + + :param args: A single value to serialize, or multiple values to + treat as a list to serialize. + :param kwargs: Treat as a dict to serialize. + """ + obj = self._prepare_response_obj(args, kwargs) + return self._app.response_class(self.dumps(obj), mimetype="application/json") + + +def _default(o: t.Any) -> t.Any: + if isinstance(o, date): + return http_date(o) + + if isinstance(o, (decimal.Decimal, uuid.UUID)): + return str(o) + + if dataclasses and dataclasses.is_dataclass(o): + return dataclasses.asdict(o) + + if hasattr(o, "__html__"): + return str(o.__html__()) + + raise TypeError(f"Object of type {type(o).__name__} is not JSON serializable") + + +class DefaultJSONProvider(JSONProvider): + """Provide JSON operations using Python's built-in :mod:`json` + library. Serializes the following additional data types: + + - :class:`datetime.datetime` and :class:`datetime.date` are + serialized to :rfc:`822` strings. This is the same as the HTTP + date format. + - :class:`uuid.UUID` is serialized to a string. + - :class:`dataclasses.dataclass` is passed to + :func:`dataclasses.asdict`. + - :class:`~markupsafe.Markup` (or any object with a ``__html__`` + method) will call the ``__html__`` method to get a string. + """ + + 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``. + """ + + ensure_ascii = True + """Replace non-ASCII characters with escape sequences. This may be + more compatible with some clients, but can be disabled for better + performance and size. + """ + + sort_keys = True + """Sort the keys in any serialized dicts. This may be useful for + some caching situations, but can be disabled for better performance. + When enabled, keys must all be strings, they are not converted + before sorting. + """ + + compact: bool | None = None + """If ``True``, or ``None`` out of debug mode, the :meth:`response` + output will not add indentation, newlines, or spaces. If ``False``, + or ``None`` in debug mode, it will use a non-compact representation. + """ + + mimetype = "application/json" + """The mimetype set in :meth:`response`.""" + + def dumps(self, obj: t.Any, **kwargs: t.Any) -> str: + """Serialize data as JSON to a string. + + Keyword arguments are passed to :func:`json.dumps`. Sets some + parameter defaults from the :attr:`default`, + :attr:`ensure_ascii`, and :attr:`sort_keys` attributes. + + :param obj: The data to serialize. + :param kwargs: Passed to :func:`json.dumps`. + """ + kwargs.setdefault("default", self.default) + kwargs.setdefault("ensure_ascii", self.ensure_ascii) + kwargs.setdefault("sort_keys", self.sort_keys) + return json.dumps(obj, **kwargs) + + def loads(self, s: str | bytes, **kwargs: t.Any) -> t.Any: + """Deserialize data as JSON from a string or bytes. + + :param s: Text or UTF-8 bytes. + :param kwargs: Passed to :func:`json.loads`. + """ + return json.loads(s, **kwargs) + + def response(self, *args: t.Any, **kwargs: t.Any) -> Response: + """Serialize the given arguments as JSON, and return a + :class:`~flask.Response` object with it. The response mimetype + will be "application/json" and can be changed with + :attr:`mimetype`. + + If :attr:`compact` is ``False`` or debug mode is enabled, the + output will be formatted to be easier to read. + + Either positional or keyword arguments can be given, not both. + If no arguments are given, ``None`` is serialized. + + :param args: A single value to serialize, or multiple values to + treat as a list to serialize. + :param kwargs: Treat as a dict to serialize. + """ + obj = self._prepare_response_obj(args, kwargs) + dump_args: dict[str, t.Any] = {} + + if (self.compact is None and self._app.debug) or self.compact is False: + dump_args.setdefault("indent", 2) + else: + dump_args.setdefault("separators", (",", ":")) + + return self._app.response_class( + f"{self.dumps(obj, **dump_args)}\n", mimetype=self.mimetype + ) diff --git a/venv/Lib/site-packages/flask/json/tag.py b/venv/Lib/site-packages/flask/json/tag.py new file mode 100644 index 00000000..8dc3629b --- /dev/null +++ b/venv/Lib/site-packages/flask/json/tag.py @@ -0,0 +1,327 @@ +""" +Tagged JSON +~~~~~~~~~~~ + +A compact representation for lossless serialization of non-standard JSON +types. :class:`~flask.sessions.SecureCookieSessionInterface` uses this +to serialize the session data, but it may be useful in other places. It +can be extended to support other types. + +.. autoclass:: TaggedJSONSerializer + :members: + +.. autoclass:: JSONTag + :members: + +Let's see an example that adds support for +:class:`~collections.OrderedDict`. Dicts don't have an order in JSON, so +to handle this we will dump the items as a list of ``[key, value]`` +pairs. Subclass :class:`JSONTag` and give it the new key ``' od'`` to +identify the type. The session serializer processes dicts first, so +insert the new tag at the front of the order since ``OrderedDict`` must +be processed before ``dict``. + +.. code-block:: python + + from flask.json.tag import JSONTag + + class TagOrderedDict(JSONTag): + __slots__ = ('serializer',) + key = ' od' + + def check(self, value): + return isinstance(value, OrderedDict) + + def to_json(self, value): + return [[k, self.serializer.tag(v)] for k, v in iteritems(value)] + + def to_python(self, value): + return OrderedDict(value) + + app.session_interface.serializer.register(TagOrderedDict, index=0) +""" + +from __future__ import annotations + +import typing as t +from base64 import b64decode +from base64 import b64encode +from datetime import datetime +from uuid import UUID + +from markupsafe import Markup +from werkzeug.http import http_date +from werkzeug.http import parse_date + +from ..json import dumps +from ..json import loads + + +class JSONTag: + """Base class for defining type tags for :class:`TaggedJSONSerializer`.""" + + __slots__ = ("serializer",) + + #: The tag to mark the serialized object with. If empty, this tag is + #: only used as an intermediate step during tagging. + key: str = "" + + def __init__(self, serializer: TaggedJSONSerializer) -> None: + """Create a tagger for the given serializer.""" + self.serializer = serializer + + def check(self, value: t.Any) -> bool: + """Check if the given value should be tagged by this tag.""" + raise NotImplementedError + + def to_json(self, value: t.Any) -> t.Any: + """Convert the Python object to an object that is a valid JSON type. + The tag will be added later.""" + raise NotImplementedError + + def to_python(self, value: t.Any) -> t.Any: + """Convert the JSON representation back to the correct type. The tag + will already be removed.""" + raise NotImplementedError + + 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)} + + +class TagDict(JSONTag): + """Tag for 1-item dicts whose only key matches a registered tag. + + Internally, the dict key is suffixed with `__`, and the suffix is removed + when deserializing. + """ + + __slots__ = () + key = " di" + + def check(self, value: t.Any) -> bool: + return ( + isinstance(value, dict) + and len(value) == 1 + and next(iter(value)) in self.serializer.tags + ) + + def to_json(self, value: t.Any) -> t.Any: + key = next(iter(value)) + return {f"{key}__": self.serializer.tag(value[key])} + + def to_python(self, value: t.Any) -> t.Any: + key = next(iter(value)) + return {key[:-2]: value[key]} + + +class PassDict(JSONTag): + __slots__ = () + + def check(self, value: t.Any) -> bool: + return isinstance(value, dict) + + def to_json(self, value: t.Any) -> t.Any: + # JSON objects may only have string keys, so don't bother tagging the + # key here. + return {k: self.serializer.tag(v) for k, v in value.items()} + + tag = to_json + + +class TagTuple(JSONTag): + __slots__ = () + key = " t" + + def check(self, value: t.Any) -> bool: + return isinstance(value, tuple) + + def to_json(self, value: t.Any) -> t.Any: + return [self.serializer.tag(item) for item in value] + + def to_python(self, value: t.Any) -> t.Any: + return tuple(value) + + +class PassList(JSONTag): + __slots__ = () + + def check(self, value: t.Any) -> bool: + return isinstance(value, list) + + def to_json(self, value: t.Any) -> t.Any: + return [self.serializer.tag(item) for item in value] + + tag = to_json + + +class TagBytes(JSONTag): + __slots__ = () + key = " b" + + def check(self, value: t.Any) -> bool: + return isinstance(value, bytes) + + def to_json(self, value: t.Any) -> t.Any: + return b64encode(value).decode("ascii") + + def to_python(self, value: t.Any) -> t.Any: + return b64decode(value) + + +class TagMarkup(JSONTag): + """Serialize anything matching the :class:`~markupsafe.Markup` API by + having a ``__html__`` method to the result of that method. Always + deserializes to an instance of :class:`~markupsafe.Markup`.""" + + __slots__ = () + key = " m" + + def check(self, value: t.Any) -> bool: + return callable(getattr(value, "__html__", None)) + + def to_json(self, value: t.Any) -> t.Any: + return str(value.__html__()) + + def to_python(self, value: t.Any) -> t.Any: + return Markup(value) + + +class TagUUID(JSONTag): + __slots__ = () + key = " u" + + def check(self, value: t.Any) -> bool: + return isinstance(value, UUID) + + def to_json(self, value: t.Any) -> t.Any: + return value.hex + + def to_python(self, value: t.Any) -> t.Any: + return UUID(value) + + +class TagDateTime(JSONTag): + __slots__ = () + key = " d" + + def check(self, value: t.Any) -> bool: + return isinstance(value, datetime) + + def to_json(self, value: t.Any) -> t.Any: + return http_date(value) + + def to_python(self, value: t.Any) -> t.Any: + return parse_date(value) + + +class TaggedJSONSerializer: + """Serializer that uses a tag system to compactly represent objects that + are not JSON types. Passed as the intermediate serializer to + :class:`itsdangerous.Serializer`. + + The following extra types are supported: + + * :class:`dict` + * :class:`tuple` + * :class:`bytes` + * :class:`~markupsafe.Markup` + * :class:`~uuid.UUID` + * :class:`~datetime.datetime` + """ + + __slots__ = ("tags", "order") + + #: Tag classes to bind when creating the serializer. Other tags can be + #: added later using :meth:`~register`. + default_tags = [ + TagDict, + PassDict, + TagTuple, + PassList, + TagBytes, + TagMarkup, + TagUUID, + TagDateTime, + ] + + def __init__(self) -> None: + self.tags: dict[str, JSONTag] = {} + self.order: list[JSONTag] = [] + + for cls in self.default_tags: + self.register(cls) + + def register( + self, + tag_class: type[JSONTag], + force: bool = False, + index: int | None = None, + ) -> None: + """Register a new tag with this serializer. + + :param tag_class: tag class to register. Will be instantiated with this + serializer instance. + :param force: overwrite an existing tag. If false (default), a + :exc:`KeyError` is raised. + :param index: index to insert the new tag in the tag order. Useful when + the new tag is a special case of an existing tag. If ``None`` + (default), the tag is appended to the end of the order. + + :raise KeyError: if the tag key is already registered and ``force`` is + not true. + """ + tag = tag_class(self) + key = tag.key + + if key: + if not force and key in self.tags: + raise KeyError(f"Tag '{key}' is already registered.") + + self.tags[key] = tag + + if index is None: + self.order.append(tag) + else: + self.order.insert(index, tag) + + 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): + return tag.tag(value) + + return value + + def untag(self, value: dict[str, t.Any]) -> t.Any: + """Convert a tagged representation back to the original type.""" + if len(value) != 1: + return value + + key = next(iter(value)) + + if key not in self.tags: + return value + + 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 self._untag_scan(loads(value)) diff --git a/venv/Lib/site-packages/flask/logging.py b/venv/Lib/site-packages/flask/logging.py new file mode 100644 index 00000000..0cb8f437 --- /dev/null +++ b/venv/Lib/site-packages/flask/logging.py @@ -0,0 +1,79 @@ +from __future__ import annotations + +import logging +import sys +import typing as t + +from werkzeug.local import LocalProxy + +from .globals import request + +if t.TYPE_CHECKING: # pragma: no cover + from .sansio.app import App + + +@LocalProxy +def wsgi_errors_stream() -> t.TextIO: + """Find the most appropriate error stream for the application. If a request + is active, log to ``wsgi.errors``, otherwise use ``sys.stderr``. + + If you configure your own :class:`logging.StreamHandler`, you may want to + use this for the stream. If you are using file or dict configuration and + can't import this directly, you can refer to it as + ``ext://flask.logging.wsgi_errors_stream``. + """ + if request: + return request.environ["wsgi.errors"] # type: ignore[no-any-return] + + return sys.stderr + + +def has_level_handler(logger: logging.Logger) -> bool: + """Check if there is a handler in the logging chain that will handle the + given logger's :meth:`effective level <~logging.Logger.getEffectiveLevel>`. + """ + level = logger.getEffectiveLevel() + current = logger + + while current: + if any(handler.level <= level for handler in current.handlers): + return True + + if not current.propagate: + break + + current = current.parent # type: ignore + + return False + + +#: Log messages to :func:`~flask.logging.wsgi_errors_stream` with the format +#: ``[%(asctime)s] %(levelname)s in %(module)s: %(message)s``. +default_handler = logging.StreamHandler(wsgi_errors_stream) # type: ignore +default_handler.setFormatter( + logging.Formatter("[%(asctime)s] %(levelname)s in %(module)s: %(message)s") +) + + +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 + :attr:`app.import_name `. + + When :attr:`~flask.Flask.debug` is enabled, set the logger level to + :data:`logging.DEBUG` if it is not set. + + If there is no handler for the logger's effective level, add a + :class:`~logging.StreamHandler` for + :func:`~flask.logging.wsgi_errors_stream` with a basic format. + """ + logger = logging.getLogger(app.name) + + if app.debug and not logger.level: + logger.setLevel(logging.DEBUG) + + if not has_level_handler(logger): + logger.addHandler(default_handler) + + return logger diff --git a/venv/Lib/site-packages/flask/py.typed b/venv/Lib/site-packages/flask/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/venv/Lib/site-packages/flask/sansio/README.md b/venv/Lib/site-packages/flask/sansio/README.md new file mode 100644 index 00000000..623ac198 --- /dev/null +++ b/venv/Lib/site-packages/flask/sansio/README.md @@ -0,0 +1,6 @@ +# Sansio + +This folder contains code that can be used by alternative Flask +implementations, for example Quart. The code therefore cannot do any +IO, nor be part of a likely IO path. Finally this code cannot use the +Flask globals. diff --git a/venv/Lib/site-packages/flask/sansio/__pycache__/app.cpython-312.pyc b/venv/Lib/site-packages/flask/sansio/__pycache__/app.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..14a50bc96120e1cf8b10234a950647c4ac0c0be2 GIT binary patch literal 33646 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�Ub3`k0a+BnnEl#<>=NcmH7*YBisnUE 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/flask/sansio/__pycache__/scaffold.cpython-312.pyc b/venv/Lib/site-packages/flask/sansio/__pycache__/scaffold.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3fde9c0db5ab13ea93be0ffe8ff852e1ce0e28d7 GIT binary patch literal 30524 zcmdUYdvF{_df)5|SYQ`eJQl>0)a_3P{J)nE7iaZOFNgzMW!FO0rFBT0WoFV
!8f8sei ztrb#2h=1OCv!6;G;!oJ`R%q1Sir|mxKXCJyk?wUNH>evX&RF^EXL-1*pl(?gY9pYY zv!PibM~!;d%^ODE3sw&M>B!r^Zr'k%mWAxGZr>*n2ny#L6`VLu&tcdVPY8F??; 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 timedelta | None: + if value is None or isinstance(value, timedelta): + return value + + return timedelta(seconds=value) + + +class App(Scaffold): + """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 + the view functions, the URL rules, template configuration and much more. + + The name of the package is used to resolve resources from inside the + package or the folder the module is contained in depending on if the + package parameter resolves to an actual python package (a folder with + an :file:`__init__.py` file inside) or a standard module (just a ``.py`` file). + + For more information about resource loading, see :func:`open_resource`. + + Usually you create a :class:`Flask` instance in your main module or + in the :file:`__init__.py` file of your package like this:: + + from flask import Flask + app = Flask(__name__) + + .. admonition:: About the First Parameter + + The idea of the first parameter is to give Flask an idea of what + belongs to your application. This name is used to find resources + on the filesystem, can be used by extensions to improve debugging + information and a lot more. + + So it's important what you provide there. If you are using a single + module, `__name__` is always the correct value. If you however are + using a package, it's usually recommended to hardcode the name of + your package there. + + For example if your application is defined in :file:`yourapplication/app.py` + you should create it with one of the two versions below:: + + app = Flask('yourapplication') + app = Flask(__name__.split('.')[0]) + + Why is that? The application will work even with `__name__`, thanks + to how resources are looked up. However it will make debugging more + painful. Certain extensions can make assumptions based on the + import name of your application. For example the Flask-SQLAlchemy + extension will look for the code in your application that triggered + an SQL query in debug mode. If the import name is not properly set + up, that debugging information is lost. (For example it would only + pick up SQL queries in `yourapplication.app` and not + `yourapplication.views.frontend`) + + .. versionadded:: 0.7 + The `static_url_path`, `static_folder`, and `template_folder` + parameters were added. + + .. versionadded:: 0.8 + The `instance_path` and `instance_relative_config` parameters were + added. + + .. versionadded:: 0.11 + The `root_path` parameter was added. + + .. versionadded:: 1.0 + The ``host_matching`` and ``static_host`` parameters were added. + + .. versionadded:: 1.0 + The ``subdomain_matching`` parameter was added. Subdomain + matching needs to be enabled manually now. Setting + :data:`SERVER_NAME` does not implicitly enable it. + + :param import_name: the name of the application package + :param static_url_path: can be used to specify a different path for the + static files on the web. Defaults to the name + of the `static_folder` folder. + :param static_folder: The folder with static files that is served at + ``static_url_path``. Relative to the application ``root_path`` + or an absolute path. Defaults to ``'static'``. + :param static_host: the host to use when adding the static route. + Defaults to None. Required when using ``host_matching=True`` + with a ``static_folder`` configured. + :param host_matching: set ``url_map.host_matching`` attribute. + Defaults to False. + :param subdomain_matching: consider the subdomain relative to + :data:`SERVER_NAME` when matching routes. Defaults to False. + :param template_folder: the folder that contains the templates that should + be used by the application. Defaults to + ``'templates'`` folder in the root path of the + application. + :param instance_path: An alternative instance path for the application. + By default the folder ``'instance'`` next to the + package or module is assumed to be the instance + path. + :param instance_relative_config: if set to ``True`` relative filenames + for loading the config are assumed to + be relative to the instance path instead + of the application root. + :param root_path: The path to the root of the application files. + This should only be set manually when it can't be detected + automatically, such as for namespace packages. + """ + + #: The class 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[bool]("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[t.Union[str, bytes, None]]("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[timedelta]( + "PERMANENT_SESSION_LIFETIME", + get_converter=_make_timedelta, # type: ignore[arg-type] + ) + + 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[str, t.Any] = {} + + #: 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 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 + + default_config: dict[str, t.Any] + response_class: type[Response] + + def __init__( + self, + import_name: str, + static_url_path: str | None = None, + 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[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, + template_folder=template_folder, + 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[str, t.Any] = {} + + #: 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(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 + + 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." + ) + + @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. + + .. versionadded:: 0.8 + """ + if self.import_name == "__main__": + fn: str | None = 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 + + @cached_property + def logger(self) -> logging.Logger: + """A standard Python :class:`~logging.Logger` for the app, with + the same name as :attr:`name`. + + In debug mode, the logger's :attr:`~logging.Logger.level` will + be set to :data:`~logging.DEBUG`. + + If there are no handlers configured, a default handler will be + added. See :doc:`/logging` for more information. + + .. versionchanged:: 1.1.0 + The logger takes the same name as :attr:`name` rather than + hard-coding ``"flask.app"``. + + .. 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. + + .. versionadded:: 0.3 + """ + return create_logger(self) + + @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() + + def create_jinja_environment(self) -> Environment: + raise NotImplementedError() + + 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. + + .. 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) + + 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") + + 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. + + The global loader dispatches between the loaders of the application + and the individual blueprints. + + .. versionadded:: 0.7 + """ + return DispatchingJinjaLoader(self) + + 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`. + + .. versionchanged:: 2.2 + Autoescaping is now enabled by default for ``.svg`` files. + + .. versionadded:: 0.5 + """ + if filename is None: + return True + return filename.endswith((".html", ".htm", ".xml", ".xhtml", ".svg")) + + @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"] # type: ignore[no-any-return] + + @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 + + @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_obj = self.url_rule_class(rule, methods=methods, **options) + rule_obj.provide_automatic_options = provide_automatic_options # type: ignore[attr-defined] + + self.url_map.add(rule_obj) + 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, blueprints: list[str] + ) -> 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 = (*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 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 + + 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 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, # type: ignore[arg-type] + ) + + def inject_url_defaults(self, endpoint: str, values: dict[str, t.Any]) -> 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 diff --git a/venv/Lib/site-packages/flask/sansio/blueprints.py b/venv/Lib/site-packages/flask/sansio/blueprints.py new file mode 100644 index 00000000..4f912cca --- /dev/null +++ b/venv/Lib/site-packages/flask/sansio/blueprints.py @@ -0,0 +1,632 @@ +from __future__ import annotations + +import os +import typing as t +from collections import defaultdict +from functools import update_wrapper + +from .. import typing as ft +from .scaffold import _endpoint_from_view_func +from .scaffold import _sentinel +from .scaffold import Scaffold +from .scaffold import setupmethod + +if t.TYPE_CHECKING: # pragma: no cover + from .app import App + +DeferredSetupFunction = t.Callable[["BlueprintSetupState"], None] +T_after_request = t.TypeVar("T_after_request", bound=ft.AfterRequestCallable[t.Any]) +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 +) + + +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. + """ + + def __init__( + self, + blueprint: Blueprint, + app: App, + options: t.Any, + first_registration: bool, + ) -> None: + #: a reference to the current application + self.app = app + + #: a reference to the blueprint that created this setup state. + self.blueprint = blueprint + + #: a dictionary with all options that were passed to the + #: :meth:`~flask.Flask.register_blueprint` method. + self.options = options + + #: 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: ft.RouteCallable | 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. + """ + 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")) + + self.app.add_url_rule( + rule, + f"{self.name_prefix}.{self.name}.{endpoint}".lstrip("."), + view_func, + defaults=defaults, + **options, + ) + + +class Blueprint(Scaffold): + """Represents a blueprint, a collection of routes and other + app-related functions that can be registered on a real application + later. + + 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. + + 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. + + See :doc:`/blueprints` for more information. + + :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. + + .. 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[str] | None = None, + static_url_path: str | None = None, + template_folder: str | os.PathLike[str] | None = None, + url_prefix: str | None = None, + subdomain: str | None = None, + url_defaults: dict[str, t.Any] | None = None, + root_path: str | None = None, + cli_group: str | None = _sentinel, # type: ignore[assignment] + ): + 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[str, t.Any]]] = [] + + 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: DeferredSetupFunction) -> 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) + + @setupmethod + def record_once(self, func: DeferredSetupFunction) -> 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: App, options: dict[str, t.Any], 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: App, options: dict[str, t.Any]) -> 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, # type: ignore[attr-defined] + endpoint="static", + ) + + # Merge blueprint data into parent. + if first_bp_registration or first_name_registration: + self._merge_blueprint_funcs(app, name) + + 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) + + def _merge_blueprint_funcs(self, app: App, name: str) -> None: + def extend( + bp_dict: dict[ft.AppOrBlueprintKey, list[t.Any]], + parent_dict: dict[ft.AppOrBlueprintKey, list[t.Any]], + ) -> None: + 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) + + @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: + def from_blueprint(state: BlueprintSetupState) -> None: + state.app.errorhandler(code)(f) + + self.record_once(from_blueprint) + 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 diff --git a/venv/Lib/site-packages/flask/sansio/scaffold.py b/venv/Lib/site-packages/flask/sansio/scaffold.py new file mode 100644 index 00000000..69e33a09 --- /dev/null +++ b/venv/Lib/site-packages/flask/sansio/scaffold.py @@ -0,0 +1,801 @@ +from __future__ import annotations + +import importlib.util +import os +import pathlib +import sys +import typing as t +from collections import defaultdict +from functools import update_wrapper + +from jinja2 import BaseLoader +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 ..helpers import get_root_path +from ..templating import _default_template_ctx_processor + +if t.TYPE_CHECKING: # pragma: no cover + from click import Group + +# 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.Any]) +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: Scaffold, *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 + """ + + cli: Group + name: str + _static_folder: str | None = None + _static_url_path: str | None = None + + def __init__( + self, + import_name: str, + static_folder: str | os.PathLike[str] | None = None, + static_url_path: str | None = None, + template_folder: str | os.PathLike[str] | 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 + + #: 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, ft.RouteCallable] = {} + + #: 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[t.Any]] + ] = 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[str] | 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 + + @cached_property + def jinja_loader(self) -> BaseLoader | 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 _method_route( + self, + method: str, + rule: str, + options: dict[str, t.Any], + ) -> 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: ft.RouteCallable) -> 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: str) -> str: + """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.submodule_search_locations: + if root_spec.origin is None or root_spec.origin == "namespace": + # 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) + else: + # package with __init__.py + return os.path.dirname(os.path.dirname(root_spec.origin)) + else: + # module + return os.path.dirname(root_spec.origin) # type: ignore[type-var, return-value] + + +def find_package(import_name: str) -> tuple[str | None, 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 new file mode 100644 index 00000000..ee19ad63 --- /dev/null +++ b/venv/Lib/site-packages/flask/sessions.py @@ -0,0 +1,379 @@ +from __future__ import annotations + +import hashlib +import typing as t +from collections.abc import MutableMapping +from datetime import datetime +from datetime import timezone + +from itsdangerous import BadSignature +from itsdangerous import URLSafeTimedSerializer +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 + from .wrappers import Response + + +# TODO generic when Python > 3.8 +class SessionMixin(MutableMapping): # type: ignore[type-arg] + """Expands a basic dictionary with session attributes.""" + + @property + def permanent(self) -> bool: + """This reflects the ``'_permanent'`` key in the dict.""" + return self.get("_permanent", False) + + @permanent.setter + def permanent(self, value: bool) -> None: + self["_permanent"] = bool(value) + + #: Some implementations can detect whether a session is newly + #: created, but that is not guaranteed. Use with caution. The mixin + # default is hard-coded ``False``. + new = False + + #: Some implementations can detect changes to the session and set + #: this when that happens. The mixin default is hard coded to + #: ``True``. + modified = True + + #: Some implementations can detect when session data is read or + #: written and set this when that happens. The mixin default is hard + #: coded to ``True``. + accessed = True + + +# 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 + :attr:`accessed` attributes. It cannot reliably track whether a + session is new (vs. empty), so :attr:`new` remains hard coded to + ``False``. + """ + + #: When data is changed, this is set to ``True``. Only the session + #: dictionary itself is tracked; if the session contains mutable + #: data (for example a nested dict) then this must be set to + #: ``True`` manually when modifying that data. The session cookie + #: will only be written to the response if this is ``True``. + modified = False + + #: When data is read or written, this is set to ``True``. Used by + # :class:`.SecureCookieSessionInterface` to add a ``Vary: Cookie`` + #: header, which allows caching proxies to cache different pages for + #: different users. + accessed = False + + def __init__(self, initial: t.Any = None) -> None: + def on_update(self: te.Self) -> None: + self.modified = True + self.accessed = True + + super().__init__(initial, on_update) + + def __getitem__(self, key: str) -> t.Any: + self.accessed = True + return super().__getitem__(key) + + def get(self, key: str, default: t.Any = None) -> t.Any: + self.accessed = True + return super().get(key, default) + + def setdefault(self, key: str, default: t.Any = None) -> t.Any: + self.accessed = True + return super().setdefault(key, default) + + +class NullSession(SecureCookieSession): + """Class used to generate nicer error messages if sessions are not + available. Will still allow read-only access to the empty session + but fail on setting. + """ + + def _fail(self, *args: t.Any, **kwargs: t.Any) -> t.NoReturn: + raise RuntimeError( + "The session is unavailable because no secret " + "key was set. Set the secret_key on the " + "application to something unique and secret." + ) + + __setitem__ = __delitem__ = clear = pop = popitem = update = setdefault = _fail # type: ignore # noqa: B950 + del _fail + + +class SessionInterface: + """The basic interface you have to implement in order to replace the + default session interface which uses werkzeug's securecookie + implementation. The only methods you have to implement are + :meth:`open_session` and :meth:`save_session`, the others have + useful defaults which you don't need to change. + + The session object returned by the :meth:`open_session` method has to + provide a dictionary like interface plus the properties and methods + from the :class:`SessionMixin`. We recommend just subclassing a dict + and adding that mixin:: + + class Session(dict, SessionMixin): + pass + + If :meth:`open_session` returns ``None`` Flask will call into + :meth:`make_null_session` to create a session that acts as replacement + if the session support cannot work because some requirement is not + fulfilled. The default :class:`NullSession` class that is created + will complain that the secret key was not set. + + To replace the session interface on an application all you have to do + is to assign :attr:`flask.Flask.session_interface`:: + + app = Flask(__name__) + app.session_interface = MySessionInterface() + + Multiple requests with the same session may be sent and handled + concurrently. When implementing a new session interface, consider + whether reads or writes to the backing store must be synchronized. + There is no guarantee on the order in which the session for each + request is opened or saved, it will occur in the order that requests + begin and end processing. + + .. versionadded:: 0.8 + """ + + #: :meth:`make_null_session` will look here for the class that should + #: be created when a null session is requested. Likewise the + #: :meth:`is_null_session` method will perform a typecheck against + #: this type. + null_session_class = NullSession + + #: A flag that indicates if the session interface is pickle based. + #: This can be used by Flask extensions to make a decision in regards + #: to how to deal with the session object. + #: + #: .. versionadded:: 0.10 + pickle_based = False + + def make_null_session(self, app: Flask) -> NullSession: + """Creates a null session which acts as a replacement object if the + real session support could not be loaded due to a configuration + error. This mainly aids the user experience because the job of the + null session is to still support lookup without complaining but + modifications are answered with a helpful error message of what + failed. + + This creates an instance of :attr:`null_session_class` by default. + """ + return self.null_session_class() + + def is_null_session(self, obj: object) -> bool: + """Checks if a given object is a null session. Null sessions are + not asked to be saved. + + This checks if the object is an instance of :attr:`null_session_class` + by default. + """ + return isinstance(obj, self.null_session_class) + + 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"] # 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, + browsers will only send the cookie to the exact domain it was set from. + Otherwise, they will send it to any subdomain of the given value as well. + + Uses the :data:`SESSION_COOKIE_DOMAIN` config. + + .. versionchanged:: 2.3 + Not set by default, does not fall back to ``SERVER_NAME``. + """ + 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 + default implementation uses the value from the ``SESSION_COOKIE_PATH`` + 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"] # 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"] # 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"] # type: ignore[no-any-return] + + 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"] # 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 + or ``None`` if the session is linked to the browser session. The + default implementation returns now + the permanent session + lifetime configured on the application. + """ + if session.permanent: + return datetime.now(timezone.utc) + app.permanent_session_lifetime + return None + + def should_set_cookie(self, app: Flask, session: SessionMixin) -> bool: + """Used by session backends to determine if a ``Set-Cookie`` header + should be set for this session cookie for this response. If the session + has been modified, the cookie is set. If the session is permanent and + the ``SESSION_REFRESH_EACH_REQUEST`` config is true, the cookie is + always set. + + This check is usually skipped if the session was deleted. + + .. versionadded:: 0.11 + """ + + return session.modified or ( + session.permanent and app.config["SESSION_REFRESH_EACH_REQUEST"] + ) + + def open_session(self, app: Flask, request: Request) -> SessionMixin | None: + """This is called at the beginning of each request, after + pushing the request context, before matching the URL. + + This must return an object which implements a dictionary-like + interface as well as the :class:`SessionMixin` interface. + + This will return ``None`` to indicate that loading failed in + some way that is not immediately an error. The request + context will fall back to using :meth:`make_null_session` + in this case. + """ + raise NotImplementedError() + + def save_session( + self, app: Flask, session: SessionMixin, response: Response + ) -> None: + """This is called at the end of each request, after generating + a response, before removing the request context. It is skipped + if :meth:`is_null_session` returns ``True``. + """ + raise NotImplementedError() + + +session_json_serializer = TaggedJSONSerializer() + + +def _lazy_sha1(string: bytes = b"") -> t.Any: + """Don't access ``hashlib.sha1`` until runtime. FIPS builds may not include + SHA-1, in which case the import and use as a default would fail before the + developer can configure something else. + """ + return hashlib.sha1(string) + + +class SecureCookieSessionInterface(SessionInterface): + """The default session interface that stores sessions in signed cookies + through the :mod:`itsdangerous` module. + """ + + #: the salt that should be applied on top of the secret key for the + #: signing of cookie based sessions. + salt = "cookie-session" + #: the hash function to use for the signature. The default is sha1 + digest_method = staticmethod(_lazy_sha1) + #: the name of the itsdangerous supported key derivation. The default + #: is hmac. + key_derivation = "hmac" + #: A python serializer for the payload. The default is a compact + #: JSON derived serializer with support for some extra Python types + #: such as datetime objects or tuples. + serializer = session_json_serializer + session_class = SecureCookieSession + + def get_signing_serializer(self, app: Flask) -> URLSafeTimedSerializer | None: + if not app.secret_key: + return None + signer_kwargs = dict( + key_derivation=self.key_derivation, digest_method=self.digest_method + ) + return URLSafeTimedSerializer( + app.secret_key, + salt=self.salt, + serializer=self.serializer, + signer_kwargs=signer_kwargs, + ) + + def open_session(self, app: Flask, request: Request) -> SecureCookieSession | None: + s = self.get_signing_serializer(app) + if s is None: + return None + val = request.cookies.get(self.get_cookie_name(app)) + if not val: + return self.session_class() + max_age = int(app.permanent_session_lifetime.total_seconds()) + try: + data = s.loads(val, max_age=max_age) + return self.session_class(data) + except BadSignature: + return self.session_class() + + def save_session( + self, app: Flask, session: SessionMixin, response: Response + ) -> None: + name = self.get_cookie_name(app) + domain = self.get_cookie_domain(app) + path = self.get_cookie_path(app) + secure = self.get_cookie_secure(app) + samesite = self.get_cookie_samesite(app) + httponly = self.get_cookie_httponly(app) + + # Add a "Vary: Cookie" header if the session was accessed at all. + if session.accessed: + response.vary.add("Cookie") + + # If the session is modified to be empty, remove the cookie. + # If the session is empty, return without setting the cookie. + if not session: + if session.modified: + response.delete_cookie( + name, + domain=domain, + path=path, + secure=secure, + samesite=samesite, + httponly=httponly, + ) + response.vary.add("Cookie") + + return + + if not self.should_set_cookie(app, session): + return + + expires = self.get_expiration_time(app, session) + val = self.get_signing_serializer(app).dumps(dict(session)) # type: ignore + response.set_cookie( + name, + val, # type: ignore + expires=expires, + httponly=httponly, + domain=domain, + path=path, + secure=secure, + samesite=samesite, + ) + response.vary.add("Cookie") diff --git a/venv/Lib/site-packages/flask/signals.py b/venv/Lib/site-packages/flask/signals.py new file mode 100644 index 00000000..444fda99 --- /dev/null +++ b/venv/Lib/site-packages/flask/signals.py @@ -0,0 +1,17 @@ +from __future__ import annotations + +from blinker import Namespace + +# This namespace is only for signals provided by Flask itself. +_signals = Namespace() + +template_rendered = _signals.signal("template-rendered") +before_render_template = _signals.signal("before-render-template") +request_started = _signals.signal("request-started") +request_finished = _signals.signal("request-finished") +request_tearing_down = _signals.signal("request-tearing-down") +got_request_exception = _signals.signal("got-request-exception") +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") diff --git a/venv/Lib/site-packages/flask/templating.py b/venv/Lib/site-packages/flask/templating.py new file mode 100644 index 00000000..618a3b35 --- /dev/null +++ b/venv/Lib/site-packages/flask/templating.py @@ -0,0 +1,219 @@ +from __future__ import annotations + +import typing as t + +from jinja2 import BaseLoader +from jinja2 import Environment as BaseEnvironment +from jinja2 import Template +from jinja2 import TemplateNotFound + +from .globals import _cv_app +from .globals import _cv_request +from .globals import current_app +from .globals import request +from .helpers import stream_with_context +from .signals import before_render_template +from .signals import template_rendered + +if t.TYPE_CHECKING: # pragma: no cover + from .app import Flask + from .sansio.app import App + from .sansio.scaffold import Scaffold + + +def _default_template_ctx_processor() -> dict[str, t.Any]: + """Default template context processor. Injects `request`, + `session` and `g`. + """ + appctx = _cv_app.get(None) + reqctx = _cv_request.get(None) + rv: dict[str, t.Any] = {} + if appctx is not None: + rv["g"] = appctx.g + if reqctx is not None: + rv["request"] = reqctx.request + rv["session"] = reqctx.session + return rv + + +class Environment(BaseEnvironment): + """Works like a regular Jinja2 environment but has some additional + knowledge of how Flask's blueprint works so that it can prepend the + name of the blueprint to referenced templates if necessary. + """ + + 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) + self.app = app + + +class DispatchingJinjaLoader(BaseLoader): + """A loader that looks for templates in the application and all + the blueprint folders. + """ + + def __init__(self, app: App) -> None: + self.app = app + + 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: 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 + + for srcobj, loader in self._iter_loaders(template): + try: + rv = loader.get_source(environment, template) + if trv is None: + trv = rv + except TemplateNotFound: + rv = None + attempts.append((loader, srcobj, rv)) + + from .debughelpers import explain_template_loading_attempts + + explain_template_loading_attempts(self.app, template, attempts) + + if trv is not None: + return trv + raise TemplateNotFound(template) + + def _get_source_fast( + 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) + except TemplateNotFound: + continue + raise TemplateNotFound(template) + + 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 + + for blueprint in self.app.iter_blueprints(): + loader = blueprint.jinja_loader + if loader is not None: + yield blueprint, loader + + def list_templates(self) -> list[str]: + result = set() + loader = self.app.jinja_loader + if loader is not None: + result.update(loader.list_templates()) + + for blueprint in self.app.iter_blueprints(): + loader = blueprint.jinja_loader + if loader is not None: + for template in loader.list_templates(): + result.add(template) + + return list(result) + + +def _render(app: Flask, template: Template, context: dict[str, t.Any]) -> str: + app.update_template_context(context) + before_render_template.send( + app, _async_wrapper=app.ensure_sync, template=template, context=context + ) + rv = template.render(context) + template_rendered.send( + app, _async_wrapper=app.ensure_sync, template=template, context=context + ) + return rv + + +def render_template( + template_name_or_list: str | Template | list[str | Template], + **context: t.Any, +) -> str: + """Render a template by name with the given context. + + :param template_name_or_list: The name of the template to render. If + a list is given, the first name to exist will be rendered. + :param context: The variables to make available in the template. + """ + app = current_app._get_current_object() # type: ignore[attr-defined] + template = app.jinja_env.get_or_select_template(template_name_or_list) + return _render(app, template, context) + + +def render_template_string(source: str, **context: t.Any) -> str: + """Render a template from the given source string with the given + context. + + :param source: The source code of the template to render. + :param context: The variables to make available in the template. + """ + app = current_app._get_current_object() # type: ignore[attr-defined] + template = app.jinja_env.from_string(source) + return _render(app, template, context) + + +def _stream( + app: Flask, template: Template, context: dict[str, t.Any] +) -> t.Iterator[str]: + app.update_template_context(context) + before_render_template.send( + app, _async_wrapper=app.ensure_sync, template=template, context=context + ) + + def generate() -> t.Iterator[str]: + yield from template.generate(context) + template_rendered.send( + app, _async_wrapper=app.ensure_sync, template=template, context=context + ) + + rv = generate() + + # If a request context is active, keep it while generating. + if request: + rv = stream_with_context(rv) + + return rv + + +def stream_template( + template_name_or_list: str | Template | list[str | Template], + **context: t.Any, +) -> t.Iterator[str]: + """Render a template by name with the given context as a stream. + This returns an iterator of strings, which can be used as a + streaming response from a view. + + :param template_name_or_list: The name of the template to render. If + a list is given, the first name to exist will be rendered. + :param context: The variables to make available in the template. + + .. versionadded:: 2.2 + """ + app = current_app._get_current_object() # type: ignore[attr-defined] + template = app.jinja_env.get_or_select_template(template_name_or_list) + return _stream(app, template, context) + + +def stream_template_string(source: str, **context: t.Any) -> t.Iterator[str]: + """Render a template from the given source string with the given + context as a stream. This returns an iterator of strings, which can + be used as a streaming response from a view. + + :param source: The source code of the template to render. + :param context: The variables to make available in the template. + + .. versionadded:: 2.2 + """ + app = current_app._get_current_object() # type: ignore[attr-defined] + template = app.jinja_env.from_string(source) + return _stream(app, template, context) diff --git a/venv/Lib/site-packages/flask/testing.py b/venv/Lib/site-packages/flask/testing.py new file mode 100644 index 00000000..a27b7c8f --- /dev/null +++ b/venv/Lib/site-packages/flask/testing.py @@ -0,0 +1,298 @@ +from __future__ import annotations + +import importlib.metadata +import typing as t +from contextlib import contextmanager +from contextlib import ExitStack +from copy import copy +from types import TracebackType +from urllib.parse import urlsplit + +import werkzeug.test +from click.testing import CliRunner +from werkzeug.test import Client +from werkzeug.wrappers import Request as BaseRequest + +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 + + +class EnvironBuilder(werkzeug.test.EnvironBuilder): + """An :class:`~werkzeug.test.EnvironBuilder`, that takes defaults from the + application. + + :param app: The Flask application to configure the environment from. + :param path: URL path being requested. + :param base_url: Base URL where the app is being served, which + ``path`` is relative to. If not given, built from + :data:`PREFERRED_URL_SCHEME`, ``subdomain``, + :data:`SERVER_NAME`, and :data:`APPLICATION_ROOT`. + :param subdomain: Subdomain name to append to :data:`SERVER_NAME`. + :param url_scheme: Scheme to use instead of + :data:`PREFERRED_URL_SCHEME`. + :param json: If given, this is serialized as JSON and passed as + ``data``. Also defaults ``content_type`` to + ``application/json``. + :param args: other positional arguments passed to + :class:`~werkzeug.test.EnvironBuilder`. + :param kwargs: other keyword arguments passed to + :class:`~werkzeug.test.EnvironBuilder`. + """ + + def __init__( + self, + app: Flask, + path: str = "/", + base_url: str | None = None, + subdomain: str | None = None, + url_scheme: str | None = None, + *args: t.Any, + **kwargs: t.Any, + ) -> None: + assert not (base_url or subdomain or url_scheme) or ( + base_url is not None + ) != bool( + subdomain or url_scheme + ), 'Cannot pass "subdomain" or "url_scheme" with "base_url".' + + if base_url is None: + http_host = app.config.get("SERVER_NAME") or "localhost" + app_root = app.config["APPLICATION_ROOT"] + + if subdomain: + http_host = f"{subdomain}.{http_host}" + + if url_scheme is None: + url_scheme = app.config["PREFERRED_URL_SCHEME"] + + url = urlsplit(path) + base_url = ( + f"{url.scheme or url_scheme}://{url.netloc or http_host}" + f"/{app_root.lstrip('/')}" + ) + path = url.path + + if url.query: + sep = b"?" if isinstance(url.query, bytes) else "?" + path += sep + url.query + + self.app = app + super().__init__(path, base_url, *args, **kwargs) + + def json_dumps(self, obj: t.Any, **kwargs: t.Any) -> str: # type: ignore + """Serialize ``obj`` to a JSON-formatted string. + + The serialization will be configured according to the config associated + with this EnvironBuilder's ``app``. + """ + return self.app.json.dumps(obj, **kwargs) + + +_werkzeug_version = "" + + +def _get_werkzeug_version() -> str: + global _werkzeug_version + + if not _werkzeug_version: + _werkzeug_version = importlib.metadata.version("werkzeug") + + return _werkzeug_version + + +class FlaskClient(Client): + """Works like a regular Werkzeug test client but has knowledge about + Flask's contexts to defer the cleanup of the request context until + the end of a ``with`` block. For general information about how to + use this class refer to :class:`werkzeug.test.Client`. + + .. versionchanged:: 0.12 + `app.test_client()` includes preset default environment, which can be + set after instantiation of the `app.test_client()` object in + `client.environ_base`. + + Basic usage is outlined in the :doc:`/testing` chapter. + """ + + application: Flask + + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + super().__init__(*args, **kwargs) + self.preserve_context = False + self._new_contexts: list[t.ContextManager[t.Any]] = [] + self._context_stack = ExitStack() + self.environ_base = { + "REMOTE_ADDR": "127.0.0.1", + "HTTP_USER_AGENT": f"Werkzeug/{_get_werkzeug_version()}", + } + + @contextmanager + def session_transaction( + self, *args: t.Any, **kwargs: t.Any + ) -> 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 + stored back. + + :: + + with client.session_transaction() as session: + session['value'] = 42 + + Internally this is implemented by going through a temporary test + request context and since session handling could depend on + request variables this function accepts the same arguments as + :meth:`~flask.Flask.test_request_context` which are directly + passed through. + """ + if self._cookies is None: + raise TypeError( + "Cookies are disabled. Create a client with 'use_cookies=True'." + ) + + app = self.application + ctx = app.test_request_context(*args, **kwargs) + self._add_cookies_to_wsgi(ctx.request.environ) + + with ctx: + sess = app.session_interface.open_session(app, ctx.request) + + if sess is None: + raise RuntimeError("Session backend did not open a session.") + + yield sess + resp = app.response_class() + + if app.session_interface.is_null_session(sess): + return + + with ctx: + app.session_interface.save_session(app, sess, resp) + + self._update_cookies_from_response( + ctx.request.host.partition(":")[0], + ctx.request.path, + resp.headers.getlist("Set-Cookie"), + ) + + def _copy_environ(self, other: WSGIEnvironment) -> WSGIEnvironment: + out = {**self.environ_base, **other} + + if self.preserve_context: + out["werkzeug.debug.preserve_context"] = self._new_contexts.append + + return out + + 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) + + try: + return builder.get_request() + finally: + builder.close() + + def open( + self, + *args: t.Any, + buffered: bool = False, + follow_redirects: bool = False, + **kwargs: t.Any, + ) -> TestResponse: + if args and isinstance( + args[0], (werkzeug.test.EnvironBuilder, dict, BaseRequest) + ): + if isinstance(args[0], werkzeug.test.EnvironBuilder): + builder = copy(args[0]) + 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( + args[0], app=self.application, environ_base=self._copy_environ({}) + ).get_request() + else: + # isinstance(args[0], BaseRequest) + request = copy(args[0]) + request.environ = self._copy_environ(request.environ) + else: + # request is None + request = self._request_from_builder_args(args, kwargs) + + # Pop any previously preserved contexts. This prevents contexts + # from being preserved across redirects or multiple requests + # within a single block. + self._context_stack.close() + + response = super().open( + request, + buffered=buffered, + follow_redirects=follow_redirects, + ) + response.json_module = self.application.json # type: ignore[assignment] + + # Re-push contexts that were preserved during the request. + while self._new_contexts: + cm = self._new_contexts.pop() + self._context_stack.enter_context(cm) + + return response + + def __enter__(self) -> FlaskClient: + if self.preserve_context: + raise RuntimeError("Cannot nest client invocations") + self.preserve_context = True + return self + + def __exit__( + self, + exc_type: type | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> None: + self.preserve_context = False + self._context_stack.close() + + +class FlaskCliRunner(CliRunner): + """A :class:`~click.testing.CliRunner` for testing a Flask app's + CLI commands. Typically created using + :meth:`~flask.Flask.test_cli_runner`. See :ref:`testing-cli`. + """ + + def __init__(self, app: Flask, **kwargs: t.Any) -> None: + self.app = app + super().__init__(**kwargs) + + def invoke( # type: ignore + self, cli: t.Any = None, args: t.Any = None, **kwargs: t.Any + ) -> t.Any: + """Invokes a CLI command in an isolated environment. See + :meth:`CliRunner.invoke ` for + full method documentation. See :ref:`testing-cli` for examples. + + If the ``obj`` argument is not given, passes an instance of + :class:`~flask.cli.ScriptInfo` that knows how to load the Flask + app being tested. + + :param cli: Command object to invoke. Default is the app's + :attr:`~flask.app.Flask.cli` group. + :param args: List of strings to invoke the command with. + + :return: a :class:`~click.testing.Result` object. + """ + if cli is None: + cli = self.app.cli + + if "obj" not in kwargs: + kwargs["obj"] = ScriptInfo(create_app=lambda: self.app) + + return super().invoke(cli, args, **kwargs) diff --git a/venv/Lib/site-packages/flask/typing.py b/venv/Lib/site-packages/flask/typing.py new file mode 100644 index 00000000..cf6d4ae6 --- /dev/null +++ b/venv/Lib/site-packages/flask/typing.py @@ -0,0 +1,90 @@ +from __future__ import annotations + +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.sansio.response import Response # noqa: F401 + +# The possible types that are directly convertible or are a Response object. +ResponseValue = t.Union[ + "Response", + str, + bytes, + t.List[t.Any], + # Only dict is actually accepted, but Mapping allows for TypedDict. + t.Mapping[str, t.Any], + t.Iterator[str], + t.Iterator[bytes], +] + +# the possible types for an individual HTTP header +# This should be a Union, but mypy doesn't pass unless it's a TypeVar. +HeaderValue = t.Union[str, t.List[str], t.Tuple[str, ...]] + +# the possible types for HTTP headers +HeadersValue = t.Union[ + "Headers", + t.Mapping[str, HeaderValue], + t.Sequence[t.Tuple[str, HeaderValue]], +] + +# The possible types returned by a route function. +ResponseReturnValue = t.Union[ + ResponseValue, + t.Tuple[ResponseValue, HeadersValue], + t.Tuple[ResponseValue, int], + t.Tuple[ResponseValue, int, HeadersValue], + "WSGIApplication", +] + +# Allow any subclass of werkzeug.Response, such as the one from Flask, +# as a callback argument. Using werkzeug.Response directly makes a +# callback annotated with flask.Response fail type checking. +ResponseClass = t.TypeVar("ResponseClass", bound="Response") + +AppOrBlueprintKey = t.Optional[str] # The App key is None, whereas blueprints are named +AfterRequestCallable = t.Union[ + t.Callable[[ResponseClass], ResponseClass], + t.Callable[[ResponseClass], t.Awaitable[ResponseClass]], +] +BeforeFirstRequestCallable = t.Union[ + t.Callable[[], None], t.Callable[[], t.Awaitable[None]] +] +BeforeRequestCallable = t.Union[ + t.Callable[[], t.Optional[ResponseReturnValue]], + t.Callable[[], t.Awaitable[t.Optional[ResponseReturnValue]]], +] +ShellContextProcessorCallable = t.Callable[[], t.Dict[str, t.Any]] +TeardownCallable = t.Union[ + t.Callable[[t.Optional[BaseException]], None], + t.Callable[[t.Optional[BaseException]], t.Awaitable[None]], +] +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, 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 +# exceptions (and using a union type on the argument). +# https://github.com/pallets/flask/issues/4095 +# https://github.com/pallets/flask/issues/4295 +# https://github.com/pallets/flask/issues/4297 +ErrorHandlerCallable = t.Union[ + t.Callable[[t.Any], ResponseReturnValue], + t.Callable[[t.Any], t.Awaitable[ResponseReturnValue]], +] + +RouteCallable = t.Union[ + t.Callable[..., ResponseReturnValue], + t.Callable[..., t.Awaitable[ResponseReturnValue]], +] diff --git a/venv/Lib/site-packages/flask/views.py b/venv/Lib/site-packages/flask/views.py new file mode 100644 index 00000000..794fdc06 --- /dev/null +++ b/venv/Lib/site-packages/flask/views.py @@ -0,0 +1,191 @@ +from __future__ import annotations + +import typing as t + +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"] +) + + +class View: + """Subclass this class and override :meth:`dispatch_request` to + create a generic class-based view. Call :meth:`as_view` to create a + view function that creates an instance of the class with the given + arguments and calls its ``dispatch_request`` method with any URL + variables. + + See :doc:`views` for a detailed guide. + + .. code-block:: python + + class Hello(View): + init_every_request = False + + def dispatch_request(self, name): + return f"Hello, {name}!" + + app.add_url_rule( + "/hello/", view_func=Hello.as_view("hello") + ) + + Set :attr:`methods` on the class to change what methods the view + accepts. + + Set :attr:`decorators` on the class to apply a list of decorators to + the generated view function. Decorators applied to the class itself + will not be applied to the generated view function! + + Set :attr:`init_every_request` to ``False`` for efficiency, unless + you need to store request-global data on ``self``. + """ + + #: The methods this view is registered for. Uses the same default + #: (``["GET", "HEAD", "OPTIONS"]``) as ``route`` and + #: ``add_url_rule`` by default. + methods: t.ClassVar[t.Collection[str] | None] = None + + #: Control whether the ``OPTIONS`` method is handled automatically. + #: Uses the same default (``True``) as ``route`` and + #: ``add_url_rule`` by default. + provide_automatic_options: t.ClassVar[bool | None] = None + + #: A list of decorators to apply, in order, to the generated view + #: function. Remember that ``@decorator`` syntax is applied bottom + #: to top, so the first decorator in the list would be the bottom + #: decorator. + #: + #: .. versionadded:: 0.8 + 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 + #: instance is used for every request. + #: + #: A single instance is more efficient, especially if complex setup + #: is done during init. However, storing data on ``self`` is no + #: longer safe across requests, and :data:`~flask.g` should be used + #: instead. + #: + #: .. versionadded:: 2.2 + init_every_request: t.ClassVar[bool] = True + + def dispatch_request(self) -> ft.ResponseReturnValue: + """The actual view function behavior. Subclasses must override + this and return a valid response. Any variables from the URL + rule are passed as keyword arguments. + """ + raise NotImplementedError() + + @classmethod + def as_view( + cls, name: str, *class_args: t.Any, **class_kwargs: t.Any + ) -> ft.RouteCallable: + """Convert the class into a view function that can be registered + for a route. + + By default, the generated view will create a new instance of the + view class for every request and call its + :meth:`dispatch_request` method. If the view class sets + :attr:`init_every_request` to ``False``, the same instance will + be used for every request. + + Except for ``name``, all other arguments passed to this method + are forwarded to the view class ``__init__`` method. + + .. versionchanged:: 2.2 + Added the ``init_every_request`` class attribute. + """ + if cls.init_every_request: + + def view(**kwargs: t.Any) -> ft.ResponseReturnValue: + self = view.view_class( # type: ignore[attr-defined] + *class_args, **class_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) # type: ignore[no-any-return] + + if cls.decorators: + view.__name__ = name + view.__module__ = cls.__module__ + for decorator in cls.decorators: + view = decorator(view) + + # We attach the view class to the view function for two reasons: + # first of all it allows us to easily figure out what class-based + # view this thing came from, secondly it's also used for instantiating + # the view class so you can actually replace it with something else + # for testing purposes and debugging. + view.view_class = cls # type: ignore + view.__name__ = name + view.__doc__ = cls.__doc__ + view.__module__ = cls.__module__ + view.methods = cls.methods # type: ignore + view.provide_automatic_options = cls.provide_automatic_options # type: ignore + return view + + +class MethodView(View): + """Dispatches request methods to the corresponding instance methods. + For example, if you implement a ``get`` method, it will be used to + handle ``GET`` requests. + + This can be useful for defining a REST API. + + :attr:`methods` is automatically set based on the methods defined on + the class. + + See :doc:`views` for a detailed guide. + + .. code-block:: python + + class CounterAPI(MethodView): + def get(self): + return str(session.get("counter", 0)) + + def post(self): + session["counter"] = session.get("counter", 0) + 1 + return redirect(url_for("counter")) + + app.add_url_rule( + "/counter", view_func=CounterAPI.as_view("counter") + ) + """ + + def __init_subclass__(cls, **kwargs: t.Any) -> None: + super().__init_subclass__(**kwargs) + + if "methods" not in cls.__dict__: + methods = set() + + for base in cls.__bases__: + if getattr(base, "methods", None): + methods.update(base.methods) # type: ignore[attr-defined] + + for key in http_method_funcs: + if hasattr(cls, key): + methods.add(key.upper()) + + if methods: + cls.methods = methods + + def dispatch_request(self, **kwargs: t.Any) -> ft.ResponseReturnValue: + meth = getattr(self, request.method.lower(), None) + + # If the request method is HEAD and we don't have a handler for it + # retry with GET. + if meth is None and request.method == "HEAD": + meth = getattr(self, "get", None) + + assert meth is not None, f"Unimplemented method {request.method!r}" + 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 new file mode 100644 index 00000000..c1eca807 --- /dev/null +++ b/venv/Lib/site-packages/flask/wrappers.py @@ -0,0 +1,174 @@ +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 + +from . import json +from .globals import current_app +from .helpers import _split_blueprint_path + +if t.TYPE_CHECKING: # pragma: no cover + from werkzeug.routing import Rule + + +class Request(RequestBase): + """The request object used by default in Flask. Remembers the + matched endpoint and view arguments. + + It is what ends up as :class:`~flask.request`. If you want to replace + the request object used you can subclass this and set + :attr:`~flask.Flask.request_class` to your subclass. + + The request object is a :class:`~werkzeug.wrappers.Request` subclass and + provides all of the attributes Werkzeug defines plus a few Flask + specific ones. + """ + + json_module: t.Any = json + + #: The internal URL rule that matched the request. This can be + #: useful to inspect which methods are allowed for the URL from + #: a before/after handler (``request.url_rule.methods``) etc. + #: Though if the request's method was invalid for the URL rule, + #: the valid list is available in ``routing_exception.valid_methods`` + #: instead (an attribute of the Werkzeug exception + #: :exc:`~werkzeug.exceptions.MethodNotAllowed`) + #: because the request was never internally bound. + #: + #: .. versionadded:: 0.6 + url_rule: Rule | None = None + + #: A dict of view arguments that matched the request. If an exception + #: happened when matching, this will be ``None``. + view_args: dict[str, t.Any] | None = None + + #: If matching the URL failed, this is the exception that will be + #: raised / was raised as part of the request handling. This is + #: usually a :exc:`~werkzeug.exceptions.NotFound` exception or + #: something similar. + routing_exception: HTTPException | None = None + + @property + 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"] # type: ignore[no-any-return] + else: + return None + + @property + def endpoint(self) -> str | None: + """The endpoint that matched the request URL. + + This will be ``None`` if matching failed or has not been + performed yet. + + This in combination with :attr:`view_args` can be used to + reconstruct the same URL or a modified URL. + """ + if self.url_rule is not None: + return self.url_rule.endpoint + + return None + + @property + def blueprint(self) -> str | None: + """The registered name of the current blueprint. + + This will be ``None`` if the endpoint is not part of a + blueprint, or if URL matching failed or has not been performed + yet. + + This does not necessarily match the name the blueprint was + created with. It may have been nested, or registered with a + different name. + """ + endpoint = self.endpoint + + if endpoint is not None and "." in endpoint: + return endpoint.rpartition(".")[0] + + return None + + @property + def blueprints(self) -> list[str]: + """The registered names of the current blueprint upwards through + parent blueprints. + + This will be an empty list if there is no current blueprint, or + if URL matching failed. + + .. versionadded:: 2.0.1 + """ + name = self.blueprint + + if name is None: + return [] + + return _split_blueprint_path(name) + + def _load_form_data(self) -> None: + super()._load_form_data() + + # In debug mode we're replacing the files multidict with an ad-hoc + # subclass that raises a different error for key errors. + if ( + current_app + and current_app.debug + and self.mimetype != "multipart/form-data" + and not self.files + ): + from .debughelpers import attach_enctype_error_multidict + + attach_enctype_error_multidict(self) + + def on_json_loading_failed(self, e: ValueError | None) -> t.Any: + try: + return super().on_json_loading_failed(e) + except BadRequest as e: + if current_app and current_app.debug: + raise + + raise BadRequest() from e + + +class Response(ResponseBase): + """The response object that is used by default in Flask. Works like the + response object from Werkzeug but is set to have an HTML mimetype by + default. Quite often you don't have to create this object yourself because + :meth:`~flask.Flask.make_response` will take care of that for you. + + If you want to replace the response object used you can subclass this and + set :attr:`~flask.Flask.response_class` to your subclass. + + .. versionchanged:: 1.0 + JSON support is added to the response, like the request. This is useful + when testing to get the test client response data as JSON. + + .. versionchanged:: 1.0 + + Added :attr:`max_cookie_size`. + """ + + default_mimetype: str | None = "text/html" + + json_module = json + + autocorrect_location_header = False + + @property + def max_cookie_size(self) -> int: # type: ignore + """Read-only view of the :data:`MAX_COOKIE_SIZE` config key. + + See :attr:`~werkzeug.wrappers.Response.max_cookie_size` in + Werkzeug's docs. + """ + if current_app: + 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/itsdangerous-2.2.0.dist-info/INSTALLER b/venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/INSTALLER new file mode 100644 index 00000000..a1b589e3 --- /dev/null +++ b/venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/LICENSE.txt b/venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/LICENSE.txt new file mode 100644 index 00000000..7b190ca6 --- /dev/null +++ b/venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/LICENSE.txt @@ -0,0 +1,28 @@ +Copyright 2011 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/itsdangerous-2.2.0.dist-info/METADATA b/venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/METADATA new file mode 100644 index 00000000..ddf54648 --- /dev/null +++ b/venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/METADATA @@ -0,0 +1,60 @@ +Metadata-Version: 2.1 +Name: itsdangerous +Version: 2.2.0 +Summary: Safely pass data to untrusted environments and back. +Maintainer-email: Pallets +Requires-Python: >=3.8 +Description-Content-Type: text/markdown +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Typing :: Typed +Project-URL: Changes, https://itsdangerous.palletsprojects.com/changes/ +Project-URL: Chat, https://discord.gg/pallets +Project-URL: Documentation, https://itsdangerous.palletsprojects.com/ +Project-URL: Donate, https://palletsprojects.com/donate +Project-URL: Source, https://github.com/pallets/itsdangerous/ + +# ItsDangerous + +... so better sign this + +Various helpers to pass data to untrusted environments and to get it +back safe and sound. Data is cryptographically signed to ensure that a +token has not been tampered with. + +It's possible to customize how data is serialized. Data is compressed as +needed. A timestamp can be added and verified automatically while +loading a token. + + +## A Simple Example + +Here's how you could generate a token for transmitting a user's id and +name between web requests. + +```python +from itsdangerous import URLSafeSerializer +auth_s = URLSafeSerializer("secret key", "auth") +token = auth_s.dumps({"id": 5, "name": "itsdangerous"}) + +print(token) +# eyJpZCI6NSwibmFtZSI6Iml0c2Rhbmdlcm91cyJ9.6YP6T0BaO67XP--9UzTrmurXSmg + +data = auth_s.loads(token) +print(data["name"]) +# itsdangerous +``` + + +## Donate + +The Pallets organization develops and supports ItsDangerous 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 + diff --git a/venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/RECORD b/venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/RECORD new file mode 100644 index 00000000..13948767 --- /dev/null +++ b/venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/RECORD @@ -0,0 +1,22 @@ +itsdangerous-2.2.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +itsdangerous-2.2.0.dist-info/LICENSE.txt,sha256=Y68JiRtr6K0aQlLtQ68PTvun_JSOIoNnvtfzxa4LCdc,1475 +itsdangerous-2.2.0.dist-info/METADATA,sha256=0rk0-1ZwihuU5DnwJVwPWoEI4yWOyCexih3JyZHblhE,1924 +itsdangerous-2.2.0.dist-info/RECORD,, +itsdangerous-2.2.0.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81 +itsdangerous/__init__.py,sha256=4SK75sCe29xbRgQE1ZQtMHnKUuZYAf3bSpZOrff1IAY,1427 +itsdangerous/__pycache__/__init__.cpython-312.pyc,, +itsdangerous/__pycache__/_json.cpython-312.pyc,, +itsdangerous/__pycache__/encoding.cpython-312.pyc,, +itsdangerous/__pycache__/exc.cpython-312.pyc,, +itsdangerous/__pycache__/serializer.cpython-312.pyc,, +itsdangerous/__pycache__/signer.cpython-312.pyc,, +itsdangerous/__pycache__/timed.cpython-312.pyc,, +itsdangerous/__pycache__/url_safe.cpython-312.pyc,, +itsdangerous/_json.py,sha256=wPQGmge2yZ9328EHKF6gadGeyGYCJQKxtU-iLKE6UnA,473 +itsdangerous/encoding.py,sha256=wwTz5q_3zLcaAdunk6_vSoStwGqYWe307Zl_U87aRFM,1409 +itsdangerous/exc.py,sha256=Rr3exo0MRFEcPZltwecyK16VV1bE2K9_F1-d-ljcUn4,3201 +itsdangerous/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +itsdangerous/serializer.py,sha256=PmdwADLqkSyQLZ0jOKAgDsAW4k_H0TlA71Ei3z0C5aI,15601 +itsdangerous/signer.py,sha256=YO0CV7NBvHA6j549REHJFUjUojw2pHqwcUpQnU7yNYQ,9647 +itsdangerous/timed.py,sha256=6RvDMqNumGMxf0-HlpaZdN9PUQQmRvrQGplKhxuivUs,8083 +itsdangerous/url_safe.py,sha256=az4e5fXi_vs-YbWj8YZwn4wiVKfeD--GEKRT5Ueu4P4,2505 diff --git a/venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/WHEEL b/venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/WHEEL new file mode 100644 index 00000000..3b5e64b5 --- /dev/null +++ b/venv/Lib/site-packages/itsdangerous-2.2.0.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: flit 3.9.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/venv/Lib/site-packages/itsdangerous/__init__.py b/venv/Lib/site-packages/itsdangerous/__init__.py new file mode 100644 index 00000000..ea55256e --- /dev/null +++ b/venv/Lib/site-packages/itsdangerous/__init__.py @@ -0,0 +1,38 @@ +from __future__ import annotations + +import typing as t + +from .encoding import base64_decode as base64_decode +from .encoding import base64_encode as base64_encode +from .encoding import want_bytes as want_bytes +from .exc import BadData as BadData +from .exc import BadHeader as BadHeader +from .exc import BadPayload as BadPayload +from .exc import BadSignature as BadSignature +from .exc import BadTimeSignature as BadTimeSignature +from .exc import SignatureExpired as SignatureExpired +from .serializer import Serializer as Serializer +from .signer import HMACAlgorithm as HMACAlgorithm +from .signer import NoneAlgorithm as NoneAlgorithm +from .signer import Signer as Signer +from .timed import TimedSerializer as TimedSerializer +from .timed import TimestampSigner as TimestampSigner +from .url_safe import URLSafeSerializer as URLSafeSerializer +from .url_safe import URLSafeTimedSerializer as URLSafeTimedSerializer + + +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" + " ItsDangerous 2.3. Use feature detection or" + " 'importlib.metadata.version(\"itsdangerous\")' instead.", + DeprecationWarning, + stacklevel=2, + ) + return importlib.metadata.version("itsdangerous") + + raise AttributeError(name) diff --git a/venv/Lib/site-packages/itsdangerous/__pycache__/__init__.cpython-312.pyc b/venv/Lib/site-packages/itsdangerous/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..82f538d52a9c4fcda48c3ed23c9fc2baa09127f8 GIT binary patch literal 1665 zcmZXU&u<$=6vt=%W4&H`ZO8dtqIMGs=^kQGTaXGBC?tg)%opfYQKs)*e;2|Q2XaNc}SJNbCf{dFbS&|EKVcyMymL+*$-PINA zzy-IUxDXg&(Jd-AfKAs_Tm&w;CB-Ii*)1zB0b8!6xC~rzD~he48rIyJ;z}?U*4?_| zD)6{FuDAv~;Z7(X3ns%UcS>;`c-oy-JRZ!1v+k_o3E*??Ip8TW`CZO^tDkYE59HDv zEh*tBiUk&a9PzaS?L@R%n9~ohdW5!PLLn*@Xfzbe9UKX-)fbe*bm22hR_vF zD2tGG^a$V~+JpAFcL^c(G06}VpnXl9P52fk@%_*!UCC}TYjKdy#9+OSK`}3VmYA=q+i#8U5`K_Ly zw$E)syNtH6pv1-zv3L9+uv^q-G>o@F^CSC9!B=tAp)Brk`*Pz-!@kR@y+PIP!t;W* zWmDNPvlsoa8#593twu-%CeSI3VLg`?e8EYYXTe#7)LcOSG!A5)a`$1g+V=vwO@pL7 z8tfF3`u`Ir#tvqYA9Z+=la$m~hf9HCzB!pnjI@lTFsvh~ET_xA!C1_kY?6y`NR#5} zE7%+q#N{5@w*C6S%12FDFyqZ`kJF$j;+=?Z`dyEQIPh9M?njjKQ+cVqiA5_G{|PcK zSV{wzKN%o^RR+yv(KyZ zPpk9KtIpG^^GoAd_1Y8T+F$0(qlstc`6s#aN01gdIko_|B+CU$bVlJMN4t5^SdRM6 zTw;3OMo%7YC`W#$L>K)oY(=7p#8AHuaF{#^Og26%s8sZRo5^lxMQP;En#o3IWocwf zLkpb#09Zu|I#(y0Rb@y`nlUNog*@)8E`@O^7(Etv9F8`dklLg)@;m*C*t?Q1f_ZIR z4`b2`=qKz7xbigd6)=ZcP16Qw;T5X?jjj&l)fk|+2WWACoB>)Ipbymb{s3K8*Tq!1 OK0u!z>lN*Sdda_9+0?24 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/itsdangerous/__pycache__/_json.cpython-312.pyc b/venv/Lib/site-packages/itsdangerous/__pycache__/_json.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0af1ffbe76af0b4340399ac45d23b7fb0401986e GIT binary patch literal 1219 zcmZ`&&1=*^6rai0#X4`DDNt~IqY+JC0 zUcBhBJ$mq<^iT29VnG`f1ig5x?LpX+Z<5`u3J&DWZ{F9u_nVh@$B*|RSgo56>w6fX zZ|cxpMh@`dRR9}^B8mgl#AS>X)&i}pXS-20HFOtIeH~F_OUt9mR!eiuAL`)Zw4a1w zB#HE+P?T!9M=tez>t4c88B9U-|N3ukHGmrOtyE7qX(-cl98hYu$K zY#`aQH9D>JJOQktt&aT>^jUJX)b!MasTo9s3TV9$lU9b6pVNMJd#3VOFfJ-_B3Mw7 z(NZYp{n!&t5_r`__#qRbr=F@o#nnh2DU@F@)peXwwnY{$R_^=Nihz!$prZvc$3(@K zf|78K0S)3(R#h=>aU0559{!WDVpg7-12F_*4SgRRdAag@W%K&`!SQW-{1BArFN#c1 z^ec)M0c@aE+~|>{eDg&y)w29H#as*TiISR#B* z2=%s(!xamB7{osmWicl}FRp=DLtpI^AMDW`dvvq-=KQXG`D5=q`KE8zp8njSaC&FZ zeRToEw(WAD43%7iA5;1JVGNyBr`m!cOfAl2!o#*Hi?_m-Yw?rd!cQwP(nF^Jr8H2llz#}fw#L>BoQ DA3Y{G literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/itsdangerous/__pycache__/encoding.cpython-312.pyc b/venv/Lib/site-packages/itsdangerous/__pycache__/encoding.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d95ed8c2d84d577a0c83d38fba12713b49cd0340 GIT binary patch literal 2719 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/itsdangerous/__pycache__/signer.cpython-312.pyc b/venv/Lib/site-packages/itsdangerous/__pycache__/signer.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..92d53c3801308185c96378b03f9853132d46b83e GIT binary patch literal 11325 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 Ge)uQez^xGg literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/itsdangerous/_json.py b/venv/Lib/site-packages/itsdangerous/_json.py new file mode 100644 index 00000000..fc23feaa --- /dev/null +++ b/venv/Lib/site-packages/itsdangerous/_json.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +import json as _json +import typing as t + + +class _CompactJSON: + """Wrapper around json module that strips whitespace.""" + + @staticmethod + def loads(payload: str | bytes) -> t.Any: + return _json.loads(payload) + + @staticmethod + def dumps(obj: t.Any, **kwargs: t.Any) -> str: + kwargs.setdefault("ensure_ascii", False) + kwargs.setdefault("separators", (",", ":")) + return _json.dumps(obj, **kwargs) diff --git a/venv/Lib/site-packages/itsdangerous/encoding.py b/venv/Lib/site-packages/itsdangerous/encoding.py new file mode 100644 index 00000000..f5ca80f9 --- /dev/null +++ b/venv/Lib/site-packages/itsdangerous/encoding.py @@ -0,0 +1,54 @@ +from __future__ import annotations + +import base64 +import string +import struct +import typing as t + +from .exc import BadData + + +def want_bytes( + s: str | bytes, encoding: str = "utf-8", errors: str = "strict" +) -> bytes: + if isinstance(s, str): + s = s.encode(encoding, errors) + + return s + + +def base64_encode(string: str | bytes) -> bytes: + """Base64 encode a string of bytes or text. The resulting bytes are + safe to use in URLs. + """ + string = want_bytes(string) + return base64.urlsafe_b64encode(string).rstrip(b"=") + + +def base64_decode(string: str | bytes) -> bytes: + """Base64 decode a URL-safe string of bytes or text. The result is + bytes. + """ + string = want_bytes(string, encoding="ascii", errors="ignore") + string += b"=" * (-len(string) % 4) + + try: + return base64.urlsafe_b64decode(string) + except (TypeError, ValueError) as e: + raise BadData("Invalid base64-encoded data") from e + + +# The alphabet used by base64.urlsafe_* +_base64_alphabet = f"{string.ascii_letters}{string.digits}-_=".encode("ascii") + +_int64_struct = struct.Struct(">Q") +_int_to_bytes = _int64_struct.pack +_bytes_to_int = t.cast("t.Callable[[bytes], tuple[int]]", _int64_struct.unpack) + + +def int_to_bytes(num: int) -> bytes: + return _int_to_bytes(num).lstrip(b"\x00") + + +def bytes_to_int(bytestr: bytes) -> int: + return _bytes_to_int(bytestr.rjust(8, b"\x00"))[0] diff --git a/venv/Lib/site-packages/itsdangerous/exc.py b/venv/Lib/site-packages/itsdangerous/exc.py new file mode 100644 index 00000000..a75adcd5 --- /dev/null +++ b/venv/Lib/site-packages/itsdangerous/exc.py @@ -0,0 +1,106 @@ +from __future__ import annotations + +import typing as t +from datetime import datetime + + +class BadData(Exception): + """Raised if bad data of any sort was encountered. This is the base + for all exceptions that ItsDangerous defines. + + .. versionadded:: 0.15 + """ + + def __init__(self, message: str): + super().__init__(message) + self.message = message + + def __str__(self) -> str: + return self.message + + +class BadSignature(BadData): + """Raised if a signature does not match.""" + + def __init__(self, message: str, payload: t.Any | None = None): + super().__init__(message) + + #: The payload that failed the signature test. In some + #: situations you might still want to inspect this, even if + #: you know it was tampered with. + #: + #: .. versionadded:: 0.14 + self.payload: t.Any | None = payload + + +class BadTimeSignature(BadSignature): + """Raised if a time-based signature is invalid. This is a subclass + of :class:`BadSignature`. + """ + + def __init__( + self, + message: str, + payload: t.Any | None = None, + date_signed: datetime | None = None, + ): + super().__init__(message, payload) + + #: If the signature expired this exposes the date of when the + #: signature was created. This can be helpful in order to + #: tell the user how long a link has been gone stale. + #: + #: .. versionchanged:: 2.0 + #: The datetime value is timezone-aware rather than naive. + #: + #: .. versionadded:: 0.14 + self.date_signed = date_signed + + +class SignatureExpired(BadTimeSignature): + """Raised if a signature timestamp is older than ``max_age``. This + is a subclass of :exc:`BadTimeSignature`. + """ + + +class BadHeader(BadSignature): + """Raised if a signed header is invalid in some form. This only + happens for serializers that have a header that goes with the + signature. + + .. versionadded:: 0.24 + """ + + def __init__( + self, + message: str, + payload: t.Any | None = None, + header: t.Any | None = None, + original_error: Exception | None = None, + ): + super().__init__(message, payload) + + #: If the header is actually available but just malformed it + #: might be stored here. + self.header: t.Any | None = header + + #: If available, the error that indicates why the payload was + #: not valid. This might be ``None``. + self.original_error: Exception | None = original_error + + +class BadPayload(BadData): + """Raised if a payload is invalid. This could happen if the payload + is loaded despite an invalid signature, or if there is a mismatch + between the serializer and deserializer. The original exception + that occurred during loading is stored on as :attr:`original_error`. + + .. versionadded:: 0.15 + """ + + def __init__(self, message: str, original_error: Exception | None = None): + super().__init__(message) + + #: If available, the error that indicates why the payload was + #: not valid. This might be ``None``. + self.original_error: Exception | None = original_error diff --git a/venv/Lib/site-packages/itsdangerous/py.typed b/venv/Lib/site-packages/itsdangerous/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/venv/Lib/site-packages/itsdangerous/serializer.py b/venv/Lib/site-packages/itsdangerous/serializer.py new file mode 100644 index 00000000..5ddf3871 --- /dev/null +++ b/venv/Lib/site-packages/itsdangerous/serializer.py @@ -0,0 +1,406 @@ +from __future__ import annotations + +import collections.abc as cabc +import json +import typing as t + +from .encoding import want_bytes +from .exc import BadPayload +from .exc import BadSignature +from .signer import _make_keys_list +from .signer import Signer + +if t.TYPE_CHECKING: + import typing_extensions as te + + # This should be either be str or bytes. To avoid having to specify the + # bound type, it falls back to a union if structural matching fails. + _TSerialized = te.TypeVar( + "_TSerialized", bound=t.Union[str, bytes], default=t.Union[str, bytes] + ) +else: + # Still available at runtime on Python < 3.13, but without the default. + _TSerialized = t.TypeVar("_TSerialized", bound=t.Union[str, bytes]) + + +class _PDataSerializer(t.Protocol[_TSerialized]): + def loads(self, payload: _TSerialized, /) -> t.Any: ... + # A signature with additional arguments is not handled correctly by type + # checkers right now, so an overload is used below for serializers that + # don't match this strict protocol. + def dumps(self, obj: t.Any, /) -> _TSerialized: ... + + +# Use TypeIs once it's available in typing_extensions or 3.13. +def is_text_serializer( + serializer: _PDataSerializer[t.Any], +) -> te.TypeGuard[_PDataSerializer[str]]: + """Checks whether a serializer generates text or binary.""" + return isinstance(serializer.dumps({}), str) + + +class Serializer(t.Generic[_TSerialized]): + """A serializer wraps a :class:`~itsdangerous.signer.Signer` to + enable serializing and securely signing data other than bytes. It + can unsign to verify that the data hasn't been changed. + + The serializer provides :meth:`dumps` and :meth:`loads`, similar to + :mod:`json`, and by default uses :mod:`json` internally to serialize + the data to bytes. + + The secret key should be a random string of ``bytes`` and should not + be saved to code or version control. Different salts should be used + to distinguish signing in different contexts. See :doc:`/concepts` + for information about the security of the secret key and salt. + + :param secret_key: The secret key to sign and verify with. Can be a + list of keys, oldest to newest, to support key rotation. + :param salt: Extra key to combine with ``secret_key`` to distinguish + signatures in different contexts. + :param serializer: An object that provides ``dumps`` and ``loads`` + methods for serializing data to a string. Defaults to + :attr:`default_serializer`, which defaults to :mod:`json`. + :param serializer_kwargs: Keyword arguments to pass when calling + ``serializer.dumps``. + :param signer: A ``Signer`` class to instantiate when signing data. + Defaults to :attr:`default_signer`, which defaults to + :class:`~itsdangerous.signer.Signer`. + :param signer_kwargs: Keyword arguments to pass when instantiating + the ``Signer`` class. + :param fallback_signers: List of signer parameters to try when + unsigning with the default signer fails. Each item can be a dict + of ``signer_kwargs``, a ``Signer`` class, or a tuple of + ``(signer, signer_kwargs)``. Defaults to + :attr:`default_fallback_signers`. + + .. versionchanged:: 2.0 + Added support for key rotation by passing a list to + ``secret_key``. + + .. versionchanged:: 2.0 + Removed the default SHA-512 fallback signer from + ``default_fallback_signers``. + + .. versionchanged:: 1.1 + Added support for ``fallback_signers`` and configured a default + SHA-512 fallback. This fallback is for users who used the yanked + 1.0.0 release which defaulted to SHA-512. + + .. versionchanged:: 0.14 + The ``signer`` and ``signer_kwargs`` parameters were added to + the constructor. + """ + + #: The default serialization module to use to serialize data to a + #: string internally. The default is :mod:`json`, but can be changed + #: to any object that provides ``dumps`` and ``loads`` methods. + default_serializer: _PDataSerializer[t.Any] = json + + #: The default ``Signer`` class to instantiate when signing data. + #: The default is :class:`itsdangerous.signer.Signer`. + default_signer: type[Signer] = Signer + + #: The default fallback signers to try when unsigning fails. + default_fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] = [] + + # Serializer[str] if no data serializer is provided, or if it returns str. + @t.overload + def __init__( + self: Serializer[str], + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None = b"itsdangerous", + serializer: None | _PDataSerializer[str] = None, + serializer_kwargs: dict[str, t.Any] | None = None, + signer: type[Signer] | None = None, + signer_kwargs: dict[str, t.Any] | None = None, + fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] + | None = None, + ): ... + + # Serializer[bytes] with a bytes data serializer positional argument. + @t.overload + def __init__( + self: Serializer[bytes], + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None, + serializer: _PDataSerializer[bytes], + serializer_kwargs: dict[str, t.Any] | None = None, + signer: type[Signer] | None = None, + signer_kwargs: dict[str, t.Any] | None = None, + fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] + | None = None, + ): ... + + # Serializer[bytes] with a bytes data serializer keyword argument. + @t.overload + def __init__( + self: Serializer[bytes], + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None = b"itsdangerous", + *, + serializer: _PDataSerializer[bytes], + serializer_kwargs: dict[str, t.Any] | None = None, + signer: type[Signer] | None = None, + signer_kwargs: dict[str, t.Any] | None = None, + fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] + | None = None, + ): ... + + # Fall back with a positional argument. If the strict signature of + # _PDataSerializer doesn't match, fall back to a union, requiring the user + # to specify the type. + @t.overload + def __init__( + self, + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None, + serializer: t.Any, + serializer_kwargs: dict[str, t.Any] | None = None, + signer: type[Signer] | None = None, + signer_kwargs: dict[str, t.Any] | None = None, + fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] + | None = None, + ): ... + + # Fall back with a keyword argument. + @t.overload + def __init__( + self, + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None = b"itsdangerous", + *, + serializer: t.Any, + serializer_kwargs: dict[str, t.Any] | None = None, + signer: type[Signer] | None = None, + signer_kwargs: dict[str, t.Any] | None = None, + fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] + | None = None, + ): ... + + def __init__( + self, + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None = b"itsdangerous", + serializer: t.Any | None = None, + serializer_kwargs: dict[str, t.Any] | None = None, + signer: type[Signer] | None = None, + signer_kwargs: dict[str, t.Any] | None = None, + fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] + | None = None, + ): + #: The list of secret keys to try for verifying signatures, from + #: oldest to newest. The newest (last) key is used for signing. + #: + #: This allows a key rotation system to keep a list of allowed + #: keys and remove expired ones. + self.secret_keys: list[bytes] = _make_keys_list(secret_key) + + if salt is not None: + salt = want_bytes(salt) + # if salt is None then the signer's default is used + + self.salt = salt + + if serializer is None: + serializer = self.default_serializer + + self.serializer: _PDataSerializer[_TSerialized] = serializer + self.is_text_serializer: bool = is_text_serializer(serializer) + + if signer is None: + signer = self.default_signer + + self.signer: type[Signer] = signer + self.signer_kwargs: dict[str, t.Any] = signer_kwargs or {} + + if fallback_signers is None: + fallback_signers = list(self.default_fallback_signers) + + self.fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] = fallback_signers + self.serializer_kwargs: dict[str, t.Any] = serializer_kwargs or {} + + @property + def secret_key(self) -> bytes: + """The newest (last) entry in the :attr:`secret_keys` list. This + is for compatibility from before key rotation support was added. + """ + return self.secret_keys[-1] + + def load_payload( + self, payload: bytes, serializer: _PDataSerializer[t.Any] | None = None + ) -> t.Any: + """Loads the encoded object. This function raises + :class:`.BadPayload` if the payload is not valid. The + ``serializer`` parameter can be used to override the serializer + stored on the class. The encoded ``payload`` should always be + bytes. + """ + if serializer is None: + use_serializer = self.serializer + is_text = self.is_text_serializer + else: + use_serializer = serializer + is_text = is_text_serializer(serializer) + + try: + if is_text: + return use_serializer.loads(payload.decode("utf-8")) # type: ignore[arg-type] + + return use_serializer.loads(payload) # type: ignore[arg-type] + except Exception as e: + raise BadPayload( + "Could not load the payload because an exception" + " occurred on unserializing the data.", + original_error=e, + ) from e + + def dump_payload(self, obj: t.Any) -> bytes: + """Dumps the encoded object. The return value is always bytes. + If the internal serializer returns text, the value will be + encoded as UTF-8. + """ + return want_bytes(self.serializer.dumps(obj, **self.serializer_kwargs)) + + def make_signer(self, salt: str | bytes | None = None) -> Signer: + """Creates a new instance of the signer to be used. The default + implementation uses the :class:`.Signer` base class. + """ + if salt is None: + salt = self.salt + + return self.signer(self.secret_keys, salt=salt, **self.signer_kwargs) + + def iter_unsigners(self, salt: str | bytes | None = None) -> cabc.Iterator[Signer]: + """Iterates over all signers to be tried for unsigning. Starts + with the configured signer, then constructs each signer + specified in ``fallback_signers``. + """ + if salt is None: + salt = self.salt + + yield self.make_signer(salt) + + for fallback in self.fallback_signers: + if isinstance(fallback, dict): + kwargs = fallback + fallback = self.signer + elif isinstance(fallback, tuple): + fallback, kwargs = fallback + else: + kwargs = self.signer_kwargs + + for secret_key in self.secret_keys: + yield fallback(secret_key, salt=salt, **kwargs) + + def dumps(self, obj: t.Any, salt: str | bytes | None = None) -> _TSerialized: + """Returns a signed string serialized with the internal + serializer. The return value can be either a byte or unicode + string depending on the format of the internal serializer. + """ + payload = want_bytes(self.dump_payload(obj)) + rv = self.make_signer(salt).sign(payload) + + if self.is_text_serializer: + return rv.decode("utf-8") # type: ignore[return-value] + + return rv # type: ignore[return-value] + + def dump(self, obj: t.Any, f: t.IO[t.Any], salt: str | bytes | None = None) -> None: + """Like :meth:`dumps` but dumps into a file. The file handle has + to be compatible with what the internal serializer expects. + """ + f.write(self.dumps(obj, salt)) + + def loads( + self, s: str | bytes, salt: str | bytes | None = None, **kwargs: t.Any + ) -> t.Any: + """Reverse of :meth:`dumps`. Raises :exc:`.BadSignature` if the + signature validation fails. + """ + s = want_bytes(s) + last_exception = None + + for signer in self.iter_unsigners(salt): + try: + return self.load_payload(signer.unsign(s)) + except BadSignature as err: + last_exception = err + + raise t.cast(BadSignature, last_exception) + + def load(self, f: t.IO[t.Any], salt: str | bytes | None = None) -> t.Any: + """Like :meth:`loads` but loads from a file.""" + return self.loads(f.read(), salt) + + def loads_unsafe( + self, s: str | bytes, salt: str | bytes | None = None + ) -> tuple[bool, t.Any]: + """Like :meth:`loads` but without verifying the signature. This + is potentially very dangerous to use depending on how your + serializer works. The return value is ``(signature_valid, + payload)`` instead of just the payload. The first item will be a + boolean that indicates if the signature is valid. This function + never fails. + + Use it for debugging only and if you know that your serializer + module is not exploitable (for example, do not use it with a + pickle serializer). + + .. versionadded:: 0.15 + """ + return self._loads_unsafe_impl(s, salt) + + def _loads_unsafe_impl( + self, + s: str | bytes, + salt: str | bytes | None, + load_kwargs: dict[str, t.Any] | None = None, + load_payload_kwargs: dict[str, t.Any] | None = None, + ) -> tuple[bool, t.Any]: + """Low level helper function to implement :meth:`loads_unsafe` + in serializer subclasses. + """ + if load_kwargs is None: + load_kwargs = {} + + try: + return True, self.loads(s, salt=salt, **load_kwargs) + except BadSignature as e: + if e.payload is None: + return False, None + + if load_payload_kwargs is None: + load_payload_kwargs = {} + + try: + return ( + False, + self.load_payload(e.payload, **load_payload_kwargs), + ) + except BadPayload: + return False, None + + def load_unsafe( + self, f: t.IO[t.Any], salt: str | bytes | None = None + ) -> tuple[bool, t.Any]: + """Like :meth:`loads_unsafe` but loads from a file. + + .. versionadded:: 0.15 + """ + return self.loads_unsafe(f.read(), salt=salt) diff --git a/venv/Lib/site-packages/itsdangerous/signer.py b/venv/Lib/site-packages/itsdangerous/signer.py new file mode 100644 index 00000000..e324dc03 --- /dev/null +++ b/venv/Lib/site-packages/itsdangerous/signer.py @@ -0,0 +1,266 @@ +from __future__ import annotations + +import collections.abc as cabc +import hashlib +import hmac +import typing as t + +from .encoding import _base64_alphabet +from .encoding import base64_decode +from .encoding import base64_encode +from .encoding import want_bytes +from .exc import BadSignature + + +class SigningAlgorithm: + """Subclasses must implement :meth:`get_signature` to provide + signature generation functionality. + """ + + def get_signature(self, key: bytes, value: bytes) -> bytes: + """Returns the signature for the given key and value.""" + raise NotImplementedError() + + def verify_signature(self, key: bytes, value: bytes, sig: bytes) -> bool: + """Verifies the given signature matches the expected + signature. + """ + return hmac.compare_digest(sig, self.get_signature(key, value)) + + +class NoneAlgorithm(SigningAlgorithm): + """Provides an algorithm that does not perform any signing and + returns an empty signature. + """ + + def get_signature(self, key: bytes, value: bytes) -> bytes: + return b"" + + +def _lazy_sha1(string: bytes = b"") -> t.Any: + """Don't access ``hashlib.sha1`` until runtime. FIPS builds may not include + SHA-1, in which case the import and use as a default would fail before the + developer can configure something else. + """ + return hashlib.sha1(string) + + +class HMACAlgorithm(SigningAlgorithm): + """Provides signature generation using HMACs.""" + + #: The digest method to use with the MAC algorithm. This defaults to + #: SHA1, but can be changed to any other function in the hashlib + #: module. + default_digest_method: t.Any = staticmethod(_lazy_sha1) + + def __init__(self, digest_method: t.Any = None): + if digest_method is None: + digest_method = self.default_digest_method + + self.digest_method: t.Any = digest_method + + def get_signature(self, key: bytes, value: bytes) -> bytes: + mac = hmac.new(key, msg=value, digestmod=self.digest_method) + return mac.digest() + + +def _make_keys_list( + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], +) -> list[bytes]: + if isinstance(secret_key, (str, bytes)): + return [want_bytes(secret_key)] + + return [want_bytes(s) for s in secret_key] # pyright: ignore + + +class Signer: + """A signer securely signs bytes, then unsigns them to verify that + the value hasn't been changed. + + The secret key should be a random string of ``bytes`` and should not + be saved to code or version control. Different salts should be used + to distinguish signing in different contexts. See :doc:`/concepts` + for information about the security of the secret key and salt. + + :param secret_key: The secret key to sign and verify with. Can be a + list of keys, oldest to newest, to support key rotation. + :param salt: Extra key to combine with ``secret_key`` to distinguish + signatures in different contexts. + :param sep: Separator between the signature and value. + :param key_derivation: How to derive the signing key from the secret + key and salt. Possible values are ``concat``, ``django-concat``, + or ``hmac``. Defaults to :attr:`default_key_derivation`, which + defaults to ``django-concat``. + :param digest_method: Hash function to use when generating the HMAC + signature. Defaults to :attr:`default_digest_method`, which + defaults to :func:`hashlib.sha1`. Note that the security of the + hash alone doesn't apply when used intermediately in HMAC. + :param algorithm: A :class:`SigningAlgorithm` instance to use + instead of building a default :class:`HMACAlgorithm` with the + ``digest_method``. + + .. versionchanged:: 2.0 + Added support for key rotation by passing a list to + ``secret_key``. + + .. versionchanged:: 0.18 + ``algorithm`` was added as an argument to the class constructor. + + .. versionchanged:: 0.14 + ``key_derivation`` and ``digest_method`` were added as arguments + to the class constructor. + """ + + #: The default digest method to use for the signer. The default is + #: :func:`hashlib.sha1`, but can be changed to any :mod:`hashlib` or + #: compatible object. Note that the security of the hash alone + #: doesn't apply when used intermediately in HMAC. + #: + #: .. versionadded:: 0.14 + default_digest_method: t.Any = staticmethod(_lazy_sha1) + + #: The default scheme to use to derive the signing key from the + #: secret key and salt. The default is ``django-concat``. Possible + #: values are ``concat``, ``django-concat``, and ``hmac``. + #: + #: .. versionadded:: 0.14 + default_key_derivation: str = "django-concat" + + def __init__( + self, + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None = b"itsdangerous.Signer", + sep: str | bytes = b".", + key_derivation: str | None = None, + digest_method: t.Any | None = None, + algorithm: SigningAlgorithm | None = None, + ): + #: The list of secret keys to try for verifying signatures, from + #: oldest to newest. The newest (last) key is used for signing. + #: + #: This allows a key rotation system to keep a list of allowed + #: keys and remove expired ones. + self.secret_keys: list[bytes] = _make_keys_list(secret_key) + self.sep: bytes = want_bytes(sep) + + if self.sep in _base64_alphabet: + raise ValueError( + "The given separator cannot be used because it may be" + " contained in the signature itself. ASCII letters," + " digits, and '-_=' must not be used." + ) + + if salt is not None: + salt = want_bytes(salt) + else: + salt = b"itsdangerous.Signer" + + self.salt = salt + + if key_derivation is None: + key_derivation = self.default_key_derivation + + self.key_derivation: str = key_derivation + + if digest_method is None: + digest_method = self.default_digest_method + + self.digest_method: t.Any = digest_method + + if algorithm is None: + algorithm = HMACAlgorithm(self.digest_method) + + self.algorithm: SigningAlgorithm = algorithm + + @property + def secret_key(self) -> bytes: + """The newest (last) entry in the :attr:`secret_keys` list. This + is for compatibility from before key rotation support was added. + """ + return self.secret_keys[-1] + + def derive_key(self, secret_key: str | bytes | None = None) -> bytes: + """This method is called to derive the key. The default key + derivation choices can be overridden here. Key derivation is not + intended to be used as a security method to make a complex key + out of a short password. Instead you should use large random + secret keys. + + :param secret_key: A specific secret key to derive from. + Defaults to the last item in :attr:`secret_keys`. + + .. versionchanged:: 2.0 + Added the ``secret_key`` parameter. + """ + if secret_key is None: + secret_key = self.secret_keys[-1] + else: + secret_key = want_bytes(secret_key) + + if self.key_derivation == "concat": + return t.cast(bytes, self.digest_method(self.salt + secret_key).digest()) + elif self.key_derivation == "django-concat": + return t.cast( + bytes, self.digest_method(self.salt + b"signer" + secret_key).digest() + ) + elif self.key_derivation == "hmac": + mac = hmac.new(secret_key, digestmod=self.digest_method) + mac.update(self.salt) + return mac.digest() + elif self.key_derivation == "none": + return secret_key + else: + raise TypeError("Unknown key derivation method") + + def get_signature(self, value: str | bytes) -> bytes: + """Returns the signature for the given value.""" + value = want_bytes(value) + key = self.derive_key() + sig = self.algorithm.get_signature(key, value) + return base64_encode(sig) + + def sign(self, value: str | bytes) -> bytes: + """Signs the given string.""" + value = want_bytes(value) + return value + self.sep + self.get_signature(value) + + def verify_signature(self, value: str | bytes, sig: str | bytes) -> bool: + """Verifies the signature for the given value.""" + try: + sig = base64_decode(sig) + except Exception: + return False + + value = want_bytes(value) + + for secret_key in reversed(self.secret_keys): + key = self.derive_key(secret_key) + + if self.algorithm.verify_signature(key, value, sig): + return True + + return False + + def unsign(self, signed_value: str | bytes) -> bytes: + """Unsigns the given string.""" + signed_value = want_bytes(signed_value) + + if self.sep not in signed_value: + raise BadSignature(f"No {self.sep!r} found in value") + + value, sig = signed_value.rsplit(self.sep, 1) + + if self.verify_signature(value, sig): + return value + + raise BadSignature(f"Signature {sig!r} does not match", payload=value) + + def validate(self, signed_value: str | bytes) -> bool: + """Only validates the given signed value. Returns ``True`` if + the signature exists and is valid. + """ + try: + self.unsign(signed_value) + return True + except BadSignature: + return False diff --git a/venv/Lib/site-packages/itsdangerous/timed.py b/venv/Lib/site-packages/itsdangerous/timed.py new file mode 100644 index 00000000..73843755 --- /dev/null +++ b/venv/Lib/site-packages/itsdangerous/timed.py @@ -0,0 +1,228 @@ +from __future__ import annotations + +import collections.abc as cabc +import time +import typing as t +from datetime import datetime +from datetime import timezone + +from .encoding import base64_decode +from .encoding import base64_encode +from .encoding import bytes_to_int +from .encoding import int_to_bytes +from .encoding import want_bytes +from .exc import BadSignature +from .exc import BadTimeSignature +from .exc import SignatureExpired +from .serializer import _TSerialized +from .serializer import Serializer +from .signer import Signer + + +class TimestampSigner(Signer): + """Works like the regular :class:`.Signer` but also records the time + of the signing and can be used to expire signatures. The + :meth:`unsign` method can raise :exc:`.SignatureExpired` if the + unsigning failed because the signature is expired. + """ + + def get_timestamp(self) -> int: + """Returns the current timestamp. The function must return an + integer. + """ + return int(time.time()) + + def timestamp_to_datetime(self, ts: int) -> datetime: + """Convert the timestamp from :meth:`get_timestamp` into an + aware :class`datetime.datetime` in UTC. + + .. versionchanged:: 2.0 + The timestamp is returned as a timezone-aware ``datetime`` + in UTC rather than a naive ``datetime`` assumed to be UTC. + """ + return datetime.fromtimestamp(ts, tz=timezone.utc) + + def sign(self, value: str | bytes) -> bytes: + """Signs the given string and also attaches time information.""" + value = want_bytes(value) + timestamp = base64_encode(int_to_bytes(self.get_timestamp())) + sep = want_bytes(self.sep) + value = value + sep + timestamp + return value + sep + self.get_signature(value) + + # Ignore overlapping signatures check, return_timestamp is the only + # parameter that affects the return type. + + @t.overload + def unsign( # type: ignore[overload-overlap] + self, + signed_value: str | bytes, + max_age: int | None = None, + return_timestamp: t.Literal[False] = False, + ) -> bytes: ... + + @t.overload + def unsign( + self, + signed_value: str | bytes, + max_age: int | None = None, + return_timestamp: t.Literal[True] = True, + ) -> tuple[bytes, datetime]: ... + + def unsign( + self, + signed_value: str | bytes, + max_age: int | None = None, + return_timestamp: bool = False, + ) -> tuple[bytes, datetime] | bytes: + """Works like the regular :meth:`.Signer.unsign` but can also + validate the time. See the base docstring of the class for + the general behavior. If ``return_timestamp`` is ``True`` the + timestamp of the signature will be returned as an aware + :class:`datetime.datetime` object in UTC. + + .. versionchanged:: 2.0 + The timestamp is returned as a timezone-aware ``datetime`` + in UTC rather than a naive ``datetime`` assumed to be UTC. + """ + try: + result = super().unsign(signed_value) + sig_error = None + except BadSignature as e: + sig_error = e + result = e.payload or b"" + + sep = want_bytes(self.sep) + + # If there is no timestamp in the result there is something + # seriously wrong. In case there was a signature error, we raise + # that one directly, otherwise we have a weird situation in + # which we shouldn't have come except someone uses a time-based + # serializer on non-timestamp data, so catch that. + if sep not in result: + if sig_error: + raise sig_error + + raise BadTimeSignature("timestamp missing", payload=result) + + value, ts_bytes = result.rsplit(sep, 1) + ts_int: int | None = None + ts_dt: datetime | None = None + + try: + ts_int = bytes_to_int(base64_decode(ts_bytes)) + except Exception: + pass + + # Signature is *not* okay. Raise a proper error now that we have + # split the value and the timestamp. + if sig_error is not None: + if ts_int is not None: + try: + ts_dt = self.timestamp_to_datetime(ts_int) + except (ValueError, OSError, OverflowError) as exc: + # Windows raises OSError + # 32-bit raises OverflowError + raise BadTimeSignature( + "Malformed timestamp", payload=value + ) from exc + + raise BadTimeSignature(str(sig_error), payload=value, date_signed=ts_dt) + + # Signature was okay but the timestamp is actually not there or + # malformed. Should not happen, but we handle it anyway. + if ts_int is None: + raise BadTimeSignature("Malformed timestamp", payload=value) + + # Check timestamp is not older than max_age + if max_age is not None: + age = self.get_timestamp() - ts_int + + if age > max_age: + raise SignatureExpired( + f"Signature age {age} > {max_age} seconds", + payload=value, + date_signed=self.timestamp_to_datetime(ts_int), + ) + + if age < 0: + raise SignatureExpired( + f"Signature age {age} < 0 seconds", + payload=value, + date_signed=self.timestamp_to_datetime(ts_int), + ) + + if return_timestamp: + return value, self.timestamp_to_datetime(ts_int) + + return value + + def validate(self, signed_value: str | bytes, max_age: int | None = None) -> bool: + """Only validates the given signed value. Returns ``True`` if + the signature exists and is valid.""" + try: + self.unsign(signed_value, max_age=max_age) + return True + except BadSignature: + return False + + +class TimedSerializer(Serializer[_TSerialized]): + """Uses :class:`TimestampSigner` instead of the default + :class:`.Signer`. + """ + + default_signer: type[TimestampSigner] = TimestampSigner + + def iter_unsigners( + self, salt: str | bytes | None = None + ) -> cabc.Iterator[TimestampSigner]: + return t.cast("cabc.Iterator[TimestampSigner]", super().iter_unsigners(salt)) + + # TODO: Signature is incompatible because parameters were added + # before salt. + + def loads( # type: ignore[override] + self, + s: str | bytes, + max_age: int | None = None, + return_timestamp: bool = False, + salt: str | bytes | None = None, + ) -> t.Any: + """Reverse of :meth:`dumps`, raises :exc:`.BadSignature` if the + signature validation fails. If a ``max_age`` is provided it will + ensure the signature is not older than that time in seconds. In + case the signature is outdated, :exc:`.SignatureExpired` is + raised. All arguments are forwarded to the signer's + :meth:`~TimestampSigner.unsign` method. + """ + s = want_bytes(s) + last_exception = None + + for signer in self.iter_unsigners(salt): + try: + base64d, timestamp = signer.unsign( + s, max_age=max_age, return_timestamp=True + ) + payload = self.load_payload(base64d) + + if return_timestamp: + return payload, timestamp + + return payload + except SignatureExpired: + # The signature was unsigned successfully but was + # expired. Do not try the next signer. + raise + except BadSignature as err: + last_exception = err + + raise t.cast(BadSignature, last_exception) + + def loads_unsafe( # type: ignore[override] + self, + s: str | bytes, + max_age: int | None = None, + salt: str | bytes | None = None, + ) -> tuple[bool, t.Any]: + return self._loads_unsafe_impl(s, salt, load_kwargs={"max_age": max_age}) diff --git a/venv/Lib/site-packages/itsdangerous/url_safe.py b/venv/Lib/site-packages/itsdangerous/url_safe.py new file mode 100644 index 00000000..56a07933 --- /dev/null +++ b/venv/Lib/site-packages/itsdangerous/url_safe.py @@ -0,0 +1,83 @@ +from __future__ import annotations + +import typing as t +import zlib + +from ._json import _CompactJSON +from .encoding import base64_decode +from .encoding import base64_encode +from .exc import BadPayload +from .serializer import _PDataSerializer +from .serializer import Serializer +from .timed import TimedSerializer + + +class URLSafeSerializerMixin(Serializer[str]): + """Mixed in with a regular serializer it will attempt to zlib + compress the string to make it shorter if necessary. It will also + base64 encode the string so that it can safely be placed in a URL. + """ + + default_serializer: _PDataSerializer[str] = _CompactJSON + + def load_payload( + self, + payload: bytes, + *args: t.Any, + serializer: t.Any | None = None, + **kwargs: t.Any, + ) -> t.Any: + decompress = False + + if payload.startswith(b"."): + payload = payload[1:] + decompress = True + + try: + json = base64_decode(payload) + except Exception as e: + raise BadPayload( + "Could not base64 decode the payload because of an exception", + original_error=e, + ) from e + + if decompress: + try: + json = zlib.decompress(json) + except Exception as e: + raise BadPayload( + "Could not zlib decompress the payload before decoding the payload", + original_error=e, + ) from e + + return super().load_payload(json, *args, **kwargs) + + def dump_payload(self, obj: t.Any) -> bytes: + json = super().dump_payload(obj) + is_compressed = False + compressed = zlib.compress(json) + + if len(compressed) < (len(json) - 1): + json = compressed + is_compressed = True + + base64d = base64_encode(json) + + if is_compressed: + base64d = b"." + base64d + + return base64d + + +class URLSafeSerializer(URLSafeSerializerMixin, Serializer[str]): + """Works like :class:`.Serializer` but dumps and loads into a URL + safe string consisting of the upper and lowercase character of the + alphabet as well as ``'_'``, ``'-'`` and ``'.'``. + """ + + +class URLSafeTimedSerializer(URLSafeSerializerMixin, TimedSerializer[str]): + """Works like :class:`.TimedSerializer` but dumps and loads into a + URL safe string consisting of the upper and lowercase character of + the alphabet as well as ``'_'``, ``'-'`` and ``'.'``. + """ diff --git a/venv/Lib/site-packages/werkzeug-3.0.3.dist-info/INSTALLER b/venv/Lib/site-packages/werkzeug-3.0.3.dist-info/INSTALLER new file mode 100644 index 00000000..a1b589e3 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug-3.0.3.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/Lib/site-packages/werkzeug-3.0.3.dist-info/LICENSE.txt b/venv/Lib/site-packages/werkzeug-3.0.3.dist-info/LICENSE.txt new file mode 100644 index 00000000..c37cae49 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug-3.0.3.dist-info/LICENSE.txt @@ -0,0 +1,28 @@ +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-3.0.3.dist-info/METADATA b/venv/Lib/site-packages/werkzeug-3.0.3.dist-info/METADATA new file mode 100644 index 00000000..7e02aa4f --- /dev/null +++ b/venv/Lib/site-packages/werkzeug-3.0.3.dist-info/METADATA @@ -0,0 +1,99 @@ +Metadata-Version: 2.1 +Name: Werkzeug +Version: 3.0.3 +Summary: The comprehensive WSGI web application library. +Maintainer-email: Pallets +Requires-Python: >=3.8 +Description-Content-Type: text/markdown +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 +Classifier: Typing :: Typed +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/ + + +## A Simple Example + +```python +# save this as app.py +from werkzeug.wrappers import Request, Response + +@Request.application +def application(request: Request) -> Response: + return Response("Hello, World!") + +if __name__ == "__main__": + from werkzeug.serving import run_simple + run_simple("127.0.0.1", 5000, application) +``` + +``` +$ python -m app + * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) +``` + + +## 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 + diff --git a/venv/Lib/site-packages/werkzeug-3.0.3.dist-info/RECORD b/venv/Lib/site-packages/werkzeug-3.0.3.dist-info/RECORD new file mode 100644 index 00000000..695f8613 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug-3.0.3.dist-info/RECORD @@ -0,0 +1,125 @@ +werkzeug-3.0.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +werkzeug-3.0.3.dist-info/LICENSE.txt,sha256=O0nc7kEF6ze6wQ-vG-JgQI_oXSUrjp3y4JefweCUQ3s,1475 +werkzeug-3.0.3.dist-info/METADATA,sha256=q6dwCfUWf4-0FFck9mU8Yfcy2DG29TXKG3u0YSsorLU,3682 +werkzeug-3.0.3.dist-info/RECORD,, +werkzeug-3.0.3.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81 +werkzeug/__init__.py,sha256=HX_PSY5E2vtVlD3R4YblwBRCjg7j3Tlm3LASbYqOSkU,727 +werkzeug/__pycache__/__init__.cpython-312.pyc,, +werkzeug/__pycache__/_internal.cpython-312.pyc,, +werkzeug/__pycache__/_reloader.cpython-312.pyc,, +werkzeug/__pycache__/exceptions.cpython-312.pyc,, +werkzeug/__pycache__/formparser.cpython-312.pyc,, +werkzeug/__pycache__/http.cpython-312.pyc,, +werkzeug/__pycache__/local.cpython-312.pyc,, +werkzeug/__pycache__/security.cpython-312.pyc,, +werkzeug/__pycache__/serving.cpython-312.pyc,, +werkzeug/__pycache__/test.cpython-312.pyc,, +werkzeug/__pycache__/testapp.cpython-312.pyc,, +werkzeug/__pycache__/urls.cpython-312.pyc,, +werkzeug/__pycache__/user_agent.cpython-312.pyc,, +werkzeug/__pycache__/utils.cpython-312.pyc,, +werkzeug/__pycache__/wsgi.cpython-312.pyc,, +werkzeug/_internal.py,sha256=su1olkbHMkzt0VKcEkPLCha8sdVzXNBuqW6YVpp8GHg,5545 +werkzeug/_reloader.py,sha256=YB1h2hopXAsnIVn2LIgt1lkEJLlTLE6qk2zlvGBCd3U,15082 +werkzeug/datastructures/__init__.py,sha256=yzBdOT9DdK3nraNG49pA3bVsvtPPLx2-t2N8ZmuAd9w,1900 +werkzeug/datastructures/__pycache__/__init__.cpython-312.pyc,, +werkzeug/datastructures/__pycache__/accept.cpython-312.pyc,, +werkzeug/datastructures/__pycache__/auth.cpython-312.pyc,, +werkzeug/datastructures/__pycache__/cache_control.cpython-312.pyc,, +werkzeug/datastructures/__pycache__/csp.cpython-312.pyc,, +werkzeug/datastructures/__pycache__/etag.cpython-312.pyc,, +werkzeug/datastructures/__pycache__/file_storage.cpython-312.pyc,, +werkzeug/datastructures/__pycache__/headers.cpython-312.pyc,, +werkzeug/datastructures/__pycache__/mixins.cpython-312.pyc,, +werkzeug/datastructures/__pycache__/range.cpython-312.pyc,, +werkzeug/datastructures/__pycache__/structures.cpython-312.pyc,, +werkzeug/datastructures/accept.py,sha256=CuCvBAxNzbt4QUb17rH986vvOVGURFUjo0DX2PQy_yI,10670 +werkzeug/datastructures/accept.pyi,sha256=6P114gncjZoy-i_n_3OQy2nJVwjEAIe7PcBxKYqCEfc,1917 +werkzeug/datastructures/auth.py,sha256=tZz0wZ1sIpIcAQoEAVhrUvy8M3kqKvIytmvGvwkAdxo,10021 +werkzeug/datastructures/cache_control.py,sha256=RTUipZev50s-1TAn2rYGZrytm_6IOIxQd67fkR5bNF0,6043 +werkzeug/datastructures/cache_control.pyi,sha256=NI5myF8f4yzgiqOHJANgp6XtT8SGCWI_GBp5JuH3NIs,3870 +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=PvUx7s2U3ifIf2YxMUhFtZFdkLFderInKG1U3VWwM9E,1457 +werkzeug/datastructures/headers.py,sha256=97-P-LgzterxEwxLbQsBEGiZpCOAXzZ7fExXXd4uH-o,17286 +werkzeug/datastructures/headers.pyi,sha256=66Gh9DbD8QNpLRBOuer4DMCj12csddHrcgxiJPLE5n8,4237 +werkzeug/datastructures/mixins.py,sha256=-IQSQ70UOMQlqtJEIyyhplOd4obaTOfzGvka-cunCtM,5337 +werkzeug/datastructures/mixins.pyi,sha256=Axe16elbs9zSOK9IuXIGs08ukgqSSPCxXFEjB_ACYSM,4189 +werkzeug/datastructures/range.py,sha256=JXSDPseG7iH5giJp3R1SnQC_SqQp634M8Iv6QTsbTxM,5669 +werkzeug/datastructures/range.pyi,sha256=bsM61iNp86gT2lyN0F_Dqg8xsnfPerdmElipuHppiJQ,1792 +werkzeug/datastructures/structures.py,sha256=8nRqvwHM8moZj_fEaxOqF-N7lguoXgnNJeT2l9LX7xA,31917 +werkzeug/datastructures/structures.pyi,sha256=9NeGm8NDS-x3XmE2ZP9676tKvQfo5G9GGvIlfV4v3aY,8237 +werkzeug/debug/__init__.py,sha256=QyiMgAHIDo7Is564apzqf5YuAw7kccQNQ7-EYPfrv8k,19838 +werkzeug/debug/__pycache__/__init__.cpython-312.pyc,, +werkzeug/debug/__pycache__/console.cpython-312.pyc,, +werkzeug/debug/__pycache__/repr.cpython-312.pyc,, +werkzeug/debug/__pycache__/tbtools.cpython-312.pyc,, +werkzeug/debug/console.py,sha256=t4hZ0Qg1p6Uu2MWimqoMDi7S3WYZvLMjnc8v_dPaxAo,6089 +werkzeug/debug/repr.py,sha256=iHMYny8whiiMDasvUqj0nm4-1VHVvwe697KleiZVK1s,9303 +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=nkYRd_yUc23roybb4i4xs3jMQxr0cebQ5HR75_zxpdk,10544 +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=DN1JDaDV4J_BAGf9ItOr1bs6HJly7iFHiTpWEDAiYCU,13265 +werkzeug/exceptions.py,sha256=SX3MUTqvWVyA9SnfMPxowNPu5beR9DyrmbUJ4AD2XT0,26160 +werkzeug/formparser.py,sha256=BabxEz6Bu1Q1BlKUwkmllb7FN4QBn_5eX2K9tHPr80s,15420 +werkzeug/http.py,sha256=x_x5xj9FcJyS5rurfnF0KOl0csyy4YlV_ur4hb1IZ2w,43546 +werkzeug/local.py,sha256=KUFuAm8BAayQouzVg0MGqW_hiwY8Z_lY5l7d1Scvsx8,22492 +werkzeug/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +werkzeug/middleware/__pycache__/__init__.cpython-312.pyc,, +werkzeug/middleware/__pycache__/dispatcher.cpython-312.pyc,, +werkzeug/middleware/__pycache__/http_proxy.cpython-312.pyc,, +werkzeug/middleware/__pycache__/lint.cpython-312.pyc,, +werkzeug/middleware/__pycache__/profiler.cpython-312.pyc,, +werkzeug/middleware/__pycache__/proxy_fix.cpython-312.pyc,, +werkzeug/middleware/__pycache__/shared_data.cpython-312.pyc,, +werkzeug/middleware/dispatcher.py,sha256=zWN5_lqJr_sc9UDv-PPoSlDHN_zR33z6B74F_4Cxpo8,2602 +werkzeug/middleware/http_proxy.py,sha256=sdk-V6GoZ6aMny-D0QNKNf5MWD2OTO3AGbBg6upp4Hc,7834 +werkzeug/middleware/lint.py,sha256=lwsZhyDNTnsNr4D8dqsgG8Akp7YP9D_X49SCiZucE04,14478 +werkzeug/middleware/profiler.py,sha256=1ZAHlDeYNdhgp8THOXkV5lgmcLl307phAr2Ufy30-lY,5562 +werkzeug/middleware/proxy_fix.py,sha256=n-HW-MRWJquCIhmqiZKoGdbbEeHuWJqPRHhFpuj4pzY,6755 +werkzeug/middleware/shared_data.py,sha256=a6gT17zipdiYhxvGb-cKnayDk8VZi04CJwxf1fr2Is0,9499 +werkzeug/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +werkzeug/routing/__init__.py,sha256=d8TRxsk24IWu2BdoOYUfL--deolHwiGVCBJqLoEe3YM,4820 +werkzeug/routing/__pycache__/__init__.cpython-312.pyc,, +werkzeug/routing/__pycache__/converters.cpython-312.pyc,, +werkzeug/routing/__pycache__/exceptions.cpython-312.pyc,, +werkzeug/routing/__pycache__/map.cpython-312.pyc,, +werkzeug/routing/__pycache__/matcher.cpython-312.pyc,, +werkzeug/routing/__pycache__/rules.cpython-312.pyc,, +werkzeug/routing/converters.py,sha256=iqpee_mAjr1oGbq0etujYF9PiDv5U7DgNkARHXnMId0,7297 +werkzeug/routing/exceptions.py,sha256=wNBiUmUk4OtFOpbdDSr7KKKUjH7yn84JqwBicUup8p8,4846 +werkzeug/routing/map.py,sha256=mEXlHOyinkg1Jtx5L0UDYsvoX4eVLiEuEVQzD5LVAz8,36515 +werkzeug/routing/matcher.py,sha256=nfBbl37eGAkZ1dQlumshFcPuyfggmFjPuSSQOE6GuYs,7849 +werkzeug/routing/rules.py,sha256=rc7FcnN_nQ_k8fzgLYjnoU59WNgShhrqgeB2h7dhFIA,32133 +werkzeug/sansio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +werkzeug/sansio/__pycache__/__init__.cpython-312.pyc,, +werkzeug/sansio/__pycache__/http.cpython-312.pyc,, +werkzeug/sansio/__pycache__/multipart.cpython-312.pyc,, +werkzeug/sansio/__pycache__/request.cpython-312.pyc,, +werkzeug/sansio/__pycache__/response.cpython-312.pyc,, +werkzeug/sansio/__pycache__/utils.cpython-312.pyc,, +werkzeug/sansio/http.py,sha256=_5fVKoogLUyNH5O2BnKty6dFB1p4REBZieJ4vYoOUOA,5370 +werkzeug/sansio/multipart.py,sha256=u_XLs68tvP2AO704Yq5zZg7ZN0A33SQaZfQE40gsduo,11490 +werkzeug/sansio/request.py,sha256=MI59ROX1P_Y6F4FkCLjaV9hwPEXE7aTTqaVphiTw4UA,19983 +werkzeug/sansio/response.py,sha256=uhKYuDy5-Q5v0Mk5VIxiF-Xob9vfGdDzWiJG7J7MYYc,27585 +werkzeug/sansio/utils.py,sha256=Y7zkEmIvBLtVvgwSdtBhFpGqCclBtYx7GUhckiRSyhI,4957 +werkzeug/security.py,sha256=SrUfgJhGzW_Ex7qjcBINRGcfWdikaiponA5bsps4kLA,5376 +werkzeug/serving.py,sha256=l8LBIbbvDYPsvKNEB1KsB-1cW7KB0Yhc3YvBDlmXTyM,39531 +werkzeug/test.py,sha256=kMEWtC_bZ5LqvBya-Pvtq1Jvtb4RR_t7pBp27_4JpJo,52782 +werkzeug/testapp.py,sha256=5_IS5Dh_WfWfNcTLmbydj01lomgcKA_4l9PPCNZnmdI,6332 +werkzeug/urls.py,sha256=XyNKwHvK5IC37-wuIDMYWkiCJ3yLTLGv7wn2GF3ndqI,6430 +werkzeug/user_agent.py,sha256=lSlLYKCcbzCUSkbdAoO8zPk2UR-8Mdn6iu_iA2kYPBA,1416 +werkzeug/utils.py,sha256=6iV_-JdFaLXG6bCR3FMSMyUY0HCnsdzlKirANavAXkk,24699 +werkzeug/wrappers/__init__.py,sha256=b78jCM8x96kJUGLZ5FYFR3zlK-3pnFAmP9RJIGU0ses,138 +werkzeug/wrappers/__pycache__/__init__.cpython-312.pyc,, +werkzeug/wrappers/__pycache__/request.cpython-312.pyc,, +werkzeug/wrappers/__pycache__/response.cpython-312.pyc,, +werkzeug/wrappers/request.py,sha256=YygiRF1cu5fypJaGsib_ntGNIFReCnW1ONoDurKXBek,24661 +werkzeug/wrappers/response.py,sha256=u6zg7VpNYrCeEjpIgf8VqgfaSi9yR_9wi9ly2uudglg,32459 +werkzeug/wsgi.py,sha256=P7jB0VpG6X6miies4uk7Zgm7NVm4Yz8Ra6Inr5q_FMs,20894 diff --git a/venv/Lib/site-packages/werkzeug-3.0.3.dist-info/WHEEL b/venv/Lib/site-packages/werkzeug-3.0.3.dist-info/WHEEL new file mode 100644 index 00000000..3b5e64b5 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug-3.0.3.dist-info/WHEEL @@ -0,0 +1,4 @@ +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 new file mode 100644 index 00000000..57cb7539 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/__init__.py @@ -0,0 +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 + + +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-312.pyc b/venv/Lib/site-packages/werkzeug/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3a684ea5549fd971722a1c0c63e2a43612a47279 GIT binary patch literal 1124 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�OhO9t!TU$rrhUo=wB93 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/__pycache__/exceptions.cpython-312.pyc b/venv/Lib/site-packages/werkzeug/__pycache__/exceptions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5a5568d9e7e7ac6ba58d27752331cfbec7ef3b21 GIT binary patch literal 32980 zcmd^odvF}bdFKpv7g)SW00c>Zj}hOX2=FaY;1eXlheSdW35pgZ8CYNk;EIb~cy=KW z8_J;+`3zm;m^vpBZAUSj^2OxIF3Xi%MX4l}*Infve;gpvMP!Rkco(}a`6mUL@FT3c ztNZ=F?wOrkEJ&M4<)2%LJ%g`%db+><`s?r2J^#JBy28NkCkLJz+%{+!f5$)EU#Yq= z@2v?M#(Ty&BWm;*Q8QW+H-}4lN=)7d;(?wZuEBU{IMfpoSSVgPT-H-2_ocWG_k?j@ z77fSChbww2Ov4Vo+UUL{cXigGp7MCraCJ|$JX?YLnw}bYwlZEjyr^f9+*idH57+h7 z$$fRaez>7$iH0`6HR)1azqnx=NtS0A22bt`=w$pv}JbSpr=F=t>{-F@dfEbhQt< zMxYj;kNKc$1-b^%wLa)Nfvy8|y$`xxpc??)=!0$$=q5m0e9(;o-3;jCKIkTaZUMB_ z2W=5(8=zZ#(9Htf2IzJl^l^di0CcAhx<#P70Nw3_whHtKK%ewM+XT7?(5HORtpaTa zbgvJ(O`!V#-S2~L7w7>%5Bi`x1bPV24j*)^W3o*oQt4x;G5dcnN=F{=A51dp;;p;GT}? zr=u^S>|vDcaLe`_iT0qbms!_%prtqRPnbR@hezV}u$@S!tcaBu9qzXsD>-OAH=e$b zOj!1%0ed7JOD0m**oD}@1#2LZu=;JwiNsQN)Eab>!`4_VeIb^ph*+=1>@h2yw9-y& zXb9CrtW-LZh(?^Kb)vibIV+J&wC>op&2sG22x_$3Dk{#UB15*-c`3qHRJ8hk6%}o5 z)<809xAw=A0~g!dtr2NcMTLc*db90Z%-EwtZDUSkWW;t-ZI1n!Q9G5kV#6azCvBZk ze-?Sd*D+m|P3&~LyT$5CrjI2@6H)cLs68m%+oWI6(k^wfC4vgC_@);1z#;wWHZ&|A z8;B5x@(644!HQIvcE)qH%Ny+UsFTQlzxR685kaumdMlE$?4otp6%`#RYdkqB1DUdI zg-8q_o=~G;Vc=7?f8-;MjpFfmq(5$3=g%HLS>fwIs?9o)9J61uoh^9M9=BLJkxX0X z_278JZ6*6(wFlC8;Kk%<1qo@$PJ>8Xa?tr!|G2y@mKd`7`cS0TEwHJjug?NeMx?hD z-VlP0p0Vu_E0(Z^V~Hr55lKrQha=80)>O36R*V_RI-U3X|a-yl^RHn z*cF>P{tHjN>Golr?ClF&`q)XpCbUiC4rDgGpytMjCyJ;|4w8 zbhv=|q6iT!Jvk^%8;c~;%)F3HUSz`~Rx~y^XgipQIn<-km>npUii~eTkzLz%aZ05} zQx%*TTfBipE0CSC2a@Q%rU*2Kun~J8HW(YQQltF?apC}zn`mGs+8;c}{ylNhNNwf;^YQo+B zqK(mNtW7m&Lk+IAxYpvjXu`N;_AEYYv@Fe5=W*l6TA;lf)2Kg!NvOzJ$U7caD~rbp zz+Ac&FVpLmyI_%jVZj1pj3^dH#_D5ZoMo7NQCYUYqa&knOhir+H3@NbxhBSL;DXF* zcTU+UAw$fp7?vJXGQye?={UF{CZw)65gS15s?K3M#g#7whQNkkr2_*^fsYJah$M#W zC|0{2ZQDKKK4T9jUqkcBO8WY^+JO@)JPh05*U>g9+I1gHC~aNFYLu|ELGpRiD1f(4+_|6VqSS~uXL)n zH$}n+Vy@i3a-{v`b15L_e;_{-_!SR!>HHqx6Kj>LQWM^mu`aOr_tfxvWsGMxjk zT)fpS*vh=uFmYc#73+UF6-(Q#BawlN9E+F7^oq6BM@}Q-&T=#*)0jt)ZEl~QMP6z> zlAm2M<|>TxnzzfQ!^>`kn`SB-rz+RXtlm6v^j6i1nPt{Q@C()VP|z443vqw!rXc

Bu&i74>;5%U0l8%hz2hQ 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/__pycache__/formparser.cpython-312.pyc b/venv/Lib/site-packages/werkzeug/__pycache__/formparser.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c344b858c59940b79d9b655a633ee8b1f1d7a8eb GIT binary patch literal 16968 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 ZdT1N~_qmTg&i(!46+<59|8nz)|39y84+sDN literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/__pycache__/http.cpython-312.pyc b/venv/Lib/site-packages/werkzeug/__pycache__/http.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..714eff0cd1782f10c809add159cd877e7e04058f GIT binary patch literal 49219 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/__pycache__/serving.cpython-312.pyc b/venv/Lib/site-packages/werkzeug/__pycache__/serving.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..da7890000d5a14e002b097cfeed985b3c9851494 GIT binary patch literal 45922 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/__pycache__/test.cpython-312.pyc b/venv/Lib/site-packages/werkzeug/__pycache__/test.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5e4c12bd15fe3df123e7df6471c9a1b232a01aa3 GIT binary patch literal 59937 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 OGYDu|_^yZ$l=1)B1MtZJ literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/__pycache__/testapp.cpython-312.pyc b/venv/Lib/site-packages/werkzeug/__pycache__/testapp.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f91f87257f4e3174c5d639a32aa672cc9b65ec5b GIT binary patch literal 8928 zcmahvTWlLwb~EJgO}#10lI3_TS(a#vF%ix;~agu#$EL39(U8HXWR=9nfT$+gq)&dm}b15dZ4C4`LhXVx6%IV=4r3+n*HdsfmiYFd+o ze1KtwcZzC4&t)V&si%~fe;6OUBBZB|NNIT0WnGbCl(x|{P?Dlc4fmV<{$WkeDH4wp z9O=@fIlVujXUu^S>hGUa)4CR&Qq?I%5;C$D1&$Em=%kR6 zmE4gxC$ech8$Rp@{0UXe@#b|IM3B}a=Aa?I_tk7d76twk0WnFv`^p_-LR#bBOv?$? z>^>!>rAtEZKA3I8)%!z&n9JEI?P*d z-2(#yhd0kfDbwV_0 z=-d$Bed2`^Cr?^u&t4qJe1gJ*HXDi@y(AMoD=09?wl0Sb9kQ&4C?!-v?d%W_)<`1A zskx1SDa~c51gei=*;Ez%!~K*{Fx_Br_yp{RcBBgo4>iie$-x@796EvH;SAn+{*9!p zfz6qcz+`C|MaXG9^Wi*Nz zBEdb1>w==o;*qWlg+{8NT`>z|w1P3*pL~{5OO@QDcvvT9118gxHWC~*4O}iI1OZ~K zr_Wp?4l0G9uttHK(G@{QN4F8t>I`fA`JO&i3u_l^;T}6PiiiA(b-~$v|DrGEbcrhE zhE*r*-rAI&lgLb7%1+f>a@GQFSQq>@ZXp$*CH7fuN{&crIFD*N1^%kbJRm13J)1<{ z(}yT=KnZI#=9AN!4(CM@Bhu6qgaV`XJAVS+%X5_vn*^$^DC#U85OaIr@Fiy8{AoP! zAi;65Cgf@SB=K5K)4|va=I&WX1Q#0u;SFb%%06Q!5(#|>vN#6(2pWj4*qAxf8F+aL zVK4&J0I0zuoyZabk^;?za|sa*uTIi_MHNIKUQb3*izK?073N?hk%1|kMN!om2>!Ae zV4m1|VY3a`r&S%7f>;rPJ30@lAOs^R4dek3s-j@ya57i|Y=j1A;J|Y&B8T`>PG5p}MdXR3sacW$5`iWfoKEC`S4~$_ycNtPfo@amS~dZzYn1Q2 zoUsO28f|UvD5ZB+MmqTkf^k5V;WT#_tQkgeA{B&XaQI0AOA0^`Xq|k?q}-D0=p>(0 zv%Hi}K%fQ|gr|fc(2|VAgGI`KQNm#+Rq*Uq!D;hnKc7N>i;Y=%R^|vY1!uuA^E|^$I)BbC zvMlqncR41<{DX6r4ZBVou8gY5bBVItVGbAkU{3bo0yLhs`_njj(rysL#hh2t4)eH^~vEes07VHBt{9u`huAzn- zPDM(G9R@pRxWTti%5xg-2+uz*jtpIR6Gm$nGJvF9(AC+rmXtGb%#Py|Sq*|4O)Gat z5@7ZwRK3zcv*?IDFoLj4;C(K$ZHc|vy)^m$-lfR$*1qMo@N#I+HFm8fwD8hHl;?(B#T}-*iVD6+WqOA_eTTWj zl4j`95YCej+(|3kzMsr##6bO>XXtWd3$()=oyl+lCx#oMsw_#9Z3yra)G>og3n_`9 z$`~GD0>W$9EccY8CuT)J%E7))gEKJP;MGK#7+y@&=pdJ@K?lr_lo?JO&!G#Vb7`pg zU`b8ljk*ouCA|QQ!%zD=xcxWt7m%yR_qVe@nZ4HgnWy~`$N5?xHnP6fM{XuiceVFQ z@74Y*{WrUAg>Qu4-&^wUo*#M8*s|!o?)`>wxW?E8?mAcj9lA<@&QF@7b?iIQ#!Q$4C4qGM$X3+xLSs`=qfm;8|;}YPtl{74HjVHx!H8t zivK<%daK5|s``B&l;6M@o!}Ut$z8<2|;sRLeZcL24tY92iW5Ab)MVJ?8$ z7rjNF7?^H<5?jZUNiThSxmD&)-(mL^d^YV*dlr2KcTK4Ut0dvIX8{S-6?|~7-yn~R zR33hPr@g+-CGa!_Ji&e!1I1vm4y0CJ2$H5kea$r+>~&bCQ1_jq8mmUtlUA`wY_27% zLcKi>=xzDHnP$a6v7z9vxpJt`0Ha!~cn(ysKj7>(?7hq};+9(LRdDq#TMo6(sXf

T8YhP;WJ_={IUB0kWyB#w>o@s;yl+Fnzg$Bx zS-?!Bx_&}49n2|dg}qANW^D{1R)Skq7ojO0f-0%8B2N$j(@V@jz$t?fW=?N#X4pd` zk%Q2{NCcya&DV6at;wC@>mO1rg2C&RfwN(bbOVN=LNRF)+`V(G6=jXQUiKS7Law>6tV{ zk9lrbkz;uuKTJcz*!jHgB9y%#oK#{Pc*Yoo@;n3$h^aB!qE`W2IZk=k>iK@YAqu7f z1=cwtWeC{|sGAukXn($s*PjDiLx(!g{6_!u*r&p8kN^)j{cx@^mU70gUNfb z9))$RJbZl-W5cK`vAk!vpEeASRXH@A5Qyi!cCz`Hfvl;JB^oQy_W3GD)s5wIr0s*2&Yi$I3g4yyA$`T+<(AmL}G zl$?_AyHI$WQJ0ySikHg^hFgOVLy~9&EHe>@Oxy^J((gql2vG?zX%^88{|^NvE18}3 z$8-qgC$hTr?gkZ=(jviJh@g)!JeC3(E{sz(g6^E)A)o;<8Ne2Xri6zZWH0bFO~=}* zMh;;whRX!Q5`yQ29EO|G8i!ChzF8Io^JK&O#);R?yn5`76X7PqNu(c@O zI-yBUK#Roiwcg|#OJ`VZBN6O$LGdLk5zIaaI!5v=_ImBQ2*ll*X834HA&w!whgO~D zbPb~>)b$M)O(hJkt#RZW4ASt|4f;RNtUu#*`M>mbKJqe+Th@Z@tHGX?V9ytg1CLzJ zM*jl0?qyn9=U-WCX!(Zmxcm#wb%)c(uWe~xoVh-;aBAJn_(H4RjumgmT7C0-2ba26 zJNB$}>?yVEU2fiYd-PY6_s{*Kx_sc|r}A>}%(qU5&%YjkdDfem#^(8#9<**-8oBk# zjaODXBP*SeQfL3lf!9B8Jv)DTZObl<^j~JLz?utht~E95WQDkRm#k*sHTbQ~ww^rY{;QS)cycTS@`tp^RujM`u?))OqV!viT4{rZr z|G{g?CGFPSjk(*-_lryF?blaA{r6v7@Y!p3FUd>iZ?pGye-gU8@AfmxTVMEt_t1I+ ztg{|sw(Y#N_r~6jChv`XeE!b)((Xg6yN|EzK3>{=vea?vL8#;A{#(O0hD)Ko2lb)% z&SJ*-uKJ*@WAVq=fBc|z>*B@h7aw#EmOA&ZwQXC>UC({Et<)A-3vFE-y*|2>EQNZj z+h4c%8{G4!9&KTQq4!=|nq3L){$$(dfg{V@k-t28i)Dgs-!QDJXRT}3yWF}HTK@+_ zo3`CNz1*-DAh^Ff-aQ2nAOBdxcc;oYC}zVj4cv z&w@V$3nb}+C_ssVsvz2X>Ql^g`&E3!+dsii+Xc6I=36%t-2R}mXSFlB(ivUtJh;+% z@RNM0^TlOv`+o&Gm$^NfvUK#3%;;X-I<;ok|B;F8)RGLA45I|>ow z2cX_#g?Z#}5V&!lAdX0>VivwxkQ6}CUu$o}4UQ|ze(i9u+_zqa?fxUv{w3r4lJS4Z zG=Igk|B2cDC#L5srgxotnGJID-iPCkCU%Tn?_(UkRW`K3hQ4tOv$2Ox#?iWtt@XEA wrnPOEX?o~(v)jR7alwWBXU_JoxbFYt>K86A9sjWJe(2x0*t&<|I_NR@f7eOwxc~qF literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/__pycache__/urls.cpython-312.pyc b/venv/Lib/site-packages/werkzeug/__pycache__/urls.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e8c7c256176fb587b6ed1f77f901f67e722bad17 GIT binary patch literal 8307 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/__pycache__/user_agent.cpython-312.pyc b/venv/Lib/site-packages/werkzeug/__pycache__/user_agent.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4e9cd67e83a86990edf3fdf0ecbbdd04e23716e2 GIT binary patch literal 2190 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 KW+#ii{eJ*=s??hR literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/_internal.py b/venv/Lib/site-packages/werkzeug/_internal.py new file mode 100644 index 00000000..7dd2fbcc --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/_internal.py @@ -0,0 +1,211 @@ +from __future__ import annotations + +import logging +import re +import sys +import typing as t +from datetime import datetime +from datetime import timezone + +if t.TYPE_CHECKING: + from _typeshed.wsgi import WSGIEnvironment + + from .wrappers.request import Request + +_logger: logging.Logger | None = None + + +class _Missing: + def __repr__(self) -> str: + return "no value" + + def __reduce__(self) -> str: + return "_missing" + + +_missing = _Missing() + + +def _wsgi_decoding_dance(s: str) -> str: + return s.encode("latin1").decode(errors="replace") + + +def _wsgi_encoding_dance(s: str) -> str: + return s.encode().decode("latin1") + + +def _get_environ(obj: WSGIEnvironment | Request) -> WSGIEnvironment: + env = getattr(obj, "environ", obj) + assert isinstance( + env, dict + ), f"{type(obj).__name__!r} is not a WSGI environment (has to be a dict)" + return env + + +def _has_level_handler(logger: logging.Logger) -> bool: + """Check if there is a handler in the logging chain that will handle + the given logger's effective level. + """ + level = logger.getEffectiveLevel() + current = logger + + while current: + if any(handler.level <= level for handler in current.handlers): + return True + + if not current.propagate: + break + + current = current.parent # type: ignore + + return False + + +class _ColorStreamHandler(logging.StreamHandler): # type: ignore[type-arg] + """On Windows, wrap stream with Colorama for ANSI style support.""" + + def __init__(self) -> None: + try: + import colorama + except ImportError: + stream = None + else: + stream = colorama.AnsiToWin32(sys.stderr) + + super().__init__(stream) + + +def _log(type: str, message: str, *args: t.Any, **kwargs: t.Any) -> None: + """Log a message to the 'werkzeug' logger. + + The logger is created the first time it is needed. If there is no + level set, it is set to :data:`logging.INFO`. If there is no handler + for the logger's effective level, a :class:`logging.StreamHandler` + is added. + """ + global _logger + + if _logger is None: + _logger = logging.getLogger("werkzeug") + + if _logger.level == logging.NOTSET: + _logger.setLevel(logging.INFO) + + if not _has_level_handler(_logger): + _logger.addHandler(_ColorStreamHandler()) + + getattr(_logger, type)(message.rstrip(), *args, **kwargs) + + +@t.overload +def _dt_as_utc(dt: None) -> None: ... + + +@t.overload +def _dt_as_utc(dt: datetime) -> datetime: ... + + +def _dt_as_utc(dt: datetime | None) -> datetime | None: + if dt is None: + return dt + + if dt.tzinfo is None: + return dt.replace(tzinfo=timezone.utc) + elif dt.tzinfo != timezone.utc: + return dt.astimezone(timezone.utc) + + return dt + + +_TAccessorValue = t.TypeVar("_TAccessorValue") + + +class _DictAccessorProperty(t.Generic[_TAccessorValue]): + """Baseclass for `environ_property` and `header_property`.""" + + read_only = False + + def __init__( + self, + name: str, + default: _TAccessorValue | None = None, + load_func: t.Callable[[str], _TAccessorValue] | None = None, + dump_func: t.Callable[[_TAccessorValue], str] | None = None, + read_only: bool | None = None, + doc: str | None = None, + ) -> None: + self.name = name + self.default = default + self.load_func = load_func + self.dump_func = dump_func + if read_only is not None: + self.read_only = read_only + self.__doc__ = doc + + def lookup(self, instance: t.Any) -> t.MutableMapping[str, t.Any]: + raise NotImplementedError + + @t.overload + def __get__( + self, instance: None, owner: type + ) -> _DictAccessorProperty[_TAccessorValue]: ... + + @t.overload + def __get__(self, instance: t.Any, owner: type) -> _TAccessorValue: ... + + def __get__( + self, instance: t.Any | None, owner: type + ) -> _TAccessorValue | _DictAccessorProperty[_TAccessorValue]: + if instance is None: + return self + + storage = self.lookup(instance) + + if self.name not in storage: + return self.default # type: ignore + + value = storage[self.name] + + if self.load_func is not None: + try: + return self.load_func(value) + except (ValueError, TypeError): + return self.default # type: ignore + + return value # type: ignore + + def __set__(self, instance: t.Any, value: _TAccessorValue) -> None: + if self.read_only: + raise AttributeError("read only property") + + if self.dump_func is not None: + self.lookup(instance)[self.name] = self.dump_func(value) + else: + self.lookup(instance)[self.name] = value + + def __delete__(self, instance: t.Any) -> None: + if self.read_only: + raise AttributeError("read only property") + + self.lookup(instance).pop(self.name, None) + + def __repr__(self) -> str: + return f"<{type(self).__name__} {self.name}>" + + +_plain_int_re = re.compile(r"-?\d+", re.ASCII) + + +def _plain_int(value: str) -> int: + """Parse an int only if it is only ASCII digits and ``-``. + + This disallows ``+``, ``_``, and non-ASCII digits, which are accepted by ``int`` but + are not allowed in HTTP header values. + + Any leading or trailing whitespace is stripped + """ + value = value.strip() + if _plain_int_re.fullmatch(value) is None: + raise ValueError + + return int(value) diff --git a/venv/Lib/site-packages/werkzeug/_reloader.py b/venv/Lib/site-packages/werkzeug/_reloader.py new file mode 100644 index 00000000..d7e91a61 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/_reloader.py @@ -0,0 +1,460 @@ +from __future__ import annotations + +import fnmatch +import os +import subprocess +import sys +import threading +import time +import typing as t +from itertools import chain +from pathlib import PurePath + +from ._internal import _log + +# The various system prefixes where imports are found. Base values are +# different when running in a virtualenv. All reloaders will ignore the +# base paths (usually the system installation). The stat reloader won't +# scan the virtualenv paths, it will only include modules that are +# already imported. +_ignore_always = tuple({sys.base_prefix, sys.base_exec_prefix}) +prefix = {*_ignore_always, sys.prefix, sys.exec_prefix} + +if hasattr(sys, "real_prefix"): + # virtualenv < 20 + prefix.add(sys.real_prefix) + +_stat_ignore_scan = tuple(prefix) +del prefix +_ignore_common_dirs = { + "__pycache__", + ".git", + ".hg", + ".tox", + ".nox", + ".pytest_cache", + ".mypy_cache", +} + + +def _iter_module_paths() -> t.Iterator[str]: + """Find the filesystem paths associated with imported modules.""" + # List is in case the value is modified by the app while updating. + for module in list(sys.modules.values()): + name = getattr(module, "__file__", None) + + if name is None or name.startswith(_ignore_always): + continue + + while not os.path.isfile(name): + # Zip file, find the base file without the module path. + old = name + name = os.path.dirname(name) + + if name == old: # skip if it was all directories somehow + break + else: + yield name + + +def _remove_by_pattern(paths: set[str], exclude_patterns: set[str]) -> None: + for pattern in exclude_patterns: + paths.difference_update(fnmatch.filter(paths, pattern)) + + +def _find_stat_paths( + extra_files: set[str], exclude_patterns: set[str] +) -> t.Iterable[str]: + """Find paths for the stat reloader to watch. Returns imported + module files, Python files under non-system paths. Extra files and + Python files under extra directories can also be scanned. + + System paths have to be excluded for efficiency. Non-system paths, + such as a project root or ``sys.path.insert``, should be the paths + of interest to the user anyway. + """ + paths = set() + + for path in chain(list(sys.path), extra_files): + path = os.path.abspath(path) + + if os.path.isfile(path): + # zip file on sys.path, or extra file + paths.add(path) + continue + + parent_has_py = {os.path.dirname(path): True} + + for root, dirs, files in os.walk(path): + # Optimizations: ignore system prefixes, __pycache__ will + # have a py or pyc module at the import path, ignore some + # common known dirs such as version control and tool caches. + if ( + root.startswith(_stat_ignore_scan) + or os.path.basename(root) in _ignore_common_dirs + ): + dirs.clear() + continue + + has_py = False + + for name in files: + if name.endswith((".py", ".pyc")): + has_py = True + paths.add(os.path.join(root, name)) + + # Optimization: stop scanning a directory if neither it nor + # its parent contained Python files. + if not (has_py or parent_has_py[os.path.dirname(root)]): + dirs.clear() + continue + + parent_has_py[root] = has_py + + paths.update(_iter_module_paths()) + _remove_by_pattern(paths, exclude_patterns) + return paths + + +def _find_watchdog_paths( + extra_files: set[str], exclude_patterns: set[str] +) -> t.Iterable[str]: + """Find paths for the stat reloader to watch. Looks at the same + sources as the stat reloader, but watches everything under + directories instead of individual files. + """ + dirs = set() + + for name in chain(list(sys.path), extra_files): + name = os.path.abspath(name) + + if os.path.isfile(name): + name = os.path.dirname(name) + + dirs.add(name) + + for name in _iter_module_paths(): + dirs.add(os.path.dirname(name)) + + _remove_by_pattern(dirs, exclude_patterns) + return _find_common_roots(dirs) + + +def _find_common_roots(paths: t.Iterable[str]) -> t.Iterable[str]: + root: dict[str, dict[str, t.Any]] = {} + + for chunks in sorted((PurePath(x).parts for x in paths), key=len, reverse=True): + node = root + + for chunk in chunks: + node = node.setdefault(chunk, {}) + + node.clear() + + rv = set() + + def _walk(node: t.Mapping[str, dict[str, t.Any]], path: tuple[str, ...]) -> None: + for prefix, child in node.items(): + _walk(child, path + (prefix,)) + + # If there are no more nodes, and a path has been accumulated, add it. + # Path may be empty if the "" entry is in sys.path. + if not node and path: + rv.add(os.path.join(*path)) + + _walk(root, ()) + return rv + + +def _get_args_for_reloading() -> list[str]: + """Determine how the script was executed, and return the args needed + to execute it again in a new process. + """ + if sys.version_info >= (3, 10): + # sys.orig_argv, added in Python 3.10, contains the exact args used to invoke + # Python. Still replace argv[0] with sys.executable for accuracy. + return [sys.executable, *sys.orig_argv[1:]] + + rv = [sys.executable] + py_script = sys.argv[0] + args = sys.argv[1:] + # Need to look at main module to determine how it was executed. + __main__ = sys.modules["__main__"] + + # The value of __package__ indicates how Python was called. It may + # not exist if a setuptools script is installed as an egg. It may be + # set incorrectly for entry points created with pip on Windows. + if getattr(__main__, "__package__", None) is None or ( + os.name == "nt" + and __main__.__package__ == "" + and not os.path.exists(py_script) + and os.path.exists(f"{py_script}.exe") + ): + # Executed a file, like "python app.py". + py_script = os.path.abspath(py_script) + + if os.name == "nt": + # Windows entry points have ".exe" extension and should be + # called directly. + if not os.path.exists(py_script) and os.path.exists(f"{py_script}.exe"): + py_script += ".exe" + + if ( + os.path.splitext(sys.executable)[1] == ".exe" + and os.path.splitext(py_script)[1] == ".exe" + ): + rv.pop(0) + + rv.append(py_script) + else: + # Executed a module, like "python -m werkzeug.serving". + if os.path.isfile(py_script): + # Rewritten by Python from "-m script" to "/path/to/script.py". + py_module = t.cast(str, __main__.__package__) + name = os.path.splitext(os.path.basename(py_script))[0] + + if name != "__main__": + py_module += f".{name}" + else: + # Incorrectly rewritten by pydevd debugger from "-m script" to "script". + py_module = py_script + + rv.extend(("-m", py_module.lstrip("."))) + + rv.extend(args) + return rv + + +class ReloaderLoop: + name = "" + + def __init__( + self, + extra_files: t.Iterable[str] | None = None, + exclude_patterns: t.Iterable[str] | None = None, + interval: int | float = 1, + ) -> None: + self.extra_files: set[str] = {os.path.abspath(x) for x in extra_files or ()} + self.exclude_patterns: set[str] = set(exclude_patterns or ()) + self.interval = interval + + def __enter__(self) -> ReloaderLoop: + """Do any setup, then run one step of the watch to populate the + initial filesystem state. + """ + self.run_step() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): # type: ignore + """Clean up any resources associated with the reloader.""" + pass + + def run(self) -> None: + """Continually run the watch step, sleeping for the configured + interval after each step. + """ + while True: + self.run_step() + time.sleep(self.interval) + + def run_step(self) -> None: + """Run one step for watching the filesystem. Called once to set + up initial state, then repeatedly to update it. + """ + pass + + def restart_with_reloader(self) -> int: + """Spawn a new Python interpreter with the same arguments as the + current one, but running the reloader thread. + """ + while True: + _log("info", f" * Restarting with {self.name}") + args = _get_args_for_reloading() + new_environ = os.environ.copy() + new_environ["WERKZEUG_RUN_MAIN"] = "true" + exit_code = subprocess.call(args, env=new_environ, close_fds=False) + + if exit_code != 3: + return exit_code + + def trigger_reload(self, filename: str) -> None: + self.log_reload(filename) + sys.exit(3) + + def log_reload(self, filename: str) -> None: + filename = os.path.abspath(filename) + _log("info", f" * Detected change in {filename!r}, reloading") + + +class StatReloaderLoop(ReloaderLoop): + name = "stat" + + def __enter__(self) -> ReloaderLoop: + self.mtimes: dict[str, float] = {} + return super().__enter__() + + def run_step(self) -> None: + for name in _find_stat_paths(self.extra_files, self.exclude_patterns): + try: + mtime = os.stat(name).st_mtime + except OSError: + continue + + old_time = self.mtimes.get(name) + + if old_time is None: + self.mtimes[name] = mtime + continue + + if mtime > old_time: + self.trigger_reload(name) + + +class WatchdogReloaderLoop(ReloaderLoop): + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + from watchdog.events import EVENT_TYPE_OPENED + from watchdog.events import FileModifiedEvent + from watchdog.events import PatternMatchingEventHandler + from watchdog.observers import Observer + + super().__init__(*args, **kwargs) + trigger_reload = self.trigger_reload + + class EventHandler(PatternMatchingEventHandler): + def on_any_event(self, event: FileModifiedEvent): # type: ignore + if event.event_type == EVENT_TYPE_OPENED: + return + + trigger_reload(event.src_path) + + reloader_name = Observer.__name__.lower() # type: ignore[attr-defined] + + if reloader_name.endswith("observer"): + reloader_name = reloader_name[:-8] + + self.name = f"watchdog ({reloader_name})" + self.observer = Observer() + # Extra patterns can be non-Python files, match them in addition + # to all Python files in default and extra directories. Ignore + # __pycache__ since a change there will always have a change to + # the source file (or initial pyc file) as well. Ignore Git and + # Mercurial internal changes. + extra_patterns = [p for p in self.extra_files if not os.path.isdir(p)] + self.event_handler = EventHandler( # type: ignore[no-untyped-call] + patterns=["*.py", "*.pyc", "*.zip", *extra_patterns], + ignore_patterns=[ + *[f"*/{d}/*" for d in _ignore_common_dirs], + *self.exclude_patterns, + ], + ) + self.should_reload = False + + def trigger_reload(self, filename: str) -> None: + # This is called inside an event handler, which means throwing + # SystemExit has no effect. + # https://github.com/gorakhargosh/watchdog/issues/294 + self.should_reload = True + self.log_reload(filename) + + def __enter__(self) -> ReloaderLoop: + self.watches: dict[str, t.Any] = {} + self.observer.start() # type: ignore[no-untyped-call] + return super().__enter__() + + def __exit__(self, exc_type, exc_val, exc_tb): # type: ignore + self.observer.stop() # type: ignore[no-untyped-call] + self.observer.join() + + def run(self) -> None: + while not self.should_reload: + self.run_step() + time.sleep(self.interval) + + sys.exit(3) + + def run_step(self) -> None: + to_delete = set(self.watches) + + for path in _find_watchdog_paths(self.extra_files, self.exclude_patterns): + if path not in self.watches: + try: + self.watches[path] = self.observer.schedule( # type: ignore[no-untyped-call] + self.event_handler, path, recursive=True + ) + except OSError: + # Clear this path from list of watches We don't want + # the same error message showing again in the next + # iteration. + self.watches[path] = None + + to_delete.discard(path) + + for path in to_delete: + watch = self.watches.pop(path, None) + + if watch is not None: + self.observer.unschedule(watch) # type: ignore[no-untyped-call] + + +reloader_loops: dict[str, type[ReloaderLoop]] = { + "stat": StatReloaderLoop, + "watchdog": WatchdogReloaderLoop, +} + +try: + __import__("watchdog.observers") +except ImportError: + reloader_loops["auto"] = reloader_loops["stat"] +else: + reloader_loops["auto"] = reloader_loops["watchdog"] + + +def ensure_echo_on() -> None: + """Ensure that echo mode is enabled. Some tools such as PDB disable + it which causes usability issues after a reload.""" + # tcgetattr will fail if stdin isn't a tty + if sys.stdin is None or not sys.stdin.isatty(): + return + + try: + import termios + except ImportError: + return + + attributes = termios.tcgetattr(sys.stdin) + + if not attributes[3] & termios.ECHO: + attributes[3] |= termios.ECHO + termios.tcsetattr(sys.stdin, termios.TCSANOW, attributes) + + +def run_with_reloader( + main_func: t.Callable[[], None], + extra_files: t.Iterable[str] | None = None, + exclude_patterns: t.Iterable[str] | None = None, + interval: int | float = 1, + reloader_type: str = "auto", +) -> None: + """Run the given function in an independent Python interpreter.""" + import signal + + signal.signal(signal.SIGTERM, lambda *args: sys.exit(0)) + reloader = reloader_loops[reloader_type]( + extra_files=extra_files, exclude_patterns=exclude_patterns, interval=interval + ) + + try: + if os.environ.get("WERKZEUG_RUN_MAIN") == "true": + ensure_echo_on() + t = threading.Thread(target=main_func, args=()) + t.daemon = True + + # Enter the reloader to set up initial state, then start + # the app thread and reloader update loop. + with reloader: + t.start() + reloader.run() + else: + sys.exit(reloader.restart_with_reloader()) + except KeyboardInterrupt: + pass diff --git a/venv/Lib/site-packages/werkzeug/datastructures/__init__.py b/venv/Lib/site-packages/werkzeug/datastructures/__init__.py new file mode 100644 index 00000000..846ffce6 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/datastructures/__init__.py @@ -0,0 +1,34 @@ +from .accept import Accept as Accept +from .accept import CharsetAccept as CharsetAccept +from .accept import LanguageAccept as LanguageAccept +from .accept import MIMEAccept as MIMEAccept +from .auth import Authorization as Authorization +from .auth import WWWAuthenticate as WWWAuthenticate +from .cache_control import RequestCacheControl as RequestCacheControl +from .cache_control import ResponseCacheControl as ResponseCacheControl +from .csp import ContentSecurityPolicy as ContentSecurityPolicy +from .etag import ETags as ETags +from .file_storage import FileMultiDict as FileMultiDict +from .file_storage import FileStorage as FileStorage +from .headers import EnvironHeaders as EnvironHeaders +from .headers import Headers as Headers +from .mixins import ImmutableDictMixin as ImmutableDictMixin +from .mixins import ImmutableHeadersMixin as ImmutableHeadersMixin +from .mixins import ImmutableListMixin as ImmutableListMixin +from .mixins import ImmutableMultiDictMixin as ImmutableMultiDictMixin +from .mixins import UpdateDictMixin as UpdateDictMixin +from .range import ContentRange as ContentRange +from .range import IfRange as IfRange +from .range import Range as Range +from .structures import CallbackDict as CallbackDict +from .structures import CombinedMultiDict as CombinedMultiDict +from .structures import HeaderSet as HeaderSet +from .structures import ImmutableDict as ImmutableDict +from .structures import ImmutableList as ImmutableList +from .structures import ImmutableMultiDict as ImmutableMultiDict +from .structures import ImmutableOrderedMultiDict as ImmutableOrderedMultiDict +from .structures import ImmutableTypeConversionDict as ImmutableTypeConversionDict +from .structures import iter_multi_items as iter_multi_items +from .structures import MultiDict as MultiDict +from .structures import OrderedMultiDict as OrderedMultiDict +from .structures import TypeConversionDict as TypeConversionDict diff --git a/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/__init__.cpython-312.pyc b/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..40d78ee059c453653e79e2b48934217d7bd0ecae GIT binary patch literal 1639 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/auth.cpython-312.pyc b/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/auth.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d8c312899ae20e549ce4602bc20d6d2497bf721a GIT binary patch literal 14453 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 Nz5ivHNnQLX{{cnATgU(a literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/csp.cpython-312.pyc b/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/csp.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2d6a0df496dd95aae3d03d2ae7f65cb5c7c96dc4 GIT binary patch literal 5297 zcmbtY|8E>e72nzW;`8O~^Vh^qV|!yeDVM}PL5}!`G~0GO%`+X_{fLOM z>?=h>^XPM??tRT%Hf=m6t`3?IxJetEh=;wmUBT>#e|JHyIY#o zEzNbsVh?BvT1uU$=}GAPkZejT()CDJ7z)01ayJ;AlJ1a6IMcK`M?G~>vr6=i?2~0b zwm{3S4D?EMZ|&mP49LvfnPSPM){N&Y+3vhqRNaDRsk0^5w5jVx!z22<=FK|Z{s7Wh zBO&1k&Mi`VappyHcE&Y5I#Sg11#OPHGfR{$RA_1L39zQ-daR_wiBtCp-7St5%X_H{ zmR6WGwDC$mjHsarMlV>7u37Flbi_P3p|slu%|ENs#_`QFx6ahuddE;z+QlQ#QL*qt zALD2CIq3X{Gm}@y-GFaOVkscz=jMjzmW#1NFzAy-hP%6H3q`527WieY(EK-TQfs0_O$=#THpp~LmT26NLtmdxa2$(plH+)Un>bEH zIK^=?!p$5vML5lID#C|2ZjNw<<8*{uI6f5NR*o|f&T`xm;Wmz2Bizn$Ho_eow?(*< zK@?P+ zJcL~X0>9!x+)-T5fq1GElg3` zoMzg5V@y1{W@EZqge1n-2=bU|V={tsWTri*!0r%P`6+P5u>$qx$F6yfDg?Nu;DBR{ zD#~S%&x#hT3rmD)N-;rP+u->Pwq<-7$RIFI3oab`B8BZN$5~Loe5i~IR65ligL3yz*&MWXyG@xaQ3s#awRQXjB{r9`y-m?u}b3rwP>Nz?0RL3a^lvgD`*=qCrvLo zY=JVQoz!C@81ps(Enu?yP3S<;6-`}lqzXn?q!qGETp0?%D9sXSMP8BAC|R#SU@s9E ziTKM>PM*9Y<>G!EBY>Y$Rns;-RrQ-4TP^XLkf)8~F~D*X+&2x~ZTi$=q>$C2xCXsXhmQkgef)>}4=+E;)4;@0eYsoYNQ;9C5R)}5aI zEn~aq)LQ)eEh1$_S^y);&rfC;ItLYLP;Nv;T8`|Uh6>Dpu;Ni>!-Xs>vt@o+!hjXl zqZpka2ge{v+>zOF*g)i9++PW_Y=(YCIY7&(5WEOfE@t?u^g*`s&E@swTI_o_w^PT2 zLXi=G0&6I)5LVcrkX-G2bma*qY%p(`pn+G&1>hVgjF1dg{Tu{g^63`koh3=2kf3>z%BMHv;y)(ry($`+MhlgNgoC|m2s@cNf_+PXHT zZZ&@#lQKtyNP!OkQG?&0KJb(xhKx_Cn}kR*7qG!&VwI=Cz)^7LY0(JNVi4nXO^0x+ zLkMGz6=uSZP;sz9)+iNRmqDk4O|b5j}LvJ3ZWgJ zlO7(qNsO3>`|)o@3MsGSgXE#_ZIEPzKpTH2fe+ZpJ)%7Dh=>Ejd5q9_P%lV?AMLA9 z-p{~+-y}ET$phSQo#81Q`w`g$KYODs4$l9x*b>Rfd*qo(?*9;2<*H-#c;VsoPK9)@m=;F4k^rwQnVU za^~IMiGR7M$alI9lRa`_kBsLMe*C&)+I|uqB_1{W1TQuGIA%)B5jG@eRSkUDPcAH? zKRn>ZKqQ1ZI2nlf8IH&d%@$@f;e{*V{RlQNtEFnBPvEk?-CcdG3Ubd*1@I~38LN@lpvPFQPY~g^HEn*nU7FCojz9?JN zsBG~?*}@62Y~h`#Y~e_hEn=y#Y*9VO56Tw75tc0h;$@4<%N9Q$-;?6iLy`IfH`SUvLFFN?_!t`8Rip)(U`{#b`E>bV#7l` z1}c$vda;KMclOf|SXQM^={~+ac&^@kzB+lYp&Rk<#$$=o_tSEs?|v$g=)d0@OB}tQ zjweps&ya+&o0JphxtH_Po6I$#d*@5|4-fb;hDJYGz<=*thOv=*HtP#d`yn&He?j7A sjI(P%g5lj&p!qmP2>G)#_@Oj(Kba=UPmjJp$obk!x3B(P!X6jk-&B!y3jhEB literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/etag.cpython-312.pyc b/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/etag.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee75c703345924d83b19e97f08d350f1a3621e9e GIT binary patch literal 5081 zcmcIoO>7&-6`uVeMe#=~iT;;ut$ys7i7YvR3pyB9;jUXwdZFn`-?*kk}ghD_wP0P`oxMkWf^!Heagb~AGjI(np z@*iO%37sWkkRd@QL!vGWNqQrEK^T(tur9-|NB3MMLtfnrv_`K1>eGEdYxP>7e%%i= z0DA(!8`Og^3h5!Bb$T68MOT2<>-9hz^ah}fGh$?0!Sm*zHfE>KpHgkZQJrzkQDd5^ zju>jnHgwgo)Y!NYyQIcPRm(I~!_mgnxUD9sVH>8S#!XdIW2LM4RcO{IW3xNis6QrG z+!y~ zuVj0#^?kE_|8jb*x$EXT`Rzxt@-_eZE;TEE9NK_=Bvw)`3{bVqyc5t_A{AyK6P0|h zip@3ktO!oflo2P2(Z^Rg&5#Li<(T)~odU^-0}yU$tH&~@42mvns2ABKFRIqa1cV$o zDY&twCTE;+%S1o#RMm3^Rdr)jOC=l?w2a%ByQ1aLqTGd5jT$P1v6|H2XmD(29Go9d z0B?}smm-oBys0E8-k_+>h(tp;7mS;+L`pZf7m0MrM&-ciw4QCz>?rI{2f01?XdA59 z&jXnw-+D=1!@{ zX=X8#Q`&OAw#|~F@t9W6KnJu+CQ9H06U9~rF94wi@J%v-`>I-*i&>y&t6P=*&0xUH zohfdPap%-bT`k@LL8vn#QdE1v7qd)9i<|alJ%rpPZnH4g#S@e5aD?3qgjw!-WzGGy0zcL?U}eL&{OLmvs&Uw`$ZSJ%Tk*Ol<~59U8u*7MKurv&VUIzxck0_u!eNL?ae zTG>a2!I@`;St%pY2GHJC2w6&X1L$!za9~v{p9UT$rGNY0xM2dM*_h!qbuwi+nCdaJ zJL+^C&@kqL0>eAD0y)2gfn})VV0s#X5p>II0|uXP5}1IUgC7|~r-Z{1S*x74qhvM< z4x!U=uu{>tEi*CG`2eFCDyU$}WRkL6i^+K0agy`^EYcPvDzeE8tY{NX;8H=xPnL2r z$KIf8yeQSC(~VV;0CPuRr+pmA9Qn>iY7c$Ay=A36zx}y+e^$=w_W}x7_-^>Xo$!Gb zJs*A{7kmNjJv%?UJeXHnbG}v{A(aSG3#T#s_%n2#79qO0WrUCk88%k6UUFTCiDQ8O z7kua?x|9)aKwbJvX;Hl9mtlSuBW{cQxoM}od1o5F=H}S}I7?vXO;_|Hp7fEPioDBG zL=mr8%Kg#c;5l{N&~(VQU{eU!&VulKTI^6C3$1DCy{=ZU7)D${p0MI3hj5lfi&;mw z@+6o$R+Jy-f_Z7;6*$E{4+Ov~pztu+I)~t8J^=>cHp1@BM3CXYT~hZb)Li{~-};{ofaY9pA&Gje>m^ zIPM9t zlc_j0^bU0-1=W$GCJZPlvHqv+um@r>G-kj6N*)%BgAs4R6SGpLQ>Y!BNg8ibYEgO+ zSm}#MP;bn%5l=Zh3o?F-?r^zmL^cHBVu1uDYYYmr%953jdw|b=AIKb857sZVXS?qP z_T35W1FQ|StxT>34&85T%I*DezOgSC>{|~g+3v4`9bbplm2LU(!E7&9Qf^&xM`>B# z*~Euc$-gpqG4z;mb?7|1tcB#ihb4d9SJhxG$kw(9O4s-U@Ddd~Oy zX2e3+WtVP+&VQtJfoBI?*ZBLzxn; z-Vh-JdZ5yg`yUJ4j|IpzCnHipb^x02C2VS!jc62}Zw}q$sIW|pPTq1B({lw&#HG3Fx?slWfNi11bksl<>$acvK`UNLrD!Z2;jf1NN2vhzIXGn!-OK;9}^31*QB? zk=?0{#Hj2FRVD3XUpLp(Ns1VO&T8)l@=y{4;VaVm4|4EZ?|DHuo;~v&!C!XKe*-B( BWr6?z literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/file_storage.cpython-312.pyc b/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/file_storage.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bb2ab6da8793bc4807de44f539588ff84fd96a36 GIT binary patch literal 7957 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 f0{FDW%a!iTJ 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 M-$X<~tYt9%-@^uo*8l(j literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/mixins.cpython-312.pyc b/venv/Lib/site-packages/werkzeug/datastructures/__pycache__/mixins.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f79e8f812b901e428a9a8a2b07bc357d498bd0d2 GIT binary patch literal 11430 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/datastructures/accept.py b/venv/Lib/site-packages/werkzeug/datastructures/accept.py new file mode 100644 index 00000000..d80f0bbb --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/datastructures/accept.py @@ -0,0 +1,326 @@ +from __future__ import annotations + +import codecs +import re + +from .structures import ImmutableList + + +class Accept(ImmutableList): + """An :class:`Accept` object is just a list subclass for lists of + ``(value, quality)`` tuples. It is automatically sorted by specificity + and quality. + + All :class:`Accept` objects work similar to a list but provide extra + functionality for working with the data. Containment checks are + normalized to the rules of that header: + + >>> a = CharsetAccept([('ISO-8859-1', 1), ('utf-8', 0.7)]) + >>> a.best + 'ISO-8859-1' + >>> 'iso-8859-1' in a + True + >>> 'UTF8' in a + True + >>> 'utf7' in a + False + + To get the quality for an item you can use normal item lookup: + + >>> print a['utf-8'] + 0.7 + >>> a['utf7'] + 0 + + .. versionchanged:: 0.5 + :class:`Accept` objects are forced immutable now. + + .. versionchanged:: 1.0.0 + :class:`Accept` internal values are no longer ordered + alphabetically for equal quality tags. Instead the initial + order is preserved. + + """ + + def __init__(self, values=()): + if values is None: + list.__init__(self) + self.provided = False + elif isinstance(values, Accept): + self.provided = values.provided + list.__init__(self, values) + else: + self.provided = True + values = sorted( + values, key=lambda x: (self._specificity(x[0]), x[1]), reverse=True + ) + list.__init__(self, values) + + def _specificity(self, value): + """Returns a tuple describing the value's specificity.""" + return (value != "*",) + + def _value_matches(self, value, item): + """Check if a value matches a given accept item.""" + return item == "*" or item.lower() == value.lower() + + def __getitem__(self, key): + """Besides index lookup (getting item n) you can also pass it a string + to get the quality for the item. If the item is not in the list, the + returned quality is ``0``. + """ + if isinstance(key, str): + return self.quality(key) + return list.__getitem__(self, key) + + def quality(self, key): + """Returns the quality of the key. + + .. versionadded:: 0.6 + In previous versions you had to use the item-lookup syntax + (eg: ``obj[key]`` instead of ``obj.quality(key)``) + """ + for item, quality in self: + if self._value_matches(key, item): + return quality + return 0 + + def __contains__(self, value): + for item, _quality in self: + if self._value_matches(value, item): + return True + return False + + def __repr__(self): + pairs_str = ", ".join(f"({x!r}, {y})" for x, y in self) + return f"{type(self).__name__}([{pairs_str}])" + + def index(self, key): + """Get the position of an entry or raise :exc:`ValueError`. + + :param key: The key to be looked up. + + .. versionchanged:: 0.5 + This used to raise :exc:`IndexError`, which was inconsistent + with the list API. + """ + if isinstance(key, str): + for idx, (item, _quality) in enumerate(self): + if self._value_matches(key, item): + return idx + raise ValueError(key) + return list.index(self, key) + + def find(self, key): + """Get the position of an entry or return -1. + + :param key: The key to be looked up. + """ + try: + return self.index(key) + except ValueError: + return -1 + + def values(self): + """Iterate over all values.""" + for item in self: + yield item[0] + + def to_header(self): + """Convert the header set into an HTTP header string.""" + result = [] + for value, quality in self: + if quality != 1: + value = f"{value};q={quality}" + result.append(value) + return ",".join(result) + + def __str__(self): + return self.to_header() + + def _best_single_match(self, match): + for client_item, quality in self: + if self._value_matches(match, client_item): + # self is sorted by specificity descending, we can exit + return client_item, quality + return None + + def best_match(self, matches, default=None): + """Returns the best match from a list of possible matches based + on the specificity and quality of the client. If two items have the + same quality and specificity, the one is returned that comes first. + + :param matches: a list of matches to check for + :param default: the value that is returned if none match + """ + result = default + best_quality = -1 + best_specificity = (-1,) + for server_item in matches: + match = self._best_single_match(server_item) + if not match: + continue + client_item, quality = match + specificity = self._specificity(client_item) + if quality <= 0 or quality < best_quality: + continue + # better quality or same quality but more specific => better match + if quality > best_quality or specificity > best_specificity: + result = server_item + best_quality = quality + best_specificity = specificity + return result + + @property + def best(self): + """The best match as value.""" + if self: + return self[0][0] + + +_mime_split_re = re.compile(r"/|(?:\s*;\s*)") + + +def _normalize_mime(value): + return _mime_split_re.split(value.lower()) + + +class MIMEAccept(Accept): + """Like :class:`Accept` but with special methods and behavior for + mimetypes. + """ + + def _specificity(self, value): + return tuple(x != "*" for x in _mime_split_re.split(value)) + + def _value_matches(self, value, item): + # item comes from the client, can't match if it's invalid. + if "/" not in item: + return False + + # value comes from the application, tell the developer when it + # doesn't look valid. + if "/" not in value: + raise ValueError(f"invalid mimetype {value!r}") + + # Split the match value into type, subtype, and a sorted list of parameters. + normalized_value = _normalize_mime(value) + value_type, value_subtype = normalized_value[:2] + value_params = sorted(normalized_value[2:]) + + # "*/*" is the only valid value that can start with "*". + if value_type == "*" and value_subtype != "*": + raise ValueError(f"invalid mimetype {value!r}") + + # Split the accept item into type, subtype, and parameters. + normalized_item = _normalize_mime(item) + item_type, item_subtype = normalized_item[:2] + item_params = sorted(normalized_item[2:]) + + # "*/not-*" from the client is invalid, can't match. + if item_type == "*" and item_subtype != "*": + return False + + return ( + (item_type == "*" and item_subtype == "*") + or (value_type == "*" and value_subtype == "*") + ) or ( + item_type == value_type + and ( + item_subtype == "*" + or value_subtype == "*" + or (item_subtype == value_subtype and item_params == value_params) + ) + ) + + @property + def accept_html(self): + """True if this object accepts HTML.""" + return ( + "text/html" in self or "application/xhtml+xml" in self or self.accept_xhtml + ) + + @property + def accept_xhtml(self): + """True if this object accepts XHTML.""" + return "application/xhtml+xml" in self or "application/xml" in self + + @property + def accept_json(self): + """True if this object accepts JSON.""" + return "application/json" in self + + +_locale_delim_re = re.compile(r"[_-]") + + +def _normalize_lang(value): + """Process a language tag for matching.""" + return _locale_delim_re.split(value.lower()) + + +class LanguageAccept(Accept): + """Like :class:`Accept` but with normalization for language tags.""" + + def _value_matches(self, value, item): + return item == "*" or _normalize_lang(value) == _normalize_lang(item) + + def best_match(self, matches, default=None): + """Given a list of supported values, finds the best match from + the list of accepted values. + + Language tags are normalized for the purpose of matching, but + are returned unchanged. + + If no exact match is found, this will fall back to matching + the first subtag (primary language only), first with the + accepted values then with the match values. This partial is not + applied to any other language subtags. + + The default is returned if no exact or fallback match is found. + + :param matches: A list of supported languages to find a match. + :param default: The value that is returned if none match. + """ + # Look for an exact match first. If a client accepts "en-US", + # "en-US" is a valid match at this point. + result = super().best_match(matches) + + if result is not None: + return result + + # Fall back to accepting primary tags. If a client accepts + # "en-US", "en" is a valid match at this point. Need to use + # re.split to account for 2 or 3 letter codes. + fallback = Accept( + [(_locale_delim_re.split(item[0], 1)[0], item[1]) for item in self] + ) + result = fallback.best_match(matches) + + if result is not None: + return result + + # Fall back to matching primary tags. If the client accepts + # "en", "en-US" is a valid match at this point. + fallback_matches = [_locale_delim_re.split(item, 1)[0] for item in matches] + result = super().best_match(fallback_matches) + + # Return a value from the original match list. Find the first + # original value that starts with the matched primary tag. + if result is not None: + return next(item for item in matches if item.startswith(result)) + + return default + + +class CharsetAccept(Accept): + """Like :class:`Accept` but with normalization for charsets.""" + + def _value_matches(self, value, item): + def _normalize(name): + try: + return codecs.lookup(name).name + except LookupError: + return name.lower() + + return item == "*" or _normalize(value) == _normalize(item) diff --git a/venv/Lib/site-packages/werkzeug/datastructures/accept.pyi b/venv/Lib/site-packages/werkzeug/datastructures/accept.pyi new file mode 100644 index 00000000..4b74dd95 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/datastructures/accept.pyi @@ -0,0 +1,54 @@ +from collections.abc import Iterable +from collections.abc import Iterator +from typing import overload + +from .structures import ImmutableList + +class Accept(ImmutableList[tuple[str, int]]): + provided: bool + def __init__( + self, values: Accept | Iterable[tuple[str, float]] | None = None + ) -> None: ... + def _specificity(self, value: str) -> tuple[bool, ...]: ... + def _value_matches(self, value: str, item: str) -> bool: ... + @overload # type: ignore + def __getitem__(self, key: str) -> int: ... + @overload + def __getitem__(self, key: int) -> tuple[str, int]: ... + @overload + def __getitem__(self, key: slice) -> Iterable[tuple[str, int]]: ... + def quality(self, key: str) -> int: ... + def __contains__(self, value: str) -> bool: ... # type: ignore + def index(self, key: str) -> int: ... # type: ignore + def find(self, key: str) -> int: ... + def values(self) -> Iterator[str]: ... + def to_header(self) -> str: ... + def _best_single_match(self, match: str) -> tuple[str, int] | None: ... + @overload + def best_match(self, matches: Iterable[str], default: str) -> str: ... + @overload + def best_match( + self, matches: Iterable[str], default: str | None = None + ) -> str | None: ... + @property + def best(self) -> str: ... + +def _normalize_mime(value: str) -> list[str]: ... + +class MIMEAccept(Accept): + def _specificity(self, value: str) -> tuple[bool, ...]: ... + def _value_matches(self, value: str, item: str) -> bool: ... + @property + def accept_html(self) -> bool: ... + @property + def accept_xhtml(self) -> bool: ... + @property + def accept_json(self) -> bool: ... + +def _normalize_lang(value: str) -> list[str]: ... + +class LanguageAccept(Accept): + def _value_matches(self, value: str, item: str) -> bool: ... + +class CharsetAccept(Accept): + def _value_matches(self, value: str, item: str) -> bool: ... diff --git a/venv/Lib/site-packages/werkzeug/datastructures/auth.py b/venv/Lib/site-packages/werkzeug/datastructures/auth.py new file mode 100644 index 00000000..a3ca0de4 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/datastructures/auth.py @@ -0,0 +1,316 @@ +from __future__ import annotations + +import base64 +import binascii +import typing as t + +from ..http import dump_header +from ..http import parse_dict_header +from ..http import quote_header_value +from .structures import CallbackDict + +if t.TYPE_CHECKING: + import typing_extensions as te + + +class Authorization: + """Represents the parts of an ``Authorization`` request header. + + :attr:`.Request.authorization` returns an instance if the header is set. + + An instance can be used with the test :class:`.Client` request methods' ``auth`` + parameter to send the header in test requests. + + Depending on the auth scheme, either :attr:`parameters` or :attr:`token` will be + set. The ``Basic`` scheme's token is decoded into the ``username`` and ``password`` + parameters. + + For convenience, ``auth["key"]`` and ``auth.key`` both access the key in the + :attr:`parameters` dict, along with ``auth.get("key")`` and ``"key" in auth``. + + .. versionchanged:: 2.3 + The ``token`` parameter and attribute was added to support auth schemes that use + a token instead of parameters, such as ``Bearer``. + + .. versionchanged:: 2.3 + The object is no longer a ``dict``. + + .. versionchanged:: 0.5 + The object is an immutable dict. + """ + + def __init__( + self, + auth_type: str, + data: dict[str, str | None] | None = None, + token: str | None = None, + ) -> None: + self.type = auth_type + """The authorization scheme, like ``basic``, ``digest``, or ``bearer``.""" + + if data is None: + data = {} + + self.parameters = data + """A dict of parameters parsed from the header. Either this or :attr:`token` + will have a value for a given scheme. + """ + + self.token = token + """A token parsed from the header. Either this or :attr:`parameters` will have a + value for a given scheme. + + .. versionadded:: 2.3 + """ + + def __getattr__(self, name: str) -> str | None: + return self.parameters.get(name) + + def __getitem__(self, name: str) -> str | None: + return self.parameters.get(name) + + def get(self, key: str, default: str | None = None) -> str | None: + return self.parameters.get(key, default) + + def __contains__(self, key: str) -> bool: + return key in self.parameters + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Authorization): + return NotImplemented + + return ( + other.type == self.type + and other.token == self.token + and other.parameters == self.parameters + ) + + @classmethod + def from_header(cls, value: str | None) -> te.Self | None: + """Parse an ``Authorization`` header value and return an instance, or ``None`` + if the value is empty. + + :param value: The header value to parse. + + .. versionadded:: 2.3 + """ + if not value: + return None + + scheme, _, rest = value.partition(" ") + scheme = scheme.lower() + rest = rest.strip() + + if scheme == "basic": + try: + username, _, password = base64.b64decode(rest).decode().partition(":") + except (binascii.Error, UnicodeError): + return None + + return cls(scheme, {"username": username, "password": password}) + + if "=" in rest.rstrip("="): + # = that is not trailing, this is parameters. + return cls(scheme, parse_dict_header(rest), None) + + # No = or only trailing =, this is a token. + return cls(scheme, None, rest) + + def to_header(self) -> str: + """Produce an ``Authorization`` header value representing this data. + + .. versionadded:: 2.0 + """ + if self.type == "basic": + value = base64.b64encode( + f"{self.username}:{self.password}".encode() + ).decode("ascii") + return f"Basic {value}" + + if self.token is not None: + return f"{self.type.title()} {self.token}" + + return f"{self.type.title()} {dump_header(self.parameters)}" + + def __str__(self) -> str: + return self.to_header() + + def __repr__(self) -> str: + return f"<{type(self).__name__} {self.to_header()}>" + + +class WWWAuthenticate: + """Represents the parts of a ``WWW-Authenticate`` response header. + + Set :attr:`.Response.www_authenticate` to an instance of list of instances to set + values for this header in the response. Modifying this instance will modify the + header value. + + Depending on the auth scheme, either :attr:`parameters` or :attr:`token` should be + set. The ``Basic`` scheme will encode ``username`` and ``password`` parameters to a + token. + + For convenience, ``auth["key"]`` and ``auth.key`` both act on the :attr:`parameters` + dict, and can be used to get, set, or delete parameters. ``auth.get("key")`` and + ``"key" in auth`` are also provided. + + .. versionchanged:: 2.3 + The ``token`` parameter and attribute was added to support auth schemes that use + a token instead of parameters, such as ``Bearer``. + + .. versionchanged:: 2.3 + The object is no longer a ``dict``. + + .. versionchanged:: 2.3 + The ``on_update`` parameter was removed. + """ + + def __init__( + self, + auth_type: str, + values: dict[str, str | None] | None = None, + token: str | None = None, + ): + self._type = auth_type.lower() + self._parameters: dict[str, str | None] = CallbackDict( + values, lambda _: self._trigger_on_update() + ) + self._token = token + self._on_update: t.Callable[[WWWAuthenticate], None] | None = None + + def _trigger_on_update(self) -> None: + if self._on_update is not None: + self._on_update(self) + + @property + def type(self) -> str: + """The authorization scheme, like ``basic``, ``digest``, or ``bearer``.""" + return self._type + + @type.setter + def type(self, value: str) -> None: + self._type = value + self._trigger_on_update() + + @property + 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. + """ + return self._parameters + + @parameters.setter + def parameters(self, value: dict[str, str]) -> None: + self._parameters = CallbackDict(value, lambda _: self._trigger_on_update()) + self._trigger_on_update() + + @property + def token(self) -> str | None: + """A dict of parameters for the header. Only one of this or :attr:`token` should + have a value for a given scheme. + """ + return self._token + + @token.setter + def token(self, value: str | None) -> None: + """A token for the header. Only one of this or :attr:`parameters` should have a + value for a given scheme. + + .. versionadded:: 2.3 + """ + self._token = value + self._trigger_on_update() + + def __getitem__(self, key: str) -> str | None: + return self.parameters.get(key) + + def __setitem__(self, key: str, value: str | None) -> None: + if value is None: + if key in self.parameters: + del self.parameters[key] + else: + self.parameters[key] = value + + self._trigger_on_update() + + def __delitem__(self, key: str) -> None: + if key in self.parameters: + del self.parameters[key] + self._trigger_on_update() + + def __getattr__(self, name: str) -> str | None: + return self[name] + + def __setattr__(self, name: str, value: str | None) -> None: + if name in {"_type", "_parameters", "_token", "_on_update"}: + super().__setattr__(name, value) + else: + self[name] = value + + def __delattr__(self, name: str) -> None: + del self[name] + + def __contains__(self, key: str) -> bool: + return key in self.parameters + + def __eq__(self, other: object) -> bool: + if not isinstance(other, WWWAuthenticate): + return NotImplemented + + return ( + other.type == self.type + and other.token == self.token + and other.parameters == self.parameters + ) + + def get(self, key: str, default: str | None = None) -> str | None: + return self.parameters.get(key, default) + + @classmethod + def from_header(cls, value: str | None) -> te.Self | None: + """Parse a ``WWW-Authenticate`` header value and return an instance, or ``None`` + if the value is empty. + + :param value: The header value to parse. + + .. versionadded:: 2.3 + """ + if not value: + return None + + scheme, _, rest = value.partition(" ") + scheme = scheme.lower() + rest = rest.strip() + + if "=" in rest.rstrip("="): + # = that is not trailing, this is parameters. + return cls(scheme, parse_dict_header(rest), None) + + # No = or only trailing =, this is a token. + return cls(scheme, None, rest) + + def to_header(self) -> str: + """Produce a ``WWW-Authenticate`` header value representing this data.""" + if self.token is not None: + return f"{self.type.title()} {self.token}" + + if self.type == "digest": + items = [] + + for key, value in self.parameters.items(): + if key in {"realm", "domain", "nonce", "opaque", "qop"}: + value = quote_header_value(value, allow_token=False) + else: + value = quote_header_value(value) + + items.append(f"{key}={value}") + + return f"Digest {', '.join(items)}" + + return f"{self.type.title()} {dump_header(self.parameters)}" + + def __str__(self) -> str: + return self.to_header() + + def __repr__(self) -> str: + return f"<{type(self).__name__} {self.to_header()}>" diff --git a/venv/Lib/site-packages/werkzeug/datastructures/cache_control.py b/venv/Lib/site-packages/werkzeug/datastructures/cache_control.py new file mode 100644 index 00000000..bff4c18b --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/datastructures/cache_control.py @@ -0,0 +1,175 @@ +from __future__ import annotations + +from .mixins import ImmutableDictMixin +from .mixins import UpdateDictMixin + + +def cache_control_property(key, empty, type): + """Return a new property object for a cache header. Useful if you + want to add support for a cache extension in a subclass. + + .. versionchanged:: 2.0 + Renamed from ``cache_property``. + """ + return property( + lambda x: x._get_cache_value(key, empty, type), + lambda x, v: x._set_cache_value(key, v, type), + lambda x: x._del_cache_value(key), + f"accessor for {key!r}", + ) + + +class _CacheControl(UpdateDictMixin, dict): + """Subclass of a dict that stores values for a Cache-Control header. It + has accessors for all the cache-control directives specified in RFC 2616. + The class does not differentiate between request and response directives. + + Because the cache-control directives in the HTTP header use dashes the + python descriptors use underscores for that. + + To get a header of the :class:`CacheControl` object again you can convert + the object into a string or call the :meth:`to_header` method. If you plan + to subclass it and add your own items have a look at the sourcecode for + that class. + + .. versionchanged:: 2.1.0 + Setting int properties such as ``max_age`` will convert the + value to an int. + + .. versionchanged:: 0.4 + + Setting `no_cache` or `private` to boolean `True` will set the implicit + none-value which is ``*``: + + >>> cc = ResponseCacheControl() + >>> cc.no_cache = True + >>> cc + + >>> cc.no_cache + '*' + >>> cc.no_cache = None + >>> cc + + + In versions before 0.5 the behavior documented here affected the now + no longer existing `CacheControl` class. + """ + + no_cache = cache_control_property("no-cache", "*", None) + no_store = cache_control_property("no-store", None, bool) + max_age = cache_control_property("max-age", -1, int) + no_transform = cache_control_property("no-transform", None, None) + + def __init__(self, values=(), on_update=None): + dict.__init__(self, values or ()) + self.on_update = on_update + self.provided = values is not None + + def _get_cache_value(self, key, empty, type): + """Used internally by the accessor properties.""" + if type is bool: + return key in self + if key in self: + value = self[key] + if value is None: + return empty + elif type is not None: + try: + value = type(value) + except ValueError: + pass + return value + return None + + def _set_cache_value(self, key, value, type): + """Used internally by the accessor properties.""" + if type is bool: + if value: + self[key] = None + else: + self.pop(key, None) + else: + if value is None: + self.pop(key, None) + elif value is True: + self[key] = None + else: + if type is not None: + self[key] = type(value) + else: + self[key] = value + + def _del_cache_value(self, key): + """Used internally by the accessor properties.""" + if key in self: + del self[key] + + def to_header(self): + """Convert the stored values into a cache control header.""" + return http.dump_header(self) + + def __str__(self): + return self.to_header() + + def __repr__(self): + kv_str = " ".join(f"{k}={v!r}" for k, v in sorted(self.items())) + return f"<{type(self).__name__} {kv_str}>" + + cache_property = staticmethod(cache_control_property) + + +class RequestCacheControl(ImmutableDictMixin, _CacheControl): + """A cache control for requests. This is immutable and gives access + to all the request-relevant cache control headers. + + To get a header of the :class:`RequestCacheControl` object again you can + convert the object into a string or call the :meth:`to_header` method. If + you plan to subclass it and add your own items have a look at the sourcecode + for that class. + + .. versionchanged:: 2.1.0 + Setting int properties such as ``max_age`` will convert the + value to an int. + + .. versionadded:: 0.5 + In previous versions a `CacheControl` class existed that was used + both for request and response. + """ + + max_stale = cache_control_property("max-stale", "*", int) + min_fresh = cache_control_property("min-fresh", "*", int) + only_if_cached = cache_control_property("only-if-cached", None, bool) + + +class ResponseCacheControl(_CacheControl): + """A cache control for responses. Unlike :class:`RequestCacheControl` + this is mutable and gives access to response-relevant cache control + headers. + + To get a header of the :class:`ResponseCacheControl` object again you can + convert the object into a string or call the :meth:`to_header` method. If + you plan to subclass it and add your own items have a look at the sourcecode + for that class. + + .. versionchanged:: 2.1.1 + ``s_maxage`` converts the value to an int. + + .. versionchanged:: 2.1.0 + Setting int properties such as ``max_age`` will convert the + value to an int. + + .. versionadded:: 0.5 + In previous versions a `CacheControl` class existed that was used + both for request and response. + """ + + public = cache_control_property("public", None, bool) + private = cache_control_property("private", "*", None) + must_revalidate = cache_control_property("must-revalidate", None, bool) + proxy_revalidate = cache_control_property("proxy-revalidate", None, bool) + s_maxage = cache_control_property("s-maxage", None, int) + immutable = cache_control_property("immutable", None, bool) + + +# circular dependencies +from .. import http diff --git a/venv/Lib/site-packages/werkzeug/datastructures/cache_control.pyi b/venv/Lib/site-packages/werkzeug/datastructures/cache_control.pyi new file mode 100644 index 00000000..54ec0208 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/datastructures/cache_control.pyi @@ -0,0 +1,115 @@ +from collections.abc import Callable +from collections.abc import Iterable +from collections.abc import Mapping +from typing import TypeVar + +from .mixins import ImmutableDictMixin +from .mixins import UpdateDictMixin + +T = TypeVar("T") +_CPT = TypeVar("_CPT", str, int, bool) + +def cache_control_property( + key: str, empty: _CPT | None, type: type[_CPT] +) -> property: ... + +class _CacheControl( + UpdateDictMixin[str, str | int | bool | None], dict[str, str | int | bool | None] +): + provided: bool + def __init__( + self, + values: Mapping[str, str | int | bool | None] + | Iterable[tuple[str, str | int | bool | None]] = (), + on_update: Callable[[_CacheControl], None] | None = None, + ) -> None: ... + @property + def no_cache(self) -> bool | None: ... + @no_cache.setter + def no_cache(self, value: bool | None) -> None: ... + @no_cache.deleter + def no_cache(self) -> None: ... + @property + def no_store(self) -> bool | None: ... + @no_store.setter + def no_store(self, value: bool | None) -> None: ... + @no_store.deleter + def no_store(self) -> None: ... + @property + def max_age(self) -> int | None: ... + @max_age.setter + def max_age(self, value: int | None) -> None: ... + @max_age.deleter + def max_age(self) -> None: ... + @property + def no_transform(self) -> bool | None: ... + @no_transform.setter + def no_transform(self, value: bool | None) -> None: ... + @no_transform.deleter + def no_transform(self) -> None: ... + def _get_cache_value(self, key: str, empty: T | None, type: type[T]) -> T: ... + def _set_cache_value(self, key: str, value: T | None, type: type[T]) -> None: ... + def _del_cache_value(self, key: str) -> None: ... + def to_header(self) -> str: ... + @staticmethod + def cache_property(key: str, empty: _CPT | None, type: type[_CPT]) -> property: ... + +class RequestCacheControl( # type: ignore[misc] + ImmutableDictMixin[str, str | int | bool | None], _CacheControl +): + @property + def max_stale(self) -> int | None: ... + @max_stale.setter + def max_stale(self, value: int | None) -> None: ... + @max_stale.deleter + def max_stale(self) -> None: ... + @property + def min_fresh(self) -> int | None: ... + @min_fresh.setter + def min_fresh(self, value: int | None) -> None: ... + @min_fresh.deleter + def min_fresh(self) -> None: ... + @property + def only_if_cached(self) -> bool | None: ... + @only_if_cached.setter + def only_if_cached(self, value: bool | None) -> None: ... + @only_if_cached.deleter + def only_if_cached(self) -> None: ... + +class ResponseCacheControl(_CacheControl): + @property + def public(self) -> bool | None: ... + @public.setter + def public(self, value: bool | None) -> None: ... + @public.deleter + def public(self) -> None: ... + @property + def private(self) -> bool | None: ... + @private.setter + def private(self, value: bool | None) -> None: ... + @private.deleter + def private(self) -> None: ... + @property + def must_revalidate(self) -> bool | None: ... + @must_revalidate.setter + def must_revalidate(self, value: bool | None) -> None: ... + @must_revalidate.deleter + def must_revalidate(self) -> None: ... + @property + def proxy_revalidate(self) -> bool | None: ... + @proxy_revalidate.setter + def proxy_revalidate(self, value: bool | None) -> None: ... + @proxy_revalidate.deleter + def proxy_revalidate(self) -> None: ... + @property + def s_maxage(self) -> int | None: ... + @s_maxage.setter + def s_maxage(self, value: int | None) -> None: ... + @s_maxage.deleter + def s_maxage(self) -> None: ... + @property + def immutable(self) -> bool | None: ... + @immutable.setter + def immutable(self, value: bool | None) -> None: ... + @immutable.deleter + def immutable(self) -> None: ... diff --git a/venv/Lib/site-packages/werkzeug/datastructures/csp.py b/venv/Lib/site-packages/werkzeug/datastructures/csp.py new file mode 100644 index 00000000..dde94149 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/datastructures/csp.py @@ -0,0 +1,94 @@ +from __future__ import annotations + +from .mixins import UpdateDictMixin + + +def csp_property(key): + """Return a new property object for a content security policy header. + Useful if you want to add support for a csp extension in a + subclass. + """ + return property( + lambda x: x._get_value(key), + lambda x, v: x._set_value(key, v), + lambda x: x._del_value(key), + f"accessor for {key!r}", + ) + + +class ContentSecurityPolicy(UpdateDictMixin, dict): + """Subclass of a dict that stores values for a Content Security Policy + header. It has accessors for all the level 3 policies. + + Because the csp directives in the HTTP header use dashes the + python descriptors use underscores for that. + + To get a header of the :class:`ContentSecuirtyPolicy` object again + you can convert the object into a string or call the + :meth:`to_header` method. If you plan to subclass it and add your + own items have a look at the sourcecode for that class. + + .. versionadded:: 1.0.0 + Support for Content Security Policy headers was added. + + """ + + base_uri = csp_property("base-uri") + child_src = csp_property("child-src") + connect_src = csp_property("connect-src") + default_src = csp_property("default-src") + font_src = csp_property("font-src") + form_action = csp_property("form-action") + frame_ancestors = csp_property("frame-ancestors") + frame_src = csp_property("frame-src") + img_src = csp_property("img-src") + manifest_src = csp_property("manifest-src") + media_src = csp_property("media-src") + navigate_to = csp_property("navigate-to") + object_src = csp_property("object-src") + prefetch_src = csp_property("prefetch-src") + plugin_types = csp_property("plugin-types") + report_to = csp_property("report-to") + report_uri = csp_property("report-uri") + sandbox = csp_property("sandbox") + script_src = csp_property("script-src") + script_src_attr = csp_property("script-src-attr") + script_src_elem = csp_property("script-src-elem") + style_src = csp_property("style-src") + style_src_attr = csp_property("style-src-attr") + style_src_elem = csp_property("style-src-elem") + worker_src = csp_property("worker-src") + + def __init__(self, values=(), on_update=None): + dict.__init__(self, values or ()) + self.on_update = on_update + self.provided = values is not None + + def _get_value(self, key): + """Used internally by the accessor properties.""" + return self.get(key) + + def _set_value(self, key, value): + """Used internally by the accessor properties.""" + if value is None: + self.pop(key, None) + else: + self[key] = value + + def _del_value(self, key): + """Used internally by the accessor properties.""" + if key in self: + del self[key] + + def to_header(self): + """Convert the stored values into a cache control header.""" + from ..http import dump_csp_header + + return dump_csp_header(self) + + def __str__(self): + return self.to_header() + + def __repr__(self): + kv_str = " ".join(f"{k}={v!r}" for k, v in sorted(self.items())) + return f"<{type(self).__name__} {kv_str}>" diff --git a/venv/Lib/site-packages/werkzeug/datastructures/csp.pyi b/venv/Lib/site-packages/werkzeug/datastructures/csp.pyi new file mode 100644 index 00000000..f9e2ac0f --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/datastructures/csp.pyi @@ -0,0 +1,169 @@ +from collections.abc import Callable +from collections.abc import Iterable +from collections.abc import Mapping + +from .mixins import UpdateDictMixin + +def csp_property(key: str) -> property: ... + +class ContentSecurityPolicy(UpdateDictMixin[str, str], dict[str, str]): + @property + def base_uri(self) -> str | None: ... + @base_uri.setter + def base_uri(self, value: str | None) -> None: ... + @base_uri.deleter + def base_uri(self) -> None: ... + @property + def child_src(self) -> str | None: ... + @child_src.setter + def child_src(self, value: str | None) -> None: ... + @child_src.deleter + def child_src(self) -> None: ... + @property + def connect_src(self) -> str | None: ... + @connect_src.setter + def connect_src(self, value: str | None) -> None: ... + @connect_src.deleter + def connect_src(self) -> None: ... + @property + def default_src(self) -> str | None: ... + @default_src.setter + def default_src(self, value: str | None) -> None: ... + @default_src.deleter + def default_src(self) -> None: ... + @property + def font_src(self) -> str | None: ... + @font_src.setter + def font_src(self, value: str | None) -> None: ... + @font_src.deleter + def font_src(self) -> None: ... + @property + def form_action(self) -> str | None: ... + @form_action.setter + def form_action(self, value: str | None) -> None: ... + @form_action.deleter + def form_action(self) -> None: ... + @property + def frame_ancestors(self) -> str | None: ... + @frame_ancestors.setter + def frame_ancestors(self, value: str | None) -> None: ... + @frame_ancestors.deleter + def frame_ancestors(self) -> None: ... + @property + def frame_src(self) -> str | None: ... + @frame_src.setter + def frame_src(self, value: str | None) -> None: ... + @frame_src.deleter + def frame_src(self) -> None: ... + @property + def img_src(self) -> str | None: ... + @img_src.setter + def img_src(self, value: str | None) -> None: ... + @img_src.deleter + def img_src(self) -> None: ... + @property + def manifest_src(self) -> str | None: ... + @manifest_src.setter + def manifest_src(self, value: str | None) -> None: ... + @manifest_src.deleter + def manifest_src(self) -> None: ... + @property + def media_src(self) -> str | None: ... + @media_src.setter + def media_src(self, value: str | None) -> None: ... + @media_src.deleter + def media_src(self) -> None: ... + @property + def navigate_to(self) -> str | None: ... + @navigate_to.setter + def navigate_to(self, value: str | None) -> None: ... + @navigate_to.deleter + def navigate_to(self) -> None: ... + @property + def object_src(self) -> str | None: ... + @object_src.setter + def object_src(self, value: str | None) -> None: ... + @object_src.deleter + def object_src(self) -> None: ... + @property + def prefetch_src(self) -> str | None: ... + @prefetch_src.setter + def prefetch_src(self, value: str | None) -> None: ... + @prefetch_src.deleter + def prefetch_src(self) -> None: ... + @property + def plugin_types(self) -> str | None: ... + @plugin_types.setter + def plugin_types(self, value: str | None) -> None: ... + @plugin_types.deleter + def plugin_types(self) -> None: ... + @property + def report_to(self) -> str | None: ... + @report_to.setter + def report_to(self, value: str | None) -> None: ... + @report_to.deleter + def report_to(self) -> None: ... + @property + def report_uri(self) -> str | None: ... + @report_uri.setter + def report_uri(self, value: str | None) -> None: ... + @report_uri.deleter + def report_uri(self) -> None: ... + @property + def sandbox(self) -> str | None: ... + @sandbox.setter + def sandbox(self, value: str | None) -> None: ... + @sandbox.deleter + def sandbox(self) -> None: ... + @property + def script_src(self) -> str | None: ... + @script_src.setter + def script_src(self, value: str | None) -> None: ... + @script_src.deleter + def script_src(self) -> None: ... + @property + def script_src_attr(self) -> str | None: ... + @script_src_attr.setter + def script_src_attr(self, value: str | None) -> None: ... + @script_src_attr.deleter + def script_src_attr(self) -> None: ... + @property + def script_src_elem(self) -> str | None: ... + @script_src_elem.setter + def script_src_elem(self, value: str | None) -> None: ... + @script_src_elem.deleter + def script_src_elem(self) -> None: ... + @property + def style_src(self) -> str | None: ... + @style_src.setter + def style_src(self, value: str | None) -> None: ... + @style_src.deleter + def style_src(self) -> None: ... + @property + def style_src_attr(self) -> str | None: ... + @style_src_attr.setter + def style_src_attr(self, value: str | None) -> None: ... + @style_src_attr.deleter + def style_src_attr(self) -> None: ... + @property + def style_src_elem(self) -> str | None: ... + @style_src_elem.setter + def style_src_elem(self, value: str | None) -> None: ... + @style_src_elem.deleter + def style_src_elem(self) -> None: ... + @property + def worker_src(self) -> str | None: ... + @worker_src.setter + def worker_src(self, value: str | None) -> None: ... + @worker_src.deleter + def worker_src(self) -> None: ... + provided: bool + def __init__( + self, + values: Mapping[str, str] | Iterable[tuple[str, str]] = (), + on_update: Callable[[ContentSecurityPolicy], None] | None = None, + ) -> None: ... + def _get_value(self, key: str) -> str | None: ... + def _set_value(self, key: str, value: str) -> None: ... + def _del_value(self, key: str) -> None: ... + def to_header(self) -> str: ... diff --git a/venv/Lib/site-packages/werkzeug/datastructures/etag.py b/venv/Lib/site-packages/werkzeug/datastructures/etag.py new file mode 100644 index 00000000..747d9966 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/datastructures/etag.py @@ -0,0 +1,95 @@ +from __future__ import annotations + +from collections.abc import Collection + + +class ETags(Collection): + """A set that can be used to check if one etag is present in a collection + of etags. + """ + + def __init__(self, strong_etags=None, weak_etags=None, star_tag=False): + if not star_tag and strong_etags: + self._strong = frozenset(strong_etags) + else: + self._strong = frozenset() + + self._weak = frozenset(weak_etags or ()) + self.star_tag = star_tag + + def as_set(self, include_weak=False): + """Convert the `ETags` object into a python set. Per default all the + weak etags are not part of this set.""" + rv = set(self._strong) + if include_weak: + rv.update(self._weak) + return rv + + def is_weak(self, etag): + """Check if an etag is weak.""" + return etag in self._weak + + def is_strong(self, etag): + """Check if an etag is strong.""" + return etag in self._strong + + def contains_weak(self, etag): + """Check if an etag is part of the set including weak and strong tags.""" + return self.is_weak(etag) or self.contains(etag) + + def contains(self, etag): + """Check if an etag is part of the set ignoring weak tags. + It is also possible to use the ``in`` operator. + """ + if self.star_tag: + return True + return self.is_strong(etag) + + def contains_raw(self, etag): + """When passed a quoted tag it will check if this tag is part of the + set. If the tag is weak it is checked against weak and strong tags, + otherwise strong only.""" + from ..http import unquote_etag + + etag, weak = unquote_etag(etag) + if weak: + return self.contains_weak(etag) + return self.contains(etag) + + def to_header(self): + """Convert the etags set into a HTTP header string.""" + if self.star_tag: + return "*" + return ", ".join( + [f'"{x}"' for x in self._strong] + [f'W/"{x}"' for x in self._weak] + ) + + def __call__(self, etag=None, data=None, include_weak=False): + if [etag, data].count(None) != 1: + raise TypeError("either tag or data required, but at least one") + if etag is None: + from ..http import generate_etag + + etag = generate_etag(data) + if include_weak: + if etag in self._weak: + return True + return etag in self._strong + + def __bool__(self): + return bool(self.star_tag or self._strong or self._weak) + + def __str__(self): + return self.to_header() + + def __len__(self): + return len(self._strong) + + def __iter__(self): + return iter(self._strong) + + def __contains__(self, etag): + return self.contains(etag) + + def __repr__(self): + return f"<{type(self).__name__} {str(self)!r}>" diff --git a/venv/Lib/site-packages/werkzeug/datastructures/etag.pyi b/venv/Lib/site-packages/werkzeug/datastructures/etag.pyi new file mode 100644 index 00000000..88e54f15 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/datastructures/etag.pyi @@ -0,0 +1,30 @@ +from collections.abc import Collection +from collections.abc import Iterable +from collections.abc import Iterator + +class ETags(Collection[str]): + _strong: frozenset[str] + _weak: frozenset[str] + star_tag: bool + def __init__( + self, + strong_etags: Iterable[str] | None = None, + weak_etags: Iterable[str] | None = None, + star_tag: bool = False, + ) -> None: ... + def as_set(self, include_weak: bool = False) -> set[str]: ... + def is_weak(self, etag: str) -> bool: ... + def is_strong(self, etag: str) -> bool: ... + def contains_weak(self, etag: str) -> bool: ... + def contains(self, etag: str) -> bool: ... + def contains_raw(self, etag: str) -> bool: ... + def to_header(self) -> str: ... + def __call__( + self, + etag: str | None = None, + data: bytes | None = None, + include_weak: bool = False, + ) -> bool: ... + def __len__(self) -> int: ... + def __iter__(self) -> Iterator[str]: ... + def __contains__(self, item: str) -> bool: ... # type: ignore diff --git a/venv/Lib/site-packages/werkzeug/datastructures/file_storage.py b/venv/Lib/site-packages/werkzeug/datastructures/file_storage.py new file mode 100644 index 00000000..e878a56d --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/datastructures/file_storage.py @@ -0,0 +1,196 @@ +from __future__ import annotations + +import mimetypes +from io import BytesIO +from os import fsdecode +from os import fspath + +from .._internal import _plain_int +from .structures import MultiDict + + +class FileStorage: + """The :class:`FileStorage` class is a thin wrapper over incoming files. + It is used by the request object to represent uploaded files. All the + attributes of the wrapper stream are proxied by the file storage so + it's possible to do ``storage.read()`` instead of the long form + ``storage.stream.read()``. + """ + + def __init__( + self, + stream=None, + filename=None, + name=None, + content_type=None, + content_length=None, + headers=None, + ): + self.name = name + self.stream = stream or BytesIO() + + # If no filename is provided, attempt to get the filename from + # the stream object. Python names special streams like + # ```` with angular brackets, skip these streams. + if filename is None: + filename = getattr(stream, "name", None) + + if filename is not None: + filename = fsdecode(filename) + + if filename and filename[0] == "<" and filename[-1] == ">": + filename = None + else: + filename = fsdecode(filename) + + self.filename = filename + + if headers is None: + from .headers import Headers + + headers = Headers() + self.headers = headers + if content_type is not None: + headers["Content-Type"] = content_type + if content_length is not None: + headers["Content-Length"] = str(content_length) + + def _parse_content_type(self): + if not hasattr(self, "_parsed_content_type"): + self._parsed_content_type = http.parse_options_header(self.content_type) + + @property + def content_type(self): + """The content-type sent in the header. Usually not available""" + return self.headers.get("content-type") + + @property + def content_length(self): + """The content-length sent in the header. Usually not available""" + if "content-length" in self.headers: + try: + return _plain_int(self.headers["content-length"]) + except ValueError: + pass + + return 0 + + @property + def mimetype(self): + """Like :attr:`content_type`, but without parameters (eg, without + charset, type etc.) and always lowercase. For example if the content + type is ``text/HTML; charset=utf-8`` the mimetype would be + ``'text/html'``. + + .. versionadded:: 0.7 + """ + self._parse_content_type() + return self._parsed_content_type[0].lower() + + @property + def mimetype_params(self): + """The mimetype parameters as dict. For example if the content + type is ``text/html; charset=utf-8`` the params would be + ``{'charset': 'utf-8'}``. + + .. versionadded:: 0.7 + """ + self._parse_content_type() + return self._parsed_content_type[1] + + def save(self, dst, buffer_size=16384): + """Save the file to a destination path or file object. If the + destination is a file object you have to close it yourself after the + call. The buffer size is the number of bytes held in memory during + the copy process. It defaults to 16KB. + + For secure file saving also have a look at :func:`secure_filename`. + + :param dst: a filename, :class:`os.PathLike`, or open file + object to write to. + :param buffer_size: Passed as the ``length`` parameter of + :func:`shutil.copyfileobj`. + + .. versionchanged:: 1.0 + Supports :mod:`pathlib`. + """ + from shutil import copyfileobj + + close_dst = False + + if hasattr(dst, "__fspath__"): + dst = fspath(dst) + + if isinstance(dst, str): + dst = open(dst, "wb") + close_dst = True + + try: + copyfileobj(self.stream, dst, buffer_size) + finally: + if close_dst: + dst.close() + + def close(self): + """Close the underlying file if possible.""" + try: + self.stream.close() + except Exception: + pass + + def __bool__(self): + return bool(self.filename) + + def __getattr__(self, name): + try: + return getattr(self.stream, name) + except AttributeError: + # SpooledTemporaryFile doesn't implement IOBase, get the + # attribute from its backing file instead. + # https://github.com/python/cpython/pull/3249 + if hasattr(self.stream, "_file"): + return getattr(self.stream._file, name) + raise + + def __iter__(self): + return iter(self.stream) + + def __repr__(self): + return f"<{type(self).__name__}: {self.filename!r} ({self.content_type!r})>" + + +class FileMultiDict(MultiDict): + """A special :class:`MultiDict` that has convenience methods to add + files to it. This is used for :class:`EnvironBuilder` and generally + useful for unittesting. + + .. versionadded:: 0.5 + """ + + def add_file(self, name, file, filename=None, content_type=None): + """Adds a new file to the dict. `file` can be a file name or + a :class:`file`-like or a :class:`FileStorage` object. + + :param name: the name of the field. + :param file: a filename or :class:`file`-like object + :param filename: an optional filename + :param content_type: an optional content type + """ + if isinstance(file, FileStorage): + value = file + else: + if isinstance(file, str): + if filename is None: + filename = file + file = open(file, "rb") + if filename and content_type is None: + content_type = ( + mimetypes.guess_type(filename)[0] or "application/octet-stream" + ) + value = FileStorage(file, filename, name, content_type) + + self.add(name, value) + + +# circular dependencies +from .. import http diff --git a/venv/Lib/site-packages/werkzeug/datastructures/file_storage.pyi b/venv/Lib/site-packages/werkzeug/datastructures/file_storage.pyi new file mode 100644 index 00000000..36a7ed9f --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/datastructures/file_storage.pyi @@ -0,0 +1,49 @@ +from collections.abc import Iterator +from os import PathLike +from typing import Any +from typing import IO + +from .headers import Headers +from .structures import MultiDict + +class FileStorage: + name: str | None + stream: IO[bytes] + filename: str | None + headers: Headers + _parsed_content_type: tuple[str, dict[str, str]] + def __init__( + self, + stream: IO[bytes] | None = None, + filename: str | PathLike[str] | None = None, + name: str | None = None, + content_type: str | None = None, + content_length: int | None = None, + headers: Headers | None = None, + ) -> None: ... + def _parse_content_type(self) -> None: ... + @property + def content_type(self) -> str: ... + @property + def content_length(self) -> int: ... + @property + def mimetype(self) -> str: ... + @property + def mimetype_params(self) -> dict[str, str]: ... + def save( + self, dst: str | PathLike[str] | IO[bytes], buffer_size: int = ... + ) -> None: ... + def close(self) -> None: ... + def __bool__(self) -> bool: ... + def __getattr__(self, name: str) -> Any: ... + def __iter__(self) -> Iterator[bytes]: ... + def __repr__(self) -> str: ... + +class FileMultiDict(MultiDict[str, FileStorage]): + def add_file( + self, + name: str, + file: FileStorage | str | IO[bytes], + filename: str | None = None, + content_type: str | None = None, + ) -> None: ... diff --git a/venv/Lib/site-packages/werkzeug/datastructures/headers.py b/venv/Lib/site-packages/werkzeug/datastructures/headers.py new file mode 100644 index 00000000..d9dd655c --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/datastructures/headers.py @@ -0,0 +1,515 @@ +from __future__ import annotations + +import re +import typing as t + +from .._internal import _missing +from ..exceptions import BadRequestKeyError +from .mixins import ImmutableHeadersMixin +from .structures import iter_multi_items +from .structures import MultiDict + + +class Headers: + """An object that stores some headers. It has a dict-like interface, + but is ordered, can store the same key multiple times, and iterating + yields ``(key, value)`` pairs instead of only keys. + + This data structure is useful if you want a nicer way to handle WSGI + headers which are stored as tuples in a list. + + From Werkzeug 0.3 onwards, the :exc:`KeyError` raised by this class is + also a subclass of the :class:`~exceptions.BadRequest` HTTP exception + and will render a page for a ``400 BAD REQUEST`` if caught in a + catch-all for HTTP exceptions. + + Headers is mostly compatible with the Python :class:`wsgiref.headers.Headers` + class, with the exception of `__getitem__`. :mod:`wsgiref` will return + `None` for ``headers['missing']``, whereas :class:`Headers` will raise + a :class:`KeyError`. + + To create a new ``Headers`` object, pass it a list, dict, or + other ``Headers`` object with default values. These values are + validated the same way values added later are. + + :param defaults: The list of default values for the :class:`Headers`. + + .. versionchanged:: 2.1.0 + Default values are validated the same as values added later. + + .. versionchanged:: 0.9 + This data structure now stores unicode values similar to how the + multi dicts do it. The main difference is that bytes can be set as + well which will automatically be latin1 decoded. + + .. versionchanged:: 0.9 + The :meth:`linked` function was removed without replacement as it + was an API that does not support the changes to the encoding model. + """ + + def __init__(self, defaults=None): + self._list = [] + if defaults is not None: + self.extend(defaults) + + def __getitem__(self, key, _get_mode=False): + if not _get_mode: + if isinstance(key, int): + return self._list[key] + elif isinstance(key, slice): + return self.__class__(self._list[key]) + if not isinstance(key, str): + raise BadRequestKeyError(key) + ikey = key.lower() + for k, v in self._list: + if k.lower() == ikey: + return v + # micro optimization: if we are in get mode we will catch that + # exception one stack level down so we can raise a standard + # key error instead of our special one. + if _get_mode: + raise KeyError() + raise BadRequestKeyError(key) + + def __eq__(self, other): + def lowered(item): + return (item[0].lower(),) + item[1:] + + return other.__class__ is self.__class__ and set( + map(lowered, other._list) + ) == set(map(lowered, self._list)) + + __hash__ = 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 + this case the function will return the default as if the value was not + found: + + >>> d = Headers([('Content-Length', '42')]) + >>> d.get('Content-Length', type=int) + 42 + + :param key: The key to be looked up. + :param default: The default value to be returned if the key can't + be looked up. If not further specified `None` is + returned. + :param type: A callable that is used to cast the value in the + :class:`Headers`. If a :exc:`ValueError` is raised + by this callable the default value is returned. + + .. versionchanged:: 3.0 + The ``as_bytes`` parameter was removed. + + .. versionchanged:: 0.9 + The ``as_bytes`` parameter was added. + """ + try: + rv = self.__getitem__(key, _get_mode=True) + except KeyError: + return default + if type is None: + return rv + try: + return type(rv) + except ValueError: + return default + + 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 + be converted with the callable defined there. + + :param key: The key to be looked up. + :param type: A callable that is used to cast the value in the + :class:`Headers`. If a :exc:`ValueError` is raised + by this callable the value will be removed from the list. + :return: a :class:`list` of all the values for the key. + + .. versionchanged:: 3.0 + The ``as_bytes`` parameter was removed. + + .. versionchanged:: 0.9 + The ``as_bytes`` parameter was added. + """ + ikey = key.lower() + result = [] + for k, v in self: + if k.lower() == ikey: + if type is not None: + try: + v = type(v) + except ValueError: + continue + result.append(v) + return result + + def get_all(self, name): + """Return a list of all the values for the named field. + + This method is compatible with the :mod:`wsgiref` + :meth:`~wsgiref.headers.Headers.get_all` method. + """ + return self.getlist(name) + + def items(self, lower=False): + for key, value in self: + if lower: + key = key.lower() + yield key, value + + def keys(self, lower=False): + for key, _ in self.items(lower): + yield key + + def values(self): + for _, value in self.items(): + yield value + + def extend(self, *args, **kwargs): + """Extend headers in this object with items from another object + containing header items as well as keyword arguments. + + To replace existing keys instead of extending, use + :meth:`update` instead. + + If provided, the first argument can be another :class:`Headers` + object, a :class:`MultiDict`, :class:`dict`, or iterable of + pairs. + + .. versionchanged:: 1.0 + Support :class:`MultiDict`. Allow passing ``kwargs``. + """ + if len(args) > 1: + raise TypeError(f"update expected at most 1 arguments, got {len(args)}") + + if args: + for key, value in iter_multi_items(args[0]): + self.add(key, value) + + for key, value in iter_multi_items(kwargs): + self.add(key, value) + + def __delitem__(self, key, _index_operation=True): + if _index_operation and isinstance(key, (int, slice)): + del self._list[key] + return + key = key.lower() + new = [] + for k, v in self._list: + if k.lower() != key: + new.append((k, v)) + self._list[:] = new + + def remove(self, key): + """Remove a key. + + :param key: The key to be removed. + """ + return self.__delitem__(key, _index_operation=False) + + def pop(self, key=None, default=_missing): + """Removes and returns a key or index. + + :param key: The key to be popped. If this is an integer the item at + that position is removed, if it's a string the value for + that key is. If the key is omitted or `None` the last + item is removed. + :return: an item. + """ + if key is None: + return self._list.pop() + if isinstance(key, int): + return self._list.pop(key) + try: + rv = self[key] + self.remove(key) + except KeyError: + if default is not _missing: + return default + raise + return rv + + def popitem(self): + """Removes a key or index and returns a (key, value) item.""" + return self.pop() + + def __contains__(self, key): + """Check if a key is present.""" + try: + self.__getitem__(key, _get_mode=True) + except KeyError: + return False + return True + + def __iter__(self): + """Yield ``(key, value)`` tuples.""" + return iter(self._list) + + def __len__(self): + return len(self._list) + + def add(self, _key, _value, **kw): + """Add a new header tuple to the list. + + Keyword arguments can specify additional parameters for the header + value, with underscores converted to dashes:: + + >>> d = Headers() + >>> d.add('Content-Type', 'text/plain') + >>> d.add('Content-Disposition', 'attachment', filename='foo.png') + + The keyword argument dumping uses :func:`dump_options_header` + behind the scenes. + + .. versionadded:: 0.4.1 + keyword arguments were added for :mod:`wsgiref` compatibility. + """ + if kw: + _value = _options_header_vkw(_value, kw) + _value = _str_header_value(_value) + self._list.append((_key, _value)) + + def add_header(self, _key, _value, **_kw): + """Add a new header tuple to the list. + + An alias for :meth:`add` for compatibility with the :mod:`wsgiref` + :meth:`~wsgiref.headers.Headers.add_header` method. + """ + self.add(_key, _value, **_kw) + + def clear(self): + """Clears all headers.""" + del self._list[:] + + def set(self, _key, _value, **kw): + """Remove all header tuples for `key` and add a new one. The newly + added key either appears at the end of the list if there was no + entry or replaces the first one. + + Keyword arguments can specify additional parameters for the header + value, with underscores converted to dashes. See :meth:`add` for + more information. + + .. versionchanged:: 0.6.1 + :meth:`set` now accepts the same arguments as :meth:`add`. + + :param key: The key to be inserted. + :param value: The value to be inserted. + """ + if kw: + _value = _options_header_vkw(_value, kw) + _value = _str_header_value(_value) + if not self._list: + self._list.append((_key, _value)) + return + listiter = iter(self._list) + ikey = _key.lower() + for idx, (old_key, _old_value) in enumerate(listiter): + if old_key.lower() == ikey: + # replace first occurrence + self._list[idx] = (_key, _value) + break + else: + self._list.append((_key, _value)) + return + self._list[idx + 1 :] = [t for t in listiter if t[0].lower() != ikey] + + def setlist(self, key, values): + """Remove any existing values for a header and add new ones. + + :param key: The header key to set. + :param values: An iterable of values to set for the key. + + .. versionadded:: 1.0 + """ + if values: + values_iter = iter(values) + self.set(key, next(values_iter)) + + for value in values_iter: + self.add(key, value) + else: + self.remove(key) + + def setdefault(self, key, default): + """Return the first value for the key if it is in the headers, + otherwise set the header to the value given by ``default`` and + return that. + + :param key: The header key to get. + :param default: The value to set for the key if it is not in the + headers. + """ + if key in self: + return self[key] + + self.set(key, default) + return default + + def setlistdefault(self, key, default): + """Return the list of values for the key if it is in the + headers, otherwise set the header to the list of values given + by ``default`` and return that. + + Unlike :meth:`MultiDict.setlistdefault`, modifying the returned + list will not affect the headers. + + :param key: The header key to get. + :param default: An iterable of values to set for the key if it + is not in the headers. + + .. versionadded:: 1.0 + """ + if key not in self: + self.setlist(key, default) + + return self.getlist(key) + + def __setitem__(self, key, value): + """Like :meth:`set` but also supports index/slice based setting.""" + if isinstance(key, (slice, int)): + if isinstance(key, int): + value = [value] + value = [(k, _str_header_value(v)) for (k, v) in value] + if isinstance(key, int): + self._list[key] = value[0] + else: + self._list[key] = value + else: + self.set(key, value) + + def update(self, *args, **kwargs): + """Replace headers in this object with items from another + headers object and keyword arguments. + + To extend existing keys instead of replacing, use :meth:`extend` + instead. + + If provided, the first argument can be another :class:`Headers` + object, a :class:`MultiDict`, :class:`dict`, or iterable of + pairs. + + .. versionadded:: 1.0 + """ + if len(args) > 1: + raise TypeError(f"update expected at most 1 arguments, got {len(args)}") + + if args: + mapping = args[0] + + if isinstance(mapping, (Headers, MultiDict)): + for key in mapping.keys(): + self.setlist(key, mapping.getlist(key)) + elif isinstance(mapping, dict): + for key, value in mapping.items(): + if isinstance(value, (list, tuple)): + self.setlist(key, value) + else: + self.set(key, value) + else: + for key, value in mapping: + self.set(key, value) + + for key, value in kwargs.items(): + if isinstance(value, (list, tuple)): + self.setlist(key, value) + else: + self.set(key, value) + + def to_wsgi_list(self): + """Convert the headers into a list suitable for WSGI. + + :return: list + """ + return list(self) + + def copy(self): + return self.__class__(self._list) + + def __copy__(self): + return self.copy() + + def __str__(self): + """Returns formatted headers suitable for HTTP transmission.""" + strs = [] + for key, value in self.to_wsgi_list(): + strs.append(f"{key}: {value}") + strs.append("\r\n") + return "\r\n".join(strs) + + def __repr__(self): + return f"{type(self).__name__}({list(self)!r})" + + +def _options_header_vkw(value: str, kw: dict[str, t.Any]): + return http.dump_options_header( + value, {k.replace("_", "-"): v for k, v in kw.items()} + ) + + +_newline_re = re.compile(r"[\r\n]") + + +def _str_header_value(value: t.Any) -> str: + if not isinstance(value, str): + value = str(value) + + if _newline_re.search(value) is not None: + raise ValueError("Header values must not contain newline characters.") + + return value + + +class EnvironHeaders(ImmutableHeadersMixin, Headers): + """Read only version of the headers from a WSGI environment. This + provides the same interface as `Headers` and is constructed from + a WSGI environment. + From Werkzeug 0.3 onwards, the `KeyError` raised by this class is also a + subclass of the :exc:`~exceptions.BadRequest` HTTP exception and will + render a page for a ``400 BAD REQUEST`` if caught in a catch-all for + HTTP exceptions. + """ + + def __init__(self, environ): + self.environ = environ + + def __eq__(self, other): + return self.environ is other.environ + + __hash__ = None + + def __getitem__(self, key, _get_mode=False): + # _get_mode is a no-op for this class as there is no index but + # used because get() calls it. + if not isinstance(key, str): + raise KeyError(key) + key = key.upper().replace("-", "_") + if key in {"CONTENT_TYPE", "CONTENT_LENGTH"}: + return self.environ[key] + return self.environ[f"HTTP_{key}"] + + def __len__(self): + # the iter is necessary because otherwise list calls our + # len which would call list again and so forth. + return len(list(iter(self))) + + def __iter__(self): + for key, value in self.environ.items(): + if key.startswith("HTTP_") and key not in { + "HTTP_CONTENT_TYPE", + "HTTP_CONTENT_LENGTH", + }: + yield key[5:].replace("_", "-").title(), value + elif key in {"CONTENT_TYPE", "CONTENT_LENGTH"} and value: + yield key.replace("_", "-").title(), value + + def copy(self): + raise TypeError(f"cannot create {type(self).__name__!r} copies") + + +# circular dependencies +from .. import http diff --git a/venv/Lib/site-packages/werkzeug/datastructures/headers.pyi b/venv/Lib/site-packages/werkzeug/datastructures/headers.pyi new file mode 100644 index 00000000..86502221 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/datastructures/headers.pyi @@ -0,0 +1,109 @@ +from collections.abc import Callable +from collections.abc import Iterable +from collections.abc import Iterator +from collections.abc import Mapping +from typing import Literal +from typing import NoReturn +from typing import overload +from typing import TypeVar + +from _typeshed import SupportsKeysAndGetItem +from _typeshed.wsgi import WSGIEnvironment + +from .mixins import ImmutableHeadersMixin + +D = TypeVar("D") +T = TypeVar("T") + +class Headers(dict[str, str]): + _list: list[tuple[str, str]] + def __init__( + self, + defaults: Mapping[str, str | Iterable[str]] + | Iterable[tuple[str, str]] + | None = None, + ) -> None: ... + @overload + def __getitem__(self, key: str) -> str: ... + @overload + def __getitem__(self, key: int) -> tuple[str, str]: ... + @overload + def __getitem__(self, key: slice) -> Headers: ... + @overload + def __getitem__(self, key: str, _get_mode: Literal[True] = ...) -> str: ... + def __eq__(self, other: object) -> bool: ... + @overload # type: ignore + def get(self, key: str, default: str) -> str: ... + @overload + def get(self, key: str, default: str | None = None) -> str | None: ... + @overload + def get( + self, key: str, default: T | None = None, type: Callable[[str], T] = ... + ) -> T | None: ... + @overload + def getlist(self, key: str) -> list[str]: ... + @overload + def getlist(self, key: str, type: Callable[[str], T]) -> list[T]: ... + def get_all(self, name: str) -> list[str]: ... + def items( # type: ignore + self, lower: bool = False + ) -> Iterator[tuple[str, str]]: ... + def keys(self, lower: bool = False) -> Iterator[str]: ... # type: ignore + def values(self) -> Iterator[str]: ... # type: ignore + def extend( + self, + *args: Mapping[str, str | Iterable[str]] | Iterable[tuple[str, str]], + **kwargs: str | Iterable[str], + ) -> None: ... + @overload + def __delitem__(self, key: str | int | slice) -> None: ... + @overload + def __delitem__(self, key: str, _index_operation: Literal[False]) -> None: ... + def remove(self, key: str) -> None: ... + @overload # type: ignore + def pop(self, key: str, default: str | None = None) -> str: ... + @overload + def pop( + self, key: int | None = None, default: tuple[str, str] | None = None + ) -> tuple[str, str]: ... + def popitem(self) -> tuple[str, str]: ... + def __contains__(self, key: str) -> bool: ... # type: ignore + def has_key(self, key: str) -> bool: ... + def __iter__(self) -> Iterator[tuple[str, str]]: ... # type: ignore + def add(self, _key: str, _value: str, **kw: str) -> None: ... + def _validate_value(self, value: str) -> None: ... + def add_header(self, _key: str, _value: str, **_kw: str) -> None: ... + def clear(self) -> None: ... + def set(self, _key: str, _value: str, **kw: str) -> None: ... + def setlist(self, key: str, values: Iterable[str]) -> None: ... + def setdefault(self, key: str, default: str) -> str: ... + def setlistdefault(self, key: str, default: Iterable[str]) -> None: ... + @overload + def __setitem__(self, key: str, value: str) -> None: ... + @overload + def __setitem__(self, key: int, value: tuple[str, str]) -> None: ... + @overload + def __setitem__(self, key: slice, value: Iterable[tuple[str, str]]) -> None: ... + @overload + def update( + self, __m: SupportsKeysAndGetItem[str, str], **kwargs: str | Iterable[str] + ) -> None: ... + @overload + def update( + self, __m: Iterable[tuple[str, str]], **kwargs: str | Iterable[str] + ) -> None: ... + @overload + def update(self, **kwargs: str | Iterable[str]) -> None: ... + def to_wsgi_list(self) -> list[tuple[str, str]]: ... + def copy(self) -> Headers: ... + def __copy__(self) -> Headers: ... + +class EnvironHeaders(ImmutableHeadersMixin, Headers): + environ: WSGIEnvironment + def __init__(self, environ: WSGIEnvironment) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __getitem__( # type: ignore + self, key: str, _get_mode: Literal[False] = False + ) -> str: ... + def __iter__(self) -> Iterator[tuple[str, str]]: ... # type: ignore + def copy(self) -> NoReturn: ... diff --git a/venv/Lib/site-packages/werkzeug/datastructures/mixins.py b/venv/Lib/site-packages/werkzeug/datastructures/mixins.py new file mode 100644 index 00000000..2c84ca8f --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/datastructures/mixins.py @@ -0,0 +1,242 @@ +from __future__ import annotations + +from itertools import repeat + +from .._internal import _missing + + +def is_immutable(self): + raise TypeError(f"{type(self).__name__!r} objects are immutable") + + +class ImmutableListMixin: + """Makes a :class:`list` immutable. + + .. versionadded:: 0.5 + + :private: + """ + + _hash_cache = None + + def __hash__(self): + if self._hash_cache is not None: + return self._hash_cache + rv = self._hash_cache = hash(tuple(self)) + return rv + + def __reduce_ex__(self, protocol): + return type(self), (list(self),) + + def __delitem__(self, key): + is_immutable(self) + + def __iadd__(self, other): + is_immutable(self) + + def __imul__(self, other): + is_immutable(self) + + def __setitem__(self, key, value): + is_immutable(self) + + def append(self, item): + is_immutable(self) + + def remove(self, item): + is_immutable(self) + + def extend(self, iterable): + is_immutable(self) + + def insert(self, pos, value): + is_immutable(self) + + def pop(self, index=-1): + is_immutable(self) + + def reverse(self): + is_immutable(self) + + def sort(self, key=None, reverse=False): + is_immutable(self) + + +class ImmutableDictMixin: + """Makes a :class:`dict` immutable. + + .. versionadded:: 0.5 + + :private: + """ + + _hash_cache = None + + @classmethod + def fromkeys(cls, keys, value=None): + instance = super().__new__(cls) + instance.__init__(zip(keys, repeat(value))) + return instance + + def __reduce_ex__(self, protocol): + return type(self), (dict(self),) + + def _iter_hashitems(self): + return self.items() + + def __hash__(self): + if self._hash_cache is not None: + return self._hash_cache + rv = self._hash_cache = hash(frozenset(self._iter_hashitems())) + return rv + + def setdefault(self, key, default=None): + is_immutable(self) + + def update(self, *args, **kwargs): + is_immutable(self) + + def pop(self, key, default=None): + is_immutable(self) + + def popitem(self): + is_immutable(self) + + def __setitem__(self, key, value): + is_immutable(self) + + def __delitem__(self, key): + is_immutable(self) + + def clear(self): + is_immutable(self) + + +class ImmutableMultiDictMixin(ImmutableDictMixin): + """Makes a :class:`MultiDict` immutable. + + .. versionadded:: 0.5 + + :private: + """ + + def __reduce_ex__(self, protocol): + return type(self), (list(self.items(multi=True)),) + + def _iter_hashitems(self): + return self.items(multi=True) + + def add(self, key, value): + is_immutable(self) + + def popitemlist(self): + is_immutable(self) + + def poplist(self, key): + is_immutable(self) + + def setlist(self, key, new_list): + is_immutable(self) + + def setlistdefault(self, key, default_list=None): + is_immutable(self) + + +class ImmutableHeadersMixin: + """Makes a :class:`Headers` immutable. We do not mark them as + hashable though since the only usecase for this datastructure + in Werkzeug is a view on a mutable structure. + + .. versionadded:: 0.5 + + :private: + """ + + def __delitem__(self, key, **kwargs): + is_immutable(self) + + def __setitem__(self, key, value): + is_immutable(self) + + def set(self, _key, _value, **kwargs): + is_immutable(self) + + def setlist(self, key, values): + is_immutable(self) + + def add(self, _key, _value, **kwargs): + is_immutable(self) + + def add_header(self, _key, _value, **_kwargs): + is_immutable(self) + + def remove(self, key): + is_immutable(self) + + def extend(self, *args, **kwargs): + is_immutable(self) + + def update(self, *args, **kwargs): + is_immutable(self) + + def insert(self, pos, value): + is_immutable(self) + + def pop(self, key=None, default=_missing): + is_immutable(self) + + def popitem(self): + is_immutable(self) + + def setdefault(self, key, default): + is_immutable(self) + + def setlistdefault(self, key, default): + is_immutable(self) + + +def _calls_update(name): + def oncall(self, *args, **kw): + rv = getattr(super(UpdateDictMixin, self), name)(*args, **kw) + + if self.on_update is not None: + self.on_update(self) + + return rv + + oncall.__name__ = name + return oncall + + +class UpdateDictMixin(dict): + """Makes dicts call `self.on_update` on modifications. + + .. versionadded:: 0.5 + + :private: + """ + + on_update = None + + def setdefault(self, key, default=None): + modified = key not in self + rv = super().setdefault(key, default) + if modified and self.on_update is not None: + self.on_update(self) + return rv + + def pop(self, key, default=_missing): + modified = key in self + if default is _missing: + rv = super().pop(key) + else: + rv = super().pop(key, default) + if modified and self.on_update is not None: + self.on_update(self) + return rv + + __setitem__ = _calls_update("__setitem__") + __delitem__ = _calls_update("__delitem__") + clear = _calls_update("clear") + popitem = _calls_update("popitem") + update = _calls_update("update") diff --git a/venv/Lib/site-packages/werkzeug/datastructures/mixins.pyi b/venv/Lib/site-packages/werkzeug/datastructures/mixins.pyi new file mode 100644 index 00000000..40453f70 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/datastructures/mixins.pyi @@ -0,0 +1,97 @@ +from collections.abc import Callable +from collections.abc import Hashable +from collections.abc import Iterable +from typing import Any +from typing import NoReturn +from typing import overload +from typing import SupportsIndex +from typing import TypeVar + +from _typeshed import SupportsKeysAndGetItem + +from .headers import Headers + +K = TypeVar("K") +T = TypeVar("T") +V = TypeVar("V") + +def is_immutable(self: object) -> NoReturn: ... + +class ImmutableListMixin(list[V]): + _hash_cache: int | None + def __hash__(self) -> int: ... # type: ignore + def __delitem__(self, key: SupportsIndex | slice) -> NoReturn: ... + def __iadd__(self, other: Any) -> NoReturn: ... # type: ignore + def __imul__(self, other: SupportsIndex) -> NoReturn: ... + def __setitem__(self, key: int | slice, value: V) -> NoReturn: ... # type: ignore + def append(self, value: V) -> NoReturn: ... + def remove(self, value: V) -> NoReturn: ... + def extend(self, values: Iterable[V]) -> NoReturn: ... + def insert(self, pos: SupportsIndex, value: V) -> NoReturn: ... + def pop(self, index: SupportsIndex = -1) -> NoReturn: ... + def reverse(self) -> NoReturn: ... + def sort( + self, key: Callable[[V], Any] | None = None, reverse: bool = False + ) -> NoReturn: ... + +class ImmutableDictMixin(dict[K, V]): + _hash_cache: int | None + @classmethod + def fromkeys( # type: ignore + cls, keys: Iterable[K], value: V | None = None + ) -> ImmutableDictMixin[K, V]: ... + def _iter_hashitems(self) -> Iterable[Hashable]: ... + def __hash__(self) -> int: ... # type: ignore + def setdefault(self, key: K, default: V | None = None) -> NoReturn: ... + def update(self, *args: Any, **kwargs: V) -> NoReturn: ... + def pop(self, key: K, default: V | None = None) -> NoReturn: ... # type: ignore + def popitem(self) -> NoReturn: ... + def __setitem__(self, key: K, value: V) -> NoReturn: ... + def __delitem__(self, key: K) -> NoReturn: ... + def clear(self) -> NoReturn: ... + +class ImmutableMultiDictMixin(ImmutableDictMixin[K, V]): + def _iter_hashitems(self) -> Iterable[Hashable]: ... + def add(self, key: K, value: V) -> NoReturn: ... + def popitemlist(self) -> NoReturn: ... + def poplist(self, key: K) -> NoReturn: ... + def setlist(self, key: K, new_list: Iterable[V]) -> NoReturn: ... + def setlistdefault( + self, key: K, default_list: Iterable[V] | None = None + ) -> NoReturn: ... + +class ImmutableHeadersMixin(Headers): + def __delitem__(self, key: Any, _index_operation: bool = True) -> NoReturn: ... + def __setitem__(self, key: Any, value: Any) -> NoReturn: ... + def set(self, _key: Any, _value: Any, **kw: Any) -> NoReturn: ... + def setlist(self, key: Any, values: Any) -> NoReturn: ... + def add(self, _key: Any, _value: Any, **kw: Any) -> NoReturn: ... + def add_header(self, _key: Any, _value: Any, **_kw: Any) -> NoReturn: ... + def remove(self, key: Any) -> NoReturn: ... + def extend(self, *args: Any, **kwargs: Any) -> NoReturn: ... + def update(self, *args: Any, **kwargs: Any) -> NoReturn: ... + def insert(self, pos: Any, value: Any) -> NoReturn: ... + def pop(self, key: Any = None, default: Any = ...) -> NoReturn: ... + def popitem(self) -> NoReturn: ... + def setdefault(self, key: Any, default: Any) -> NoReturn: ... + def setlistdefault(self, key: Any, default: Any) -> NoReturn: ... + +def _calls_update(name: str) -> Callable[[UpdateDictMixin[K, V]], Any]: ... + +class UpdateDictMixin(dict[K, V]): + on_update: Callable[[UpdateDictMixin[K, V] | None, None], None] + def setdefault(self, key: K, default: V | None = None) -> V: ... + @overload + def pop(self, key: K) -> V: ... + @overload + def pop(self, key: K, default: V | T = ...) -> V | T: ... + def __setitem__(self, key: K, value: V) -> None: ... + def __delitem__(self, key: K) -> None: ... + def clear(self) -> None: ... + def popitem(self) -> tuple[K, V]: ... + @overload + def update(self, __m: SupportsKeysAndGetItem[K, V], **kwargs: V) -> None: ... + @overload + def update(self, __m: Iterable[tuple[K, V]], **kwargs: V) -> None: ... + @overload + def update(self, **kwargs: V) -> None: ... diff --git a/venv/Lib/site-packages/werkzeug/datastructures/range.py b/venv/Lib/site-packages/werkzeug/datastructures/range.py new file mode 100644 index 00000000..7011ea4a --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/datastructures/range.py @@ -0,0 +1,180 @@ +from __future__ import annotations + + +class IfRange: + """Very simple object that represents the `If-Range` header in parsed + form. It will either have neither a etag or date or one of either but + never both. + + .. versionadded:: 0.7 + """ + + def __init__(self, etag=None, date=None): + #: The etag parsed and unquoted. Ranges always operate on strong + #: etags so the weakness information is not necessary. + self.etag = etag + #: The date in parsed format or `None`. + self.date = date + + def to_header(self): + """Converts the object back into an HTTP header.""" + if self.date is not None: + return http.http_date(self.date) + if self.etag is not None: + return http.quote_etag(self.etag) + return "" + + def __str__(self): + return self.to_header() + + def __repr__(self): + return f"<{type(self).__name__} {str(self)!r}>" + + +class Range: + """Represents a ``Range`` header. All methods only support only + bytes as the unit. Stores a list of ranges if given, but the methods + only work if only one range is provided. + + :raise ValueError: If the ranges provided are invalid. + + .. versionchanged:: 0.15 + The ranges passed in are validated. + + .. versionadded:: 0.7 + """ + + def __init__(self, units, ranges): + #: The units of this range. Usually "bytes". + self.units = units + #: A list of ``(begin, end)`` tuples for the range header provided. + #: The ranges are non-inclusive. + self.ranges = ranges + + for start, end in ranges: + if start is None or (end is not None and (start < 0 or start >= end)): + raise ValueError(f"{(start, end)} is not a valid range.") + + def range_for_length(self, length): + """If the range is for bytes, the length is not None and there is + exactly one range and it is satisfiable it returns a ``(start, stop)`` + tuple, otherwise `None`. + """ + if self.units != "bytes" or length is None or len(self.ranges) != 1: + return None + start, end = self.ranges[0] + if end is None: + end = length + if start < 0: + start += length + if http.is_byte_range_valid(start, end, length): + return start, min(end, length) + return None + + def make_content_range(self, length): + """Creates a :class:`~werkzeug.datastructures.ContentRange` object + from the current range and given content length. + """ + rng = self.range_for_length(length) + if rng is not None: + return ContentRange(self.units, rng[0], rng[1], length) + return None + + def to_header(self): + """Converts the object back into an HTTP header.""" + ranges = [] + for begin, end in self.ranges: + if end is None: + ranges.append(f"{begin}-" if begin >= 0 else str(begin)) + else: + ranges.append(f"{begin}-{end - 1}") + return f"{self.units}={','.join(ranges)}" + + def to_content_range_header(self, length): + """Converts the object into `Content-Range` HTTP header, + based on given length + """ + range = self.range_for_length(length) + if range is not None: + return f"{self.units} {range[0]}-{range[1] - 1}/{length}" + return None + + def __str__(self): + return self.to_header() + + def __repr__(self): + return f"<{type(self).__name__} {str(self)!r}>" + + +def _callback_property(name): + def fget(self): + return getattr(self, name) + + def fset(self, value): + setattr(self, name, value) + if self.on_update is not None: + self.on_update(self) + + return property(fget, fset) + + +class ContentRange: + """Represents the content range header. + + .. versionadded:: 0.7 + """ + + def __init__(self, units, start, stop, length=None, on_update=None): + assert http.is_byte_range_valid(start, stop, length), "Bad range provided" + self.on_update = on_update + self.set(start, stop, length, units) + + #: The units to use, usually "bytes" + units = _callback_property("_units") + #: The start point of the range or `None`. + start = _callback_property("_start") + #: The stop point of the range (non-inclusive) or `None`. Can only be + #: `None` if also start is `None`. + stop = _callback_property("_stop") + #: The length of the range or `None`. + length = _callback_property("_length") + + def set(self, start, stop, length=None, units="bytes"): + """Simple method to update the ranges.""" + assert http.is_byte_range_valid(start, stop, length), "Bad range provided" + self._units = units + self._start = start + self._stop = stop + self._length = length + if self.on_update is not None: + self.on_update(self) + + def unset(self): + """Sets the units to `None` which indicates that the header should + no longer be used. + """ + self.set(None, None, units=None) + + def to_header(self): + if self.units is None: + return "" + if self.length is None: + length = "*" + else: + length = self.length + if self.start is None: + return f"{self.units} */{length}" + return f"{self.units} {self.start}-{self.stop - 1}/{length}" + + def __bool__(self): + return self.units is not None + + def __str__(self): + return self.to_header() + + def __repr__(self): + return f"<{type(self).__name__} {str(self)!r}>" + + +# circular dependencies +from .. import http diff --git a/venv/Lib/site-packages/werkzeug/datastructures/range.pyi b/venv/Lib/site-packages/werkzeug/datastructures/range.pyi new file mode 100644 index 00000000..f38ad69e --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/datastructures/range.pyi @@ -0,0 +1,57 @@ +from collections.abc import Callable +from datetime import datetime + +class IfRange: + etag: str | None + date: datetime | None + def __init__( + self, etag: str | None = None, date: datetime | None = None + ) -> None: ... + def to_header(self) -> str: ... + +class Range: + units: str + ranges: list[tuple[int, int | None]] + def __init__(self, units: str, ranges: list[tuple[int, int | None]]) -> None: ... + def range_for_length(self, length: int | None) -> tuple[int, int] | None: ... + def make_content_range(self, length: int | None) -> ContentRange | None: ... + def to_header(self) -> str: ... + def to_content_range_header(self, length: int | None) -> str | None: ... + +def _callback_property(name: str) -> property: ... + +class ContentRange: + on_update: Callable[[ContentRange], None] | None + def __init__( + self, + units: str | None, + start: int | None, + stop: int | None, + length: int | None = None, + on_update: Callable[[ContentRange], None] | None = None, + ) -> None: ... + @property + def units(self) -> str | None: ... + @units.setter + def units(self, value: str | None) -> None: ... + @property + def start(self) -> int | None: ... + @start.setter + def start(self, value: int | None) -> None: ... + @property + def stop(self) -> int | None: ... + @stop.setter + def stop(self, value: int | None) -> None: ... + @property + def length(self) -> int | None: ... + @length.setter + def length(self, value: int | None) -> None: ... + def set( + self, + start: int | None, + stop: int | None, + length: int | None = None, + units: str | None = "bytes", + ) -> None: ... + def unset(self) -> None: ... + def to_header(self) -> str: ... diff --git a/venv/Lib/site-packages/werkzeug/datastructures/structures.py b/venv/Lib/site-packages/werkzeug/datastructures/structures.py new file mode 100644 index 00000000..4279ceb9 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/datastructures/structures.py @@ -0,0 +1,1010 @@ +from __future__ import annotations + +from collections.abc import MutableSet +from copy import deepcopy + +from .. import exceptions +from .._internal import _missing +from .mixins import ImmutableDictMixin +from .mixins import ImmutableListMixin +from .mixins import ImmutableMultiDictMixin +from .mixins import UpdateDictMixin + + +def is_immutable(self): + raise TypeError(f"{type(self).__name__!r} objects are immutable") + + +def iter_multi_items(mapping): + """Iterates over the items of a mapping yielding keys and values + without dropping any from more complex structures. + """ + if isinstance(mapping, MultiDict): + yield from mapping.items(multi=True) + elif isinstance(mapping, dict): + for key, value in mapping.items(): + if isinstance(value, (tuple, list)): + for v in value: + yield key, v + else: + yield key, value + else: + yield from mapping + + +class ImmutableList(ImmutableListMixin, list): + """An immutable :class:`list`. + + .. versionadded:: 0.5 + + :private: + """ + + def __repr__(self): + return f"{type(self).__name__}({list.__repr__(self)})" + + +class TypeConversionDict(dict): + """Works like a regular dict but the :meth:`get` method can perform + type conversions. :class:`MultiDict` and :class:`CombinedMultiDict` + are subclasses of this class and provide the same feature. + + .. versionadded:: 0.5 + """ + + 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 + this case the function will return the default as if the value was not + found: + + >>> d = TypeConversionDict(foo='42', bar='blub') + >>> d.get('foo', type=int) + 42 + >>> d.get('bar', -1, type=int) + -1 + + :param key: The key to be looked up. + :param default: The default value to be returned if the key can't + be looked up. If not further specified `None` is + returned. + :param type: A callable that is used to cast the value in the + :class:`MultiDict`. If a :exc:`ValueError` or a + :exc:`TypeError` is raised by this callable the default + value is returned. + + .. versionchanged:: 3.0.2 + Returns the default value on :exc:`TypeError`, too. + """ + try: + rv = self[key] + except KeyError: + return default + if type is not None: + try: + rv = type(rv) + except (ValueError, TypeError): + rv = default + return rv + + +class ImmutableTypeConversionDict(ImmutableDictMixin, TypeConversionDict): + """Works like a :class:`TypeConversionDict` but does not support + modifications. + + .. versionadded:: 0.5 + """ + + def copy(self): + """Return a shallow mutable copy of this object. Keep in mind that + the standard library's :func:`copy` function is a no-op for this class + like for any other python immutable type (eg: :class:`tuple`). + """ + return TypeConversionDict(self) + + def __copy__(self): + return self + + +class MultiDict(TypeConversionDict): + """A :class:`MultiDict` is a dictionary subclass customized to deal with + multiple values for the same key which is for example used by the parsing + functions in the wrappers. This is necessary because some HTML form + elements pass multiple values for the same key. + + :class:`MultiDict` implements all standard dictionary methods. + Internally, it saves all values for a key as a list, but the standard dict + access methods will only return the first value for a key. If you want to + gain access to the other values, too, you have to use the `list` methods as + explained below. + + Basic Usage: + + >>> d = MultiDict([('a', 'b'), ('a', 'c')]) + >>> d + MultiDict([('a', 'b'), ('a', 'c')]) + >>> d['a'] + 'b' + >>> d.getlist('a') + ['b', 'c'] + >>> 'a' in d + True + + It behaves like a normal dict thus all dict functions will only return the + first value when multiple values for one key are found. + + From Werkzeug 0.3 onwards, the `KeyError` raised by this class is also a + subclass of the :exc:`~exceptions.BadRequest` HTTP exception and will + render a page for a ``400 BAD REQUEST`` if caught in a catch-all for HTTP + exceptions. + + A :class:`MultiDict` can be constructed from an iterable of + ``(key, value)`` tuples, a dict, a :class:`MultiDict` or from Werkzeug 0.2 + onwards some keyword parameters. + + :param mapping: the initial value for the :class:`MultiDict`. Either a + regular dict, an iterable of ``(key, value)`` tuples + or `None`. + """ + + def __init__(self, mapping=None): + if isinstance(mapping, MultiDict): + dict.__init__(self, ((k, vs[:]) for k, vs in mapping.lists())) + elif isinstance(mapping, dict): + tmp = {} + for key, value in mapping.items(): + if isinstance(value, (tuple, list)): + if len(value) == 0: + continue + value = list(value) + else: + value = [value] + tmp[key] = value + dict.__init__(self, tmp) + else: + tmp = {} + for key, value in mapping or (): + tmp.setdefault(key, []).append(value) + dict.__init__(self, tmp) + + def __getstate__(self): + return dict(self.lists()) + + def __setstate__(self, value): + dict.clear(self) + dict.update(self, value) + + def __iter__(self): + # Work around https://bugs.python.org/issue43246. + # (`return super().__iter__()` also works here, which makes this look + # even more like it should be a no-op, yet it isn't.) + return dict.__iter__(self) + + def __getitem__(self, key): + """Return the first data value for this key; + raises KeyError if not found. + + :param key: The key to be looked up. + :raise KeyError: if the key does not exist. + """ + + if key in self: + lst = dict.__getitem__(self, key) + if len(lst) > 0: + return lst[0] + raise exceptions.BadRequestKeyError(key) + + def __setitem__(self, key, value): + """Like :meth:`add` but removes an existing key first. + + :param key: the key for the value. + :param value: the value to set. + """ + dict.__setitem__(self, key, [value]) + + def add(self, key, value): + """Adds a new value for the key. + + .. versionadded:: 0.6 + + :param key: the key for the value. + :param value: the value to add. + """ + dict.setdefault(self, key, []).append(value) + + def getlist(self, key, type=None): + """Return the list of items for a given key. If that key is not in the + `MultiDict`, the return value will be an empty list. Just like `get`, + `getlist` accepts a `type` parameter. All items will be converted + with the callable defined there. + + :param key: The key to be looked up. + :param type: A callable that is used to cast the value in the + :class:`MultiDict`. If a :exc:`ValueError` is raised + by this callable the value will be removed from the list. + :return: a :class:`list` of all the values for the key. + """ + try: + rv = dict.__getitem__(self, key) + except KeyError: + return [] + if type is None: + return list(rv) + result = [] + for item in rv: + try: + result.append(type(item)) + except ValueError: + pass + return result + + def setlist(self, key, new_list): + """Remove the old values for a key and add new ones. Note that the list + you pass the values in will be shallow-copied before it is inserted in + the dictionary. + + >>> d = MultiDict() + >>> d.setlist('foo', ['1', '2']) + >>> d['foo'] + '1' + >>> d.getlist('foo') + ['1', '2'] + + :param key: The key for which the values are set. + :param new_list: An iterable with the new values for the key. Old values + are removed first. + """ + dict.__setitem__(self, key, list(new_list)) + + def setdefault(self, key, default=None): + """Returns the value for the key if it is in the dict, otherwise it + returns `default` and sets that value for `key`. + + :param key: The key to be looked up. + :param default: The default value to be returned if the key is not + in the dict. If not further specified it's `None`. + """ + if key not in self: + self[key] = default + else: + default = self[key] + return default + + def setlistdefault(self, key, default_list=None): + """Like `setdefault` but sets multiple values. The list returned + is not a copy, but the list that is actually used internally. This + means that you can put new values into the dict by appending items + to the list: + + >>> d = MultiDict({"foo": 1}) + >>> d.setlistdefault("foo").extend([2, 3]) + >>> d.getlist("foo") + [1, 2, 3] + + :param key: The key to be looked up. + :param default_list: An iterable of default values. It is either copied + (in case it was a list) or converted into a list + before returned. + :return: a :class:`list` + """ + if key not in self: + default_list = list(default_list or ()) + dict.__setitem__(self, key, default_list) + else: + default_list = dict.__getitem__(self, key) + return default_list + + def items(self, multi=False): + """Return an iterator of ``(key, value)`` pairs. + + :param multi: If set to `True` the iterator returned will have a pair + for each value of each key. Otherwise it will only + contain pairs for the first value of each key. + """ + for key, values in dict.items(self): + if multi: + for value in values: + yield key, value + else: + yield key, values[0] + + def lists(self): + """Return a iterator of ``(key, values)`` pairs, where values is the list + of all values associated with the key.""" + for key, values in dict.items(self): + yield key, list(values) + + def values(self): + """Returns an iterator of the first value on every key's value list.""" + for values in dict.values(self): + yield values[0] + + def listvalues(self): + """Return an iterator of all values associated with a key. Zipping + :meth:`keys` and this is the same as calling :meth:`lists`: + + >>> d = MultiDict({"foo": [1, 2, 3]}) + >>> zip(d.keys(), d.listvalues()) == d.lists() + True + """ + return dict.values(self) + + def copy(self): + """Return a shallow copy of this object.""" + return self.__class__(self) + + def deepcopy(self, memo=None): + """Return a deep copy of this object.""" + return self.__class__(deepcopy(self.to_dict(flat=False), memo)) + + def to_dict(self, flat=True): + """Return the contents as regular dict. If `flat` is `True` the + returned dict will only have the first item present, if `flat` is + `False` all values will be returned as lists. + + :param flat: If set to `False` the dict returned will have lists + with all the values in it. Otherwise it will only + contain the first value for each key. + :return: a :class:`dict` + """ + if flat: + return dict(self.items()) + return dict(self.lists()) + + def update(self, mapping): + """update() extends rather than replaces existing key lists: + + >>> a = MultiDict({'x': 1}) + >>> b = MultiDict({'x': 2, 'y': 3}) + >>> a.update(b) + >>> a + MultiDict([('y', 3), ('x', 1), ('x', 2)]) + + If the value list for a key in ``other_dict`` is empty, no new values + will be added to the dict and the key will not be created: + + >>> x = {'empty_list': []} + >>> y = MultiDict() + >>> y.update(x) + >>> y + MultiDict([]) + """ + for key, value in iter_multi_items(mapping): + MultiDict.add(self, key, value) + + def pop(self, key, default=_missing): + """Pop the first item for a list on the dict. Afterwards the + key is removed from the dict, so additional values are discarded: + + >>> d = MultiDict({"foo": [1, 2, 3]}) + >>> d.pop("foo") + 1 + >>> "foo" in d + False + + :param key: the key to pop. + :param default: if provided the value to return if the key was + not in the dictionary. + """ + try: + lst = dict.pop(self, key) + + if len(lst) == 0: + raise exceptions.BadRequestKeyError(key) + + return lst[0] + except KeyError: + if default is not _missing: + return default + + raise exceptions.BadRequestKeyError(key) from None + + def popitem(self): + """Pop an item from the dict.""" + try: + item = dict.popitem(self) + + if len(item[1]) == 0: + raise exceptions.BadRequestKeyError(item[0]) + + return (item[0], item[1][0]) + except KeyError as e: + raise exceptions.BadRequestKeyError(e.args[0]) from None + + def poplist(self, key): + """Pop the list for a key from the dict. If the key is not in the dict + an empty list is returned. + + .. versionchanged:: 0.5 + If the key does no longer exist a list is returned instead of + raising an error. + """ + return dict.pop(self, key, []) + + def popitemlist(self): + """Pop a ``(key, list)`` tuple from the dict.""" + try: + return dict.popitem(self) + except KeyError as e: + raise exceptions.BadRequestKeyError(e.args[0]) from None + + def __copy__(self): + return self.copy() + + def __deepcopy__(self, memo): + return self.deepcopy(memo=memo) + + def __repr__(self): + return f"{type(self).__name__}({list(self.items(multi=True))!r})" + + +class _omd_bucket: + """Wraps values in the :class:`OrderedMultiDict`. This makes it + possible to keep an order over multiple different keys. It requires + a lot of extra memory and slows down access a lot, but makes it + possible to access elements in O(1) and iterate in O(n). + """ + + __slots__ = ("prev", "key", "value", "next") + + def __init__(self, omd, key, value): + self.prev = omd._last_bucket + self.key = key + self.value = value + self.next = None + + if omd._first_bucket is None: + omd._first_bucket = self + if omd._last_bucket is not None: + omd._last_bucket.next = self + omd._last_bucket = self + + def unlink(self, omd): + if self.prev: + self.prev.next = self.next + if self.next: + self.next.prev = self.prev + if omd._first_bucket is self: + omd._first_bucket = self.next + if omd._last_bucket is self: + omd._last_bucket = self.prev + + +class OrderedMultiDict(MultiDict): + """Works like a regular :class:`MultiDict` but preserves the + order of the fields. To convert the ordered multi dict into a + list you can use the :meth:`items` method and pass it ``multi=True``. + + In general an :class:`OrderedMultiDict` is an order of magnitude + slower than a :class:`MultiDict`. + + .. admonition:: note + + Due to a limitation in Python you cannot convert an ordered + multi dict into a regular dict by using ``dict(multidict)``. + Instead you have to use the :meth:`to_dict` method, otherwise + the internal bucket objects are exposed. + """ + + def __init__(self, mapping=None): + dict.__init__(self) + self._first_bucket = self._last_bucket = None + if mapping is not None: + OrderedMultiDict.update(self, mapping) + + def __eq__(self, other): + if not isinstance(other, MultiDict): + return NotImplemented + if isinstance(other, OrderedMultiDict): + iter1 = iter(self.items(multi=True)) + iter2 = iter(other.items(multi=True)) + try: + for k1, v1 in iter1: + k2, v2 = next(iter2) + if k1 != k2 or v1 != v2: + return False + except StopIteration: + return False + try: + next(iter2) + except StopIteration: + return True + return False + if len(self) != len(other): + return False + for key, values in self.lists(): + if other.getlist(key) != values: + return False + return True + + __hash__ = None + + def __reduce_ex__(self, protocol): + return type(self), (list(self.items(multi=True)),) + + def __getstate__(self): + return list(self.items(multi=True)) + + def __setstate__(self, values): + dict.clear(self) + for key, value in values: + self.add(key, value) + + def __getitem__(self, key): + if key in self: + return dict.__getitem__(self, key)[0].value + raise exceptions.BadRequestKeyError(key) + + def __setitem__(self, key, value): + self.poplist(key) + self.add(key, value) + + def __delitem__(self, key): + self.pop(key) + + def keys(self): + return (key for key, value in self.items()) + + def __iter__(self): + return iter(self.keys()) + + def values(self): + return (value for key, value in self.items()) + + def items(self, multi=False): + ptr = self._first_bucket + if multi: + while ptr is not None: + yield ptr.key, ptr.value + ptr = ptr.next + else: + returned_keys = set() + while ptr is not None: + if ptr.key not in returned_keys: + returned_keys.add(ptr.key) + yield ptr.key, ptr.value + ptr = ptr.next + + def lists(self): + returned_keys = set() + ptr = self._first_bucket + while ptr is not None: + if ptr.key not in returned_keys: + yield ptr.key, self.getlist(ptr.key) + returned_keys.add(ptr.key) + ptr = ptr.next + + def listvalues(self): + for _key, values in self.lists(): + yield values + + def add(self, key, value): + dict.setdefault(self, key, []).append(_omd_bucket(self, key, value)) + + def getlist(self, key, type=None): + try: + rv = dict.__getitem__(self, key) + except KeyError: + return [] + if type is None: + return [x.value for x in rv] + result = [] + for item in rv: + try: + result.append(type(item.value)) + except ValueError: + pass + return result + + def setlist(self, key, new_list): + self.poplist(key) + for value in new_list: + self.add(key, value) + + def setlistdefault(self, key, default_list=None): + raise TypeError("setlistdefault is unsupported for ordered multi dicts") + + def update(self, mapping): + for key, value in iter_multi_items(mapping): + OrderedMultiDict.add(self, key, value) + + def poplist(self, key): + buckets = dict.pop(self, key, ()) + for bucket in buckets: + bucket.unlink(self) + return [x.value for x in buckets] + + def pop(self, key, default=_missing): + try: + buckets = dict.pop(self, key) + except KeyError: + if default is not _missing: + return default + + raise exceptions.BadRequestKeyError(key) from None + + for bucket in buckets: + bucket.unlink(self) + + return buckets[0].value + + def popitem(self): + try: + key, buckets = dict.popitem(self) + except KeyError as e: + raise exceptions.BadRequestKeyError(e.args[0]) from None + + for bucket in buckets: + bucket.unlink(self) + + return key, buckets[0].value + + def popitemlist(self): + try: + key, buckets = dict.popitem(self) + except KeyError as e: + raise exceptions.BadRequestKeyError(e.args[0]) from None + + for bucket in buckets: + bucket.unlink(self) + + return key, [x.value for x in buckets] + + +class CombinedMultiDict(ImmutableMultiDictMixin, MultiDict): + """A read only :class:`MultiDict` that you can pass multiple :class:`MultiDict` + instances as sequence and it will combine the return values of all wrapped + dicts: + + >>> from werkzeug.datastructures import CombinedMultiDict, MultiDict + >>> post = MultiDict([('foo', 'bar')]) + >>> get = MultiDict([('blub', 'blah')]) + >>> combined = CombinedMultiDict([get, post]) + >>> combined['foo'] + 'bar' + >>> combined['blub'] + 'blah' + + This works for all read operations and will raise a `TypeError` for + methods that usually change data which isn't possible. + + From Werkzeug 0.3 onwards, the `KeyError` raised by this class is also a + subclass of the :exc:`~exceptions.BadRequest` HTTP exception and will + render a page for a ``400 BAD REQUEST`` if caught in a catch-all for HTTP + exceptions. + """ + + def __reduce_ex__(self, protocol): + return type(self), (self.dicts,) + + def __init__(self, dicts=None): + self.dicts = list(dicts) or [] + + @classmethod + def fromkeys(cls, keys, value=None): + raise TypeError(f"cannot create {cls.__name__!r} instances by fromkeys") + + def __getitem__(self, key): + for d in self.dicts: + if key in d: + return d[key] + raise exceptions.BadRequestKeyError(key) + + def get(self, key, default=None, type=None): + for d in self.dicts: + if key in d: + if type is not None: + try: + return type(d[key]) + except ValueError: + continue + return d[key] + return default + + def getlist(self, key, type=None): + rv = [] + for d in self.dicts: + rv.extend(d.getlist(key, type)) + return rv + + def _keys_impl(self): + """This function exists so __len__ can be implemented more efficiently, + saving one list creation from an iterator. + """ + rv = set() + rv.update(*self.dicts) + return rv + + def keys(self): + return self._keys_impl() + + def __iter__(self): + return iter(self.keys()) + + def items(self, multi=False): + found = set() + for d in self.dicts: + for key, value in d.items(multi): + if multi: + yield key, value + elif key not in found: + found.add(key) + yield key, value + + def values(self): + for _key, value in self.items(): + yield value + + def lists(self): + rv = {} + for d in self.dicts: + for key, values in d.lists(): + rv.setdefault(key, []).extend(values) + return list(rv.items()) + + def listvalues(self): + return (x[1] for x in self.lists()) + + def copy(self): + """Return a shallow mutable copy of this object. + + This returns a :class:`MultiDict` representing the data at the + time of copying. The copy will no longer reflect changes to the + wrapped dicts. + + .. versionchanged:: 0.15 + Return a mutable :class:`MultiDict`. + """ + return MultiDict(self) + + def to_dict(self, flat=True): + """Return the contents as regular dict. If `flat` is `True` the + returned dict will only have the first item present, if `flat` is + `False` all values will be returned as lists. + + :param flat: If set to `False` the dict returned will have lists + with all the values in it. Otherwise it will only + contain the first item for each key. + :return: a :class:`dict` + """ + if flat: + return dict(self.items()) + + return dict(self.lists()) + + def __len__(self): + return len(self._keys_impl()) + + def __contains__(self, key): + for d in self.dicts: + if key in d: + return True + return False + + def __repr__(self): + return f"{type(self).__name__}({self.dicts!r})" + + +class ImmutableDict(ImmutableDictMixin, dict): + """An immutable :class:`dict`. + + .. versionadded:: 0.5 + """ + + def __repr__(self): + return f"{type(self).__name__}({dict.__repr__(self)})" + + def copy(self): + """Return a shallow mutable copy of this object. Keep in mind that + the standard library's :func:`copy` function is a no-op for this class + like for any other python immutable type (eg: :class:`tuple`). + """ + return dict(self) + + def __copy__(self): + return self + + +class ImmutableMultiDict(ImmutableMultiDictMixin, MultiDict): + """An immutable :class:`MultiDict`. + + .. versionadded:: 0.5 + """ + + def copy(self): + """Return a shallow mutable copy of this object. Keep in mind that + the standard library's :func:`copy` function is a no-op for this class + like for any other python immutable type (eg: :class:`tuple`). + """ + return MultiDict(self) + + def __copy__(self): + return self + + +class ImmutableOrderedMultiDict(ImmutableMultiDictMixin, OrderedMultiDict): + """An immutable :class:`OrderedMultiDict`. + + .. versionadded:: 0.6 + """ + + def _iter_hashitems(self): + return enumerate(self.items(multi=True)) + + def copy(self): + """Return a shallow mutable copy of this object. Keep in mind that + the standard library's :func:`copy` function is a no-op for this class + like for any other python immutable type (eg: :class:`tuple`). + """ + return OrderedMultiDict(self) + + def __copy__(self): + return self + + +class CallbackDict(UpdateDictMixin, dict): + """A dict that calls a function passed every time something is changed. + The function is passed the dict instance. + """ + + def __init__(self, initial=None, on_update=None): + dict.__init__(self, initial or ()) + self.on_update = on_update + + def __repr__(self): + return f"<{type(self).__name__} {dict.__repr__(self)}>" + + +class HeaderSet(MutableSet): + """Similar to the :class:`ETags` class this implements a set-like structure. + Unlike :class:`ETags` this is case insensitive and used for vary, allow, and + content-language headers. + + If not constructed using the :func:`parse_set_header` function the + instantiation works like this: + + >>> hs = HeaderSet(['foo', 'bar', 'baz']) + >>> hs + HeaderSet(['foo', 'bar', 'baz']) + """ + + def __init__(self, headers=None, on_update=None): + self._headers = list(headers or ()) + self._set = {x.lower() for x in self._headers} + self.on_update = on_update + + def add(self, header): + """Add a new header to the set.""" + self.update((header,)) + + def remove(self, header): + """Remove a header from the set. This raises an :exc:`KeyError` if the + header is not in the set. + + .. versionchanged:: 0.5 + In older versions a :exc:`IndexError` was raised instead of a + :exc:`KeyError` if the object was missing. + + :param header: the header to be removed. + """ + key = header.lower() + if key not in self._set: + raise KeyError(header) + self._set.remove(key) + for idx, key in enumerate(self._headers): + if key.lower() == header: + del self._headers[idx] + break + if self.on_update is not None: + self.on_update(self) + + def update(self, iterable): + """Add all the headers from the iterable to the set. + + :param iterable: updates the set with the items from the iterable. + """ + inserted_any = False + for header in iterable: + key = header.lower() + if key not in self._set: + self._headers.append(header) + self._set.add(key) + inserted_any = True + if inserted_any and self.on_update is not None: + self.on_update(self) + + def discard(self, header): + """Like :meth:`remove` but ignores errors. + + :param header: the header to be discarded. + """ + try: + self.remove(header) + except KeyError: + pass + + def find(self, header): + """Return the index of the header in the set or return -1 if not found. + + :param header: the header to be looked up. + """ + header = header.lower() + for idx, item in enumerate(self._headers): + if item.lower() == header: + return idx + return -1 + + def index(self, header): + """Return the index of the header in the set or raise an + :exc:`IndexError`. + + :param header: the header to be looked up. + """ + rv = self.find(header) + if rv < 0: + raise IndexError(header) + return rv + + def clear(self): + """Clear the set.""" + self._set.clear() + del self._headers[:] + if self.on_update is not None: + self.on_update(self) + + def as_set(self, preserve_casing=False): + """Return the set as real python set type. When calling this, all + the items are converted to lowercase and the ordering is lost. + + :param preserve_casing: if set to `True` the items in the set returned + will have the original case like in the + :class:`HeaderSet`, otherwise they will + be lowercase. + """ + if preserve_casing: + return set(self._headers) + return set(self._set) + + def to_header(self): + """Convert the header set into an HTTP header string.""" + return ", ".join(map(http.quote_header_value, self._headers)) + + def __getitem__(self, idx): + return self._headers[idx] + + def __delitem__(self, idx): + rv = self._headers.pop(idx) + self._set.remove(rv.lower()) + if self.on_update is not None: + self.on_update(self) + + def __setitem__(self, idx, value): + old = self._headers[idx] + self._set.remove(old.lower()) + self._headers[idx] = value + self._set.add(value.lower()) + if self.on_update is not None: + self.on_update(self) + + def __contains__(self, header): + return header.lower() in self._set + + def __len__(self): + return len(self._set) + + def __iter__(self): + return iter(self._headers) + + def __bool__(self): + return bool(self._set) + + def __str__(self): + return self.to_header() + + def __repr__(self): + return f"{type(self).__name__}({self._headers!r})" + + +# circular dependencies +from .. import http diff --git a/venv/Lib/site-packages/werkzeug/datastructures/structures.pyi b/venv/Lib/site-packages/werkzeug/datastructures/structures.pyi new file mode 100644 index 00000000..7086ddae --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/datastructures/structures.pyi @@ -0,0 +1,206 @@ +from collections.abc import Callable +from collections.abc import Iterable +from collections.abc import Iterator +from collections.abc import Mapping +from typing import Any +from typing import Generic +from typing import Literal +from typing import NoReturn +from typing import overload +from typing import TypeVar + +from .mixins import ImmutableDictMixin +from .mixins import ImmutableListMixin +from .mixins import ImmutableMultiDictMixin +from .mixins import UpdateDictMixin + +D = TypeVar("D") +K = TypeVar("K") +T = TypeVar("T") +V = TypeVar("V") +_CD = TypeVar("_CD", bound="CallbackDict[Any, Any]") + +def is_immutable(self: object) -> NoReturn: ... +def iter_multi_items( + mapping: Mapping[K, V | Iterable[V]] | Iterable[tuple[K, V]], +) -> Iterator[tuple[K, V]]: ... + +class ImmutableList(ImmutableListMixin[V]): ... + +class TypeConversionDict(dict[K, V]): + @overload + def get(self, key: K, default: None = ..., type: None = ...) -> V | None: ... + @overload + def get(self, key: K, default: D, type: None = ...) -> D | V: ... + @overload + def get(self, key: K, default: D, type: Callable[[V], T]) -> D | T: ... + @overload + def get(self, key: K, type: Callable[[V], T]) -> T | None: ... + +class ImmutableTypeConversionDict(ImmutableDictMixin[K, V], TypeConversionDict[K, V]): + def copy(self) -> TypeConversionDict[K, V]: ... + def __copy__(self) -> ImmutableTypeConversionDict[K, V]: ... + +class MultiDict(TypeConversionDict[K, V]): + def __init__( + self, + mapping: Mapping[K, Iterable[V] | V] | Iterable[tuple[K, V]] | None = None, + ) -> None: ... + def __getitem__(self, item: K) -> V: ... + def __setitem__(self, key: K, value: V) -> None: ... + def add(self, key: K, value: V) -> None: ... + @overload + def getlist(self, key: K) -> list[V]: ... + @overload + def getlist(self, key: K, type: Callable[[V], T] = ...) -> list[T]: ... + def setlist(self, key: K, new_list: Iterable[V]) -> None: ... + def setdefault(self, key: K, default: V | None = None) -> V: ... + def setlistdefault( + self, key: K, default_list: Iterable[V] | None = None + ) -> list[V]: ... + def items(self, multi: bool = False) -> Iterator[tuple[K, V]]: ... # type: ignore + def lists(self) -> Iterator[tuple[K, list[V]]]: ... + def values(self) -> Iterator[V]: ... # type: ignore + def listvalues(self) -> Iterator[list[V]]: ... + def copy(self) -> MultiDict[K, V]: ... + def deepcopy(self, memo: Any = None) -> MultiDict[K, V]: ... + @overload + def to_dict(self) -> dict[K, V]: ... + @overload + def to_dict(self, flat: Literal[False]) -> dict[K, list[V]]: ... + def update( # type: ignore + self, mapping: Mapping[K, Iterable[V] | V] | Iterable[tuple[K, V]] + ) -> None: ... + @overload + def pop(self, key: K) -> V: ... + @overload + def pop(self, key: K, default: V | T = ...) -> V | T: ... + def popitem(self) -> tuple[K, V]: ... + def poplist(self, key: K) -> list[V]: ... + def popitemlist(self) -> tuple[K, list[V]]: ... + def __copy__(self) -> MultiDict[K, V]: ... + def __deepcopy__(self, memo: Any) -> MultiDict[K, V]: ... + +class _omd_bucket(Generic[K, V]): + prev: _omd_bucket[K, V] | None + next: _omd_bucket[K, V] | None + key: K + value: V + def __init__(self, omd: OrderedMultiDict[K, V], key: K, value: V) -> None: ... + def unlink(self, omd: OrderedMultiDict[K, V]) -> None: ... + +class OrderedMultiDict(MultiDict[K, V]): + _first_bucket: _omd_bucket[K, V] | None + _last_bucket: _omd_bucket[K, V] | None + def __init__(self, mapping: Mapping[K, V] | None = None) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __getitem__(self, key: K) -> V: ... + def __setitem__(self, key: K, value: V) -> None: ... + def __delitem__(self, key: K) -> None: ... + def keys(self) -> Iterator[K]: ... # type: ignore + def __iter__(self) -> Iterator[K]: ... + def values(self) -> Iterator[V]: ... # type: ignore + def items(self, multi: bool = False) -> Iterator[tuple[K, V]]: ... # type: ignore + def lists(self) -> Iterator[tuple[K, list[V]]]: ... + def listvalues(self) -> Iterator[list[V]]: ... + def add(self, key: K, value: V) -> None: ... + @overload + def getlist(self, key: K) -> list[V]: ... + @overload + def getlist(self, key: K, type: Callable[[V], T] = ...) -> list[T]: ... + def setlist(self, key: K, new_list: Iterable[V]) -> None: ... + def setlistdefault( + self, key: K, default_list: Iterable[V] | None = None + ) -> list[V]: ... + def update( # type: ignore + self, mapping: Mapping[K, V] | Iterable[tuple[K, V]] + ) -> None: ... + def poplist(self, key: K) -> list[V]: ... + @overload + def pop(self, key: K) -> V: ... + @overload + def pop(self, key: K, default: V | T = ...) -> V | T: ... + def popitem(self) -> tuple[K, V]: ... + def popitemlist(self) -> tuple[K, list[V]]: ... + +class CombinedMultiDict(ImmutableMultiDictMixin[K, V], MultiDict[K, V]): # type: ignore + dicts: list[MultiDict[K, V]] + def __init__(self, dicts: Iterable[MultiDict[K, V]] | None) -> None: ... + @classmethod + def fromkeys(cls, keys: Any, value: Any = None) -> NoReturn: ... + def __getitem__(self, key: K) -> V: ... + @overload # type: ignore + def get(self, key: K) -> V | None: ... + @overload + def get(self, key: K, default: V | T = ...) -> V | T: ... + @overload + def get( + self, key: K, default: T | None = None, type: Callable[[V], T] = ... + ) -> T | None: ... + @overload + def getlist(self, key: K) -> list[V]: ... + @overload + def getlist(self, key: K, type: Callable[[V], T] = ...) -> list[T]: ... + def _keys_impl(self) -> set[K]: ... + def keys(self) -> set[K]: ... # type: ignore + def __iter__(self) -> set[K]: ... # type: ignore + def items(self, multi: bool = False) -> Iterator[tuple[K, V]]: ... # type: ignore + def values(self) -> Iterator[V]: ... # type: ignore + def lists(self) -> Iterator[tuple[K, list[V]]]: ... + def listvalues(self) -> Iterator[list[V]]: ... + def copy(self) -> MultiDict[K, V]: ... + @overload + def to_dict(self) -> dict[K, V]: ... + @overload + def to_dict(self, flat: Literal[False]) -> dict[K, list[V]]: ... + def __contains__(self, key: K) -> bool: ... # type: ignore + def has_key(self, key: K) -> bool: ... + +class ImmutableDict(ImmutableDictMixin[K, V], dict[K, V]): + def copy(self) -> dict[K, V]: ... + def __copy__(self) -> ImmutableDict[K, V]: ... + +class ImmutableMultiDict( # type: ignore + ImmutableMultiDictMixin[K, V], MultiDict[K, V] +): + def copy(self) -> MultiDict[K, V]: ... + def __copy__(self) -> ImmutableMultiDict[K, V]: ... + +class ImmutableOrderedMultiDict( # type: ignore + ImmutableMultiDictMixin[K, V], OrderedMultiDict[K, V] +): + def _iter_hashitems(self) -> Iterator[tuple[int, tuple[K, V]]]: ... + def copy(self) -> OrderedMultiDict[K, V]: ... + def __copy__(self) -> ImmutableOrderedMultiDict[K, V]: ... + +class CallbackDict(UpdateDictMixin[K, V], dict[K, V]): + def __init__( + self, + initial: Mapping[K, V] | Iterable[tuple[K, V]] | None = None, + on_update: Callable[[_CD], None] | None = None, + ) -> None: ... + +class HeaderSet(set[str]): + _headers: list[str] + _set: set[str] + on_update: Callable[[HeaderSet], None] | None + def __init__( + self, + headers: Iterable[str] | None = None, + on_update: Callable[[HeaderSet], None] | None = None, + ) -> None: ... + def add(self, header: str) -> None: ... + def remove(self, header: str) -> None: ... + def update(self, iterable: Iterable[str]) -> None: ... # type: ignore + def discard(self, header: str) -> None: ... + def find(self, header: str) -> int: ... + def index(self, header: str) -> int: ... + def clear(self) -> None: ... + def as_set(self, preserve_casing: bool = False) -> set[str]: ... + def to_header(self) -> str: ... + def __getitem__(self, idx: int) -> str: ... + def __delitem__(self, idx: int) -> None: ... + def __setitem__(self, idx: int, value: str) -> None: ... + def __contains__(self, header: str) -> bool: ... # type: ignore + def __len__(self) -> int: ... + def __iter__(self) -> Iterator[str]: ... diff --git a/venv/Lib/site-packages/werkzeug/debug/__init__.py b/venv/Lib/site-packages/werkzeug/debug/__init__.py new file mode 100644 index 00000000..6bef30fb --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/debug/__init__.py @@ -0,0 +1,560 @@ +from __future__ import annotations + +import getpass +import hashlib +import json +import os +import pkgutil +import re +import sys +import time +import typing as t +import uuid +from contextlib import ExitStack +from io import BytesIO +from itertools import chain +from os.path import basename +from os.path import join +from zlib import adler32 + +from .._internal import _log +from ..exceptions import NotFound +from ..exceptions import SecurityError +from ..http import parse_cookie +from ..sansio.utils import host_is_trusted +from ..security import gen_salt +from ..utils import send_file +from ..wrappers.request import Request +from ..wrappers.response import Response +from .console import Console +from .tbtools import DebugFrameSummary +from .tbtools import DebugTraceback +from .tbtools import render_console_html + +if t.TYPE_CHECKING: + from _typeshed.wsgi import StartResponse + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + +# A week +PIN_TIME = 60 * 60 * 24 * 7 + + +def hash_pin(pin: str) -> str: + return hashlib.sha1(f"{pin} added salt".encode("utf-8", "replace")).hexdigest()[:12] + + +_machine_id: str | bytes | None = None + + +def get_machine_id() -> str | bytes | None: + global _machine_id + + if _machine_id is not None: + return _machine_id + + def _generate() -> str | bytes | None: + linux = b"" + + # machine-id is stable across boots, boot_id is not. + for filename in "/etc/machine-id", "/proc/sys/kernel/random/boot_id": + try: + with open(filename, "rb") as f: + value = f.readline().strip() + except OSError: + continue + + if value: + linux += value + break + + # Containers share the same machine id, add some cgroup + # information. This is used outside containers too but should be + # relatively stable across boots. + try: + with open("/proc/self/cgroup", "rb") as f: + linux += f.readline().strip().rpartition(b"/")[2] + except OSError: + pass + + if linux: + return linux + + # On OS X, use ioreg to get the computer's serial number. + try: + # subprocess may not be available, e.g. Google App Engine + # https://github.com/pallets/werkzeug/issues/925 + from subprocess import PIPE + from subprocess import Popen + + dump = Popen( + ["ioreg", "-c", "IOPlatformExpertDevice", "-d", "2"], stdout=PIPE + ).communicate()[0] + match = re.search(b'"serial-number" = <([^>]+)', dump) + + if match is not None: + return match.group(1) + except (OSError, ImportError): + pass + + # On Windows, use winreg to get the machine guid. + if sys.platform == "win32": + import winreg + + try: + with winreg.OpenKey( + winreg.HKEY_LOCAL_MACHINE, + "SOFTWARE\\Microsoft\\Cryptography", + 0, + winreg.KEY_READ | winreg.KEY_WOW64_64KEY, + ) as rk: + guid: str | bytes + guid_type: int + guid, guid_type = winreg.QueryValueEx(rk, "MachineGuid") + + if guid_type == winreg.REG_SZ: + return guid.encode() + + return guid + except OSError: + pass + + return None + + _machine_id = _generate() + return _machine_id + + +class _ConsoleFrame: + """Helper class so that we can reuse the frame console code for the + standalone console. + """ + + def __init__(self, namespace: dict[str, t.Any]): + self.console = Console(namespace) + self.id = 0 + + def eval(self, code: str) -> t.Any: + return self.console.eval(code) + + +def get_pin_and_cookie_name( + app: WSGIApplication, +) -> tuple[str, str] | tuple[None, None]: + """Given an application object this returns a semi-stable 9 digit pin + code and a random key. The hope is that this is stable between + restarts to not make debugging particularly frustrating. If the pin + was forcefully disabled this returns `None`. + + Second item in the resulting tuple is the cookie name for remembering. + """ + pin = os.environ.get("WERKZEUG_DEBUG_PIN") + rv = None + num = None + + # Pin was explicitly disabled + if pin == "off": + return None, None + + # Pin was provided explicitly + if pin is not None and pin.replace("-", "").isdecimal(): + # If there are separators in the pin, return it directly + if "-" in pin: + rv = pin + else: + num = pin + + modname = getattr(app, "__module__", t.cast(object, app).__class__.__module__) + username: str | None + + try: + # getuser imports the pwd module, which does not exist in Google + # App Engine. It may also raise a KeyError if the UID does not + # have a username, such as in Docker. + username = getpass.getuser() + except (ImportError, KeyError): + username = None + + mod = sys.modules.get(modname) + + # This information only exists to make the cookie unique on the + # computer, not as a security feature. + probably_public_bits = [ + username, + modname, + getattr(app, "__name__", type(app).__name__), + getattr(mod, "__file__", None), + ] + + # This information is here to make it harder for an attacker to + # guess the cookie name. They are unlikely to be contained anywhere + # within the unauthenticated debug page. + private_bits = [str(uuid.getnode()), get_machine_id()] + + h = hashlib.sha1() + for bit in chain(probably_public_bits, private_bits): + if not bit: + continue + if isinstance(bit, str): + bit = bit.encode() + h.update(bit) + h.update(b"cookiesalt") + + cookie_name = f"__wzd{h.hexdigest()[:20]}" + + # If we need to generate a pin we salt it a bit more so that we don't + # end up with the same value and generate out 9 digits + if num is None: + h.update(b"pinsalt") + num = f"{int(h.hexdigest(), 16):09d}"[:9] + + # Format the pincode in groups of digits for easier remembering if + # we don't have a result yet. + if rv is None: + for group_size in 5, 4, 3: + if len(num) % group_size == 0: + rv = "-".join( + num[x : x + group_size].rjust(group_size, "0") + for x in range(0, len(num), group_size) + ) + break + else: + rv = num + + return rv, cookie_name + + +class DebuggedApplication: + """Enables debugging support for a given application:: + + from werkzeug.debug import DebuggedApplication + from myapp import app + app = DebuggedApplication(app, evalex=True) + + The ``evalex`` argument allows evaluating expressions in any frame + of a traceback. This works by preserving each frame with its local + state. Some state, such as context globals, cannot be restored with + the frame by default. When ``evalex`` is enabled, + ``environ["werkzeug.debug.preserve_context"]`` will be a callable + that takes a context manager, and can be called multiple times. + Each context manager will be entered before evaluating code in the + frame, then exited again, so they can perform setup and cleanup for + each call. + + :param app: the WSGI application to run debugged. + :param evalex: enable exception evaluation feature (interactive + debugging). This requires a non-forking server. + :param request_key: The key that points to the request object in this + environment. This parameter is ignored in current + versions. + :param console_path: the URL for a general purpose console. + :param console_init_func: the function that is executed before starting + the general purpose console. The return value + is used as initial namespace. + :param show_hidden_frames: by default hidden traceback frames are skipped. + You can show them by setting this parameter + to `True`. + :param pin_security: can be used to disable the pin based security system. + :param pin_logging: enables the logging of the pin system. + + .. versionchanged:: 2.2 + Added the ``werkzeug.debug.preserve_context`` environ key. + """ + + _pin: str + _pin_cookie: str + + def __init__( + self, + app: WSGIApplication, + evalex: bool = False, + request_key: str = "werkzeug.request", + console_path: str = "/console", + console_init_func: t.Callable[[], dict[str, t.Any]] | None = None, + show_hidden_frames: bool = False, + pin_security: bool = True, + pin_logging: bool = True, + ) -> None: + if not console_init_func: + console_init_func = None + self.app = app + self.evalex = evalex + self.frames: dict[int, DebugFrameSummary | _ConsoleFrame] = {} + self.frame_contexts: dict[int, list[t.ContextManager[None]]] = {} + self.request_key = request_key + self.console_path = console_path + self.console_init_func = console_init_func + self.show_hidden_frames = show_hidden_frames + self.secret = gen_salt(20) + self._failed_pin_auth = 0 + + self.pin_logging = pin_logging + if pin_security: + # Print out the pin for the debugger on standard out. + if os.environ.get("WERKZEUG_RUN_MAIN") == "true" and pin_logging: + _log("warning", " * Debugger is active!") + if self.pin is None: + _log("warning", " * Debugger PIN disabled. DEBUGGER UNSECURED!") + else: + _log("info", " * Debugger PIN: %s", self.pin) + else: + self.pin = None + + self.trusted_hosts: list[str] = [".localhost", "127.0.0.1"] + """List of domains to allow requests to the debugger from. A leading dot + allows all subdomains. This only allows ``".localhost"`` domains by + default. + + .. versionadded:: 3.0.3 + """ + + @property + def pin(self) -> str | None: + if not hasattr(self, "_pin"): + pin_cookie = get_pin_and_cookie_name(self.app) + self._pin, self._pin_cookie = pin_cookie # type: ignore + return self._pin + + @pin.setter + def pin(self, value: str) -> None: + self._pin = value + + @property + def pin_cookie_name(self) -> str: + """The name of the pin cookie.""" + if not hasattr(self, "_pin_cookie"): + pin_cookie = get_pin_and_cookie_name(self.app) + self._pin, self._pin_cookie = pin_cookie # type: ignore + return self._pin_cookie + + def debug_application( + self, environ: WSGIEnvironment, start_response: StartResponse + ) -> t.Iterator[bytes]: + """Run the application and conserve the traceback frames.""" + contexts: list[t.ContextManager[t.Any]] = [] + + if self.evalex: + environ["werkzeug.debug.preserve_context"] = contexts.append + + app_iter = None + try: + app_iter = self.app(environ, start_response) + yield from app_iter + if hasattr(app_iter, "close"): + app_iter.close() + except Exception as e: + if hasattr(app_iter, "close"): + app_iter.close() # type: ignore + + tb = DebugTraceback(e, skip=1, hide=not self.show_hidden_frames) + + for frame in tb.all_frames: + self.frames[id(frame)] = frame + self.frame_contexts[id(frame)] = contexts + + is_trusted = bool(self.check_pin_trust(environ)) + html = tb.render_debugger_html( + evalex=self.evalex and self.check_host_trust(environ), + secret=self.secret, + evalex_trusted=is_trusted, + ) + response = Response(html, status=500, mimetype="text/html") + + try: + yield from response(environ, start_response) + except Exception: + # if we end up here there has been output but an error + # occurred. in that situation we can do nothing fancy any + # more, better log something into the error log and fall + # back gracefully. + environ["wsgi.errors"].write( + "Debugging middleware caught exception in streamed " + "response at a point where response headers were already " + "sent.\n" + ) + + environ["wsgi.errors"].write("".join(tb.render_traceback_text())) + + def execute_command( # type: ignore[return] + self, + request: Request, + command: str, + frame: DebugFrameSummary | _ConsoleFrame, + ) -> Response: + """Execute a command in a console.""" + if not self.check_host_trust(request.environ): + return SecurityError() # type: ignore[return-value] + + contexts = self.frame_contexts.get(id(frame), []) + + with ExitStack() as exit_stack: + for cm in contexts: + exit_stack.enter_context(cm) + + return Response(frame.eval(command), mimetype="text/html") + + def display_console(self, request: Request) -> Response: + """Display a standalone shell.""" + if not self.check_host_trust(request.environ): + return SecurityError() # type: ignore[return-value] + + if 0 not in self.frames: + if self.console_init_func is None: + ns = {} + else: + ns = dict(self.console_init_func()) + ns.setdefault("app", self.app) + self.frames[0] = _ConsoleFrame(ns) + is_trusted = bool(self.check_pin_trust(request.environ)) + return Response( + render_console_html(secret=self.secret, evalex_trusted=is_trusted), + mimetype="text/html", + ) + + def get_resource(self, request: Request, filename: str) -> Response: + """Return a static resource from the shared folder.""" + path = join("shared", basename(filename)) + + try: + data = pkgutil.get_data(__package__, path) + except OSError: + return NotFound() # type: ignore[return-value] + else: + if data is None: + return NotFound() # type: ignore[return-value] + + etag = str(adler32(data) & 0xFFFFFFFF) + return send_file( + BytesIO(data), request.environ, download_name=filename, etag=etag + ) + + def check_pin_trust(self, environ: WSGIEnvironment) -> bool | None: + """Checks if the request passed the pin test. This returns `True` if the + request is trusted on a pin/cookie basis and returns `False` if not. + Additionally if the cookie's stored pin hash is wrong it will return + `None` so that appropriate action can be taken. + """ + if self.pin is None: + return True + val = parse_cookie(environ).get(self.pin_cookie_name) + if not val or "|" not in val: + return False + ts_str, pin_hash = val.split("|", 1) + + try: + ts = int(ts_str) + except ValueError: + return False + + if pin_hash != hash_pin(self.pin): + return None + return (time.time() - PIN_TIME) < ts + + def check_host_trust(self, environ: WSGIEnvironment) -> bool: + return host_is_trusted(environ.get("HTTP_HOST"), self.trusted_hosts) + + def _fail_pin_auth(self) -> None: + time.sleep(5.0 if self._failed_pin_auth > 5 else 0.5) + self._failed_pin_auth += 1 + + def pin_auth(self, request: Request) -> Response: + """Authenticates with the pin.""" + if not self.check_host_trust(request.environ): + return SecurityError() # type: ignore[return-value] + + exhausted = False + auth = False + trust = self.check_pin_trust(request.environ) + pin = t.cast(str, self.pin) + + # If the trust return value is `None` it means that the cookie is + # set but the stored pin hash value is bad. This means that the + # pin was changed. In this case we count a bad auth and unset the + # cookie. This way it becomes harder to guess the cookie name + # instead of the pin as we still count up failures. + bad_cookie = False + if trust is None: + self._fail_pin_auth() + bad_cookie = True + + # If we're trusted, we're authenticated. + elif trust: + auth = True + + # If we failed too many times, then we're locked out. + elif self._failed_pin_auth > 10: + exhausted = True + + # Otherwise go through pin based authentication + else: + entered_pin = request.args["pin"] + + if entered_pin.strip().replace("-", "") == pin.replace("-", ""): + self._failed_pin_auth = 0 + auth = True + else: + self._fail_pin_auth() + + rv = Response( + json.dumps({"auth": auth, "exhausted": exhausted}), + mimetype="application/json", + ) + if auth: + rv.set_cookie( + self.pin_cookie_name, + f"{int(time.time())}|{hash_pin(pin)}", + httponly=True, + samesite="Strict", + secure=request.is_secure, + ) + elif bad_cookie: + rv.delete_cookie(self.pin_cookie_name) + return rv + + def log_pin_request(self, request: Request) -> Response: + """Log the pin if needed.""" + if not self.check_host_trust(request.environ): + return SecurityError() # type: ignore[return-value] + + if self.pin_logging and self.pin is not None: + _log( + "info", " * To enable the debugger you need to enter the security pin:" + ) + _log("info", " * Debugger pin code: %s", self.pin) + return Response("") + + def __call__( + self, environ: WSGIEnvironment, start_response: StartResponse + ) -> t.Iterable[bytes]: + """Dispatch the requests.""" + # important: don't ever access a function here that reads the incoming + # form data! Otherwise the application won't have access to that data + # any more! + request = Request(environ) + response = self.debug_application + if request.args.get("__debugger__") == "yes": + cmd = request.args.get("cmd") + arg = request.args.get("f") + secret = request.args.get("s") + frame = self.frames.get(request.args.get("frm", type=int)) # type: ignore + if cmd == "resource" and arg: + response = self.get_resource(request, arg) # type: ignore + elif cmd == "pinauth" and secret == self.secret: + response = self.pin_auth(request) # type: ignore + elif cmd == "printpin" and secret == self.secret: + response = self.log_pin_request(request) # type: ignore + elif ( + self.evalex + and cmd is not None + and frame is not None + and self.secret == secret + and self.check_pin_trust(environ) + ): + response = self.execute_command(request, cmd, frame) # type: ignore + elif ( + self.evalex + and self.console_path is not None + and request.path == self.console_path + ): + response = self.display_console(request) # type: ignore + return response(environ, start_response) diff --git a/venv/Lib/site-packages/werkzeug/debug/__pycache__/__init__.cpython-312.pyc b/venv/Lib/site-packages/werkzeug/debug/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a60d898c7a5d52a924426ecf3a1e09e25800d241 GIT binary patch literal 23153 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�beWgR7|mf&BSsQ#0liL?4d&x@C}qECGv>-spdfUR z-Q!J%kp{62EFPV>RgB%jb_%ZE-?!3E*~8IZiMOs^Z3*`D*o97-g5mXgtNBoH>HQ-c zxVS}E>ipU=?%|F38gvLV7SiE-NSK(M-ijVlYfvRwzhxbVqM5==yxum`2$Uvwp9ZT; z^fdTs-9TGo!@=qi-^vmV;YyBq=}0zx1@+wn)AtzIKxXI03?abks1`6oV#ur!5r2dp zs~(LY(yiB{_puEhI7ryCmD!WSiiKn`V=zD)frx@+@=4bvc`(`m`F%o?lcU!OWAv9A$XVD6WWWsx4l}V=eOfkV_s{Z4G?S8_DzaX}TLDp1 zFjVA98;cjFX$`qFVvfi@)x#L;us$KG9W*9o!faz;VX8{yVIZjVsa8g*l8QrLE376g z{>cncn@*YNM}yqcjaYs!Al<-PjQcOS~` zJNVP4KWUmex)9ivcJIFDubQ$io7Opc_-s|(jmy_A&y~zKW~%m19m`fVExMZ)+)YcM zJuc5x-GxZpSAD~C&GQRikSyCa!7B~^EzF#JH7i^bs`x_AyT0JZWleMZJb&xR;?5K4 zohKHyow!?e5`yuXx;GkLZTO5cmF}OhWfWX{)$EarnG+(4FeWtK!!@AbF zqx0X$)E!v#9k}B=@XIBCJ?s`s%b_4CtDY@I?-qSq(!MRrCR0V@|2A97%9mY8ez080 zl?G-`rQI!y?zXhMZNB_RRc}|_Dqrk8p6)!J={%7Mom_My{nW3PAEo*FKQeJ(zZLA> z!~dY_U=#PJ{J{pxzYYR=x6XvfcN=(uo1EP{&F_W|*K}_+zi)PTRhr)S@?GV&_kC7^ zD|m{pZ0Oo;eSe3E;9WeyyLSTqV3QRUeX!MvDn8h02HcM;*>FVL0-5tGM7%M(CxmMq ztZE7g$y~C2Rmx7qV9NFD6sFZh>E>btL4g&C!9))ls#sXLkn)W=y>=Ll!+HfM8V45$ z+&b*lbL2x9$>&A_MQ#O+^iXR zhb+maE=~G|yOFGfNob7QlZlf__}3x*8Crdtm)=9r6LL!b7NN#P(iqu=rty>Pexr3# zOPSbMXLH$AB|V3PP>E(+hDS9_9!lnOtVJnJRs9Dl>m=cE7#CIlMb*?l!qn@zTSn7g zdUMi3RL{y5r)Ae>!aT{Zp#Dj2*#lG0MejPO%;wI$6@D{(*Sl-UAH4qPg1ZGb%c6fn z+P^_NmN&Q0i|_2a)tB+_o3ejgTKV#`%bdk=l%IVpTiHl1c#lbQ6i;U2H3hq#D|RaMy{_*{_RPjO37GwyBT-f{ zA~(Nd3wFM-$BXw`V+QiiPms$XbM^K>;{Mv(!04|zZ^gEXz%3$BOPOyPCrq$_IM3ZS z;L~i$x+75;2K3`NM%c-^RjNEBLao7MSB!!H$I4LoPti|y%A`d=Ap3 zo`j9RsN}@W8YOa$D)@lBpOHs5g+$UoRz2)1$)HpE5T0s^%hDYH%;!-a=@Ya3bPw6! zqgY3}wqyR%k6w8Dg-q?EnToGXIhVXuOYWvQ<*h4kUiq{AcilbNiXgP!KIbxLb~wMt zj~Yh|4_;Z1L{l6%pDE%QS2xw#vy|7Vs2v)A3EvGA0hp)oZ<4wLNSm+ z%A#*d!IVVr^e=Fk22v>4^hR!Mbh1iX;~>4ZV!P&$D4_$z;P=*XQYrXhmtI8gSHI~Q zAP${>0HWA-ni-pEds>P16ZxDNgCQ5V1gMvZtH7H`(iRRRVj!nGK+Ci5m>G5d-3x#B zf*CRlwN$_x$XFsGFi8ZAj+Mw@$jxY2iI{S=Wc1?Tm=Xo4NtP-wsEI&$4;>VH=LqW_ zH!?BFzC>~xiKauM)zFaAUO*vRe#=XbAwso~3Jb>vl}Y~<5i%{khE}u_;HI|p0VQ>z zaiJEzqc8`l3gmC24w7>DXO6u-GO-CvF8Y{@qhY zK+^gGQ>mq@rt8TCU-QTQ%I{s8RjyCWCDT=%8UOaFBOuw9d{wiS>!q`o-gx2F7j6&b zUUDph;Pm|RzL)b=(c6W_C4b%Z{de3A^su4v0kdkpQG0MV_wH{0A-DNG7Y~0GE}@0T zBJe9;V;dcJBqG+jNq*c^udyQqMrChXFFTARfmWs#)DuQ`5)g3Ocn-h>KVwiI^p$ z_Ov?)VkcOrjw%zp*LgC!N7u!>pj#e*+|Bn zOesv=qj$m7;w9MI4Z=P`)B9_+8$Hu(F^y*#VC|#??9gC65hQ&IdYLmaAVY+Bz7suX zAMmMeO(NiAiU!GpDb99dSzHCacX`h8mi zr@$qS^VWfSFmHDERp+#m=%cOa z+O4;m@6_(Nw&S+{uKnSx-MwgUxMOdaD@)s3{-eD?o5$SxqBO@5*~0ctHT-{Eo&#@R zcqZy69KXi>dg>h9G$DHChH*=;b&7FUV`b@vS>p@>%!(e^K!o{`&mf}mnRT4N?D1`J zIc1Sxqc~xX$C2N_6Z!mj#X~!W@|fc1ixXagg@nW;k}vk$U2k!iGOcli%6CW$z9I)x z-#hW7eU-rg`tJLxu_}h}UNMC8!^Rt(e5ar!FbVe84Psbu+;kc!=R;+^4(H$86nl2B&yao|@xvdk{M($A@v{ebae{Tg&kXRz-s{{feJ4k*<2>6T z=dnq3=xw{+hkRJPw|eVZ>5wN`cM!^T2szk;YM@WDF3h3)j_$Lc&?eOo-bI39$D&0CPC*q6=l>R5OtF|+E=-sb`%=iTlMS&1IhTrm#A=IW{ z{-8J9u<8;+RcrahpHX|?ChsNk`p9b~?^oo}wS#U*gR%rtiDMS_nl#x$&8t>=Z7nk< zGp7xd2w5{M&F(J^J;NqnGa(W|(Tg#doaJ)s<^vy6p}!;V|H4zPn(UhTp*s6O{cH4y z>SD4Y&1OOR9#V^#5ojo$X0Es`0TBR{`WJK$UWE5+IZ~*39a(Q+%7RC}i@tSfUrr8` z@$H(jE_u9H2c`$UcV>3o8*Q()0qeu;+QU`WO*ykJ-_*ldw+{m5w7co9yE$9aF#Xu4 zHqPS*5}!T-BY02wVrfgdv}Nw}ozjij>iX&9!1#47Z)|yW%iLJHwtecvJ#XzIdmWka zwk&#Kx4mh8$1PLFyW^JplPf>I@@G4KZp!RAamRZi+ZLL!-1V)84EUPm9`%&O%82;5 zv|m3JSxQW1@}mErHiiZj4_WzkVg-Z(S%~ zH+S%dL-XdsXStm7l}|Z7wQ`S&Wm7qIc_9)p2oQP`@{fr`a5#x=TjQjyh&TA{eBz6(c~(Ne z<4gUrD&%z$PrA<{rF9)4avK6H!QiqXpYueGb1!Q0jqTw6tuda-n9&t+PLQ`o~Y-bw2`;&8+F=)PlWEqeeh?7>*Vj-~37( zej!ztR{%4LqHy!&3PadpE=IKt7ok4#6tG|-JPKw0uD?fVgI0um|2J>b*h-*Ce)CcW zz5_*3blVmi-`}HtTi&SQreVsbV?QYHt%99V8Wq5xL`4}#!&oyiCNSPDPgvr2P{zT^ zG&8e=BJ>$cEk|udXtDwsfF*ttbvb{-x{7H_@+)PD{|?$KA}EuRDeH@uQ}!31M|zRI zrb<}*gGjB)k6DNE`%?NkaulK2FgwMC$zFCah+?-d;W|o}2Zx z3~5dIoH|rEvoM0dOZ2=E1gddl4uatA%X*RL`>#y5|PvTE)h5u_8S zS>^FDAdP^nd6_;*S6lf2};MW2km`G}x*$ouc){S`c&@*oaW`U~}lb(nV5&Guwmo9D+e zuASNPnj2fLZCNaDNtd_G?aGvg@O*4l+>T87M#NRr-#CBm{9?uWbjA9){!GQDsqSo` zngr?Ww^ok6b@I)Vi|cl#*X_LZ)Svqo*6qx!>&gVWr@FFu5;lCt8+`LjwytRra)`Pu z^XG2$XX^G}v*QWgtm95u^W4BK%THWCcHPX>P1kL`Rg$UOd(93~VlG^c&d3%>TOuj+>TntRT&80<_3JMZ{9VOqIZ zwK-k2dH!goYUk9k4}m30<{C5JZ435o3{iwix|2S`C`HF)c8JQD0;8wh-SRJ}8dfVq zzzjNzIMqV$mw|!lBk6>cDpwZDn3Gh7wMvN+euI-xHG?S@axS)ii7nGMFnY z?bkVTKLi4f0YXarT-P|GiilTIFmoCw!)_dQfBY5I!tNP%r!dtvW0LYWPAy$KgOqOr zdF#k~L-+m~fK0y@^V|4Qkru0c`52EUs^312gO;%bV0uyAkgaRa)^Esm9nZG5WgD8a z^}%KTCae2iY5lSlA()1;iscf5>|ABdvV$NeS5>?0BFN3vALf@!3G{Fc&C6bbd|XxC zav4G8TxBh*ZaKg?Ju4Ly_cYJhJfE2?)|wSFApB~TwFdRr9N+1EvG+3*Z>^K()UH@5X8AeZXFbZ_E300%5`ap|F$4xe$I-d$ zV4#z8man)7Tv_L{I#l1))o6sYxzoycUtS0t6Qv{tW^rB zQG{soz7?T1_cZM|K&>?U`2)nDGg{JhN@C}o?Z#I2;{e)^&EO2u0oG#ZbZ%k4S$QFJ zfPP$NV2s{?Ve^2}AW-xE=!V%k*o`k+1YEfIBd~etX|{k znyl%+i1H$fEM=4l)m66~iQ@rR2Pw{ECYgn#AjeW@j|A|8rj$fPmg>8NADYAQkUMfW zCqu<*SwXaBObPD@%LW{X#R-X@DbPa z5x0r?4eYaxA)YhySskUzGCC~XLuai){T>{h0>-B zw_(M0g6FF~KW}pK8$adXE!Upm`MRtF(t85#S=&~u1biMdowD+spK;`~z5V|HPOj7p literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/debug/__pycache__/console.cpython-312.pyc b/venv/Lib/site-packages/werkzeug/debug/__pycache__/console.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e189864ce6fd6dbca7a9178fe892beff9d6b5e7f GIT binary patch literal 11674 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 J2oPrK{{AZLkwN4tZcFYiw5)x)&@gBz*71Hj3zy zt=otstznZ6Y?I8A*|fpU&Mcj#Q}66fH_6U)cG^E8at2vpXS}o9&g9Py4oQ=;Kicm* zNB2r1a&~90hjWk4`ObIFJ?DFW$N%Yc+8B7g?0RbOxor&dYy6=<0Z$?Y=Y7NcfWM z(=N);Js2E|Xf_<$hApb>ldmD1ZzQJ3zHk(fj1YXjU{vyrDlus+B>Nsa{lqEgR5dv$ z`C@~r(;7hU{QQm!`ekMLw8>v8F?~zCD<2I!lN3()46eJ0IOXLtG=P| z6$?gN^DZo*x}k<;Zz0b~UPA437rXkzgb+Fs6p1vZ@-*J3|+P+CWUp zHy~VOrw+Ii`>x2*E9Xyz2hOXoK|6sWE(M2V_54*?xfGYjhISE3--T;wA05xy$j<=& zR2;CT7Wh-EpqONSW3Nlw8of)!0s&W#b5bDo!~T+-alNX z*YfY-ihNzKB{+QsmKo>#d|Y}8PFf@EnWF(o)`H=P>eFIARg-`R+XGh>I00WPiWlT# z7yy(I(tv?&K6pYPdtwn;XY;G_Ku!wDN;?7g6p5tQZ&h$VvgVPn3iL3PwM%0oqk%{) z6pW}@UL9An)&NM8K_xgMXDx$z4+W#E%bMt#vL?DHKd;~}X89pGn&p%$DzX7ANwH(A zfDqz zscv7|xqphgOFQTK($2;tw<4HdJo&=OWx=~7c&7(G7HSkcq7eKU53T?nc5N$^vy9C2 zv!KE_i3ODDU0faugTn9yeWO7wbWxUku<7l&Q>I60 z2o~}YIgUV$Jj;x;ey%s(dU||RCM#&AN8%?7!6o^E13)f0S<-%!xyfb)5C(%(M$jX% z1PF@;0`cJ`Dv2LxH`$k_J$08gLb5L*j;vQ8df>WB-qJq8Z^;oxDZlr zE5V%-aPjM$#J$NEZ~`Oo5)=@=J(8qbhqKrMf3!(pow>XB&IemOH5I-WSS6-ai-t% z4JCF>^BCC01W@lATpUhAUm3^}#&7A3R~%ADfpV!%CCYN$d}tT8bt!l}3VwB*v)Qr& zQaq>^kto}&Kvb$-sYDS$SKU^J)%hm_#xMWCdt2n-X} zvU~*iUl@*sqgm@?$4)&JKzqe+R(u4F78?zRl4P^d~Xwzfc_qSyw}fsa9} zAeth@v`SkZ!2$mW1<g2McZpl%X^6g&Swz%n5b*lczZO74!qw*7r z=dQ(h!!~7`-h10pvjRe;f2x1kS-0e@OFJ7tuz0sFds~;htqYZDZ(Gu~VlPX^Uw&ln z>5uI78BhJ(<(Vgv$1?7k*@H6&=i&>E3tQ*@Jne3u>bdKw#g4kU;LPD<&x*y4lPp_m zmMk@I_!q@n+EPo;Qf<%2mScpWZgN?ZB5Pwx6a=oO++=(Gg0c&~Bks`Jk3`3P314pv z^n3?2Wd*GIOvZQ zO=Sz#u-FfODxUOp<{rn1mNg429{9rK5|6VK9DzbPF$z_NL4qWV?;(tzw|){_ULor? zvQm@adYB8q7{G7_Dd$-@!H+R2`$|QE34u4!#|TW+RT7Q^70)FALSiQbP{xL`C2Jx@ zNd?bcVU=2m|HDVT&Pc+WJj@}y&Xih7DMJCqh#<%q$@HcO)}6UG%K$ss17K@~BU(6$ zhh><r=&bF?zemHAe7*w;W1*0M0Ep2QxA_H@R#0S+JAbbr+ z{4jjGf~V*gvtl?3C8C_+0l#u7*tYmhU?5iO~MJiLsR>Loe4% zOH0<;jLVaBWSp+ab%$EygL8d4is@z>%_3 zw%3_AfqN^Bhs!7)k`kQ55c}8Fo!hKg}jDVGuoN3CI z$CckP$y`!n8{b*Hi>Rge_V)JU>s*^}y}F1iz^jv^k}PfQI;}LprzmKU_(cT?G;5Wk zV& zn~= zd5TI0=ro?3#$nff3rz*)zZYbJ(9dJ6N#Z147IoI{H<30$5@mzk`z@pm3@zDez}ZNf z1={RJn}f7jq0MQul|lRnV^S`uTG~V+Qsoe5sws{#NwpB668kHp&5{#fE2S+YBITCq zAoe6cY^z=>hqo8r74WW-8l*~iR|6*B2272T2QY0Kcl);z2d~JhMzfEoZdCZQ%n5^!ogSLBr# z>R=S6D2y9`0Z_sx(PbET?}l+w%Cty;$0%|7p-uqFBskCt9MlA?N#dJ4s25XjylgBQ z4#gySCk=LGP2nN%6Xh(c#9I`(9iwUCh}>HAjWoam9^sID4efxO8Q=$N5ce@_1_u$i zESfbT<`5`!D4_=awh#IVD=I|3pfUm>MTHZD6#*fUNb$N72bS-?4#U*LP=Lm-teRG5 zpPzX?UD=d0uT*ZD-8r*!-kkDx-g-D)*^@M9Jl^S{>9f;2p<1#3mU(gfeM`!9B4s&2 z+0W32fDTtr=neHNC}kG4W?oz5HD-|>H8fpE?YXLW|b;DCQUZ)iOP4r@kF;W3u@CZwIpaq^KdVwZ%f+X`5Ms*p# zEW{G1hu>DcSE<&F_)|hmu$mEL(#rK^4;mEO2lfQeqpf#?tw||uYuBg50ek`m=9MdP z3gg`Ntyy+}_=XT7!Vgs1pn%{lQJM;xOv;m3pt7avSR5FKNG6f=ep3NS68Zi#f`y=f z2<4cj;&`e?DUsUgk|>qdm!Q&u{+D2^`fpHROrj-Kwmr$OILfEpFP})VV1GJX$;&VA zn?5$xHFxAAdtFY${l-%}w|nl{IeD%#?P<<Q=2{MdcT~*;%$)$$&Em zuH$Q0=7O)HYP_3Mjh(kGb(ymA8$(k=)1j9mNq!aPb0(jj+B3a(>hRptbCaQWux#WOcAfP$Y7Ep)u+{VBibNV^ZG9EV{{xjSj2d?LVi=#RlC|3UZ!tk8-) zmq3h!EpV)I`~{8>2iyeFhY2w+yFkj7u>bU!hfcd-pGI=_sfk#r<_Sx}1h$}1iVF%U zI$=sc+zh!gXRDSTQD8kvW{h=|#A^X-wG@9Ci5`%v*89L%^lt$e+nb{P3+&n`*tKYJ zm!8v}hrADjBM(t<2F{YvUxU|6pOC+-H!eO82JXZ<`}}d`6dX5UECm_wbiBDp35*8A z%GR!;rc3fTsDrwkIym4f>bw$+jLFblmg~;F2tTj06VzTdQw9~Ja7a^nVODSvF~31M zm~c0;F>tyMJ6VaCZ`3d&jQb2UD}C6|4gK>C~O@Kco(GxWglX@2?qf1{yy99ZvY6TT_M}}8rz{UsE-$n>kmHFq+y|9n z+eD{1uqW68kJ88w0F^_GlsK>(BsvZIZtWuH?&qk)Mj=~d9H9JFQF0jPWv2u0FLw+2 zQRJ#4}^1U@U8mK zTokyEDfoI;Gh}2wx1t$YoI&0QGMO;IL*vLBFo2|@j}EJr7tLVAfc1Wm98Af0un~3M zFoU6ei!iwLdGqhQwX*>Ox-;Z4FjT@CFy^}9NwAPH%2Ct?9Gi4dynsBIM4~$@O%RfpAK4tqe*YgzrO*;HXW)62L_BcJl}u=A}RDK!tsFKntvDLfIUNb zxZx>0Ils^wcNYm_jECp=Zv$^akYYdvq3*BFnh**?b4LI*gd3s|Rv3)Lf?C#$9zjID zM*U<^%Zw6580#}=ykmMt8N%N}B&=##E0%<3m7~~dBSAsZ03l=u$XlSGKdpvOl`S)> zbf|)}T*U$z43o7Gyr5m^ycJ1~r=X!&ayTW(i($r!jEMV6O8Wwc4-vx-Fl;b7U-nwd z)ZvV`^@mjpw#Dr!_kon-z>3{F9eShtZSN1wsm%}Gw(rh(cPw;3(62h>?o2s4X@AFU zd&AoY7DDgsz1`Fa(a2?Y(~`SszB}!1O*vXAOy_NTGjz>9GV{oMN7}tT<=9R;_T0An zu%m0HYrbiIJni0{a_pvEdvDttjWG+>wELlyB{BWfFxWnHv zq#e{5L>&}k=ms;mX#>kxn#jAgCI^ou9NS%IV!+@FB-7Bz7Mgi%q5>Ta@g5YkEm z5bwA@Jr_aTfw5{3ib-abLV(AAGOrU)%nv?VsD~gn9xNWr~y4}$eJ?4(75pdn;&%H1fKv+Nm4Er zh+ad=CCY-mTyjLoODu*KWXVM-7-_^!@-r67)cfH61}_EEKpSXjCwSW>eFdEsL)_W> zeZ}vYNvF-GKWe-_8WIb~PlNA!0XBi!)=8*k`O(;@@(Q-yfC9ofKCGinLhbC+P*d>; z!7QUe8)Co3t4qjKT@&WsRObs08LjvuvGrQT}OEV+^QKMeh2Pt(dcy*_5P15jx;kL zG}j#6%zx0u9<66StYxA8;btD|^;?b}D5hxK9YWFnLn)d8n;4UahO{g4wQ|;w@(fVE zFog77@)nRzC%^><^O>0foMPU>)GY=T;z<)caLBgO(IJS!l%&agwguubxmAN-dKmbO zpL~X_vGh2Dw;&p6$lbi{0c(fc@Puf^j10PN8=m{EM|0_bz?Y)_b^6MR*0@^$eJ~+> z8=?wYr9NudtJm`H{How~gIEix&o{X^tQqoBr7c%J0dZ12eoFY;u$w_wz%GNH7&W*F z;;8oS7+h?MYAW2eiQ9W}*P!6i(>m)TPV}e$2an%DEGmUqG%Bo*no%S^g*#T6*Nc0WPEU5b|q zV3B)O6#r*95^lmG|3a2W$h1F{6n{g^A7Y_+8sYkYrwWSt#_xx}7k>THXN=(7H)EUT zr_ZcZH_kV`t=_JF06F~F9ZyZB+&k--amp{D5X_j)na;VsSG(Y1LNy42SFa)NnrR*+K}8LSli5=EHS6T+!H4#??l$h{ZPxCG#h>qIp)AsI z=65c@Dq9-QYv~5L_s@0A3M^Z*d?OP%i?OjZZ!rJIKv&uha@a!xIQ! zTC+hiS?8cWa%CaJPryy3tB~*Sjc+=Ix95CUf=U=OVk+Ft^O5^=U{MnqBGiOm0l6?A zycG0*^F&2}He~4)v;po}LN$-(oYwN@xnu zo37seQAKO2{8&<4D#twIvWuQks{OGv3E}_m8qZW5XUQ5~t2<&c^%f=Djgwe`xiEhO z{LX@0YRSt~h^iTq8h~<7QB=c-)?rj5VSyUP_EABa@NnHguk5LtO z#ZC0iaeNbn0|{(^ijDHG42fw9;K9B%F;$zA zwoGjUnx<#jsgsa__GTPa^lH_-<%d;^{JWbMoTeN%urs|*j*zqm2U(d3? za@82HsrrX+gDdB1Sn+P2JND{@OjXNG?JezAWvb=St>Y~o){T*zmg{p_d55!gh5G1CAy zs%Wl)q$U!Gk09NHaO|gO%R-VHxiY>VLzd^jQ*R@eur9z*(gIl8@X~Ez1N0F9EKYd{+HTUp$}IL9U9cHs!R7hoapHkBi&t!3YqyBDHIGxQU#qVY53W7P0Zsrs z#Xbf8pF^}k#Dl&&_R)9OW8{6;>iArQei9HMpQ^kCpP8fo0zx2MVNs6Zj*vqnP6iTV9$Db##AVf|m4kO|8KKZOEBAj{t4L{|8sjA5I8 z%{V_{te-HpPngPIGIhUX9{4Y&?pL<*ms=;zney6c4sY~8_$yt$d(wFaqU?2PN5iCb zRS=y5yc{NvkYdW$Dmius5YDEC&lxLQ`{kx;_BeZwA>|&%R`GJpq-o7$WB06XVfF$k odDx1K%}L)?HT2!sOyBLh$@{L@y(R+Om+b=_Yx|7Bl6>9&14bR*(EtDd literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/debug/__pycache__/tbtools.cpython-312.pyc b/venv/Lib/site-packages/werkzeug/debug/__pycache__/tbtools.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..001431b4f95ca2de3dabc96ca79ef1942c8973c7 GIT binary patch literal 16704 zcmbVzdvF}bncvL5pI9swZ-CGcd|^cb5crZr0gxyHd`KcGlaM9BqE?HY0kPm>7n&Im z#CoBFY~2}j@lHTwAAy((p(&D;v{T>d_uX|q}&K}^nU(ko~*!2j1zn15? z>zv4me1aR}2YH_2mV{-{!rs(&&m*MWN`Wa4>{aUomyCTJ#Ur zh!umiZceO2?E$g!hQ-Zo{U_Fo!S|elbr$XfCsw`3iJ>1@^idAhv$SfY)kv<<{d#I? z_ycQ!vQcahYu~dCHceT=jlZJAFt2%|$z)26s_|4(IfHM9q{O1*(yyp`d@lCnEGHvUX(Oze>*B{d<(B$hDT!-{sq6s5J$kCIwP zQnDJGP=$Cbm272gQcwTL22hqJq+>jJ(P%=6$?bUGef>g@u&b@mjj*!M9Ec$FfA#1~{jafn>2fq7T`89x894XK z`GNkv60(RYPbjJ+ZYDq9f9zcU0LWT*wJB0emejD)`i#C-CH?L0G*-QPC?!q_Yz2B+ zhoZ4d7vb*Fk3TjH2N=*nuQ$n|*%BkcE6JJG?ya_LchVoJI_9#6Jo zKaGv6X4gwc6;CEN4XS(OKyP8U2`{B$mn0E0dq8i3&B%B$E(il7l2G6QA+87sRzQd* zMIk9kqC!(MBncDALI$QtqCvzlM=FZGeCmub5~}-k%wjB>6o%udppY+&gBnaBq_AaD zNtUdrXdx=Iv~fxv-aMItfL)tNfHtZM@9$i+VAijpU>(s?w^dCtUj&wn|NmpXhILXuq^)sx1qzi;4Odv{X zG@fAXb&nhDDakTsK!E@$se(#li3>gG9@%eDn|#>XJ7BV8w=~w}0w%vO@dG2^B>bl}JC+)s>UiB>^QO(NEX7+c2HSW1+T3U!f6CZ;eA ztVdLkropJ7I2$E1DqR6>l4&wFtpn(aAww_=OfZ0z&85NaWlSo(dj7;IAv!*uh{ssz zjw0VDQ{OkBtZk5(p`l#a0i6yOnXYQ;=fG)M)f_R2ST=Tc_C8 zz^;o9B_!bmWS-a61W7E~Yte*~5+)Q$h>BvIcqp0>hNY-F0gWao6KFH4UVSB} zXv_&uZ}W6-Z*TX2gUpqQ#1ycRr@bA5dN1q)+5)FYs6=CG{4%i&(~OVqfz2y+ibM(e zYe+9+KJ+m{G)<`^7$^#1B8cC>le+Xl2_^{&quIQa#0K#sCKA&~Jci-KBw>=~5Nt+r z3^I^b*5nm)ny_=GI?Rn%!fJ&sLO+)z7UVXyR~Rw)`0a@}En9J!F`+1fZAhUf{Yq&K z(B7o_Bupn`71Kz<0(DO$up5SD8Ubwud?IyMn+SW2tPIQt?Pa~%sFjwXq;ITTs}L7^ zT3NJxS6f^Ri@Nw3IoNwV4qohz#j&dkd^?O3ny%JfXnErU{p#){s$RpofE9&;(wI?< zzDat~JDH-6LgpC6SgvkjT!VOcA_)$G%|#V$vkGysKbaA-x+R{THW&epipMorh1Jm)n~*+_$Y=2KW)$0 z-`$q6i?&PXpKK9r({?prP>xm_vEn;p%h=7{*)kS+_aCH(Z)CP-%kZOB#oCOR$Ym^l zYU$xFa?=ixpLVJ>Mh@C5Tf*o=A9vw@aNHZ{J^iL#)9z_c#wwr4xJGM@I*RYnhGN`^ ziFT1UYMb_EyrSbWCwq*Z;M;kr4Rh0S?GWNFdbZ8zMTS4mg{#jThfnV#J7;1%Lb6-YDoisj z5r@?*lSI_2B5(-{-cN)?Y9!LjG8{t^%w9DiVZ> z1kqs_4O#_znFIqeNtdu)a~WDlbLiSdvyraAR|+<+qB$=nQbQE6!@NwUG&kJ0h#qxC zm|BTMGzY1b)OeVeN!EVCgi=5p_uOm(E25 z&BLNsRavttQ;Oz>l9bgH>|D(eS72F6njKD3?2=|pLmjz>BZXdDQA{S;C)ZIx9HY61 zBgR;qBTpJLt!W`P+<7(cIS zwit{*&HsEVGQhf9iJVvqLNXr*i(U%vv6}xM1UI<@Rjl_N5)Gtp~EL z2Xd7MXHKp8Lf<*I(hy!gnDad`)4%TYtvKuR-paYIZ$F){u9@xsW%ahz>Ydr@olCFZ zvgfJ~&$@r^^sbd8+HQ%t>St!%d0)l*C$FBIdu2|Xdu8_QdU)^5sl2~x=J*2-SJ$`@ zUOM;jYahLqtL?bQ?XVxY>bdHe4bC2&vwZ0dKdQ^@%; z*tB>#Us=E5;2N54dKbO<+WPD9`FOs%_WFtW6ZzV@>!b6d`MUb+U!VW_U9Y3fGwXR! z$@xNa`{yR_cpuBR?)<|ebg$MU<|K2Cg;z}MHaz8&kX;Hs-W>#D!g@OZZ2 zspUZgU$~y$IKgAIchB&gw>lqeTE;yJKf)U=Ns>=VHCsj!#6H0AN$eSPtN9QyJx*?4SSbgyj}HoEk9}bOuW-@ ze%5lWg2*%9$gEY@Tt7a4{6^36jvuvu(w?h+YSz707hVqEI``u@{^pI(YL%6c`W8SbF&Ta|o|<5;w?;9H4G~&O40S6?FXz*0M|~*Ap+JeNpXSR1A4buC5A-rd-g%kBd~R~=mUipfOCyj!Q_=G zVZ&C<8c`*!20{i9a|GM5D0>twSdz?yg>r_epaI#9F2xF%HnZf((q@)CnJU-0q79zm zGnR}sWBVchgTmxwxQJWJ1w-GWWlN}(_ zxIf|Lvv?`g89^VOGXhyIV|2BTjE<$#5rRVI=M0w*R(ArQa{VuaWc`cnYYhEs_|YLc)TlRo`?-vk3}M_^^a z6f$6(t@Q%%!O_l$Bo~VcsaR}628bQDvsIU$!U$wCkF{#7_QW{DR*Lq5+=(pE5^HEy z0KS@&l~1Zr*CW6RaWn+HviLy?d&@4V*u1mT+qQ7i=IsBCmXz-zfGTxyf#9q)UsZd( zZN6>Rxn5DTQn5WBs9g;R*?_PT*tcQhYxZtfxnMJBAKSg!(wS}PocHE~^{c_1+2GEV z;1jp&R-b<5^QT`S(#K0lyRt1^^WOEA);VvUf^}cu{THsju;5*Kb)~NTj;|wMyM6Y= z#x5>Uo2}Ts(Z*GVW<8JcuoGQnJWSvLV1%;#+sD1nzi)Zp8na#GVwMXq@|gR}6L*y3 zX}HPYKk%V--g?bpQX!lvxfv%MDqeyo7>mDH>Z=8}?-z_Wjy!tMqJe2!38!Gsn;E z&)3vle{KG?drnGsaKWm3cFte1VZ~?O=YK$Nkh?v#+h_+Y{O=2F?wod^9(Tt1ZcWBL z>fcOHU!pDJgqE^@X!a7K(>01wZhcT|9Ao#W)VAUrM8mPsyLqg#~eCKCV0lp_vP~xx#8;r%k;N9XMW-rQWo!dx(y7-=4sccyHQ|v&oWA8=WDi?LgBH z^oDKF;rcOyW@p`nBxJ`5nz!^Yp+=6P+nOh?01#q8JCKcuG0lo|0nH^#bm%9Ey5b=# zfoUK4AjJ+*KuDTqizgvvNxEy^g21G+Qdy)FRE-l^))!qOyD+Wnz*O_=SYiUW6dRdLx=*Vp^uM6-G#?H=FPVd~ z(GjG4i3qJQW#q3>%#FmzIBp97O=6QHE0i3-WazUWVbd=sD3x`4cp{OIllUx)SLkZ@ zQO!b=@rN}FQB{*>`D+BwEKeN#ao}$PKW%t!xoctj&3)h7x6<(3Yy|@N%Qp@zypwC{ z_-S3=zYE=ZGuPF3r><|-2W2#C`*~nnzV^V?6YFHy{8DIJu3imyXT#k&q30gw_3xke z%~|JOWj_9TsCMqzya6q&?CJaT{HOh&)vfIM%AM+S>)UqTys&uT9%l`{!q4@uHE+9l zeDV059S3hox#sS<{`|9jf0zE-^ulZ3A6|atM}waX=5{}|vg_&F!EDnrpUHE5X1zxymnCC8MCu7GBW9Qyk zo@+eHGh_Ju(D6=NjCO(XkZNNJ|370zH@U3;-!{&)4OWw>&W(nCr`2St@gn6`p^U>N zQ^7+eSVRj{A8b-1CR$ZfnP+VJae*Oni#fleSEl-Vw8ZbQ^U9b5<&M0J`cO-#I5s0D z+JERUD2jc=AsN<*+ME4Dku2#aHGfd#at(GT%^$r7wmKb0G zWL-{!I&E+N`u=^>5Bc`62e<6lH7u<{f5X;f_pBh9^|Nwau((Z^4}^xds0&DE;r@o^ zHjm*opBcr8yyhre&GKosL>&J5i|B)Vg#r@3vV=f$zkKvWKa-zKM$4Behd~10q-+ZS zeaQTwutWDLW+|J{CHX34Q}^`40{JiSt&lrSK8E3CR2L+^Xk5@^(r!n2J9-n*F%ovS zQ5|3t2UpXuP<^w0v3|AT$!x=uxrV26)kgqw>PR8P!pX&xH_tAf%{2ou>i}dHsQ*&f zJwLpB;>)(fi^I21VBfCQG%cK3iY~sCs|l|)Jho){*!huj*_{*i=Nb;Iwd^bvBCETe z&F*?O*8*wUv|87dt?T;KzEanft2<65-MNOx*P2^M;w?S7eC$?3uKAgKZS&n4Z!@;P zm$tvxt^0}cTNpvuqa#U&5oi9`!yTq^(P3Hu{l@AF8R1L{_rGZ4X;$2`(xG{STWOw1L`VB^6yb|R-kyji=nMy6 zIBZZ7DOHI?0&i{l-5vbbmvQ!@z2yHM*p!%MQ`vFS5 zU(MSO!OwNnY*-rYp>=ofJv-j_twzemQ;6NO*zKM|8pSGj`{NHB?RL+`n|!6+1KqJ< zrT5M~`uph~_Fi}U9@vqt4~qs9!_RyQf``9P2M*tY;wS}qMa!V=5dhXb%MC+lzvm-Z zSM-Rs5&)MVU9V^_NpT^?2Qat@`?>)f6DW)`X@Y|Xy|eHf2Yqm;1I)rpf98(0Yy`FDxHQR*yy#?&OmOhL z0hZ0^OiVST8z0$&CZJ5iQpgGV4MbxF=dQTw#6d^a)wyI_adj>`K6iEI>sw}R*Zlbg z0T6S=qlgs^13&&gVSm31Y#22M3!=hP{8h^+(QJ9jO>c%* z!H4=&4w^0tIGamSgq{DAzYhMr_M8oT`d8LTUROhQcF3%Ga5@XGW-_jhXqJ?s*~X)Y z!JUgHCi}qgy}R?(HxO(Ap^7 zsO&fYi&z)FL&&pUSq(teAAJAx)zhoKomt<`CGm&P|LDXgCsy}$XZLmA9?0!`er4CO zRbSs-E9igAER1g+wH-WzO>EEmF;3pq?6>KpB=_$&c0(JIHc+AWE_vw;VjZo9jfVk6_MAg=6R%QI$FPf=E~38rqU*ItIrXI zhTsL}NSwRnxoTpea8aw!0x6L#kL(cLqW6YFchz78nenjy%&$m~$Rxv=yJp@Ir8=;3 z9LC1qAII8%*TR%#feR?xcnlR$PO2Mu({y}6O>KG1d`k%yZ;rVn%{*3QTxKnD*;U+d z6fK5RTFQUO0djBV*fTtCj>34rtwNjJoUzM|xJ~KESkveiVcUq?@P$zqQsC@nY{l;n z?KlO1(>v|B>Kp~fZGC`y^QZ@RGK+QM&Z)PA)6G!;N^pFSZW>xL4#eFk=ljrQtTj(I zmj8@?>xrZTyZXeixpb9h(*^rU+?AHkgCy-N+*N~Q=_$*nDW6rk@U-F~CPr2?IzD_O!~1pS%ORjrAZQf=w|k7}$EFtw;{3yD-_ zmL%oRP%rdYK2&%8+4*O4p`ELtz1h&-T&R7vFW)TOd|~m0@4Ym8dab5@q56Z@zaLt< zklTJBTXSIc_*!+-Lf_J!<*r4=Hj0R9{Z^OllXdV zczMSy$0z%iTW(F}akofX8eU9h>)KapJC>DO@lSVU51+_(o?PiTwPEFJUgYoMu(lRn zSVPmCecji%>f4_6ZO>QjSUR?JXr=9$+s8iL{gan&_kLFQ<-VuCb9kZsPN?m+vQl;I z%jWI#hnD1(s>hdiZ`dut{r_yW`E}z~uB3G#*j6U#A>)9E)0_p&moY-vp;_W$@s^$D z-*nrYZm;3CnEV+E0_ymWRLKnr{*xJFF7Nj!->zY!7f4!h>3<1l2B03#q}C}$8b2#5|G<-eyd7gZmLi&tRjkmbgr zE=5g^Q%wG^RE%}_zaXZNyn3s^Ny2lw&7&f%aOS?rA}HaY8Df?e1iuBdRE1`pYn2VF zm95#z)}@0V_k7fotL&P!6Bj)*|IAWw{@GdAT6Nt5zwp%3#cWdnbP3gdM_D-ZL3-(_ z<%`)Jhi>!P9Z%;%M`m65hNjty|6>ceEABqd`GWcSZ42>b*GhfYJ&yP9o3njcNvziP zMI{dMk#HBTc>RKXVPr9|+;GQtU>)=$%fZFO@@p&ghpEh7qs*Q!DsiHZ1Yks!jcZla za|0i=t<|?Is5hq#xth4vf5}1Q;9*gA}f9!u=|j* z{uKfZ*LLvdxWG|vQWA4X$y#CVmrU%&%rItW$;2FVk(GI)G5m!dTsU6-7^%#tEU?fM zREUDF5~F+rk>9+>-L+Wkt__zX=m5e}>9%jDv;XZIPKvp>s@4rR#XMZ7e#1*K9}tv( z_EW5u;Um2L$pSut7{gQUS!^a|f*48=j>6jyn{W|1X2%CqlAy40mzud6huou8v`x*I9K_>qh1#AWEImN|fEtH|DA%rqTmAxc2n?gDG(574%{olog{V=sCnq9ZDL%B z4omtepdEj$mQZwH@7q*{W?OTd>_6Jqe@=58KYMnd|C~&Yw5(AuMZpRMPg3v#0?k({ z#~2I9G_70kz=y7G9ct{Ae}P=gIj2wviNW(f<63{gRsIA0ceDQ<7RP7RKXKdsiQDlr zu6e^&<+05;Hrndg>mX^A+r4&jgCs&jBlgTSz(*;Y}9j|2R7_q0Y_gCREv|> Hx#|A_idM(S literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/debug/console.py b/venv/Lib/site-packages/werkzeug/debug/console.py new file mode 100644 index 00000000..4e40475a --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/debug/console.py @@ -0,0 +1,219 @@ +from __future__ import annotations + +import code +import sys +import typing as t +from contextvars import ContextVar +from types import CodeType + +from markupsafe import escape + +from .repr import debug_repr +from .repr import dump +from .repr import helper + +_stream: ContextVar[HTMLStringO] = ContextVar("werkzeug.debug.console.stream") +_ipy: ContextVar[_InteractiveConsole] = ContextVar("werkzeug.debug.console.ipy") + + +class HTMLStringO: + """A StringO version that HTML escapes on write.""" + + def __init__(self) -> None: + self._buffer: list[str] = [] + + def isatty(self) -> bool: + return False + + def close(self) -> None: + pass + + def flush(self) -> None: + pass + + def seek(self, n: int, mode: int = 0) -> None: + pass + + def readline(self) -> str: + if len(self._buffer) == 0: + return "" + ret = self._buffer[0] + del self._buffer[0] + return ret + + def reset(self) -> str: + val = "".join(self._buffer) + del self._buffer[:] + return val + + def _write(self, x: str) -> None: + self._buffer.append(x) + + def write(self, x: str) -> None: + self._write(escape(x)) + + def writelines(self, x: t.Iterable[str]) -> None: + self._write(escape("".join(x))) + + +class ThreadedStream: + """Thread-local wrapper for sys.stdout for the interactive console.""" + + @staticmethod + def push() -> None: + if not isinstance(sys.stdout, ThreadedStream): + sys.stdout = t.cast(t.TextIO, ThreadedStream()) + + _stream.set(HTMLStringO()) + + @staticmethod + def fetch() -> str: + try: + stream = _stream.get() + except LookupError: + return "" + + return stream.reset() + + @staticmethod + def displayhook(obj: object) -> None: + try: + stream = _stream.get() + except LookupError: + return _displayhook(obj) # type: ignore + + # stream._write bypasses escaping as debug_repr is + # already generating HTML for us. + if obj is not None: + _ipy.get().locals["_"] = obj + stream._write(debug_repr(obj)) + + def __setattr__(self, name: str, value: t.Any) -> None: + raise AttributeError(f"read only attribute {name}") + + def __dir__(self) -> list[str]: + return dir(sys.__stdout__) + + def __getattribute__(self, name: str) -> t.Any: + try: + stream = _stream.get() + except LookupError: + stream = sys.__stdout__ # type: ignore[assignment] + + return getattr(stream, name) + + def __repr__(self) -> str: + return repr(sys.__stdout__) + + +# add the threaded stream as display hook +_displayhook = sys.displayhook +sys.displayhook = ThreadedStream.displayhook + + +class _ConsoleLoader: + def __init__(self) -> None: + self._storage: dict[int, str] = {} + + def register(self, code: CodeType, source: str) -> None: + self._storage[id(code)] = source + # register code objects of wrapped functions too. + for var in code.co_consts: + if isinstance(var, CodeType): + self._storage[id(var)] = source + + def get_source_by_code(self, code: CodeType) -> str | None: + try: + return self._storage[id(code)] + except KeyError: + return None + + +class _InteractiveConsole(code.InteractiveInterpreter): + locals: dict[str, t.Any] + + def __init__(self, globals: dict[str, t.Any], locals: dict[str, t.Any]) -> None: + self.loader = _ConsoleLoader() + locals = { + **globals, + **locals, + "dump": dump, + "help": helper, + "__loader__": self.loader, + } + super().__init__(locals) + original_compile = self.compile + + def compile(source: str, filename: str, symbol: str) -> CodeType | None: + code = original_compile(source, filename, symbol) + + if code is not None: + self.loader.register(code, source) + + return code + + self.compile = compile # type: ignore[assignment] + self.more = False + self.buffer: list[str] = [] + + def runsource(self, source: str, **kwargs: t.Any) -> str: # type: ignore + source = f"{source.rstrip()}\n" + ThreadedStream.push() + prompt = "... " if self.more else ">>> " + try: + source_to_eval = "".join(self.buffer + [source]) + if super().runsource(source_to_eval, "", "single"): + self.more = True + self.buffer.append(source) + else: + self.more = False + del self.buffer[:] + finally: + output = ThreadedStream.fetch() + return f"{prompt}{escape(source)}{output}" + + def runcode(self, code: CodeType) -> None: + try: + exec(code, self.locals) + except Exception: + self.showtraceback() + + def showtraceback(self) -> None: + from .tbtools import DebugTraceback + + exc = t.cast(BaseException, sys.exc_info()[1]) + te = DebugTraceback(exc, skip=1) + sys.stdout._write(te.render_traceback_html()) # type: ignore + + def showsyntaxerror(self, filename: str | None = None) -> None: + from .tbtools import DebugTraceback + + exc = t.cast(BaseException, sys.exc_info()[1]) + te = DebugTraceback(exc, skip=4) + sys.stdout._write(te.render_traceback_html()) # type: ignore + + def write(self, data: str) -> None: + sys.stdout.write(data) + + +class Console: + """An interactive console.""" + + def __init__( + self, + globals: dict[str, t.Any] | None = None, + locals: dict[str, t.Any] | None = None, + ) -> None: + if locals is None: + locals = {} + if globals is None: + globals = {} + self._ipy = _InteractiveConsole(globals, locals) + + def eval(self, code: str) -> str: + _ipy.set(self._ipy) + old_sys_stdout = sys.stdout + try: + return self._ipy.runsource(code) + finally: + sys.stdout = old_sys_stdout diff --git a/venv/Lib/site-packages/werkzeug/debug/repr.py b/venv/Lib/site-packages/werkzeug/debug/repr.py new file mode 100644 index 00000000..2bbd9d54 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/debug/repr.py @@ -0,0 +1,282 @@ +"""Object representations for debugging purposes. Unlike the default +repr, these expose more information and produce HTML instead of ASCII. + +Together with the CSS and JavaScript of the debugger this gives a +colorful and more compact output. +""" + +from __future__ import annotations + +import codecs +import re +import sys +import typing as t +from collections import deque +from traceback import format_exception_only + +from markupsafe import escape + +missing = object() +_paragraph_re = re.compile(r"(?:\r\n|\r|\n){2,}") +RegexType = type(_paragraph_re) + +HELP_HTML = """\ +

+

%(title)s

+
%(text)s
+
\ +""" +OBJECT_DUMP_HTML = """\ +
+

%(title)s

+ %(repr)s + %(items)s
+
\ +""" + + +def debug_repr(obj: object) -> str: + """Creates a debug repr of an object as HTML string.""" + return DebugReprGenerator().repr(obj) + + +def dump(obj: object = missing) -> None: + """Print the object details to stdout._write (for the interactive + console of the web debugger. + """ + gen = DebugReprGenerator() + if obj is missing: + rv = gen.dump_locals(sys._getframe(1).f_locals) + else: + rv = gen.dump_object(obj) + sys.stdout._write(rv) # type: ignore + + +class _Helper: + """Displays an HTML version of the normal help, for the interactive + debugger only because it requires a patched sys.stdout. + """ + + def __repr__(self) -> str: + return "Type help(object) for help about object." + + def __call__(self, topic: t.Any | None = None) -> None: + if topic is None: + sys.stdout._write(f"{self!r}") # type: ignore + return + import pydoc + + pydoc.help(topic) + rv = sys.stdout.reset() # type: ignore + paragraphs = _paragraph_re.split(rv) + if len(paragraphs) > 1: + title = paragraphs[0] + text = "\n\n".join(paragraphs[1:]) + else: + title = "Help" + text = paragraphs[0] + sys.stdout._write(HELP_HTML % {"title": title, "text": text}) # type: ignore + + +helper = _Helper() + + +def _add_subclass_info(inner: str, obj: object, base: type | tuple[type, ...]) -> str: + if isinstance(base, tuple): + for cls in base: + if type(obj) is cls: + return inner + elif type(obj) is base: + return inner + module = "" + if obj.__class__.__module__ not in ("__builtin__", "exceptions"): + module = f'{obj.__class__.__module__}.' + return f"{module}{type(obj).__name__}({inner})" + + +def _sequence_repr_maker( + left: str, right: str, base: type, limit: int = 8 +) -> t.Callable[[DebugReprGenerator, t.Iterable[t.Any], bool], str]: + def proxy(self: DebugReprGenerator, obj: t.Iterable[t.Any], recursive: bool) -> str: + if recursive: + return _add_subclass_info(f"{left}...{right}", obj, base) + buf = [left] + have_extended_section = False + for idx, item in enumerate(obj): + if idx: + buf.append(", ") + if idx == limit: + buf.append('') + have_extended_section = True + buf.append(self.repr(item)) + if have_extended_section: + buf.append("") + buf.append(right) + return _add_subclass_info("".join(buf), obj, base) + + return proxy + + +class DebugReprGenerator: + def __init__(self) -> None: + self._stack: list[t.Any] = [] + + list_repr = _sequence_repr_maker("[", "]", list) + tuple_repr = _sequence_repr_maker("(", ")", tuple) + set_repr = _sequence_repr_maker("set([", "])", set) + frozenset_repr = _sequence_repr_maker("frozenset([", "])", frozenset) + deque_repr = _sequence_repr_maker( + 'collections.deque([', "])", deque + ) + + def regex_repr(self, obj: t.Pattern[t.AnyStr]) -> str: + pattern = repr(obj.pattern) + pattern = codecs.decode(pattern, "unicode-escape", "ignore") + pattern = f"r{pattern}" + return f're.compile({pattern})' + + def string_repr(self, obj: str | bytes, limit: int = 70) -> str: + buf = [''] + r = repr(obj) + + # shorten the repr when the hidden part would be at least 3 chars + if len(r) - limit > 2: + buf.extend( + ( + escape(r[:limit]), + '', + escape(r[limit:]), + "", + ) + ) + else: + buf.append(escape(r)) + + buf.append("") + out = "".join(buf) + + # if the repr looks like a standard string, add subclass info if needed + if r[0] in "'\"" or (r[0] == "b" and r[1] in "'\""): + return _add_subclass_info(out, obj, (bytes, str)) + + # otherwise, assume the repr distinguishes the subclass already + return out + + def dict_repr( + self, + d: dict[int, None] | dict[str, int] | dict[str | int, int], + recursive: bool, + limit: int = 5, + ) -> str: + if recursive: + return _add_subclass_info("{...}", d, dict) + buf = ["{"] + have_extended_section = False + for idx, (key, value) in enumerate(d.items()): + if idx: + buf.append(", ") + if idx == limit - 1: + buf.append('') + have_extended_section = True + buf.append( + f'{self.repr(key)}:' + f' {self.repr(value)}' + ) + if have_extended_section: + buf.append("") + buf.append("}") + return _add_subclass_info("".join(buf), d, dict) + + def object_repr(self, obj: t.Any) -> str: + r = repr(obj) + return f'{escape(r)}' + + def dispatch_repr(self, obj: t.Any, recursive: bool) -> str: + if obj is helper: + return f'{helper!r}' + if isinstance(obj, (int, float, complex)): + return f'{obj!r}' + if isinstance(obj, str) or isinstance(obj, bytes): + return self.string_repr(obj) + if isinstance(obj, RegexType): + return self.regex_repr(obj) + if isinstance(obj, list): + return self.list_repr(obj, recursive) + if isinstance(obj, tuple): + return self.tuple_repr(obj, recursive) + if isinstance(obj, set): + return self.set_repr(obj, recursive) + if isinstance(obj, frozenset): + return self.frozenset_repr(obj, recursive) + if isinstance(obj, dict): + return self.dict_repr(obj, recursive) + if isinstance(obj, deque): + return self.deque_repr(obj, recursive) + return self.object_repr(obj) + + def fallback_repr(self) -> str: + try: + info = "".join(format_exception_only(*sys.exc_info()[:2])) + except Exception: + info = "?" + return ( + '' + f"<broken repr ({escape(info.strip())})>" + ) + + def repr(self, obj: object) -> str: + recursive = False + for item in self._stack: + if item is obj: + recursive = True + break + self._stack.append(obj) + try: + try: + return self.dispatch_repr(obj, recursive) + except Exception: + return self.fallback_repr() + finally: + self._stack.pop() + + def dump_object(self, obj: object) -> str: + repr = None + items: list[tuple[str, str]] | None = None + + if isinstance(obj, dict): + title = "Contents of" + items = [] + for key, value in obj.items(): + if not isinstance(key, str): + items = None + break + items.append((key, self.repr(value))) + if items is None: + items = [] + repr = self.repr(obj) + for key in dir(obj): + try: + items.append((key, self.repr(getattr(obj, key)))) + except Exception: + pass + title = "Details for" + title += f" {object.__repr__(obj)[1:-1]}" + return self.render_object_dump(items, title, repr) + + def dump_locals(self, d: dict[str, t.Any]) -> str: + items = [(key, self.repr(value)) for key, value in d.items()] + return self.render_object_dump(items, "Local variables in frame") + + def render_object_dump( + self, items: list[tuple[str, str]], title: str, repr: str | None = None + ) -> str: + html_items = [] + for key, value in items: + html_items.append(f"{escape(key)}
{value}
") + if not html_items: + html_items.append("Nothing") + return OBJECT_DUMP_HTML % { + "title": escape(title), + "repr": f"
{repr if repr else ''}
", + "items": "\n".join(html_items), + } diff --git a/venv/Lib/site-packages/werkzeug/debug/shared/ICON_LICENSE.md b/venv/Lib/site-packages/werkzeug/debug/shared/ICON_LICENSE.md new file mode 100644 index 00000000..3bdbfc73 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/debug/shared/ICON_LICENSE.md @@ -0,0 +1,6 @@ +Silk icon set 1.3 by Mark James + +http://www.famfamfam.com/lab/icons/silk/ + +License: [CC-BY-2.5](https://creativecommons.org/licenses/by/2.5/) +or [CC-BY-3.0](https://creativecommons.org/licenses/by/3.0/) diff --git a/venv/Lib/site-packages/werkzeug/debug/shared/console.png b/venv/Lib/site-packages/werkzeug/debug/shared/console.png new file mode 100644 index 0000000000000000000000000000000000000000..c28dd63812d80e416682f835652f8e5824bdccb2 GIT binary patch literal 507 zcmVM#v1#3=Jvuyn6YS$$CoGDq?9U@APAh~ZOF>jp#TVKsZ^rDVDQJO z=z(unDix`Pp@6(DbUGbswOaq~fE^0}P{J^D(r7fOTCGyOUUzmBMUy*UwAN0eHX3ch z0VGKh>h*fm?RK3*p0=MB(nc}0O&K6sv)ObGuro)jd=1C!>krB`s44?r3Yg)uiG7!8OBL$a-Fo@&0(0LjNH2#KGJZi@q2YVKu xMc5!EfO4vTMwnf`nA&W_@!!aPwbiWO`5Vn?>V~$MffN7$002ovPDHLkV1l0g-PQmA literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/debug/shared/debugger.js b/venv/Lib/site-packages/werkzeug/debug/shared/debugger.js new file mode 100644 index 00000000..18c65834 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/debug/shared/debugger.js @@ -0,0 +1,360 @@ +docReady(() => { + if (!EVALEX_TRUSTED) { + initPinBox(); + } + // if we are in console mode, show the console. + if (CONSOLE_MODE && EVALEX) { + createInteractiveConsole(); + } + + const frames = document.querySelectorAll("div.traceback div.frame"); + if (EVALEX) { + addConsoleIconToFrames(frames); + } + addEventListenersToElements(document.querySelectorAll("div.detail"), "click", () => + document.querySelector("div.traceback").scrollIntoView(false) + ); + addToggleFrameTraceback(frames); + addToggleTraceTypesOnClick(document.querySelectorAll("h2.traceback")); + addInfoPrompt(document.querySelectorAll("span.nojavascript")); + wrapPlainTraceback(); +}); + +function addToggleFrameTraceback(frames) { + frames.forEach((frame) => { + frame.addEventListener("click", () => { + frame.getElementsByTagName("pre")[0].parentElement.classList.toggle("expanded"); + }); + }) +} + + +function wrapPlainTraceback() { + const plainTraceback = document.querySelector("div.plain textarea"); + const wrapper = document.createElement("pre"); + const textNode = document.createTextNode(plainTraceback.textContent); + wrapper.appendChild(textNode); + plainTraceback.replaceWith(wrapper); +} + +function initPinBox() { + document.querySelector(".pin-prompt form").addEventListener( + "submit", + function (event) { + event.preventDefault(); + const pin = encodeURIComponent(this.pin.value); + const encodedSecret = encodeURIComponent(SECRET); + const btn = this.btn; + btn.disabled = true; + + fetch( + `${document.location}?__debugger__=yes&cmd=pinauth&pin=${pin}&s=${encodedSecret}` + ) + .then((res) => res.json()) + .then(({auth, exhausted}) => { + if (auth) { + EVALEX_TRUSTED = true; + fadeOut(document.getElementsByClassName("pin-prompt")[0]); + } else { + alert( + `Error: ${ + exhausted + ? "too many attempts. Restart server to retry." + : "incorrect pin" + }` + ); + } + }) + .catch((err) => { + alert("Error: Could not verify PIN. Network error?"); + console.error(err); + }) + .finally(() => (btn.disabled = false)); + }, + false + ); +} + +function promptForPin() { + if (!EVALEX_TRUSTED) { + const encodedSecret = encodeURIComponent(SECRET); + fetch( + `${document.location}?__debugger__=yes&cmd=printpin&s=${encodedSecret}` + ); + const pinPrompt = document.getElementsByClassName("pin-prompt")[0]; + fadeIn(pinPrompt); + document.querySelector('.pin-prompt input[name="pin"]').focus(); + } +} + +/** + * Helper function for shell initialization + */ +function openShell(consoleNode, target, frameID) { + promptForPin(); + if (consoleNode) { + slideToggle(consoleNode); + return consoleNode; + } + let historyPos = 0; + const history = [""]; + const consoleElement = createConsole(); + const output = createConsoleOutput(); + const form = createConsoleInputForm(); + const command = createConsoleInput(); + + target.parentNode.appendChild(consoleElement); + consoleElement.append(output); + consoleElement.append(form); + form.append(command); + command.focus(); + slideToggle(consoleElement); + + form.addEventListener("submit", (e) => { + handleConsoleSubmit(e, command, frameID).then((consoleOutput) => { + output.append(consoleOutput); + command.focus(); + consoleElement.scrollTo(0, consoleElement.scrollHeight); + const old = history.pop(); + history.push(command.value); + if (typeof old !== "undefined") { + history.push(old); + } + historyPos = history.length - 1; + command.value = ""; + }); + }); + + command.addEventListener("keydown", (e) => { + if (e.key === "l" && e.ctrlKey) { + output.innerText = "--- screen cleared ---"; + } else if (e.key === "ArrowUp" || e.key === "ArrowDown") { + // Handle up arrow and down arrow. + if (e.key === "ArrowUp" && historyPos > 0) { + e.preventDefault(); + historyPos--; + } else if (e.key === "ArrowDown" && historyPos < history.length - 1) { + historyPos++; + } + command.value = history[historyPos]; + } + return false; + }); + + return consoleElement; +} + +function addEventListenersToElements(elements, event, listener) { + elements.forEach((el) => el.addEventListener(event, listener)); +} + +/** + * Add extra info + */ +function addInfoPrompt(elements) { + for (let i = 0; i < elements.length; i++) { + elements[i].innerHTML = + "

To switch between the interactive traceback and the plaintext " + + 'one, you can click on the "Traceback" headline. From the text ' + + "traceback you can also create a paste of it. " + + (!EVALEX + ? "" + : "For code execution mouse-over the frame you want to debug and " + + "click on the console icon on the right side." + + "

You can execute arbitrary Python code in the stack frames and " + + "there are some extra helpers available for introspection:" + + "

  • dump() shows all variables in the frame" + + "
  • dump(obj) dumps all that's known about the object
"); + elements[i].classList.remove("nojavascript"); + } +} + +function addConsoleIconToFrames(frames) { + for (let i = 0; i < frames.length; i++) { + let consoleNode = null; + const target = frames[i]; + const frameID = frames[i].id.substring(6); + + for (let j = 0; j < target.getElementsByTagName("pre").length; j++) { + const img = createIconForConsole(); + img.addEventListener("click", (e) => { + e.stopPropagation(); + consoleNode = openShell(consoleNode, target, frameID); + return false; + }); + target.getElementsByTagName("pre")[j].append(img); + } + } +} + +function slideToggle(target) { + target.classList.toggle("active"); +} + +/** + * toggle traceback types on click. + */ +function addToggleTraceTypesOnClick(elements) { + for (let i = 0; i < elements.length; i++) { + elements[i].addEventListener("click", () => { + document.querySelector("div.traceback").classList.toggle("hidden"); + document.querySelector("div.plain").classList.toggle("hidden"); + }); + elements[i].style.cursor = "pointer"; + document.querySelector("div.plain").classList.toggle("hidden"); + } +} + +function createConsole() { + const consoleNode = document.createElement("pre"); + consoleNode.classList.add("console"); + consoleNode.classList.add("active"); + return consoleNode; +} + +function createConsoleOutput() { + const output = document.createElement("div"); + output.classList.add("output"); + output.innerHTML = "[console ready]"; + return output; +} + +function createConsoleInputForm() { + const form = document.createElement("form"); + form.innerHTML = ">>> "; + return form; +} + +function createConsoleInput() { + const command = document.createElement("input"); + command.type = "text"; + command.setAttribute("autocomplete", "off"); + command.setAttribute("spellcheck", false); + command.setAttribute("autocapitalize", "off"); + command.setAttribute("autocorrect", "off"); + return command; +} + +function createIconForConsole() { + const img = document.createElement("img"); + img.setAttribute("src", "?__debugger__=yes&cmd=resource&f=console.png"); + img.setAttribute("title", "Open an interactive python shell in this frame"); + return img; +} + +function createExpansionButtonForConsole() { + const expansionButton = document.createElement("a"); + expansionButton.setAttribute("href", "#"); + expansionButton.setAttribute("class", "toggle"); + expansionButton.innerHTML = "  "; + return expansionButton; +} + +function createInteractiveConsole() { + const target = document.querySelector("div.console div.inner"); + while (target.firstChild) { + target.removeChild(target.firstChild); + } + openShell(null, target, 0); +} + +function handleConsoleSubmit(e, command, frameID) { + // Prevent page from refreshing. + e.preventDefault(); + + return new Promise((resolve) => { + // Get input command. + const cmd = command.value; + + // Setup GET request. + const urlPath = ""; + const params = { + __debugger__: "yes", + cmd: cmd, + frm: frameID, + s: SECRET, + }; + const paramString = Object.keys(params) + .map((key) => { + return "&" + encodeURIComponent(key) + "=" + encodeURIComponent(params[key]); + }) + .join(""); + + fetch(urlPath + "?" + paramString) + .then((res) => { + return res.text(); + }) + .then((data) => { + const tmp = document.createElement("div"); + tmp.innerHTML = data; + resolve(tmp); + + // Handle expandable span for long list outputs. + // Example to test: list(range(13)) + let wrapperAdded = false; + const wrapperSpan = document.createElement("span"); + const expansionButton = createExpansionButtonForConsole(); + + tmp.querySelectorAll("span.extended").forEach((spanToWrap) => { + const parentDiv = spanToWrap.parentNode; + if (!wrapperAdded) { + parentDiv.insertBefore(wrapperSpan, spanToWrap); + wrapperAdded = true; + } + parentDiv.removeChild(spanToWrap); + wrapperSpan.append(spanToWrap); + spanToWrap.hidden = true; + + expansionButton.addEventListener("click", (event) => { + event.preventDefault(); + spanToWrap.hidden = !spanToWrap.hidden; + expansionButton.classList.toggle("open"); + return false; + }); + }); + + // Add expansion button at end of wrapper. + if (wrapperAdded) { + wrapperSpan.append(expansionButton); + } + }) + .catch((err) => { + console.error(err); + }); + return false; + }); +} + +function fadeOut(element) { + element.style.opacity = 1; + + (function fade() { + element.style.opacity -= 0.1; + if (element.style.opacity < 0) { + element.style.display = "none"; + } else { + requestAnimationFrame(fade); + } + })(); +} + +function fadeIn(element, display) { + element.style.opacity = 0; + element.style.display = display || "block"; + + (function fade() { + let val = parseFloat(element.style.opacity) + 0.1; + if (val <= 1) { + element.style.opacity = val; + requestAnimationFrame(fade); + } + })(); +} + +function docReady(fn) { + if (document.readyState === "complete" || document.readyState === "interactive") { + setTimeout(fn, 1); + } else { + document.addEventListener("DOMContentLoaded", fn); + } +} diff --git a/venv/Lib/site-packages/werkzeug/debug/shared/less.png b/venv/Lib/site-packages/werkzeug/debug/shared/less.png new file mode 100644 index 0000000000000000000000000000000000000000..5efefd62b43e4f11dd300be4355a4b413c7a70d2 GIT binary patch literal 191 zcmeAS@N?(olHy`uVBq!ia0vp^+#t-s1|(OmDOUqhjKx9jP7LeL$-D$|*pj^6T^Kq* zuw()wNEK&+M`STji!cZ?GR&GI0Tg5}@$_|NzriFWuEAp!-_8pZviEdx43W5;{N}}r zALkuZ5)u;<5;XMn)mLoTym@kCXCsS)^--M@GHz~eJgoo!|3AJ?BC=^;p4#2!xpU{b bXtFW%8S(l$O4%<58pYu0>gTe~DWM4fkj^!0 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/debug/shared/more.png b/venv/Lib/site-packages/werkzeug/debug/shared/more.png new file mode 100644 index 0000000000000000000000000000000000000000..804fa226fe3ed9e6cc2bd044a848f33a2d7b4e4f GIT binary patch literal 200 zcmeAS@N?(olHy`uVBq!ia0vp^+#t-s1|(OmDOUqhjKx9jP7LeL$-D$|*pj^6T^Rm@ z;DWu&Cj&(|3p^r=fm(z?n2}-D90{Nxdx@v7EBg&5DRB)3hmHb?jr_0{K>_1cC{J-%1r lr(<|}#G9!1a#KtW>0AF44oJ8ZkqR`E!PC{xWt~$(698mrJ|X}B literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/debug/shared/style.css b/venv/Lib/site-packages/werkzeug/debug/shared/style.css new file mode 100644 index 00000000..e9397ca0 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/debug/shared/style.css @@ -0,0 +1,150 @@ +body, input { font-family: sans-serif; color: #000; text-align: center; + margin: 1em; padding: 0; font-size: 15px; } +h1, h2, h3 { font-weight: normal; } + +input { background-color: #fff; margin: 0; text-align: left; + outline: none !important; } +input[type="submit"] { padding: 3px 6px; } +a { color: #11557C; } +a:hover { color: #177199; } +pre, code, +textarea { font-family: monospace; font-size: 14px; } + +div.debugger { text-align: left; padding: 12px; margin: auto; + background-color: white; } +h1 { font-size: 36px; margin: 0 0 0.3em 0; } +div.detail { cursor: pointer; } +div.detail p { margin: 0 0 8px 13px; font-size: 14px; white-space: pre-wrap; + font-family: monospace; } +div.explanation { margin: 20px 13px; font-size: 15px; color: #555; } +div.footer { font-size: 13px; text-align: right; margin: 30px 0; + color: #86989B; } + +h2 { font-size: 16px; margin: 1.3em 0 0.0 0; padding: 9px; + background-color: #11557C; color: white; } +h2 em, h3 em { font-style: normal; color: #A5D6D9; font-weight: normal; } + +div.traceback, div.plain { border: 1px solid #ddd; margin: 0 0 1em 0; padding: 10px; } +div.plain p { margin: 0; } +div.plain textarea, +div.plain pre { margin: 10px 0 0 0; padding: 4px; + background-color: #E8EFF0; border: 1px solid #D3E7E9; } +div.plain textarea { width: 99%; height: 300px; } +div.traceback h3 { font-size: 1em; margin: 0 0 0.8em 0; } +div.traceback ul { list-style: none; margin: 0; padding: 0 0 0 1em; } +div.traceback h4 { font-size: 13px; font-weight: normal; margin: 0.7em 0 0.1em 0; } +div.traceback pre { margin: 0; padding: 5px 0 3px 15px; + background-color: #E8EFF0; border: 1px solid #D3E7E9; } +div.traceback .library .current { background: white; color: #555; } +div.traceback .expanded .current { background: #E8EFF0; color: black; } +div.traceback pre:hover { background-color: #DDECEE; color: black; cursor: pointer; } +div.traceback div.source.expanded pre + pre { border-top: none; } + +div.traceback span.ws { display: none; } +div.traceback pre.before, div.traceback pre.after { display: none; background: white; } +div.traceback div.source.expanded pre.before, +div.traceback div.source.expanded pre.after { + display: block; +} + +div.traceback div.source.expanded span.ws { + display: inline; +} + +div.traceback blockquote { margin: 1em 0 0 0; padding: 0; white-space: pre-line; } +div.traceback img { float: right; padding: 2px; margin: -3px 2px 0 0; display: none; } +div.traceback img:hover { background-color: #ddd; cursor: pointer; + border-color: #BFDDE0; } +div.traceback pre:hover img { display: block; } +div.traceback cite.filename { font-style: normal; color: #3B666B; } + +pre.console { border: 1px solid #ccc; background: white!important; + color: black; padding: 5px!important; + margin: 3px 0 0 0!important; cursor: default!important; + max-height: 400px; overflow: auto; } +pre.console form { color: #555; } +pre.console input { background-color: transparent; color: #555; + width: 90%; font-family: monospace; font-size: 14px; + border: none!important; } + +span.string { color: #30799B; } +span.number { color: #9C1A1C; } +span.help { color: #3A7734; } +span.object { color: #485F6E; } +span.extended { opacity: 0.5; } +span.extended:hover { opacity: 1; } +a.toggle { text-decoration: none; background-repeat: no-repeat; + background-position: center center; + background-image: url(?__debugger__=yes&cmd=resource&f=more.png); } +a.toggle:hover { background-color: #444; } +a.open { background-image: url(?__debugger__=yes&cmd=resource&f=less.png); } + +pre.console div.traceback, +pre.console div.box { margin: 5px 10px; white-space: normal; + border: 1px solid #11557C; padding: 10px; + font-family: sans-serif; } +pre.console div.box h3, +pre.console div.traceback h3 { margin: -10px -10px 10px -10px; padding: 5px; + background: #11557C; color: white; } + +pre.console div.traceback pre:hover { cursor: default; background: #E8EFF0; } +pre.console div.traceback pre.syntaxerror { background: inherit; border: none; + margin: 20px -10px -10px -10px; + padding: 10px; border-top: 1px solid #BFDDE0; + background: #E8EFF0; } +pre.console div.noframe-traceback pre.syntaxerror { margin-top: -10px; border: none; } + +pre.console div.box pre.repr { padding: 0; margin: 0; background-color: white; border: none; } +pre.console div.box table { margin-top: 6px; } +pre.console div.box pre { border: none; } +pre.console div.box pre.help { background-color: white; } +pre.console div.box pre.help:hover { cursor: default; } +pre.console table tr { vertical-align: top; } +div.console { border: 1px solid #ccc; padding: 4px; background-color: #fafafa; } + +div.traceback pre, div.console pre { + white-space: pre-wrap; /* css-3 should we be so lucky... */ + white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ + white-space: -pre-wrap; /* Opera 4-6 ?? */ + white-space: -o-pre-wrap; /* Opera 7 ?? */ + word-wrap: break-word; /* Internet Explorer 5.5+ */ + _white-space: pre; /* IE only hack to re-specify in + addition to word-wrap */ +} + + +div.pin-prompt { + position: absolute; + display: none; + top: 0; + bottom: 0; + left: 0; + right: 0; + background: rgba(255, 255, 255, 0.8); +} + +div.pin-prompt .inner { + background: #eee; + padding: 10px 50px; + width: 350px; + margin: 10% auto 0 auto; + border: 1px solid #ccc; + border-radius: 2px; +} + +div.exc-divider { + margin: 0.7em 0 0 -1em; + padding: 0.5em; + background: #11557C; + color: #ddd; + border: 1px solid #ddd; +} + +.console.active { + max-height: 0!important; + display: none; +} + +.hidden { + display: none; +} diff --git a/venv/Lib/site-packages/werkzeug/debug/tbtools.py b/venv/Lib/site-packages/werkzeug/debug/tbtools.py new file mode 100644 index 00000000..0574c966 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/debug/tbtools.py @@ -0,0 +1,439 @@ +from __future__ import annotations + +import itertools +import linecache +import os +import re +import sys +import sysconfig +import traceback +import typing as t + +from markupsafe import escape + +from ..utils import cached_property +from .console import Console + +HEADER = """\ + + + + %(title)s // Werkzeug Debugger + + + + + + +
+""" + +FOOTER = """\ + +
+ +
+
+

Console Locked

+

+ The console is locked and needs to be unlocked by entering the PIN. + You can find the PIN printed out on the standard output of your + shell that runs the server. +

+

PIN: + + +

+
+
+ + +""" + +PAGE_HTML = ( + HEADER + + """\ +

%(exception_type)s

+
+

%(exception)s

+
+

Traceback (most recent call last)

+%(summary)s +
+

+ This is the Copy/Paste friendly version of the traceback. +

+ +
+
+ The debugger caught an exception in your WSGI application. You can now + look at the traceback which led to the error. + If you enable JavaScript you can also use additional features such as code + execution (if the evalex feature is enabled), automatic pasting of the + exceptions and much more. +
+""" + + FOOTER + + """ + +""" +) + +CONSOLE_HTML = ( + HEADER + + """\ +

Interactive Console

+
+In this console you can execute Python expressions in the context of the +application. The initial namespace was created by the debugger automatically. +
+
The Console requires JavaScript.
+""" + + FOOTER +) + +SUMMARY_HTML = """\ +
+ %(title)s +
    %(frames)s
+ %(description)s +
+""" + +FRAME_HTML = """\ +
+

File "%(filename)s", + line %(lineno)s, + in %(function_name)s

+
%(lines)s
+
+""" + + +def _process_traceback( + exc: BaseException, + te: traceback.TracebackException | None = None, + *, + skip: int = 0, + hide: bool = True, +) -> traceback.TracebackException: + if te is None: + te = traceback.TracebackException.from_exception(exc, lookup_lines=False) + + # Get the frames the same way StackSummary.extract did, in order + # to match each frame with the FrameSummary to augment. + frame_gen = traceback.walk_tb(exc.__traceback__) + limit = getattr(sys, "tracebacklimit", None) + + if limit is not None: + if limit < 0: + limit = 0 + + frame_gen = itertools.islice(frame_gen, limit) + + if skip: + frame_gen = itertools.islice(frame_gen, skip, None) + del te.stack[:skip] + + new_stack: list[DebugFrameSummary] = [] + hidden = False + + # Match each frame with the FrameSummary that was generated. + # Hide frames using Paste's __traceback_hide__ rules. Replace + # all visible FrameSummary with DebugFrameSummary. + for (f, _), fs in zip(frame_gen, te.stack): + if hide: + hide_value = f.f_locals.get("__traceback_hide__", False) + + if hide_value in {"before", "before_and_this"}: + new_stack = [] + hidden = False + + if hide_value == "before_and_this": + continue + elif hide_value in {"reset", "reset_and_this"}: + hidden = False + + if hide_value == "reset_and_this": + continue + elif hide_value in {"after", "after_and_this"}: + hidden = True + + if hide_value == "after_and_this": + continue + elif hide_value or hidden: + continue + + frame_args: dict[str, t.Any] = { + "filename": fs.filename, + "lineno": fs.lineno, + "name": fs.name, + "locals": f.f_locals, + "globals": f.f_globals, + } + + if hasattr(fs, "colno"): + frame_args["colno"] = fs.colno + frame_args["end_colno"] = fs.end_colno + + new_stack.append(DebugFrameSummary(**frame_args)) + + # The codeop module is used to compile code from the interactive + # debugger. Hide any codeop frames from the bottom of the traceback. + while new_stack: + module = new_stack[0].global_ns.get("__name__") + + if module is None: + module = new_stack[0].local_ns.get("__name__") + + if module == "codeop": + del new_stack[0] + else: + break + + te.stack[:] = new_stack + + if te.__context__: + context_exc = t.cast(BaseException, exc.__context__) + te.__context__ = _process_traceback(context_exc, te.__context__, hide=hide) + + if te.__cause__: + cause_exc = t.cast(BaseException, exc.__cause__) + te.__cause__ = _process_traceback(cause_exc, te.__cause__, hide=hide) + + return te + + +class DebugTraceback: + __slots__ = ("_te", "_cache_all_tracebacks", "_cache_all_frames") + + def __init__( + self, + exc: BaseException, + te: traceback.TracebackException | None = None, + *, + skip: int = 0, + hide: bool = True, + ) -> None: + self._te = _process_traceback(exc, te, skip=skip, hide=hide) + + def __str__(self) -> str: + return f"<{type(self).__name__} {self._te}>" + + @cached_property + def all_tracebacks( + self, + ) -> list[tuple[str | None, traceback.TracebackException]]: + out = [] + current = self._te + + while current is not None: + if current.__cause__ is not None: + chained_msg = ( + "The above exception was the direct cause of the" + " following exception" + ) + chained_exc = current.__cause__ + elif current.__context__ is not None and not current.__suppress_context__: + chained_msg = ( + "During handling of the above exception, another" + " exception occurred" + ) + chained_exc = current.__context__ + else: + chained_msg = None + chained_exc = None + + out.append((chained_msg, current)) + current = chained_exc + + return out + + @cached_property + def all_frames(self) -> list[DebugFrameSummary]: + return [ + f # type: ignore[misc] + for _, te in self.all_tracebacks + for f in te.stack + ] + + def render_traceback_text(self) -> str: + return "".join(self._te.format()) + + def render_traceback_html(self, include_title: bool = True) -> str: + library_frames = [f.is_library for f in self.all_frames] + mark_library = 0 < sum(library_frames) < len(library_frames) + rows = [] + + if not library_frames: + classes = "traceback noframe-traceback" + else: + classes = "traceback" + + for msg, current in reversed(self.all_tracebacks): + row_parts = [] + + if msg is not None: + row_parts.append(f'
  • {msg}:
    ') + + for frame in current.stack: + frame = t.cast(DebugFrameSummary, frame) + info = f' title="{escape(frame.info)}"' if frame.info else "" + row_parts.append(f"{frame.render_html(mark_library)}") + + rows.append("\n".join(row_parts)) + + is_syntax_error = issubclass(self._te.exc_type, SyntaxError) + + if include_title: + if is_syntax_error: + title = "Syntax Error" + else: + title = "Traceback (most recent call last):" + else: + title = "" + + exc_full = escape("".join(self._te.format_exception_only())) + + if is_syntax_error: + description = f"
    {exc_full}
    " + else: + description = f"
    {exc_full}
    " + + return SUMMARY_HTML % { + "classes": classes, + "title": f"

    {title}

    ", + "frames": "\n".join(rows), + "description": description, + } + + def render_debugger_html( + self, evalex: bool, secret: str, evalex_trusted: bool + ) -> str: + exc_lines = list(self._te.format_exception_only()) + plaintext = "".join(self._te.format()) + return PAGE_HTML % { + "evalex": "true" if evalex else "false", + "evalex_trusted": "true" if evalex_trusted else "false", + "console": "false", + "title": escape(exc_lines[0]), + "exception": escape("".join(exc_lines)), + "exception_type": escape(self._te.exc_type.__name__), + "summary": self.render_traceback_html(include_title=False), + "plaintext": escape(plaintext), + "plaintext_cs": re.sub("-{2,}", "-", plaintext), + "secret": secret, + } + + +class DebugFrameSummary(traceback.FrameSummary): + """A :class:`traceback.FrameSummary` that can evaluate code in the + frame's namespace. + """ + + __slots__ = ( + "local_ns", + "global_ns", + "_cache_info", + "_cache_is_library", + "_cache_console", + ) + + def __init__( + self, + *, + locals: dict[str, t.Any], + globals: dict[str, t.Any], + **kwargs: t.Any, + ) -> None: + super().__init__(locals=None, **kwargs) + self.local_ns = locals + self.global_ns = globals + + @cached_property + def info(self) -> str | None: + return self.local_ns.get("__traceback_info__") + + @cached_property + def is_library(self) -> bool: + return any( + self.filename.startswith((path, os.path.realpath(path))) + for path in sysconfig.get_paths().values() + ) + + @cached_property + def console(self) -> Console: + return Console(self.global_ns, self.local_ns) + + def eval(self, code: str) -> t.Any: + return self.console.eval(code) + + def render_html(self, mark_library: bool) -> str: + context = 5 + lines = linecache.getlines(self.filename) + line_idx = self.lineno - 1 # type: ignore[operator] + start_idx = max(0, line_idx - context) + stop_idx = min(len(lines), line_idx + context + 1) + rendered_lines = [] + + def render_line(line: str, cls: str) -> None: + line = line.expandtabs().rstrip() + stripped_line = line.strip() + prefix = len(line) - len(stripped_line) + colno = getattr(self, "colno", 0) + end_colno = getattr(self, "end_colno", 0) + + if cls == "current" and colno and end_colno: + arrow = ( + f'\n{" " * prefix}' + f'{" " * (colno - prefix)}{"^" * (end_colno - colno)}' + ) + else: + arrow = "" + + rendered_lines.append( + f'
    {" " * prefix}'
    +                f"{escape(stripped_line) if stripped_line else ' '}"
    +                f"{arrow if arrow else ''}
    " + ) + + if lines: + for line in lines[start_idx:line_idx]: + render_line(line, "before") + + render_line(lines[line_idx], "current") + + for line in lines[line_idx + 1 : stop_idx]: + render_line(line, "after") + + return FRAME_HTML % { + "id": id(self), + "filename": escape(self.filename), + "lineno": self.lineno, + "function_name": escape(self.name), + "lines": "\n".join(rendered_lines), + "library": "library" if mark_library and self.is_library else "", + } + + +def render_console_html(secret: str, evalex_trusted: bool) -> str: + return CONSOLE_HTML % { + "evalex": "true", + "evalex_trusted": "true" if evalex_trusted else "false", + "console": "true", + "title": "Console", + "secret": secret, + } diff --git a/venv/Lib/site-packages/werkzeug/exceptions.py b/venv/Lib/site-packages/werkzeug/exceptions.py new file mode 100644 index 00000000..6ce7ef95 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/exceptions.py @@ -0,0 +1,881 @@ +"""Implements a number of Python exceptions which can be raised from within +a view to trigger a standard HTTP non-200 response. + +Usage Example +------------- + +.. code-block:: python + + from werkzeug.wrappers.request import Request + from werkzeug.exceptions import HTTPException, NotFound + + def view(request): + raise NotFound() + + @Request.application + def application(request): + try: + return view(request) + except HTTPException as e: + return e + +As you can see from this example those exceptions are callable WSGI +applications. However, they are not Werkzeug response objects. You +can get a response object by calling ``get_response()`` on a HTTP +exception. + +Keep in mind that you may have to pass an environ (WSGI) or scope +(ASGI) to ``get_response()`` because some errors fetch additional +information relating to the request. + +If you want to hook in a different exception page to say, a 404 status +code, you can add a second except for a specific subclass of an error: + +.. code-block:: python + + @Request.application + def application(request): + try: + return view(request) + except NotFound as e: + return not_found(request) + except HTTPException as e: + return e + +""" + +from __future__ import annotations + +import typing as t +from datetime import datetime + +from markupsafe import escape +from markupsafe import Markup + +from ._internal import _get_environ + +if t.TYPE_CHECKING: + from _typeshed.wsgi import StartResponse + from _typeshed.wsgi import WSGIEnvironment + + from .datastructures import WWWAuthenticate + from .sansio.response import Response + from .wrappers.request import Request as WSGIRequest + from .wrappers.response import Response as WSGIResponse + + +class HTTPException(Exception): + """The base class for all HTTP exceptions. This exception can be called as a WSGI + application to render a default error page or you can catch the subclasses + of it independently and render nicer error messages. + + .. versionchanged:: 2.1 + Removed the ``wrap`` class method. + """ + + code: int | None = None + description: str | None = None + + def __init__( + self, + description: str | None = None, + response: Response | None = None, + ) -> None: + super().__init__() + if description is not None: + self.description = description + self.response = response + + @property + def name(self) -> str: + """The status name.""" + from .http import HTTP_STATUS_CODES + + return HTTP_STATUS_CODES.get(self.code, "Unknown Error") # type: ignore + + def get_description( + self, + environ: WSGIEnvironment | None = None, + scope: dict[str, t.Any] | None = None, + ) -> str: + """Get the description.""" + if self.description is None: + description = "" + else: + description = self.description + + description = escape(description).replace("\n", Markup("
    ")) + return f"

    {description}

    " + + def get_body( + self, + environ: WSGIEnvironment | None = None, + scope: dict[str, t.Any] | None = None, + ) -> str: + """Get the HTML body.""" + return ( + "\n" + "\n" + f"{self.code} {escape(self.name)}\n" + f"

    {escape(self.name)}

    \n" + f"{self.get_description(environ)}\n" + ) + + def get_headers( + self, + environ: WSGIEnvironment | None = None, + scope: dict[str, t.Any] | None = None, + ) -> list[tuple[str, str]]: + """Get a list of headers.""" + return [("Content-Type", "text/html; charset=utf-8")] + + def get_response( + self, + environ: WSGIEnvironment | WSGIRequest | None = None, + scope: dict[str, t.Any] | None = None, + ) -> Response: + """Get a response object. If one was passed to the exception + it's returned directly. + + :param environ: the optional environ for the request. This + can be used to modify the response depending + on how the request looked like. + :return: a :class:`Response` object or a subclass thereof. + """ + from .wrappers.response import Response as WSGIResponse # noqa: F811 + + if self.response is not None: + return self.response + if environ is not None: + environ = _get_environ(environ) + headers = self.get_headers(environ, scope) + return WSGIResponse(self.get_body(environ, scope), self.code, headers) + + def __call__( + self, environ: WSGIEnvironment, start_response: StartResponse + ) -> t.Iterable[bytes]: + """Call the exception as WSGI application. + + :param environ: the WSGI environment. + :param start_response: the response callable provided by the WSGI + server. + """ + response = t.cast("WSGIResponse", self.get_response(environ)) + return response(environ, start_response) + + def __str__(self) -> str: + code = self.code if self.code is not None else "???" + return f"{code} {self.name}: {self.description}" + + def __repr__(self) -> str: + code = self.code if self.code is not None else "???" + return f"<{type(self).__name__} '{code}: {self.name}'>" + + +class BadRequest(HTTPException): + """*400* `Bad Request` + + Raise if the browser sends something to the application the application + or server cannot handle. + """ + + code = 400 + description = ( + "The browser (or proxy) sent a request that this server could " + "not understand." + ) + + +class BadRequestKeyError(BadRequest, KeyError): + """An exception that is used to signal both a :exc:`KeyError` and a + :exc:`BadRequest`. Used by many of the datastructures. + """ + + _description = BadRequest.description + #: Show the KeyError along with the HTTP error message in the + #: response. This should be disabled in production, but can be + #: useful in a debug mode. + show_exception = False + + def __init__(self, arg: str | None = None, *args: t.Any, **kwargs: t.Any): + super().__init__(*args, **kwargs) + + if arg is None: + KeyError.__init__(self) + else: + KeyError.__init__(self, arg) + + @property # type: ignore + def description(self) -> str: + if self.show_exception: + return ( + f"{self._description}\n" + f"{KeyError.__name__}: {KeyError.__str__(self)}" + ) + + return self._description + + @description.setter + def description(self, value: str) -> None: + self._description = value + + +class ClientDisconnected(BadRequest): + """Internal exception that is raised if Werkzeug detects a disconnected + client. Since the client is already gone at that point attempting to + send the error message to the client might not work and might ultimately + result in another exception in the server. Mainly this is here so that + it is silenced by default as far as Werkzeug is concerned. + + Since disconnections cannot be reliably detected and are unspecified + by WSGI to a large extent this might or might not be raised if a client + is gone. + + .. versionadded:: 0.8 + """ + + +class SecurityError(BadRequest): + """Raised if something triggers a security error. This is otherwise + exactly like a bad request error. + + .. versionadded:: 0.9 + """ + + +class BadHost(BadRequest): + """Raised if the submitted host is badly formatted. + + .. versionadded:: 0.11.2 + """ + + +class Unauthorized(HTTPException): + """*401* ``Unauthorized`` + + Raise if the user is not authorized to access a resource. + + The ``www_authenticate`` argument should be used to set the + ``WWW-Authenticate`` header. This is used for HTTP basic auth and + other schemes. Use :class:`~werkzeug.datastructures.WWWAuthenticate` + to create correctly formatted values. Strictly speaking a 401 + response is invalid if it doesn't provide at least one value for + this header, although real clients typically don't care. + + :param description: Override the default message used for the body + of the response. + :param www-authenticate: A single value, or list of values, for the + WWW-Authenticate header(s). + + .. versionchanged:: 2.0 + Serialize multiple ``www_authenticate`` items into multiple + ``WWW-Authenticate`` headers, rather than joining them + into a single value, for better interoperability. + + .. versionchanged:: 0.15.3 + If the ``www_authenticate`` argument is not set, the + ``WWW-Authenticate`` header is not set. + + .. versionchanged:: 0.15.3 + The ``response`` argument was restored. + + .. versionchanged:: 0.15.1 + ``description`` was moved back as the first argument, restoring + its previous position. + + .. versionchanged:: 0.15.0 + ``www_authenticate`` was added as the first argument, ahead of + ``description``. + """ + + code = 401 + description = ( + "The server could not verify that you are authorized to access" + " the URL requested. You either supplied the wrong credentials" + " (e.g. a bad password), or your browser doesn't understand" + " how to supply the credentials required." + ) + + def __init__( + self, + description: str | None = None, + response: Response | None = None, + www_authenticate: None | (WWWAuthenticate | t.Iterable[WWWAuthenticate]) = None, + ) -> None: + super().__init__(description, response) + + from .datastructures import WWWAuthenticate + + if isinstance(www_authenticate, WWWAuthenticate): + www_authenticate = (www_authenticate,) + + self.www_authenticate = www_authenticate + + def get_headers( + self, + environ: WSGIEnvironment | None = None, + scope: dict[str, t.Any] | None = None, + ) -> list[tuple[str, str]]: + headers = super().get_headers(environ, scope) + if self.www_authenticate: + headers.extend(("WWW-Authenticate", str(x)) for x in self.www_authenticate) + return headers + + +class Forbidden(HTTPException): + """*403* `Forbidden` + + Raise if the user doesn't have the permission for the requested resource + but was authenticated. + """ + + code = 403 + description = ( + "You don't have the permission to access the requested" + " resource. It is either read-protected or not readable by the" + " server." + ) + + +class NotFound(HTTPException): + """*404* `Not Found` + + Raise if a resource does not exist and never existed. + """ + + code = 404 + description = ( + "The requested URL was not found on the server. If you entered" + " the URL manually please check your spelling and try again." + ) + + +class MethodNotAllowed(HTTPException): + """*405* `Method Not Allowed` + + Raise if the server used a method the resource does not handle. For + example `POST` if the resource is view only. Especially useful for REST. + + The first argument for this exception should be a list of allowed methods. + Strictly speaking the response would be invalid if you don't provide valid + methods in the header which you can do with that list. + """ + + code = 405 + description = "The method is not allowed for the requested URL." + + def __init__( + self, + valid_methods: t.Iterable[str] | None = None, + description: str | None = None, + response: Response | None = None, + ) -> None: + """Takes an optional list of valid http methods + starting with werkzeug 0.3 the list will be mandatory.""" + super().__init__(description=description, response=response) + self.valid_methods = valid_methods + + def get_headers( + self, + environ: WSGIEnvironment | None = None, + scope: dict[str, t.Any] | None = None, + ) -> list[tuple[str, str]]: + headers = super().get_headers(environ, scope) + if self.valid_methods: + headers.append(("Allow", ", ".join(self.valid_methods))) + return headers + + +class NotAcceptable(HTTPException): + """*406* `Not Acceptable` + + Raise if the server can't return any content conforming to the + `Accept` headers of the client. + """ + + code = 406 + description = ( + "The resource identified by the request is only capable of" + " generating response entities which have content" + " characteristics not acceptable according to the accept" + " headers sent in the request." + ) + + +class RequestTimeout(HTTPException): + """*408* `Request Timeout` + + Raise to signalize a timeout. + """ + + code = 408 + description = ( + "The server closed the network connection because the browser" + " didn't finish the request within the specified time." + ) + + +class Conflict(HTTPException): + """*409* `Conflict` + + Raise to signal that a request cannot be completed because it conflicts + with the current state on the server. + + .. versionadded:: 0.7 + """ + + code = 409 + description = ( + "A conflict happened while processing the request. The" + " resource might have been modified while the request was being" + " processed." + ) + + +class Gone(HTTPException): + """*410* `Gone` + + Raise if a resource existed previously and went away without new location. + """ + + code = 410 + description = ( + "The requested URL is no longer available on this server and" + " there is no forwarding address. If you followed a link from a" + " foreign page, please contact the author of this page." + ) + + +class LengthRequired(HTTPException): + """*411* `Length Required` + + Raise if the browser submitted data but no ``Content-Length`` header which + is required for the kind of processing the server does. + """ + + code = 411 + description = ( + "A request with this method requires a valid Content-" + "Length header." + ) + + +class PreconditionFailed(HTTPException): + """*412* `Precondition Failed` + + Status code used in combination with ``If-Match``, ``If-None-Match``, or + ``If-Unmodified-Since``. + """ + + code = 412 + description = ( + "The precondition on the request for the URL failed positive evaluation." + ) + + +class RequestEntityTooLarge(HTTPException): + """*413* `Request Entity Too Large` + + The status code one should return if the data submitted exceeded a given + limit. + """ + + code = 413 + description = "The data value transmitted exceeds the capacity limit." + + +class RequestURITooLarge(HTTPException): + """*414* `Request URI Too Large` + + Like *413* but for too long URLs. + """ + + code = 414 + description = ( + "The length of the requested URL exceeds the capacity limit for" + " this server. The request cannot be processed." + ) + + +class UnsupportedMediaType(HTTPException): + """*415* `Unsupported Media Type` + + The status code returned if the server is unable to handle the media type + the client transmitted. + """ + + code = 415 + description = ( + "The server does not support the media type transmitted in the request." + ) + + +class RequestedRangeNotSatisfiable(HTTPException): + """*416* `Requested Range Not Satisfiable` + + The client asked for an invalid part of the file. + + .. versionadded:: 0.7 + """ + + code = 416 + description = "The server cannot provide the requested range." + + def __init__( + self, + length: int | None = None, + units: str = "bytes", + description: str | None = None, + response: Response | None = None, + ) -> None: + """Takes an optional `Content-Range` header value based on ``length`` + parameter. + """ + super().__init__(description=description, response=response) + self.length = length + self.units = units + + def get_headers( + self, + environ: WSGIEnvironment | None = None, + scope: dict[str, t.Any] | None = None, + ) -> list[tuple[str, str]]: + headers = super().get_headers(environ, scope) + if self.length is not None: + headers.append(("Content-Range", f"{self.units} */{self.length}")) + return headers + + +class ExpectationFailed(HTTPException): + """*417* `Expectation Failed` + + The server cannot meet the requirements of the Expect request-header. + + .. versionadded:: 0.7 + """ + + code = 417 + description = "The server could not meet the requirements of the Expect header" + + +class ImATeapot(HTTPException): + """*418* `I'm a teapot` + + The server should return this if it is a teapot and someone attempted + to brew coffee with it. + + .. versionadded:: 0.7 + """ + + code = 418 + description = "This server is a teapot, not a coffee machine" + + +class UnprocessableEntity(HTTPException): + """*422* `Unprocessable Entity` + + Used if the request is well formed, but the instructions are otherwise + incorrect. + """ + + code = 422 + description = ( + "The request was well-formed but was unable to be followed due" + " to semantic errors." + ) + + +class Locked(HTTPException): + """*423* `Locked` + + Used if the resource that is being accessed is locked. + """ + + code = 423 + description = "The resource that is being accessed is locked." + + +class FailedDependency(HTTPException): + """*424* `Failed Dependency` + + Used if the method could not be performed on the resource + because the requested action depended on another action and that action failed. + """ + + code = 424 + description = ( + "The method could not be performed on the resource because the" + " requested action depended on another action and that action" + " failed." + ) + + +class PreconditionRequired(HTTPException): + """*428* `Precondition Required` + + The server requires this request to be conditional, typically to prevent + the lost update problem, which is a race condition between two or more + clients attempting to update a resource through PUT or DELETE. By requiring + each client to include a conditional header ("If-Match" or "If-Unmodified- + Since") with the proper value retained from a recent GET request, the + server ensures that each client has at least seen the previous revision of + the resource. + """ + + code = 428 + description = ( + "This request is required to be conditional; try using" + ' "If-Match" or "If-Unmodified-Since".' + ) + + +class _RetryAfter(HTTPException): + """Adds an optional ``retry_after`` parameter which will set the + ``Retry-After`` header. May be an :class:`int` number of seconds or + a :class:`~datetime.datetime`. + """ + + def __init__( + self, + description: str | None = None, + response: Response | None = None, + retry_after: datetime | int | None = None, + ) -> None: + super().__init__(description, response) + self.retry_after = retry_after + + def get_headers( + self, + environ: WSGIEnvironment | None = None, + scope: dict[str, t.Any] | None = None, + ) -> list[tuple[str, str]]: + headers = super().get_headers(environ, scope) + + if self.retry_after: + if isinstance(self.retry_after, datetime): + from .http import http_date + + value = http_date(self.retry_after) + else: + value = str(self.retry_after) + + headers.append(("Retry-After", value)) + + return headers + + +class TooManyRequests(_RetryAfter): + """*429* `Too Many Requests` + + The server is limiting the rate at which this user receives + responses, and this request exceeds that rate. (The server may use + any convenient method to identify users and their request rates). + The server may include a "Retry-After" header to indicate how long + the user should wait before retrying. + + :param retry_after: If given, set the ``Retry-After`` header to this + value. May be an :class:`int` number of seconds or a + :class:`~datetime.datetime`. + + .. versionchanged:: 1.0 + Added ``retry_after`` parameter. + """ + + code = 429 + description = "This user has exceeded an allotted request count. Try again later." + + +class RequestHeaderFieldsTooLarge(HTTPException): + """*431* `Request Header Fields Too Large` + + The server refuses to process the request because the header fields are too + large. One or more individual fields may be too large, or the set of all + headers is too large. + """ + + code = 431 + description = "One or more header fields exceeds the maximum size." + + +class UnavailableForLegalReasons(HTTPException): + """*451* `Unavailable For Legal Reasons` + + This status code indicates that the server is denying access to the + resource as a consequence of a legal demand. + """ + + code = 451 + description = "Unavailable for legal reasons." + + +class InternalServerError(HTTPException): + """*500* `Internal Server Error` + + Raise if an internal server error occurred. This is a good fallback if an + unknown error occurred in the dispatcher. + + .. versionchanged:: 1.0.0 + Added the :attr:`original_exception` attribute. + """ + + code = 500 + description = ( + "The server encountered an internal error and was unable to" + " complete your request. Either the server is overloaded or" + " there is an error in the application." + ) + + def __init__( + self, + description: str | None = None, + response: Response | None = None, + original_exception: BaseException | None = None, + ) -> None: + #: The original exception that caused this 500 error. Can be + #: used by frameworks to provide context when handling + #: unexpected errors. + self.original_exception = original_exception + super().__init__(description=description, response=response) + + +class NotImplemented(HTTPException): + """*501* `Not Implemented` + + Raise if the application does not support the action requested by the + browser. + """ + + code = 501 + description = "The server does not support the action requested by the browser." + + +class BadGateway(HTTPException): + """*502* `Bad Gateway` + + If you do proxying in your application you should return this status code + if you received an invalid response from the upstream server it accessed + in attempting to fulfill the request. + """ + + code = 502 + description = ( + "The proxy server received an invalid response from an upstream server." + ) + + +class ServiceUnavailable(_RetryAfter): + """*503* `Service Unavailable` + + Status code you should return if a service is temporarily + unavailable. + + :param retry_after: If given, set the ``Retry-After`` header to this + value. May be an :class:`int` number of seconds or a + :class:`~datetime.datetime`. + + .. versionchanged:: 1.0 + Added ``retry_after`` parameter. + """ + + code = 503 + description = ( + "The server is temporarily unable to service your request due" + " to maintenance downtime or capacity problems. Please try" + " again later." + ) + + +class GatewayTimeout(HTTPException): + """*504* `Gateway Timeout` + + Status code you should return if a connection to an upstream server + times out. + """ + + code = 504 + description = "The connection to an upstream server timed out." + + +class HTTPVersionNotSupported(HTTPException): + """*505* `HTTP Version Not Supported` + + The server does not support the HTTP protocol version used in the request. + """ + + code = 505 + description = ( + "The server does not support the HTTP protocol version used in the request." + ) + + +default_exceptions: dict[int, type[HTTPException]] = {} + + +def _find_exceptions() -> None: + for obj in globals().values(): + try: + is_http_exception = issubclass(obj, HTTPException) + except TypeError: + is_http_exception = False + if not is_http_exception or obj.code is None: + continue + old_obj = default_exceptions.get(obj.code, None) + if old_obj is not None and issubclass(obj, old_obj): + continue + default_exceptions[obj.code] = obj + + +_find_exceptions() +del _find_exceptions + + +class Aborter: + """When passed a dict of code -> exception items it can be used as + callable that raises exceptions. If the first argument to the + callable is an integer it will be looked up in the mapping, if it's + a WSGI application it will be raised in a proxy exception. + + The rest of the arguments are forwarded to the exception constructor. + """ + + def __init__( + self, + mapping: dict[int, type[HTTPException]] | None = None, + extra: dict[int, type[HTTPException]] | None = None, + ) -> None: + if mapping is None: + mapping = default_exceptions + self.mapping = dict(mapping) + if extra is not None: + self.mapping.update(extra) + + def __call__( + self, code: int | Response, *args: t.Any, **kwargs: t.Any + ) -> t.NoReturn: + from .sansio.response import Response + + if isinstance(code, Response): + raise HTTPException(response=code) + + if code not in self.mapping: + raise LookupError(f"no exception for {code!r}") + + raise self.mapping[code](*args, **kwargs) + + +def abort(status: int | Response, *args: t.Any, **kwargs: t.Any) -> t.NoReturn: + """Raises an :py:exc:`HTTPException` for the given status code or WSGI + application. + + If a status code is given, it will be looked up in the list of + exceptions and will raise that exception. If passed a WSGI application, + it will wrap it in a proxy WSGI exception and raise that:: + + abort(404) # 404 Not Found + abort(Response('Hello World')) + + """ + _aborter(status, *args, **kwargs) + + +_aborter: Aborter = Aborter() diff --git a/venv/Lib/site-packages/werkzeug/formparser.py b/venv/Lib/site-packages/werkzeug/formparser.py new file mode 100644 index 00000000..ba84721e --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/formparser.py @@ -0,0 +1,423 @@ +from __future__ import annotations + +import typing as t +from io import BytesIO +from urllib.parse import parse_qsl + +from ._internal import _plain_int +from .datastructures import FileStorage +from .datastructures import Headers +from .datastructures import MultiDict +from .exceptions import RequestEntityTooLarge +from .http import parse_options_header +from .sansio.multipart import Data +from .sansio.multipart import Epilogue +from .sansio.multipart import Field +from .sansio.multipart import File +from .sansio.multipart import MultipartDecoder +from .sansio.multipart import NeedData +from .wsgi import get_content_length +from .wsgi import get_input_stream + +# there are some platforms where SpooledTemporaryFile is not available. +# In that case we need to provide a fallback. +try: + from tempfile import SpooledTemporaryFile +except ImportError: + from tempfile import TemporaryFile + + SpooledTemporaryFile = None # type: ignore + +if t.TYPE_CHECKING: + import typing as te + + from _typeshed.wsgi import WSGIEnvironment + + t_parse_result = t.Tuple[ + t.IO[bytes], MultiDict[str, str], MultiDict[str, FileStorage] + ] + + class TStreamFactory(te.Protocol): + def __call__( + self, + total_content_length: int | None, + content_type: str | None, + filename: str | None, + content_length: int | None = None, + ) -> t.IO[bytes]: ... + + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) + + +def default_stream_factory( + total_content_length: int | None, + content_type: str | None, + filename: str | None, + content_length: int | None = None, +) -> t.IO[bytes]: + max_size = 1024 * 500 + + if SpooledTemporaryFile is not None: + return t.cast(t.IO[bytes], SpooledTemporaryFile(max_size=max_size, mode="rb+")) + elif total_content_length is None or total_content_length > max_size: + return t.cast(t.IO[bytes], TemporaryFile("rb+")) + + return BytesIO() + + +def parse_form_data( + environ: WSGIEnvironment, + stream_factory: TStreamFactory | None = None, + max_form_memory_size: int | None = None, + max_content_length: int | None = None, + cls: type[MultiDict[str, t.Any]] | None = None, + silent: bool = True, + *, + max_form_parts: int | None = None, +) -> t_parse_result: + """Parse the form data in the environ and return it as tuple in the form + ``(stream, form, files)``. You should only call this method if the + transport method is `POST`, `PUT`, or `PATCH`. + + If the mimetype of the data transmitted is `multipart/form-data` the + files multidict will be filled with `FileStorage` objects. If the + mimetype is unknown the input stream is wrapped and returned as first + argument, else the stream is empty. + + This is a shortcut for the common usage of :class:`FormDataParser`. + + :param environ: the WSGI environment to be used for parsing. + :param stream_factory: An optional callable that returns a new read and + writeable file descriptor. This callable works + the same as :meth:`Response._get_file_stream`. + :param max_form_memory_size: the maximum number of bytes to be accepted for + in-memory stored form data. If the data + exceeds the value specified an + :exc:`~exceptions.RequestEntityTooLarge` + exception is raised. + :param max_content_length: If this is provided and the transmitted data + is longer than this value an + :exc:`~exceptions.RequestEntityTooLarge` + exception is raised. + :param cls: an optional dict class to use. If this is not specified + or `None` the default :class:`MultiDict` is used. + :param silent: If set to False parsing errors will not be caught. + :param max_form_parts: The maximum number of multipart parts to be parsed. If this + is exceeded, a :exc:`~exceptions.RequestEntityTooLarge` exception is raised. + :return: A tuple in the form ``(stream, form, files)``. + + .. versionchanged:: 3.0 + The ``charset`` and ``errors`` parameters were removed. + + .. versionchanged:: 2.3 + Added the ``max_form_parts`` parameter. + + .. versionadded:: 0.5.1 + Added the ``silent`` parameter. + + .. versionadded:: 0.5 + Added the ``max_form_memory_size``, ``max_content_length``, and ``cls`` + parameters. + """ + return FormDataParser( + stream_factory=stream_factory, + max_form_memory_size=max_form_memory_size, + max_content_length=max_content_length, + max_form_parts=max_form_parts, + silent=silent, + cls=cls, + ).parse_from_environ(environ) + + +class FormDataParser: + """This class implements parsing of form data for Werkzeug. By itself + it can parse multipart and url encoded form data. It can be subclassed + and extended but for most mimetypes it is a better idea to use the + untouched stream and expose it as separate attributes on a request + object. + + :param stream_factory: An optional callable that returns a new read and + writeable file descriptor. This callable works + the same as :meth:`Response._get_file_stream`. + :param max_form_memory_size: the maximum number of bytes to be accepted for + in-memory stored form data. If the data + exceeds the value specified an + :exc:`~exceptions.RequestEntityTooLarge` + exception is raised. + :param max_content_length: If this is provided and the transmitted data + is longer than this value an + :exc:`~exceptions.RequestEntityTooLarge` + exception is raised. + :param cls: an optional dict class to use. If this is not specified + or `None` the default :class:`MultiDict` is used. + :param silent: If set to False parsing errors will not be caught. + :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:: 3.0 + The ``charset`` and ``errors`` parameters were removed. + + .. versionchanged:: 3.0 + The ``parse_functions`` attribute and ``get_parse_func`` methods were removed. + + .. versionchanged:: 2.2.3 + Added the ``max_form_parts`` parameter. + + .. versionadded:: 0.8 + """ + + def __init__( + self, + stream_factory: TStreamFactory | None = None, + max_form_memory_size: int | None = None, + max_content_length: int | None = None, + cls: type[MultiDict[str, t.Any]] | None = None, + silent: bool = True, + *, + max_form_parts: int | None = None, + ) -> None: + if stream_factory is None: + stream_factory = default_stream_factory + + self.stream_factory = stream_factory + self.max_form_memory_size = max_form_memory_size + self.max_content_length = max_content_length + self.max_form_parts = max_form_parts + + if cls is None: + cls = t.cast("type[MultiDict[str, t.Any]]", MultiDict) + + self.cls = cls + self.silent = silent + + def parse_from_environ(self, environ: WSGIEnvironment) -> t_parse_result: + """Parses the information from the environment as form data. + + :param environ: the WSGI environment to be used for parsing. + :return: A tuple in the form ``(stream, form, files)``. + """ + stream = get_input_stream(environ, max_content_length=self.max_content_length) + content_length = get_content_length(environ) + mimetype, options = parse_options_header(environ.get("CONTENT_TYPE")) + return self.parse( + stream, + content_length=content_length, + mimetype=mimetype, + options=options, + ) + + def parse( + self, + stream: t.IO[bytes], + mimetype: str, + content_length: int | None, + options: dict[str, str] | None = None, + ) -> t_parse_result: + """Parses the information from the given stream, mimetype, + content length and mimetype parameters. + + :param stream: an input stream + :param mimetype: the mimetype of the data + :param content_length: the content length of the incoming data + :param options: optional mimetype parameters (used for + the multipart boundary for instance) + :return: A tuple in the form ``(stream, form, files)``. + + .. 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 + else: + return stream, self.cls(), self.cls() + + if options is None: + options = {} + + try: + return parse_func(stream, mimetype, content_length, options) + except ValueError: + if not self.silent: + raise + + return stream, self.cls(), self.cls() + + def _parse_multipart( + self, + stream: t.IO[bytes], + mimetype: str, + content_length: int | None, + options: dict[str, str], + ) -> t_parse_result: + parser = MultiPartParser( + stream_factory=self.stream_factory, + max_form_memory_size=self.max_form_memory_size, + max_form_parts=self.max_form_parts, + cls=self.cls, + ) + boundary = options.get("boundary", "").encode("ascii") + + if not boundary: + raise ValueError("Missing boundary") + + form, files = parser.parse(stream, boundary, content_length) + return stream, form, files + + def _parse_urlencoded( + self, + stream: t.IO[bytes], + mimetype: str, + content_length: int | None, + options: dict[str, str], + ) -> t_parse_result: + if ( + self.max_form_memory_size is not None + and content_length is not None + and content_length > self.max_form_memory_size + ): + raise RequestEntityTooLarge() + + try: + items = parse_qsl( + stream.read().decode(), + keep_blank_values=True, + errors="werkzeug.url_quote", + ) + except ValueError as e: + raise RequestEntityTooLarge() from e + + return stream, self.cls(items), self.cls() + + +class MultiPartParser: + def __init__( + self, + stream_factory: TStreamFactory | None = None, + max_form_memory_size: int | None = None, + cls: type[MultiDict[str, t.Any]] | None = None, + buffer_size: int = 64 * 1024, + max_form_parts: int | None = None, + ) -> None: + self.max_form_memory_size = max_form_memory_size + self.max_form_parts = max_form_parts + + if stream_factory is None: + stream_factory = default_stream_factory + + self.stream_factory = stream_factory + + if cls is None: + cls = t.cast("type[MultiDict[str, t.Any]]", MultiDict) + + self.cls = cls + self.buffer_size = buffer_size + + def fail(self, message: str) -> te.NoReturn: + raise ValueError(message) + + def get_part_charset(self, headers: Headers) -> str: + # Figure out input charset for current part + content_type = headers.get("content-type") + + if content_type: + parameters = parse_options_header(content_type)[1] + ct_charset = parameters.get("charset", "").lower() + + # A safe list of encodings. Modern clients should only send ASCII or UTF-8. + # This list will not be extended further. + if ct_charset in {"ascii", "us-ascii", "utf-8", "iso-8859-1"}: + return ct_charset + + return "utf-8" + + def start_file_streaming( + self, event: File, total_content_length: int | None + ) -> t.IO[bytes]: + content_type = event.headers.get("content-type") + + try: + content_length = _plain_int(event.headers["content-length"]) + except (KeyError, ValueError): + content_length = 0 + + container = self.stream_factory( + total_content_length=total_content_length, + filename=event.filename, + content_type=content_type, + content_length=content_length, + ) + return container + + def parse( + self, stream: t.IO[bytes], boundary: bytes, content_length: int | None + ) -> tuple[MultiDict[str, str], MultiDict[str, FileStorage]]: + current_part: Field | File + container: t.IO[bytes] | list[bytes] + _write: t.Callable[[bytes], t.Any] + + parser = MultipartDecoder( + boundary, + max_form_memory_size=self.max_form_memory_size, + max_parts=self.max_form_parts, + ) + + fields = [] + files = [] + + for data in _chunk_iter(stream.read, self.buffer_size): + parser.receive_data(data) + event = parser.next_event() + while not isinstance(event, (Epilogue, NeedData)): + if isinstance(event, Field): + current_part = event + container = [] + _write = container.append + elif isinstance(event, File): + current_part = event + container = self.start_file_streaming(event, content_length) + _write = container.write + elif isinstance(event, Data): + _write(event.data) + if not event.more_data: + if isinstance(current_part, Field): + value = b"".join(container).decode( + self.get_part_charset(current_part.headers), "replace" + ) + fields.append((current_part.name, value)) + else: + container = t.cast(t.IO[bytes], container) + container.seek(0) + files.append( + ( + current_part.name, + FileStorage( + container, + current_part.filename, + current_part.name, + headers=current_part.headers, + ), + ) + ) + + event = parser.next_event() + + return self.cls(fields), self.cls(files) + + +def _chunk_iter(read: t.Callable[[int], bytes], size: int) -> t.Iterator[bytes | None]: + """Read data in chunks for multipart/form-data parsing. Stop if no data is read. + Yield ``None`` at the end to signal end of parsing. + """ + while True: + data = read(size) + + if not data: + break + + yield data + + yield None diff --git a/venv/Lib/site-packages/werkzeug/http.py b/venv/Lib/site-packages/werkzeug/http.py new file mode 100644 index 00000000..27fa9af9 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/http.py @@ -0,0 +1,1370 @@ +from __future__ import annotations + +import email.utils +import re +import typing as t +import warnings +from datetime import date +from datetime import datetime +from datetime import time +from datetime import timedelta +from datetime import timezone +from enum import Enum +from hashlib import sha1 +from time import mktime +from time import struct_time +from urllib.parse import quote +from urllib.parse import unquote +from urllib.request import parse_http_list as _parse_list_header + +from ._internal import _dt_as_utc +from ._internal import _plain_int + +if t.TYPE_CHECKING: + from _typeshed.wsgi import WSGIEnvironment + +_token_chars = frozenset( + "!#$%&'*+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz|~" +) +_etag_re = re.compile(r'([Ww]/)?(?:"(.*?)"|(.*?))(?:\s*,\s*|$)') +_entity_headers = frozenset( + [ + "allow", + "content-encoding", + "content-language", + "content-length", + "content-location", + "content-md5", + "content-range", + "content-type", + "expires", + "last-modified", + ] +) +_hop_by_hop_headers = frozenset( + [ + "connection", + "keep-alive", + "proxy-authenticate", + "proxy-authorization", + "te", + "trailer", + "transfer-encoding", + "upgrade", + ] +) +HTTP_STATUS_CODES = { + 100: "Continue", + 101: "Switching Protocols", + 102: "Processing", + 103: "Early Hints", # see RFC 8297 + 200: "OK", + 201: "Created", + 202: "Accepted", + 203: "Non Authoritative Information", + 204: "No Content", + 205: "Reset Content", + 206: "Partial Content", + 207: "Multi Status", + 208: "Already Reported", # see RFC 5842 + 226: "IM Used", # see RFC 3229 + 300: "Multiple Choices", + 301: "Moved Permanently", + 302: "Found", + 303: "See Other", + 304: "Not Modified", + 305: "Use Proxy", + 306: "Switch Proxy", # unused + 307: "Temporary Redirect", + 308: "Permanent Redirect", + 400: "Bad Request", + 401: "Unauthorized", + 402: "Payment Required", # unused + 403: "Forbidden", + 404: "Not Found", + 405: "Method Not Allowed", + 406: "Not Acceptable", + 407: "Proxy Authentication Required", + 408: "Request Timeout", + 409: "Conflict", + 410: "Gone", + 411: "Length Required", + 412: "Precondition Failed", + 413: "Request Entity Too Large", + 414: "Request URI Too Long", + 415: "Unsupported Media Type", + 416: "Requested Range Not Satisfiable", + 417: "Expectation Failed", + 418: "I'm a teapot", # see RFC 2324 + 421: "Misdirected Request", # see RFC 7540 + 422: "Unprocessable Entity", + 423: "Locked", + 424: "Failed Dependency", + 425: "Too Early", # see RFC 8470 + 426: "Upgrade Required", + 428: "Precondition Required", # see RFC 6585 + 429: "Too Many Requests", + 431: "Request Header Fields Too Large", + 449: "Retry With", # proprietary MS extension + 451: "Unavailable For Legal Reasons", + 500: "Internal Server Error", + 501: "Not Implemented", + 502: "Bad Gateway", + 503: "Service Unavailable", + 504: "Gateway Timeout", + 505: "HTTP Version Not Supported", + 506: "Variant Also Negotiates", # see RFC 2295 + 507: "Insufficient Storage", + 508: "Loop Detected", # see RFC 5842 + 510: "Not Extended", + 511: "Network Authentication Failed", +} + + +class COEP(Enum): + """Cross Origin Embedder Policies""" + + UNSAFE_NONE = "unsafe-none" + REQUIRE_CORP = "require-corp" + + +class COOP(Enum): + """Cross Origin Opener Policies""" + + UNSAFE_NONE = "unsafe-none" + SAME_ORIGIN_ALLOW_POPUPS = "same-origin-allow-popups" + SAME_ORIGIN = "same-origin" + + +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. + + This is the reverse of :func:`unquote_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. + + .. versionadded:: 0.5 + """ + value_str = str(value) + + if not value_str: + return '""' + + if allow_token: + token_chars = _token_chars + + if token_chars.issuperset(value_str): + return value_str + + value_str = value_str.replace("\\", "\\\\").replace('"', '\\"') + return f'"{value_str}"' + + +def unquote_header_value(value: str) -> str: + """Remove double quotes and decode slash-escaped ``"`` and ``\\`` characters in a + header value. + + This is the reverse of :func:`quote_header_value`. + + :param value: The header value to unquote. + + .. versionchanged:: 3.0 + The ``is_filename`` parameter is removed. + """ + if len(value) >= 2 and value[0] == value[-1] == '"': + value = value[1:-1] + return value.replace("\\\\", "\\").replace('\\"', '"') + + return value + + +def dump_options_header(header: str | None, options: t.Mapping[str, t.Any]) -> str: + """Produce a header value and ``key=value`` parameters separated by semicolons + ``;``. For example, the ``Content-Type`` header. + + .. code-block:: python + + dump_options_header("text/html", {"charset": "UTF-8"}) + 'text/html; charset=UTF-8' + + This is the reverse of :func:`parse_options_header`. + + If a value contains non-token characters, it will be quoted. + + If a value is ``None``, the parameter is skipped. + + In some keys for some headers, a UTF-8 value can be encoded using a special + ``key*=UTF-8''value`` form, where ``value`` is percent encoded. This function will + not produce that format automatically, but if a given key ends with an asterisk + ``*``, the value is assumed to have that form and will not be quoted further. + + :param header: The primary header value. + :param options: Parameters to encode as ``key=value`` pairs. + + .. versionchanged:: 2.3 + Keys with ``None`` values are skipped rather than treated as a bare key. + + .. versionchanged:: 2.2.3 + If a key ends with ``*``, its value will not be quoted. + """ + segments = [] + + if header is not None: + segments.append(header) + + for key, value in options.items(): + if value is None: + continue + + if key[-1] == "*": + segments.append(f"{key}={value}") + else: + segments.append(f"{key}={quote_header_value(value)}") + + return "; ".join(segments) + + +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 ``,``. + + This is the reverse of :func:`parse_list_header`, :func:`parse_dict_header`, and + :func:`parse_set_header`. + + If a value contains non-token characters, it will be quoted. + + If a value is ``None``, the key is output alone. + + In some keys for some headers, a UTF-8 value can be encoded using a special + ``key*=UTF-8''value`` form, where ``value`` is percent encoded. This function will + not produce that format automatically, but if a given key ends with an asterisk + ``*``, the value is assumed to have that form and will not be quoted further. + + .. code-block:: python + + dump_header(["foo", "bar baz"]) + 'foo, "bar baz"' + + dump_header({"foo": "bar baz"}) + 'foo="bar baz"' + + :param iterable: The items to create a header from. + + .. 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 isinstance(iterable, dict): + items = [] + + for key, value in iterable.items(): + if value is None: + items.append(key) + elif key[-1] == "*": + items.append(f"{key}={value}") + else: + items.append(f"{key}={quote_header_value(value)}") + else: + items = [quote_header_value(x) for x in iterable] + + return ", ".join(items) + + +def dump_csp_header(header: ds.ContentSecurityPolicy) -> str: + """Dump a Content Security Policy header. + + These are structured into policies such as "default-src 'self'; + script-src 'self'". + + .. versionadded:: 1.0.0 + Support for Content Security Policy headers was added. + + """ + return "; ".join(f"{key} {value}" for key, value in header.items()) + + +def parse_list_header(value: str) -> list[str]: + """Parse a header value that consists of a list of comma separated items according + to `RFC 9110 `__. + + This extends :func:`urllib.request.parse_http_list` to remove surrounding quotes + from values. + + .. code-block:: python + + parse_list_header('token, "quoted value"') + ['token', 'quoted value'] + + This is the reverse of :func:`dump_header`. + + :param value: The header value to parse. + """ + result = [] + + for item in _parse_list_header(value): + if len(item) >= 2 and item[0] == item[-1] == '"': + item = item[1:-1] + + result.append(item) + + return result + + +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. + + .. code-block:: python + + parse_dict_header('a=b, c="d, e", f') + {"a": "b", "c": "d, e", "f": None} + + This is the reverse of :func:`dump_header`. + + If a key does not have a value, it is ``None``. + + This handles charsets for values as described in + `RFC 2231 `__. Only ASCII, UTF-8, + and ISO-8859-1 charsets are accepted, otherwise the value remains quoted. + + :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:: 0.9 + The ``cls`` argument was added. + """ + result: dict[str, str | None] = {} + + for item in parse_list_header(value): + key, has_value, value = item.partition("=") + key = key.strip() + + if not has_value: + result[key] = None + continue + + value = value.strip() + encoding: str | None = None + + if key[-1] == "*": + # key*=charset''value becomes key=value, where value is percent encoded + # adapted from parse_options_header, without the continuation handling + key = key[:-1] + match = _charset_value_re.match(value) + + if match: + # If there is a charset marker in the value, split it off. + encoding, value = match.groups() + encoding = encoding.lower() + + # A safe list of encodings. Modern clients should only send ASCII or UTF-8. + # This list will not be extended further. An invalid encoding will leave the + # value quoted. + if encoding in {"ascii", "us-ascii", "utf-8", "iso-8859-1"}: + # invalid bytes are replaced during unquoting + value = unquote(value, encoding=encoding) + + if len(value) >= 2 and value[0] == value[-1] == '"': + value = value[1:-1] + + result[key] = value + + return result + + +# https://httpwg.org/specs/rfc9110.html#parameter +_parameter_re = re.compile( + r""" + # don't match multiple empty parts, that causes backtracking + \s*;\s* # find the part delimiter + (?: + ([\w!#$%&'*+\-.^`|~]+) # key, one or more token chars + = # equals, with no space on either side + ( # value, token or quoted string + [\w!#$%&'*+\-.^`|~]+ # one or more token chars + | + "(?:\\\\|\\"|.)*?" # quoted string, consuming slash escapes + ) + )? # optionally match key=value, to account for empty parts + """, + re.ASCII | re.VERBOSE, +) +# https://www.rfc-editor.org/rfc/rfc2231#section-4 +_charset_value_re = re.compile( + r""" + ([\w!#$%&*+\-.^`|~]*)' # charset part, could be empty + [\w!#$%&*+\-.^`|~]*' # don't care about language part, usually empty + ([\w!#$%&'*+\-.^`|~]+) # one or more token chars with percent encoding + """, + re.ASCII | re.VERBOSE, +) +# https://www.rfc-editor.org/rfc/rfc2231#section-3 +_continuation_re = re.compile(r"\*(\d+)$", re.ASCII) + + +def parse_options_header(value: str | None) -> tuple[str, dict[str, str]]: + """Parse a header that consists of a value with ``key=value`` parameters separated + by semicolons ``;``. For example, the ``Content-Type`` header. + + .. code-block:: python + + parse_options_header("text/html; charset=UTF-8") + ('text/html', {'charset': 'UTF-8'}) + + parse_options_header("") + ("", {}) + + This is the reverse of :func:`dump_options_header`. + + This parses valid parameter parts as described in + `RFC 9110 `__. Invalid parts are + skipped. + + This handles continuations and charsets as described in + `RFC 2231 `__, although not as + strictly as the RFC. Only ASCII, UTF-8, and ISO-8859-1 charsets are accepted, + otherwise the value remains quoted. + + Clients may not be consistent in how they handle a quote character within a quoted + value. The `HTML Standard `__ + replaces it with ``%22`` in multipart form data. + `RFC 9110 `__ uses backslash + escapes in HTTP headers. Both are decoded to the ``"`` character. + + Clients may not be consistent in how they handle non-ASCII characters. HTML + documents must declare ````, otherwise browsers may replace with + HTML character references, which can be decoded using :func:`html.unescape`. + + :param value: The header value to parse. + :return: ``(value, options)``, where ``options`` is a dict + + .. versionchanged:: 2.3 + Invalid parts, such as keys with no value, quoted keys, and incorrectly quoted + values, are discarded instead of treating as ``None``. + + .. versionchanged:: 2.3 + Only ASCII, UTF-8, and ISO-8859-1 are accepted for charset values. + + .. versionchanged:: 2.3 + Escaped quotes in quoted values, like ``%22`` and ``\\"``, are handled. + + .. versionchanged:: 2.2 + Option names are always converted to lowercase. + + .. versionchanged:: 2.2 + The ``multiple`` parameter was removed. + + .. versionchanged:: 0.15 + :rfc:`2231` parameter continuations are handled. + + .. versionadded:: 0.5 + """ + if value is None: + return "", {} + + value, _, rest = value.partition(";") + value = value.strip() + rest = rest.strip() + + if not value or not rest: + # empty (invalid) value, or value without options + return value, {} + + rest = f";{rest}" + options: dict[str, str] = {} + encoding: str | None = None + continued_encoding: str | None = None + + for pk, pv in _parameter_re.findall(rest): + if not pk: + # empty or invalid part + continue + + pk = pk.lower() + + if pk[-1] == "*": + # key*=charset''value becomes key=value, where value is percent encoded + pk = pk[:-1] + match = _charset_value_re.match(pv) + + if match: + # If there is a valid charset marker in the value, split it off. + encoding, pv = match.groups() + # This might be the empty string, handled next. + encoding = encoding.lower() + + # No charset marker, or marker with empty charset value. + if not encoding: + encoding = continued_encoding + + # A safe list of encodings. Modern clients should only send ASCII or UTF-8. + # This list will not be extended further. An invalid encoding will leave the + # value quoted. + if encoding in {"ascii", "us-ascii", "utf-8", "iso-8859-1"}: + # Continuation parts don't require their own charset marker. This is + # looser than the RFC, it will persist across different keys and allows + # changing the charset during a continuation. But this implementation is + # much simpler than tracking the full state. + continued_encoding = encoding + # invalid bytes are replaced during unquoting + pv = unquote(pv, encoding=encoding) + + # Remove quotes. At this point the value cannot be empty or a single quote. + if pv[0] == pv[-1] == '"': + # HTTP headers use slash, multipart form data uses percent + pv = pv[1:-1].replace("\\\\", "\\").replace('\\"', '"').replace("%22", '"') + + match = _continuation_re.search(pk) + + if match: + # key*0=a; key*1=b becomes key=ab + pk = pk[: match.start()] + options[pk] = options.get(pk, "") + pv + else: + options[pk] = pv + + return value, options + + +_q_value_re = re.compile(r"-?\d+(\.\d+)?", re.ASCII) +_TAnyAccept = t.TypeVar("_TAnyAccept", bound="ds.Accept") + + +@t.overload +def parse_accept_header(value: str | None) -> ds.Accept: ... + + +@t.overload +def parse_accept_header(value: str | None, cls: type[_TAnyAccept]) -> _TAnyAccept: ... + + +def parse_accept_header( + value: str | None, cls: type[_TAnyAccept] | None = None +) -> _TAnyAccept: + """Parse an ``Accept`` header according to + `RFC 9110 `__. + + Returns an :class:`.Accept` instance, which can sort and inspect items based on + their quality parameter. When parsing ``Accept-Charset``, ``Accept-Encoding``, or + ``Accept-Language``, pass the appropriate :class:`.Accept` subclass. + + :param value: The header value to parse. + :param cls: The :class:`.Accept` class to wrap the result in. + :return: An instance of ``cls``. + + .. versionchanged:: 2.3 + Parse according to RFC 9110. Items with invalid ``q`` values are skipped. + """ + if cls is None: + cls = t.cast(t.Type[_TAnyAccept], ds.Accept) + + if not value: + return cls(None) + + result = [] + + for item in parse_list_header(value): + item, options = parse_options_header(item) + + if "q" in options: + # pop q, remaining options are reconstructed + q_str = options.pop("q").strip() + + if _q_value_re.fullmatch(q_str) is None: + # ignore an invalid q + continue + + q = float(q_str) + + if q < 0 or q > 1: + # ignore an invalid q + continue + else: + q = 1 + + if options: + # reconstruct the media type with any options + item = dump_options_header(item, options) + + result.append((item, q)) + + return cls(result) + + +_TAnyCC = t.TypeVar("_TAnyCC", bound="ds.cache_control._CacheControl") + + +@t.overload +def parse_cache_control_header( + value: str | None, + on_update: t.Callable[[ds.cache_control._CacheControl], None] | None = None, +) -> ds.RequestCacheControl: ... + + +@t.overload +def parse_cache_control_header( + value: str | None, + on_update: t.Callable[[ds.cache_control._CacheControl], None] | None = None, + cls: type[_TAnyCC] = ..., +) -> _TAnyCC: ... + + +def parse_cache_control_header( + value: str | None, + on_update: t.Callable[[ds.cache_control._CacheControl], None] | None = None, + cls: type[_TAnyCC] | None = None, +) -> _TAnyCC: + """Parse a cache control header. The RFC differs between response and + request cache control, this method does not. It's your responsibility + to not use the wrong control statements. + + .. versionadded:: 0.5 + The `cls` was added. If not specified an immutable + :class:`~werkzeug.datastructures.RequestCacheControl` is returned. + + :param value: a cache control header to be parsed. + :param on_update: an optional callable that is called every time a value + on the :class:`~werkzeug.datastructures.CacheControl` + object is changed. + :param cls: the class for the returned object. By default + :class:`~werkzeug.datastructures.RequestCacheControl` is used. + :return: a `cls` object. + """ + if cls is None: + cls = t.cast("type[_TAnyCC]", ds.RequestCacheControl) + + if not value: + return cls((), on_update) + + return cls(parse_dict_header(value), on_update) + + +_TAnyCSP = t.TypeVar("_TAnyCSP", bound="ds.ContentSecurityPolicy") + + +@t.overload +def parse_csp_header( + value: str | None, + on_update: t.Callable[[ds.ContentSecurityPolicy], None] | None = None, +) -> ds.ContentSecurityPolicy: ... + + +@t.overload +def parse_csp_header( + value: str | None, + on_update: t.Callable[[ds.ContentSecurityPolicy], None] | None = None, + cls: type[_TAnyCSP] = ..., +) -> _TAnyCSP: ... + + +def parse_csp_header( + value: str | None, + on_update: t.Callable[[ds.ContentSecurityPolicy], None] | None = None, + cls: type[_TAnyCSP] | None = None, +) -> _TAnyCSP: + """Parse a Content Security Policy header. + + .. versionadded:: 1.0.0 + Support for Content Security Policy headers was added. + + :param value: a csp header to be parsed. + :param on_update: an optional callable that is called every time a value + on the object is changed. + :param cls: the class for the returned object. By default + :class:`~werkzeug.datastructures.ContentSecurityPolicy` is used. + :return: a `cls` object. + """ + if cls is None: + cls = t.cast("type[_TAnyCSP]", ds.ContentSecurityPolicy) + + if value is None: + return cls((), on_update) + + items = [] + + for policy in value.split(";"): + policy = policy.strip() + + # Ignore badly formatted policies (no space) + if " " in policy: + directive, value = policy.strip().split(" ", 1) + items.append((directive.strip(), value.strip())) + + return cls(items, on_update) + + +def parse_set_header( + value: str | None, + on_update: t.Callable[[ds.HeaderSet], None] | None = None, +) -> ds.HeaderSet: + """Parse a set-like header and return a + :class:`~werkzeug.datastructures.HeaderSet` object: + + >>> hs = parse_set_header('token, "quoted value"') + + The return value is an object that treats the items case-insensitively + and keeps the order of the items: + + >>> 'TOKEN' in hs + True + >>> hs.index('quoted value') + 1 + >>> hs + HeaderSet(['token', 'quoted value']) + + To create a header from the :class:`HeaderSet` again, use the + :func:`dump_header` function. + + :param value: a set header to be parsed. + :param on_update: an optional callable that is called every time a + value on the :class:`~werkzeug.datastructures.HeaderSet` + object is changed. + :return: a :class:`~werkzeug.datastructures.HeaderSet` + """ + if not value: + return ds.HeaderSet(None, on_update) + return ds.HeaderSet(parse_list_header(value), on_update) + + +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. + + .. versionchanged:: 2.0 + If the value represents a datetime, it is timezone-aware. + + .. versionadded:: 0.7 + """ + if not value: + return ds.IfRange() + date = parse_date(value) + if date is not None: + return ds.IfRange(date=date) + # drop weakness information + return ds.IfRange(unquote_etag(value)[0]) + + +def parse_range_header( + value: str | None, make_inclusive: bool = True +) -> ds.Range | None: + """Parses a range header into a :class:`~werkzeug.datastructures.Range` + object. If the header is missing or malformed `None` is returned. + `ranges` is a list of ``(start, stop)`` tuples where the ranges are + non-inclusive. + + .. versionadded:: 0.7 + """ + if not value or "=" not in value: + return None + + ranges = [] + last_end = 0 + units, rng = value.split("=", 1) + units = units.strip().lower() + + for item in rng.split(","): + item = item.strip() + if "-" not in item: + return None + if item.startswith("-"): + if last_end < 0: + return None + try: + begin = _plain_int(item) + except ValueError: + return None + end = None + last_end = -1 + elif "-" in item: + begin_str, end_str = item.split("-", 1) + begin_str = begin_str.strip() + end_str = end_str.strip() + + try: + begin = _plain_int(begin_str) + except ValueError: + return None + + if begin < last_end or last_end < 0: + return None + if end_str: + try: + end = _plain_int(end_str) + 1 + except ValueError: + return None + + if begin >= end: + return None + else: + end = None + last_end = end if end is not None else -1 + ranges.append((begin, end)) + + return ds.Range(units, ranges) + + +def parse_content_range_header( + value: str | None, + on_update: t.Callable[[ds.ContentRange], None] | None = None, +) -> ds.ContentRange | None: + """Parses a range header into a + :class:`~werkzeug.datastructures.ContentRange` object or `None` if + parsing is not possible. + + .. versionadded:: 0.7 + + :param value: a content range header to be parsed. + :param on_update: an optional callable that is called every time a value + on the :class:`~werkzeug.datastructures.ContentRange` + object is changed. + """ + if value is None: + return None + try: + units, rangedef = (value or "").strip().split(None, 1) + except ValueError: + return None + + if "/" not in rangedef: + return None + rng, length_str = rangedef.split("/", 1) + if length_str == "*": + length = None + else: + try: + length = _plain_int(length_str) + except ValueError: + return None + + if rng == "*": + if not is_byte_range_valid(None, None, length): + return None + + return ds.ContentRange(units, None, None, length, on_update=on_update) + elif "-" not in rng: + return None + + start_str, stop_str = rng.split("-", 1) + try: + start = _plain_int(start_str) + stop = _plain_int(stop_str) + 1 + except ValueError: + return None + + if is_byte_range_valid(start, stop, length): + return ds.ContentRange(units, start, stop, length, on_update=on_update) + + return None + + +def quote_etag(etag: str, weak: bool = False) -> str: + """Quote an etag. + + :param etag: the etag to quote. + :param weak: set to `True` to tag it "weak". + """ + if '"' in etag: + raise ValueError("invalid etag") + etag = f'"{etag}"' + if weak: + etag = f"W/{etag}" + return etag + + +def unquote_etag( + etag: str | None, +) -> tuple[str, bool] | tuple[None, None]: + """Unquote a single etag: + + >>> unquote_etag('W/"bar"') + ('bar', True) + >>> unquote_etag('"bar"') + ('bar', False) + + :param etag: the etag identifier to unquote. + :return: a ``(etag, weak)`` tuple. + """ + if not etag: + return None, None + etag = etag.strip() + weak = False + if etag.startswith(("W/", "w/")): + weak = True + etag = etag[2:] + if etag[:1] == etag[-1:] == '"': + etag = etag[1:-1] + return etag, weak + + +def parse_etags(value: str | None) -> ds.ETags: + """Parse an etag header. + + :param value: the tag header to parse + :return: an :class:`~werkzeug.datastructures.ETags` object. + """ + if not value: + return ds.ETags() + strong = [] + weak = [] + end = len(value) + pos = 0 + while pos < end: + match = _etag_re.match(value, pos) + if match is None: + break + is_weak, quoted, raw = match.groups() + if raw == "*": + return ds.ETags(star_tag=True) + elif quoted: + raw = quoted + if is_weak: + weak.append(raw) + else: + strong.append(raw) + pos = match.end() + return ds.ETags(strong, weak) + + +def generate_etag(data: bytes) -> str: + """Generate an etag for some data. + + .. versionchanged:: 2.0 + Use SHA-1. MD5 may not be available in some environments. + """ + return sha1(data).hexdigest() + + +def parse_date(value: str | None) -> datetime | None: + """Parse an :rfc:`2822` date into a timezone-aware + :class:`datetime.datetime` object, or ``None`` if parsing fails. + + This is a wrapper for :func:`email.utils.parsedate_to_datetime`. It + returns ``None`` if parsing fails instead of raising an exception, + and always returns a timezone-aware datetime object. If the string + doesn't have timezone information, it is assumed to be UTC. + + :param value: A string with a supported date format. + + .. versionchanged:: 2.0 + Return a timezone-aware datetime object. Use + ``email.utils.parsedate_to_datetime``. + """ + if value is None: + return None + + try: + dt = email.utils.parsedate_to_datetime(value) + except (TypeError, ValueError): + return None + + if dt.tzinfo is None: + return dt.replace(tzinfo=timezone.utc) + + return dt + + +def http_date( + timestamp: datetime | date | int | float | struct_time | None = None, +) -> str: + """Format a datetime object or timestamp into an :rfc:`2822` date + string. + + This is a wrapper for :func:`email.utils.format_datetime`. It + assumes naive datetime objects are in UTC instead of raising an + exception. + + :param timestamp: The datetime or timestamp to format. Defaults to + the current time. + + .. versionchanged:: 2.0 + Use ``email.utils.format_datetime``. Accept ``date`` objects. + """ + if isinstance(timestamp, date): + if not isinstance(timestamp, datetime): + # Assume plain date is midnight UTC. + timestamp = datetime.combine(timestamp, time(), tzinfo=timezone.utc) + else: + # Ensure datetime is timezone-aware. + timestamp = _dt_as_utc(timestamp) + + return email.utils.format_datetime(timestamp, usegmt=True) + + if isinstance(timestamp, struct_time): + timestamp = mktime(timestamp) + + return email.utils.formatdate(timestamp, usegmt=True) + + +def parse_age(value: str | None = None) -> timedelta | None: + """Parses a base-10 integer count of seconds into a timedelta. + + If parsing fails, the return value is `None`. + + :param value: a string consisting of an integer represented in base-10 + :return: a :class:`datetime.timedelta` object or `None`. + """ + if not value: + return None + try: + seconds = int(value) + except ValueError: + return None + if seconds < 0: + return None + try: + return timedelta(seconds=seconds) + except OverflowError: + return None + + +def dump_age(age: timedelta | int | None = None) -> str | None: + """Formats the duration as a base-10 integer. + + :param age: should be an integer number of seconds, + a :class:`datetime.timedelta` object, or, + if the age is unknown, `None` (default). + """ + if age is None: + return None + if isinstance(age, timedelta): + age = int(age.total_seconds()) + else: + age = int(age) + + if age < 0: + raise ValueError("age cannot be negative") + + return str(age) + + +def is_resource_modified( + environ: WSGIEnvironment, + etag: str | None = None, + data: bytes | None = None, + last_modified: datetime | str | None = None, + ignore_if_range: bool = True, +) -> bool: + """Convenience method for conditional requests. + + :param environ: the WSGI environment of the request to be checked. + :param etag: the etag for the response for comparison. + :param data: or alternatively the data of the response to automatically + generate an etag using :func:`generate_etag`. + :param last_modified: an optional date of the last modification. + :param ignore_if_range: If `False`, `If-Range` header will be taken into + account. + :return: `True` if the resource was modified, otherwise `False`. + + .. versionchanged:: 2.0 + SHA-1 is used to generate an etag value for the data. MD5 may + not be available in some environments. + + .. versionchanged:: 1.0.0 + The check is run for methods other than ``GET`` and ``HEAD``. + """ + return _sansio_http.is_resource_modified( + http_range=environ.get("HTTP_RANGE"), + http_if_range=environ.get("HTTP_IF_RANGE"), + http_if_modified_since=environ.get("HTTP_IF_MODIFIED_SINCE"), + http_if_none_match=environ.get("HTTP_IF_NONE_MATCH"), + http_if_match=environ.get("HTTP_IF_MATCH"), + etag=etag, + data=data, + last_modified=last_modified, + ignore_if_range=ignore_if_range, + ) + + +def remove_entity_headers( + headers: ds.Headers | list[tuple[str, str]], + allowed: t.Iterable[str] = ("expires", "content-location"), +) -> None: + """Remove all entity headers from a list or :class:`Headers` object. This + operation works in-place. `Expires` and `Content-Location` headers are + by default not removed. The reason for this is :rfc:`2616` section + 10.3.5 which specifies some entity headers that should be sent. + + .. versionchanged:: 0.5 + added `allowed` parameter. + + :param headers: a list or :class:`Headers` object. + :param allowed: a list of headers that should still be allowed even though + they are entity headers. + """ + allowed = {x.lower() for x in allowed} + headers[:] = [ + (key, value) + for key, value in headers + if not is_entity_header(key) or key.lower() in allowed + ] + + +def remove_hop_by_hop_headers(headers: ds.Headers | list[tuple[str, str]]) -> None: + """Remove all HTTP/1.1 "Hop-by-Hop" headers from a list or + :class:`Headers` object. This operation works in-place. + + .. versionadded:: 0.5 + + :param headers: a list or :class:`Headers` object. + """ + headers[:] = [ + (key, value) for key, value in headers if not is_hop_by_hop_header(key) + ] + + +def is_entity_header(header: str) -> bool: + """Check if a header is an entity header. + + .. versionadded:: 0.5 + + :param header: the header to test. + :return: `True` if it's an entity header, `False` otherwise. + """ + return header.lower() in _entity_headers + + +def is_hop_by_hop_header(header: str) -> bool: + """Check if a header is an HTTP/1.1 "Hop-by-Hop" header. + + .. versionadded:: 0.5 + + :param header: the header to test. + :return: `True` if it's an HTTP/1.1 "Hop-by-Hop" header, `False` otherwise. + """ + return header.lower() in _hop_by_hop_headers + + +def parse_cookie( + header: WSGIEnvironment | str | None, + cls: type[ds.MultiDict[str, str]] | None = None, +) -> ds.MultiDict[str, str]: + """Parse a cookie from a string or WSGI environ. + + The same key can be provided multiple times, the values are stored + in-order. The default :class:`MultiDict` will have the first value + first, and all values can be retrieved with + :meth:`MultiDict.getlist`. + + :param header: The cookie header as a string, or a WSGI environ dict + with a ``HTTP_COOKIE`` key. + :param cls: A dict-like class to store the parsed cookies in. + Defaults to :class:`MultiDict`. + + .. versionchanged:: 3.0 + Passing bytes, and the ``charset`` and ``errors`` parameters, were removed. + + .. versionchanged:: 1.0 + Returns a :class:`MultiDict` instead of a ``TypeConversionDict``. + + .. versionchanged:: 0.5 + Returns a :class:`TypeConversionDict` instead of a regular dict. The ``cls`` + parameter was added. + """ + if isinstance(header, dict): + cookie = header.get("HTTP_COOKIE") + else: + cookie = header + + if cookie: + cookie = cookie.encode("latin1").decode() + + return _sansio_http.parse_cookie(cookie=cookie, cls=cls) + + +_cookie_no_quote_re = re.compile(r"[\w!#$%&'()*+\-./:<=>?@\[\]^`{|}~]*", re.A) +_cookie_slash_re = re.compile(rb"[\x00-\x19\",;\\\x7f-\xff]", re.A) +_cookie_slash_map = {b'"': b'\\"', b"\\": b"\\\\"} +_cookie_slash_map.update( + (v.to_bytes(1, "big"), b"\\%03o" % v) + for v in [*range(0x20), *b",;", *range(0x7F, 256)] +) + + +def dump_cookie( + key: str, + value: str = "", + max_age: timedelta | int | None = None, + expires: str | datetime | int | float | None = None, + path: str | None = "/", + domain: str | None = None, + secure: bool = False, + httponly: bool = False, + sync_expires: bool = True, + max_size: int = 4093, + samesite: str | None = None, +) -> str: + """Create a Set-Cookie header without the ``Set-Cookie`` prefix. + + The return value is usually restricted to ascii as the vast majority + of values are properly escaped, but that is no guarantee. It's + tunneled through latin1 as required by :pep:`3333`. + + The return value is not ASCII safe if the key contains unicode + characters. This is technically against the specification but + happens in the wild. It's strongly recommended to not use + non-ASCII values for the keys. + + :param max_age: should be a number of seconds, or `None` (default) if + the cookie should last only as long as the client's + browser session. Additionally `timedelta` objects + are accepted, too. + :param expires: should be a `datetime` object or unix timestamp. + :param path: limits the cookie to a given path, per default it will + span the whole domain. + :param domain: Use this if you want to set a cross-domain cookie. For + example, ``domain="example.com"`` will set a cookie + that is readable by the domain ``www.example.com``, + ``foo.example.com`` etc. Otherwise, a cookie will only + be readable by the domain that set it. + :param secure: The cookie will only be available via HTTPS + :param httponly: disallow JavaScript to access the cookie. This is an + extension to the cookie standard and probably not + supported by all browsers. + :param charset: the encoding for string values. + :param sync_expires: automatically set expires if max_age is defined + but expires not. + :param max_size: Warn if the final header value exceeds this size. The + default, 4093, should be safely `supported by most browsers + `_. Set to 0 to disable this check. + :param samesite: Limits the scope of the cookie such that it will + only be attached to requests if those requests are same-site. + + .. _`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. + + .. versionchanged:: 2.3.1 + The value allows more characters without quoting. + + .. versionchanged:: 2.3 + ``localhost`` and other names without a dot are allowed for the domain. A + leading dot is ignored. + + .. versionchanged:: 2.3 + The ``path`` parameter is ``None`` by default. + + .. versionchanged:: 1.0.0 + The string ``'None'`` is accepted for ``samesite``. + """ + 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="%!$&'()*+,/:=@") + + if domain: + domain = domain.partition(":")[0].lstrip(".").encode("idna").decode("ascii") + + if isinstance(max_age, timedelta): + max_age = int(max_age.total_seconds()) + + if expires is not None: + if not isinstance(expires, str): + expires = http_date(expires) + elif max_age is not None and sync_expires: + expires = http_date(datetime.now(tz=timezone.utc).timestamp() + max_age) + + if samesite is not None: + samesite = samesite.title() + + if samesite not in {"Strict", "Lax", "None"}: + raise ValueError("SameSite must be 'Strict', 'Lax', or 'None'.") + + # Quote value if it contains characters not allowed by RFC 6265. Slash-escape with + # three octal digits, which matches http.cookies, although the RFC suggests base64. + 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() + ).decode("ascii") + value = f'"{value}"' + + # Send a non-ASCII key as mojibake. Everything else should already be ASCII. + # TODO Remove encoding dance, it seems like clients accept UTF-8 keys + buf = [f"{key.encode().decode('latin1')}={value}"] + + for k, v in ( + ("Domain", domain), + ("Expires", expires), + ("Max-Age", max_age), + ("Secure", secure), + ("HttpOnly", httponly), + ("Path", path), + ("SameSite", samesite), + ): + if v is None or v is False: + continue + + if v is True: + buf.append(k) + continue + + buf.append(f"{k}={v}") + + rv = "; ".join(buf) + + # Warn if the final value of the cookie is larger than the limit. If the cookie is + # too large, then it may be silently ignored by the browser, which can be quite hard + # to debug. + cookie_size = len(rv) + + if max_size and cookie_size > max_size: + value_size = len(value) + warnings.warn( + f"The '{key}' cookie is too large: the value was {value_size} bytes but the" + f" header required {cookie_size - value_size} extra bytes. The final size" + f" was {cookie_size} bytes but the limit is {max_size} bytes. Browsers may" + " silently ignore cookies larger than this.", + stacklevel=2, + ) + + return rv + + +def is_byte_range_valid( + start: int | None, stop: int | None, length: int | None +) -> bool: + """Checks if a given byte content range is valid for the given length. + + .. versionadded:: 0.7 + """ + if (start is None) != (stop is None): + return False + elif start is None: + return length is None or length >= 0 + elif length is None: + return 0 <= start < stop # type: ignore + elif start >= stop: # type: ignore + return False + return 0 <= start < length + + +# circular dependencies +from . import datastructures as ds +from .sansio import http as _sansio_http diff --git a/venv/Lib/site-packages/werkzeug/local.py b/venv/Lib/site-packages/werkzeug/local.py new file mode 100644 index 00000000..302589bb --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/local.py @@ -0,0 +1,653 @@ +from __future__ import annotations + +import copy +import math +import operator +import typing as t +from contextvars import ContextVar +from functools import partial +from functools import update_wrapper +from operator import attrgetter + +from .wsgi import ClosingIterator + +if t.TYPE_CHECKING: + from _typeshed.wsgi import StartResponse + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + +T = t.TypeVar("T") +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) + + +def release_local(local: Local | LocalStack[t.Any]) -> None: + """Release the data for the current context in a :class:`Local` or + :class:`LocalStack` without using a :class:`LocalManager`. + + This should not be needed for modern use cases, and may be removed + in the future. + + .. versionadded:: 0.6.1 + """ + local.__release_local__() + + +class Local: + """Create a namespace of context-local data. This wraps a + :class:`ContextVar` containing a :class:`dict` value. + + This may incur a performance penalty compared to using individual + context vars, as it has to copy data to avoid mutating the dict + between nested contexts. + + :param context_var: The :class:`~contextvars.ContextVar` to use as + storage for this local. If not given, one will be created. + Context vars not created at the global scope may interfere with + garbage collection. + + .. versionchanged:: 2.0 + Uses ``ContextVar`` instead of a custom storage implementation. + """ + + __slots__ = ("__storage",) + + def __init__(self, context_var: ContextVar[dict[str, t.Any]] | None = None) -> None: + if context_var is None: + # A ContextVar not created at global scope interferes with + # Python's garbage collection. However, a local only makes + # sense defined at the global scope as well, in which case + # the GC issue doesn't seem relevant. + context_var = ContextVar(f"werkzeug.Local<{id(self)}>.storage") + + object.__setattr__(self, "_Local__storage", context_var) + + def __iter__(self) -> t.Iterator[tuple[str, t.Any]]: + return iter(self.__storage.get({}).items()) + + def __call__( + self, name: str, *, unbound_message: str | None = None + ) -> LocalProxy[t.Any]: + """Create a :class:`LocalProxy` that access an attribute on this + local namespace. + + :param name: Proxy this attribute. + :param unbound_message: The error message that the proxy will + show if the attribute isn't set. + """ + return LocalProxy(self, name, unbound_message=unbound_message) + + def __release_local__(self) -> None: + self.__storage.set({}) + + def __getattr__(self, name: str) -> t.Any: + values = self.__storage.get({}) + + if name in values: + return values[name] + + raise AttributeError(name) + + def __setattr__(self, name: str, value: t.Any) -> None: + values = self.__storage.get({}).copy() + values[name] = value + self.__storage.set(values) + + def __delattr__(self, name: str) -> None: + values = self.__storage.get({}) + + if name in values: + values = values.copy() + del values[name] + self.__storage.set(values) + else: + raise AttributeError(name) + + +class LocalStack(t.Generic[T]): + """Create a stack of context-local data. This wraps a + :class:`ContextVar` containing a :class:`list` value. + + This may incur a performance penalty compared to using individual + context vars, as it has to copy data to avoid mutating the list + between nested contexts. + + :param context_var: The :class:`~contextvars.ContextVar` to use as + storage for this local. If not given, one will be created. + Context vars not created at the global scope may interfere with + garbage collection. + + .. versionchanged:: 2.0 + Uses ``ContextVar`` instead of a custom storage implementation. + + .. versionadded:: 0.6.1 + """ + + __slots__ = ("_storage",) + + def __init__(self, context_var: ContextVar[list[T]] | None = None) -> None: + if context_var is None: + # A ContextVar not created at global scope interferes with + # Python's garbage collection. However, a local only makes + # sense defined at the global scope as well, in which case + # the GC issue doesn't seem relevant. + context_var = ContextVar(f"werkzeug.LocalStack<{id(self)}>.storage") + + self._storage = context_var + + def __release_local__(self) -> None: + self._storage.set([]) + + def push(self, obj: T) -> list[T]: + """Add a new item to the top of the stack.""" + stack = self._storage.get([]).copy() + stack.append(obj) + self._storage.set(stack) + return stack + + def pop(self) -> T | None: + """Remove the top item from the stack and return it. If the + stack is empty, return ``None``. + """ + stack = self._storage.get([]) + + if len(stack) == 0: + return None + + rv = stack[-1] + self._storage.set(stack[:-1]) + return rv + + @property + def top(self) -> T | None: + """The topmost item on the stack. If the stack is empty, + `None` is returned. + """ + stack = self._storage.get([]) + + if len(stack) == 0: + return None + + return stack[-1] + + def __call__( + self, name: str | None = None, *, unbound_message: str | None = None + ) -> LocalProxy[t.Any]: + """Create a :class:`LocalProxy` that accesses the top of this + local stack. + + :param name: If given, the proxy access this attribute of the + top item, rather than the item itself. + :param unbound_message: The error message that the proxy will + show if the stack is empty. + """ + return LocalProxy(self, name, unbound_message=unbound_message) + + +class LocalManager: + """Manage releasing the data for the current context in one or more + :class:`Local` and :class:`LocalStack` objects. + + This should not be needed for modern use cases, and may be removed + in the future. + + :param locals: A local or list of locals to manage. + + .. versionchanged:: 2.1 + The ``ident_func`` was removed. + + .. versionchanged:: 0.7 + The ``ident_func`` parameter was added. + + .. versionchanged:: 0.6.1 + The :func:`release_local` function can be used instead of a + manager. + """ + + __slots__ = ("locals",) + + def __init__( + self, + locals: None + | (Local | LocalStack[t.Any] | t.Iterable[Local | LocalStack[t.Any]]) = None, + ) -> None: + if locals is None: + self.locals = [] + elif isinstance(locals, Local): + self.locals = [locals] + else: + self.locals = list(locals) # type: ignore[arg-type] + + def cleanup(self) -> None: + """Release the data in the locals for this context. Call this at + the end of each request or use :meth:`make_middleware`. + """ + for local in self.locals: + release_local(local) + + def make_middleware(self, app: WSGIApplication) -> WSGIApplication: + """Wrap a WSGI application so that local data is released + automatically after the response has been sent for a request. + """ + + def application( + environ: WSGIEnvironment, start_response: StartResponse + ) -> t.Iterable[bytes]: + return ClosingIterator(app(environ, start_response), self.cleanup) + + return application + + def middleware(self, func: WSGIApplication) -> WSGIApplication: + """Like :meth:`make_middleware` but used as a decorator on the + WSGI application function. + + .. code-block:: python + + @manager.middleware + def application(environ, start_response): + ... + """ + return update_wrapper(self.make_middleware(func), func) + + def __repr__(self) -> str: + return f"<{type(self).__name__} storages: {len(self.locals)}>" + + +class _ProxyLookup: + """Descriptor that handles proxied attribute lookup for + :class:`LocalProxy`. + + :param f: The built-in function this attribute is accessed through. + Instead of looking up the special method, the function call + is redone on the object. + :param fallback: Return this function if the proxy is unbound + instead of raising a :exc:`RuntimeError`. + :param is_attr: This proxied name is an attribute, not a function. + Call the fallback immediately to get the value. + :param class_value: Value to return when accessed from the + ``LocalProxy`` class directly. Used for ``__doc__`` so building + docs still works. + """ + + __slots__ = ("bind_f", "fallback", "is_attr", "class_value", "name") + + def __init__( + self, + f: t.Callable[..., t.Any] | None = None, + fallback: t.Callable[[LocalProxy[t.Any]], t.Any] | None = None, + class_value: t.Any | None = None, + is_attr: bool = False, + ) -> None: + bind_f: t.Callable[[LocalProxy[t.Any], t.Any], t.Callable[..., t.Any]] | None + + if hasattr(f, "__get__"): + # A Python function, can be turned into a bound method. + + def bind_f( + instance: LocalProxy[t.Any], obj: t.Any + ) -> t.Callable[..., t.Any]: + return f.__get__(obj, type(obj)) # type: ignore + + elif f is not None: + # A C function, use partial to bind the first argument. + + def bind_f( + instance: LocalProxy[t.Any], obj: t.Any + ) -> t.Callable[..., t.Any]: + return partial(f, obj) + + else: + # Use getattr, which will produce a bound method. + bind_f = None + + self.bind_f = bind_f + self.fallback = fallback + self.class_value = class_value + self.is_attr = is_attr + + def __set_name__(self, owner: LocalProxy[t.Any], name: str) -> None: + self.name = name + + def __get__(self, instance: LocalProxy[t.Any], owner: type | None = None) -> t.Any: + if instance is None: + if self.class_value is not None: + return self.class_value + + return self + + try: + obj = instance._get_current_object() + except RuntimeError: + if self.fallback is None: + raise + + fallback = self.fallback.__get__(instance, owner) + + if self.is_attr: + # __class__ and __doc__ are attributes, not methods. + # Call the fallback to get the value. + return fallback() + + return fallback + + if self.bind_f is not None: + return self.bind_f(instance, obj) + + return getattr(obj, self.name) + + def __repr__(self) -> str: + return f"proxy {self.name}" + + def __call__( + self, instance: LocalProxy[t.Any], *args: t.Any, **kwargs: t.Any + ) -> t.Any: + """Support calling unbound methods from the class. For example, + this happens with ``copy.copy``, which does + ``type(x).__copy__(x)``. ``type(x)`` can't be proxied, so it + returns the proxy type and descriptor. + """ + return self.__get__(instance, type(instance))(*args, **kwargs) + + +class _ProxyIOp(_ProxyLookup): + """Look up an augmented assignment method on a proxied object. The + method is wrapped to return the proxy instead of the object. + """ + + __slots__ = () + + def __init__( + self, + f: t.Callable[..., t.Any] | None = None, + fallback: t.Callable[[LocalProxy[t.Any]], t.Any] | None = None, + ) -> None: + super().__init__(f, fallback) + + def bind_f(instance: LocalProxy[t.Any], obj: t.Any) -> t.Callable[..., t.Any]: + def i_op(self: t.Any, other: t.Any) -> LocalProxy[t.Any]: + f(self, other) # type: ignore + return instance + + return i_op.__get__(obj, type(obj)) # type: ignore + + self.bind_f = bind_f + + +def _l_to_r_op(op: F) -> F: + """Swap the argument order to turn an l-op into an r-op.""" + + def r_op(obj: t.Any, other: t.Any) -> t.Any: + return op(other, obj) + + return t.cast(F, r_op) + + +def _identity(o: T) -> T: + return o + + +class LocalProxy(t.Generic[T]): + """A proxy to the object bound to a context-local object. All + operations on the proxy are forwarded to the bound object. If no + object is bound, a ``RuntimeError`` is raised. + + :param local: The context-local object that provides the proxied + object. + :param name: Proxy this attribute from the proxied object. + :param unbound_message: The error message to show if the + context-local object is unbound. + + Proxy a :class:`~contextvars.ContextVar` to make it easier to + access. Pass a name to proxy that attribute. + + .. code-block:: python + + _request_var = ContextVar("request") + request = LocalProxy(_request_var) + session = LocalProxy(_request_var, "session") + + Proxy an attribute on a :class:`Local` namespace by calling the + local with the attribute name: + + .. code-block:: python + + data = Local() + user = data("user") + + Proxy the top item on a :class:`LocalStack` by calling the local. + Pass a name to proxy that attribute. + + .. code-block:: + + app_stack = LocalStack() + current_app = app_stack() + g = app_stack("g") + + Pass a function to proxy the return value from that function. This + was previously used to access attributes of local objects before + that was supported directly. + + .. code-block:: python + + session = LocalProxy(lambda: request.session) + + ``__repr__`` and ``__class__`` are proxied, so ``repr(x)`` and + ``isinstance(x, cls)`` will look like the proxied object. Use + ``issubclass(type(x), LocalProxy)`` to check if an object is a + proxy. + + .. code-block:: python + + repr(user) # + isinstance(user, User) # True + issubclass(type(user), LocalProxy) # True + + .. versionchanged:: 2.2.2 + ``__wrapped__`` is set when wrapping an object, not only when + wrapping a function, to prevent doctest from failing. + + .. versionchanged:: 2.2 + Can proxy a ``ContextVar`` or ``LocalStack`` directly. + + .. versionchanged:: 2.2 + The ``name`` parameter can be used with any proxied object, not + only ``Local``. + + .. versionchanged:: 2.2 + Added the ``unbound_message`` parameter. + + .. versionchanged:: 2.0 + Updated proxied attributes and methods to reflect the current + data model. + + .. versionchanged:: 0.6.1 + The class can be instantiated with a callable. + """ + + __slots__ = ("__wrapped", "_get_current_object") + + _get_current_object: t.Callable[[], T] + """Return the current object this proxy is bound to. If the proxy is + unbound, this raises a ``RuntimeError``. + + This should be used if you need to pass the object to something that + doesn't understand the proxy. It can also be useful for performance + if you are accessing the object multiple times in a function, rather + than going through the proxy multiple times. + """ + + def __init__( + self, + local: ContextVar[T] | Local | LocalStack[T] | t.Callable[[], T], + name: str | None = None, + *, + unbound_message: str | None = None, + ) -> None: + if name is None: + get_name = _identity + else: + get_name = attrgetter(name) # type: ignore[assignment] + + if unbound_message is None: + unbound_message = "object is not bound" + + if isinstance(local, Local): + if name is None: + raise TypeError("'name' is required when proxying a 'Local' object.") + + def _get_current_object() -> T: + try: + return get_name(local) # type: ignore[return-value] + except AttributeError: + raise RuntimeError(unbound_message) from None + + elif isinstance(local, LocalStack): + + def _get_current_object() -> T: + obj = local.top + + if obj is None: + raise RuntimeError(unbound_message) + + return get_name(obj) + + elif isinstance(local, ContextVar): + + def _get_current_object() -> T: + try: + obj = local.get() + except LookupError: + raise RuntimeError(unbound_message) from None + + return get_name(obj) + + elif callable(local): + + def _get_current_object() -> T: + return get_name(local()) + + else: + raise TypeError(f"Don't know how to proxy '{type(local)}'.") + + object.__setattr__(self, "_LocalProxy__wrapped", local) + object.__setattr__(self, "_get_current_object", _get_current_object) + + __doc__ = _ProxyLookup( # type: ignore[assignment] + class_value=__doc__, fallback=lambda self: type(self).__doc__, is_attr=True + ) + __wrapped__ = _ProxyLookup( + fallback=lambda self: self._LocalProxy__wrapped, # type: ignore[attr-defined] + is_attr=True, + ) + # __del__ should only delete the proxy + __repr__ = _ProxyLookup( # type: ignore[assignment] + repr, fallback=lambda self: f"<{type(self).__name__} unbound>" + ) + __str__ = _ProxyLookup(str) # type: ignore[assignment] + __bytes__ = _ProxyLookup(bytes) + __format__ = _ProxyLookup() # type: ignore[assignment] + __lt__ = _ProxyLookup(operator.lt) + __le__ = _ProxyLookup(operator.le) + __eq__ = _ProxyLookup(operator.eq) # type: ignore[assignment] + __ne__ = _ProxyLookup(operator.ne) # type: ignore[assignment] + __gt__ = _ProxyLookup(operator.gt) + __ge__ = _ProxyLookup(operator.ge) + __hash__ = _ProxyLookup(hash) # type: ignore[assignment] + __bool__ = _ProxyLookup(bool, fallback=lambda self: False) + __getattr__ = _ProxyLookup(getattr) + # __getattribute__ triggered through __getattr__ + __setattr__ = _ProxyLookup(setattr) # type: ignore[assignment] + __delattr__ = _ProxyLookup(delattr) # type: ignore[assignment] + __dir__ = _ProxyLookup(dir, fallback=lambda self: []) # type: ignore[assignment] + # __get__ (proxying descriptor not supported) + # __set__ (descriptor) + # __delete__ (descriptor) + # __set_name__ (descriptor) + # __objclass__ (descriptor) + # __slots__ used by proxy itself + # __dict__ (__getattr__) + # __weakref__ (__getattr__) + # __init_subclass__ (proxying metaclass not supported) + # __prepare__ (metaclass) + __class__ = _ProxyLookup(fallback=lambda self: type(self), is_attr=True) # type: ignore[assignment] + __instancecheck__ = _ProxyLookup(lambda self, other: isinstance(other, self)) + __subclasscheck__ = _ProxyLookup(lambda self, other: issubclass(other, self)) + # __class_getitem__ triggered through __getitem__ + __call__ = _ProxyLookup(lambda self, *args, **kwargs: self(*args, **kwargs)) + __len__ = _ProxyLookup(len) + __length_hint__ = _ProxyLookup(operator.length_hint) + __getitem__ = _ProxyLookup(operator.getitem) + __setitem__ = _ProxyLookup(operator.setitem) + __delitem__ = _ProxyLookup(operator.delitem) + # __missing__ triggered through __getitem__ + __iter__ = _ProxyLookup(iter) + __next__ = _ProxyLookup(next) + __reversed__ = _ProxyLookup(reversed) + __contains__ = _ProxyLookup(operator.contains) + __add__ = _ProxyLookup(operator.add) + __sub__ = _ProxyLookup(operator.sub) + __mul__ = _ProxyLookup(operator.mul) + __matmul__ = _ProxyLookup(operator.matmul) + __truediv__ = _ProxyLookup(operator.truediv) + __floordiv__ = _ProxyLookup(operator.floordiv) + __mod__ = _ProxyLookup(operator.mod) + __divmod__ = _ProxyLookup(divmod) + __pow__ = _ProxyLookup(pow) + __lshift__ = _ProxyLookup(operator.lshift) + __rshift__ = _ProxyLookup(operator.rshift) + __and__ = _ProxyLookup(operator.and_) + __xor__ = _ProxyLookup(operator.xor) + __or__ = _ProxyLookup(operator.or_) + __radd__ = _ProxyLookup(_l_to_r_op(operator.add)) + __rsub__ = _ProxyLookup(_l_to_r_op(operator.sub)) + __rmul__ = _ProxyLookup(_l_to_r_op(operator.mul)) + __rmatmul__ = _ProxyLookup(_l_to_r_op(operator.matmul)) + __rtruediv__ = _ProxyLookup(_l_to_r_op(operator.truediv)) + __rfloordiv__ = _ProxyLookup(_l_to_r_op(operator.floordiv)) + __rmod__ = _ProxyLookup(_l_to_r_op(operator.mod)) + __rdivmod__ = _ProxyLookup(_l_to_r_op(divmod)) + __rpow__ = _ProxyLookup(_l_to_r_op(pow)) + __rlshift__ = _ProxyLookup(_l_to_r_op(operator.lshift)) + __rrshift__ = _ProxyLookup(_l_to_r_op(operator.rshift)) + __rand__ = _ProxyLookup(_l_to_r_op(operator.and_)) + __rxor__ = _ProxyLookup(_l_to_r_op(operator.xor)) + __ror__ = _ProxyLookup(_l_to_r_op(operator.or_)) + __iadd__ = _ProxyIOp(operator.iadd) + __isub__ = _ProxyIOp(operator.isub) + __imul__ = _ProxyIOp(operator.imul) + __imatmul__ = _ProxyIOp(operator.imatmul) + __itruediv__ = _ProxyIOp(operator.itruediv) + __ifloordiv__ = _ProxyIOp(operator.ifloordiv) + __imod__ = _ProxyIOp(operator.imod) + __ipow__ = _ProxyIOp(operator.ipow) + __ilshift__ = _ProxyIOp(operator.ilshift) + __irshift__ = _ProxyIOp(operator.irshift) + __iand__ = _ProxyIOp(operator.iand) + __ixor__ = _ProxyIOp(operator.ixor) + __ior__ = _ProxyIOp(operator.ior) + __neg__ = _ProxyLookup(operator.neg) + __pos__ = _ProxyLookup(operator.pos) + __abs__ = _ProxyLookup(abs) + __invert__ = _ProxyLookup(operator.invert) + __complex__ = _ProxyLookup(complex) + __int__ = _ProxyLookup(int) + __float__ = _ProxyLookup(float) + __index__ = _ProxyLookup(operator.index) + __round__ = _ProxyLookup(round) + __trunc__ = _ProxyLookup(math.trunc) + __floor__ = _ProxyLookup(math.floor) + __ceil__ = _ProxyLookup(math.ceil) + __enter__ = _ProxyLookup() + __exit__ = _ProxyLookup() + __await__ = _ProxyLookup() + __aiter__ = _ProxyLookup() + __anext__ = _ProxyLookup() + __aenter__ = _ProxyLookup() + __aexit__ = _ProxyLookup() + __copy__ = _ProxyLookup(copy.copy) + __deepcopy__ = _ProxyLookup(copy.deepcopy) + # __getnewargs_ex__ (pickle through proxy not supported) + # __getnewargs__ (pickle) + # __getstate__ (pickle) + # __setstate__ (pickle) + # __reduce__ (pickle) + # __reduce_ex__ (pickle) diff --git a/venv/Lib/site-packages/werkzeug/middleware/__init__.py b/venv/Lib/site-packages/werkzeug/middleware/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/venv/Lib/site-packages/werkzeug/middleware/__pycache__/__init__.cpython-312.pyc b/venv/Lib/site-packages/werkzeug/middleware/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..13e7e2f323aadfeb8c62b48e20ffe7a0f42443b0 GIT binary patch literal 234 zcmX@j%ge<81mEof(m?cM5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!>U6e>2`x@7Dvl{A zEl$meDakL-E6&I)h%e4f%!yAbEzZnKEiQ%&>n3L;mL%nuAOt|_a&%z|bwP4vsd;5F zKAA}|#hE3kx&?{J*@@|?#WCfnMcGxUrRn;)nJFnbspW}9srvEpnR%Hd@$q^EmA^P_ ea`RJ4b5iY!Sb;8M1ma>4<0CU8BV!RWkOcs2gGT-U literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/middleware/__pycache__/dispatcher.cpython-312.pyc b/venv/Lib/site-packages/werkzeug/middleware/__pycache__/dispatcher.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b7367a93e2578309c0863a48690dc7975e080b52 GIT binary patch literal 3352 zcmaJ@-A^3X6`$Fe{TjT03BieR$~8$aTQ5uM$W6PAZ4pdtT$>_8Qe{mvn%%hzW9BRO z&Vm;&QdLj!ne_l)wNp;IoECeNKxfJsL8pMumb0>ykgiE)`hjF-zDz{t%34FoXN?a)xi;qe zwpBF(%X7(9i~B}UU7$?6bnK&PvkMj{j%AuQT{ajcRYr|~a$*o}x$`z9zn-}^NsJ>x zg9RfXW-OVLz|)+%9auhW_5#rL+!0XV)!l%aM4YVED5Gvb-n)I1fO!kNsA*%Q>NyUW zOz#;EN&>5vnH7KyV9HEn%Cs?0$r1kwWEs>^((vkJ!C0ci&?-iCk-8=sn+Bs{Hb{+m zpc+UN2L!QPa>H0MW~$8c0}^@TfyZDvVO||oYOn`iE6iKw;1B~VVS>FbVf3Rq<-#!2 zie2%+GLEVoaSqNbQC5WD(nI~=Q9G#DXK&pU_T88vH4EGoX7B~CZkwb+i4b6tn#W*Q zL&GS+GPg|7GB|4Z)*!@8q%MxvGYNr1Q3QOS)Lj!yj}8@(VVDlM2N~7cEL<5xCIn2d zTK1qD!mTKRXeqp+QMMiBT5E-amr9yO-~qYh(h=a@YC~&SIHkw=D3JT&03JGrE}bjE zVM_-Ag`*6#4OuuJGFS7OHt7?0)j<8 zsZqu35tI71*MOtT7NirfKz##{)he+_dO^b^prt&38m_jiiUb`3!?YN!26lr~8b5Rk zfKeF^*Jeyfoo1Sx}1(lzug8ThXXQ zlYCGDVnd=*S%$8eFy*q6mCR&TihpLxR30SDYW$9~nKso2YB||Rln=d@w9>*OZ%*E*Lm86Zd2e zcb+A|DkLF#8q#BVDxU}ya5+px=H!)7<oN4=A+f z0&m&n3zn~Q$FTKE9d0Sgd3$-J3b#|m3l0}>pFI+996{a^b(iLDT9r9&1$4xRD+Am2 z+%jd0E3`g8dUzd<#{NS$_ZwkG*DcoybbaNOtMLWM4xv_z**`@O`e68tw696Mr`FU@ zGP`|e{$y_@*3>_C8~{gpCXV>kAmVrv;NDpU@Il{yu8u>uz&N;qrsh%BWtPM-oCZ$Ugyd6 z_kZu~cD=aS`+5JT{o7ry?e?ACSbVs+HMHHAUsra!U)&=@U*!IhdsO|~Pj<+~?Gvx8 zy}LfSd%Ayfa`P8kjjhEeFI`xh+|Nl}J?roP!P)PYUO2UJ{^9wJ(TAg3Lyunj>+s{@ zot`(>#`gw>KEMCz{YRI+8r&JU^dxg;_w2>5Qrl-Qucg;>+nFEj4h#d;v7I^dPZ0Ii z=G0Et`6rq4tQ#C)_ywF#MMaMo6bV>4Yg*Od4i8vCG{Yk|$%mv#raYHIj%ZM4Zm8lL zNI)zz#1%V@16C6wFsz&mijx6lM#ZLgDh>Fq{V<=xNMjCkJ=ApFfe#29=@YvCQQfdx zoV2c+URBpw7s?ud;W5+_(wHoU=@UZ%2z~>G2hw+mbTYI5l63Ls`{`8j)xF%A{Upqg zq>~rK%6>-DIuEkA*8M`VuzPA~U%~lj6Jp-Wp8j_d-Xk@rA>YAXhY8KB)xlvflVM*B zQ$fRrjvUHCn4A4zdO{z+J~95w$*F5$rw%O27pPfW=JQq*3K3fo9%1(oh&c4+Y3A)H z2A3H+gT=u|VAxk=S^h3DAglj*N0#JQzmaweYoCg#Y)c*X6>$Bp8Ia F{{p3qz9j$v literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/middleware/__pycache__/http_proxy.cpython-312.pyc b/venv/Lib/site-packages/werkzeug/middleware/__pycache__/http_proxy.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c1a52a091dcfc40df6b3ba4fda18a2fa23f3b3f3 GIT binary patch literal 9444 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 b)N+nxTE8V&(i#323@|2# literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/middleware/__pycache__/lint.cpython-312.pyc b/venv/Lib/site-packages/werkzeug/middleware/__pycache__/lint.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2eefeb0001889468186dd22b69d7a61a9586e843 GIT binary patch literal 17840 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/middleware/__pycache__/profiler.cpython-312.pyc b/venv/Lib/site-packages/werkzeug/middleware/__pycache__/profiler.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f350c99ebe9e0799baa2dac44c1e8837986f1853 GIT binary patch literal 7238 zcmc&(U2GdiexD_m-z!lcjy^0Zn%F)|Oe9)~?KozgoLxtWb?G^}ldrj`4#*XEDN$Z> zd9zDPR4BlAHDKy+Xlw)r$;cJfz4UDO(B!Q^pK@;n`eN2L#BLna!1bkX%sFiiMj!hB z&+d|xt+Xysplf+P|MQ!f|JVFy_>UbOVFKSbZ_H%>{y9Q^j}`Y5?Sk;=b0};Oh0GB} zP&}HD_sn?&EPFNYoXB(EoR8=JIX}+>b3w>OEtC(>g#{8qYM&O#OLLMy)Zo(d?j)_8 z^#F}u>&kb}b#wFplzZlScsU5=-nm|Zc*xsC30)>i__oI$JGZCo-A~kx1B8V*txxN; z=Xsy};?-rc;(ebi3-O42Tp%QTs#w%=8QIL~1v4JQF1na((Kr5(To z<>(o?Ko?c5NCCTG=CWlvubZZ(7SzlIgXRjftd|(g=!zQVbLC~AP^qEm%ele=RrF5e_F3R4$o%Mw1OAnQZR!`^}Yy!^wJ6gZ{i*iP`Bn#(~H*_5fR?zjs>z6e`@IkSQ08r>m$uRYNZWX}h%d`k;i|U9 zA;Rd&8t`ZWtXFlnMeX)Kp>I!}8>chx&O)l1nFPmoh8AUW@#%rsN_9p@nXa3o)L7I@ zn!;HJCI<4)86bZYzk{M`a?w!RR^T*H&>fY6(&27g2L#R|4-Cl}YDO<8#ttB)Z!Kq+ z(Ji%e`J4gwfGpX;X>0^GyS~mgk#t(M%^Da5Cc-xWQc+impd7ZgFx4!v0!Yu5uOmkE ziYlQ zQRWyeNN}tLQ_dAo*LGEeF>Z*C)0GtkfPk$)%7L;a*o7KT^2-00h>}H_$$9ikNzS~d zyc8JcGB_|>{tmV6Ns{zEa1vm|=sRp929vV;67=2>Peg6G(E$~K3Xq_e1KP77^wf86N-znV^O2X9=(b{todzQr9Fau>A? z{Ls^w6g{Fb&RL9H0vZ_{q-}R#*Jv|1SmuPBqi9u{T(H9#d0xM$I+hC&07eZH^O~Ny z03krJ48E&iyEDYjGCfb5cGC)JnkGbZ`63E%#)*+yXJT`GpmX%~T?odYBW_Ox%ebgA z11vbRC>It~1(tF+G1-JT#cc|0@4qu+&T1rif~#YWpNOZ^O+;=EDk?(LE1^TNJ*VxF zppGsNDz?9GvFxd}P9|PH+Jxq(>yE=!g+rL;@N8~V8Nh8F&=@1wXIv9TaSUt6L1$at z>#mh?;uiC`>jBp7SdPuRri)bMb54s za!hx~j?eCV(_nV8J3<&DILIDl8UT4e%lK^q3|UP6RNKjtA`MW~Qev%BJf8bs^% zSMfs43HAx8h){!(PM;5I&P4_K{7`d(M?%WvmT)%ix5N}Eq!j??g7K3TOmSB;U$#0@ z?Yp<6RNKC-uGACS77tjWp=w#ni*SAiE#QJlJFp_{P|+ul3}ggaA$*V5Pbbg+$N(cb zUxYYYJ8wcvZ!G4DDI+gysd)@xRKsXCPk8+rsO81;f0mm+Z-9TF zD9V`&@`7rdckTU9%ia$;>tu;y890-I!_Z8nR)=PuGW&$v`93V(co~vQq|rm38>ouE z2-W+CA(QF@Ban5}hvQZ8UT|+cHj4RRogS)+_k+WaP-dB??4W{-;94*MGQ2{r2vd8Xbe>wE?h_a^|sS40SvZCU-EUkIXriR=`VEEQVvx&ac)EK)D z(a+De%sNUC*&cO&~A?*Fp|1B zt^BWuK+OGaUFrN5{HTjBfQ**D<*`ah6_q1Jeq{UFFhOZCbSNR0vsWp+m89e#=T3e z`J1I#!cd{%{lO1TTrDVZA9`}xEE!f{QI!?&!&XpT$$%rw>Q)E%bpD2sn%9-G)d`dk zq`Hj;E(BcorArKDZuP^nQf3h%Gz??g=vGhc>P_(+O{EeiAVPy1%^Qht8C?KqV+fKz zULyZ2?cIEJ>)6dR_oSosUb@{oTI(IXBaN~V=+D6a=7+9}^XPA(@Jn$6IJDGsmrW|N z?rC!-&11CHuL$5I97pQrYslMOTw{f_jKhRy6Z@;c+l)Jsbyvtz)7`lgEw?PdkzFQn z?^))DPKY!#9iET;tP5*`X^9!xFfG5j0uLJs$V)b4(`4*L=mt3kL82)*?-)Z3v;t0W z$WTieItE)_m*BJIUQ@n55TS686TJv>;MzUIQ7PRq+n4EeyR2 z8^r{J9V_5ON;o-Kw62 zsru@{=tt4@=%)W(aHJmX`DkT*WwYaM^k7wd(9^dOzZU=ar7d5r=R4KF!$@>H601dG zcOqjCqWzoV_P|7KVB%gh@o?ne&A!^mp$9Q~WBU5^C+~da^G^0$4cr-csn#{#2$C4x z9-6ETO@7vYcj)-F;KQL2yS_bitTuG4KD6)7{=>DQBlSVLHB=j%*d9Dw8$A5k>AQok zZ4aKT4W9hG_w&r%!SDUk;JJUu)?@p(vbEUwgTdh&gVzT?8ES+`?6pV47wfqi{8}Od zdpDM^EpJq=Rkq&TK5(>l;OJ*7cl%FP&(x*fZRz=%^!(<^J!uSf^U;a*6L%u}AM6?2 z9KCtq)A3v5_x2pCPSvGYb!uCR)uh;_awC5|Uz5fkNZnV*w)Xrc_UqVvDFHF;jmY)L z);pi|+#NVloyOj`Hji&k-02y+X?|L{Rk<&ns7u2LGhCB~H_h#lmuv7Ry@I4azWTk* z`KyO+il0iir2Ep*hh2NB7pooIyyKm$AN2{Hr)_&(W@7r?zW{CifL1741jDFfH44q( z$WZZlC&Nf%b$Dkhi~z-jG^^>d`2oW-Vl@m{E>sK}EH5;$*P+DTfCQW=S8I$!ZQVvs z%llWm+u-NRrfPf;58{;oUnNp0E1XK%zlA_qN~L~Mk~OC%kV+|f2AaJ_*}&se(Mu-7 zUwGILFhRw)eH44?0LvJVQzUqW)3eC0Xf3pSWQ4vhXL`RinT|B?EuuhsV*tdGnz zqMg3UhaIto4{}ib?iU&XECor=3ylz#!leI5BZ4J~^wCBKmO9DaVLr0aMZ%qry0MME z;_IvTA8dFre{G8A4?}yu_CeiVA1)1i(w{?y`?VkVQYnT}g%!|^1oxm*P-E|5g09)} zo8=-L`s^iMvEQ_8zY}zS_2FL5icIq-l_|z_#_$ZWI?la6GnG1hX6p1i(`VndI#U3p z8jGruST+`N{1mj6h0BXGZYMJz2T#~Sd4tISh$p;(7wU#r5QMKhk|6$jj0pRGPddLK z;V($!3)1r?8EA-Jfqp4c;GyWd^4*_(_e*j7OECd|-}uXd(Dw&|=^q=tB-r~0A_(+f c#1TZKj}3IHTmDl~^1Eo{)Ufw=F#*#50-qL;+yDRo literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/middleware/__pycache__/proxy_fix.cpython-312.pyc b/venv/Lib/site-packages/werkzeug/middleware/__pycache__/proxy_fix.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a62fba8e9fa247a09a00a6b8c8ab18902826a1f GIT binary patch literal 7235 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 None: + self.app = app + self.mounts = mounts or {} + + def __call__( + self, environ: WSGIEnvironment, start_response: StartResponse + ) -> t.Iterable[bytes]: + script = environ.get("PATH_INFO", "") + path_info = "" + + while "/" in script: + if script in self.mounts: + app = self.mounts[script] + break + + script, last_item = script.rsplit("/", 1) + path_info = f"/{last_item}{path_info}" + else: + app = self.mounts.get(script, self.app) + + original_script_name = environ.get("SCRIPT_NAME", "") + environ["SCRIPT_NAME"] = original_script_name + script + environ["PATH_INFO"] = path_info + return app(environ, start_response) diff --git a/venv/Lib/site-packages/werkzeug/middleware/http_proxy.py b/venv/Lib/site-packages/werkzeug/middleware/http_proxy.py new file mode 100644 index 00000000..5e239156 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/middleware/http_proxy.py @@ -0,0 +1,236 @@ +""" +Basic HTTP Proxy +================ + +.. autoclass:: ProxyMiddleware + +:copyright: 2007 Pallets +:license: BSD-3-Clause +""" + +from __future__ import annotations + +import typing as t +from http import client +from urllib.parse import quote +from urllib.parse import urlsplit + +from ..datastructures import EnvironHeaders +from ..http import is_hop_by_hop_header +from ..wsgi import get_input_stream + +if t.TYPE_CHECKING: + from _typeshed.wsgi import StartResponse + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + + +class ProxyMiddleware: + """Proxy requests under a path to an external server, routing other + requests to the app. + + This middleware can only proxy HTTP requests, as HTTP is the only + protocol handled by the WSGI server. Other protocols, such as + WebSocket requests, cannot be proxied at this layer. This should + only be used for development, in production a real proxy server + should be used. + + The middleware takes a dict mapping a path prefix to a dict + describing the host to be proxied to:: + + app = ProxyMiddleware(app, { + "/static/": { + "target": "http://127.0.0.1:5001/", + } + }) + + Each host has the following options: + + ``target``: + The target URL to dispatch to. This is required. + ``remove_prefix``: + Whether to remove the prefix from the URL before dispatching it + to the target. The default is ``False``. + ``host``: + ``""`` (default): + The host header is automatically rewritten to the URL of the + target. + ``None``: + The host header is unmodified from the client request. + Any other value: + The host header is overwritten with the value. + ``headers``: + A dictionary of headers to be sent with the request to the + target. The default is ``{}``. + ``ssl_context``: + A :class:`ssl.SSLContext` defining how to verify requests if the + target is HTTPS. The default is ``None``. + + In the example above, everything under ``"/static/"`` is proxied to + the server on port 5001. The host header is rewritten to the target, + and the ``"/static/"`` prefix is removed from the URLs. + + :param app: The WSGI application to wrap. + :param targets: Proxy target configurations. See description above. + :param chunk_size: Size of chunks to read from input stream and + write to target. + :param timeout: Seconds before an operation to a target fails. + + .. versionadded:: 0.14 + """ + + def __init__( + self, + app: WSGIApplication, + targets: t.Mapping[str, dict[str, t.Any]], + chunk_size: int = 2 << 13, + timeout: int = 10, + ) -> None: + def _set_defaults(opts: dict[str, t.Any]) -> dict[str, t.Any]: + opts.setdefault("remove_prefix", False) + opts.setdefault("host", "") + opts.setdefault("headers", {}) + opts.setdefault("ssl_context", None) + return opts + + self.app = app + self.targets = { + f"/{k.strip('/')}/": _set_defaults(v) for k, v in targets.items() + } + self.chunk_size = chunk_size + self.timeout = timeout + + def proxy_to( + self, opts: dict[str, t.Any], path: str, prefix: str + ) -> WSGIApplication: + target = urlsplit(opts["target"]) + # socket can handle unicode host, but header must be ascii + host = target.hostname.encode("idna").decode("ascii") + + def application( + environ: WSGIEnvironment, start_response: StartResponse + ) -> t.Iterable[bytes]: + headers = list(EnvironHeaders(environ).items()) + headers[:] = [ + (k, v) + for k, v in headers + if not is_hop_by_hop_header(k) + and k.lower() not in ("content-length", "host") + ] + headers.append(("Connection", "close")) + + if opts["host"] == "": + headers.append(("Host", host)) + elif opts["host"] is None: + headers.append(("Host", environ["HTTP_HOST"])) + else: + headers.append(("Host", opts["host"])) + + headers.extend(opts["headers"].items()) + remote_path = path + + if opts["remove_prefix"]: + remote_path = remote_path[len(prefix) :].lstrip("/") + remote_path = f"{target.path.rstrip('/')}/{remote_path}" + + content_length = environ.get("CONTENT_LENGTH") + chunked = False + + if content_length not in ("", None): + headers.append(("Content-Length", content_length)) # type: ignore + elif content_length is not None: + headers.append(("Transfer-Encoding", "chunked")) + chunked = True + + try: + if target.scheme == "http": + con = client.HTTPConnection( + host, target.port or 80, timeout=self.timeout + ) + elif target.scheme == "https": + con = client.HTTPSConnection( + host, + target.port or 443, + timeout=self.timeout, + context=opts["ssl_context"], + ) + else: + raise RuntimeError( + "Target scheme must be 'http' or 'https', got" + f" {target.scheme!r}." + ) + + con.connect() + # safe = https://url.spec.whatwg.org/#url-path-segment-string + # as well as percent for things that are already quoted + remote_url = quote(remote_path, safe="!$&'()*+,/:;=@%") + querystring = environ["QUERY_STRING"] + + if querystring: + remote_url = f"{remote_url}?{querystring}" + + con.putrequest(environ["REQUEST_METHOD"], remote_url, skip_host=True) + + for k, v in headers: + if k.lower() == "connection": + v = "close" + + con.putheader(k, v) + + con.endheaders() + stream = get_input_stream(environ) + + while True: + data = stream.read(self.chunk_size) + + if not data: + break + + if chunked: + con.send(b"%x\r\n%s\r\n" % (len(data), data)) + else: + con.send(data) + + resp = con.getresponse() + except OSError: + from ..exceptions import BadGateway + + return BadGateway()(environ, start_response) + + start_response( + f"{resp.status} {resp.reason}", + [ + (k.title(), v) + for k, v in resp.getheaders() + if not is_hop_by_hop_header(k) + ], + ) + + def read() -> t.Iterator[bytes]: + while True: + try: + data = resp.read(self.chunk_size) + except OSError: + break + + if not data: + break + + yield data + + return read() + + return application + + def __call__( + self, environ: WSGIEnvironment, start_response: StartResponse + ) -> t.Iterable[bytes]: + path = environ["PATH_INFO"] + app = self.app + + for prefix, opts in self.targets.items(): + if path.startswith(prefix): + app = self.proxy_to(opts, path, prefix) + break + + return app(environ, start_response) diff --git a/venv/Lib/site-packages/werkzeug/middleware/lint.py b/venv/Lib/site-packages/werkzeug/middleware/lint.py new file mode 100644 index 00000000..de93b526 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/middleware/lint.py @@ -0,0 +1,439 @@ +""" +WSGI Protocol Linter +==================== + +This module provides a middleware that performs sanity checks on the +behavior of the WSGI server and application. It checks that the +:pep:`3333` WSGI spec is properly implemented. It also warns on some +common HTTP errors such as non-empty responses for 304 status codes. + +.. autoclass:: LintMiddleware + +:copyright: 2007 Pallets +:license: BSD-3-Clause +""" + +from __future__ import annotations + +import typing as t +from types import TracebackType +from urllib.parse import urlparse +from warnings import warn + +from ..datastructures import Headers +from ..http import is_entity_header +from ..wsgi import FileWrapper + +if t.TYPE_CHECKING: + from _typeshed.wsgi import StartResponse + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + + +class WSGIWarning(Warning): + """Warning class for WSGI warnings.""" + + +class HTTPWarning(Warning): + """Warning class for HTTP warnings.""" + + +def check_type(context: str, obj: object, need: type = str) -> None: + if type(obj) is not need: + warn( + f"{context!r} requires {need.__name__!r}, got {type(obj).__name__!r}.", + WSGIWarning, + stacklevel=3, + ) + + +class InputStream: + def __init__(self, stream: t.IO[bytes]) -> None: + self._stream = stream + + def read(self, *args: t.Any) -> bytes: + if len(args) == 0: + warn( + "WSGI does not guarantee an EOF marker on the input stream, thus making" + " calls to 'wsgi.input.read()' unsafe. Conforming servers may never" + " return from this call.", + WSGIWarning, + stacklevel=2, + ) + elif len(args) != 1: + warn( + "Too many parameters passed to 'wsgi.input.read()'.", + WSGIWarning, + stacklevel=2, + ) + return self._stream.read(*args) + + def readline(self, *args: t.Any) -> bytes: + if len(args) == 0: + warn( + "Calls to 'wsgi.input.readline()' without arguments are unsafe. Use" + " 'wsgi.input.read()' instead.", + WSGIWarning, + stacklevel=2, + ) + elif len(args) == 1: + warn( + "'wsgi.input.readline()' was called with a size hint. WSGI does not" + " support this, although it's available on all major servers.", + WSGIWarning, + stacklevel=2, + ) + else: + raise TypeError("Too many arguments passed to 'wsgi.input.readline()'.") + return self._stream.readline(*args) + + def __iter__(self) -> t.Iterator[bytes]: + try: + return iter(self._stream) + except TypeError: + warn("'wsgi.input' is not iterable.", WSGIWarning, stacklevel=2) + return iter(()) + + def close(self) -> None: + warn("The application closed the input stream!", WSGIWarning, stacklevel=2) + self._stream.close() + + +class ErrorStream: + def __init__(self, stream: t.IO[str]) -> None: + self._stream = stream + + def write(self, s: str) -> None: + check_type("wsgi.error.write()", s, str) + self._stream.write(s) + + def flush(self) -> None: + self._stream.flush() + + def writelines(self, seq: t.Iterable[str]) -> None: + for line in seq: + self.write(line) + + def close(self) -> None: + warn("The application closed the error stream!", WSGIWarning, stacklevel=2) + self._stream.close() + + +class GuardedWrite: + def __init__(self, write: t.Callable[[bytes], object], chunks: list[int]) -> None: + self._write = write + self._chunks = chunks + + def __call__(self, s: bytes) -> None: + check_type("write()", s, bytes) + self._write(s) + self._chunks.append(len(s)) + + +class GuardedIterator: + def __init__( + self, + iterator: t.Iterable[bytes], + headers_set: tuple[int, Headers], + chunks: list[int], + ) -> None: + self._iterator = iterator + self._next = iter(iterator).__next__ + self.closed = False + self.headers_set = headers_set + self.chunks = chunks + + def __iter__(self) -> GuardedIterator: + return self + + def __next__(self) -> bytes: + if self.closed: + warn("Iterated over closed 'app_iter'.", WSGIWarning, stacklevel=2) + + rv = self._next() + + if not self.headers_set: + warn( + "The application returned before it started the response.", + WSGIWarning, + stacklevel=2, + ) + + check_type("application iterator items", rv, bytes) + self.chunks.append(len(rv)) + return rv + + def close(self) -> None: + self.closed = True + + if hasattr(self._iterator, "close"): + self._iterator.close() + + if self.headers_set: + status_code, headers = self.headers_set + bytes_sent = sum(self.chunks) + content_length = headers.get("content-length", type=int) + + if status_code == 304: + for key, _value in headers: + key = key.lower() + if key not in ("expires", "content-location") and is_entity_header( + key + ): + warn( + f"Entity header {key!r} found in 304 response.", + HTTPWarning, + stacklevel=2, + ) + if bytes_sent: + warn( + "304 responses must not have a body.", + HTTPWarning, + stacklevel=2, + ) + elif 100 <= status_code < 200 or status_code == 204: + if content_length != 0: + warn( + f"{status_code} responses must have an empty content length.", + HTTPWarning, + stacklevel=2, + ) + if bytes_sent: + warn( + f"{status_code} responses must not have a body.", + HTTPWarning, + stacklevel=2, + ) + elif content_length is not None and content_length != bytes_sent: + warn( + "Content-Length and the number of bytes sent to the" + " client do not match.", + WSGIWarning, + stacklevel=2, + ) + + def __del__(self) -> None: + if not self.closed: + try: + warn( + "Iterator was garbage collected before it was closed.", + WSGIWarning, + stacklevel=2, + ) + except Exception: + pass + + +class LintMiddleware: + """Warns about common errors in the WSGI and HTTP behavior of the + server and wrapped application. Some of the issues it checks are: + + - invalid status codes + - non-bytes sent to the WSGI server + - strings returned from the WSGI application + - non-empty conditional responses + - unquoted etags + - relative URLs in the Location header + - unsafe calls to wsgi.input + - unclosed iterators + + Error information is emitted using the :mod:`warnings` module. + + :param app: The WSGI application to wrap. + + .. code-block:: python + + from werkzeug.middleware.lint import LintMiddleware + app = LintMiddleware(app) + """ + + def __init__(self, app: WSGIApplication) -> None: + self.app = app + + def check_environ(self, environ: WSGIEnvironment) -> None: + if type(environ) is not dict: # noqa: E721 + warn( + "WSGI environment is not a standard Python dict.", + WSGIWarning, + stacklevel=4, + ) + for key in ( + "REQUEST_METHOD", + "SERVER_NAME", + "SERVER_PORT", + "wsgi.version", + "wsgi.input", + "wsgi.errors", + "wsgi.multithread", + "wsgi.multiprocess", + "wsgi.run_once", + ): + if key not in environ: + warn( + f"Required environment key {key!r} not found", + WSGIWarning, + stacklevel=3, + ) + if environ["wsgi.version"] != (1, 0): + warn("Environ is not a WSGI 1.0 environ.", WSGIWarning, stacklevel=3) + + script_name = environ.get("SCRIPT_NAME", "") + path_info = environ.get("PATH_INFO", "") + + if script_name and script_name[0] != "/": + warn( + f"'SCRIPT_NAME' does not start with a slash: {script_name!r}", + WSGIWarning, + stacklevel=3, + ) + + if path_info and path_info[0] != "/": + warn( + f"'PATH_INFO' does not start with a slash: {path_info!r}", + WSGIWarning, + stacklevel=3, + ) + + def check_start_response( + self, + status: str, + headers: list[tuple[str, str]], + exc_info: None | (tuple[type[BaseException], BaseException, TracebackType]), + ) -> tuple[int, Headers]: + check_type("status", status, str) + status_code_str = status.split(None, 1)[0] + + if len(status_code_str) != 3 or not status_code_str.isdecimal(): + warn("Status code must be three digits.", WSGIWarning, stacklevel=3) + + if len(status) < 4 or status[3] != " ": + warn( + f"Invalid value for status {status!r}. Valid status strings are three" + " digits, a space and a status explanation.", + WSGIWarning, + stacklevel=3, + ) + + status_code = int(status_code_str) + + if status_code < 100: + warn("Status code < 100 detected.", WSGIWarning, stacklevel=3) + + if type(headers) is not list: # noqa: E721 + warn("Header list is not a list.", WSGIWarning, stacklevel=3) + + for item in headers: + if type(item) is not tuple or len(item) != 2: + warn("Header items must be 2-item tuples.", WSGIWarning, stacklevel=3) + name, value = item + if type(name) is not str or type(value) is not str: # noqa: E721 + warn( + "Header keys and values must be strings.", WSGIWarning, stacklevel=3 + ) + if name.lower() == "status": + warn( + "The status header is not supported due to" + " conflicts with the CGI spec.", + WSGIWarning, + stacklevel=3, + ) + + if exc_info is not None and not isinstance(exc_info, tuple): + warn("Invalid value for exc_info.", WSGIWarning, stacklevel=3) + + headers_obj = Headers(headers) + self.check_headers(headers_obj) + + return status_code, headers_obj + + def check_headers(self, headers: Headers) -> None: + etag = headers.get("etag") + + if etag is not None: + if etag.startswith(("W/", "w/")): + if etag.startswith("w/"): + warn( + "Weak etag indicator should be upper case.", + HTTPWarning, + stacklevel=4, + ) + + etag = etag[2:] + + if not (etag[:1] == etag[-1:] == '"'): + warn("Unquoted etag emitted.", HTTPWarning, stacklevel=4) + + location = headers.get("location") + + if location is not None: + if not urlparse(location).netloc: + warn( + "Absolute URLs required for location header.", + HTTPWarning, + stacklevel=4, + ) + + def check_iterator(self, app_iter: t.Iterable[bytes]) -> None: + if isinstance(app_iter, str): + warn( + "The application returned a string. The response will send one" + " character at a time to the client, which will kill performance." + " Return a list or iterable instead.", + WSGIWarning, + stacklevel=3, + ) + + def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Iterable[bytes]: + if len(args) != 2: + warn("A WSGI app takes two arguments.", WSGIWarning, stacklevel=2) + + if kwargs: + warn( + "A WSGI app does not take keyword arguments.", WSGIWarning, stacklevel=2 + ) + + environ: WSGIEnvironment = args[0] + start_response: StartResponse = args[1] + + self.check_environ(environ) + environ["wsgi.input"] = InputStream(environ["wsgi.input"]) + environ["wsgi.errors"] = ErrorStream(environ["wsgi.errors"]) + + # Hook our own file wrapper in so that applications will always + # iterate to the end and we can check the content length. + environ["wsgi.file_wrapper"] = FileWrapper + + headers_set: list[t.Any] = [] + chunks: list[int] = [] + + def checking_start_response( + *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[bytes], None]: + if len(args) not in {2, 3}: + warn( + f"Invalid number of arguments: {len(args)}, expected 2 or 3.", + WSGIWarning, + stacklevel=2, + ) + + if kwargs: + warn( + "'start_response' does not take keyword arguments.", + WSGIWarning, + stacklevel=2, + ) + + status: str = args[0] + headers: list[tuple[str, str]] = args[1] + exc_info: ( + None | (tuple[type[BaseException], BaseException, TracebackType]) + ) = args[2] if len(args) == 3 else None + + headers_set[:] = self.check_start_response(status, headers, exc_info) + return GuardedWrite(start_response(status, headers, exc_info), chunks) + + app_iter = self.app(environ, t.cast("StartResponse", checking_start_response)) + self.check_iterator(app_iter) + return GuardedIterator( + app_iter, t.cast(t.Tuple[int, Headers], headers_set), chunks + ) diff --git a/venv/Lib/site-packages/werkzeug/middleware/profiler.py b/venv/Lib/site-packages/werkzeug/middleware/profiler.py new file mode 100644 index 00000000..112b8777 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/middleware/profiler.py @@ -0,0 +1,155 @@ +""" +Application Profiler +==================== + +This module provides a middleware that profiles each request with the +:mod:`cProfile` module. This can help identify bottlenecks in your code +that may be slowing down your application. + +.. autoclass:: ProfilerMiddleware + +:copyright: 2007 Pallets +:license: BSD-3-Clause +""" + +from __future__ import annotations + +import os.path +import sys +import time +import typing as t +from pstats import Stats + +try: + from cProfile import Profile +except ImportError: + from profile import Profile # type: ignore + +if t.TYPE_CHECKING: + from _typeshed.wsgi import StartResponse + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + + +class ProfilerMiddleware: + """Wrap a WSGI application and profile the execution of each + request. Responses are buffered so that timings are more exact. + + If ``stream`` is given, :class:`pstats.Stats` are written to it + after each request. If ``profile_dir`` is given, :mod:`cProfile` + data files are saved to that directory, one file per request. + + The filename can be customized by passing ``filename_format``. If + it is a string, it will be formatted using :meth:`str.format` with + the following fields available: + + - ``{method}`` - The request method; GET, POST, etc. + - ``{path}`` - The request path or 'root' should one not exist. + - ``{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`` 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``. + :param sort_by: A tuple of columns to sort stats by. See + :meth:`pstats.Stats.sort_stats`. + :param restrictions: A tuple of restrictions to filter stats by. See + :meth:`pstats.Stats.print_stats`. + :param profile_dir: Save profile data files to this directory. + :param filename_format: Format string for profile data file names, + or a callable returning a name. See explanation above. + + .. code-block:: python + + 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``. + + .. versionadded:: 0.15 + Added ``filename_format``. + + .. versionadded:: 0.9 + Added ``restrictions`` and ``profile_dir``. + """ + + def __init__( + self, + app: WSGIApplication, + stream: t.IO[str] | None = sys.stdout, + sort_by: t.Iterable[str] = ("time", "calls"), + restrictions: t.Iterable[str | int | float] = (), + profile_dir: str | None = None, + filename_format: str = "{method}.{path}.{elapsed:.0f}ms.{time:.0f}.prof", + ) -> None: + self._app = app + self._stream = stream + self._sort_by = sort_by + self._restrictions = restrictions + self._profile_dir = profile_dir + self._filename_format = filename_format + + def __call__( + self, environ: WSGIEnvironment, start_response: StartResponse + ) -> t.Iterable[bytes]: + response_body: list[bytes] = [] + + def catching_start_response(status, headers, exc_info=None): # type: ignore + start_response(status, headers, exc_info) + return response_body.append + + def runapp() -> None: + app_iter = self._app( + environ, t.cast("StartResponse", catching_start_response) + ) + response_body.extend(app_iter) + + if hasattr(app_iter, "close"): + app_iter.close() + + profile = Profile() + start = time.time() + profile.runcall(runapp) + body = b"".join(response_body) + elapsed = time.time() - start + + 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( + method=environ["REQUEST_METHOD"], + path=environ["PATH_INFO"].strip("/").replace("/", ".") or "root", + elapsed=elapsed * 1000.0, + time=time.time(), + ) + filename = os.path.join(self._profile_dir, filename) + profile.dump_stats(filename) + + if self._stream is not None: + stats = Stats(profile, stream=self._stream) + stats.sort_stats(*self._sort_by) + print("-" * 80, file=self._stream) + path_info = environ.get("PATH_INFO", "") + print(f"PATH: {path_info!r}", file=self._stream) + stats.print_stats(*self._restrictions) + print(f"{'-' * 80}\n", file=self._stream) + + return [body] diff --git a/venv/Lib/site-packages/werkzeug/middleware/proxy_fix.py b/venv/Lib/site-packages/werkzeug/middleware/proxy_fix.py new file mode 100644 index 00000000..cbf4e0ba --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/middleware/proxy_fix.py @@ -0,0 +1,183 @@ +""" +X-Forwarded-For Proxy Fix +========================= + +This module provides a middleware that adjusts the WSGI environ based on +``X-Forwarded-`` headers that proxies in front of an application may +set. + +When an application is running behind a proxy server, WSGI may see the +request as coming from that server rather than the real client. Proxies +set various headers to track where the request actually came from. + +This middleware should only be used if the application is actually +behind such a proxy, and should be configured with the number of proxies +that are chained in front of it. Not all proxies set all the headers. +Since incoming headers can be faked, you must set how many proxies are +setting each header so the middleware knows what to trust. + +.. autoclass:: ProxyFix + +:copyright: 2007 Pallets +:license: BSD-3-Clause +""" + +from __future__ import annotations + +import typing as t + +from ..http import parse_list_header + +if t.TYPE_CHECKING: + from _typeshed.wsgi import StartResponse + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + + +class ProxyFix: + """Adjust the WSGI environ based on ``X-Forwarded-`` that proxies in + front of the application may set. + + - ``X-Forwarded-For`` sets ``REMOTE_ADDR``. + - ``X-Forwarded-Proto`` sets ``wsgi.url_scheme``. + - ``X-Forwarded-Host`` sets ``HTTP_HOST``, ``SERVER_NAME``, and + ``SERVER_PORT``. + - ``X-Forwarded-Port`` sets ``HTTP_HOST`` and ``SERVER_PORT``. + - ``X-Forwarded-Prefix`` sets ``SCRIPT_NAME``. + + You must tell the middleware how many proxies set each header so it + knows what values to trust. It is a security issue to trust values + that came from the client rather than a proxy. + + The original values of the headers are stored in the WSGI + environ as ``werkzeug.proxy_fix.orig``, a dict. + + :param app: The WSGI application to wrap. + :param x_for: Number of values to trust for ``X-Forwarded-For``. + :param x_proto: Number of values to trust for ``X-Forwarded-Proto``. + :param x_host: Number of values to trust for ``X-Forwarded-Host``. + :param x_port: Number of values to trust for ``X-Forwarded-Port``. + :param x_prefix: Number of values to trust for + ``X-Forwarded-Prefix``. + + .. code-block:: python + + from werkzeug.middleware.proxy_fix import ProxyFix + # App is behind one proxy that sets the -For and -Host headers. + app = ProxyFix(app, x_for=1, x_host=1) + + .. versionchanged:: 1.0 + The ``num_proxies`` argument and attribute; the ``get_remote_addr`` method; and + the environ keys ``orig_remote_addr``, ``orig_wsgi_url_scheme``, and + ``orig_http_host`` were removed. + + .. versionchanged:: 0.15 + All headers support multiple values. Each header is configured with a separate + number of trusted proxies. + + .. versionchanged:: 0.15 + Original WSGI environ values are stored in the ``werkzeug.proxy_fix.orig`` dict. + + .. versionchanged:: 0.15 + Support ``X-Forwarded-Port`` and ``X-Forwarded-Prefix``. + + .. versionchanged:: 0.15 + ``X-Forwarded-Host`` and ``X-Forwarded-Port`` modify + ``SERVER_NAME`` and ``SERVER_PORT``. + """ + + def __init__( + self, + app: WSGIApplication, + x_for: int = 1, + x_proto: int = 1, + x_host: int = 0, + x_port: int = 0, + x_prefix: int = 0, + ) -> None: + self.app = app + self.x_for = x_for + self.x_proto = x_proto + self.x_host = x_host + self.x_port = x_port + self.x_prefix = x_prefix + + def _get_real_value(self, trusted: int, value: str | None) -> str | None: + """Get the real value from a list header based on the configured + number of trusted proxies. + + :param trusted: Number of values to trust in the header. + :param value: Comma separated list header value to parse. + :return: The real value, or ``None`` if there are fewer values + than the number of trusted proxies. + + .. versionchanged:: 1.0 + Renamed from ``_get_trusted_comma``. + + .. versionadded:: 0.15 + """ + if not (trusted and value): + return None + values = parse_list_header(value) + if len(values) >= trusted: + return values[-trusted] + return None + + def __call__( + self, environ: WSGIEnvironment, start_response: StartResponse + ) -> t.Iterable[bytes]: + """Modify the WSGI environ based on the various ``Forwarded`` + headers before calling the wrapped application. Store the + original environ values in ``werkzeug.proxy_fix.orig_{key}``. + """ + environ_get = environ.get + orig_remote_addr = environ_get("REMOTE_ADDR") + orig_wsgi_url_scheme = environ_get("wsgi.url_scheme") + orig_http_host = environ_get("HTTP_HOST") + environ.update( + { + "werkzeug.proxy_fix.orig": { + "REMOTE_ADDR": orig_remote_addr, + "wsgi.url_scheme": orig_wsgi_url_scheme, + "HTTP_HOST": orig_http_host, + "SERVER_NAME": environ_get("SERVER_NAME"), + "SERVER_PORT": environ_get("SERVER_PORT"), + "SCRIPT_NAME": environ_get("SCRIPT_NAME"), + } + } + ) + + x_for = self._get_real_value(self.x_for, environ_get("HTTP_X_FORWARDED_FOR")) + if x_for: + environ["REMOTE_ADDR"] = x_for + + x_proto = self._get_real_value( + self.x_proto, environ_get("HTTP_X_FORWARDED_PROTO") + ) + if x_proto: + environ["wsgi.url_scheme"] = x_proto + + x_host = self._get_real_value(self.x_host, environ_get("HTTP_X_FORWARDED_HOST")) + if x_host: + environ["HTTP_HOST"] = environ["SERVER_NAME"] = x_host + # "]" to check for IPv6 address without port + if ":" in x_host and not x_host.endswith("]"): + environ["SERVER_NAME"], environ["SERVER_PORT"] = x_host.rsplit(":", 1) + + x_port = self._get_real_value(self.x_port, environ_get("HTTP_X_FORWARDED_PORT")) + if x_port: + host = environ.get("HTTP_HOST") + if host: + # "]" to check for IPv6 address without port + if ":" in host and not host.endswith("]"): + host = host.rsplit(":", 1)[0] + environ["HTTP_HOST"] = f"{host}:{x_port}" + environ["SERVER_PORT"] = x_port + + x_prefix = self._get_real_value( + self.x_prefix, environ_get("HTTP_X_FORWARDED_PREFIX") + ) + if x_prefix: + environ["SCRIPT_NAME"] = x_prefix + + return self.app(environ, start_response) diff --git a/venv/Lib/site-packages/werkzeug/middleware/shared_data.py b/venv/Lib/site-packages/werkzeug/middleware/shared_data.py new file mode 100644 index 00000000..0a0c9567 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/middleware/shared_data.py @@ -0,0 +1,282 @@ +""" +Serve Shared Static Files +========================= + +.. autoclass:: SharedDataMiddleware + :members: is_allowed + +:copyright: 2007 Pallets +:license: BSD-3-Clause +""" + +from __future__ import annotations + +import importlib.util +import mimetypes +import os +import posixpath +import typing as t +from datetime import datetime +from datetime import timezone +from io import BytesIO +from time import time +from zlib import adler32 + +from ..http import http_date +from ..http import is_resource_modified +from ..security import safe_join +from ..utils import get_content_type +from ..wsgi import get_path_info +from ..wsgi import wrap_file + +_TOpener = t.Callable[[], t.Tuple[t.IO[bytes], datetime, int]] +_TLoader = t.Callable[[t.Optional[str]], t.Tuple[t.Optional[str], t.Optional[_TOpener]]] + +if t.TYPE_CHECKING: + from _typeshed.wsgi import StartResponse + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + + +class SharedDataMiddleware: + """A WSGI middleware which provides static content for development + environments or simple server setups. Its usage is quite simple:: + + import os + from werkzeug.middleware.shared_data import SharedDataMiddleware + + app = SharedDataMiddleware(app, { + '/shared': os.path.join(os.path.dirname(__file__), 'shared') + }) + + The contents of the folder ``./shared`` will now be available on + ``http://example.com/shared/``. This is pretty useful during development + because a standalone media server is not required. Files can also be + mounted on the root folder and still continue to use the application because + the shared data middleware forwards all unhandled requests to the + application, even if the requests are below one of the shared folders. + + If `pkg_resources` is available you can also tell the middleware to serve + files from package data:: + + app = SharedDataMiddleware(app, { + '/static': ('myapplication', 'static') + }) + + This will then serve the ``static`` folder in the `myapplication` + Python package. + + The optional `disallow` parameter can be a list of :func:`~fnmatch.fnmatch` + rules for files that are not accessible from the web. If `cache` is set to + `False` no caching headers are sent. + + Currently the middleware does not support non-ASCII filenames. If the + encoding on the file system happens to match the encoding of the URI it may + work but this could also be by accident. We strongly suggest using ASCII + only file names for static files. + + The middleware will guess the mimetype using the Python `mimetype` + module. If it's unable to figure out the charset it will fall back + to `fallback_mimetype`. + + :param app: the application to wrap. If you don't want to wrap an + application you can pass it :exc:`NotFound`. + :param exports: a list or dict of exported files and folders. + :param disallow: a list of :func:`~fnmatch.fnmatch` rules. + :param cache: enable or disable caching headers. + :param cache_timeout: the cache timeout in seconds for the headers. + :param fallback_mimetype: The fallback mimetype for unknown files. + + .. versionchanged:: 1.0 + The default ``fallback_mimetype`` is + ``application/octet-stream``. If a filename looks like a text + mimetype, the ``utf-8`` charset is added to it. + + .. versionadded:: 0.6 + Added ``fallback_mimetype``. + + .. versionchanged:: 0.5 + Added ``cache_timeout``. + """ + + def __init__( + self, + app: WSGIApplication, + exports: ( + dict[str, str | tuple[str, str]] + | t.Iterable[tuple[str, str | tuple[str, str]]] + ), + disallow: None = None, + cache: bool = True, + cache_timeout: int = 60 * 60 * 12, + fallback_mimetype: str = "application/octet-stream", + ) -> None: + self.app = app + self.exports: list[tuple[str, _TLoader]] = [] + self.cache = cache + self.cache_timeout = cache_timeout + + if isinstance(exports, dict): + exports = exports.items() + + for key, value in exports: + if isinstance(value, tuple): + loader = self.get_package_loader(*value) + elif isinstance(value, str): + if os.path.isfile(value): + loader = self.get_file_loader(value) + else: + loader = self.get_directory_loader(value) + else: + raise TypeError(f"unknown def {value!r}") + + self.exports.append((key, loader)) + + if disallow is not None: + from fnmatch import fnmatch + + self.is_allowed = lambda x: not fnmatch(x, disallow) + + self.fallback_mimetype = fallback_mimetype + + def is_allowed(self, filename: str) -> bool: + """Subclasses can override this method to disallow the access to + certain files. However by providing `disallow` in the constructor + this method is overwritten. + """ + return True + + def _opener(self, filename: str) -> _TOpener: + return lambda: ( + open(filename, "rb"), + datetime.fromtimestamp(os.path.getmtime(filename), tz=timezone.utc), + int(os.path.getsize(filename)), + ) + + def get_file_loader(self, filename: str) -> _TLoader: + return lambda x: (os.path.basename(filename), self._opener(filename)) + + def get_package_loader(self, package: str, package_path: str) -> _TLoader: + load_time = datetime.now(timezone.utc) + spec = importlib.util.find_spec(package) + reader = spec.loader.get_resource_reader(package) # type: ignore[union-attr] + + def loader( + path: str | None, + ) -> tuple[str | None, _TOpener | None]: + if path is None: + return None, None + + path = safe_join(package_path, path) + + if path is None: + return None, None + + basename = posixpath.basename(path) + + try: + resource = reader.open_resource(path) + except OSError: + return None, None + + if isinstance(resource, BytesIO): + return ( + basename, + lambda: (resource, load_time, len(resource.getvalue())), + ) + + return ( + basename, + lambda: ( + resource, + datetime.fromtimestamp( + os.path.getmtime(resource.name), tz=timezone.utc + ), + os.path.getsize(resource.name), + ), + ) + + return loader + + def get_directory_loader(self, directory: str) -> _TLoader: + def loader( + path: str | None, + ) -> tuple[str | None, _TOpener | None]: + if path is not None: + path = safe_join(directory, path) + + if path is None: + return None, None + else: + path = directory + + if os.path.isfile(path): + return os.path.basename(path), self._opener(path) + + return None, None + + return loader + + def generate_etag(self, mtime: datetime, file_size: int, real_filename: str) -> str: + fn_str = os.fsencode(real_filename) + timestamp = mtime.timestamp() + checksum = adler32(fn_str) & 0xFFFFFFFF + return f"wzsdm-{timestamp}-{file_size}-{checksum}" + + def __call__( + self, environ: WSGIEnvironment, start_response: StartResponse + ) -> t.Iterable[bytes]: + path = get_path_info(environ) + file_loader = None + + for search_path, loader in self.exports: + if search_path == path: + real_filename, file_loader = loader(None) + + if file_loader is not None: + break + + if not search_path.endswith("/"): + search_path += "/" + + if path.startswith(search_path): + real_filename, file_loader = loader(path[len(search_path) :]) + + if file_loader is not None: + break + + if file_loader is None or not self.is_allowed(real_filename): # type: ignore + return self.app(environ, start_response) + + guessed_type = mimetypes.guess_type(real_filename) # type: ignore + mime_type = get_content_type(guessed_type[0] or self.fallback_mimetype, "utf-8") + f, mtime, file_size = file_loader() + + headers = [("Date", http_date())] + + if self.cache: + timeout = self.cache_timeout + etag = self.generate_etag(mtime, file_size, real_filename) # type: ignore + headers += [ + ("Etag", f'"{etag}"'), + ("Cache-Control", f"max-age={timeout}, public"), + ] + + if not is_resource_modified(environ, etag, last_modified=mtime): + f.close() + start_response("304 Not Modified", headers) + return [] + + headers.append(("Expires", http_date(time() + timeout))) + else: + headers.append(("Cache-Control", "public")) + + headers.extend( + ( + ("Content-Type", mime_type), + ("Content-Length", str(file_size)), + ("Last-Modified", http_date(mtime)), + ) + ) + start_response("200 OK", headers) + return wrap_file(environ, f) diff --git a/venv/Lib/site-packages/werkzeug/py.typed b/venv/Lib/site-packages/werkzeug/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/venv/Lib/site-packages/werkzeug/routing/__init__.py b/venv/Lib/site-packages/werkzeug/routing/__init__.py new file mode 100644 index 00000000..62adc48f --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/routing/__init__.py @@ -0,0 +1,134 @@ +"""When it comes to combining multiple controller or view functions +(however you want to call them) you need a dispatcher. A simple way +would be applying regular expression tests on the ``PATH_INFO`` and +calling registered callback functions that return the value then. + +This module implements a much more powerful system than simple regular +expression matching because it can also convert values in the URLs and +build URLs. + +Here a simple example that creates a URL map for an application with +two subdomains (www and kb) and some URL rules: + +.. code-block:: python + + m = Map([ + # Static URLs + Rule('/', endpoint='static/index'), + Rule('/about', endpoint='static/about'), + Rule('/help', endpoint='static/help'), + # Knowledge Base + Subdomain('kb', [ + Rule('/', endpoint='kb/index'), + Rule('/browse/', endpoint='kb/browse'), + Rule('/browse//', endpoint='kb/browse'), + Rule('/browse//', endpoint='kb/browse') + ]) + ], default_subdomain='www') + +If the application doesn't use subdomains it's perfectly fine to not set +the default subdomain and not use the `Subdomain` rule factory. The +endpoint in the rules can be anything, for example import paths or +unique identifiers. The WSGI application can use those endpoints to get the +handler for that URL. It doesn't have to be a string at all but it's +recommended. + +Now it's possible to create a URL adapter for one of the subdomains and +build URLs: + +.. code-block:: python + + c = m.bind('example.com') + + c.build("kb/browse", dict(id=42)) + 'http://kb.example.com/browse/42/' + + c.build("kb/browse", dict()) + 'http://kb.example.com/browse/' + + c.build("kb/browse", dict(id=42, page=3)) + 'http://kb.example.com/browse/42/3' + + c.build("static/about") + '/about' + + c.build("static/index", force_external=True) + 'http://www.example.com/' + + c = m.bind('example.com', subdomain='kb') + + c.build("static/about") + 'http://www.example.com/about' + +The first argument to bind is the server name *without* the subdomain. +Per default it will assume that the script is mounted on the root, but +often that's not the case so you can provide the real mount point as +second argument: + +.. code-block:: python + + c = m.bind('example.com', '/applications/example') + +The third argument can be the subdomain, if not given the default +subdomain is used. For more details about binding have a look at the +documentation of the `MapAdapter`. + +And here is how you can match URLs: + +.. code-block:: python + + c = m.bind('example.com') + + c.match("/") + ('static/index', {}) + + c.match("/about") + ('static/about', {}) + + c = m.bind('example.com', '/', 'kb') + + c.match("/") + ('kb/index', {}) + + c.match("/browse/42/23") + ('kb/browse', {'id': 42, 'page': 23}) + +If matching fails you get a ``NotFound`` exception, if the rule thinks +it's a good idea to redirect (for example because the URL was defined +to have a slash at the end but the request was missing that slash) it +will raise a ``RequestRedirect`` exception. Both are subclasses of +``HTTPException`` so you can use those errors as responses in the +application. + +If matching succeeded but the URL rule was incompatible to the given +method (for example there were only rules for ``GET`` and ``HEAD`` but +routing tried to match a ``POST`` request) a ``MethodNotAllowed`` +exception is raised. +""" + +from .converters import AnyConverter as AnyConverter +from .converters import BaseConverter as BaseConverter +from .converters import FloatConverter as FloatConverter +from .converters import IntegerConverter as IntegerConverter +from .converters import PathConverter as PathConverter +from .converters import UnicodeConverter as UnicodeConverter +from .converters import UUIDConverter as UUIDConverter +from .converters import ValidationError as ValidationError +from .exceptions import BuildError as BuildError +from .exceptions import NoMatch as NoMatch +from .exceptions import RequestAliasRedirect as RequestAliasRedirect +from .exceptions import RequestPath as RequestPath +from .exceptions import RequestRedirect as RequestRedirect +from .exceptions import RoutingException as RoutingException +from .exceptions import WebsocketMismatch as WebsocketMismatch +from .map import Map as Map +from .map import MapAdapter as MapAdapter +from .matcher import StateMachineMatcher as StateMachineMatcher +from .rules import EndpointPrefix as EndpointPrefix +from .rules import parse_converter_args as parse_converter_args +from .rules import Rule as Rule +from .rules import RuleFactory as RuleFactory +from .rules import RuleTemplate as RuleTemplate +from .rules import RuleTemplateFactory as RuleTemplateFactory +from .rules import Subdomain as Subdomain +from .rules import Submount as Submount diff --git a/venv/Lib/site-packages/werkzeug/routing/__pycache__/__init__.cpython-312.pyc b/venv/Lib/site-packages/werkzeug/routing/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0e5c35b55e98eb2cb760588e61b9c97ca1d51708 GIT binary patch literal 4707 zcmbtYO>Y~?5gl5xC67LB$=~+6;SEC4kjOxGfFtCiRDYt<^Gbl#PVgx%ic1}OVO47Rqrax%h9#|b?-XM zS0LZ;Zm@h6@=fn1%hw>^@@}zw9rA7OHp@34-|_CSd=v6V-bXCof_&GzTXBwW*YC)$ z<{aluPsJj%!cY3j2%C`53F9!{7yUtG!!%M*#a1U#q_jx1I1JTLbO*6-!z4Ctt(Oed zAwC@^12L4bWg{|*gzc$*oz<~Yfsi5yO)9P5Q@SBGg$es)d?=6IVKRsU(NRLCX>?3$ zYqdX!q!#KZ)yf#GB&;&l2oicqwAtV?|PRq}SKGml>He2yitq0S&gP^b&no?}CB zB#)Uok{mnUjjxqNZ15FhMPH_(i&LUS5X{h*bWCCx+Ma8Ni7oyv2GEl3XDDe;0*F}14dr9oN0)Kt6AzsL|_IvJ;=40{7Vl|(A z9&2}Ct5$7RSA>d#Gznw7Q8hfS8ODJ+s@7M|j+LEcVBa?(`|$3Bo{G}N4!gDHSBj#8oRO zt;Z9P#B_hw3W<@VfZPhhpg;=XoB(Q|pB^|wyMs9V3cwEoAUy1bN*gvU-n{y3 z>lAPZt(jK|d@ELFrrcNHI=BN02#6aAc8o~iupz{j6-9`8@{of-?g?WxF#>pqNU}4q z95PocFfRxvPyz7%G8yKPOu&8}M1ZK7k)8ux1~LWB@u~@8oMb0dI*HRH@&P#efOEeA z<_~JsJZKF#L}yPa>Nl8v z`XOsS$j}F^uum&E?R~ZJ;0Kw6Zx7Cz%~^^`=U%?SW=270wo&FY#081qtCl*#@x(IP z*wq7dIz9l^sQ?u#f6p1LoFHX``04DKywB16B+jl>xDOZsn%W=<_5OhB93>bXzd#Kn z2pXlSs>c#F;XaiQaPs}^OF@matM%Xtmg zNn%$B0B+K?xP=gQz#YT`G|vY?3zSK^R}czQog4zM{0%^eGIOZN;7cs(8UP6H7yJ2< z`Mx5m%@WI*W}e6tbP5`{MV~BEkPh#&2+N8HyS$72@Q}APW;}O{P6`KTB>+!618wl# zB2ZR_5ehk#KRyfM3FZzdq9i#W&cI4;kobILS!vIK-$pUs%s8P90@*|;aM1$A8G#$x zIEs8D;R^jD12j%Pwp6><%%M>`UDa2_*ME9vXc*C=W@_^_A^4bI12nrw@p(E+?F;QMp36QvqzD(>h zBzJ(js5|`@` za&O98X^1C@#WF}IWU!Al4KAEX*KN0-@9u6tEw+p~CBT--rq+o@VjJw$qZ7PgWo>0$=;z$D+aBg3Ltb)_g#8)_vyE_VHjB81D zWuSPs+n+t%&E61@pFiFF6dfWZ4HRm-4}m-t2!tGHl=gq&?dsGkZ)Z2-<4>{|fYRvC(S@|En12=}(v~%gTcFQir&=A?KcKtxMs*Bc6Ux2_4M!Bp32+yF+!G2zZ|N0Y3F?KU z2NZtz-3ayk)BG8+t$~IkXqHo{jcWNtFk2`A2Kvbz+#K;cL%+|m3naR;)ZJBhK*2ov zOXdE_u#4j>D8@{q!`16$UA&!YwZOow)?0VHq|p;NRXs_~d1@|DGeylbH5aL&e{8%x zNeZK9NIOdn9^#Ikr)GhgMQSdgc{|aUsmA|79KA%{DUO)ZdYO9XHFje375aRYnrqZt zr{)GVH>tTr&22P)b-qFCeD~*1*7shcx|_We2#xk^GK@_xOj|gPsMQ&OD)8bSt5m%o9Bj^6e r`E=y0jU05Le>`$-jhy<(`PImI!0p|UvpRB`BWDBpe>heB6D;sQ*4EMK literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/routing/__pycache__/converters.cpython-312.pyc b/venv/Lib/site-packages/werkzeug/routing/__pycache__/converters.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d75079a578919f66a4b42af1616610711c03d150 GIT binary patch literal 10947 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 t)B?AaJzTw*uiKgrwB&}93yo;&!e*xj*84Lgb literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/routing/__pycache__/exceptions.cpython-312.pyc b/venv/Lib/site-packages/werkzeug/routing/__pycache__/exceptions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2de99afad57c34fb977409e91d8b38b429ba118e GIT binary patch literal 7950 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 Ld@1n{W5)jnllIfC literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/routing/__pycache__/matcher.cpython-312.pyc b/venv/Lib/site-packages/werkzeug/routing/__pycache__/matcher.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..51796dfc18391447bdbe3b7f1e12cc4eb55dd151 GIT binary patch literal 8318 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 literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/routing/converters.py b/venv/Lib/site-packages/werkzeug/routing/converters.py new file mode 100644 index 00000000..6016a975 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/routing/converters.py @@ -0,0 +1,261 @@ +from __future__ import annotations + +import re +import typing as t +import uuid +from urllib.parse import quote + +if t.TYPE_CHECKING: + from .map import Map + + +class ValidationError(ValueError): + """Validation error. If a rule converter raises this exception the rule + does not match the current URL and the next URL is tried. + """ + + +class BaseConverter: + """Base class for all converters. + + .. versionchanged:: 2.3 + ``part_isolating`` defaults to ``False`` if ``regex`` contains a ``/``. + """ + + regex = "[^/]+" + weight = 100 + part_isolating = True + + def __init_subclass__(cls, **kwargs: t.Any) -> None: + super().__init_subclass__(**kwargs) + + # If the converter isn't inheriting its regex, disable part_isolating by default + # if the regex contains a / character. + if "regex" in cls.__dict__ and "part_isolating" not in cls.__dict__: + cls.part_isolating = "/" not in cls.regex + + def __init__(self, map: Map, *args: t.Any, **kwargs: t.Any) -> None: + self.map = map + + def to_python(self, value: str) -> t.Any: + return value + + def to_url(self, value: t.Any) -> str: + # safe = https://url.spec.whatwg.org/#url-path-segment-string + return quote(str(value), safe="!$&'()*+,/:;=@") + + +class UnicodeConverter(BaseConverter): + """This converter is the default converter and accepts any string but + only one path segment. Thus the string can not include a slash. + + This is the default validator. + + Example:: + + Rule('/pages/'), + Rule('/') + + :param map: the :class:`Map`. + :param minlength: the minimum length of the string. Must be greater + or equal 1. + :param maxlength: the maximum length of the string. + :param length: the exact length of the string. + """ + + def __init__( + self, + map: Map, + minlength: int = 1, + maxlength: int | None = None, + length: int | None = None, + ) -> None: + super().__init__(map) + if length is not None: + length_regex = f"{{{int(length)}}}" + else: + if maxlength is None: + maxlength_value = "" + else: + maxlength_value = str(int(maxlength)) + length_regex = f"{{{int(minlength)},{maxlength_value}}}" + self.regex = f"[^/]{length_regex}" + + +class AnyConverter(BaseConverter): + """Matches one of the items provided. Items can either be Python + identifiers or strings:: + + Rule('/') + + :param map: the :class:`Map`. + :param items: this function accepts the possible items as positional + arguments. + + .. versionchanged:: 2.2 + Value is validated when building a URL. + """ + + def __init__(self, map: Map, *items: str) -> None: + super().__init__(map) + self.items = set(items) + self.regex = f"(?:{'|'.join([re.escape(x) for x in items])})" + + def to_url(self, value: t.Any) -> str: + if value in self.items: + return str(value) + + valid_values = ", ".join(f"'{item}'" for item in sorted(self.items)) + raise ValueError(f"'{value}' is not one of {valid_values}") + + +class PathConverter(BaseConverter): + """Like the default :class:`UnicodeConverter`, but it also matches + slashes. This is useful for wikis and similar applications:: + + Rule('/') + Rule('//edit') + + :param map: the :class:`Map`. + """ + + part_isolating = False + regex = "[^/].*?" + weight = 200 + + +class NumberConverter(BaseConverter): + """Baseclass for `IntegerConverter` and `FloatConverter`. + + :internal: + """ + + weight = 50 + num_convert: t.Callable[[t.Any], t.Any] = int + + def __init__( + self, + map: Map, + fixed_digits: int = 0, + min: int | None = None, + max: int | None = None, + signed: bool = False, + ) -> None: + if signed: + self.regex = self.signed_regex + super().__init__(map) + self.fixed_digits = fixed_digits + self.min = min + self.max = max + self.signed = signed + + def to_python(self, value: str) -> t.Any: + if self.fixed_digits and len(value) != self.fixed_digits: + raise ValidationError() + value_num = self.num_convert(value) + if (self.min is not None and value_num < self.min) or ( + self.max is not None and value_num > self.max + ): + raise ValidationError() + return value_num + + def to_url(self, value: t.Any) -> str: + value_str = str(self.num_convert(value)) + if self.fixed_digits: + value_str = value_str.zfill(self.fixed_digits) + return value_str + + @property + def signed_regex(self) -> str: + return f"-?{self.regex}" + + +class IntegerConverter(NumberConverter): + """This converter only accepts integer values:: + + Rule("/page/") + + By default it only accepts unsigned, positive values. The ``signed`` + parameter will enable signed, negative values. :: + + Rule("/page/") + + :param map: The :class:`Map`. + :param fixed_digits: The number of fixed digits in the URL. If you + set this to ``4`` for example, the rule will only match if the + URL looks like ``/0001/``. The default is variable length. + :param min: The minimal value. + :param max: The maximal value. + :param signed: Allow signed (negative) values. + + .. versionadded:: 0.15 + The ``signed`` parameter. + """ + + regex = r"\d+" + + +class FloatConverter(NumberConverter): + """This converter only accepts floating point values:: + + Rule("/probability/") + + By default it only accepts unsigned, positive values. The ``signed`` + parameter will enable signed, negative values. :: + + Rule("/offset/") + + :param map: The :class:`Map`. + :param min: The minimal value. + :param max: The maximal value. + :param signed: Allow signed (negative) values. + + .. versionadded:: 0.15 + The ``signed`` parameter. + """ + + regex = r"\d+\.\d+" + num_convert = float + + def __init__( + self, + map: Map, + min: float | None = None, + max: float | None = None, + signed: bool = False, + ) -> None: + super().__init__(map, min=min, max=max, signed=signed) # type: ignore + + +class UUIDConverter(BaseConverter): + """This converter only accepts UUID strings:: + + Rule('/object/') + + .. versionadded:: 0.10 + + :param map: the :class:`Map`. + """ + + regex = ( + r"[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-" + r"[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}" + ) + + def to_python(self, value: str) -> uuid.UUID: + return uuid.UUID(value) + + def to_url(self, value: uuid.UUID) -> str: + return str(value) + + +#: the default converter mapping for the map. +DEFAULT_CONVERTERS: t.Mapping[str, type[BaseConverter]] = { + "default": UnicodeConverter, + "string": UnicodeConverter, + "any": AnyConverter, + "path": PathConverter, + "int": IntegerConverter, + "float": FloatConverter, + "uuid": UUIDConverter, +} diff --git a/venv/Lib/site-packages/werkzeug/routing/exceptions.py b/venv/Lib/site-packages/werkzeug/routing/exceptions.py new file mode 100644 index 00000000..eeabd4ed --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/routing/exceptions.py @@ -0,0 +1,152 @@ +from __future__ import annotations + +import difflib +import typing as t + +from ..exceptions import BadRequest +from ..exceptions import HTTPException +from ..utils import cached_property +from ..utils import redirect + +if t.TYPE_CHECKING: + from _typeshed.wsgi import WSGIEnvironment + + from ..wrappers.request import Request + from ..wrappers.response import Response + from .map import MapAdapter + from .rules import Rule + + +class RoutingException(Exception): + """Special exceptions that require the application to redirect, notifying + about missing urls, etc. + + :internal: + """ + + +class RequestRedirect(HTTPException, RoutingException): + """Raise if the map requests a redirect. This is for example the case if + `strict_slashes` are activated and an url that requires a trailing slash. + + The attribute `new_url` contains the absolute destination url. + """ + + code = 308 + + def __init__(self, new_url: str) -> None: + super().__init__(new_url) + self.new_url = new_url + + def get_response( + self, + environ: WSGIEnvironment | Request | None = None, + scope: dict[str, t.Any] | None = None, + ) -> Response: + return redirect(self.new_url, self.code) + + +class RequestPath(RoutingException): + """Internal exception.""" + + __slots__ = ("path_info",) + + def __init__(self, path_info: str) -> None: + super().__init__() + self.path_info = path_info + + +class RequestAliasRedirect(RoutingException): # noqa: B903 + """This rule is an alias and wants to redirect to the canonical URL.""" + + def __init__(self, matched_values: t.Mapping[str, t.Any], endpoint: t.Any) -> None: + super().__init__() + self.matched_values = matched_values + self.endpoint = endpoint + + +class BuildError(RoutingException, LookupError): + """Raised if the build system cannot find a URL for an endpoint with the + values provided. + """ + + def __init__( + self, + endpoint: t.Any, + values: t.Mapping[str, t.Any], + method: str | None, + adapter: MapAdapter | None = None, + ) -> None: + super().__init__(endpoint, values, method) + self.endpoint = endpoint + self.values = values + self.method = method + self.adapter = adapter + + @cached_property + def suggested(self) -> Rule | None: + return self.closest_rule(self.adapter) + + def closest_rule(self, adapter: MapAdapter | None) -> Rule | None: + def _score_rule(rule: Rule) -> float: + return sum( + [ + 0.98 + * difflib.SequenceMatcher( + # endpoints can be any type, compare as strings + None, + str(rule.endpoint), + str(self.endpoint), + ).ratio(), + 0.01 * bool(set(self.values or ()).issubset(rule.arguments)), + 0.01 * bool(rule.methods and self.method in rule.methods), + ] + ) + + if adapter and adapter.map._rules: + return max(adapter.map._rules, key=_score_rule) + + return None + + def __str__(self) -> str: + message = [f"Could not build url for endpoint {self.endpoint!r}"] + if self.method: + message.append(f" ({self.method!r})") + if self.values: + message.append(f" with values {sorted(self.values)!r}") + message.append(".") + if self.suggested: + if self.endpoint == self.suggested.endpoint: + if ( + self.method + and self.suggested.methods is not None + and self.method not in self.suggested.methods + ): + message.append( + " Did you mean to use methods" + f" {sorted(self.suggested.methods)!r}?" + ) + missing_values = self.suggested.arguments.union( + set(self.suggested.defaults or ()) + ) - set(self.values.keys()) + if missing_values: + message.append( + f" Did you forget to specify values {sorted(missing_values)!r}?" + ) + else: + message.append(f" Did you mean {self.suggested.endpoint!r} instead?") + return "".join(message) + + +class WebsocketMismatch(BadRequest): + """The only matched rule is either a WebSocket and the request is + HTTP, or the rule is HTTP and the request is a WebSocket. + """ + + +class NoMatch(Exception): + __slots__ = ("have_match_for", "websocket_mismatch") + + def __init__(self, have_match_for: set[str], websocket_mismatch: bool) -> None: + self.have_match_for = have_match_for + self.websocket_mismatch = websocket_mismatch diff --git a/venv/Lib/site-packages/werkzeug/routing/map.py b/venv/Lib/site-packages/werkzeug/routing/map.py new file mode 100644 index 00000000..4d15e882 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/routing/map.py @@ -0,0 +1,951 @@ +from __future__ import annotations + +import typing as t +import warnings +from pprint import pformat +from threading import Lock +from urllib.parse import quote +from urllib.parse import urljoin +from urllib.parse import urlunsplit + +from .._internal import _get_environ +from .._internal import _wsgi_decoding_dance +from ..datastructures import ImmutableDict +from ..datastructures import MultiDict +from ..exceptions import BadHost +from ..exceptions import HTTPException +from ..exceptions import MethodNotAllowed +from ..exceptions import NotFound +from ..urls import _urlencode +from ..wsgi import get_host +from .converters import DEFAULT_CONVERTERS +from .exceptions import BuildError +from .exceptions import NoMatch +from .exceptions import RequestAliasRedirect +from .exceptions import RequestPath +from .exceptions import RequestRedirect +from .exceptions import WebsocketMismatch +from .matcher import StateMachineMatcher +from .rules import _simple_rule_re +from .rules import Rule + +if t.TYPE_CHECKING: + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + + from ..wrappers.request import Request + from .converters import BaseConverter + from .rules import RuleFactory + + +class Map: + """The map class stores all the URL rules and some configuration + parameters. Some of the configuration values are only stored on the + `Map` instance since those affect all rules, others are just defaults + and can be overridden for each rule. Note that you have to specify all + arguments besides the `rules` as keyword arguments! + + :param rules: sequence of url rules for this map. + :param default_subdomain: The default subdomain for rules without a + subdomain defined. + :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 + building URLs. Matches will redirect to the normalized URL. + Slashes in variable parts are not merged. + :param redirect_defaults: This will redirect to the default rule if it + wasn't visited that way. This helps creating + unique URLs. + :param converters: A dict of converters that adds additional converters + to the list of converters. If you redefine one + converter this will override the original one. + :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 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:: 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. + + .. versionchanged:: 1.0 + The ``merge_slashes`` parameter was added. + + .. versionchanged:: 0.7 + The ``encoding_errors`` and ``host_matching`` parameters were added. + + .. versionchanged:: 0.5 + The ``sort_parameters`` and ``sort_key`` paramters were added. + """ + + #: A dict of default converters to be used. + default_converters = ImmutableDict(DEFAULT_CONVERTERS) + + #: The type of lock to use when updating. + #: + #: .. versionadded:: 1.0 + lock_class = Lock + + def __init__( + self, + rules: t.Iterable[RuleFactory] | None = None, + default_subdomain: str = "", + 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, + host_matching: bool = False, + ) -> None: + self._matcher = StateMachineMatcher(merge_slashes) + self._rules_by_endpoint: dict[t.Any, list[Rule]] = {} + self._remap = True + self._remap_lock = self.lock_class() + + self.default_subdomain = default_subdomain + self.strict_slashes = strict_slashes + self.redirect_defaults = redirect_defaults + self.host_matching = host_matching + + self.converters = self.default_converters.copy() + if converters: + self.converters.update(converters) + + self.sort_parameters = sort_parameters + self.sort_key = sort_key + + for rulefactory in rules or (): + self.add(rulefactory) + + @property + def merge_slashes(self) -> bool: + return self._matcher.merge_slashes + + @merge_slashes.setter + def merge_slashes(self, value: bool) -> None: + self._matcher.merge_slashes = value + + def is_endpoint_expecting(self, endpoint: t.Any, *arguments: str) -> bool: + """Iterate over all rules and check if the endpoint expects + the arguments provided. This is for example useful if you have + some URLs that expect a language code and others that do not and + you want to wrap the builder a bit so that the current language + code is automatically added if not provided but endpoints expect + it. + + :param endpoint: the endpoint to check. + :param arguments: this function accepts one or more arguments + as positional arguments. Each one of them is + checked. + """ + self.update() + arguments_set = set(arguments) + for rule in self._rules_by_endpoint[endpoint]: + if arguments_set.issubset(rule.arguments): + return True + return False + + @property + def _rules(self) -> list[Rule]: + return [rule for rules in self._rules_by_endpoint.values() for rule in rules] + + def iter_rules(self, endpoint: t.Any | None = None) -> t.Iterator[Rule]: + """Iterate over all rules or the rules of an endpoint. + + :param endpoint: if provided only the rules for that endpoint + are returned. + :return: an iterator + """ + self.update() + if endpoint is not None: + return iter(self._rules_by_endpoint[endpoint]) + return iter(self._rules) + + def add(self, rulefactory: RuleFactory) -> None: + """Add a new rule or factory to the map and bind it. Requires that the + rule is not bound to another map. + + :param rulefactory: a :class:`Rule` or :class:`RuleFactory` + """ + for rule in rulefactory.get_rules(self): + rule.bind(self) + if not rule.build_only: + self._matcher.add(rule) + self._rules_by_endpoint.setdefault(rule.endpoint, []).append(rule) + self._remap = True + + def bind( + self, + server_name: str, + script_name: str | None = None, + subdomain: str | None = None, + url_scheme: str = "http", + default_method: str = "GET", + path_info: str | None = None, + query_args: t.Mapping[str, t.Any] | str | None = None, + ) -> MapAdapter: + """Return a new :class:`MapAdapter` with the details specified to the + call. Note that `script_name` will default to ``'/'`` if not further + specified or `None`. The `server_name` at least is a requirement + because the HTTP RFC requires absolute URLs for redirects and so all + redirect exceptions raised by Werkzeug will contain the full canonical + URL. + + If no path_info is passed to :meth:`match` it will use the default path + info passed to bind. While this doesn't really make sense for + manual bind calls, it's useful if you bind a map to a WSGI + environment which already contains the path info. + + `subdomain` will default to the `default_subdomain` for this map if + no defined. If there is no `default_subdomain` you cannot use the + subdomain feature. + + .. versionchanged:: 1.0 + If ``url_scheme`` is ``ws`` or ``wss``, only WebSocket rules + will match. + + .. versionchanged:: 0.15 + ``path_info`` defaults to ``'/'`` if ``None``. + + .. versionchanged:: 0.8 + ``query_args`` can be a string. + + .. versionchanged:: 0.7 + Added ``query_args``. + """ + server_name = server_name.lower() + if self.host_matching: + if subdomain is not None: + raise RuntimeError("host matching enabled and a subdomain was provided") + elif subdomain is None: + subdomain = self.default_subdomain + if script_name is None: + script_name = "/" + if path_info is None: + path_info = "/" + + # Port isn't part of IDNA, and might push a name over the 63 octet limit. + server_name, port_sep, port = server_name.partition(":") + + try: + server_name = server_name.encode("idna").decode("ascii") + except UnicodeError as e: + raise BadHost() from e + + return MapAdapter( + self, + f"{server_name}{port_sep}{port}", + script_name, + subdomain, + url_scheme, + path_info, + default_method, + query_args, + ) + + def bind_to_environ( + self, + environ: WSGIEnvironment | Request, + server_name: str | None = None, + subdomain: str | None = None, + ) -> MapAdapter: + """Like :meth:`bind` but you can pass it an WSGI environment and it + will fetch the information from that dictionary. Note that because of + limitations in the protocol there is no way to get the current + subdomain and real `server_name` from the environment. If you don't + provide it, Werkzeug will use `SERVER_NAME` and `SERVER_PORT` (or + `HTTP_HOST` if provided) as used `server_name` with disabled subdomain + feature. + + If `subdomain` is `None` but an environment and a server name is + provided it will calculate the current subdomain automatically. + Example: `server_name` is ``'example.com'`` and the `SERVER_NAME` + in the wsgi `environ` is ``'staging.dev.example.com'`` the calculated + subdomain will be ``'staging.dev'``. + + If the object passed as environ has an environ attribute, the value of + this attribute is used instead. This allows you to pass request + objects. Additionally `PATH_INFO` added as a default of the + :class:`MapAdapter` so that you don't have to pass the path info to + the match method. + + .. versionchanged:: 1.0.0 + If the passed server name specifies port 443, it will match + if the incoming scheme is ``https`` without a port. + + .. versionchanged:: 1.0.0 + A warning is shown when the passed server name does not + match the incoming WSGI server name. + + .. versionchanged:: 0.8 + This will no longer raise a ValueError when an unexpected server + name was passed. + + .. versionchanged:: 0.5 + previously this method accepted a bogus `calculate_subdomain` + parameter that did not have any effect. It was removed because + of that. + + :param environ: a WSGI environment. + :param server_name: an optional server name hint (see above). + :param subdomain: optionally the current subdomain (see above). + """ + env = _get_environ(environ) + wsgi_server_name = get_host(env).lower() + scheme = env["wsgi.url_scheme"] + upgrade = any( + v.strip() == "upgrade" + for v in env.get("HTTP_CONNECTION", "").lower().split(",") + ) + + if upgrade and env.get("HTTP_UPGRADE", "").lower() == "websocket": + scheme = "wss" if scheme == "https" else "ws" + + if server_name is None: + server_name = wsgi_server_name + else: + server_name = server_name.lower() + + # strip standard port to match get_host() + if scheme in {"http", "ws"} and server_name.endswith(":80"): + server_name = server_name[:-3] + elif scheme in {"https", "wss"} and server_name.endswith(":443"): + server_name = server_name[:-4] + + if subdomain is None and not self.host_matching: + cur_server_name = wsgi_server_name.split(".") + real_server_name = server_name.split(".") + offset = -len(real_server_name) + + if cur_server_name[offset:] != real_server_name: + # This can happen even with valid configs if the server was + # accessed directly by IP address under some situations. + # Instead of raising an exception like in Werkzeug 0.7 or + # earlier we go by an invalid subdomain which will result + # in a 404 error on matching. + warnings.warn( + f"Current server name {wsgi_server_name!r} doesn't match configured" + f" server name {server_name!r}", + stacklevel=2, + ) + subdomain = "" + else: + subdomain = ".".join(filter(None, cur_server_name[:offset])) + + def _get_wsgi_string(name: str) -> str | None: + val = env.get(name) + if val is not None: + return _wsgi_decoding_dance(val) + return None + + script_name = _get_wsgi_string("SCRIPT_NAME") + path_info = _get_wsgi_string("PATH_INFO") + query_args = _get_wsgi_string("QUERY_STRING") + return Map.bind( + self, + server_name, + script_name, + subdomain, + scheme, + env["REQUEST_METHOD"], + path_info, + query_args=query_args, + ) + + def update(self) -> None: + """Called before matching and building to keep the compiled rules + in the correct order after things changed. + """ + if not self._remap: + return + + with self._remap_lock: + if not self._remap: + return + + self._matcher.update() + for rules in self._rules_by_endpoint.values(): + rules.sort(key=lambda x: x.build_compare_key()) + self._remap = False + + def __repr__(self) -> str: + rules = self.iter_rules() + return f"{type(self).__name__}({pformat(list(rules))})" + + +class MapAdapter: + """Returned by :meth:`Map.bind` or :meth:`Map.bind_to_environ` and does + the URL matching and building based on runtime information. + """ + + def __init__( + self, + map: Map, + server_name: str, + script_name: str, + subdomain: str | None, + url_scheme: str, + path_info: str, + default_method: str, + query_args: t.Mapping[str, t.Any] | str | None = None, + ): + self.map = map + self.server_name = server_name + + if not script_name.endswith("/"): + script_name += "/" + + self.script_name = script_name + self.subdomain = subdomain + self.url_scheme = url_scheme + self.path_info = path_info + self.default_method = default_method + self.query_args = query_args + self.websocket = self.url_scheme in {"ws", "wss"} + + def dispatch( + self, + view_func: t.Callable[[str, t.Mapping[str, t.Any]], WSGIApplication], + path_info: str | None = None, + method: str | None = None, + catch_http_exceptions: bool = False, + ) -> WSGIApplication: + """Does the complete dispatching process. `view_func` is called with + the endpoint and a dict with the values for the view. It should + look up the view function, call it, and return a response object + or WSGI application. http exceptions are not caught by default + so that applications can display nicer error messages by just + catching them by hand. If you want to stick with the default + error messages you can pass it ``catch_http_exceptions=True`` and + it will catch the http exceptions. + + Here a small example for the dispatch usage:: + + from werkzeug.wrappers import Request, Response + from werkzeug.wsgi import responder + from werkzeug.routing import Map, Rule + + def on_index(request): + return Response('Hello from the index') + + url_map = Map([Rule('/', endpoint='index')]) + views = {'index': on_index} + + @responder + def application(environ, start_response): + request = Request(environ) + urls = url_map.bind_to_environ(environ) + return urls.dispatch(lambda e, v: views[e](request, **v), + catch_http_exceptions=True) + + Keep in mind that this method might return exception objects, too, so + use :class:`Response.force_type` to get a response object. + + :param view_func: a function that is called with the endpoint as + first argument and the value dict as second. Has + to dispatch to the actual view function with this + information. (see above) + :param path_info: the path info to use for matching. Overrides the + path info specified on binding. + :param method: the HTTP method used for matching. Overrides the + method specified on binding. + :param catch_http_exceptions: set to `True` to catch any of the + werkzeug :class:`HTTPException`\\s. + """ + try: + try: + endpoint, args = self.match(path_info, method) + except RequestRedirect as e: + return e + return view_func(endpoint, args) + except HTTPException as e: + if catch_http_exceptions: + return e + raise + + @t.overload + def match( + self, + path_info: str | None = None, + method: str | None = None, + return_rule: t.Literal[False] = False, + query_args: t.Mapping[str, t.Any] | str | None = None, + websocket: bool | None = None, + ) -> tuple[t.Any, t.Mapping[str, t.Any]]: ... + + @t.overload + def match( + self, + path_info: str | None = None, + method: str | None = None, + return_rule: t.Literal[True] = True, + query_args: t.Mapping[str, t.Any] | str | None = None, + websocket: bool | None = None, + ) -> tuple[Rule, t.Mapping[str, t.Any]]: ... + + def match( + self, + path_info: str | None = None, + method: str | None = None, + return_rule: bool = False, + query_args: t.Mapping[str, t.Any] | str | None = None, + websocket: bool | None = None, + ) -> tuple[t.Any | Rule, t.Mapping[str, t.Any]]: + """The usage is simple: you just pass the match method the current + path info as well as the method (which defaults to `GET`). The + following things can then happen: + + - you receive a `NotFound` exception that indicates that no URL is + matching. A `NotFound` exception is also a WSGI application you + can call to get a default page not found page (happens to be the + same object as `werkzeug.exceptions.NotFound`) + + - you receive a `MethodNotAllowed` exception that indicates that there + is a match for this URL but not for the current request method. + This is useful for RESTful applications. + + - you receive a `RequestRedirect` exception with a `new_url` + attribute. This exception is used to notify you about a request + Werkzeug requests from your WSGI application. This is for example the + case if you request ``/foo`` although the correct URL is ``/foo/`` + You can use the `RequestRedirect` instance as response-like object + similar to all other subclasses of `HTTPException`. + + - you receive a ``WebsocketMismatch`` exception if the only + match is a WebSocket rule but the bind is an HTTP request, or + if the match is an HTTP rule but the bind is a WebSocket + request. + + - you get a tuple in the form ``(endpoint, arguments)`` if there is + a match (unless `return_rule` is True, in which case you get a tuple + in the form ``(rule, arguments)``) + + If the path info is not passed to the match method the default path + info of the map is used (defaults to the root URL if not defined + explicitly). + + All of the exceptions raised are subclasses of `HTTPException` so they + can be used as WSGI responses. They will all render generic error or + redirect pages. + + Here is a small example for matching: + + >>> m = Map([ + ... Rule('/', endpoint='index'), + ... Rule('/downloads/', endpoint='downloads/index'), + ... Rule('/downloads/', endpoint='downloads/show') + ... ]) + >>> urls = m.bind("example.com", "/") + >>> urls.match("/", "GET") + ('index', {}) + >>> urls.match("/downloads/42") + ('downloads/show', {'id': 42}) + + And here is what happens on redirect and missing URLs: + + >>> urls.match("/downloads") + Traceback (most recent call last): + ... + RequestRedirect: http://example.com/downloads/ + >>> urls.match("/missing") + Traceback (most recent call last): + ... + NotFound: 404 Not Found + + :param path_info: the path info to use for matching. Overrides the + path info specified on binding. + :param method: the HTTP method used for matching. Overrides the + method specified on binding. + :param return_rule: return the rule that matched instead of just the + endpoint (defaults to `False`). + :param query_args: optional query arguments that are used for + automatic redirects as string or dictionary. It's + currently not possible to use the query arguments + for URL matching. + :param websocket: Match WebSocket instead of HTTP requests. A + websocket request has a ``ws`` or ``wss`` + :attr:`url_scheme`. This overrides that detection. + + .. versionadded:: 1.0 + Added ``websocket``. + + .. versionchanged:: 0.8 + ``query_args`` can be a string. + + .. versionadded:: 0.7 + Added ``query_args``. + + .. versionadded:: 0.6 + Added ``return_rule``. + """ + self.map.update() + if path_info is None: + path_info = self.path_info + if query_args is None: + query_args = self.query_args or {} + method = (method or self.default_method).upper() + + if websocket is None: + websocket = self.websocket + + domain_part = self.server_name + + if not self.map.host_matching and self.subdomain is not None: + domain_part = self.subdomain + + path_part = f"/{path_info.lstrip('/')}" if path_info else "" + + try: + 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="!$&'()*+,/:;=@") + raise RequestRedirect( + self.make_redirect_url(new_path, query_args) + ) from None + except RequestAliasRedirect as e: + raise RequestRedirect( + self.make_alias_redirect_url( + f"{domain_part}|{path_part}", + e.endpoint, + e.matched_values, + method, + query_args, + ) + ) from None + except NoMatch as e: + if e.have_match_for: + raise MethodNotAllowed(valid_methods=list(e.have_match_for)) from None + + if e.websocket_mismatch: + raise WebsocketMismatch() from None + + raise NotFound() from None + else: + rule, rv = result + + if self.map.redirect_defaults: + redirect_url = self.get_default_redirect(rule, method, rv, query_args) + if redirect_url is not None: + raise RequestRedirect(redirect_url) + + if rule.redirect_to is not None: + if isinstance(rule.redirect_to, str): + + def _handle_match(match: t.Match[str]) -> str: + value = rv[match.group(1)] + return rule._converters[match.group(1)].to_url(value) + + redirect_url = _simple_rule_re.sub(_handle_match, rule.redirect_to) + else: + redirect_url = rule.redirect_to(self, **rv) + + if self.subdomain: + netloc = f"{self.subdomain}.{self.server_name}" + else: + netloc = self.server_name + + raise RequestRedirect( + urljoin( + f"{self.url_scheme or 'http'}://{netloc}{self.script_name}", + redirect_url, + ) + ) + + if return_rule: + return rule, rv + else: + return rule.endpoint, rv + + def test(self, path_info: str | None = None, method: str | None = None) -> bool: + """Test if a rule would match. Works like `match` but returns `True` + if the URL matches, or `False` if it does not exist. + + :param path_info: the path info to use for matching. Overrides the + path info specified on binding. + :param method: the HTTP method used for matching. Overrides the + method specified on binding. + """ + try: + self.match(path_info, method) + except RequestRedirect: + pass + except HTTPException: + return False + return True + + def allowed_methods(self, path_info: str | None = None) -> t.Iterable[str]: + """Returns the valid methods that match for a given path. + + .. versionadded:: 0.7 + """ + try: + self.match(path_info, method="--") + except MethodNotAllowed as e: + return e.valid_methods # type: ignore + except HTTPException: + pass + return [] + + def get_host(self, domain_part: str | None) -> str: + """Figures out the full host name for the given domain part. The + domain part is a subdomain in case host matching is disabled or + a full host name. + """ + if self.map.host_matching: + if domain_part is None: + return self.server_name + + return domain_part + + if domain_part is None: + subdomain = self.subdomain + else: + subdomain = domain_part + + if subdomain: + return f"{subdomain}.{self.server_name}" + else: + return self.server_name + + def get_default_redirect( + self, + rule: Rule, + method: str, + values: t.MutableMapping[str, t.Any], + query_args: t.Mapping[str, t.Any] | str, + ) -> str | None: + """A helper that returns the URL to redirect to if it finds one. + This is used for default redirecting only. + + :internal: + """ + assert self.map.redirect_defaults + for r in self.map._rules_by_endpoint[rule.endpoint]: + # every rule that comes after this one, including ourself + # has a lower priority for the defaults. We order the ones + # with the highest priority up for building. + if r is rule: + break + if r.provides_defaults_for(rule) and r.suitable_for(values, method): + values.update(r.defaults) # type: ignore + domain_part, path = r.build(values) # type: ignore + return self.make_redirect_url(path, query_args, domain_part=domain_part) + return None + + def encode_query_args(self, query_args: t.Mapping[str, t.Any] | str) -> str: + if not isinstance(query_args, str): + return _urlencode(query_args) + return query_args + + def make_redirect_url( + self, + path_info: str, + query_args: t.Mapping[str, t.Any] | str | None = None, + domain_part: str | None = None, + ) -> str: + """Creates a redirect URL. + + :internal: + """ + if query_args is None: + query_args = self.query_args + + if query_args: + query_str = self.encode_query_args(query_args) + else: + query_str = None + + scheme = self.url_scheme or "http" + host = self.get_host(domain_part) + path = "/".join((self.script_name.strip("/"), path_info.lstrip("/"))) + return urlunsplit((scheme, host, path, query_str, None)) + + def make_alias_redirect_url( + self, + path: str, + endpoint: t.Any, + values: t.Mapping[str, t.Any], + method: str, + query_args: t.Mapping[str, t.Any] | str, + ) -> str: + """Internally called to make an alias redirect URL.""" + url = self.build( + endpoint, values, method, append_unknown=False, force_external=True + ) + if query_args: + url += f"?{self.encode_query_args(query_args)}" + assert url != path, "detected invalid alias setting. No canonical URL found" + return url + + def _partial_build( + self, + endpoint: t.Any, + values: t.Mapping[str, t.Any], + method: str | None, + append_unknown: bool, + ) -> tuple[str, str, bool] | None: + """Helper for :meth:`build`. Returns subdomain and path for the + rule that accepts this endpoint, values and method. + + :internal: + """ + # in case the method is none, try with the default method first + if method is None: + rv = self._partial_build( + endpoint, values, self.default_method, append_unknown + ) + if rv is not None: + return rv + + # Default method did not match or a specific method is passed. + # Check all for first match with matching host. If no matching + # host is found, go with first result. + first_match = None + + for rule in self.map._rules_by_endpoint.get(endpoint, ()): + if rule.suitable_for(values, method): + build_rv = rule.build(values, append_unknown) + + if build_rv is not None: + rv = (build_rv[0], build_rv[1], rule.websocket) + if self.map.host_matching: + if rv[0] == self.server_name: + return rv + elif first_match is None: + first_match = rv + else: + return rv + + return first_match + + def build( + self, + endpoint: t.Any, + values: t.Mapping[str, t.Any] | None = None, + method: str | None = None, + force_external: bool = False, + append_unknown: bool = True, + url_scheme: str | None = None, + ) -> str: + """Building URLs works pretty much the other way round. Instead of + `match` you call `build` and pass it the endpoint and a dict of + arguments for the placeholders. + + The `build` function also accepts an argument called `force_external` + which, if you set it to `True` will force external URLs. Per default + external URLs (include the server name) will only be used if the + target URL is on a different subdomain. + + >>> m = Map([ + ... Rule('/', endpoint='index'), + ... Rule('/downloads/', endpoint='downloads/index'), + ... Rule('/downloads/', endpoint='downloads/show') + ... ]) + >>> urls = m.bind("example.com", "/") + >>> urls.build("index", {}) + '/' + >>> urls.build("downloads/show", {'id': 42}) + '/downloads/42' + >>> urls.build("downloads/show", {'id': 42}, force_external=True) + 'http://example.com/downloads/42' + + Because URLs cannot contain non ASCII data you will always get + bytes back. Non ASCII characters are urlencoded with the + charset defined on the map instance. + + Additional values are converted to strings and appended to the URL as + URL querystring parameters: + + >>> urls.build("index", {'q': 'My Searchstring'}) + '/?q=My+Searchstring' + + When processing those additional values, lists are furthermore + interpreted as multiple values (as per + :py:class:`werkzeug.datastructures.MultiDict`): + + >>> urls.build("index", {'q': ['a', 'b', 'c']}) + '/?q=a&q=b&q=c' + + Passing a ``MultiDict`` will also add multiple values: + + >>> urls.build("index", MultiDict((('p', 'z'), ('q', 'a'), ('q', 'b')))) + '/?p=z&q=a&q=b' + + If a rule does not exist when building a `BuildError` exception is + raised. + + The build method accepts an argument called `method` which allows you + to specify the method you want to have an URL built for if you have + different methods for the same endpoint specified. + + :param endpoint: the endpoint of the URL to build. + :param values: the values for the URL to build. Unhandled values are + appended to the URL as query parameters. + :param method: the HTTP method for the rule if there are different + URLs for different methods on the same endpoint. + :param force_external: enforce full canonical external URLs. If the URL + scheme is not provided, this will generate + a protocol-relative URL. + :param append_unknown: unknown parameters are appended to the generated + URL as query string argument. Disable this + if you want the builder to ignore those. + :param url_scheme: Scheme to use in place of the bound + :attr:`url_scheme`. + + .. versionchanged:: 2.0 + Added the ``url_scheme`` parameter. + + .. versionadded:: 0.6 + Added the ``append_unknown`` parameter. + """ + self.map.update() + + if values: + if isinstance(values, MultiDict): + values = { + k: (v[0] if len(v) == 1 else v) + for k, v in dict.items(values) + if len(v) != 0 + } + else: # plain dict + values = {k: v for k, v in values.items() if v is not None} + else: + values = {} + + rv = self._partial_build(endpoint, values, method, append_unknown) + if rv is None: + raise BuildError(endpoint, values, method, self) + + domain_part, path, websocket = rv + host = self.get_host(domain_part) + + if url_scheme is None: + url_scheme = self.url_scheme + + # Always build WebSocket routes with the scheme (browsers + # require full URLs). If bound to a WebSocket, ensure that HTTP + # routes are built with an HTTP scheme. + secure = url_scheme in {"https", "wss"} + + if websocket: + force_external = True + url_scheme = "wss" if secure else "ws" + elif url_scheme: + url_scheme = "https" if secure else "http" + + # shortcut this. + if not force_external and ( + (self.map.host_matching and host == self.server_name) + or (not self.map.host_matching and domain_part == self.subdomain) + ): + return f"{self.script_name.rstrip('/')}/{path.lstrip('/')}" + + scheme = f"{url_scheme}:" if url_scheme else "" + return f"{scheme}//{host}{self.script_name[:-1]}/{path.lstrip('/')}" diff --git a/venv/Lib/site-packages/werkzeug/routing/matcher.py b/venv/Lib/site-packages/werkzeug/routing/matcher.py new file mode 100644 index 00000000..1fd00efc --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/routing/matcher.py @@ -0,0 +1,202 @@ +from __future__ import annotations + +import re +import typing as t +from dataclasses import dataclass +from dataclasses import field + +from .converters import ValidationError +from .exceptions import NoMatch +from .exceptions import RequestAliasRedirect +from .exceptions import RequestPath +from .rules import Rule +from .rules import RulePart + + +class SlashRequired(Exception): + pass + + +@dataclass +class State: + """A representation of a rule state. + + This includes the *rules* that correspond to the state and the + possible *static* and *dynamic* transitions to the next state. + """ + + dynamic: list[tuple[RulePart, State]] = field(default_factory=list) + rules: list[Rule] = field(default_factory=list) + static: dict[str, State] = field(default_factory=dict) + + +class StateMachineMatcher: + def __init__(self, merge_slashes: bool) -> None: + self._root = State() + self.merge_slashes = merge_slashes + + def add(self, rule: Rule) -> None: + state = self._root + for part in rule._parts: + if part.static: + state.static.setdefault(part.content, State()) + state = state.static[part.content] + else: + for test_part, new_state in state.dynamic: + if test_part == part: + state = new_state + break + else: + new_state = State() + state.dynamic.append((part, new_state)) + state = new_state + state.rules.append(rule) + + def update(self) -> None: + # For every state the dynamic transitions should be sorted by + # the weight of the transition + state = self._root + + def _update_state(state: State) -> None: + state.dynamic.sort(key=lambda entry: entry[0].weight) + for new_state in state.static.values(): + _update_state(new_state) + for _, new_state in state.dynamic: + _update_state(new_state) + + _update_state(state) + + def match( + self, domain: str, path: str, method: str, websocket: bool + ) -> tuple[Rule, t.MutableMapping[str, t.Any]]: + # To match to a rule we need to start at the root state and + # try to follow the transitions until we find a match, or find + # there is no transition to follow. + + have_match_for = set() + websocket_mismatch = False + + def _match( + state: State, parts: list[str], values: list[str] + ) -> tuple[Rule, list[str]] | None: + # This function is meant to be called recursively, and will attempt + # to match the head part to the state's transitions. + nonlocal have_match_for, websocket_mismatch + + # The base case is when all parts have been matched via + # transitions. Hence if there is a rule with methods & + # websocket that work return it and the dynamic values + # extracted. + if parts == []: + for rule in state.rules: + if rule.methods is not None and method not in rule.methods: + have_match_for.update(rule.methods) + elif rule.websocket != websocket: + websocket_mismatch = True + else: + return rule, values + + # Test if there is a match with this path with a + # trailing slash, if so raise an exception to report + # that matching is possible with an additional slash + if "" in state.static: + for rule in state.static[""].rules: + if websocket == rule.websocket and ( + rule.methods is None or method in rule.methods + ): + if rule.strict_slashes: + raise SlashRequired() + else: + return rule, values + return None + + part = parts[0] + # To match this part try the static transitions first + if part in state.static: + rv = _match(state.static[part], parts[1:], values) + if rv is not None: + return rv + # No match via the static transitions, so try the dynamic + # ones. + for test_part, new_state in state.dynamic: + target = part + remaining = parts[1:] + # A final part indicates a transition that always + # consumes the remaining parts i.e. transitions to a + # final state. + if test_part.final: + target = "/".join(parts) + remaining = [] + match = re.compile(test_part.content).match(target) + if match is not None: + if test_part.suffixed: + # If a part_isolating=False part has a slash suffix, remove the + # suffix from the match and check for the slash redirect next. + suffix = match.groups()[-1] + if suffix == "/": + remaining = [""] + + converter_groups = sorted( + match.groupdict().items(), key=lambda entry: entry[0] + ) + groups = [ + value + for key, value in converter_groups + if key[:11] == "__werkzeug_" + ] + rv = _match(new_state, remaining, values + groups) + if rv is not None: + return rv + + # If there is no match and the only part left is a + # trailing slash ("") consider rules that aren't + # strict-slashes as these should match if there is a final + # slash part. + if parts == [""]: + for rule in state.rules: + if rule.strict_slashes: + continue + if rule.methods is not None and method not in rule.methods: + have_match_for.update(rule.methods) + elif rule.websocket != websocket: + websocket_mismatch = True + else: + return rule, values + + return None + + try: + rv = _match(self._root, [domain, *path.split("/")], []) + except SlashRequired: + raise RequestPath(f"{path}/") from None + + if self.merge_slashes and rv is None: + # Try to match again, but with slashes merged + path = re.sub("/{2,}?", "/", path) + try: + rv = _match(self._root, [domain, *path.split("/")], []) + except SlashRequired: + raise RequestPath(f"{path}/") from None + if rv is None or rv[0].merge_slashes is False: + raise NoMatch(have_match_for, websocket_mismatch) + else: + raise RequestPath(f"{path}") + elif rv is not None: + rule, values = rv + + result = {} + for name, value in zip(rule._converters.keys(), values): + try: + value = rule._converters[name].to_python(value) + except ValidationError: + raise NoMatch(have_match_for, websocket_mismatch) from None + result[str(name)] = value + if rule.defaults: + result.update(rule.defaults) + + if rule.alias and rule.map.redirect_defaults: + raise RequestAliasRedirect(result, rule.endpoint) + + return rule, result + + raise NoMatch(have_match_for, websocket_mismatch) diff --git a/venv/Lib/site-packages/werkzeug/routing/rules.py b/venv/Lib/site-packages/werkzeug/routing/rules.py new file mode 100644 index 00000000..6a02f8d3 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/routing/rules.py @@ -0,0 +1,917 @@ +from __future__ import annotations + +import ast +import re +import typing as t +from dataclasses import dataclass +from string import Template +from types import CodeType +from urllib.parse import quote + +from ..datastructures import iter_multi_items +from ..urls import _urlencode +from .converters import ValidationError + +if t.TYPE_CHECKING: + from .converters import BaseConverter + from .map import Map + + +class Weighting(t.NamedTuple): + number_static_weights: int + static_weights: list[tuple[int, int]] + number_argument_weights: int + argument_weights: list[int] + + +@dataclass +class RulePart: + """A part of a rule. + + Rules can be represented by parts as delimited by `/` with + instances of this class representing those parts. The *content* is + either the raw content if *static* or a regex string to match + against. The *weight* can be used to order parts when matching. + + """ + + content: str + final: bool + static: bool + suffixed: bool + weight: Weighting + + +_part_re = re.compile( + r""" + (?: + (?P/) # a slash + | + (?P[^[a-zA-Z_][a-zA-Z0-9_]*) # converter name + (?:\((?P.*?)\))? # converter arguments + : # variable delimiter + )? + (?P[a-zA-Z_][a-zA-Z0-9_]*) # variable name + > + ) + ) + """, + re.VERBOSE, +) + +_simple_rule_re = re.compile(r"<([^>]+)>") +_converter_args_re = re.compile( + r""" + \s* + ((?P\w+)\s*=\s*)? + (?P + True|False| + \d+.\d+| + \d+.| + \d+| + [\w\d_.]+| + [urUR]?(?P"[^"]*?"|'[^']*') + )\s*, + """, + re.VERBOSE, +) + + +_PYTHON_CONSTANTS = {"None": None, "True": True, "False": False} + + +def _find(value: str, target: str, pos: int) -> int: + """Find the *target* in *value* after *pos*. + + Returns the *value* length if *target* isn't found. + """ + try: + return value.index(target, pos) + except ValueError: + return len(value) + + +def _pythonize(value: str) -> None | bool | int | float | str: + if value in _PYTHON_CONSTANTS: + return _PYTHON_CONSTANTS[value] + for convert in int, float: + try: + return convert(value) # type: ignore + except ValueError: + pass + if value[:1] == value[-1:] and value[0] in "\"'": + value = value[1:-1] + return str(value) + + +def parse_converter_args(argstr: str) -> tuple[tuple[t.Any, ...], dict[str, t.Any]]: + argstr += "," + args = [] + kwargs = {} + position = 0 + + for item in _converter_args_re.finditer(argstr): + if item.start() != position: + raise ValueError( + f"Cannot parse converter argument '{argstr[position:item.start()]}'" + ) + + value = item.group("stringval") + if value is None: + value = item.group("value") + value = _pythonize(value) + if not item.group("name"): + args.append(value) + else: + name = item.group("name") + kwargs[name] = value + position = item.end() + + return tuple(args), kwargs + + +class RuleFactory: + """As soon as you have more complex URL setups it's a good idea to use rule + factories to avoid repetitive tasks. Some of them are builtin, others can + be added by subclassing `RuleFactory` and overriding `get_rules`. + """ + + def get_rules(self, map: Map) -> t.Iterable[Rule]: + """Subclasses of `RuleFactory` have to override this method and return + an iterable of rules.""" + raise NotImplementedError() + + +class Subdomain(RuleFactory): + """All URLs provided by this factory have the subdomain set to a + specific domain. For example if you want to use the subdomain for + the current language this can be a good setup:: + + url_map = Map([ + Rule('/', endpoint='#select_language'), + Subdomain('', [ + Rule('/', endpoint='index'), + Rule('/about', endpoint='about'), + Rule('/help', endpoint='help') + ]) + ]) + + All the rules except for the ``'#select_language'`` endpoint will now + listen on a two letter long subdomain that holds the language code + for the current request. + """ + + def __init__(self, subdomain: str, rules: t.Iterable[RuleFactory]) -> None: + self.subdomain = subdomain + self.rules = rules + + def get_rules(self, map: Map) -> t.Iterator[Rule]: + for rulefactory in self.rules: + for rule in rulefactory.get_rules(map): + rule = rule.empty() + rule.subdomain = self.subdomain + yield rule + + +class Submount(RuleFactory): + """Like `Subdomain` but prefixes the URL rule with a given string:: + + url_map = Map([ + Rule('/', endpoint='index'), + Submount('/blog', [ + Rule('/', endpoint='blog/index'), + Rule('/entry/', endpoint='blog/show') + ]) + ]) + + Now the rule ``'blog/show'`` matches ``/blog/entry/``. + """ + + def __init__(self, path: str, rules: t.Iterable[RuleFactory]) -> None: + self.path = path.rstrip("/") + self.rules = rules + + def get_rules(self, map: Map) -> t.Iterator[Rule]: + for rulefactory in self.rules: + for rule in rulefactory.get_rules(map): + rule = rule.empty() + rule.rule = self.path + rule.rule + yield rule + + +class EndpointPrefix(RuleFactory): + """Prefixes all endpoints (which must be strings for this factory) with + another string. This can be useful for sub applications:: + + url_map = Map([ + Rule('/', endpoint='index'), + EndpointPrefix('blog/', [Submount('/blog', [ + Rule('/', endpoint='index'), + Rule('/entry/', endpoint='show') + ])]) + ]) + """ + + def __init__(self, prefix: str, rules: t.Iterable[RuleFactory]) -> None: + self.prefix = prefix + self.rules = rules + + def get_rules(self, map: Map) -> t.Iterator[Rule]: + for rulefactory in self.rules: + for rule in rulefactory.get_rules(map): + rule = rule.empty() + rule.endpoint = self.prefix + rule.endpoint + yield rule + + +class RuleTemplate: + """Returns copies of the rules wrapped and expands string templates in + the endpoint, rule, defaults or subdomain sections. + + Here a small example for such a rule template:: + + from werkzeug.routing import Map, Rule, RuleTemplate + + resource = RuleTemplate([ + Rule('/$name/', endpoint='$name.list'), + Rule('/$name/', endpoint='$name.show') + ]) + + url_map = Map([resource(name='user'), resource(name='page')]) + + When a rule template is called the keyword arguments are used to + replace the placeholders in all the string parameters. + """ + + def __init__(self, rules: t.Iterable[Rule]) -> None: + self.rules = list(rules) + + def __call__(self, *args: t.Any, **kwargs: t.Any) -> RuleTemplateFactory: + return RuleTemplateFactory(self.rules, dict(*args, **kwargs)) + + +class RuleTemplateFactory(RuleFactory): + """A factory that fills in template variables into rules. Used by + `RuleTemplate` internally. + + :internal: + """ + + def __init__( + self, rules: t.Iterable[RuleFactory], context: dict[str, t.Any] + ) -> None: + self.rules = rules + self.context = context + + def get_rules(self, map: Map) -> t.Iterator[Rule]: + for rulefactory in self.rules: + for rule in rulefactory.get_rules(map): + new_defaults = subdomain = None + if rule.defaults: + new_defaults = {} + for key, value in rule.defaults.items(): + if isinstance(value, str): + value = Template(value).substitute(self.context) + new_defaults[key] = value + if rule.subdomain is not None: + subdomain = Template(rule.subdomain).substitute(self.context) + new_endpoint = rule.endpoint + if isinstance(new_endpoint, str): + new_endpoint = Template(new_endpoint).substitute(self.context) + yield Rule( + Template(rule.rule).substitute(self.context), + new_defaults, + subdomain, + rule.methods, + rule.build_only, + new_endpoint, + rule.strict_slashes, + ) + + +def _prefix_names(src: str) -> ast.stmt: + """ast parse and prefix names with `.` to avoid collision with user vars""" + tree = ast.parse(src).body[0] + if isinstance(tree, ast.Expr): + tree = tree.value # type: ignore + for node in ast.walk(tree): + if isinstance(node, ast.Name): + node.id = f".{node.id}" + return tree + + +_CALL_CONVERTER_CODE_FMT = "self._converters[{elem!r}].to_url()" +_IF_KWARGS_URL_ENCODE_CODE = """\ +if kwargs: + params = self._encode_query_vars(kwargs) + q = "?" if params else "" +else: + q = params = "" +""" +_IF_KWARGS_URL_ENCODE_AST = _prefix_names(_IF_KWARGS_URL_ENCODE_CODE) +_URL_ENCODE_AST_NAMES = (_prefix_names("q"), _prefix_names("params")) + + +class Rule(RuleFactory): + """A Rule represents one URL pattern. There are some options for `Rule` + that change the way it behaves and are passed to the `Rule` constructor. + Note that besides the rule-string all arguments *must* be keyword arguments + in order to not break the application on Werkzeug upgrades. + + `string` + Rule strings basically are just normal URL paths with placeholders in + the format ```` where the converter and the + arguments are optional. If no converter is defined the `default` + converter is used which means `string` in the normal configuration. + + URL rules that end with a slash are branch URLs, others are leaves. + If you have `strict_slashes` enabled (which is the default), all + branch URLs that are matched without a trailing slash will trigger a + redirect to the same URL with the missing slash appended. + + The converters are defined on the `Map`. + + `endpoint` + The endpoint for this rule. This can be anything. A reference to a + function, a string, a number etc. The preferred way is using a string + because the endpoint is used for URL generation. + + `defaults` + An optional dict with defaults for other rules with the same endpoint. + This is a bit tricky but useful if you want to have unique URLs:: + + url_map = Map([ + Rule('/all/', defaults={'page': 1}, endpoint='all_entries'), + Rule('/all/page/', endpoint='all_entries') + ]) + + If a user now visits ``http://example.com/all/page/1`` they will be + redirected to ``http://example.com/all/``. If `redirect_defaults` is + disabled on the `Map` instance this will only affect the URL + generation. + + `subdomain` + The subdomain rule string for this rule. If not specified the rule + only matches for the `default_subdomain` of the map. If the map is + not bound to a subdomain this feature is disabled. + + Can be useful if you want to have user profiles on different subdomains + and all subdomains are forwarded to your application:: + + url_map = Map([ + Rule('/', subdomain='', endpoint='user/homepage'), + Rule('/stats', subdomain='', endpoint='user/stats') + ]) + + `methods` + A sequence of http methods this rule applies to. If not specified, all + methods are allowed. For example this can be useful if you want different + endpoints for `POST` and `GET`. If methods are defined and the path + matches but the method matched against is not in this list or in the + list of another rule for that path the error raised is of the type + `MethodNotAllowed` rather than `NotFound`. If `GET` is present in the + list of methods and `HEAD` is not, `HEAD` is added automatically. + + `strict_slashes` + Override the `Map` setting for `strict_slashes` only for this rule. If + not specified the `Map` setting is used. + + `merge_slashes` + Override :attr:`Map.merge_slashes` for this rule. + + `build_only` + Set this to True and the rule will never match but will create a URL + that can be build. This is useful if you have resources on a subdomain + or folder that are not handled by the WSGI application (like static data) + + `redirect_to` + If given this must be either a string or callable. In case of a + callable it's called with the url adapter that triggered the match and + the values of the URL as keyword arguments and has to return the target + for the redirect, otherwise it has to be a string with placeholders in + rule syntax:: + + def foo_with_slug(adapter, id): + # ask the database for the slug for the old id. this of + # course has nothing to do with werkzeug. + return f'foo/{Foo.get_slug_for_id(id)}' + + url_map = Map([ + Rule('/foo/', endpoint='foo'), + Rule('/some/old/url/', redirect_to='foo/'), + Rule('/other/old/url/', redirect_to=foo_with_slug) + ]) + + When the rule is matched the routing system will raise a + `RequestRedirect` exception with the target for the redirect. + + Keep in mind that the URL will be joined against the URL root of the + script so don't use a leading slash on the target URL unless you + really mean root of that domain. + + `alias` + If enabled this rule serves as an alias for another rule with the same + endpoint and arguments. + + `host` + If provided and the URL map has host matching enabled this can be + used to provide a match rule for the whole host. This also means + that the subdomain feature is disabled. + + `websocket` + If ``True``, this rule is only matches for WebSocket (``ws://``, + ``wss://``) requests. By default, rules will only match for HTTP + requests. + + .. versionchanged:: 2.1 + Percent-encoded newlines (``%0a``), which are decoded by WSGI + servers, are considered when routing instead of terminating the + match early. + + .. versionadded:: 1.0 + Added ``websocket``. + + .. versionadded:: 1.0 + Added ``merge_slashes``. + + .. versionadded:: 0.7 + Added ``alias`` and ``host``. + + .. versionchanged:: 0.6.1 + ``HEAD`` is added to ``methods`` if ``GET`` is present. + """ + + def __init__( + self, + string: str, + defaults: t.Mapping[str, t.Any] | None = None, + subdomain: str | None = None, + methods: t.Iterable[str] | None = None, + build_only: bool = False, + endpoint: t.Any | None = None, + strict_slashes: bool | None = None, + merge_slashes: bool | None = None, + redirect_to: str | t.Callable[..., str] | None = None, + alias: bool = False, + host: str | None = None, + websocket: bool = False, + ) -> None: + if not string.startswith("/"): + raise ValueError(f"URL rule '{string}' must start with a slash.") + + self.rule = string + self.is_leaf = not string.endswith("/") + self.is_branch = string.endswith("/") + + self.map: Map = None # type: ignore + self.strict_slashes = strict_slashes + self.merge_slashes = merge_slashes + self.subdomain = subdomain + self.host = host + self.defaults = defaults + self.build_only = build_only + self.alias = alias + self.websocket = websocket + + if methods is not None: + if isinstance(methods, str): + raise TypeError("'methods' should be a list of strings.") + + methods = {x.upper() for x in methods} + + if "HEAD" not in methods and "GET" in methods: + methods.add("HEAD") + + if websocket and methods - {"GET", "HEAD", "OPTIONS"}: + raise ValueError( + "WebSocket rules can only use 'GET', 'HEAD', and 'OPTIONS' methods." + ) + + self.methods = methods + self.endpoint: t.Any = endpoint + self.redirect_to = redirect_to + + if defaults: + self.arguments = set(map(str, defaults)) + else: + self.arguments = set() + + self._converters: dict[str, BaseConverter] = {} + self._trace: list[tuple[bool, str]] = [] + self._parts: list[RulePart] = [] + + def empty(self) -> Rule: + """ + Return an unbound copy of this rule. + + This can be useful if want to reuse an already bound URL for another + map. See ``get_empty_kwargs`` to override what keyword arguments are + provided to the new copy. + """ + return type(self)(self.rule, **self.get_empty_kwargs()) + + def get_empty_kwargs(self) -> t.Mapping[str, t.Any]: + """ + Provides kwargs for instantiating empty copy with empty() + + Use this method to provide custom keyword arguments to the subclass of + ``Rule`` when calling ``some_rule.empty()``. Helpful when the subclass + has custom keyword arguments that are needed at instantiation. + + Must return a ``dict`` that will be provided as kwargs to the new + instance of ``Rule``, following the initial ``self.rule`` value which + is always provided as the first, required positional argument. + """ + defaults = None + if self.defaults: + defaults = dict(self.defaults) + return dict( + defaults=defaults, + subdomain=self.subdomain, + methods=self.methods, + build_only=self.build_only, + endpoint=self.endpoint, + strict_slashes=self.strict_slashes, + redirect_to=self.redirect_to, + alias=self.alias, + host=self.host, + ) + + def get_rules(self, map: Map) -> t.Iterator[Rule]: + yield self + + def refresh(self) -> None: + """Rebinds and refreshes the URL. Call this if you modified the + rule in place. + + :internal: + """ + self.bind(self.map, rebind=True) + + def bind(self, map: Map, rebind: bool = False) -> None: + """Bind the url to a map and create a regular expression based on + the information from the rule itself and the defaults from the map. + + :internal: + """ + if self.map is not None and not rebind: + raise RuntimeError(f"url rule {self!r} already bound to map {self.map!r}") + self.map = map + if self.strict_slashes is None: + self.strict_slashes = map.strict_slashes + if self.merge_slashes is None: + self.merge_slashes = map.merge_slashes + if self.subdomain is None: + self.subdomain = map.default_subdomain + self.compile() + + def get_converter( + self, + variable_name: str, + converter_name: str, + args: tuple[t.Any, ...], + kwargs: t.Mapping[str, t.Any], + ) -> BaseConverter: + """Looks up the converter for the given parameter. + + .. versionadded:: 0.9 + """ + if converter_name not in self.map.converters: + raise LookupError(f"the converter {converter_name!r} does not exist") + return self.map.converters[converter_name](self.map, *args, **kwargs) + + def _encode_query_vars(self, query_vars: t.Mapping[str, t.Any]) -> str: + items: t.Iterable[tuple[str, str]] = iter_multi_items(query_vars) + + if self.map.sort_parameters: + items = sorted(items, key=self.map.sort_key) + + return _urlencode(items) + + def _parse_rule(self, rule: str) -> t.Iterable[RulePart]: + content = "" + static = True + argument_weights = [] + static_weights: list[tuple[int, int]] = [] + final = False + convertor_number = 0 + + pos = 0 + while pos < len(rule): + match = _part_re.match(rule, pos) + if match is None: + raise ValueError(f"malformed url rule: {rule!r}") + + data = match.groupdict() + if data["static"] is not None: + static_weights.append((len(static_weights), -len(data["static"]))) + self._trace.append((False, data["static"])) + content += data["static"] if static else re.escape(data["static"]) + + if data["variable"] is not None: + if static: + # Switching content to represent regex, hence the need to escape + content = re.escape(content) + static = False + c_args, c_kwargs = parse_converter_args(data["arguments"] or "") + convobj = self.get_converter( + data["variable"], data["converter"] or "default", c_args, c_kwargs + ) + self._converters[data["variable"]] = convobj + self.arguments.add(data["variable"]) + if not convobj.part_isolating: + final = True + content += f"(?P<__werkzeug_{convertor_number}>{convobj.regex})" + convertor_number += 1 + argument_weights.append(convobj.weight) + self._trace.append((True, data["variable"])) + + if data["slash"] is not None: + self._trace.append((False, "/")) + if final: + content += "/" + else: + if not static: + content += r"\Z" + weight = Weighting( + -len(static_weights), + static_weights, + -len(argument_weights), + argument_weights, + ) + yield RulePart( + content=content, + final=final, + static=static, + suffixed=False, + weight=weight, + ) + content = "" + static = True + argument_weights = [] + static_weights = [] + final = False + convertor_number = 0 + + pos = match.end() + + suffixed = False + if final and content[-1] == "/": + # If a converter is part_isolating=False (matches slashes) and ends with a + # slash, augment the regex to support slash redirects. + suffixed = True + content = content[:-1] + "(? None: + """Compiles the regular expression and stores it.""" + assert self.map is not None, "rule not bound" + + if self.map.host_matching: + domain_rule = self.host or "" + else: + domain_rule = self.subdomain or "" + self._parts = [] + self._trace = [] + self._converters = {} + if domain_rule == "": + self._parts = [ + RulePart( + content="", + final=False, + static=True, + suffixed=False, + weight=Weighting(0, [], 0, []), + ) + ] + else: + self._parts.extend(self._parse_rule(domain_rule)) + self._trace.append((False, "|")) + rule = self.rule + if self.merge_slashes: + rule = re.sub("/{2,}?", "/", self.rule) + self._parts.extend(self._parse_rule(rule)) + + self._build: t.Callable[..., tuple[str, str]] + self._build = self._compile_builder(False).__get__(self, None) + self._build_unknown: t.Callable[..., tuple[str, str]] + self._build_unknown = self._compile_builder(True).__get__(self, None) + + @staticmethod + def _get_func_code(code: CodeType, name: str) -> t.Callable[..., tuple[str, str]]: + globs: dict[str, t.Any] = {} + locs: dict[str, t.Any] = {} + exec(code, globs, locs) + return locs[name] # type: ignore + + def _compile_builder( + self, append_unknown: bool = True + ) -> t.Callable[..., tuple[str, str]]: + defaults = self.defaults or {} + dom_ops: list[tuple[bool, str]] = [] + url_ops: list[tuple[bool, str]] = [] + + opl = dom_ops + for is_dynamic, data in self._trace: + if data == "|" and opl is dom_ops: + opl = url_ops + continue + # this seems like a silly case to ever come up but: + # if a default is given for a value that appears in the rule, + # resolve it to a constant ahead of time + if is_dynamic and data in defaults: + data = self._converters[data].to_url(defaults[data]) + opl.append((False, data)) + elif not is_dynamic: + # safe = https://url.spec.whatwg.org/#url-path-segment-string + opl.append((False, quote(data, safe="!$&'()*+,/:;=@"))) + else: + opl.append((True, data)) + + def _convert(elem: str) -> ast.stmt: + ret = _prefix_names(_CALL_CONVERTER_CODE_FMT.format(elem=elem)) + ret.args = [ast.Name(str(elem), ast.Load())] # type: ignore # str for py2 + return ret + + def _parts(ops: list[tuple[bool, str]]) -> list[ast.AST]: + parts = [ + _convert(elem) if is_dynamic else ast.Constant(elem) + for is_dynamic, elem in ops + ] + parts = parts or [ast.Constant("")] + # constant fold + ret = [parts[0]] + for p in parts[1:]: + if isinstance(p, ast.Constant) and isinstance(ret[-1], ast.Constant): + ret[-1] = ast.Constant(ret[-1].value + p.value) + else: + ret.append(p) + return ret + + dom_parts = _parts(dom_ops) + url_parts = _parts(url_ops) + if not append_unknown: + body = [] + else: + body = [_IF_KWARGS_URL_ENCODE_AST] + url_parts.extend(_URL_ENCODE_AST_NAMES) + + def _join(parts: list[ast.AST]) -> ast.AST: + if len(parts) == 1: # shortcut + return parts[0] + return ast.JoinedStr(parts) + + body.append( + ast.Return(ast.Tuple([_join(dom_parts), _join(url_parts)], ast.Load())) + ) + + pargs = [ + elem + for is_dynamic, elem in dom_ops + url_ops + if is_dynamic and elem not in defaults + ] + kargs = [str(k) for k in defaults] + + func_ast: ast.FunctionDef = _prefix_names("def _(): pass") # type: ignore + func_ast.name = f"" + func_ast.args.args.append(ast.arg(".self", None)) + for arg in pargs + kargs: + func_ast.args.args.append(ast.arg(arg, None)) + func_ast.args.kwarg = ast.arg(".kwargs", None) + for _ in kargs: + func_ast.args.defaults.append(ast.Constant("")) + func_ast.body = body + + # Use `ast.parse` instead of `ast.Module` for better portability, since the + # signature of `ast.Module` can change. + module = ast.parse("") + module.body = [func_ast] + + # mark everything as on line 1, offset 0 + # less error-prone than `ast.fix_missing_locations` + # bad line numbers cause an assert to fail in debug builds + for node in ast.walk(module): + if "lineno" in node._attributes: + node.lineno = 1 + if "end_lineno" in node._attributes: + node.end_lineno = node.lineno + if "col_offset" in node._attributes: + node.col_offset = 0 + if "end_col_offset" in node._attributes: + node.end_col_offset = node.col_offset + + code = compile(module, "", "exec") + return self._get_func_code(code, func_ast.name) + + def build( + self, values: t.Mapping[str, t.Any], append_unknown: bool = True + ) -> tuple[str, str] | None: + """Assembles the relative url for that rule and the subdomain. + If building doesn't work for some reasons `None` is returned. + + :internal: + """ + try: + if append_unknown: + return self._build_unknown(**values) + else: + return self._build(**values) + except ValidationError: + return None + + def provides_defaults_for(self, rule: Rule) -> bool: + """Check if this rule has defaults for a given rule. + + :internal: + """ + return bool( + not self.build_only + and self.defaults + and self.endpoint == rule.endpoint + and self != rule + and self.arguments == rule.arguments + ) + + def suitable_for( + self, values: t.Mapping[str, t.Any], method: str | None = None + ) -> bool: + """Check if the dict of values has enough data for url generation. + + :internal: + """ + # if a method was given explicitly and that method is not supported + # by this rule, this rule is not suitable. + if ( + method is not None + and self.methods is not None + and method not in self.methods + ): + return False + + defaults = self.defaults or () + + # all arguments required must be either in the defaults dict or + # the value dictionary otherwise it's not suitable + for key in self.arguments: + if key not in defaults and key not in values: + return False + + # in case defaults are given we ensure that either the value was + # skipped or the value is the same as the default value. + if defaults: + for key, value in defaults.items(): + if key in values and value != values[key]: + return False + + return True + + def build_compare_key(self) -> tuple[int, int, int]: + """The build compare key for sorting. + + :internal: + """ + return (1 if self.alias else 0, -len(self.arguments), -len(self.defaults or ())) + + def __eq__(self, other: object) -> bool: + return isinstance(other, type(self)) and self._trace == other._trace + + __hash__ = None # type: ignore + + def __str__(self) -> str: + return self.rule + + def __repr__(self) -> str: + if self.map is None: + return f"<{type(self).__name__} (unbound)>" + parts = [] + for is_dynamic, data in self._trace: + if is_dynamic: + parts.append(f"<{data}>") + else: + parts.append(data) + parts_str = "".join(parts).lstrip("|") + methods = f" ({', '.join(self.methods)})" if self.methods is not None else "" + return f"<{type(self).__name__} {parts_str!r}{methods} -> {self.endpoint}>" diff --git a/venv/Lib/site-packages/werkzeug/sansio/__init__.py b/venv/Lib/site-packages/werkzeug/sansio/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/venv/Lib/site-packages/werkzeug/sansio/__pycache__/__init__.cpython-312.pyc b/venv/Lib/site-packages/werkzeug/sansio/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b7c3dc6a8b8562070d45b482ecb135b88af075bd GIT binary patch literal 230 zcmX@j%ge<81mEof(m?cM5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!YIU}X2`x@7Dvl{A zEl$meDakL-E6&I)h%e4f%!yAbEzZnKEiQ%&>n3L;mL%nuAOt|_a&%z|bwP4vsd;5F zKAA}|#hE3kx&?{J*@@|?#WCfnMcGxUrRn;`iFw7D`TFtknR%Hd@$q^EmA^P_a`RJ4 ab5iY!Sb?r$1ma>4<0CU8BV!RWkOcrU=S67% literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/sansio/__pycache__/http.cpython-312.pyc b/venv/Lib/site-packages/werkzeug/sansio/__pycache__/http.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e1abd629b0493443889549802ac6feced1df17b3 GIT binary patch literal 5759 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 k_KZ~RgG_71*Iu8CFJ9|RJ=h5!Hn literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/sansio/__pycache__/multipart.cpython-312.pyc b/venv/Lib/site-packages/werkzeug/sansio/__pycache__/multipart.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..16b801cfb33ba6ed01678fb152c4479e0c476e45 GIT binary patch literal 14087 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 JVN96ue*s_ff)M}! literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/sansio/__pycache__/request.cpython-312.pyc b/venv/Lib/site-packages/werkzeug/sansio/__pycache__/request.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..491602b21d8b2cf5f6823dfac1829d0a674ff697 GIT binary patch literal 21923 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 F{{_!&P9y*T literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/sansio/http.py b/venv/Lib/site-packages/werkzeug/sansio/http.py new file mode 100644 index 00000000..b2b88779 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/sansio/http.py @@ -0,0 +1,171 @@ +from __future__ import annotations + +import re +import typing as t +from datetime import datetime + +from .._internal import _dt_as_utc +from ..http import generate_etag +from ..http import parse_date +from ..http import parse_etags +from ..http import parse_if_range_header +from ..http import unquote_etag + +_etag_re = re.compile(r'([Ww]/)?(?:"(.*?)"|(.*?))(?:\s*,\s*|$)') + + +def is_resource_modified( + http_range: str | None = None, + http_if_range: str | None = None, + http_if_modified_since: str | None = None, + http_if_none_match: str | None = None, + http_if_match: str | None = None, + etag: str | None = None, + data: bytes | None = None, + last_modified: datetime | str | None = None, + ignore_if_range: bool = True, +) -> bool: + """Convenience method for conditional requests. + :param http_range: Range HTTP header + :param http_if_range: If-Range HTTP header + :param http_if_modified_since: If-Modified-Since HTTP header + :param http_if_none_match: If-None-Match HTTP header + :param http_if_match: If-Match HTTP header + :param etag: the etag for the response for comparison. + :param data: or alternatively the data of the response to automatically + generate an etag using :func:`generate_etag`. + :param last_modified: an optional date of the last modification. + :param ignore_if_range: If `False`, `If-Range` header will be taken into + account. + :return: `True` if the resource was modified, otherwise `False`. + + .. versionadded:: 2.2 + """ + if etag is None and data is not None: + etag = generate_etag(data) + elif data is not None: + raise TypeError("both data and etag given") + + unmodified = False + if isinstance(last_modified, str): + last_modified = parse_date(last_modified) + + # HTTP doesn't use microsecond, remove it to avoid false positive + # comparisons. Mark naive datetimes as UTC. + if last_modified is not None: + last_modified = _dt_as_utc(last_modified.replace(microsecond=0)) + + if_range = None + if not ignore_if_range and http_range is not None: + # https://tools.ietf.org/html/rfc7233#section-3.2 + # A server MUST ignore an If-Range header field received in a request + # that does not contain a Range header field. + if_range = parse_if_range_header(http_if_range) + + if if_range is not None and if_range.date is not None: + modified_since: datetime | None = if_range.date + else: + modified_since = parse_date(http_if_modified_since) + + if modified_since and last_modified and last_modified <= modified_since: + unmodified = True + + if etag: + etag, _ = unquote_etag(etag) + etag = t.cast(str, etag) + + if if_range is not None and if_range.etag is not None: + unmodified = parse_etags(if_range.etag).contains(etag) + else: + if_none_match = parse_etags(http_if_none_match) + if if_none_match: + # https://tools.ietf.org/html/rfc7232#section-3.2 + # "A recipient MUST use the weak comparison function when comparing + # entity-tags for If-None-Match" + unmodified = if_none_match.contains_weak(etag) + + # https://tools.ietf.org/html/rfc7232#section-3.1 + # "Origin server MUST use the strong comparison function when + # comparing entity-tags for If-Match" + if_match = parse_etags(http_if_match) + if if_match: + unmodified = not if_match.is_strong(etag) + + return not unmodified + + +_cookie_re = re.compile( + r""" + ([^=;]*) + (?:\s*=\s* + ( + "(?:[^\\"]|\\.)*" + | + .*? + ) + )? + \s*;\s* + """, + flags=re.ASCII | re.VERBOSE, +) +_cookie_unslash_re = re.compile(rb"\\([0-3][0-7]{2}|.)") + + +def _cookie_unslash_replace(m: t.Match[bytes]) -> bytes: + v = m.group(1) + + if len(v) == 1: + return v + + return int(v, 8).to_bytes(1, "big") + + +def parse_cookie( + cookie: str | None = None, + cls: type[ds.MultiDict[str, str]] | None = None, +) -> ds.MultiDict[str, str]: + """Parse a cookie from a string. + + The same key can be provided multiple times, the values are stored + in-order. The default :class:`MultiDict` will have the first value + first, and all values can be retrieved with + :meth:`MultiDict.getlist`. + + :param cookie: The cookie header as a string. + :param cls: A dict-like class to store the parsed cookies in. + Defaults to :class:`MultiDict`. + + .. versionchanged:: 3.0 + Passing bytes, and the ``charset`` and ``errors`` parameters, were removed. + + .. versionadded:: 2.2 + """ + if cls is None: + cls = t.cast("type[ds.MultiDict[str, str]]", ds.MultiDict) + + if not cookie: + return cls() + + cookie = f"{cookie};" + out = [] + + for ck, cv in _cookie_re.findall(cookie): + ck = ck.strip() + cv = cv.strip() + + if not ck: + continue + + if len(cv) >= 2 and cv[0] == cv[-1] == '"': + # 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(errors="replace") + + out.append((ck, cv)) + + return cls(out) + + +# circular dependencies +from .. import datastructures as ds diff --git a/venv/Lib/site-packages/werkzeug/sansio/multipart.py b/venv/Lib/site-packages/werkzeug/sansio/multipart.py new file mode 100644 index 00000000..fc873537 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/sansio/multipart.py @@ -0,0 +1,321 @@ +from __future__ import annotations + +import re +import typing as t +from dataclasses import dataclass +from enum import auto +from enum import Enum + +from ..datastructures import Headers +from ..exceptions import RequestEntityTooLarge +from ..http import parse_options_header + + +class Event: + pass + + +@dataclass(frozen=True) +class Preamble(Event): + data: bytes + + +@dataclass(frozen=True) +class Field(Event): + name: str + headers: Headers + + +@dataclass(frozen=True) +class File(Event): + name: str + filename: str + headers: Headers + + +@dataclass(frozen=True) +class Data(Event): + data: bytes + more_data: bool + + +@dataclass(frozen=True) +class Epilogue(Event): + data: bytes + + +class NeedData(Event): + pass + + +NEED_DATA = NeedData() + + +class State(Enum): + PREAMBLE = auto() + PART = auto() + DATA = auto() + DATA_START = auto() + EPILOGUE = auto() + COMPLETE = auto() + + +# Multipart line breaks MUST be CRLF (\r\n) by RFC-7578, except that +# many implementations break this and either use CR or LF alone. +LINE_BREAK = b"(?:\r\n|\n|\r)" +BLANK_LINE_RE = re.compile(b"(?:\r\n\r\n|\r\r|\n\n)", re.MULTILINE) +LINE_BREAK_RE = re.compile(LINE_BREAK, re.MULTILINE) +# Header values can be continued via a space or tab after the linebreak, as +# per RFC2231 +HEADER_CONTINUATION_RE = re.compile(b"%s[ \t]" % LINE_BREAK, re.MULTILINE) +# This must be long enough to contain any line breaks plus any +# additional boundary markers (--) such that they will be found in a +# subsequent search +SEARCH_EXTRA_LENGTH = 8 + + +class MultipartDecoder: + """Decodes a multipart message as bytes into Python events. + + The part data is returned as available to allow the caller to save + the data from memory to disk, if desired. + """ + + def __init__( + self, + boundary: bytes, + max_form_memory_size: int | None = None, + *, + max_parts: int | None = None, + ) -> None: + self.buffer = bytearray() + self.complete = False + self.max_form_memory_size = max_form_memory_size + self.max_parts = max_parts + self.state = State.PREAMBLE + self.boundary = boundary + + # Note in the below \h i.e. horizontal whitespace is used + # as [^\S\n\r] as \h isn't supported in python. + + # The preamble must end with a boundary where the boundary is + # prefixed by a line break, RFC2046. Except that many + # implementations including Werkzeug's tests omit the line + # break prefix. In addition the first boundary could be the + # epilogue boundary (for empty form-data) hence the matching + # group to understand if it is an epilogue boundary. + self.preamble_re = re.compile( + rb"%s?--%s(--[^\S\n\r]*%s?|[^\S\n\r]*%s)" + % (LINE_BREAK, re.escape(boundary), LINE_BREAK, LINE_BREAK), + re.MULTILINE, + ) + # A boundary must include a line break prefix and suffix, and + # may include trailing whitespace. In addition the boundary + # could be the epilogue boundary hence the matching group to + # understand if it is an epilogue boundary. + self.boundary_re = re.compile( + rb"%s--%s(--[^\S\n\r]*%s?|[^\S\n\r]*%s)" + % (LINE_BREAK, re.escape(boundary), LINE_BREAK, LINE_BREAK), + re.MULTILINE, + ) + self._search_position = 0 + self._parts_decoded = 0 + + def last_newline(self, data: bytes) -> int: + try: + last_nl = data.rindex(b"\n") + except ValueError: + last_nl = len(data) + try: + last_cr = data.rindex(b"\r") + except ValueError: + last_cr = len(data) + + return min(last_nl, last_cr) + + def receive_data(self, data: bytes | None) -> None: + if data is None: + self.complete = True + elif ( + self.max_form_memory_size is not None + and len(self.buffer) + len(data) > self.max_form_memory_size + ): + raise RequestEntityTooLarge() + else: + self.buffer.extend(data) + + def next_event(self) -> Event: + event: Event = NEED_DATA + + if self.state == State.PREAMBLE: + match = self.preamble_re.search(self.buffer, self._search_position) + if match is not None: + if match.group(1).startswith(b"--"): + self.state = State.EPILOGUE + else: + self.state = State.PART + data = bytes(self.buffer[: match.start()]) + del self.buffer[: match.end()] + event = Preamble(data=data) + self._search_position = 0 + else: + # Update the search start position to be equal to the + # current buffer length (already searched) minus a + # safe buffer for part of the search target. + self._search_position = max( + 0, len(self.buffer) - len(self.boundary) - SEARCH_EXTRA_LENGTH + ) + + elif self.state == State.PART: + match = BLANK_LINE_RE.search(self.buffer, self._search_position) + if match is not None: + headers = self._parse_headers(self.buffer[: match.start()]) + # The final header ends with a single CRLF, however a + # blank line indicates the start of the + # body. Therefore the end is after the first CRLF. + headers_end = (match.start() + match.end()) // 2 + del self.buffer[:headers_end] + + if "content-disposition" not in headers: + raise ValueError("Missing Content-Disposition header") + + disposition, extra = parse_options_header( + headers["content-disposition"] + ) + name = t.cast(str, extra.get("name")) + filename = extra.get("filename") + if filename is not None: + event = File( + filename=filename, + headers=headers, + name=name, + ) + else: + event = Field( + headers=headers, + name=name, + ) + self.state = State.DATA_START + self._search_position = 0 + self._parts_decoded += 1 + + if self.max_parts is not None and self._parts_decoded > self.max_parts: + raise RequestEntityTooLarge() + else: + # Update the search start position to be equal to the + # current buffer length (already searched) minus a + # safe buffer for part of the search target. + self._search_position = max(0, len(self.buffer) - SEARCH_EXTRA_LENGTH) + + elif self.state == State.DATA_START: + data, del_index, more_data = self._parse_data(self.buffer, start=True) + del self.buffer[:del_index] + event = Data(data=data, more_data=more_data) + if more_data: + self.state = State.DATA + + elif self.state == State.DATA: + data, del_index, more_data = self._parse_data(self.buffer, start=False) + del self.buffer[:del_index] + if data or not more_data: + event = Data(data=data, more_data=more_data) + + elif self.state == State.EPILOGUE and self.complete: + event = Epilogue(data=bytes(self.buffer)) + del self.buffer[:] + self.state = State.COMPLETE + + if self.complete and isinstance(event, NeedData): + raise ValueError(f"Invalid form-data cannot parse beyond {self.state}") + + return event + + def _parse_headers(self, data: bytes) -> Headers: + headers: list[tuple[str, str]] = [] + # Merge the continued headers into one line + data = HEADER_CONTINUATION_RE.sub(b" ", data) + # Now there is one header per line + for line in data.splitlines(): + line = line.strip() + + if line != b"": + name, _, value = line.decode().partition(":") + headers.append((name.strip(), value.strip())) + return Headers(headers) + + def _parse_data(self, data: bytes, *, start: bool) -> tuple[bytes, int, bool]: + # Body parts must start with CRLF (or CR or LF) + if start: + match = LINE_BREAK_RE.match(data) + data_start = t.cast(t.Match[bytes], match).end() + else: + data_start = 0 + + 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) + if match is not None: + if match.group(1).startswith(b"--"): + self.state = State.EPILOGUE + else: + self.state = State.PART + data_end = match.start() + del_index = match.end() + else: + data_end = del_index = self.last_newline(data[data_start:]) + data_start + more_data = match is None + + return bytes(data[data_start:data_end]), del_index, more_data + + +class MultipartEncoder: + def __init__(self, boundary: bytes) -> None: + self.boundary = boundary + self.state = State.PREAMBLE + + def send_event(self, event: Event) -> bytes: + if isinstance(event, Preamble) and self.state == State.PREAMBLE: + self.state = State.PART + return event.data + elif isinstance(event, (Field, File)) and self.state in { + State.PREAMBLE, + State.PART, + State.DATA, + }: + data = b"\r\n--" + self.boundary + b"\r\n" + data += b'Content-Disposition: form-data; name="%s"' % event.name.encode() + if isinstance(event, File): + data += b'; filename="%s"' % event.filename.encode() + data += b"\r\n" + for name, value in t.cast(Field, event).headers: + if name.lower() != "content-disposition": + data += f"{name}: {value}\r\n".encode() + self.state = State.DATA_START + return data + elif isinstance(event, Data) and self.state == State.DATA_START: + self.state = State.DATA + if len(event.data) > 0: + return b"\r\n" + event.data + else: + return event.data + elif isinstance(event, Data) and self.state == State.DATA: + return event.data + elif isinstance(event, Epilogue): + self.state = State.COMPLETE + return b"\r\n--" + self.boundary + b"--\r\n" + event.data + else: + raise ValueError(f"Cannot generate {event} in state: {self.state}") diff --git a/venv/Lib/site-packages/werkzeug/sansio/request.py b/venv/Lib/site-packages/werkzeug/sansio/request.py new file mode 100644 index 00000000..dd0805d7 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/sansio/request.py @@ -0,0 +1,536 @@ +from __future__ import annotations + +import typing as t +from datetime import datetime +from urllib.parse import parse_qsl + +from ..datastructures import Accept +from ..datastructures import Authorization +from ..datastructures import CharsetAccept +from ..datastructures import ETags +from ..datastructures import Headers +from ..datastructures import HeaderSet +from ..datastructures import IfRange +from ..datastructures import ImmutableList +from ..datastructures import ImmutableMultiDict +from ..datastructures import LanguageAccept +from ..datastructures import MIMEAccept +from ..datastructures import MultiDict +from ..datastructures import Range +from ..datastructures import RequestCacheControl +from ..http import parse_accept_header +from ..http import parse_cache_control_header +from ..http import parse_date +from ..http import parse_etags +from ..http import parse_if_range_header +from ..http import parse_list_header +from ..http import parse_options_header +from ..http import parse_range_header +from ..http import parse_set_header +from ..user_agent import UserAgent +from ..utils import cached_property +from ..utils import header_property +from .http import parse_cookie +from .utils import get_content_length +from .utils import get_current_url +from .utils import get_host + + +class Request: + """Represents the non-IO parts of a HTTP request, including the + method, URL info, and headers. + + This class is not meant for general use. It should only be used when + implementing WSGI, ASGI, or another HTTP application spec. Werkzeug + provides a WSGI implementation at :cls:`werkzeug.wrappers.Request`. + + :param method: The method the request was made with, such as + ``GET``. + :param scheme: The URL scheme of the protocol the request used, such + as ``https`` or ``wss``. + :param server: The address of the server. ``(host, port)``, + ``(path, None)`` for unix sockets, or ``None`` if not known. + :param root_path: The prefix that the application is mounted under. + This is prepended to generated URLs, but is not part of route + matching. + :param path: The path part of the URL after ``root_path``. + :param query_string: The part of the URL after the "?". + :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 + """ + + #: 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 + #: :class:`~werkzeug.datastructures.ImmutableOrderedMultiDict` which + #: preserves order or a :class:`~werkzeug.datastructures.ImmutableDict` + #: which is the fastest but only remembers the last key. It is also + #: possible to use mutable structures, but this is not recommended. + #: + #: .. versionadded:: 0.6 + parameter_storage_class: type[MultiDict[str, t.Any]] = ImmutableMultiDict + + #: The type to be used for dict values from the incoming WSGI + #: environment. (For example for :attr:`cookies`.) By default an + #: :class:`~werkzeug.datastructures.ImmutableMultiDict` is used. + #: + #: .. versionchanged:: 1.0.0 + #: Changed to ``ImmutableMultiDict`` to support multiple values. + #: + #: .. versionadded:: 0.6 + dict_storage_class: type[MultiDict[str, t.Any]] = ImmutableMultiDict + + #: the type to be used for list values from the incoming WSGI environment. + #: By default an :class:`~werkzeug.datastructures.ImmutableList` is used + #: (for example for :attr:`access_list`). + #: + #: .. versionadded:: 0.6 + list_storage_class: type[list[t.Any]] = ImmutableList + + user_agent_class: type[UserAgent] = UserAgent + """The class used and returned by the :attr:`user_agent` property to + parse the header. Defaults to + :class:`~werkzeug.user_agent.UserAgent`, which does no parsing. An + extension can provide a subclass that uses a parser to provide other + data. + + .. versionadded:: 2.0 + """ + + #: Valid host names when handling requests. By default all hosts are + #: trusted, which means that whatever the client says the host is + #: will be accepted. + #: + #: Because ``Host`` and ``X-Forwarded-Host`` headers can be set to + #: any value by a malicious client, it is recommended to either set + #: this property or implement similar validation in the proxy (if + #: the application is being run behind one). + #: + #: .. versionadded:: 0.9 + trusted_hosts: list[str] | None = None + + def __init__( + self, + method: str, + scheme: str, + server: tuple[str, int | None] | None, + root_path: str, + path: str, + query_string: bytes, + headers: Headers, + remote_addr: str | None, + ) -> None: + #: 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 + #: ``https`` or ``wss``. + self.scheme = scheme + #: The address of the server. ``(host, port)``, ``(path, None)`` + #: for unix sockets, or ``None`` if not known. + self.server = server + #: The prefix that the application is mounted under, without a + #: trailing slash. :attr:`path` comes after this. + self.root_path = root_path.rstrip("/") + #: The path part of the URL after :attr:`root_path`. This is the + #: path used for routing within the application. + self.path = "/" + path.lstrip("/") + #: The part of the URL after the "?". This is the raw value, use + #: :attr:`args` for the parsed values. + self.query_string = query_string + #: The headers received with the request. + self.headers = headers + #: The address of the client sending the request. + self.remote_addr = remote_addr + + def __repr__(self) -> str: + try: + url = self.url + except Exception as e: + url = f"(invalid URL: {e})" + + return f"<{type(self).__name__} {url!r} [{self.method}]>" + + @cached_property + def args(self) -> MultiDict[str, str]: + """The parsed URL parameters (the part in the URL after the question + mark). + + By default an + :class:`~werkzeug.datastructures.ImmutableMultiDict` + is returned from this function. This can be changed by setting + :attr:`parameter_storage_class` to a different type. This might + be necessary if the order of the form data is important. + + .. versionchanged:: 2.3 + Invalid bytes remain percent encoded. + """ + return self.parameter_storage_class( + parse_qsl( + self.query_string.decode(), + keep_blank_values=True, + errors="werkzeug.url_quote", + ) + ) + + @cached_property + def access_route(self) -> list[str]: + """If a forwarded header exists this is a list of all ip addresses + from the client ip to the last proxy server. + """ + if "X-Forwarded-For" in self.headers: + return self.list_storage_class( + parse_list_header(self.headers["X-Forwarded-For"]) + ) + elif self.remote_addr is not None: + return self.list_storage_class([self.remote_addr]) + return self.list_storage_class() + + @cached_property + def full_path(self) -> str: + """Requested path, including the query string.""" + return f"{self.path}?{self.query_string.decode()}" + + @property + def is_secure(self) -> bool: + """``True`` if the request was made with a secure protocol + (HTTPS or WSS). + """ + return self.scheme in {"https", "wss"} + + @cached_property + def url(self) -> str: + """The full request URL with the scheme, host, root path, path, + and query string.""" + return get_current_url( + self.scheme, self.host, self.root_path, self.path, self.query_string + ) + + @cached_property + def base_url(self) -> str: + """Like :attr:`url` but without the query string.""" + return get_current_url(self.scheme, self.host, self.root_path, self.path) + + @cached_property + def root_url(self) -> str: + """The request URL scheme, host, and root path. This is the root + that the application is accessed from. + """ + return get_current_url(self.scheme, self.host, self.root_path) + + @cached_property + def host_url(self) -> str: + """The request URL scheme and host only.""" + return get_current_url(self.scheme, self.host) + + @cached_property + def host(self) -> str: + """The host name the request was made to, including the port if + it's non-standard. Validated with :attr:`trusted_hosts`. + """ + return get_host( + self.scheme, self.headers.get("host"), self.server, self.trusted_hosts + ) + + @cached_property + def cookies(self) -> ImmutableMultiDict[str, str]: + """A :class:`dict` with the contents of all cookies transmitted with + the request.""" + wsgi_combined_cookie = ";".join(self.headers.getlist("Cookie")) + return parse_cookie( # type: ignore + wsgi_combined_cookie, cls=self.dict_storage_class + ) + + # Common Descriptors + + content_type = header_property[str]( + "Content-Type", + doc="""The Content-Type entity-header field indicates the media + type of the entity-body sent to the recipient or, in the case of + the HEAD method, the media type that would have been sent had + the request been a GET.""", + read_only=True, + ) + + @cached_property + def content_length(self) -> int | None: + """The Content-Length entity-header field indicates the size of the + entity-body in bytes or, in the case of the HEAD method, the size of + the entity-body that would have been sent had the request been a + GET. + """ + return get_content_length( + http_content_length=self.headers.get("Content-Length"), + http_transfer_encoding=self.headers.get("Transfer-Encoding"), + ) + + content_encoding = header_property[str]( + "Content-Encoding", + doc="""The Content-Encoding entity-header field is used as a + modifier to the media-type. When present, its value indicates + what additional content codings have been applied to the + entity-body, and thus what decoding mechanisms must be applied + in order to obtain the media-type referenced by the Content-Type + header field. + + .. versionadded:: 0.9""", + read_only=True, + ) + content_md5 = header_property[str]( + "Content-MD5", + doc="""The Content-MD5 entity-header field, as defined in + RFC 1864, is an MD5 digest of the entity-body for the purpose of + providing an end-to-end message integrity check (MIC) of the + entity-body. (Note: a MIC is good for detecting accidental + modification of the entity-body in transit, but is not proof + against malicious attacks.) + + .. versionadded:: 0.9""", + read_only=True, + ) + referrer = header_property[str]( + "Referer", + doc="""The Referer[sic] request-header field allows the client + to specify, for the server's benefit, the address (URI) of the + resource from which the Request-URI was obtained (the + "referrer", although the header field is misspelled).""", + read_only=True, + ) + date = header_property( + "Date", + None, + parse_date, + doc="""The Date general-header field represents the date and + time at which the message was originated, having the same + semantics as orig-date in RFC 822. + + .. versionchanged:: 2.0 + The datetime object is timezone-aware. + """, + read_only=True, + ) + max_forwards = header_property( + "Max-Forwards", + None, + int, + doc="""The Max-Forwards request-header field provides a + mechanism with the TRACE and OPTIONS methods to limit the number + of proxies or gateways that can forward the request to the next + inbound server.""", + read_only=True, + ) + + def _parse_content_type(self) -> None: + if not hasattr(self, "_parsed_content_type"): + self._parsed_content_type = parse_options_header( + self.headers.get("Content-Type", "") + ) + + @property + def mimetype(self) -> str: + """Like :attr:`content_type`, but without parameters (eg, without + charset, type etc.) and always lowercase. For example if the content + type is ``text/HTML; charset=utf-8`` the mimetype would be + ``'text/html'``. + """ + self._parse_content_type() + return self._parsed_content_type[0].lower() + + @property + def mimetype_params(self) -> dict[str, str]: + """The mimetype parameters as dict. For example if the content + type is ``text/html; charset=utf-8`` the params would be + ``{'charset': 'utf-8'}``. + """ + self._parse_content_type() + return self._parsed_content_type[1] + + @cached_property + def pragma(self) -> HeaderSet: + """The Pragma general-header field is used to include + implementation-specific directives that might apply to any recipient + along the request/response chain. All pragma directives specify + optional behavior from the viewpoint of the protocol; however, some + systems MAY require that behavior be consistent with the directives. + """ + return parse_set_header(self.headers.get("Pragma", "")) + + # Accept + + @cached_property + def accept_mimetypes(self) -> MIMEAccept: + """List of mimetypes this client supports as + :class:`~werkzeug.datastructures.MIMEAccept` object. + """ + return parse_accept_header(self.headers.get("Accept"), MIMEAccept) + + @cached_property + def accept_charsets(self) -> CharsetAccept: + """List of charsets this client supports as + :class:`~werkzeug.datastructures.CharsetAccept` object. + """ + return parse_accept_header(self.headers.get("Accept-Charset"), CharsetAccept) + + @cached_property + def accept_encodings(self) -> Accept: + """List of encodings this client accepts. Encodings in a HTTP term + are compression encodings such as gzip. For charsets have a look at + :attr:`accept_charset`. + """ + return parse_accept_header(self.headers.get("Accept-Encoding")) + + @cached_property + def accept_languages(self) -> LanguageAccept: + """List of languages this client accepts as + :class:`~werkzeug.datastructures.LanguageAccept` object. + + .. versionchanged 0.5 + In previous versions this was a regular + :class:`~werkzeug.datastructures.Accept` object. + """ + return parse_accept_header(self.headers.get("Accept-Language"), LanguageAccept) + + # ETag + + @cached_property + def cache_control(self) -> RequestCacheControl: + """A :class:`~werkzeug.datastructures.RequestCacheControl` object + for the incoming cache control headers. + """ + cache_control = self.headers.get("Cache-Control") + return parse_cache_control_header(cache_control, None, RequestCacheControl) + + @cached_property + def if_match(self) -> ETags: + """An object containing all the etags in the `If-Match` header. + + :rtype: :class:`~werkzeug.datastructures.ETags` + """ + return parse_etags(self.headers.get("If-Match")) + + @cached_property + def if_none_match(self) -> ETags: + """An object containing all the etags in the `If-None-Match` header. + + :rtype: :class:`~werkzeug.datastructures.ETags` + """ + return parse_etags(self.headers.get("If-None-Match")) + + @cached_property + def if_modified_since(self) -> datetime | None: + """The parsed `If-Modified-Since` header as a datetime object. + + .. versionchanged:: 2.0 + The datetime object is timezone-aware. + """ + return parse_date(self.headers.get("If-Modified-Since")) + + @cached_property + def if_unmodified_since(self) -> datetime | None: + """The parsed `If-Unmodified-Since` header as a datetime object. + + .. versionchanged:: 2.0 + The datetime object is timezone-aware. + """ + return parse_date(self.headers.get("If-Unmodified-Since")) + + @cached_property + def if_range(self) -> IfRange: + """The parsed ``If-Range`` header. + + .. versionchanged:: 2.0 + ``IfRange.date`` is timezone-aware. + + .. versionadded:: 0.7 + """ + return parse_if_range_header(self.headers.get("If-Range")) + + @cached_property + def range(self) -> Range | None: + """The parsed `Range` header. + + .. versionadded:: 0.7 + + :rtype: :class:`~werkzeug.datastructures.Range` + """ + return parse_range_header(self.headers.get("Range")) + + # User Agent + + @cached_property + def user_agent(self) -> UserAgent: + """The user agent. Use ``user_agent.string`` to get the header + value. Set :attr:`user_agent_class` to a subclass of + :class:`~werkzeug.user_agent.UserAgent` to provide parsing for + the other properties or other extended data. + + .. versionchanged:: 2.1 + The built-in parser was removed. Set ``user_agent_class`` to a ``UserAgent`` + subclass to parse data from the string. + """ + return self.user_agent_class(self.headers.get("User-Agent", "")) + + # Authorization + + @cached_property + def authorization(self) -> Authorization | None: + """The ``Authorization`` header parsed into an :class:`.Authorization` object. + ``None`` if the header is not present. + + .. versionchanged:: 2.3 + :class:`Authorization` is no longer a ``dict``. The ``token`` attribute + was added for auth schemes that use a token instead of parameters. + """ + return Authorization.from_header(self.headers.get("Authorization")) + + # CORS + + origin = header_property[str]( + "Origin", + doc=( + "The host that the request originated from. Set" + " :attr:`~CORSResponseMixin.access_control_allow_origin` on" + " the response to indicate which origins are allowed." + ), + read_only=True, + ) + + access_control_request_headers = header_property( + "Access-Control-Request-Headers", + load_func=parse_set_header, + doc=( + "Sent with a preflight request to indicate which headers" + " will be sent with the cross origin request. Set" + " :attr:`~CORSResponseMixin.access_control_allow_headers`" + " on the response to indicate which headers are allowed." + ), + read_only=True, + ) + + access_control_request_method = header_property[str]( + "Access-Control-Request-Method", + doc=( + "Sent with a preflight request to indicate which method" + " will be used for the cross origin request. Set" + " :attr:`~CORSResponseMixin.access_control_allow_methods`" + " on the response to indicate which methods are allowed." + ), + read_only=True, + ) + + @property + def is_json(self) -> bool: + """Check if the mimetype indicates JSON data, either + :mimetype:`application/json` or :mimetype:`application/*+json`. + """ + mt = self.mimetype + return ( + mt == "application/json" + or mt.startswith("application/") + and mt.endswith("+json") + ) diff --git a/venv/Lib/site-packages/werkzeug/sansio/response.py b/venv/Lib/site-packages/werkzeug/sansio/response.py new file mode 100644 index 00000000..9093b0a8 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/sansio/response.py @@ -0,0 +1,754 @@ +from __future__ import annotations + +import typing as t +from datetime import datetime +from datetime import timedelta +from datetime import timezone +from http import HTTPStatus + +from ..datastructures import CallbackDict +from ..datastructures import ContentRange +from ..datastructures import ContentSecurityPolicy +from ..datastructures import Headers +from ..datastructures import HeaderSet +from ..datastructures import ResponseCacheControl +from ..datastructures import WWWAuthenticate +from ..http import COEP +from ..http import COOP +from ..http import dump_age +from ..http import dump_cookie +from ..http import dump_header +from ..http import dump_options_header +from ..http import http_date +from ..http import HTTP_STATUS_CODES +from ..http import parse_age +from ..http import parse_cache_control_header +from ..http import parse_content_range_header +from ..http import parse_csp_header +from ..http import parse_date +from ..http import parse_options_header +from ..http import parse_set_header +from ..http import quote_etag +from ..http import unquote_etag +from ..utils import get_content_type +from ..utils import header_property + +if t.TYPE_CHECKING: + from ..datastructures.cache_control import _CacheControl + + +def _set_property(name: str, doc: str | None = None) -> property: + def fget(self: Response) -> HeaderSet: + def on_update(header_set: HeaderSet) -> None: + if not header_set and name in self.headers: + del self.headers[name] + elif header_set: + self.headers[name] = header_set.to_header() + + return parse_set_header(self.headers.get(name), on_update) + + def fset( + self: Response, + value: None | (str | dict[str, str | int] | t.Iterable[str]), + ) -> None: + if not value: + del self.headers[name] + elif isinstance(value, str): + self.headers[name] = value + else: + self.headers[name] = dump_header(value) + + return property(fget, fset, doc=doc) + + +class Response: + """Represents the non-IO parts of an HTTP response, specifically the + status and headers but not the body. + + This class is not meant for general use. It should only be used when + implementing WSGI, ASGI, or another HTTP application spec. Werkzeug + provides a WSGI implementation at :cls:`werkzeug.wrappers.Response`. + + :param status: The status code for the response. Either an int, in + which case the default status message is added, or a string in + the form ``{code} {message}``, like ``404 Not Found``. Defaults + to 200. + :param headers: A :class:`~werkzeug.datastructures.Headers` object, + or a list of ``(key, value)`` tuples that will be converted to a + ``Headers`` object. + :param mimetype: The mime type (content type without charset or + other parameters) of the response. If the value starts with + ``text/`` (or matches some other special cases), the charset + will be added to create the ``content_type``. + :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 + """ + + #: the default status if none is provided. + default_status = 200 + + #: the default mimetype if none is provided. + default_mimetype: str | None = "text/plain" + + #: Warn if a cookie header exceeds this size. The default, 4093, should be + #: safely `supported by most browsers `_. A cookie larger than + #: this size will still be sent, but it may be ignored or handled + #: incorrectly by some browsers. Set to 0 to disable this check. + #: + #: .. versionadded:: 0.13 + #: + #: .. _`cookie`: http://browsercookielimits.squawky.net/ + max_cookie_size = 4093 + + # A :class:`Headers` object representing the response headers. + headers: Headers + + def __init__( + self, + status: int | str | HTTPStatus | None = None, + headers: t.Mapping[str, str | t.Iterable[str]] + | t.Iterable[tuple[str, str]] + | None = None, + mimetype: str | None = None, + content_type: str | None = None, + ) -> None: + if isinstance(headers, Headers): + self.headers = headers + elif not headers: + self.headers = Headers() + else: + self.headers = Headers(headers) + + if content_type is None: + 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, "utf-8") + content_type = mimetype + if content_type is not None: + self.headers["Content-Type"] = content_type + if status is None: + status = self.default_status + self.status = status # type: ignore + + def __repr__(self) -> str: + return f"<{type(self).__name__} [{self.status}]>" + + @property + def status_code(self) -> int: + """The HTTP status code as a number.""" + return self._status_code + + @status_code.setter + def status_code(self, code: int) -> None: + self.status = code # type: ignore + + @property + def status(self) -> str: + """The HTTP status code as a string.""" + return self._status + + @status.setter + def status(self, value: str | int | HTTPStatus) -> None: + self._status, self._status_code = self._clean_status(value) + + def _clean_status(self, value: str | int | HTTPStatus) -> tuple[str, int]: + if isinstance(value, (int, HTTPStatus)): + status_code = int(value) + else: + value = value.strip() + + if not value: + raise ValueError("Empty status argument") + + code_str, sep, _ = value.partition(" ") + + try: + status_code = int(code_str) + except ValueError: + # only message + return f"0 {value}", 0 + + if sep: + # code and message + return value, status_code + + # only code, look up message + try: + status = f"{status_code} {HTTP_STATUS_CODES[status_code].upper()}" + except KeyError: + status = f"{status_code} UNKNOWN" + + return status, status_code + + def set_cookie( + self, + key: str, + value: str = "", + max_age: timedelta | int | None = None, + expires: str | datetime | int | float | None = None, + path: str | None = "/", + domain: str | None = None, + secure: bool = False, + httponly: bool = False, + samesite: str | None = None, + ) -> None: + """Sets a cookie. + + A warning is raised if the size of the cookie header exceeds + :attr:`max_cookie_size`, but the header will still be set. + + :param key: the key (name) of the cookie to be set. + :param value: the value of the cookie. + :param max_age: should be a number of seconds, or `None` (default) if + the cookie should last only as long as the client's + browser session. + :param expires: should be a `datetime` object or UNIX timestamp. + :param path: limits the cookie to a given path, per default it will + span the whole domain. + :param domain: if you want to set a cross-domain cookie. For example, + ``domain="example.com"`` will set a cookie that is + readable by the domain ``www.example.com``, + ``foo.example.com`` etc. Otherwise, a cookie will only + be readable by the domain that set it. + :param secure: If ``True``, the cookie will only be available + via HTTPS. + :param httponly: Disallow JavaScript access to the cookie. + :param samesite: Limit the scope of the cookie to only be + attached to requests that are "same-site". + """ + self.headers.add( + "Set-Cookie", + dump_cookie( + key, + value=value, + max_age=max_age, + expires=expires, + path=path, + domain=domain, + secure=secure, + httponly=httponly, + max_size=self.max_cookie_size, + samesite=samesite, + ), + ) + + def delete_cookie( + self, + key: str, + path: str | None = "/", + domain: str | None = None, + secure: bool = False, + httponly: bool = False, + samesite: str | None = None, + ) -> None: + """Delete a cookie. Fails silently if key doesn't exist. + + :param key: the key (name) of the cookie to be deleted. + :param path: if the cookie that should be deleted was limited to a + path, the path has to be defined here. + :param domain: if the cookie that should be deleted was limited to a + domain, that domain has to be defined here. + :param secure: If ``True``, the cookie will only be available + via HTTPS. + :param httponly: Disallow JavaScript access to the cookie. + :param samesite: Limit the scope of the cookie to only be + attached to requests that are "same-site". + """ + self.set_cookie( + key, + expires=0, + max_age=0, + path=path, + domain=domain, + secure=secure, + httponly=httponly, + samesite=samesite, + ) + + @property + def is_json(self) -> bool: + """Check if the mimetype indicates JSON data, either + :mimetype:`application/json` or :mimetype:`application/*+json`. + """ + mt = self.mimetype + return mt is not None and ( + mt == "application/json" + or mt.startswith("application/") + and mt.endswith("+json") + ) + + # Common Descriptors + + @property + def mimetype(self) -> str | None: + """The mimetype (content type without charset etc.)""" + ct = self.headers.get("content-type") + + if ct: + return ct.split(";")[0].strip() + else: + return None + + @mimetype.setter + def mimetype(self, value: str) -> None: + self.headers["Content-Type"] = get_content_type(value, "utf-8") + + @property + def mimetype_params(self) -> dict[str, str]: + """The mimetype parameters as dict. For example if the + content type is ``text/html; charset=utf-8`` the params would be + ``{'charset': 'utf-8'}``. + + .. versionadded:: 0.5 + """ + + def on_update(d: CallbackDict[str, str]) -> None: + self.headers["Content-Type"] = dump_options_header(self.mimetype, d) + + d = parse_options_header(self.headers.get("content-type", ""))[1] + return CallbackDict(d, on_update) + + location = header_property[str]( + "Location", + doc="""The Location response-header field is used to redirect + the recipient to a location other than the Request-URI for + completion of the request or identification of a new + resource.""", + ) + age = header_property( + "Age", + None, + parse_age, + dump_age, # type: ignore + doc="""The Age response-header field conveys the sender's + estimate of the amount of time since the response (or its + revalidation) was generated at the origin server. + + Age values are non-negative decimal integers, representing time + in seconds.""", + ) + content_type = header_property[str]( + "Content-Type", + doc="""The Content-Type entity-header field indicates the media + type of the entity-body sent to the recipient or, in the case of + the HEAD method, the media type that would have been sent had + the request been a GET.""", + ) + content_length = header_property( + "Content-Length", + None, + int, + str, + doc="""The Content-Length entity-header field indicates the size + of the entity-body, in decimal number of OCTETs, sent to the + recipient or, in the case of the HEAD method, the size of the + entity-body that would have been sent had the request been a + GET.""", + ) + content_location = header_property[str]( + "Content-Location", + doc="""The Content-Location entity-header field MAY be used to + supply the resource location for the entity enclosed in the + message when that entity is accessible from a location separate + from the requested resource's URI.""", + ) + content_encoding = header_property[str]( + "Content-Encoding", + doc="""The Content-Encoding entity-header field is used as a + modifier to the media-type. When present, its value indicates + what additional content codings have been applied to the + entity-body, and thus what decoding mechanisms must be applied + in order to obtain the media-type referenced by the Content-Type + header field.""", + ) + content_md5 = header_property[str]( + "Content-MD5", + doc="""The Content-MD5 entity-header field, as defined in + RFC 1864, is an MD5 digest of the entity-body for the purpose of + providing an end-to-end message integrity check (MIC) of the + entity-body. (Note: a MIC is good for detecting accidental + modification of the entity-body in transit, but is not proof + against malicious attacks.)""", + ) + date = header_property( + "Date", + None, + parse_date, + http_date, + doc="""The Date general-header field represents the date and + time at which the message was originated, having the same + semantics as orig-date in RFC 822. + + .. versionchanged:: 2.0 + The datetime object is timezone-aware. + """, + ) + expires = header_property( + "Expires", + None, + parse_date, + http_date, + doc="""The Expires entity-header field gives the date/time after + which the response is considered stale. A stale cache entry may + not normally be returned by a cache. + + .. versionchanged:: 2.0 + The datetime object is timezone-aware. + """, + ) + last_modified = header_property( + "Last-Modified", + None, + parse_date, + http_date, + doc="""The Last-Modified entity-header field indicates the date + and time at which the origin server believes the variant was + last modified. + + .. versionchanged:: 2.0 + The datetime object is timezone-aware. + """, + ) + + @property + def retry_after(self) -> datetime | None: + """The Retry-After response-header field can be used with a + 503 (Service Unavailable) response to indicate how long the + service is expected to be unavailable to the requesting client. + + Time in seconds until expiration or date. + + .. versionchanged:: 2.0 + The datetime object is timezone-aware. + """ + value = self.headers.get("retry-after") + if value is None: + return None + + try: + seconds = int(value) + except ValueError: + return parse_date(value) + + return datetime.now(timezone.utc) + timedelta(seconds=seconds) + + @retry_after.setter + def retry_after(self, value: datetime | int | str | None) -> None: + if value is None: + if "retry-after" in self.headers: + del self.headers["retry-after"] + return + elif isinstance(value, datetime): + value = http_date(value) + else: + value = str(value) + self.headers["Retry-After"] = value + + vary = _set_property( + "Vary", + doc="""The Vary field value indicates the set of request-header + fields that fully determines, while the response is fresh, + whether a cache is permitted to use the response to reply to a + subsequent request without revalidation.""", + ) + content_language = _set_property( + "Content-Language", + doc="""The Content-Language entity-header field describes the + natural language(s) of the intended audience for the enclosed + entity. Note that this might not be equivalent to all the + languages used within the entity-body.""", + ) + allow = _set_property( + "Allow", + doc="""The Allow entity-header field lists the set of methods + supported by the resource identified by the Request-URI. The + purpose of this field is strictly to inform the recipient of + valid methods associated with the resource. An Allow header + field MUST be present in a 405 (Method Not Allowed) + response.""", + ) + + # ETag + + @property + def cache_control(self) -> ResponseCacheControl: + """The Cache-Control general-header field is used to specify + directives that MUST be obeyed by all caching mechanisms along the + request/response chain. + """ + + def on_update(cache_control: _CacheControl) -> None: + if not cache_control and "cache-control" in self.headers: + del self.headers["cache-control"] + elif cache_control: + self.headers["Cache-Control"] = cache_control.to_header() + + return parse_cache_control_header( + self.headers.get("cache-control"), on_update, ResponseCacheControl + ) + + def set_etag(self, etag: str, weak: bool = False) -> None: + """Set the etag, and override the old one if there was one.""" + self.headers["ETag"] = quote_etag(etag, weak) + + def get_etag(self) -> tuple[str, bool] | tuple[None, None]: + """Return a tuple in the form ``(etag, is_weak)``. If there is no + ETag the return value is ``(None, None)``. + """ + return unquote_etag(self.headers.get("ETag")) + + accept_ranges = header_property[str]( + "Accept-Ranges", + doc="""The `Accept-Ranges` header. Even though the name would + indicate that multiple values are supported, it must be one + string token only. + + The values ``'bytes'`` and ``'none'`` are common. + + .. versionadded:: 0.7""", + ) + + @property + def content_range(self) -> ContentRange: + """The ``Content-Range`` header as a + :class:`~werkzeug.datastructures.ContentRange` object. Available + even if the header is not set. + + .. versionadded:: 0.7 + """ + + def on_update(rng: ContentRange) -> None: + if not rng: + del self.headers["content-range"] + else: + self.headers["Content-Range"] = rng.to_header() + + rv = parse_content_range_header(self.headers.get("content-range"), on_update) + # always provide a content range object to make the descriptor + # more user friendly. It provides an unset() method that can be + # used to remove the header quickly. + if rv is None: + rv = ContentRange(None, None, None, on_update=on_update) + return rv + + @content_range.setter + def content_range(self, value: ContentRange | str | None) -> None: + if not value: + del self.headers["content-range"] + elif isinstance(value, str): + self.headers["Content-Range"] = value + else: + self.headers["Content-Range"] = value.to_header() + + # Authorization + + @property + def www_authenticate(self) -> WWWAuthenticate: + """The ``WWW-Authenticate`` header parsed into a :class:`.WWWAuthenticate` + object. Modifying the object will modify the header value. + + This header is not set by default. To set this header, assign an instance of + :class:`.WWWAuthenticate` to this attribute. + + .. code-block:: python + + response.www_authenticate = WWWAuthenticate( + "basic", {"realm": "Authentication Required"} + ) + + Multiple values for this header can be sent to give the client multiple options. + Assign a list to set multiple headers. However, modifying the items in the list + will not automatically update the header values, and accessing this attribute + will only ever return the first value. + + To unset this header, assign ``None`` or use ``del``. + + .. versionchanged:: 2.3 + This attribute can be assigned to to set the header. A list can be assigned + to set multiple header values. Use ``del`` to unset the header. + + .. versionchanged:: 2.3 + :class:`WWWAuthenticate` is no longer a ``dict``. The ``token`` attribute + was added for auth challenges that use a token instead of parameters. + """ + value = WWWAuthenticate.from_header(self.headers.get("WWW-Authenticate")) + + if value is None: + value = WWWAuthenticate("basic") + + def on_update(value: WWWAuthenticate) -> None: + self.www_authenticate = value + + value._on_update = on_update + return value + + @www_authenticate.setter + def www_authenticate( + self, value: WWWAuthenticate | list[WWWAuthenticate] | None + ) -> None: + if not value: # None or empty list + del self.www_authenticate + elif isinstance(value, list): + # Clear any existing header by setting the first item. + self.headers.set("WWW-Authenticate", value[0].to_header()) + + for item in value[1:]: + # Add additional header lines for additional items. + self.headers.add("WWW-Authenticate", item.to_header()) + else: + self.headers.set("WWW-Authenticate", value.to_header()) + + def on_update(value: WWWAuthenticate) -> None: + self.www_authenticate = value + + # When setting a single value, allow updating it directly. + value._on_update = on_update + + @www_authenticate.deleter + def www_authenticate(self) -> None: + if "WWW-Authenticate" in self.headers: + del self.headers["WWW-Authenticate"] + + # CSP + + @property + def content_security_policy(self) -> ContentSecurityPolicy: + """The ``Content-Security-Policy`` header as a + :class:`~werkzeug.datastructures.ContentSecurityPolicy` object. Available + even if the header is not set. + + The Content-Security-Policy header adds an additional layer of + security to help detect and mitigate certain types of attacks. + """ + + def on_update(csp: ContentSecurityPolicy) -> None: + if not csp: + del self.headers["content-security-policy"] + else: + self.headers["Content-Security-Policy"] = csp.to_header() + + rv = parse_csp_header(self.headers.get("content-security-policy"), on_update) + if rv is None: + rv = ContentSecurityPolicy(None, on_update=on_update) + return rv + + @content_security_policy.setter + def content_security_policy( + self, value: ContentSecurityPolicy | str | None + ) -> None: + if not value: + del self.headers["content-security-policy"] + elif isinstance(value, str): + self.headers["Content-Security-Policy"] = value + else: + self.headers["Content-Security-Policy"] = value.to_header() + + @property + def content_security_policy_report_only(self) -> ContentSecurityPolicy: + """The ``Content-Security-policy-report-only`` header as a + :class:`~werkzeug.datastructures.ContentSecurityPolicy` object. Available + even if the header is not set. + + The Content-Security-Policy-Report-Only header adds a csp policy + that is not enforced but is reported thereby helping detect + certain types of attacks. + """ + + def on_update(csp: ContentSecurityPolicy) -> None: + if not csp: + del self.headers["content-security-policy-report-only"] + else: + self.headers["Content-Security-policy-report-only"] = csp.to_header() + + rv = parse_csp_header( + self.headers.get("content-security-policy-report-only"), on_update + ) + if rv is None: + rv = ContentSecurityPolicy(None, on_update=on_update) + return rv + + @content_security_policy_report_only.setter + def content_security_policy_report_only( + self, value: ContentSecurityPolicy | str | None + ) -> None: + if not value: + del self.headers["content-security-policy-report-only"] + elif isinstance(value, str): + self.headers["Content-Security-policy-report-only"] = value + else: + self.headers["Content-Security-policy-report-only"] = value.to_header() + + # CORS + + @property + def access_control_allow_credentials(self) -> bool: + """Whether credentials can be shared by the browser to + JavaScript code. As part of the preflight request it indicates + whether credentials can be used on the cross origin request. + """ + return "Access-Control-Allow-Credentials" in self.headers + + @access_control_allow_credentials.setter + def access_control_allow_credentials(self, value: bool | None) -> None: + if value is True: + self.headers["Access-Control-Allow-Credentials"] = "true" + else: + self.headers.pop("Access-Control-Allow-Credentials", None) + + access_control_allow_headers = header_property( + "Access-Control-Allow-Headers", + load_func=parse_set_header, + dump_func=dump_header, + doc="Which headers can be sent with the cross origin request.", + ) + + access_control_allow_methods = header_property( + "Access-Control-Allow-Methods", + load_func=parse_set_header, + dump_func=dump_header, + doc="Which methods can be used for the cross origin request.", + ) + + access_control_allow_origin = header_property[str]( + "Access-Control-Allow-Origin", + doc="The origin or '*' for any origin that may make cross origin requests.", + ) + + access_control_expose_headers = header_property( + "Access-Control-Expose-Headers", + load_func=parse_set_header, + dump_func=dump_header, + doc="Which headers can be shared by the browser to JavaScript code.", + ) + + access_control_max_age = header_property( + "Access-Control-Max-Age", + load_func=int, + dump_func=str, + doc="The maximum age in seconds the access control settings can be cached for.", + ) + + cross_origin_opener_policy = header_property[COOP]( + "Cross-Origin-Opener-Policy", + load_func=lambda value: COOP(value), + dump_func=lambda value: value.value, + default=COOP.UNSAFE_NONE, + doc="""Allows control over sharing of browsing context group with cross-origin + documents. Values must be a member of the :class:`werkzeug.http.COOP` enum.""", + ) + + cross_origin_embedder_policy = header_property[COEP]( + "Cross-Origin-Embedder-Policy", + load_func=lambda value: COEP(value), + dump_func=lambda value: value.value, + default=COEP.UNSAFE_NONE, + doc="""Prevents a document from loading any cross-origin resources that do not + explicitly grant the document permission. Values must be a member of the + :class:`werkzeug.http.COEP` enum.""", + ) diff --git a/venv/Lib/site-packages/werkzeug/sansio/utils.py b/venv/Lib/site-packages/werkzeug/sansio/utils.py new file mode 100644 index 00000000..14fa0ac8 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/sansio/utils.py @@ -0,0 +1,159 @@ +from __future__ import annotations + +import typing as t +from urllib.parse import quote + +from .._internal import _plain_int +from ..exceptions import SecurityError +from ..urls import uri_to_iri + + +def host_is_trusted(hostname: str | None, trusted_list: t.Iterable[str]) -> bool: + """Check if a host matches a list of trusted names. + + :param hostname: The name to check. + :param trusted_list: A list of valid names to match. If a name + starts with a dot it will match all subdomains. + + .. versionadded:: 0.9 + """ + if not hostname: + return False + + try: + hostname = hostname.partition(":")[0].encode("idna").decode("ascii") + except UnicodeEncodeError: + return False + + if isinstance(trusted_list, str): + trusted_list = [trusted_list] + + for ref in trusted_list: + if ref.startswith("."): + ref = ref[1:] + suffix_match = True + else: + suffix_match = False + + try: + ref = ref.partition(":")[0].encode("idna").decode("ascii") + except UnicodeEncodeError: + return False + + if ref == hostname or (suffix_match and hostname.endswith(f".{ref}")): + return True + + return False + + +def get_host( + scheme: str, + host_header: str | None, + server: tuple[str, int | None] | None = None, + trusted_hosts: t.Iterable[str] | None = None, +) -> str: + """Return the host for the given parameters. + + This first checks the ``host_header``. If it's not present, then + ``server`` is used. The host will only contain the port if it is + different than the standard port for the protocol. + + Optionally, verify that the host is trusted using + :func:`host_is_trusted` and raise a + :exc:`~werkzeug.exceptions.SecurityError` if it is not. + + :param scheme: The protocol the request used, like ``"https"``. + :param host_header: The ``Host`` header value. + :param server: Address of the server. ``(host, port)``, or + ``(path, None)`` for unix sockets. + :param trusted_hosts: A list of trusted host names. + + :return: Host, with port if necessary. + :raise ~werkzeug.exceptions.SecurityError: If the host is not + trusted. + """ + host = "" + + if host_header is not None: + host = host_header + elif server is not None: + host = server[0] + + if server[1] is not None: + host = f"{host}:{server[1]}" + + if scheme in {"http", "ws"} and host.endswith(":80"): + host = host[:-3] + elif scheme in {"https", "wss"} and host.endswith(":443"): + host = host[:-4] + + if trusted_hosts is not None: + if not host_is_trusted(host, trusted_hosts): + raise SecurityError(f"Host {host!r} is not trusted.") + + return host + + +def get_current_url( + scheme: str, + host: str, + root_path: str | None = None, + path: str | None = None, + query_string: bytes | None = None, +) -> str: + """Recreate the URL for a request. If an optional part isn't + provided, it and subsequent parts are not included in the URL. + + The URL is an IRI, not a URI, so it may contain Unicode characters. + Use :func:`~werkzeug.urls.iri_to_uri` to convert it to ASCII. + + :param scheme: The protocol the request used, like ``"https"``. + :param host: The host the request was made to. See :func:`get_host`. + :param root_path: Prefix that the application is mounted under. This + is prepended to ``path``. + :param path: The path part of the URL after ``root_path``. + :param query_string: The portion of the URL after the "?". + """ + url = [scheme, "://", host] + + if root_path is None: + url.append("/") + return uri_to_iri("".join(url)) + + # safe = https://url.spec.whatwg.org/#url-path-segment-string + # as well as percent for things that are already quoted + url.append(quote(root_path.rstrip("/"), safe="!$&'()*+,/:;=@%")) + url.append("/") + + if path is None: + return uri_to_iri("".join(url)) + + url.append(quote(path.lstrip("/"), safe="!$&'()*+,/:;=@%")) + + if query_string: + url.append("?") + url.append(quote(query_string, safe="!$&'()*+,/:;=?@%")) + + return uri_to_iri("".join(url)) + + +def get_content_length( + http_content_length: str | None = None, + http_transfer_encoding: str | None = None, +) -> int | None: + """Return the ``Content-Length`` header value as an int. If the header is not given + or the ``Transfer-Encoding`` header is ``chunked``, ``None`` is returned to indicate + a streaming request. If the value is not an integer, or negative, 0 is returned. + + :param http_content_length: The Content-Length HTTP header. + :param http_transfer_encoding: The Transfer-Encoding HTTP header. + + .. versionadded:: 2.2 + """ + if http_transfer_encoding == "chunked" or http_content_length is None: + return None + + try: + return max(0, _plain_int(http_content_length)) + except ValueError: + return 0 diff --git a/venv/Lib/site-packages/werkzeug/security.py b/venv/Lib/site-packages/werkzeug/security.py new file mode 100644 index 00000000..9999509d --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/security.py @@ -0,0 +1,161 @@ +from __future__ import annotations + +import hashlib +import hmac +import os +import posixpath +import secrets + +SALT_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" +DEFAULT_PBKDF2_ITERATIONS = 600000 + +_os_alt_seps: list[str] = list( + sep for sep in [os.sep, os.path.altsep] if sep is not None and sep != "/" +) + + +def gen_salt(length: int) -> str: + """Generate a random string of SALT_CHARS with specified ``length``.""" + if length <= 0: + raise ValueError("Salt length must be at least 1.") + + return "".join(secrets.choice(SALT_CHARS) for _ in range(length)) + + +def _hash_internal(method: str, salt: str, password: str) -> tuple[str, str]: + method, *args = method.split(":") + salt_bytes = salt.encode() + password_bytes = password.encode() + + if method == "scrypt": + if not args: + n = 2**15 + r = 8 + p = 1 + else: + try: + n, r, p = map(int, args) + except ValueError: + raise ValueError("'scrypt' takes 3 arguments.") from None + + maxmem = 132 * n * r * p # ideally 128, but some extra seems needed + return ( + hashlib.scrypt( + password_bytes, salt=salt_bytes, n=n, r=r, p=p, maxmem=maxmem + ).hex(), + f"scrypt:{n}:{r}:{p}", + ) + elif method == "pbkdf2": + len_args = len(args) + + if len_args == 0: + hash_name = "sha256" + iterations = DEFAULT_PBKDF2_ITERATIONS + elif len_args == 1: + hash_name = args[0] + iterations = DEFAULT_PBKDF2_ITERATIONS + elif len_args == 2: + hash_name = args[0] + iterations = int(args[1]) + else: + raise ValueError("'pbkdf2' takes 2 arguments.") + + return ( + hashlib.pbkdf2_hmac( + hash_name, password_bytes, salt_bytes, iterations + ).hex(), + f"pbkdf2:{hash_name}:{iterations}", + ) + else: + raise ValueError(f"Invalid hash method '{method}'.") + + +def generate_password_hash( + 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``, 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 + deprecated and removed if they are no longer considered secure. To migrate old + hashes, you may generate a new hash when checking an old hash, or you may contact + users with a link to reset their password. + + :param password: The plaintext password. + :param method: The key derivation function and parameters. + :param salt_length: The number of characters to generate for the salt. + + .. versionchanged:: 2.3 + Scrypt support was added. + + .. versionchanged:: 2.3 + The default iterations for pbkdf2 was increased to 600,000. + + .. versionchanged:: 2.3 + All plain hashes are deprecated and will not be supported in Werkzeug 3.0. + """ + salt = gen_salt(salt_length) + h, actual_method = _hash_internal(method, salt, password) + return f"{actual_method}${salt}${h}" + + +def check_password_hash(pwhash: str, password: str) -> bool: + """Securely check that the given stored password hash, previously generated using + :func:`generate_password_hash`, matches the given password. + + Methods may be deprecated and removed if they are no longer considered secure. To + migrate old hashes, you may generate a new hash when checking an old hash, or you + may contact users with a link to reset their password. + + :param pwhash: The hashed password. + :param password: The plaintext password. + + .. versionchanged:: 2.3 + All plain hashes are deprecated and will not be supported in Werkzeug 3.0. + """ + try: + method, salt, hashval = pwhash.split("$", 2) + except ValueError: + return False + + return hmac.compare_digest(_hash_internal(method, salt, password)[0], hashval) + + +def safe_join(directory: str, *pathnames: str) -> str | None: + """Safely join zero or more untrusted path components to a base + directory to avoid escaping the base directory. + + :param directory: The trusted base directory. + :param pathnames: The untrusted path components relative to the + base directory. + :return: A safe path, otherwise ``None``. + """ + if not directory: + # Ensure we end up with ./path if directory="" is given, + # otherwise the first untrusted part could become trusted. + directory = "." + + parts = [directory] + + for filename in pathnames: + if filename != "": + filename = posixpath.normpath(filename) + + if ( + any(sep in filename for sep in _os_alt_seps) + or os.path.isabs(filename) + or filename == ".." + or filename.startswith("../") + ): + return None + + parts.append(filename) + + return posixpath.join(*parts) diff --git a/venv/Lib/site-packages/werkzeug/serving.py b/venv/Lib/site-packages/werkzeug/serving.py new file mode 100644 index 00000000..859f9aac --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/serving.py @@ -0,0 +1,1116 @@ +"""A WSGI and HTTP server for use **during development only**. This +server is convenient to use, but is not designed to be particularly +stable, secure, or efficient. Use a dedicate WSGI server and HTTP +server when deploying to production. + +It provides features like interactive debugging and code reloading. Use +``run_simple`` to start the server. Put this in a ``run.py`` script: + +.. code-block:: python + + from myapp import create_app + from werkzeug import run_simple +""" + +from __future__ import annotations + +import errno +import io +import os +import selectors +import socket +import socketserver +import sys +import typing as t +from datetime import datetime as dt +from datetime import timedelta +from datetime import timezone +from http.server import BaseHTTPRequestHandler +from http.server import HTTPServer +from urllib.parse import unquote +from urllib.parse import urlsplit + +from ._internal import _log +from ._internal import _wsgi_encoding_dance +from .exceptions import InternalServerError +from .urls import uri_to_iri + +try: + import ssl +except ImportError: + + class _SslDummy: + def __getattr__(self, name: str) -> t.Any: + raise RuntimeError( # noqa: B904 + "SSL is unavailable because this Python runtime was not" + " compiled with SSL/TLS support." + ) + + ssl = _SslDummy() # type: ignore + +_log_add_style = True + +if os.name == "nt": + try: + __import__("colorama") + except ImportError: + _log_add_style = False + +can_fork = hasattr(os, "fork") + +if can_fork: + ForkingMixIn = socketserver.ForkingMixIn +else: + + class ForkingMixIn: # type: ignore + pass + + +try: + af_unix = socket.AF_UNIX +except AttributeError: + af_unix = None # type: ignore + +LISTEN_QUEUE = 128 + +_TSSLContextArg = t.Optional[ + t.Union["ssl.SSLContext", t.Tuple[str, t.Optional[str]], t.Literal["adhoc"]] +] + +if t.TYPE_CHECKING: + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + from cryptography.hazmat.primitives.asymmetric.rsa import ( + RSAPrivateKeyWithSerialization, + ) + from cryptography.x509 import Certificate + + +class DechunkedInput(io.RawIOBase): + """An input stream that handles Transfer-Encoding 'chunked'""" + + def __init__(self, rfile: t.IO[bytes]) -> None: + self._rfile = rfile + self._done = False + self._len = 0 + + def readable(self) -> bool: + return True + + def read_chunk_len(self) -> int: + try: + line = self._rfile.readline().decode("latin1") + _len = int(line.strip(), 16) + except ValueError as e: + raise OSError("Invalid chunk header") from e + if _len < 0: + raise OSError("Negative chunk length not allowed") + return _len + + def readinto(self, buf: bytearray) -> int: # type: ignore + read = 0 + while not self._done and read < len(buf): + if self._len == 0: + # This is the first chunk or we fully consumed the previous + # one. Read the next length of the next chunk + self._len = self.read_chunk_len() + + if self._len == 0: + # Found the final chunk of size 0. The stream is now exhausted, + # but there is still a final newline that should be consumed + self._done = True + + if self._len > 0: + # There is data (left) in this chunk, so append it to the + # buffer. If this operation fully consumes the chunk, this will + # reset self._len to 0. + n = min(len(buf), self._len) + + # If (read + chunk size) becomes more than len(buf), buf will + # grow beyond the original size and read more data than + # required. So only read as much data as can fit in buf. + if read + n > len(buf): + buf[read:] = self._rfile.read(len(buf) - read) + self._len -= len(buf) - read + read = len(buf) + else: + buf[read : read + n] = self._rfile.read(n) + self._len -= n + read += n + + if self._len == 0: + # Skip the terminating newline of a chunk that has been fully + # consumed. This also applies to the 0-sized final chunk + terminator = self._rfile.readline() + if terminator not in (b"\n", b"\r\n", b"\r"): + raise OSError("Missing chunk terminating newline") + + return read + + +class WSGIRequestHandler(BaseHTTPRequestHandler): + """A request handler that implements WSGI dispatching.""" + + server: BaseWSGIServer + + @property + def server_version(self) -> str: # type: ignore + return self.server._server_version + + def make_environ(self) -> WSGIEnvironment: + request_url = urlsplit(self.path) + url_scheme = "http" if self.server.ssl_context is None else "https" + + if not self.client_address: + self.client_address = ("", 0) + elif isinstance(self.client_address, str): + self.client_address = (self.client_address, 0) + + # If there was no scheme but the path started with two slashes, + # the first segment may have been incorrectly parsed as the + # netloc, prepend it to the path again. + if not request_url.scheme and request_url.netloc: + path_info = f"/{request_url.netloc}{request_url.path}" + else: + path_info = request_url.path + + path_info = unquote(path_info) + + environ: WSGIEnvironment = { + "wsgi.version": (1, 0), + "wsgi.url_scheme": url_scheme, + "wsgi.input": self.rfile, + "wsgi.errors": sys.stderr, + "wsgi.multithread": self.server.multithread, + "wsgi.multiprocess": self.server.multiprocess, + "wsgi.run_once": False, + "werkzeug.socket": self.connection, + "SERVER_SOFTWARE": self.server_version, + "REQUEST_METHOD": self.command, + "SCRIPT_NAME": "", + "PATH_INFO": _wsgi_encoding_dance(path_info), + "QUERY_STRING": _wsgi_encoding_dance(request_url.query), + # Non-standard, added by mod_wsgi, uWSGI + "REQUEST_URI": _wsgi_encoding_dance(self.path), + # Non-standard, added by gunicorn + "RAW_URI": _wsgi_encoding_dance(self.path), + "REMOTE_ADDR": self.address_string(), + "REMOTE_PORT": self.port_integer(), + "SERVER_NAME": self.server.server_address[0], + "SERVER_PORT": str(self.server.server_address[1]), + "SERVER_PROTOCOL": self.request_version, + } + + for key, value in self.headers.items(): + if "_" in key: + continue + + key = key.upper().replace("-", "_") + value = value.replace("\r\n", "") + if key not in ("CONTENT_TYPE", "CONTENT_LENGTH"): + key = f"HTTP_{key}" + if key in environ: + value = f"{environ[key]},{value}" + environ[key] = value + + if environ.get("HTTP_TRANSFER_ENCODING", "").strip().lower() == "chunked": + environ["wsgi.input_terminated"] = True + environ["wsgi.input"] = DechunkedInput(environ["wsgi.input"]) + + # Per RFC 2616, if the URL is absolute, use that as the host. + # We're using "has a scheme" to indicate an absolute URL. + if request_url.scheme and request_url.netloc: + environ["HTTP_HOST"] = request_url.netloc + + try: + # binary_form=False gives nicer information, but wouldn't be compatible with + # what Nginx or Apache could return. + peer_cert = self.connection.getpeercert(binary_form=True) + if peer_cert is not None: + # Nginx and Apache use PEM format. + environ["SSL_CLIENT_CERT"] = ssl.DER_cert_to_PEM_cert(peer_cert) + except ValueError: + # SSL handshake hasn't finished. + self.server.log("error", "Cannot fetch SSL peer certificate info") + except AttributeError: + # Not using TLS, the socket will not have getpeercert(). + pass + + return environ + + def run_wsgi(self) -> None: + if self.headers.get("Expect", "").lower().strip() == "100-continue": + self.wfile.write(b"HTTP/1.1 100 Continue\r\n\r\n") + + self.environ = environ = self.make_environ() + status_set: str | None = None + headers_set: list[tuple[str, str]] | None = None + status_sent: str | None = None + headers_sent: list[tuple[str, str]] | None = None + chunk_response: bool = False + + def write(data: bytes) -> None: + nonlocal status_sent, headers_sent, chunk_response + assert status_set is not None, "write() before start_response" + assert headers_set is not None, "write() before start_response" + if status_sent is None: + status_sent = status_set + headers_sent = headers_set + try: + code_str, msg = status_sent.split(None, 1) + except ValueError: + code_str, msg = status_sent, "" + code = int(code_str) + self.send_response(code, msg) + header_keys = set() + for key, value in headers_sent: + self.send_header(key, value) + header_keys.add(key.lower()) + + # Use chunked transfer encoding if there is no content + # length. Do not use for 1xx and 204 responses. 304 + # responses and HEAD requests are also excluded, which + # is the more conservative behavior and matches other + # parts of the code. + # https://httpwg.org/specs/rfc7230.html#rfc.section.3.3.1 + if ( + not ( + "content-length" in header_keys + or environ["REQUEST_METHOD"] == "HEAD" + or (100 <= code < 200) + or code in {204, 304} + ) + and self.protocol_version >= "HTTP/1.1" + ): + chunk_response = True + self.send_header("Transfer-Encoding", "chunked") + + # Always close the connection. This disables HTTP/1.1 + # keep-alive connections. They aren't handled well by + # Python's http.server because it doesn't know how to + # drain the stream before the next request line. + self.send_header("Connection", "close") + self.end_headers() + + assert isinstance(data, bytes), "applications must write bytes" + + if data: + if chunk_response: + self.wfile.write(hex(len(data))[2:].encode()) + self.wfile.write(b"\r\n") + + self.wfile.write(data) + + if chunk_response: + self.wfile.write(b"\r\n") + + self.wfile.flush() + + def start_response(status, headers, exc_info=None): # type: ignore + nonlocal status_set, headers_set + if exc_info: + try: + if headers_sent: + raise exc_info[1].with_traceback(exc_info[2]) + finally: + exc_info = None + elif headers_set: + raise AssertionError("Headers already set") + status_set = status + headers_set = headers + return write + + def execute(app: WSGIApplication) -> None: + application_iter = app(environ, start_response) + try: + for data in application_iter: + write(data) + if not headers_sent: + write(b"") + if chunk_response: + self.wfile.write(b"0\r\n\r\n") + finally: + # Check for any remaining data in the read socket, and discard it. This + # will read past request.max_content_length, but lets the client see a + # 413 response instead of a connection reset failure. If we supported + # keep-alive connections, this naive approach would break by reading the + # next request line. Since we know that write (above) closes every + # connection we can read everything. + selector = selectors.DefaultSelector() + selector.register(self.connection, selectors.EVENT_READ) + total_size = 0 + total_reads = 0 + + # A timeout of 0 tends to fail because a client needs a small amount of + # time to continue sending its data. + while selector.select(timeout=0.01): + # Only read 10MB into memory at a time. + data = self.rfile.read(10_000_000) + total_size += len(data) + total_reads += 1 + + # Stop reading on no data, >=10GB, or 1000 reads. If a client sends + # more than that, they'll get a connection reset failure. + if not data or total_size >= 10_000_000_000 or total_reads > 1000: + break + + selector.close() + + if hasattr(application_iter, "close"): + application_iter.close() + + try: + execute(self.server.app) + except (ConnectionError, socket.timeout) as e: + self.connection_dropped(e, environ) + except Exception as e: + if self.server.passthrough_errors: + raise + + if status_sent is not None and chunk_response: + self.close_connection = True + + try: + # if we haven't yet sent the headers but they are set + # we roll back to be able to set them again. + if status_sent is None: + status_set = None + headers_set = None + execute(InternalServerError()) + except Exception: + pass + + from .debug.tbtools import DebugTraceback + + msg = DebugTraceback(e).render_traceback_text() + self.server.log("error", f"Error on request:\n{msg}") + + def handle(self) -> None: + """Handles a request ignoring dropped connections.""" + try: + super().handle() + except (ConnectionError, socket.timeout) as e: + self.connection_dropped(e) + except Exception as e: + if self.server.ssl_context is not None and is_ssl_error(e): + self.log_error("SSL error occurred: %s", e) + else: + raise + + def connection_dropped( + self, error: BaseException, environ: WSGIEnvironment | None = None + ) -> None: + """Called if the connection was closed by the client. By default + nothing happens. + """ + + def __getattr__(self, name: str) -> t.Any: + # All HTTP methods are handled by run_wsgi. + if name.startswith("do_"): + return self.run_wsgi + + # All other attributes are forwarded to the base class. + return getattr(super(), name) + + def address_string(self) -> str: + if getattr(self, "environ", None): + return self.environ["REMOTE_ADDR"] # type: ignore + + if not self.client_address: + return "" + + return self.client_address[0] + + def port_integer(self) -> int: + return self.client_address[1] + + # Escape control characters. This is defined (but private) in Python 3.12. + _control_char_table = str.maketrans( + {c: rf"\x{c:02x}" for c in [*range(0x20), *range(0x7F, 0xA0)]} + ) + _control_char_table[ord("\\")] = r"\\" + + def log_request(self, code: int | str = "-", size: int | str = "-") -> None: + try: + path = uri_to_iri(self.path) + msg = f"{self.command} {path} {self.request_version}" + except AttributeError: + # path isn't set if the requestline was bad + msg = self.requestline + + # Escape control characters that may be in the decoded path. + msg = msg.translate(self._control_char_table) + code = str(code) + + if code[0] == "1": # 1xx - Informational + msg = _ansi_style(msg, "bold") + elif code == "200": # 2xx - Success + pass + elif code == "304": # 304 - Resource Not Modified + msg = _ansi_style(msg, "cyan") + elif code[0] == "3": # 3xx - Redirection + msg = _ansi_style(msg, "green") + elif code == "404": # 404 - Resource Not Found + msg = _ansi_style(msg, "yellow") + elif code[0] == "4": # 4xx - Client Error + msg = _ansi_style(msg, "bold", "red") + else: # 5xx, or any other response + msg = _ansi_style(msg, "bold", "magenta") + + self.log("info", '"%s" %s %s', msg, code, size) + + def log_error(self, format: str, *args: t.Any) -> None: + self.log("error", format, *args) + + def log_message(self, format: str, *args: t.Any) -> None: + self.log("info", format, *args) + + def log(self, type: str, message: str, *args: t.Any) -> None: + _log( + type, + f"{self.address_string()} - - [{self.log_date_time_string()}] {message}\n", + *args, + ) + + +def _ansi_style(value: str, *styles: str) -> str: + if not _log_add_style: + return value + + codes = { + "bold": 1, + "red": 31, + "green": 32, + "yellow": 33, + "magenta": 35, + "cyan": 36, + } + + for style in styles: + value = f"\x1b[{codes[style]}m{value}" + + return f"{value}\x1b[0m" + + +def generate_adhoc_ssl_pair( + cn: str | None = None, +) -> tuple[Certificate, RSAPrivateKeyWithSerialization]: + try: + from cryptography import x509 + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.asymmetric import rsa + from cryptography.x509.oid import NameOID + except ImportError: + raise TypeError( + "Using ad-hoc certificates requires the cryptography library." + ) from None + + backend = default_backend() + pkey = rsa.generate_private_key( + public_exponent=65537, key_size=2048, backend=backend + ) + + # pretty damn sure that this is not actually accepted by anyone + if cn is None: + cn = "*" + + subject = x509.Name( + [ + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Dummy Certificate"), + x509.NameAttribute(NameOID.COMMON_NAME, cn), + ] + ) + + backend = default_backend() + cert = ( + x509.CertificateBuilder() + .subject_name(subject) + .issuer_name(subject) + .public_key(pkey.public_key()) + .serial_number(x509.random_serial_number()) + .not_valid_before(dt.now(timezone.utc)) + .not_valid_after(dt.now(timezone.utc) + timedelta(days=365)) + .add_extension(x509.ExtendedKeyUsage([x509.OID_SERVER_AUTH]), critical=False) + .add_extension( + x509.SubjectAlternativeName([x509.DNSName(cn), x509.DNSName(f"*.{cn}")]), + critical=False, + ) + .sign(pkey, hashes.SHA256(), backend) + ) + return cert, pkey + + +def make_ssl_devcert( + base_path: str, host: str | None = None, cn: str | None = None +) -> tuple[str, str]: + """Creates an SSL key for development. This should be used instead of + the ``'adhoc'`` key which generates a new cert on each server start. + It accepts a path for where it should store the key and cert and + either a host or CN. If a host is given it will use the CN + ``*.host/CN=host``. + + For more information see :func:`run_simple`. + + .. versionadded:: 0.9 + + :param base_path: the path to the certificate and key. The extension + ``.crt`` is added for the certificate, ``.key`` is + added for the key. + :param host: the name of the host. This can be used as an alternative + for the `cn`. + :param cn: the `CN` to use. + """ + + if host is not None: + cn = host + cert, pkey = generate_adhoc_ssl_pair(cn=cn) + + from cryptography.hazmat.primitives import serialization + + cert_file = f"{base_path}.crt" + pkey_file = f"{base_path}.key" + + with open(cert_file, "wb") as f: + f.write(cert.public_bytes(serialization.Encoding.PEM)) + with open(pkey_file, "wb") as f: + f.write( + pkey.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption(), + ) + ) + + return cert_file, pkey_file + + +def generate_adhoc_ssl_context() -> ssl.SSLContext: + """Generates an adhoc SSL context for the development server.""" + import atexit + import tempfile + + cert, pkey = generate_adhoc_ssl_pair() + + from cryptography.hazmat.primitives import serialization + + cert_handle, cert_file = tempfile.mkstemp() + pkey_handle, pkey_file = tempfile.mkstemp() + atexit.register(os.remove, pkey_file) + atexit.register(os.remove, cert_file) + + os.write(cert_handle, cert.public_bytes(serialization.Encoding.PEM)) + os.write( + pkey_handle, + pkey.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption(), + ), + ) + + os.close(cert_handle) + os.close(pkey_handle) + ctx = load_ssl_context(cert_file, pkey_file) + return ctx + + +def load_ssl_context( + cert_file: str, pkey_file: str | None = None, protocol: int | None = None +) -> ssl.SSLContext: + """Loads SSL context from cert/private key files and optional protocol. + Many parameters are directly taken from the API of + :py:class:`ssl.SSLContext`. + + :param cert_file: Path of the certificate to use. + :param pkey_file: Path of the private key to use. If not given, the key + will be obtained from the certificate file. + :param protocol: A ``PROTOCOL`` constant from the :mod:`ssl` module. + Defaults to :data:`ssl.PROTOCOL_TLS_SERVER`. + """ + if protocol is None: + protocol = ssl.PROTOCOL_TLS_SERVER + + ctx = ssl.SSLContext(protocol) + ctx.load_cert_chain(cert_file, pkey_file) + return ctx + + +def is_ssl_error(error: Exception | None = None) -> bool: + """Checks if the given error (or the current one) is an SSL error.""" + if error is None: + error = t.cast(Exception, sys.exc_info()[1]) + return isinstance(error, ssl.SSLError) + + +def select_address_family(host: str, port: int) -> socket.AddressFamily: + """Return ``AF_INET4``, ``AF_INET6``, or ``AF_UNIX`` depending on + the host and port.""" + if host.startswith("unix://"): + return socket.AF_UNIX + elif ":" in host and hasattr(socket, "AF_INET6"): + return socket.AF_INET6 + return socket.AF_INET + + +def get_sockaddr( + host: str, port: int, family: socket.AddressFamily +) -> tuple[str, int] | str: + """Return a fully qualified socket address that can be passed to + :func:`socket.bind`.""" + if family == af_unix: + # Absolute path avoids IDNA encoding error when path starts with dot. + return os.path.abspath(host.partition("://")[2]) + try: + res = socket.getaddrinfo( + host, port, family, socket.SOCK_STREAM, socket.IPPROTO_TCP + ) + except socket.gaierror: + return host, port + return res[0][4] # type: ignore + + +def get_interface_ip(family: socket.AddressFamily) -> str: + """Get the IP address of an external interface. Used when binding to + 0.0.0.0 or ::1 to show a more useful URL. + + :meta private: + """ + # arbitrary private address + host = "fd31:f903:5ab5:1::1" if family == socket.AF_INET6 else "10.253.155.219" + + with socket.socket(family, socket.SOCK_DGRAM) as s: + try: + s.connect((host, 58162)) + except OSError: + return "::1" if family == socket.AF_INET6 else "127.0.0.1" + + return s.getsockname()[0] # type: ignore + + +class BaseWSGIServer(HTTPServer): + """A WSGI server that that handles one request at a time. + + Use :func:`make_server` to create a server instance. + """ + + multithread = False + multiprocess = False + request_queue_size = LISTEN_QUEUE + allow_reuse_address = True + + def __init__( + self, + host: str, + port: int, + app: WSGIApplication, + handler: type[WSGIRequestHandler] | None = None, + passthrough_errors: bool = False, + ssl_context: _TSSLContextArg | None = None, + fd: int | None = None, + ) -> None: + if handler is None: + handler = WSGIRequestHandler + + # If the handler doesn't directly set a protocol version and + # thread or process workers are used, then allow chunked + # responses and keep-alive connections by enabling HTTP/1.1. + if "protocol_version" not in vars(handler) and ( + self.multithread or self.multiprocess + ): + handler.protocol_version = "HTTP/1.1" + + self.host = host + self.port = port + self.app = app + self.passthrough_errors = passthrough_errors + + self.address_family = address_family = select_address_family(host, port) + server_address = get_sockaddr(host, int(port), address_family) + + # Remove a leftover Unix socket file from a previous run. Don't + # remove a file that was set up by run_simple. + if address_family == af_unix and fd is None: + server_address = t.cast(str, server_address) + + if os.path.exists(server_address): + os.unlink(server_address) + + # Bind and activate will be handled manually, it should only + # happen if we're not using a socket that was already set up. + super().__init__( + server_address, # type: ignore[arg-type] + handler, + bind_and_activate=False, + ) + + if fd is None: + # No existing socket descriptor, do bind_and_activate=True. + try: + self.server_bind() + self.server_activate() + except OSError as e: + # Catch connection issues and show them without the traceback. Show + # extra instructions for address not found, and for macOS. + self.server_close() + print(e.strerror, file=sys.stderr) + + if e.errno == errno.EADDRINUSE: + print( + f"Port {port} is in use by another program. Either identify and" + " stop that program, or start the server with a different" + " port.", + file=sys.stderr, + ) + + if sys.platform == "darwin" and port == 5000: + print( + "On macOS, try disabling the 'AirPlay Receiver' service" + " from System Preferences -> General -> AirDrop & Handoff.", + file=sys.stderr, + ) + + sys.exit(1) + except BaseException: + self.server_close() + raise + else: + # TCPServer automatically opens a socket even if bind_and_activate is False. + # Close it to silence a ResourceWarning. + self.server_close() + + # Use the passed in socket directly. + self.socket = socket.fromfd(fd, address_family, socket.SOCK_STREAM) + self.server_address = self.socket.getsockname() + + if address_family != af_unix: + # If port was 0, this will record the bound port. + self.port = self.server_address[1] + + if ssl_context is not None: + if isinstance(ssl_context, tuple): + ssl_context = load_ssl_context(*ssl_context) + elif ssl_context == "adhoc": + ssl_context = generate_adhoc_ssl_context() + + self.socket = ssl_context.wrap_socket(self.socket, server_side=True) + self.ssl_context: ssl.SSLContext | None = ssl_context + 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) + + def serve_forever(self, poll_interval: float = 0.5) -> None: + try: + super().serve_forever(poll_interval=poll_interval) + except KeyboardInterrupt: + pass + finally: + self.server_close() + + def handle_error( + self, request: t.Any, client_address: tuple[str, int] | str + ) -> None: + if self.passthrough_errors: + raise + + return super().handle_error(request, client_address) + + def log_startup(self) -> None: + """Show information about the address when starting the server.""" + dev_warning = ( + "WARNING: This is a development server. Do not use it in a production" + " deployment. Use a production WSGI server instead." + ) + dev_warning = _ansi_style(dev_warning, "bold", "red") + messages = [dev_warning] + + if self.address_family == af_unix: + messages.append(f" * Running on {self.host}") + else: + scheme = "http" if self.ssl_context is None else "https" + display_hostname = self.host + + if self.host in {"0.0.0.0", "::"}: + messages.append(f" * Running on all addresses ({self.host})") + + if self.host == "0.0.0.0": + localhost = "127.0.0.1" + display_hostname = get_interface_ip(socket.AF_INET) + else: + localhost = "[::1]" + display_hostname = get_interface_ip(socket.AF_INET6) + + messages.append(f" * Running on {scheme}://{localhost}:{self.port}") + + if ":" in display_hostname: + display_hostname = f"[{display_hostname}]" + + messages.append(f" * Running on {scheme}://{display_hostname}:{self.port}") + + _log("info", "\n".join(messages)) + + +class ThreadedWSGIServer(socketserver.ThreadingMixIn, BaseWSGIServer): + """A WSGI server that handles concurrent requests in separate + threads. + + Use :func:`make_server` to create a server instance. + """ + + multithread = True + daemon_threads = True + + +class ForkingWSGIServer(ForkingMixIn, BaseWSGIServer): + """A WSGI server that handles concurrent requests in separate forked + processes. + + Use :func:`make_server` to create a server instance. + """ + + multiprocess = True + + def __init__( + self, + host: str, + port: int, + app: WSGIApplication, + processes: int = 40, + handler: type[WSGIRequestHandler] | None = None, + passthrough_errors: bool = False, + ssl_context: _TSSLContextArg | None = None, + fd: int | None = None, + ) -> None: + if not can_fork: + raise ValueError("Your platform does not support forking.") + + super().__init__(host, port, app, handler, passthrough_errors, ssl_context, fd) + self.max_children = processes + + +def make_server( + host: str, + port: int, + app: WSGIApplication, + threaded: bool = False, + processes: int = 1, + request_handler: type[WSGIRequestHandler] | None = None, + passthrough_errors: bool = False, + ssl_context: _TSSLContextArg | None = None, + fd: int | None = None, +) -> BaseWSGIServer: + """Create an appropriate WSGI server instance based on the value of + ``threaded`` and ``processes``. + + This is called from :func:`run_simple`, but can be used separately + to have access to the server object, such as to run it in a separate + thread. + + See :func:`run_simple` for parameter docs. + """ + if threaded and processes > 1: + raise ValueError("Cannot have a multi-thread and multi-process server.") + + if threaded: + return ThreadedWSGIServer( + host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd + ) + + if processes > 1: + return ForkingWSGIServer( + host, + port, + app, + processes, + request_handler, + passthrough_errors, + ssl_context, + fd=fd, + ) + + return BaseWSGIServer( + host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd + ) + + +def is_running_from_reloader() -> bool: + """Check if the server is running as a subprocess within the + Werkzeug reloader. + + .. versionadded:: 0.10 + """ + return os.environ.get("WERKZEUG_RUN_MAIN") == "true" + + +def run_simple( + hostname: str, + port: int, + application: WSGIApplication, + use_reloader: bool = False, + use_debugger: bool = False, + use_evalex: bool = True, + extra_files: t.Iterable[str] | None = None, + exclude_patterns: t.Iterable[str] | None = None, + reloader_interval: int = 1, + reloader_type: str = "auto", + threaded: bool = False, + processes: int = 1, + request_handler: type[WSGIRequestHandler] | None = None, + static_files: dict[str, str | tuple[str, str]] | None = None, + passthrough_errors: bool = False, + ssl_context: _TSSLContextArg | None = None, +) -> None: + """Start a development server for a WSGI application. Various + optional features can be enabled. + + .. warning:: + + Do not use the development server when deploying to production. + It is intended for use only during local development. It is not + designed to be particularly efficient, stable, or secure. + + :param hostname: The host to bind to, for example ``'localhost'``. + Can be a domain, IPv4 or IPv6 address, or file path starting + with ``unix://`` for a Unix socket. + :param port: The port to bind to, for example ``8080``. Using ``0`` + tells the OS to pick a random free port. + :param application: The WSGI application to run. + :param use_reloader: Use a reloader process to restart the server + process when files are changed. + :param use_debugger: Use Werkzeug's debugger, which will show + formatted tracebacks on unhandled exceptions. + :param use_evalex: Make the debugger interactive. A Python terminal + can be opened for any frame in the traceback. Some protection is + provided by requiring a PIN, but this should never be enabled + on a publicly visible server. + :param extra_files: The reloader will watch these files for changes + in addition to Python modules. For example, watch a + configuration file. + :param exclude_patterns: The reloader will ignore changes to any + files matching these :mod:`fnmatch` patterns. For example, + ignore cache files. + :param reloader_interval: How often the reloader tries to check for + changes. + :param reloader_type: The reloader to use. The ``'stat'`` reloader + is built in, but may require significant CPU to watch files. The + ``'watchdog'`` reloader is much more efficient but requires + installing the ``watchdog`` package first. + :param threaded: Handle concurrent requests using threads. Cannot be + used with ``processes``. + :param processes: Handle concurrent requests using up to this number + of processes. Cannot be used with ``threaded``. + :param request_handler: Use a different + :class:`~BaseHTTPServer.BaseHTTPRequestHandler` subclass to + handle requests. + :param static_files: A dict mapping URL prefixes to directories to + serve static files from using + :class:`~werkzeug.middleware.SharedDataMiddleware`. + :param passthrough_errors: Don't catch unhandled exceptions at the + server level, let the server crash instead. If ``use_debugger`` + is enabled, the debugger will still catch such errors. + :param ssl_context: Configure TLS to serve over HTTPS. Can be an + :class:`ssl.SSLContext` object, a ``(cert_file, key_file)`` + tuple to create a typical context, or the string ``'adhoc'`` to + generate a temporary self-signed certificate. + + .. versionchanged:: 2.1 + Instructions are shown for dealing with an "address already in + use" error. + + .. versionchanged:: 2.1 + Running on ``0.0.0.0`` or ``::`` shows the loopback IP in + addition to a real IP. + + .. versionchanged:: 2.1 + The command-line interface was removed. + + .. versionchanged:: 2.0 + Running on ``0.0.0.0`` or ``::`` shows a real IP address that + was bound as well as a warning not to run the development server + in production. + + .. versionchanged:: 2.0 + The ``exclude_patterns`` parameter was added. + + .. versionchanged:: 0.15 + Bind to a Unix socket by passing a ``hostname`` that starts with + ``unix://``. + + .. versionchanged:: 0.10 + Improved the reloader and added support for changing the backend + through the ``reloader_type`` parameter. + + .. versionchanged:: 0.9 + A command-line interface was added. + + .. versionchanged:: 0.8 + ``ssl_context`` can be a tuple of paths to the certificate and + private key files. + + .. versionchanged:: 0.6 + The ``ssl_context`` parameter was added. + + .. versionchanged:: 0.5 + The ``static_files`` and ``passthrough_errors`` parameters were + added. + """ + if not isinstance(port, int): + raise TypeError("port must be an integer") + + if static_files: + from .middleware.shared_data import SharedDataMiddleware + + application = SharedDataMiddleware(application, static_files) + + if use_debugger: + from .debug import DebuggedApplication + + application = DebuggedApplication(application, evalex=use_evalex) + # Allow the specified hostname to use the debugger, in addition to + # localhost domains. + application.trusted_hosts.append(hostname) + + if not is_running_from_reloader(): + fd = None + else: + fd = int(os.environ["WERKZEUG_SERVER_FD"]) + + srv = make_server( + hostname, + port, + application, + threaded, + processes, + request_handler, + passthrough_errors, + ssl_context, + fd=fd, + ) + srv.socket.set_inheritable(True) + os.environ["WERKZEUG_SERVER_FD"] = str(srv.fileno()) + + if not is_running_from_reloader(): + srv.log_startup() + _log("info", _ansi_style("Press CTRL+C to quit", "yellow")) + + if use_reloader: + from ._reloader import run_with_reloader + + try: + run_with_reloader( + srv.serve_forever, + extra_files=extra_files, + exclude_patterns=exclude_patterns, + interval=reloader_interval, + reloader_type=reloader_type, + ) + finally: + srv.server_close() + else: + srv.serve_forever() diff --git a/venv/Lib/site-packages/werkzeug/test.py b/venv/Lib/site-packages/werkzeug/test.py new file mode 100644 index 00000000..38f69bfb --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/test.py @@ -0,0 +1,1464 @@ +from __future__ import annotations + +import dataclasses +import mimetypes +import sys +import typing as t +from collections import defaultdict +from datetime import datetime +from io import BytesIO +from itertools import chain +from random import random +from tempfile import TemporaryFile +from time import time +from urllib.parse import unquote +from urllib.parse import urlsplit +from urllib.parse import urlunsplit + +from ._internal import _get_environ +from ._internal import _wsgi_decoding_dance +from ._internal import _wsgi_encoding_dance +from .datastructures import Authorization +from .datastructures import CallbackDict +from .datastructures import CombinedMultiDict +from .datastructures import EnvironHeaders +from .datastructures import FileMultiDict +from .datastructures import Headers +from .datastructures import MultiDict +from .http import dump_cookie +from .http import dump_options_header +from .http import parse_cookie +from .http import parse_date +from .http import parse_options_header +from .sansio.multipart import Data +from .sansio.multipart import Epilogue +from .sansio.multipart import Field +from .sansio.multipart import File +from .sansio.multipart import MultipartEncoder +from .sansio.multipart import Preamble +from .urls import _urlencode +from .urls import iri_to_uri +from .utils import cached_property +from .utils import get_content_type +from .wrappers.request import Request +from .wrappers.response import Response +from .wsgi import ClosingIterator +from .wsgi import get_current_url + +if t.TYPE_CHECKING: + import typing_extensions as te + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + + +def stream_encode_multipart( + data: t.Mapping[str, t.Any], + use_tempfile: bool = True, + threshold: int = 1024 * 500, + boundary: 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:: 3.0 + The ``charset`` parameter was removed. + """ + if boundary is None: + boundary = f"---------------WerkzeugFormPart_{time()}{random()}" + + stream: t.IO[bytes] = BytesIO() + total_length = 0 + on_disk = False + write_binary: t.Callable[[bytes], int] + + if use_tempfile: + + def write_binary(s: bytes) -> int: + nonlocal stream, total_length, on_disk + + if on_disk: + return stream.write(s) + else: + length = len(s) + + if length + total_length <= threshold: + stream.write(s) + else: + new_stream = t.cast(t.IO[bytes], TemporaryFile("wb+")) + new_stream.write(stream.getvalue()) # type: ignore + new_stream.write(s) + stream = new_stream + on_disk = True + + total_length += length + return length + + else: + write_binary = stream.write + + encoder = MultipartEncoder(boundary.encode()) + write_binary(encoder.send_event(Preamble(data=b""))) + for key, value in _iter_data(data): + reader = getattr(value, "read", None) + if reader is not None: + filename = getattr(value, "filename", getattr(value, "name", None)) + content_type = getattr(value, "content_type", None) + if content_type is None: + content_type = ( + filename + and mimetypes.guess_type(filename)[0] + or "application/octet-stream" + ) + headers = value.headers + headers.update([("Content-Type", content_type)]) + if filename is None: + write_binary(encoder.send_event(Field(name=key, headers=headers))) + else: + write_binary( + encoder.send_event( + File(name=key, filename=filename, headers=headers) + ) + ) + while True: + chunk = reader(16384) + + if not chunk: + write_binary(encoder.send_event(Data(data=chunk, more_data=False))) + break + + write_binary(encoder.send_event(Data(data=chunk, more_data=True))) + else: + 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(), more_data=False))) + + write_binary(encoder.send_event(Epilogue(data=b""))) + + length = stream.tell() + stream.seek(0) + return stream, length, boundary + + +def encode_multipart( + 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:: 3.0 + The ``charset`` parameter was removed. + """ + stream, length, boundary = stream_encode_multipart( + values, use_tempfile=False, boundary=boundary + ) + return boundary, stream.read() + + +def _iter_data(data: t.Mapping[str, t.Any]) -> t.Iterator[tuple[str, t.Any]]: + """Iterate over a mapping that might have a list of values, yielding + all key, value pairs. Almost like iter_multi_items but only allows + lists, not tuples, of values so tuples can be used for files. + """ + if isinstance(data, MultiDict): + yield from data.items(multi=True) + else: + for key, value in data.items(): + if isinstance(value, list): + for v in value: + yield key, v + else: + yield key, value + + +_TAnyMultiDict = t.TypeVar("_TAnyMultiDict", bound="MultiDict[t.Any, t.Any]") + + +class EnvironBuilder: + """This class can be used to conveniently create a WSGI environment + for testing purposes. It can be used to quickly create WSGI environments + or request objects from arbitrary data. + + The signature of this class is also used in some other places as of + Werkzeug 0.5 (:func:`create_environ`, :meth:`Response.from_values`, + :meth:`Client.open`). Because of this most of the functionality is + available through the constructor alone. + + Files and regular form data can be manipulated independently of each + other with the :attr:`form` and :attr:`files` attributes, but are + passed with the same argument to the constructor: `data`. + + `data` can be any of these values: + + - a `str` or `bytes` object: The object is converted into an + :attr:`input_stream`, the :attr:`content_length` is set and you have to + provide a :attr:`content_type`. + - a `dict` or :class:`MultiDict`: The keys have to be strings. The values + have to be either any of the following objects, or a list of any of the + following objects: + + - a :class:`file`-like object: These are converted into + :class:`FileStorage` objects automatically. + - a `tuple`: The :meth:`~FileMultiDict.add_file` method is called + with the key and the unpacked `tuple` items as positional + arguments. + - a `str`: The string is set as form data for the associated key. + - a file-like object: The object content is loaded in memory and then + handled like a regular `str` or a `bytes`. + + :param path: the path of the request. In the WSGI environment this will + end up as `PATH_INFO`. If the `query_string` is not defined + and there is a question mark in the `path` everything after + it is used as query string. + :param base_url: the base URL is a URL that is used to extract the WSGI + URL scheme, host (server name + server port) and the + script root (`SCRIPT_NAME`). + :param query_string: an optional string or dict with URL parameters. + :param method: the HTTP method to use, defaults to `GET`. + :param input_stream: an optional input stream. Do not specify this and + `data`. As soon as an input stream is set you can't + modify :attr:`args` and :attr:`files` unless you + set the :attr:`input_stream` to `None` again. + :param content_type: The content type for the request. As of 0.5 you + don't have to provide this when specifying files + and form data via `data`. + :param content_length: The content length for the request. You don't + have to specify this when providing data via + `data`. + :param errors_stream: an optional error stream that is used for + `wsgi.errors`. Defaults to :data:`stderr`. + :param multithread: controls `wsgi.multithread`. Defaults to `False`. + :param multiprocess: controls `wsgi.multiprocess`. Defaults to `False`. + :param run_once: controls `wsgi.run_once`. Defaults to `False`. + :param headers: an optional list or :class:`Headers` object of headers. + :param data: a string or dict of form data or a file-object. + See explanation above. + :param json: An object to be serialized and assigned to ``data``. + Defaults the content type to ``"application/json"``. + Serialized with the function assigned to :attr:`json_dumps`. + :param environ_base: an optional dict of environment defaults. + :param environ_overrides: an optional dict of environment overrides. + :param auth: An authorization object to use for the + ``Authorization`` header value. A ``(username, password)`` tuple + is a shortcut for ``Basic`` authorization. + + .. versionchanged:: 3.0 + The ``charset`` parameter was removed. + + .. versionchanged:: 2.1 + ``CONTENT_TYPE`` and ``CONTENT_LENGTH`` are not duplicated as + header keys in the environ. + + .. versionchanged:: 2.0 + ``REQUEST_URI`` and ``RAW_URI`` is the full raw URI including + the query string, not only the path. + + .. versionchanged:: 2.0 + The default :attr:`request_class` is ``Request`` instead of + ``BaseRequest``. + + .. versionadded:: 2.0 + Added the ``auth`` parameter. + + .. versionadded:: 0.15 + The ``json`` param and :meth:`json_dumps` method. + + .. versionadded:: 0.15 + The environ has keys ``REQUEST_URI`` and ``RAW_URI`` containing + the path before percent-decoding. This is not part of the WSGI + PEP, but many WSGI servers include it. + + .. versionchanged:: 0.6 + ``path`` and ``base_url`` can now be unicode strings that are + encoded with :func:`iri_to_uri`. + """ + + #: the server protocol to use. defaults to HTTP/1.1 + server_protocol = "HTTP/1.1" + + #: the wsgi version to use. defaults to (1, 0) + wsgi_version = (1, 0) + + #: The default request class used by :meth:`get_request`. + request_class = Request + + import json + + #: The serialization function used when ``json`` is passed. + json_dumps = staticmethod(json.dumps) + del json + + _args: MultiDict[str, str] | None + _query_string: str | None + _input_stream: t.IO[bytes] | None + _form: MultiDict[str, str] | None + _files: FileMultiDict | None + + def __init__( + self, + path: str = "/", + base_url: str | None = None, + query_string: t.Mapping[str, str] | str | None = None, + method: str = "GET", + input_stream: t.IO[bytes] | None = None, + content_type: str | None = None, + content_length: int | None = None, + errors_stream: t.IO[str] | None = None, + multithread: bool = False, + multiprocess: bool = False, + run_once: bool = False, + headers: Headers | t.Iterable[tuple[str, str]] | None = None, + 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, + mimetype: str | None = None, + json: t.Mapping[str, t.Any] | None = None, + auth: Authorization | tuple[str, str] | None = None, + ) -> None: + 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 "?" in path: + query_string = request_uri.query + + 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) + self.base_url = base_url # type: ignore + if isinstance(query_string, str): + self.query_string = query_string + else: + if query_string is None: + query_string = MultiDict() + elif not isinstance(query_string, MultiDict): + query_string = MultiDict(query_string) + self.args = query_string + self.method = method + if headers is None: + headers = Headers() + elif not isinstance(headers, Headers): + headers = Headers(headers) + self.headers = headers + if content_type is not None: + self.content_type = content_type + if errors_stream is None: + errors_stream = sys.stderr + self.errors_stream = errors_stream + self.multithread = multithread + self.multiprocess = multiprocess + self.run_once = run_once + self.environ_base = environ_base + self.environ_overrides = environ_overrides + self.input_stream = input_stream + self.content_length = content_length + self.closed = False + + if auth is not None: + if isinstance(auth, tuple): + auth = Authorization( + "basic", {"username": auth[0], "password": auth[1]} + ) + + self.headers.set("Authorization", auth.to_header()) + + if json is not None: + if data is not None: + raise TypeError("can't provide both json and data") + + data = self.json_dumps(json) + + if self.content_type is None: + self.content_type = "application/json" + + if data: + if input_stream is not None: + raise TypeError("can't provide input stream and data") + if hasattr(data, "read"): + data = data.read() + if isinstance(data, str): + data = data.encode() + if isinstance(data, bytes): + self.input_stream = BytesIO(data) + if self.content_length is None: + self.content_length = len(data) + else: + for key, value in _iter_data(data): + if isinstance(value, (tuple, dict)) or hasattr(value, "read"): + self._add_file_from_data(key, value) + else: + self.form.setlistdefault(key).append(value) + + if mimetype is not None: + self.mimetype = mimetype + + @classmethod + def from_environ(cls, environ: WSGIEnvironment, **kwargs: t.Any) -> EnvironBuilder: + """Turn an environ dict back into a builder. Any extra kwargs + override the args extracted from the environ. + + .. versionchanged:: 2.0 + Path and query values are passed through the WSGI decoding + dance to avoid double encoding. + + .. versionadded:: 0.15 + """ + headers = Headers(EnvironHeaders(environ)) + out = { + "path": _wsgi_decoding_dance(environ["PATH_INFO"]), + "base_url": cls._make_base_url( + environ["wsgi.url_scheme"], + headers.pop("Host"), + _wsgi_decoding_dance(environ["SCRIPT_NAME"]), + ), + "query_string": _wsgi_decoding_dance(environ["QUERY_STRING"]), + "method": environ["REQUEST_METHOD"], + "input_stream": environ["wsgi.input"], + "content_type": headers.pop("Content-Type", None), + "content_length": headers.pop("Content-Length", None), + "errors_stream": environ["wsgi.errors"], + "multithread": environ["wsgi.multithread"], + "multiprocess": environ["wsgi.multiprocess"], + "run_once": environ["wsgi.run_once"], + "headers": headers, + } + out.update(kwargs) + return cls(**out) + + def _add_file_from_data( + self, + key: str, + value: (t.IO[bytes] | tuple[t.IO[bytes], str] | tuple[t.IO[bytes], str, str]), + ) -> None: + """Called in the EnvironBuilder to add files from the data dict.""" + if isinstance(value, tuple): + self.files.add_file(key, *value) + else: + self.files.add_file(key, value) + + @staticmethod + def _make_base_url(scheme: str, host: str, script_root: str) -> str: + return urlunsplit((scheme, host, script_root, "", "")).rstrip("/") + "/" + + @property + def base_url(self) -> str: + """The base URL is used to extract the URL scheme, host name, + port, and root path. + """ + return self._make_base_url(self.url_scheme, self.host, self.script_root) + + @base_url.setter + def base_url(self, value: str | None) -> None: + if value is None: + scheme = "http" + netloc = "localhost" + script_root = "" + else: + scheme, netloc, script_root, qs, anchor = urlsplit(value) + if qs or anchor: + raise ValueError("base url must not contain a query string or fragment") + self.script_root = script_root.rstrip("/") + self.host = netloc + self.url_scheme = scheme + + @property + def content_type(self) -> str | None: + """The content type for the request. Reflected from and to + the :attr:`headers`. Do not set if you set :attr:`files` or + :attr:`form` for auto detection. + """ + ct = self.headers.get("Content-Type") + if ct is None and not self._input_stream: + if self._files: + return "multipart/form-data" + if self._form: + return "application/x-www-form-urlencoded" + return None + return ct + + @content_type.setter + def content_type(self, value: str | None) -> None: + if value is None: + self.headers.pop("Content-Type", None) + else: + self.headers["Content-Type"] = value + + @property + def mimetype(self) -> str | None: + """The mimetype (content type without charset etc.) + + .. versionadded:: 0.14 + """ + ct = self.content_type + return ct.split(";")[0].strip() if ct else None + + @mimetype.setter + def mimetype(self, value: str) -> None: + self.content_type = get_content_type(value, "utf-8") + + @property + def mimetype_params(self) -> t.Mapping[str, str]: + """The mimetype parameters as dict. For example if the + content type is ``text/html; charset=utf-8`` the params would be + ``{'charset': 'utf-8'}``. + + .. versionadded:: 0.14 + """ + + def on_update(d: CallbackDict[str, str]) -> None: + self.headers["Content-Type"] = dump_options_header(self.mimetype, d) + + d = parse_options_header(self.headers.get("content-type", ""))[1] + return CallbackDict(d, on_update) + + @property + def content_length(self) -> int | None: + """The content length as integer. Reflected from and to the + :attr:`headers`. Do not set if you set :attr:`files` or + :attr:`form` for auto detection. + """ + return self.headers.get("Content-Length", type=int) + + @content_length.setter + def content_length(self, value: int | None) -> None: + if value is None: + self.headers.pop("Content-Length", None) + else: + self.headers["Content-Length"] = str(value) + + def _get_form(self, name: str, storage: type[_TAnyMultiDict]) -> _TAnyMultiDict: + """Common behavior for getting the :attr:`form` and + :attr:`files` properties. + + :param name: Name of the internal cached attribute. + :param storage: Storage class used for the data. + """ + if self.input_stream is not None: + raise AttributeError("an input stream is defined") + + rv = getattr(self, name) + + if rv is None: + rv = storage() + setattr(self, name, rv) + + return rv # type: ignore + + def _set_form(self, name: str, value: MultiDict[str, t.Any]) -> None: + """Common behavior for setting the :attr:`form` and + :attr:`files` properties. + + :param name: Name of the internal cached attribute. + :param value: Value to assign to the attribute. + """ + self._input_stream = None + setattr(self, name, value) + + @property + def form(self) -> MultiDict[str, str]: + """A :class:`MultiDict` of form values.""" + return self._get_form("_form", MultiDict) + + @form.setter + def form(self, value: MultiDict[str, str]) -> None: + self._set_form("_form", value) + + @property + def files(self) -> FileMultiDict: + """A :class:`FileMultiDict` of uploaded files. Use + :meth:`~FileMultiDict.add_file` to add new files. + """ + return self._get_form("_files", FileMultiDict) + + @files.setter + def files(self, value: FileMultiDict) -> None: + self._set_form("_files", value) + + @property + def input_stream(self) -> t.IO[bytes] | None: + """An optional input stream. This is mutually exclusive with + setting :attr:`form` and :attr:`files`, setting it will clear + those. Do not provide this if the method is not ``POST`` or + another method that has a body. + """ + return self._input_stream + + @input_stream.setter + def input_stream(self, value: t.IO[bytes] | None) -> None: + self._input_stream = value + self._form = None + self._files = None + + @property + def query_string(self) -> str: + """The query string. If you set this to a string + :attr:`args` will no longer be available. + """ + if self._query_string is None: + if self._args is not None: + return _urlencode(self._args) + return "" + return self._query_string + + @query_string.setter + def query_string(self, value: str | None) -> None: + self._query_string = value + self._args = None + + @property + def args(self) -> MultiDict[str, str]: + """The URL arguments as :class:`MultiDict`.""" + if self._query_string is not None: + raise AttributeError("a query string is defined") + if self._args is None: + self._args = MultiDict() + return self._args + + @args.setter + def args(self, value: MultiDict[str, str] | None) -> None: + self._query_string = None + self._args = value + + @property + def server_name(self) -> str: + """The server name (read-only, use :attr:`host` to set)""" + return self.host.split(":", 1)[0] + + @property + def server_port(self) -> int: + """The server port as integer (read-only, use :attr:`host` to set)""" + pieces = self.host.split(":", 1) + + if len(pieces) == 2: + try: + return int(pieces[1]) + except ValueError: + pass + + if self.url_scheme == "https": + return 443 + return 80 + + def __del__(self) -> None: + try: + self.close() + except Exception: + pass + + def close(self) -> None: + """Closes all files. If you put real :class:`file` objects into the + :attr:`files` dict you can call this method to automatically close + them all in one go. + """ + if self.closed: + return + try: + files = self.files.values() + except AttributeError: + files = () # type: ignore + for f in files: + try: + f.close() + except Exception: + pass + self.closed = True + + def get_environ(self) -> WSGIEnvironment: + """Return the built environ. + + .. versionchanged:: 0.15 + The content type and length headers are set based on + input stream detection. Previously this only set the WSGI + keys. + """ + input_stream = self.input_stream + content_length = self.content_length + + mimetype = self.mimetype + content_type = self.content_type + + if input_stream is not None: + start_pos = input_stream.tell() + input_stream.seek(0, 2) + end_pos = input_stream.tell() + input_stream.seek(start_pos) + content_length = end_pos - start_pos + elif mimetype == "multipart/form-data": + input_stream, content_length, boundary = stream_encode_multipart( + CombinedMultiDict([self.form, self.files]) + ) + content_type = f'{mimetype}; boundary="{boundary}"' + elif mimetype == "application/x-www-form-urlencoded": + form_encoded = _urlencode(self.form).encode("ascii") + content_length = len(form_encoded) + input_stream = BytesIO(form_encoded) + else: + input_stream = BytesIO() + + result: WSGIEnvironment = {} + if self.environ_base: + result.update(self.environ_base) + + def _path_encode(x: str) -> str: + return _wsgi_encoding_dance(unquote(x)) + + 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), + # Non-standard, added by mod_wsgi, uWSGI + "REQUEST_URI": raw_uri, + # Non-standard, added by gunicorn + "RAW_URI": raw_uri, + "SERVER_NAME": self.server_name, + "SERVER_PORT": str(self.server_port), + "HTTP_HOST": self.host, + "SERVER_PROTOCOL": self.server_protocol, + "wsgi.version": self.wsgi_version, + "wsgi.url_scheme": self.url_scheme, + "wsgi.input": input_stream, + "wsgi.errors": self.errors_stream, + "wsgi.multithread": self.multithread, + "wsgi.multiprocess": self.multiprocess, + "wsgi.run_once": self.run_once, + } + ) + + headers = self.headers.copy() + # Don't send these as headers, they're part of the environ. + headers.remove("Content-Type") + headers.remove("Content-Length") + + if content_type is not None: + result["CONTENT_TYPE"] = content_type + + if content_length is not None: + result["CONTENT_LENGTH"] = str(content_length) + + combined_headers = defaultdict(list) + + for key, value in headers.to_wsgi_list(): + combined_headers[f"HTTP_{key.upper().replace('-', '_')}"].append(value) + + for key, values in combined_headers.items(): + result[key] = ", ".join(values) + + if self.environ_overrides: + result.update(self.environ_overrides) + + return result + + def get_request(self, cls: type[Request] | None = None) -> Request: + """Returns a request with the data. If the request class is not + specified :attr:`request_class` is used. + + :param cls: The request wrapper to use. + """ + if cls is None: + cls = self.request_class + + return cls(self.get_environ()) + + +class ClientRedirectError(Exception): + """If a redirect loop is detected when using follow_redirects=True with + the :cls:`Client`, then this exception is raised. + """ + + +class Client: + """Simulate sending requests to a WSGI application without running a WSGI or HTTP + server. + + :param application: The WSGI application to make requests to. + :param response_wrapper: A :class:`.Response` class to wrap response data with. + Defaults to :class:`.TestResponse`. If it's not a subclass of ``TestResponse``, + one will be created. + :param use_cookies: Persist cookies from ``Set-Cookie`` response headers to the + ``Cookie`` header in subsequent requests. Domain and path matching is supported, + but other cookie parameters are ignored. + :param allow_subdomain_redirects: Allow requests to follow redirects to subdomains. + Enable this if the application handles subdomains and redirects between them. + + .. versionchanged:: 2.3 + Simplify cookie implementation, support domain and path matching. + + .. versionchanged:: 2.1 + All data is available as properties on the returned response object. The + response cannot be returned as a tuple. + + .. versionchanged:: 2.0 + ``response_wrapper`` is always a subclass of :class:``TestResponse``. + + .. versionchanged:: 0.5 + Added the ``use_cookies`` parameter. + """ + + def __init__( + self, + application: WSGIApplication, + response_wrapper: type[Response] | None = None, + use_cookies: bool = True, + allow_subdomain_redirects: bool = False, + ) -> None: + self.application = application + + if response_wrapper in {None, Response}: + response_wrapper = TestResponse + elif response_wrapper is not None and not issubclass( + response_wrapper, TestResponse + ): + response_wrapper = type( + "WrapperTestResponse", + (TestResponse, response_wrapper), + {}, + ) + + self.response_wrapper = t.cast(t.Type["TestResponse"], response_wrapper) + + if use_cookies: + self._cookies: dict[tuple[str, str, str], Cookie] | None = {} + else: + self._cookies = None + + self.allow_subdomain_redirects = allow_subdomain_redirects + + def get_cookie( + self, key: str, domain: str = "localhost", path: str = "/" + ) -> Cookie | None: + """Return a :class:`.Cookie` if it exists. Cookies are uniquely identified by + ``(domain, path, key)``. + + :param key: The decoded form of the key for the cookie. + :param domain: The domain the cookie was set for. + :param path: The path the cookie was set for. + + .. versionadded:: 2.3 + """ + if self._cookies is None: + raise TypeError( + "Cookies are disabled. Create a client with 'use_cookies=True'." + ) + + return self._cookies.get((domain, path, key)) + + def set_cookie( + self, + key: str, + value: str = "", + *, + domain: str = "localhost", + origin_only: bool = True, + path: str = "/", + **kwargs: t.Any, + ) -> None: + """Set a cookie to be sent in subsequent requests. + + This is a convenience to skip making a test request to a route that would set + the cookie. To test the cookie, make a test request to a route that uses the + cookie value. + + The client uses ``domain``, ``origin_only``, and ``path`` to determine which + cookies to send with a request. It does not use other cookie parameters that + browsers use, since they're not applicable in tests. + + :param key: The key part of the cookie. + :param value: The value part of the cookie. + :param domain: Send this cookie with requests that match this domain. If + ``origin_only`` is true, it must be an exact match, otherwise it may be a + suffix match. + :param origin_only: Whether the domain must be an exact match to the request. + :param path: Send this cookie with requests that match this path either exactly + 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``. + """ + if self._cookies is None: + raise TypeError( + "Cookies are disabled. Create a client with 'use_cookies=True'." + ) + + cookie = Cookie._from_response_header( + domain, "/", dump_cookie(key, value, domain=domain, path=path, **kwargs) + ) + cookie.origin_only = origin_only + + if cookie._should_delete: + self._cookies.pop(cookie._storage_key, None) + else: + self._cookies[cookie._storage_key] = cookie + + def delete_cookie( + self, + key: str, + *, + domain: str = "localhost", + path: str = "/", + ) -> None: + """Delete a cookie if it exists. Cookies are uniquely identified by + ``(domain, path, key)``. + + :param key: The decoded form of the key for the cookie. + :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``. + """ + if self._cookies is None: + raise TypeError( + "Cookies are disabled. Create a client with 'use_cookies=True'." + ) + + self._cookies.pop((domain, path, key), None) + + def _add_cookies_to_wsgi(self, environ: WSGIEnvironment) -> None: + """If cookies are enabled, set the ``Cookie`` header in the environ to the + cookies that are applicable to the request host and path. + + :meta private: + + .. versionadded:: 2.3 + """ + if self._cookies is None: + return + + url = urlsplit(get_current_url(environ)) + server_name = url.hostname or "localhost" + value = "; ".join( + c._to_request_header() + for c in self._cookies.values() + if c._matches_request(server_name, url.path) + ) + + if value: + environ["HTTP_COOKIE"] = value + else: + environ.pop("HTTP_COOKIE", None) + + def _update_cookies_from_response( + self, server_name: str, path: str, headers: list[str] + ) -> None: + """If cookies are enabled, update the stored cookies from any ``Set-Cookie`` + headers in the response. + + :meta private: + + .. versionadded:: 2.3 + """ + if self._cookies is None: + return + + for header in headers: + cookie = Cookie._from_response_header(server_name, path, header) + + if cookie._should_delete: + self._cookies.pop(cookie._storage_key, None) + else: + self._cookies[cookie._storage_key] = cookie + + def run_wsgi_app( + self, environ: WSGIEnvironment, buffered: bool = False + ) -> tuple[t.Iterable[bytes], str, Headers]: + """Runs the wrapped WSGI app with the given environment. + + :meta private: + """ + self._add_cookies_to_wsgi(environ) + rv = run_wsgi_app(self.application, environ, buffered=buffered) + url = urlsplit(get_current_url(environ)) + self._update_cookies_from_response( + url.hostname or "localhost", url.path, rv[2].getlist("Set-Cookie") + ) + return rv + + def resolve_redirect( + self, response: TestResponse, buffered: bool = False + ) -> TestResponse: + """Perform a new request to the location given by the redirect + response to the previous request. + + :meta private: + """ + scheme, netloc, path, qs, anchor = urlsplit(response.location) + builder = EnvironBuilder.from_environ( + response.request.environ, path=path, query_string=qs + ) + + to_name_parts = netloc.split(":", 1)[0].split(".") + from_name_parts = builder.server_name.split(".") + + if to_name_parts != [""]: + # The new location has a host, use it for the base URL. + builder.url_scheme = scheme + builder.host = netloc + else: + # A local redirect with autocorrect_location_header=False + # doesn't have a host, so use the request's host. + to_name_parts = from_name_parts + + # Explain why a redirect to a different server name won't be followed. + if to_name_parts != from_name_parts: + if to_name_parts[-len(from_name_parts) :] == from_name_parts: + if not self.allow_subdomain_redirects: + raise RuntimeError("Following subdomain redirects is not enabled.") + else: + raise RuntimeError("Following external redirects is not supported.") + + path_parts = path.split("/") + root_parts = builder.script_root.split("/") + + if path_parts[: len(root_parts)] == root_parts: + # Strip the script root from the path. + builder.path = path[len(builder.script_root) :] + else: + # The new location is not under the script root, so use the + # whole path and clear the previous root. + builder.path = path + builder.script_root = "" + + # Only 307 and 308 preserve all of the original request. + if response.status_code not in {307, 308}: + # HEAD is preserved, everything else becomes GET. + if builder.method != "HEAD": + builder.method = "GET" + + # Clear the body and the headers that describe it. + + if builder.input_stream is not None: + builder.input_stream.close() + builder.input_stream = None + + builder.content_type = None + builder.content_length = None + builder.headers.pop("Transfer-Encoding", None) + + return self.open(builder, buffered=buffered) + + def open( + self, + *args: t.Any, + buffered: bool = False, + follow_redirects: bool = False, + **kwargs: t.Any, + ) -> TestResponse: + """Generate an environ dict from the given arguments, make a + request to the application using it, and return the response. + + :param args: Passed to :class:`EnvironBuilder` to create the + environ for the request. If a single arg is passed, it can + be an existing :class:`EnvironBuilder` or an environ dict. + :param buffered: Convert the iterator returned by the app into + a list. If the iterator has a ``close()`` method, it is + called automatically. + :param follow_redirects: Make additional requests to follow HTTP + redirects until a non-redirect status is returned. + :attr:`TestResponse.history` lists the intermediate + responses. + + .. versionchanged:: 2.1 + Removed the ``as_tuple`` parameter. + + .. versionchanged:: 2.0 + The request input stream is closed when calling + ``response.close()``. Input streams for redirects are + automatically closed. + + .. versionchanged:: 0.5 + If a dict is provided as file in the dict for the ``data`` + parameter the content type has to be called ``content_type`` + instead of ``mimetype``. This change was made for + consistency with :class:`werkzeug.FileWrapper`. + + .. versionchanged:: 0.5 + Added the ``follow_redirects`` parameter. + """ + request: Request | None = None + + if not kwargs and len(args) == 1: + arg = args[0] + + if isinstance(arg, EnvironBuilder): + request = arg.get_request() + elif isinstance(arg, dict): + request = EnvironBuilder.from_environ(arg).get_request() + elif isinstance(arg, Request): + request = arg + + if request is None: + builder = EnvironBuilder(*args, **kwargs) + + try: + request = builder.get_request() + finally: + builder.close() + + response_parts = self.run_wsgi_app(request.environ, buffered=buffered) + response = self.response_wrapper(*response_parts, request=request) + + redirects = set() + history: list[TestResponse] = [] + + if not follow_redirects: + return response + + while response.status_code in { + 301, + 302, + 303, + 305, + 307, + 308, + }: + # Exhaust intermediate response bodies to ensure middleware + # that returns an iterator runs any cleanup code. + if not buffered: + response.make_sequence() + response.close() + + new_redirect_entry = (response.location, response.status_code) + + if new_redirect_entry in redirects: + raise ClientRedirectError( + f"Loop detected: A {response.status_code} redirect" + f" to {response.location} was already made." + ) + + redirects.add(new_redirect_entry) + response.history = tuple(history) + history.append(response) + response = self.resolve_redirect(response, buffered=buffered) + else: + # This is the final request after redirects. + response.history = tuple(history) + # Close the input stream when closing the response, in case + # the input is an open temporary file. + response.call_on_close(request.input_stream.close) + return response + + def get(self, *args: t.Any, **kw: t.Any) -> TestResponse: + """Call :meth:`open` with ``method`` set to ``GET``.""" + kw["method"] = "GET" + return self.open(*args, **kw) + + def post(self, *args: t.Any, **kw: t.Any) -> TestResponse: + """Call :meth:`open` with ``method`` set to ``POST``.""" + kw["method"] = "POST" + return self.open(*args, **kw) + + def put(self, *args: t.Any, **kw: t.Any) -> TestResponse: + """Call :meth:`open` with ``method`` set to ``PUT``.""" + kw["method"] = "PUT" + return self.open(*args, **kw) + + def delete(self, *args: t.Any, **kw: t.Any) -> TestResponse: + """Call :meth:`open` with ``method`` set to ``DELETE``.""" + kw["method"] = "DELETE" + return self.open(*args, **kw) + + def patch(self, *args: t.Any, **kw: t.Any) -> TestResponse: + """Call :meth:`open` with ``method`` set to ``PATCH``.""" + kw["method"] = "PATCH" + return self.open(*args, **kw) + + def options(self, *args: t.Any, **kw: t.Any) -> TestResponse: + """Call :meth:`open` with ``method`` set to ``OPTIONS``.""" + kw["method"] = "OPTIONS" + return self.open(*args, **kw) + + def head(self, *args: t.Any, **kw: t.Any) -> TestResponse: + """Call :meth:`open` with ``method`` set to ``HEAD``.""" + kw["method"] = "HEAD" + return self.open(*args, **kw) + + def trace(self, *args: t.Any, **kw: t.Any) -> TestResponse: + """Call :meth:`open` with ``method`` set to ``TRACE``.""" + kw["method"] = "TRACE" + return self.open(*args, **kw) + + def __repr__(self) -> str: + return f"<{type(self).__name__} {self.application!r}>" + + +def create_environ(*args: t.Any, **kwargs: t.Any) -> WSGIEnvironment: + """Create a new WSGI environ dict based on the values passed. The first + parameter should be the path of the request which defaults to '/'. The + second one can either be an absolute path (in that case the host is + localhost:80) or a full path to the request with scheme, netloc port and + the path to the script. + + This accepts the same arguments as the :class:`EnvironBuilder` + constructor. + + .. versionchanged:: 0.5 + This function is now a thin wrapper over :class:`EnvironBuilder` which + was added in 0.5. The `headers`, `environ_base`, `environ_overrides` + and `charset` parameters were added. + """ + builder = EnvironBuilder(*args, **kwargs) + + try: + return builder.get_environ() + finally: + builder.close() + + +def run_wsgi_app( + app: WSGIApplication, environ: WSGIEnvironment, buffered: bool = False +) -> tuple[t.Iterable[bytes], str, Headers]: + """Return a tuple in the form (app_iter, status, headers) of the + application output. This works best if you pass it an application that + returns an iterator all the time. + + Sometimes applications may use the `write()` callable returned + by the `start_response` function. This tries to resolve such edge + cases automatically. But if you don't get the expected output you + should set `buffered` to `True` which enforces buffering. + + If passed an invalid WSGI application the behavior of this function is + undefined. Never pass non-conforming WSGI applications to this function. + + :param app: the application to execute. + :param buffered: set to `True` to enforce buffering. + :return: tuple in the form ``(app_iter, status, headers)`` + """ + # Copy environ to ensure any mutations by the app (ProxyFix, for + # example) don't affect subsequent requests (such as redirects). + environ = _get_environ(environ).copy() + status: str + response: tuple[str, list[tuple[str, str]]] | None = None + buffer: list[bytes] = [] + + def start_response(status, headers, exc_info=None): # type: ignore + nonlocal response + + if exc_info: + try: + raise exc_info[1].with_traceback(exc_info[2]) + finally: + exc_info = None + + response = (status, headers) + return buffer.append + + app_rv = app(environ, start_response) + close_func = getattr(app_rv, "close", None) + app_iter: t.Iterable[bytes] = iter(app_rv) + + # when buffering we emit the close call early and convert the + # application iterator into a regular list + if buffered: + try: + app_iter = list(app_iter) + finally: + if close_func is not None: + close_func() + + # otherwise we iterate the application iter until we have a response, chain + # the already received data with the already collected data and wrap it in + # a new `ClosingIterator` if we need to restore a `close` callable from the + # original return value. + else: + for item in app_iter: + buffer.append(item) + + if response is not None: + break + + if buffer: + app_iter = chain(buffer, app_iter) + + if close_func is not None and app_iter is not app_rv: + app_iter = ClosingIterator(app_iter, close_func) + + status, headers = response # type: ignore + return app_iter, status, Headers(headers) + + +class TestResponse(Response): + """:class:`~werkzeug.wrappers.Response` subclass that provides extra + information about requests made with the test :class:`Client`. + + Test client requests will always return an instance of this class. + If a custom response class is passed to the client, it is + subclassed along with this to support test information. + + If the test request included large files, or if the application is + serving a file, call :meth:`close` to close any open files and + prevent Python showing a ``ResourceWarning``. + + .. versionchanged:: 2.2 + Set the ``default_mimetype`` to None to prevent a mimetype being + assumed if missing. + + .. versionchanged:: 2.1 + Response instances cannot be treated as tuples. + + .. versionadded:: 2.0 + Test client methods always return instances of this class. + """ + + default_mimetype = None + # Don't assume a mimetype, instead use whatever the response provides + + request: Request + """A request object with the environ used to make the request that + resulted in this response. + """ + + history: tuple[TestResponse, ...] + """A list of intermediate responses. Populated when the test request + is made with ``follow_redirects`` enabled. + """ + + # Tell Pytest to ignore this, it's not a test class. + __test__ = False + + def __init__( + self, + response: t.Iterable[bytes], + status: str, + headers: Headers, + request: Request, + history: tuple[TestResponse] = (), # type: ignore + **kwargs: t.Any, + ) -> None: + super().__init__(response, status, headers, **kwargs) + self.request = request + self.history = history + self._compat_tuple = response, status, headers + + @cached_property + def text(self) -> str: + """The response data as text. A shortcut for + ``response.get_data(as_text=True)``. + + .. versionadded:: 2.1 + """ + return self.get_data(as_text=True) + + +@dataclasses.dataclass +class Cookie: + """A cookie key, value, and parameters. + + The class itself is not a public API. Its attributes are documented for inspection + with :meth:`.Client.get_cookie` only. + + .. versionadded:: 2.3 + """ + + key: str + """The cookie key, encoded as a client would see it.""" + + value: str + """The cookie key, encoded as a client would see it.""" + + decoded_key: str + """The cookie key, decoded as the application would set and see it.""" + + decoded_value: str + """The cookie value, decoded as the application would set and see it.""" + + expires: datetime | None + """The time at which the cookie is no longer valid.""" + + max_age: int | None + """The number of seconds from when the cookie was set at which it is + no longer valid. + """ + + domain: str + """The domain that the cookie was set for, or the request domain if not set.""" + + origin_only: bool + """Whether the cookie will be sent for exact domain matches only. This is ``True`` + if the ``Domain`` parameter was not present. + """ + + path: str + """The path that the cookie was set for.""" + + secure: bool | None + """The ``Secure`` parameter.""" + + http_only: bool | None + """The ``HttpOnly`` parameter.""" + + same_site: str | None + """The ``SameSite`` parameter.""" + + def _matches_request(self, server_name: str, path: str) -> bool: + return ( + server_name == self.domain + or ( + not self.origin_only + and server_name.endswith(self.domain) + and server_name[: -len(self.domain)].endswith(".") + ) + ) and ( + path == self.path + or ( + path.startswith(self.path) + and path[len(self.path) - self.path.endswith("/") :].startswith("/") + ) + ) + + def _to_request_header(self) -> str: + return f"{self.key}={self.value}" + + @classmethod + def _from_response_header(cls, server_name: str, path: str, header: str) -> te.Self: + header, _, parameters_str = header.partition(";") + key, _, value = header.partition("=") + decoded_key, decoded_value = next(parse_cookie(header).items()) + params = {} + + for item in parameters_str.split(";"): + k, sep, v = item.partition("=") + params[k.strip().lower()] = v.strip() if sep else None + + return cls( + key=key.strip(), + value=value.strip(), + decoded_key=decoded_key, + decoded_value=decoded_value, + expires=parse_date(params.get("expires")), + max_age=int(params["max-age"] or 0) if "max-age" in params else None, + domain=params.get("domain") or server_name, + origin_only="domain" not in params, + path=params.get("path") or path.rpartition("/")[0] or "/", + secure="secure" in params, + http_only="httponly" in params, + same_site=params.get("samesite"), + ) + + @property + def _storage_key(self) -> tuple[str, str, str]: + return self.domain, self.path, self.decoded_key + + @property + def _should_delete(self) -> bool: + return self.max_age == 0 or ( + self.expires is not None and self.expires.timestamp() == 0 + ) diff --git a/venv/Lib/site-packages/werkzeug/testapp.py b/venv/Lib/site-packages/werkzeug/testapp.py new file mode 100644 index 00000000..cdf7fac1 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/testapp.py @@ -0,0 +1,194 @@ +"""A small application that can be used to test a WSGI server and check +it for WSGI compliance. +""" + +from __future__ import annotations + +import importlib.metadata +import os +import sys +import typing as t +from textwrap import wrap + +from markupsafe import escape + +from .wrappers.request import Request +from .wrappers.response import Response + +TEMPLATE = """\ + + +WSGI Information + +

    +

    WSGI Information

    +

    + This page displays all available information about the WSGI server and + the underlying Python interpreter. +

    Python Interpreter

    + + + + + + +
    Python Version + %(python_version)s +
    Platform + %(platform)s [%(os)s] +
    API Version + %(api_version)s +
    Byteorder + %(byteorder)s +
    Werkzeug Version + %(werkzeug_version)s +
    +

    WSGI Environment

    + %(wsgi_env)s
    +

    Installed Eggs

    +

    + The following python packages were installed on the system as + Python eggs: +

      %(python_eggs)s
    +

    System Path

    +

    + The following paths are the current contents of the load path. The + following entries are looked up for Python packages. Note that not + all items in this path are folders. Gray and underlined items are + entries pointing to invalid resources or used by custom import hooks + such as the zip importer. +

    + Items with a bright background were expanded for display from a relative + path. If you encounter such paths in the output you might want to check + your setup as relative paths are usually problematic in multithreaded + environments. +

      %(sys_path)s
    +
    +""" + + +def iter_sys_path() -> t.Iterator[tuple[str, bool, bool]]: + if os.name == "posix": + + def strip(x: str) -> str: + prefix = os.path.expanduser("~") + if x.startswith(prefix): + x = f"~{x[len(prefix) :]}" + return x + + else: + + def strip(x: str) -> str: + return x + + cwd = os.path.abspath(os.getcwd()) + for item in sys.path: + path = os.path.join(cwd, item or os.path.curdir) + yield strip(os.path.normpath(path)), not os.path.isdir(path), path != item + + +@Request.application +def test_app(req: Request) -> Response: + """Simple test application that dumps the environment. You can use + it to check if Werkzeug is working properly: + + .. sourcecode:: pycon + + >>> from werkzeug.serving import run_simple + >>> from werkzeug.testapp import test_app + >>> run_simple('localhost', 3000, test_app) + * Running on http://localhost:3000/ + + The application displays important information from the WSGI environment, + the Python interpreter and the installed libraries. + """ + try: + import pkg_resources + except ImportError: + eggs: t.Iterable[t.Any] = () + else: + eggs = sorted( + pkg_resources.working_set, + key=lambda x: x.project_name.lower(), + ) + python_eggs = [] + for egg in eggs: + try: + version = egg.version + except (ValueError, AttributeError): + version = "unknown" + python_eggs.append( + f"
  • {escape(egg.project_name)} [{escape(version)}]" + ) + + wsgi_env = [] + sorted_environ = sorted(req.environ.items(), key=lambda x: repr(x[0]).lower()) + for key, value in sorted_environ: + value = "".join(wrap(str(escape(repr(value))))) + wsgi_env.append(f"{escape(key)}{value}") + + sys_path = [] + for item, virtual, expanded in iter_sys_path(): + css = [] + if virtual: + css.append("virtual") + if expanded: + css.append("exp") + class_str = f' class="{" ".join(css)}"' if css else "" + sys_path.append(f"{escape(item)}") + + context = { + "python_version": "
    ".join(escape(sys.version).splitlines()), + "platform": escape(sys.platform), + "os": escape(os.name), + "api_version": sys.api_version, + "byteorder": sys.byteorder, + "werkzeug_version": _get_werkzeug_version(), + "python_eggs": "\n".join(python_eggs), + "wsgi_env": "\n".join(wsgi_env), + "sys_path": "\n".join(sys_path), + } + return Response(TEMPLATE % context, mimetype="text/html") + + +_werkzeug_version = "" + + +def _get_werkzeug_version() -> str: + global _werkzeug_version + + if not _werkzeug_version: + _werkzeug_version = importlib.metadata.version("werkzeug") + + return _werkzeug_version + + +if __name__ == "__main__": + from .serving import run_simple + + run_simple("localhost", 5000, test_app, use_reloader=True) diff --git a/venv/Lib/site-packages/werkzeug/urls.py b/venv/Lib/site-packages/werkzeug/urls.py new file mode 100644 index 00000000..5bffe392 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/urls.py @@ -0,0 +1,203 @@ +from __future__ import annotations + +import codecs +import re +import typing as t +import urllib.parse +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 .datastructures import iter_multi_items + + +def _codec_error_url_quote(e: UnicodeError) -> tuple[str, int]: + """Used in :func:`uri_to_iri` after unquoting to re-quote any + invalid bytes. + """ + # the docs state that UnicodeError does have these attributes, + # but mypy isn't picking them up + out = quote(e.object[e.start : e.end], safe="") # type: ignore + return out, e.end # type: ignore + + +codecs.register_error("werkzeug.url_quote", _codec_error_url_quote) + + +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. + """ + choices = "|".join(f"{ord(c):02X}" for c in sorted(chars)) + pattern = re.compile(f"((?:%(?:{choices}))+)", re.I) + + def _unquote_partial(value: str) -> str: + parts = iter(pattern.split(value)) + out = [] + + for part in parts: + out.append(unquote(part, "utf-8", "werkzeug.url_quote")) + out.append(next(parts, "")) + + return "".join(out) + + _unquote_partial.__name__ = f"_unquote_{name}" + return _unquote_partial + + +# characters that should remain quoted in URL parts +# based on https://url.spec.whatwg.org/#percent-encoded-bytes +# always keep all controls, space, and % quoted +_always_unsafe = bytes((*range(0x21), 0x25, 0x7F)).decode() +_unquote_fragment = _make_unquote_part("fragment", _always_unsafe) +_unquote_query = _make_unquote_part("query", _always_unsafe + "&=+#") +_unquote_path = _make_unquote_part("path", _always_unsafe + "/?#") +_unquote_user = _make_unquote_part("user", _always_unsafe + ":@/?#") + + +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. + + >>> uri_to_iri("http://xn--n3h.net/p%C3%A5th?q=%C3%A8ry%DF") + 'http://\\u2603.net/p\\xe5th?q=\\xe8ry%DF' + + :param uri: The URI to convert. + + .. 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. + + .. versionchanged:: 0.15 + All reserved and invalid characters remain quoted. Previously, + only some reserved characters were preserved, and invalid bytes + were replaced instead of left quoted. + + .. versionadded:: 0.6 + """ + parts = urlsplit(uri) + path = _unquote_path(parts.path) + query = _unquote_query(parts.query) + fragment = _unquote_fragment(parts.fragment) + + if parts.hostname: + netloc = _decode_idna(parts.hostname) + else: + netloc = "" + + if ":" in netloc: + netloc = f"[{netloc}]" + + if parts.port: + netloc = f"{netloc}:{parts.port}" + + if parts.username: + auth = _unquote_user(parts.username) + + if parts.password: + 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) -> 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. + + >>> iri_to_uri('http://\\u2603.net/p\\xe5th?q=\\xe8ry%DF') + 'http://xn--n3h.net/p%C3%A5th?q=%C3%A8ry%DF' + + :param iri: The IRI to convert. + + .. 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:: 0.15 + All reserved characters remain unquoted. Previously, only some reserved + characters were left unquoted. + + .. versionchanged:: 0.9.6 + The ``safe_conversion`` parameter was added. + + .. versionadded:: 0.6 + """ + 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="%!$&'()*+,/:;=@") + query = quote(parts.query, safe="%!$&'()*+,/:;=?@") + fragment = quote(parts.fragment, safe="%!#$&'()*+,/:;=?@") + + if parts.hostname: + netloc = parts.hostname.encode("idna").decode("ascii") + else: + netloc = "" + + if ":" in netloc: + netloc = f"[{netloc}]" + + if parts.port: + netloc = f"{netloc}:{parts.port}" + + if parts.username: + auth = quote(parts.username, safe="%!$&'()*+,;=") + + if parts.password: + password = quote(parts.password, safe="%!$&'()*+,;=") + auth = f"{auth}:{password}" + + netloc = f"{auth}@{netloc}" + + return urlunsplit((parts.scheme, netloc, path, query, fragment)) + + +# Python < 3.12 +# itms-services was worked around in previous iri_to_uri implementations, but +# we can tell Python directly that it needs to preserve the //. +if "itms-services" not in urllib.parse.uses_netloc: + urllib.parse.uses_netloc.append("itms-services") + + +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) + + +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="!$'()*,/:;?@") diff --git a/venv/Lib/site-packages/werkzeug/user_agent.py b/venv/Lib/site-packages/werkzeug/user_agent.py new file mode 100644 index 00000000..17e5d3fd --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/user_agent.py @@ -0,0 +1,47 @@ +from __future__ import annotations + + +class UserAgent: + """Represents a parsed user agent header value. + + The default implementation does no parsing, only the :attr:`string` + attribute is set. A subclass may parse the string to set the + common attributes or expose other information. Set + :attr:`werkzeug.wrappers.Request.user_agent_class` to use a + subclass. + + :param string: The header value to parse. + + .. versionadded:: 2.0 + This replaces the previous ``useragents`` module, but does not + provide a built-in parser. + """ + + platform: str | None = None + """The OS name, if it could be parsed from the string.""" + + browser: str | None = None + """The browser name, if it could be parsed from the string.""" + + version: str | None = None + """The browser version, if it could be parsed from the string.""" + + language: str | None = None + """The browser language, if it could be parsed from the string.""" + + def __init__(self, string: str) -> None: + self.string: str = string + """The original header value.""" + + def __repr__(self) -> str: + return f"<{type(self).__name__} {self.browser}/{self.version}>" + + def __str__(self) -> str: + return self.string + + def __bool__(self) -> bool: + return bool(self.browser) + + def to_header(self) -> str: + """Convert to a header value.""" + return self.string diff --git a/venv/Lib/site-packages/werkzeug/utils.py b/venv/Lib/site-packages/werkzeug/utils.py new file mode 100644 index 00000000..59b97b73 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/utils.py @@ -0,0 +1,691 @@ +from __future__ import annotations + +import io +import mimetypes +import os +import pkgutil +import re +import sys +import typing as t +import unicodedata +from datetime import datetime +from time import time +from urllib.parse import quote +from zlib import adler32 + +from markupsafe import escape + +from ._internal import _DictAccessorProperty +from ._internal import _missing +from ._internal import _TAccessorValue +from .datastructures import Headers +from .exceptions import NotFound +from .exceptions import RequestedRangeNotSatisfiable +from .security import safe_join +from .wsgi import wrap_file + +if t.TYPE_CHECKING: + from _typeshed.wsgi import WSGIEnvironment + + from .wrappers.request import Request + from .wrappers.response import Response + +_T = t.TypeVar("_T") + +_entity_re = re.compile(r"&([^;]+);") +_filename_ascii_strip_re = re.compile(r"[^A-Za-z0-9_.-]") +_windows_device_files = { + "CON", + "PRN", + "AUX", + "NUL", + *(f"COM{i}" for i in range(10)), + *(f"LPT{i}" for i in range(10)), +} + + +class cached_property(property, t.Generic[_T]): + """A :func:`property` that is only evaluated once. Subsequent access + returns the cached value. Setting the property sets the cached + value. Deleting the property clears the cached value, accessing it + again will evaluate it again. + + .. code-block:: python + + class Example: + @cached_property + def value(self): + # calculate something important here + return 42 + + e = Example() + e.value # evaluates + e.value # uses cache + e.value = 16 # sets cache + del e.value # clears cache + + If the class defines ``__slots__``, it must add ``_cache_{name}`` as + a slot. Alternatively, it can add ``__dict__``, but that's usually + not desirable. + + .. versionchanged:: 2.1 + Works with ``__slots__``. + + .. versionchanged:: 2.0 + ``del obj.name`` clears the cached value. + """ + + def __init__( + self, + fget: t.Callable[[t.Any], _T], + name: str | None = None, + doc: str | None = None, + ) -> None: + super().__init__(fget, doc=doc) + self.__name__ = name or fget.__name__ + self.slot_name = f"_cache_{self.__name__}" + self.__module__ = fget.__module__ + + def __set__(self, obj: object, value: _T) -> None: + if hasattr(obj, "__dict__"): + obj.__dict__[self.__name__] = value + else: + setattr(obj, self.slot_name, value) + + def __get__(self, obj: object, type: type = None) -> _T: # type: ignore + if obj is None: + return self # type: ignore + + obj_dict = getattr(obj, "__dict__", None) + + if obj_dict is not None: + value: _T = obj_dict.get(self.__name__, _missing) + else: + value = getattr(obj, self.slot_name, _missing) # type: ignore[arg-type] + + if value is _missing: + value = self.fget(obj) # type: ignore + + if obj_dict is not None: + obj.__dict__[self.__name__] = value + else: + setattr(obj, self.slot_name, value) + + return value + + def __delete__(self, obj: object) -> None: + if hasattr(obj, "__dict__"): + del obj.__dict__[self.__name__] + else: + setattr(obj, self.slot_name, _missing) + + +class environ_property(_DictAccessorProperty[_TAccessorValue]): + """Maps request attributes to environment variables. This works not only + for the Werkzeug request object, but also any other class with an + environ attribute: + + >>> class Test(object): + ... environ = {'key': 'value'} + ... test = environ_property('key') + >>> var = Test() + >>> var.test + 'value' + + If you pass it a second value it's used as default if the key does not + exist, the third one can be a converter that takes a value and converts + it. If it raises :exc:`ValueError` or :exc:`TypeError` the default value + is used. If no default value is provided `None` is used. + + Per default the property is read only. You have to explicitly enable it + by passing ``read_only=False`` to the constructor. + """ + + read_only = True + + def lookup(self, obj: Request) -> WSGIEnvironment: + return obj.environ + + +class header_property(_DictAccessorProperty[_TAccessorValue]): + """Like `environ_property` but for headers.""" + + def lookup(self, obj: Request | Response) -> Headers: + return obj.headers + + +# https://cgit.freedesktop.org/xdg/shared-mime-info/tree/freedesktop.org.xml.in +# https://www.iana.org/assignments/media-types/media-types.xhtml +# Types listed in the XDG mime info that have a charset in the IANA registration. +_charset_mimetypes = { + "application/ecmascript", + "application/javascript", + "application/sql", + "application/xml", + "application/xml-dtd", + "application/xml-external-parsed-entity", +} + + +def get_content_type(mimetype: str, charset: str) -> str: + """Returns the full content type string with charset for a mimetype. + + If the mimetype represents text, the charset parameter will be + appended, otherwise the mimetype is returned unchanged. + + :param mimetype: The mimetype to be used as content type. + :param charset: The charset to be appended for text mimetypes. + :return: The content type. + + .. versionchanged:: 0.15 + Any type that ends with ``+xml`` gets a charset, not just those + that start with ``application/``. Known text types such as + ``application/javascript`` are also given charsets. + """ + if ( + mimetype.startswith("text/") + or mimetype in _charset_mimetypes + or mimetype.endswith("+xml") + ): + mimetype += f"; charset={charset}" + + return mimetype + + +def secure_filename(filename: str) -> str: + r"""Pass it a filename and it will return a secure version of it. This + filename can then safely be stored on a regular file system and passed + to :func:`os.path.join`. The filename returned is an ASCII only string + for maximum portability. + + On windows systems the function also makes sure that the file is not + named after one of the special device files. + + >>> secure_filename("My cool movie.mov") + 'My_cool_movie.mov' + >>> secure_filename("../../../etc/passwd") + 'etc_passwd' + >>> secure_filename('i contain cool \xfcml\xe4uts.txt') + 'i_contain_cool_umlauts.txt' + + The function might return an empty filename. It's your responsibility + to ensure that the filename is unique and that you abort or + generate a random filename if the function returned an empty one. + + .. versionadded:: 0.5 + + :param filename: the filename to secure + """ + filename = unicodedata.normalize("NFKD", filename) + filename = filename.encode("ascii", "ignore").decode("ascii") + + for sep in os.sep, os.path.altsep: + if sep: + filename = filename.replace(sep, " ") + filename = str(_filename_ascii_strip_re.sub("", "_".join(filename.split()))).strip( + "._" + ) + + # on nt a couple of special files are present in each folder. We + # have to ensure that the target file is not such a filename. In + # this case we prepend an underline + if ( + os.name == "nt" + and filename + and filename.split(".")[0].upper() in _windows_device_files + ): + filename = f"_{filename}" + + return filename + + +def redirect( + location: str, code: int = 302, Response: type[Response] | None = None +) -> Response: + """Returns a response object (a WSGI application) that, if called, + redirects the client to the target location. Supported codes are + 301, 302, 303, 305, 307, and 308. 300 is not supported because + it's not a real redirect and 304 because it's the answer for a + request with a request with defined If-Modified-Since headers. + + .. versionadded:: 0.6 + The location can now be a unicode string that is encoded using + the :func:`iri_to_uri` function. + + .. versionadded:: 0.10 + The class used for the Response object can now be passed in. + + :param location: the location the response should redirect to. + :param code: the redirect status code. defaults to 302. + :param class Response: a Response class to use when instantiating a + response. The default is :class:`werkzeug.wrappers.Response` if + unspecified. + """ + if Response is None: + from .wrappers import Response + + html_location = escape(location) + response = Response( # type: ignore[misc] + "\n" + "\n" + "Redirecting...\n" + "

    Redirecting...

    \n" + "

    You should be redirected automatically to the target URL: " + f'{html_location}. If not, click the link.\n', + code, + mimetype="text/html", + ) + response.headers["Location"] = location + return response + + +def append_slash_redirect(environ: WSGIEnvironment, code: int = 308) -> Response: + """Redirect to the current URL with a slash appended. + + If the current URL is ``/user/42``, the redirect URL will be + ``42/``. When joined to the current URL during response + processing or by the browser, this will produce ``/user/42/``. + + The behavior is undefined if the path ends with a slash already. If + called unconditionally on a URL, it may produce a redirect loop. + + :param environ: Use the path and query from this WSGI environment + to produce the redirect URL. + :param code: the status code for the redirect. + + .. versionchanged:: 2.1 + Produce a relative URL that only modifies the last segment. + Relevant when the current path has multiple segments. + + .. versionchanged:: 2.1 + The default status code is 308 instead of 301. This preserves + the request method and body. + """ + tail = environ["PATH_INFO"].rpartition("/")[2] + + if not tail: + new_path = "./" + else: + new_path = f"{tail}/" + + query_string = environ.get("QUERY_STRING") + + if query_string: + new_path = f"{new_path}?{query_string}" + + return redirect(new_path, code) + + +def send_file( + path_or_file: os.PathLike[str] | str | t.IO[bytes], + environ: WSGIEnvironment, + mimetype: str | None = None, + as_attachment: bool = False, + download_name: str | None = None, + conditional: bool = True, + etag: bool | str = True, + last_modified: datetime | int | float | None = None, + max_age: None | (int | t.Callable[[str | None], int | None]) = None, + use_x_sendfile: bool = False, + response_class: type[Response] | None = None, + _root_path: os.PathLike[str] | str | None = None, +) -> Response: + """Send the contents of a file to the client. + + The first argument can be a file path or a file-like object. Paths + are preferred in most cases because Werkzeug can manage the file and + get extra information from the path. Passing a file-like object + requires that the file is opened in binary mode, and is mostly + useful when building a file in memory with :class:`io.BytesIO`. + + Never pass file paths provided by a user. The path is assumed to be + trusted, so a user could craft a path to access a file you didn't + intend. Use :func:`send_from_directory` to safely serve user-provided paths. + + If the WSGI server sets a ``file_wrapper`` in ``environ``, it is + used, otherwise Werkzeug's built-in wrapper is used. Alternatively, + if the HTTP server supports ``X-Sendfile``, ``use_x_sendfile=True`` + will tell the server to send the given path, which is much more + efficient than reading it in Python. + + :param path_or_file: The path to the file to send, relative to the + current working directory if a relative path is given. + Alternatively, a file-like object opened in binary mode. Make + sure the file pointer is seeked to the start of the data. + :param environ: The WSGI environ for the current request. + :param mimetype: The MIME type to send for the file. If not + provided, it will try to detect it from the file name. + :param as_attachment: Indicate to a browser that it should offer to + save the file instead of displaying it. + :param download_name: The default name browsers will use when saving + the file. Defaults to the passed file name. + :param conditional: Enable conditional and range responses based on + request headers. Requires passing a file path and ``environ``. + :param etag: Calculate an ETag for the file, which requires passing + a file path. Can also be a string to use instead. + :param last_modified: The last modified time to send for the file, + in seconds. If not provided, it will try to detect it from the + file path. + :param max_age: How long the client should cache the file, in + seconds. If set, ``Cache-Control`` will be ``public``, otherwise + it will be ``no-cache`` to prefer conditional caching. + :param use_x_sendfile: Set the ``X-Sendfile`` header to let the + server to efficiently send the file. Requires support from the + HTTP server. Requires passing a file path. + :param response_class: Build the response using this class. Defaults + to :class:`~werkzeug.wrappers.Response`. + :param _root_path: Do not use. For internal use only. Use + :func:`send_from_directory` to safely send files under a path. + + .. versionchanged:: 2.0.2 + ``send_file`` only sets a detected ``Content-Encoding`` if + ``as_attachment`` is disabled. + + .. versionadded:: 2.0 + Adapted from Flask's implementation. + + .. versionchanged:: 2.0 + ``download_name`` replaces Flask's ``attachment_filename`` + parameter. If ``as_attachment=False``, it is passed with + ``Content-Disposition: inline`` instead. + + .. versionchanged:: 2.0 + ``max_age`` replaces Flask's ``cache_timeout`` parameter. + ``conditional`` is enabled and ``max_age`` is not set by + default. + + .. versionchanged:: 2.0 + ``etag`` replaces Flask's ``add_etags`` parameter. It can be a + string to use instead of generating one. + + .. versionchanged:: 2.0 + If an encoding is returned when guessing ``mimetype`` from + ``download_name``, set the ``Content-Encoding`` header. + """ + if response_class is None: + from .wrappers import Response + + response_class = Response + + path: str | None = None + file: t.IO[bytes] | None = None + size: int | None = None + mtime: float | None = None + headers = Headers() + + if isinstance(path_or_file, (os.PathLike, str)) or hasattr( + path_or_file, "__fspath__" + ): + path_or_file = t.cast("t.Union[os.PathLike[str], str]", path_or_file) + + # Flask will pass app.root_path, allowing its send_file wrapper + # to not have to deal with paths. + if _root_path is not None: + path = os.path.join(_root_path, path_or_file) + else: + path = os.path.abspath(path_or_file) + + stat = os.stat(path) + size = stat.st_size + mtime = stat.st_mtime + else: + file = path_or_file + + if download_name is None and path is not None: + download_name = os.path.basename(path) + + if mimetype is None: + if download_name is None: + raise TypeError( + "Unable to detect the MIME type because a file name is" + " not available. Either set 'download_name', pass a" + " path instead of a file, or set 'mimetype'." + ) + + mimetype, encoding = mimetypes.guess_type(download_name) + + if mimetype is None: + mimetype = "application/octet-stream" + + # Don't send encoding for attachments, it causes browsers to + # save decompress tar.gz files. + if encoding is not None and not as_attachment: + headers.set("Content-Encoding", encoding) + + if download_name is not None: + try: + download_name.encode("ascii") + except UnicodeEncodeError: + simple = unicodedata.normalize("NFKD", download_name) + simple = simple.encode("ascii", "ignore").decode("ascii") + # safe = RFC 5987 attr-char + quoted = quote(download_name, safe="!#$&+-.^_`|~") + names = {"filename": simple, "filename*": f"UTF-8''{quoted}"} + else: + names = {"filename": download_name} + + value = "attachment" if as_attachment else "inline" + headers.set("Content-Disposition", value, **names) + elif as_attachment: + raise TypeError( + "No name provided for attachment. Either set" + " 'download_name' or pass a path instead of a file." + ) + + if use_x_sendfile and path is not None: + headers["X-Sendfile"] = path + data = None + else: + if file is None: + file = open(path, "rb") # type: ignore + elif isinstance(file, io.BytesIO): + size = file.getbuffer().nbytes + elif isinstance(file, io.TextIOBase): + raise ValueError("Files must be opened in binary mode or use BytesIO.") + + data = wrap_file(environ, file) + + rv = response_class( + data, mimetype=mimetype, headers=headers, direct_passthrough=True + ) + + if size is not None: + rv.content_length = size + + if last_modified is not None: + rv.last_modified = last_modified # type: ignore + elif mtime is not None: + rv.last_modified = mtime # type: ignore + + rv.cache_control.no_cache = True + + # Flask will pass app.get_send_file_max_age, allowing its send_file + # wrapper to not have to deal with paths. + if callable(max_age): + max_age = max_age(path) + + if max_age is not None: + if max_age > 0: + rv.cache_control.no_cache = None + rv.cache_control.public = True + + rv.cache_control.max_age = max_age + rv.expires = int(time() + max_age) # type: ignore + + if isinstance(etag, str): + rv.set_etag(etag) + elif etag and path is not None: + check = adler32(path.encode()) & 0xFFFFFFFF + rv.set_etag(f"{mtime}-{size}-{check}") + + if conditional: + try: + rv = rv.make_conditional(environ, accept_ranges=True, complete_length=size) + except RequestedRangeNotSatisfiable: + if file is not None: + file.close() + + raise + + # Some x-sendfile implementations incorrectly ignore the 304 + # status code and send the file anyway. + if rv.status_code == 304: + rv.headers.pop("x-sendfile", None) + + return rv + + +def send_from_directory( + directory: os.PathLike[str] | str, + path: os.PathLike[str] | str, + environ: WSGIEnvironment, + **kwargs: t.Any, +) -> Response: + """Send a file from within a directory using :func:`send_file`. + + This is a secure way to serve files from a folder, such as static + files or uploads. Uses :func:`~werkzeug.security.safe_join` to + ensure the path coming from the client is not maliciously crafted to + point outside the specified directory. + + If the final path does not point to an existing regular file, + returns a 404 :exc:`~werkzeug.exceptions.NotFound` error. + + :param directory: The directory that ``path`` must be located under. This *must not* + be a value provided by the client, otherwise it becomes insecure. + :param path: The path to the file to send, relative to ``directory``. This is the + part of the path provided by the client, which is checked for security. + :param environ: The WSGI environ for the current request. + :param kwargs: Arguments to pass to :func:`send_file`. + + .. versionadded:: 2.0 + Adapted from Flask's implementation. + """ + path_str = safe_join(os.fspath(directory), os.fspath(path)) + + if path_str is None: + raise NotFound() + + # Flask will pass app.root_path, allowing its send_from_directory + # wrapper to not have to deal with paths. + if "_root_path" in kwargs: + path_str = os.path.join(kwargs["_root_path"], path_str) + + if not os.path.isfile(path_str): + raise NotFound() + + return send_file(path_str, environ, **kwargs) + + +def import_string(import_name: str, silent: bool = False) -> t.Any: + """Imports an object based on a string. This is useful if you want to + use import paths as endpoints or something similar. An import path can + be specified either in dotted notation (``xml.sax.saxutils.escape``) + or with a colon as object delimiter (``xml.sax.saxutils:escape``). + + If `silent` is True the return value will be `None` if the import fails. + + :param import_name: the dotted name for the object to import. + :param silent: if set to `True` import errors are ignored and + `None` is returned instead. + :return: imported object + """ + import_name = import_name.replace(":", ".") + try: + try: + __import__(import_name) + except ImportError: + if "." not in import_name: + raise + else: + return sys.modules[import_name] + + module_name, obj_name = import_name.rsplit(".", 1) + module = __import__(module_name, globals(), locals(), [obj_name]) + try: + return getattr(module, obj_name) + except AttributeError as e: + raise ImportError(e) from None + + except ImportError as e: + if not silent: + raise ImportStringError(import_name, e).with_traceback( + sys.exc_info()[2] + ) from None + + return None + + +def find_modules( + import_path: str, include_packages: bool = False, recursive: bool = False +) -> t.Iterator[str]: + """Finds all the modules below a package. This can be useful to + automatically import all views / controllers so that their metaclasses / + function decorators have a chance to register themselves on the + application. + + Packages are not returned unless `include_packages` is `True`. This can + also recursively list modules but in that case it will import all the + packages to get the correct load path of that module. + + :param import_path: the dotted name for the package to find child modules. + :param include_packages: set to `True` if packages should be returned, too. + :param recursive: set to `True` if recursion should happen. + :return: generator + """ + module = import_string(import_path) + path = getattr(module, "__path__", None) + if path is None: + raise ValueError(f"{import_path!r} is not a package") + basename = f"{module.__name__}." + for _importer, modname, ispkg in pkgutil.iter_modules(path): + modname = basename + modname + if ispkg: + if include_packages: + yield modname + if recursive: + yield from find_modules(modname, include_packages, True) + else: + yield modname + + +class ImportStringError(ImportError): + """Provides information about a failed :func:`import_string` attempt.""" + + #: String in dotted notation that failed to be imported. + import_name: str + #: Wrapped exception. + exception: BaseException + + def __init__(self, import_name: str, exception: BaseException) -> None: + self.import_name = import_name + self.exception = exception + msg = import_name + name = "" + tracked = [] + for part in import_name.replace(":", ".").split("."): + name = f"{name}.{part}" if name else part + imported = import_string(name, silent=True) + if imported: + tracked.append((name, getattr(imported, "__file__", None))) + else: + track = [f"- {n!r} found in {i!r}." for n, i in tracked] + track.append(f"- {name!r} not found.") + track_str = "\n".join(track) + msg = ( + f"import_string() failed for {import_name!r}. Possible reasons" + f" are:\n\n" + "- missing __init__.py in a package;\n" + "- package or module path not included in sys.path;\n" + "- duplicated package or module name taking precedence in" + " sys.path;\n" + "- missing module, class, function or variable;\n\n" + f"Debugged import:\n\n{track_str}\n\n" + f"Original exception:\n\n{type(exception).__name__}: {exception}" + ) + break + + super().__init__(msg) + + def __repr__(self) -> str: + return f"<{type(self).__name__}({self.import_name!r}, {self.exception!r})>" diff --git a/venv/Lib/site-packages/werkzeug/wrappers/__init__.py b/venv/Lib/site-packages/werkzeug/wrappers/__init__.py new file mode 100644 index 00000000..b36f228f --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/wrappers/__init__.py @@ -0,0 +1,3 @@ +from .request import Request as Request +from .response import Response as Response +from .response import ResponseStream as ResponseStream diff --git a/venv/Lib/site-packages/werkzeug/wrappers/__pycache__/__init__.cpython-312.pyc b/venv/Lib/site-packages/werkzeug/wrappers/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2dd6948b9cc18806a610dac9a48619a7347b86a0 GIT binary patch literal 354 zcmYL^&q~8U5XN`&heoA+4UG_c^B~fTR}rar34yR_#&+o@S$8%K^#uf9z-RDTe1kx} zc=9InAoS!!Tb;wVzxnoCcILI;-)B@`qwBjTI^VlySI!pJM+DC-XE|4_;4$YNJIX2C z*!^Lb*o!^RmR{(8aEgb#?Tz8S2DF617*JOw0`XwSH`ahCu0p@17+3ZP<9! z8lU8atx4JD@!~AGMKGA?8bKwtYD%0*onj$Wn$;*vK>V|#$xPU+vj0YxQ_(IMb$1IW z7s;i}5|kDqEhcj@1xy++^9QJ>!^Q}$>C0i7%2L`iJ<-cea9&h-tzcvhXs&Ky9FTmv RobwesT(RRfc53#>`Uc_ZX{Z1I literal 0 HcmV?d00001 diff --git a/venv/Lib/site-packages/werkzeug/wrappers/__pycache__/request.cpython-312.pyc b/venv/Lib/site-packages/werkzeug/wrappers/__pycache__/request.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..55f671a270b1986936fb2f16cecc80320fffc288 GIT binary patch literal 26179 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�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%0DH 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 None: + super().__init__( + method=environ.get("REQUEST_METHOD", "GET"), + scheme=environ.get("wsgi.url_scheme", "http"), + server=_get_server(environ), + root_path=_wsgi_decoding_dance(environ.get("SCRIPT_NAME") or ""), + path=_wsgi_decoding_dance(environ.get("PATH_INFO") or ""), + query_string=environ.get("QUERY_STRING", "").encode("latin1"), + headers=EnvironHeaders(environ), + remote_addr=environ.get("REMOTE_ADDR"), + ) + self.environ = environ + self.shallow = shallow + + if populate_request and not shallow: + self.environ["werkzeug.request"] = self + + @classmethod + def from_values(cls, *args: t.Any, **kwargs: t.Any) -> Request: + """Create a new request object based on the values provided. If + environ is given missing values are filled from there. This method is + useful for small scripts when you need to simulate a request from an URL. + Do not use this method for unittesting, there is a full featured client + object (:class:`Client`) that allows to create multipart requests, + support for cookies etc. + + This accepts the same options as the + :class:`~werkzeug.test.EnvironBuilder`. + + .. versionchanged:: 0.5 + This method now accepts the same arguments as + :class:`~werkzeug.test.EnvironBuilder`. Because of this the + `environ` parameter is now called `environ_overrides`. + + :return: request object + """ + from ..test import EnvironBuilder + + builder = EnvironBuilder(*args, **kwargs) + try: + return builder.get_request(cls) + finally: + builder.close() + + @classmethod + def application(cls, f: t.Callable[[Request], WSGIApplication]) -> WSGIApplication: + """Decorate a function as responder that accepts the request as + the last argument. This works like the :func:`responder` + decorator but the function is passed the request object as the + last argument and the request object will be closed + automatically:: + + @Request.application + def my_wsgi_app(request): + return Response('Hello World!') + + As of Werkzeug 0.14 HTTP exceptions are automatically caught and + converted to responses instead of failing. + + :param f: the WSGI callable to decorate + :return: a new WSGI callable + """ + #: return a callable that wraps the -2nd argument with the request + #: and calls the function with all the arguments up to that one and + #: the request. The return value is then called with the latest + #: two arguments. This makes it possible to use this decorator for + #: both standalone WSGI functions as well as bound methods and + #: partially applied functions. + from ..exceptions import HTTPException + + @functools.wraps(f) + def application(*args: t.Any) -> cabc.Iterable[bytes]: + request = cls(args[-2]) + with request: + try: + resp = f(*args[:-2] + (request,)) + except HTTPException as e: + resp = t.cast("WSGIApplication", e.get_response(args[-2])) + return resp(*args[-2:]) + + return t.cast("WSGIApplication", application) + + def _get_file_stream( + self, + total_content_length: int | None, + content_type: str | None, + filename: str | None = None, + content_length: int | None = None, + ) -> t.IO[bytes]: + """Called to get a stream for the file upload. + + This must provide a file-like class with `read()`, `readline()` + and `seek()` methods that is both writeable and readable. + + The default implementation returns a temporary file if the total + content length is higher than 500KB. Because many browsers do not + provide a content length for the files only the total content + length matters. + + :param total_content_length: the total content length of all the + data in the request combined. This value + is guaranteed to be there. + :param content_type: the mimetype of the uploaded file. + :param filename: the filename of the uploaded file. May be `None`. + :param content_length: the length of this file. This value is usually + not provided because webbrowsers do not provide + this value. + """ + return default_stream_factory( + total_content_length=total_content_length, + filename=filename, + content_type=content_type, + content_length=content_length, + ) + + @property + def want_form_data_parsed(self) -> bool: + """``True`` if the request method carries content. By default + this is true if a ``Content-Type`` is sent. + + .. versionadded:: 0.8 + """ + return bool(self.environ.get("CONTENT_TYPE")) + + def make_form_data_parser(self) -> FormDataParser: + """Creates the form data parser. Instantiates the + :attr:`form_data_parser_class` with some parameters. + + .. versionadded:: 0.8 + """ + return self.form_data_parser_class( + stream_factory=self._get_file_stream, + max_form_memory_size=self.max_form_memory_size, + max_content_length=self.max_content_length, + max_form_parts=self.max_form_parts, + cls=self.parameter_storage_class, + ) + + def _load_form_data(self) -> None: + """Method used internally to retrieve submitted data. After calling + this sets `form` and `files` on the request object to multi dicts + filled with the incoming form data. As a matter of fact the input + stream will be empty afterwards. You can also call this method to + force the parsing of the form data. + + .. versionadded:: 0.8 + """ + # abort early if we have already consumed the stream + if "form" in self.__dict__: + return + + if self.want_form_data_parsed: + parser = self.make_form_data_parser() + data = parser.parse( + self._get_stream_for_parsing(), + self.mimetype, + self.content_length, + self.mimetype_params, + ) + else: + data = ( + self.stream, + self.parameter_storage_class(), + self.parameter_storage_class(), + ) + + # inject the values into the instance dict so that we bypass + # our cached_property non-data descriptor. + d = self.__dict__ + d["stream"], d["form"], d["files"] = data + + def _get_stream_for_parsing(self) -> t.IO[bytes]: + """This is the same as accessing :attr:`stream` with the difference + that if it finds cached data from calling :meth:`get_data` first it + will create a new stream out of the cached data. + + .. versionadded:: 0.9.3 + """ + cached_data = getattr(self, "_cached_data", None) + if cached_data is not None: + return BytesIO(cached_data) + return self.stream + + def close(self) -> None: + """Closes associated resources of this request object. This + closes all file handles explicitly. You can also use the request + object in a with statement which will automatically close it. + + .. versionadded:: 0.9 + """ + files = self.__dict__.get("files") + for _key, value in iter_multi_items(files or ()): + value.close() + + def __enter__(self) -> Request: + return self + + def __exit__(self, exc_type, exc_value, tb) -> None: # type: ignore + self.close() + + @cached_property + def stream(self) -> t.IO[bytes]: + """The WSGI input stream, with safety checks. This stream can only be consumed + once. + + Use :meth:`get_data` to get the full data as bytes or text. The :attr:`data` + attribute will contain the full bytes only if they do not represent form data. + The :attr:`form` attribute will contain the parsed form data in that case. + + Unlike :attr:`input_stream`, this stream guards against infinite streams or + reading past :attr:`content_length` or :attr:`max_content_length`. + + If ``max_content_length`` is set, it can be enforced on streams if + ``wsgi.input_terminated`` is set. Otherwise, an empty stream is returned. + + If the limit is reached before the underlying stream is exhausted (such as a + file that is too large, or an infinite stream), the remaining contents of the + stream cannot be read safely. Depending on how the server handles this, clients + may show a "connection reset" failure instead of seeing the 413 response. + + .. versionchanged:: 2.3 + Check ``max_content_length`` preemptively and while reading. + + .. versionchanged:: 0.9 + The stream is always set (but may be consumed) even if form parsing was + accessed first. + """ + if self.shallow: + raise RuntimeError( + "This request was created with 'shallow=True', reading" + " from the input stream is disabled." + ) + + return get_input_stream( + self.environ, max_content_length=self.max_content_length + ) + + input_stream = environ_property[t.IO[bytes]]( + "wsgi.input", + doc="""The raw WSGI input stream, without any safety checks. + + This is dangerous to use. It does not guard against infinite streams or reading + past :attr:`content_length` or :attr:`max_content_length`. + + Use :attr:`stream` instead. + """, + ) + + @cached_property + def data(self) -> bytes: + """The raw data read from :attr:`stream`. Will be empty if the request + represents form data. + + To get the raw data even if it represents form data, use :meth:`get_data`. + """ + return self.get_data(parse_form_data=True) + + @t.overload + def get_data( # type: ignore + self, + cache: bool = True, + as_text: t.Literal[False] = False, + parse_form_data: bool = False, + ) -> bytes: ... + + @t.overload + def get_data( + self, + cache: bool = True, + as_text: t.Literal[True] = ..., + parse_form_data: bool = False, + ) -> str: ... + + def get_data( + self, cache: bool = True, as_text: bool = False, parse_form_data: bool = False + ) -> bytes | str: + """This reads the buffered incoming data from the client into one + bytes object. By default this is cached but that behavior can be + changed by setting `cache` to `False`. + + Usually it's a bad idea to call this method without checking the + content length first as a client could send dozens of megabytes or more + to cause memory problems on the server. + + Note that if the form data was already parsed this method will not + return anything as form data parsing does not cache the data like + this method does. To implicitly invoke form data parsing function + set `parse_form_data` to `True`. When this is done the return value + of this method will be an empty string if the form parser handles + the data. This generally is not necessary as if the whole data is + cached (which is the default) the form parser will used the cached + data to parse the form data. Please be generally aware of checking + the content length first in any case before calling this method + to avoid exhausting server memory. + + If `as_text` is set to `True` the return value will be a decoded + string. + + .. versionadded:: 0.9 + """ + rv = getattr(self, "_cached_data", None) + if rv is None: + if parse_form_data: + self._load_form_data() + rv = self.stream.read() + if cache: + self._cached_data = rv + if as_text: + rv = rv.decode(errors="replace") + return rv + + @cached_property + def form(self) -> ImmutableMultiDict[str, str]: + """The form parameters. By default an + :class:`~werkzeug.datastructures.ImmutableMultiDict` + is returned from this function. This can be changed by setting + :attr:`parameter_storage_class` to a different type. This might + be necessary if the order of the form data is important. + + Please keep in mind that file uploads will not end up here, but instead + in the :attr:`files` attribute. + + .. versionchanged:: 0.9 + + Previous to Werkzeug 0.9 this would only contain form data for POST + and PUT requests. + """ + self._load_form_data() + return self.form + + @cached_property + def values(self) -> CombinedMultiDict[str, str]: + """A :class:`werkzeug.datastructures.CombinedMultiDict` that + combines :attr:`args` and :attr:`form`. + + For GET requests, only ``args`` are present, not ``form``. + + .. versionchanged:: 2.0 + For GET requests, only ``args`` are present, not ``form``. + """ + sources = [self.args] + + if self.method != "GET": + # GET requests can have a body, and some caching proxies + # might not treat that differently than a normal GET + # request, allowing form data to "invisibly" affect the + # cache without indication in the query string / URL. + sources.append(self.form) + + args = [] + + for d in sources: + if not isinstance(d, MultiDict): + d = MultiDict(d) + + args.append(d) + + return CombinedMultiDict(args) + + @cached_property + def files(self) -> ImmutableMultiDict[str, FileStorage]: + """:class:`~werkzeug.datastructures.MultiDict` object containing + all uploaded files. Each key in :attr:`files` is the name from the + ````. Each value in :attr:`files` is a + Werkzeug :class:`~werkzeug.datastructures.FileStorage` object. + + It basically behaves like a standard file object you know from Python, + with the difference that it also has a + :meth:`~werkzeug.datastructures.FileStorage.save` function that can + store the file on the filesystem. + + Note that :attr:`files` will only contain data if the request method was + POST, PUT or PATCH and the ``

    `` that posted to the request had + ``enctype="multipart/form-data"``. It will be empty otherwise. + + See the :class:`~werkzeug.datastructures.MultiDict` / + :class:`~werkzeug.datastructures.FileStorage` documentation for + more details about the used data structure. + """ + self._load_form_data() + return self.files + + @property + def script_root(self) -> str: + """Alias for :attr:`self.root_path`. ``environ["SCRIPT_ROOT"]`` + without a trailing slash. + """ + return self.root_path + + @cached_property + def url_root(self) -> str: + """Alias for :attr:`root_url`. The URL with scheme, host, and + root path. For example, ``https://example.com/app/``. + """ + return self.root_url + + remote_user = environ_property[str]( + "REMOTE_USER", + doc="""If the server supports user authentication, and the + script is protected, this attribute contains the username the + user has authenticated as.""", + ) + is_multithread = environ_property[bool]( + "wsgi.multithread", + doc="""boolean that is `True` if the application is served by a + multithreaded WSGI server.""", + ) + is_multiprocess = environ_property[bool]( + "wsgi.multiprocess", + doc="""boolean that is `True` if the application is served by a + WSGI server that spawns multiple processes.""", + ) + is_run_once = environ_property[bool]( + "wsgi.run_once", + doc="""boolean that is `True` if the application will be + executed only once in a process lifetime. This is the case for + CGI for example, but it's not guaranteed that the execution only + happens one time.""", + ) + + # JSON + + #: A module or other object that has ``dumps`` and ``loads`` + #: functions that match the API of the built-in :mod:`json` module. + json_module = json + + @property + def json(self) -> t.Any | None: + """The parsed JSON data if :attr:`mimetype` indicates JSON + (:mimetype:`application/json`, see :attr:`is_json`). + + Calls :meth:`get_json` with default arguments. + + If the request content type is not ``application/json``, this + will raise a 415 Unsupported Media Type error. + + .. versionchanged:: 2.3 + Raise a 415 error instead of 400. + + .. versionchanged:: 2.1 + Raise a 400 error if the content type is incorrect. + """ + return self.get_json() + + # Cached values for ``(silent=False, silent=True)``. Initialized + # with sentinel values. + _cached_json: tuple[t.Any, t.Any] = (Ellipsis, Ellipsis) + + @t.overload + def get_json( + self, force: bool = ..., silent: t.Literal[False] = ..., cache: bool = ... + ) -> t.Any: ... + + @t.overload + def get_json( + self, force: bool = ..., silent: bool = ..., cache: bool = ... + ) -> t.Any | None: ... + + def get_json( + self, force: bool = False, silent: bool = False, cache: bool = True + ) -> t.Any | None: + """Parse :attr:`data` as JSON. + + If the mimetype does not indicate JSON + (:mimetype:`application/json`, see :attr:`is_json`), or parsing + fails, :meth:`on_json_loading_failed` is called and + its return value is used as the return value. By default this + raises a 415 Unsupported Media Type resp. + + :param force: Ignore the mimetype and always try to parse JSON. + :param silent: Silence mimetype and parsing errors, and + return ``None`` instead. + :param cache: Store the parsed JSON to return for subsequent + calls. + + .. versionchanged:: 2.3 + Raise a 415 error instead of 400. + + .. versionchanged:: 2.1 + Raise a 400 error if the content type is incorrect. + """ + if cache and self._cached_json[silent] is not Ellipsis: + return self._cached_json[silent] + + if not (force or self.is_json): + if not silent: + return self.on_json_loading_failed(None) + else: + return None + + data = self.get_data(cache=cache) + + try: + rv = self.json_module.loads(data) + except ValueError as e: + if silent: + rv = None + + if cache: + normal_rv, _ = self._cached_json + self._cached_json = (normal_rv, rv) + else: + rv = self.on_json_loading_failed(e) + + if cache: + _, silent_rv = self._cached_json + self._cached_json = (rv, silent_rv) + else: + if cache: + self._cached_json = (rv, rv) + + return rv + + def on_json_loading_failed(self, e: ValueError | None) -> t.Any: + """Called if :meth:`get_json` fails and isn't silenced. + + If this method returns a value, it is used as the return value + for :meth:`get_json`. The default implementation raises + :exc:`~werkzeug.exceptions.BadRequest`. + + :param e: If parsing failed, this is the exception. It will be + ``None`` if the content type wasn't ``application/json``. + + .. versionchanged:: 2.3 + Raise a 415 error instead of 400. + """ + if e is not None: + raise BadRequest(f"Failed to decode JSON object: {e}") + + raise UnsupportedMediaType( + "Did not attempt to load JSON data because the request" + " Content-Type was not 'application/json'." + ) diff --git a/venv/Lib/site-packages/werkzeug/wrappers/response.py b/venv/Lib/site-packages/werkzeug/wrappers/response.py new file mode 100644 index 00000000..7f01287c --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/wrappers/response.py @@ -0,0 +1,831 @@ +from __future__ import annotations + +import json +import typing as t +from http import HTTPStatus +from urllib.parse import urljoin + +from .._internal import _get_environ +from ..datastructures import Headers +from ..http import generate_etag +from ..http import http_date +from ..http import is_resource_modified +from ..http import parse_etags +from ..http import parse_range_header +from ..http import remove_entity_headers +from ..sansio.response import Response as _SansIOResponse +from ..urls import iri_to_uri +from ..utils import cached_property +from ..wsgi import _RangeWrapper +from ..wsgi import ClosingIterator +from ..wsgi import get_current_url + +if t.TYPE_CHECKING: + from _typeshed.wsgi import StartResponse + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + + from .request import Request + + +def _iter_encoded(iterable: t.Iterable[str | bytes]) -> t.Iterator[bytes]: + for item in iterable: + if isinstance(item, str): + yield item.encode() + else: + yield item + + +class Response(_SansIOResponse): + """Represents an outgoing WSGI HTTP response with body, status, and + headers. Has properties and methods for using the functionality + defined by various HTTP specs. + + The response body is flexible to support different use cases. The + simple form is passing bytes, or a string which will be encoded as + UTF-8. Passing an iterable of bytes or strings makes this a + streaming response. A generator is particularly useful for building + a CSV file in memory or using SSE (Server Sent Events). A file-like + object is also iterable, although the + :func:`~werkzeug.utils.send_file` helper should be used in that + case. + + The response object is itself a WSGI application callable. When + called (:meth:`__call__`) with ``environ`` and ``start_response``, + it will pass its status and headers to ``start_response`` then + return its body as an iterable. + + .. code-block:: python + + from werkzeug.wrappers.response import Response + + def index(): + return Response("Hello, World!") + + def application(environ, start_response): + path = environ.get("PATH_INFO") or "/" + + if path == "/": + response = index() + else: + response = Response("Not Found", status=404) + + return response(environ, start_response) + + :param response: The data for the body of the response. A string or + bytes, or tuple or list of strings or bytes, for a fixed-length + response, or any other iterable of strings or bytes for a + streaming response. Defaults to an empty body. + :param status: The status code for the response. Either an int, in + which case the default status message is added, or a string in + the form ``{code} {message}``, like ``404 Not Found``. Defaults + to 200. + :param headers: A :class:`~werkzeug.datastructures.Headers` object, + or a list of ``(key, value)`` tuples that will be converted to a + ``Headers`` object. + :param mimetype: The mime type (content type without charset or + other parameters) of the response. If the value starts with + ``text/`` (or matches some other special cases), the charset + will be added to create the ``content_type``. + :param content_type: The full content type of the response. + Overrides building the value from ``mimetype``. + :param direct_passthrough: Pass the response body directly through + as the WSGI iterable. This can be used when the body is a binary + file or other iterator of bytes, to skip some unnecessary + checks. Use :func:`~werkzeug.utils.send_file` instead of setting + this manually. + + .. versionchanged:: 2.1 + Old ``BaseResponse`` and mixin classes were removed. + + .. versionchanged:: 2.0 + Combine ``BaseResponse`` and mixins into a single ``Response`` + class. + + .. versionchanged:: 0.5 + The ``direct_passthrough`` parameter was added. + """ + + #: if set to `False` accessing properties on the response object will + #: not try to consume the response iterator and convert it into a list. + #: + #: .. versionadded:: 0.6.2 + #: + #: That attribute was previously called `implicit_seqence_conversion`. + #: (Notice the typo). If you did use this feature, you have to adapt + #: your code to the name change. + implicit_sequence_conversion = True + + #: If a redirect ``Location`` header is a relative URL, make it an + #: absolute URL, including scheme and domain. + #: + #: .. versionchanged:: 2.1 + #: This is disabled by default, so responses will send relative + #: redirects. + #: + #: .. versionadded:: 0.8 + autocorrect_location_header = False + + #: Should this response object automatically set the content-length + #: header if possible? This is true by default. + #: + #: .. versionadded:: 0.8 + automatically_set_content_length = True + + #: The response body to send as the WSGI iterable. A list of strings + #: or bytes represents a fixed-length response, any other iterable + #: is a streaming response. Strings are encoded to bytes as UTF-8. + #: + #: Do not set to a plain string or bytes, that will cause sending + #: the response to be very inefficient as it will iterate one byte + #: at a time. + response: t.Iterable[str] | t.Iterable[bytes] + + def __init__( + self, + response: t.Iterable[bytes] | bytes | t.Iterable[str] | str | None = None, + status: int | str | HTTPStatus | None = None, + headers: t.Mapping[str, str | t.Iterable[str]] + | t.Iterable[tuple[str, str]] + | None = None, + mimetype: str | None = None, + content_type: str | None = None, + direct_passthrough: bool = False, + ) -> None: + super().__init__( + status=status, + headers=headers, + mimetype=mimetype, + content_type=content_type, + ) + + #: Pass the response body directly through as the WSGI iterable. + #: This can be used when the body is a binary file or other + #: iterator of bytes, to skip some unnecessary checks. Use + #: :func:`~werkzeug.utils.send_file` instead of setting this + #: manually. + self.direct_passthrough = direct_passthrough + self._on_close: list[t.Callable[[], t.Any]] = [] + + # we set the response after the headers so that if a class changes + # the charset attribute, the data is set in the correct charset. + if response is None: + self.response = [] + elif isinstance(response, (str, bytes, bytearray)): + self.set_data(response) + else: + self.response = response + + def call_on_close(self, func: t.Callable[[], t.Any]) -> t.Callable[[], t.Any]: + """Adds a function to the internal list of functions that should + be called as part of closing down the response. Since 0.7 this + function also returns the function that was passed so that this + can be used as a decorator. + + .. versionadded:: 0.6 + """ + self._on_close.append(func) + return func + + def __repr__(self) -> str: + if self.is_sequence: + body_info = f"{sum(map(len, self.iter_encoded()))} bytes" + else: + body_info = "streamed" if self.is_streamed else "likely-streamed" + return f"<{type(self).__name__} {body_info} [{self.status}]>" + + @classmethod + def force_type( + cls, response: Response, environ: WSGIEnvironment | None = None + ) -> Response: + """Enforce that the WSGI response is a response object of the current + type. Werkzeug will use the :class:`Response` internally in many + situations like the exceptions. If you call :meth:`get_response` on an + exception you will get back a regular :class:`Response` object, even + if you are using a custom subclass. + + This method can enforce a given response type, and it will also + convert arbitrary WSGI callables into response objects if an environ + is provided:: + + # convert a Werkzeug response object into an instance of the + # MyResponseClass subclass. + response = MyResponseClass.force_type(response) + + # convert any WSGI application into a response object + response = MyResponseClass.force_type(response, environ) + + This is especially useful if you want to post-process responses in + the main dispatcher and use functionality provided by your subclass. + + Keep in mind that this will modify response objects in place if + possible! + + :param response: a response object or wsgi application. + :param environ: a WSGI environment object. + :return: a response object. + """ + if not isinstance(response, Response): + if environ is None: + raise TypeError( + "cannot convert WSGI application into response" + " objects without an environ" + ) + + from ..test import run_wsgi_app + + response = Response(*run_wsgi_app(response, environ)) + + response.__class__ = cls + return response + + @classmethod + def from_app( + cls, app: WSGIApplication, environ: WSGIEnvironment, buffered: bool = False + ) -> Response: + """Create a new response object from an application output. This + works best if you pass it an application that returns a generator all + the time. Sometimes applications may use the `write()` callable + returned by the `start_response` function. This tries to resolve such + edge cases automatically. But if you don't get the expected output + you should set `buffered` to `True` which enforces buffering. + + :param app: the WSGI application to execute. + :param environ: the WSGI environment to execute against. + :param buffered: set to `True` to enforce buffering. + :return: a response object. + """ + from ..test import run_wsgi_app + + return cls(*run_wsgi_app(app, environ, buffered)) + + @t.overload + def get_data(self, as_text: t.Literal[False] = False) -> bytes: ... + + @t.overload + def get_data(self, as_text: t.Literal[True]) -> str: ... + + def get_data(self, as_text: bool = False) -> bytes | str: + """The string representation of the response body. Whenever you call + this property the response iterable is encoded and flattened. This + can lead to unwanted behavior if you stream big data. + + This behavior can be disabled by setting + :attr:`implicit_sequence_conversion` to `False`. + + If `as_text` is set to `True` the return value will be a decoded + string. + + .. versionadded:: 0.9 + """ + self._ensure_sequence() + rv = b"".join(self.iter_encoded()) + + if as_text: + return rv.decode() + + return rv + + def set_data(self, value: bytes | str) -> None: + """Sets a new string as response. The value must be a string or + bytes. If a string is set it's encoded to the charset of the + response (utf-8 by default). + + .. versionadded:: 0.9 + """ + if isinstance(value, str): + value = value.encode() + self.response = [value] + if self.automatically_set_content_length: + self.headers["Content-Length"] = str(len(value)) + + data = property( + get_data, + set_data, + doc="A descriptor that calls :meth:`get_data` and :meth:`set_data`.", + ) + + def calculate_content_length(self) -> int | None: + """Returns the content length if available or `None` otherwise.""" + try: + self._ensure_sequence() + except RuntimeError: + return None + return sum(len(x) for x in self.iter_encoded()) + + def _ensure_sequence(self, mutable: bool = False) -> None: + """This method can be called by methods that need a sequence. If + `mutable` is true, it will also ensure that the response sequence + is a standard Python list. + + .. versionadded:: 0.6 + """ + if self.is_sequence: + # if we need a mutable object, we ensure it's a list. + if mutable and not isinstance(self.response, list): + self.response = list(self.response) # type: ignore + return + if self.direct_passthrough: + raise RuntimeError( + "Attempted implicit sequence conversion but the" + " response object is in direct passthrough mode." + ) + if not self.implicit_sequence_conversion: + raise RuntimeError( + "The response object required the iterable to be a" + " sequence, but the implicit conversion was disabled." + " Call make_sequence() yourself." + ) + self.make_sequence() + + def make_sequence(self) -> None: + """Converts the response iterator in a list. By default this happens + automatically if required. If `implicit_sequence_conversion` is + disabled, this method is not automatically called and some properties + might raise exceptions. This also encodes all the items. + + .. versionadded:: 0.6 + """ + if not self.is_sequence: + # if we consume an iterable we have to ensure that the close + # method of the iterable is called if available when we tear + # down the response + close = getattr(self.response, "close", None) + self.response = list(self.iter_encoded()) + if close is not None: + self.call_on_close(close) + + def iter_encoded(self) -> t.Iterator[bytes]: + """Iter the response encoded with the encoding of the response. + If the response object is invoked as WSGI application the return + value of this method is used as application iterator unless + :attr:`direct_passthrough` was activated. + """ + # 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) + + @property + def is_streamed(self) -> bool: + """If the response is streamed (the response is not an iterable with + a length information) this property is `True`. In this case streamed + means that there is no information about the number of iterations. + This is usually `True` if a generator is passed to the response object. + + This is useful for checking before applying some sort of post + filtering that should not take place for streamed responses. + """ + try: + len(self.response) # type: ignore + except (TypeError, AttributeError): + return True + return False + + @property + def is_sequence(self) -> bool: + """If the iterator is buffered, this property will be `True`. A + response object will consider an iterator to be buffered if the + response attribute is a list or tuple. + + .. versionadded:: 0.6 + """ + return isinstance(self.response, (tuple, list)) + + def close(self) -> None: + """Close the wrapped response if possible. You can also use the object + in a with statement which will automatically close it. + + .. versionadded:: 0.9 + Can now be used in a with statement. + """ + if hasattr(self.response, "close"): + self.response.close() + for func in self._on_close: + func() + + def __enter__(self) -> Response: + return self + + def __exit__(self, exc_type, exc_value, tb): # type: ignore + self.close() + + def freeze(self) -> None: + """Make the response object ready to be pickled. Does the + following: + + * Buffer the response into a list, ignoring + :attr:`implicity_sequence_conversion` and + :attr:`direct_passthrough`. + * Set the ``Content-Length`` header. + * Generate an ``ETag`` header if one is not already set. + + .. versionchanged:: 2.1 + Removed the ``no_etag`` parameter. + + .. versionchanged:: 2.0 + An ``ETag`` header is always added. + + .. versionchanged:: 0.6 + The ``Content-Length`` header is set. + """ + # Always freeze the encoded response body, ignore + # implicit_sequence_conversion and direct_passthrough. + self.response = list(self.iter_encoded()) + self.headers["Content-Length"] = str(sum(map(len, self.response))) + self.add_etag() + + def get_wsgi_headers(self, environ: WSGIEnvironment) -> Headers: + """This is automatically called right before the response is started + and returns headers modified for the given environment. It returns a + copy of the headers from the response with some modifications applied + if necessary. + + For example the location header (if present) is joined with the root + URL of the environment. Also the content length is automatically set + to zero here for certain status codes. + + .. versionchanged:: 0.6 + Previously that function was called `fix_headers` and modified + the response object in place. Also since 0.6, IRIs in location + and content-location headers are handled properly. + + Also starting with 0.6, Werkzeug will attempt to set the content + length if it is able to figure it out on its own. This is the + case if all the strings in the response iterable are already + encoded and the iterable is buffered. + + :param environ: the WSGI environment of the request. + :return: returns a new :class:`~werkzeug.datastructures.Headers` + object. + """ + headers = Headers(self.headers) + location: str | None = None + content_location: str | None = None + content_length: str | int | None = None + status = self.status_code + + # iterate over the headers to find all values in one go. Because + # get_wsgi_headers is used each response that gives us a tiny + # speedup. + for key, value in headers: + ikey = key.lower() + if ikey == "location": + location = value + elif ikey == "content-location": + content_location = value + elif ikey == "content-length": + content_length = value + + if location is not None: + location = iri_to_uri(location) + + if self.autocorrect_location_header: + # Make the location header an absolute URL. + current_url = get_current_url(environ, strip_querystring=True) + current_url = iri_to_uri(current_url) + location = urljoin(current_url, location) + + headers["Location"] = location + + # make sure the content location is a URL + if content_location is not None: + headers["Content-Location"] = iri_to_uri(content_location) + + if 100 <= status < 200 or status == 204: + # Per section 3.3.2 of RFC 7230, "a server MUST NOT send a + # Content-Length header field in any response with a status + # code of 1xx (Informational) or 204 (No Content)." + headers.remove("Content-Length") + elif status == 304: + remove_entity_headers(headers) + + # if we can determine the content length automatically, we + # should try to do that. But only if this does not involve + # flattening the iterator or encoding of strings in the + # response. We however should not do that if we have a 304 + # response. + if ( + self.automatically_set_content_length + and self.is_sequence + and content_length is None + and status not in (204, 304) + and not (100 <= status < 200) + ): + content_length = sum(len(x) for x in self.iter_encoded()) + headers["Content-Length"] = str(content_length) + + return headers + + def get_app_iter(self, environ: WSGIEnvironment) -> t.Iterable[bytes]: + """Returns the application iterator for the given environ. Depending + on the request method and the current status code the return value + might be an empty response rather than the one from the response. + + If the request method is `HEAD` or the status code is in a range + where the HTTP specification requires an empty response, an empty + iterable is returned. + + .. versionadded:: 0.6 + + :param environ: the WSGI environment of the request. + :return: a response iterable. + """ + status = self.status_code + if ( + environ["REQUEST_METHOD"] == "HEAD" + or 100 <= status < 200 + or status in (204, 304) + ): + iterable: t.Iterable[bytes] = () + elif self.direct_passthrough: + return self.response # type: ignore + else: + iterable = self.iter_encoded() + return ClosingIterator(iterable, self.close) + + def get_wsgi_response( + self, environ: WSGIEnvironment + ) -> tuple[t.Iterable[bytes], str, list[tuple[str, str]]]: + """Returns the final WSGI response as tuple. The first item in + the tuple is the application iterator, the second the status and + the third the list of headers. The response returned is created + specially for the given environment. For example if the request + method in the WSGI environment is ``'HEAD'`` the response will + be empty and only the headers and status code will be present. + + .. versionadded:: 0.6 + + :param environ: the WSGI environment of the request. + :return: an ``(app_iter, status, headers)`` tuple. + """ + headers = self.get_wsgi_headers(environ) + app_iter = self.get_app_iter(environ) + return app_iter, self.status, headers.to_wsgi_list() + + def __call__( + self, environ: WSGIEnvironment, start_response: StartResponse + ) -> t.Iterable[bytes]: + """Process this response as WSGI application. + + :param environ: the WSGI environment. + :param start_response: the response callable provided by the WSGI + server. + :return: an application iterator + """ + app_iter, status, headers = self.get_wsgi_response(environ) + start_response(status, headers) + return app_iter + + # JSON + + #: A module or other object that has ``dumps`` and ``loads`` + #: functions that match the API of the built-in :mod:`json` module. + json_module = json + + @property + def json(self) -> t.Any | None: + """The parsed JSON data if :attr:`mimetype` indicates JSON + (:mimetype:`application/json`, see :attr:`is_json`). + + Calls :meth:`get_json` with default arguments. + """ + return self.get_json() + + @t.overload + def get_json(self, force: bool = ..., silent: t.Literal[False] = ...) -> t.Any: ... + + @t.overload + def get_json(self, force: bool = ..., silent: bool = ...) -> t.Any | None: ... + + def get_json(self, force: bool = False, silent: bool = False) -> t.Any | None: + """Parse :attr:`data` as JSON. Useful during testing. + + If the mimetype does not indicate JSON + (:mimetype:`application/json`, see :attr:`is_json`), this + returns ``None``. + + Unlike :meth:`Request.get_json`, the result is not cached. + + :param force: Ignore the mimetype and always try to parse JSON. + :param silent: Silence parsing errors and return ``None`` + instead. + """ + if not (force or self.is_json): + return None + + data = self.get_data() + + try: + return self.json_module.loads(data) + except ValueError: + if not silent: + raise + + return None + + # Stream + + @cached_property + def stream(self) -> ResponseStream: + """The response iterable as write-only stream.""" + return ResponseStream(self) + + def _wrap_range_response(self, start: int, length: int) -> None: + """Wrap existing Response in case of Range Request context.""" + if self.status_code == 206: + self.response = _RangeWrapper(self.response, start, length) # type: ignore + + def _is_range_request_processable(self, environ: WSGIEnvironment) -> bool: + """Return ``True`` if `Range` header is present and if underlying + resource is considered unchanged when compared with `If-Range` header. + """ + return ( + "HTTP_IF_RANGE" not in environ + or not is_resource_modified( + environ, + self.headers.get("etag"), + None, + self.headers.get("last-modified"), + ignore_if_range=False, + ) + ) and "HTTP_RANGE" in environ + + def _process_range_request( + self, + environ: WSGIEnvironment, + complete_length: int | None, + accept_ranges: bool | str, + ) -> bool: + """Handle Range Request related headers (RFC7233). If `Accept-Ranges` + header is valid, and Range Request is processable, we set the headers + as described by the RFC, and wrap the underlying response in a + RangeWrapper. + + Returns ``True`` if Range Request can be fulfilled, ``False`` otherwise. + + :raises: :class:`~werkzeug.exceptions.RequestedRangeNotSatisfiable` + if `Range` header could not be parsed or satisfied. + + .. versionchanged:: 2.0 + Returns ``False`` if the length is 0. + """ + from ..exceptions import RequestedRangeNotSatisfiable + + if ( + not accept_ranges + or complete_length is None + or complete_length == 0 + or not self._is_range_request_processable(environ) + ): + return False + + if accept_ranges is True: + accept_ranges = "bytes" + + parsed_range = parse_range_header(environ.get("HTTP_RANGE")) + + if parsed_range is None: + raise RequestedRangeNotSatisfiable(complete_length) + + range_tuple = parsed_range.range_for_length(complete_length) + content_range_header = parsed_range.to_content_range_header(complete_length) + + if range_tuple is None or content_range_header is None: + raise RequestedRangeNotSatisfiable(complete_length) + + content_length = range_tuple[1] - range_tuple[0] + self.headers["Content-Length"] = str(content_length) + self.headers["Accept-Ranges"] = accept_ranges + self.content_range = content_range_header # type: ignore + self.status_code = 206 + self._wrap_range_response(range_tuple[0], content_length) + return True + + def make_conditional( + self, + request_or_environ: WSGIEnvironment | Request, + accept_ranges: bool | str = False, + complete_length: int | None = None, + ) -> Response: + """Make the response conditional to the request. This method works + best if an etag was defined for the response already. The `add_etag` + method can be used to do that. If called without etag just the date + header is set. + + This does nothing if the request method in the request or environ is + anything but GET or HEAD. + + For optimal performance when handling range requests, it's recommended + that your response data object implements `seekable`, `seek` and `tell` + methods as described by :py:class:`io.IOBase`. Objects returned by + :meth:`~werkzeug.wsgi.wrap_file` automatically implement those methods. + + It does not remove the body of the response because that's something + the :meth:`__call__` function does for us automatically. + + Returns self so that you can do ``return resp.make_conditional(req)`` + but modifies the object in-place. + + :param request_or_environ: a request object or WSGI environment to be + used to make the response conditional + against. + :param accept_ranges: This parameter dictates the value of + `Accept-Ranges` header. If ``False`` (default), + the header is not set. If ``True``, it will be set + to ``"bytes"``. If it's a string, it will use this + value. + :param complete_length: Will be used only in valid Range Requests. + It will set `Content-Range` complete length + value and compute `Content-Length` real value. + This parameter is mandatory for successful + Range Requests completion. + :raises: :class:`~werkzeug.exceptions.RequestedRangeNotSatisfiable` + if `Range` header could not be parsed or satisfied. + + .. versionchanged:: 2.0 + Range processing is skipped if length is 0 instead of + raising a 416 Range Not Satisfiable error. + """ + environ = _get_environ(request_or_environ) + if environ["REQUEST_METHOD"] in ("GET", "HEAD"): + # if the date is not in the headers, add it now. We however + # will not override an already existing header. Unfortunately + # this header will be overridden by many WSGI servers including + # wsgiref. + if "date" not in self.headers: + self.headers["Date"] = http_date() + is206 = self._process_range_request(environ, complete_length, accept_ranges) + if not is206 and not is_resource_modified( + environ, + self.headers.get("etag"), + None, + self.headers.get("last-modified"), + ): + if parse_etags(environ.get("HTTP_IF_MATCH")): + self.status_code = 412 + else: + self.status_code = 304 + if ( + self.automatically_set_content_length + and "content-length" not in self.headers + ): + length = self.calculate_content_length() + if length is not None: + self.headers["Content-Length"] = str(length) + return self + + def add_etag(self, overwrite: bool = False, weak: bool = False) -> None: + """Add an etag for the current response if there is none yet. + + .. versionchanged:: 2.0 + SHA-1 is used to generate the value. MD5 may not be + available in some environments. + """ + if overwrite or "etag" not in self.headers: + self.set_etag(generate_etag(self.get_data()), weak) + + +class ResponseStream: + """A file descriptor like object used by :meth:`Response.stream` to + represent the body of the stream. It directly pushes into the + response iterable of the response object. + """ + + mode = "wb+" + + def __init__(self, response: Response): + self.response = response + self.closed = False + + def write(self, value: bytes) -> int: + if self.closed: + raise ValueError("I/O operation on closed file") + self.response._ensure_sequence(mutable=True) + self.response.response.append(value) # type: ignore + self.response.headers.pop("Content-Length", None) + return len(value) + + def writelines(self, seq: t.Iterable[bytes]) -> None: + for item in seq: + self.write(item) + + def close(self) -> None: + self.closed = True + + def flush(self) -> None: + if self.closed: + raise ValueError("I/O operation on closed file") + + def isatty(self) -> bool: + if self.closed: + raise ValueError("I/O operation on closed file") + return False + + def tell(self) -> int: + self.response._ensure_sequence() + return sum(map(len, self.response.response)) + + @property + def encoding(self) -> str: + return "utf-8" diff --git a/venv/Lib/site-packages/werkzeug/wsgi.py b/venv/Lib/site-packages/werkzeug/wsgi.py new file mode 100644 index 00000000..01d40af2 --- /dev/null +++ b/venv/Lib/site-packages/werkzeug/wsgi.py @@ -0,0 +1,595 @@ +from __future__ import annotations + +import io +import typing as t +from functools import partial +from functools import update_wrapper + +from .exceptions import ClientDisconnected +from .exceptions import RequestEntityTooLarge +from .sansio import utils as _sansio_utils +from .sansio.utils import host_is_trusted # noqa: F401 # Imported as part of API + +if t.TYPE_CHECKING: + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + + +def responder(f: t.Callable[..., WSGIApplication]) -> WSGIApplication: + """Marks a function as responder. Decorate a function with it and it + will automatically call the return value as WSGI application. + + Example:: + + @responder + def application(environ, start_response): + return Response('Hello World!') + """ + return update_wrapper(lambda *a: f(*a)(*a[-2:]), f) + + +def get_current_url( + environ: WSGIEnvironment, + root_only: bool = False, + strip_querystring: bool = False, + host_only: bool = False, + trusted_hosts: t.Iterable[str] | None = None, +) -> str: + """Recreate the URL for a request from the parts in a WSGI + environment. + + The URL is an IRI, not a URI, so it may contain Unicode characters. + Use :func:`~werkzeug.urls.iri_to_uri` to convert it to ASCII. + + :param environ: The WSGI environment to get the URL parts from. + :param root_only: Only build the root path, don't include the + remaining path or query string. + :param strip_querystring: Don't include the query string. + :param host_only: Only build the scheme and host. + :param trusted_hosts: A list of trusted host names to validate the + host against. + """ + parts = { + "scheme": environ["wsgi.url_scheme"], + "host": get_host(environ, trusted_hosts), + } + + if not host_only: + parts["root_path"] = environ.get("SCRIPT_NAME", "") + + if not root_only: + parts["path"] = environ.get("PATH_INFO", "") + + if not strip_querystring: + parts["query_string"] = environ.get("QUERY_STRING", "").encode("latin1") + + return _sansio_utils.get_current_url(**parts) + + +def _get_server( + environ: WSGIEnvironment, +) -> tuple[str, int | None] | None: + name = environ.get("SERVER_NAME") + + if name is None: + return None + + try: + port: int | None = int(environ.get("SERVER_PORT", None)) + except (TypeError, ValueError): + # unix socket + port = None + + return name, port + + +def get_host( + environ: WSGIEnvironment, trusted_hosts: t.Iterable[str] | None = None +) -> str: + """Return the host for the given WSGI environment. + + The ``Host`` header is preferred, then ``SERVER_NAME`` if it's not + set. The returned host will only contain the port if it is different + than the standard port for the protocol. + + Optionally, verify that the host is trusted using + :func:`host_is_trusted` and raise a + :exc:`~werkzeug.exceptions.SecurityError` if it is not. + + :param environ: A WSGI environment dict. + :param trusted_hosts: A list of trusted host names. + + :return: Host, with port if necessary. + :raise ~werkzeug.exceptions.SecurityError: If the host is not + trusted. + """ + return _sansio_utils.get_host( + environ["wsgi.url_scheme"], + environ.get("HTTP_HOST"), + _get_server(environ), + trusted_hosts, + ) + + +def get_content_length(environ: WSGIEnvironment) -> int | None: + """Return the ``Content-Length`` header value as an int. If the header is not given + or the ``Transfer-Encoding`` header is ``chunked``, ``None`` is returned to indicate + a streaming request. If the value is not an integer, or negative, 0 is returned. + + :param environ: The WSGI environ to get the content length from. + + .. versionadded:: 0.9 + """ + return _sansio_utils.get_content_length( + http_content_length=environ.get("CONTENT_LENGTH"), + http_transfer_encoding=environ.get("HTTP_TRANSFER_ENCODING"), + ) + + +def get_input_stream( + environ: WSGIEnvironment, + safe_fallback: bool = True, + max_content_length: int | None = None, +) -> t.IO[bytes]: + """Return the WSGI input stream, wrapped so that it may be read safely without going + past the ``Content-Length`` header value or ``max_content_length``. + + If ``Content-Length`` exceeds ``max_content_length``, a + :exc:`RequestEntityTooLarge`` ``413 Content Too Large`` error is raised. + + If the WSGI server sets ``environ["wsgi.input_terminated"]``, it indicates that the + server handles terminating the stream, so it is safe to read directly. For example, + a server that knows how to handle chunked requests safely would set this. + + If ``max_content_length`` is set, it can be enforced on streams if + ``wsgi.input_terminated`` is set. Otherwise, an empty stream is returned unless the + user explicitly disables this safe fallback. + + If the limit is reached before the underlying stream is exhausted (such as a file + that is too large, or an infinite stream), the remaining contents of the stream + cannot be read safely. Depending on how the server handles this, clients may show a + "connection reset" failure instead of seeing the 413 response. + + :param environ: The WSGI environ containing the stream. + :param safe_fallback: Return an empty stream when ``Content-Length`` is not set. + Disabling this allows infinite streams, which can be a denial-of-service risk. + :param max_content_length: The maximum length that content-length or streaming + requests may not exceed. + + .. versionchanged:: 2.3.2 + ``max_content_length`` is only applied to streaming requests if the server sets + ``wsgi.input_terminated``. + + .. versionchanged:: 2.3 + Check ``max_content_length`` and raise an error if it is exceeded. + + .. versionadded:: 0.9 + """ + stream = t.cast(t.IO[bytes], environ["wsgi.input"]) + content_length = get_content_length(environ) + + if content_length is not None and max_content_length is not None: + if content_length > max_content_length: + raise RequestEntityTooLarge() + + # A WSGI server can set this to indicate that it terminates the input stream. In + # that case the stream is safe without wrapping, or can enforce a max length. + if "wsgi.input_terminated" in environ: + if max_content_length is not None: + # If this is moved above, it can cause the stream to hang if a read attempt + # is made when the client sends no data. For example, the development server + # does not handle buffering except for chunked encoding. + return t.cast( + t.IO[bytes], LimitedStream(stream, max_content_length, is_max=True) + ) + + return stream + + # No limit given, return an empty stream unless the user explicitly allows the + # potentially infinite stream. An infinite stream is dangerous if it's not expected, + # as it can tie up a worker indefinitely. + if content_length is None: + return io.BytesIO() if safe_fallback else stream + + return t.cast(t.IO[bytes], LimitedStream(stream, content_length)) + + +def get_path_info(environ: WSGIEnvironment) -> str: + """Return ``PATH_INFO`` from the WSGI environment. + + :param environ: WSGI environment to get the path from. + + .. versionchanged:: 3.0 + The ``charset`` and ``errors`` parameters were removed. + + .. versionadded:: 0.9 + """ + path: bytes = environ.get("PATH_INFO", "").encode("latin1") + return path.decode(errors="replace") + + +class ClosingIterator: + """The WSGI specification requires that all middlewares and gateways + respect the `close` callback of the iterable returned by the application. + Because it is useful to add another close action to a returned iterable + and adding a custom iterable is a boring task this class can be used for + that:: + + return ClosingIterator(app(environ, start_response), [cleanup_session, + cleanup_locals]) + + If there is just one close function it can be passed instead of the list. + + A closing iterator is not needed if the application uses response objects + and finishes the processing if the response is started:: + + try: + return response(environ, start_response) + finally: + cleanup_session() + cleanup_locals() + """ + + def __init__( + self, + iterable: t.Iterable[bytes], + callbacks: None + | (t.Callable[[], None] | t.Iterable[t.Callable[[], None]]) = None, + ) -> None: + iterator = iter(iterable) + self._next = t.cast(t.Callable[[], bytes], partial(next, iterator)) + if callbacks is None: + callbacks = [] + elif callable(callbacks): + callbacks = [callbacks] + else: + callbacks = list(callbacks) + iterable_close = getattr(iterable, "close", None) + if iterable_close: + callbacks.insert(0, iterable_close) + self._callbacks = callbacks + + def __iter__(self) -> ClosingIterator: + return self + + def __next__(self) -> bytes: + return self._next() + + def close(self) -> None: + for callback in self._callbacks: + callback() + + +def wrap_file( + environ: WSGIEnvironment, file: t.IO[bytes], buffer_size: int = 8192 +) -> t.Iterable[bytes]: + """Wraps a file. This uses the WSGI server's file wrapper if available + or otherwise the generic :class:`FileWrapper`. + + .. versionadded:: 0.5 + + If the file wrapper from the WSGI server is used it's important to not + iterate over it from inside the application but to pass it through + unchanged. If you want to pass out a file wrapper inside a response + object you have to set :attr:`Response.direct_passthrough` to `True`. + + More information about file wrappers are available in :pep:`333`. + + :param file: a :class:`file`-like object with a :meth:`~file.read` method. + :param buffer_size: number of bytes for one iteration. + """ + return environ.get("wsgi.file_wrapper", FileWrapper)( # type: ignore + file, buffer_size + ) + + +class FileWrapper: + """This class can be used to convert a :class:`file`-like object into + an iterable. It yields `buffer_size` blocks until the file is fully + read. + + You should not use this class directly but rather use the + :func:`wrap_file` function that uses the WSGI server's file wrapper + support if it's available. + + .. versionadded:: 0.5 + + If you're using this object together with a :class:`Response` you have + to use the `direct_passthrough` mode. + + :param file: a :class:`file`-like object with a :meth:`~file.read` method. + :param buffer_size: number of bytes for one iteration. + """ + + def __init__(self, file: t.IO[bytes], buffer_size: int = 8192) -> None: + self.file = file + self.buffer_size = buffer_size + + def close(self) -> None: + if hasattr(self.file, "close"): + self.file.close() + + def seekable(self) -> bool: + if hasattr(self.file, "seekable"): + return self.file.seekable() + if hasattr(self.file, "seek"): + return True + return False + + def seek(self, *args: t.Any) -> None: + if hasattr(self.file, "seek"): + self.file.seek(*args) + + def tell(self) -> int | None: + if hasattr(self.file, "tell"): + return self.file.tell() + return None + + def __iter__(self) -> FileWrapper: + return self + + def __next__(self) -> bytes: + data = self.file.read(self.buffer_size) + if data: + return data + raise StopIteration() + + +class _RangeWrapper: + # private for now, but should we make it public in the future ? + + """This class can be used to convert an iterable object into + an iterable that will only yield a piece of the underlying content. + It yields blocks until the underlying stream range is fully read. + The yielded blocks will have a size that can't exceed the original + iterator defined block size, but that can be smaller. + + If you're using this object together with a :class:`Response` you have + to use the `direct_passthrough` mode. + + :param iterable: an iterable object with a :meth:`__next__` method. + :param start_byte: byte from which read will start. + :param byte_range: how many bytes to read. + """ + + def __init__( + self, + iterable: t.Iterable[bytes] | t.IO[bytes], + start_byte: int = 0, + byte_range: int | None = None, + ): + self.iterable = iter(iterable) + self.byte_range = byte_range + self.start_byte = start_byte + self.end_byte = None + + if byte_range is not None: + self.end_byte = start_byte + byte_range + + self.read_length = 0 + self.seekable = hasattr(iterable, "seekable") and iterable.seekable() + self.end_reached = False + + def __iter__(self) -> _RangeWrapper: + return self + + def _next_chunk(self) -> bytes: + try: + chunk = next(self.iterable) + self.read_length += len(chunk) + return chunk + except StopIteration: + self.end_reached = True + raise + + def _first_iteration(self) -> tuple[bytes | None, int]: + chunk = None + if self.seekable: + self.iterable.seek(self.start_byte) # type: ignore + self.read_length = self.iterable.tell() # type: ignore + contextual_read_length = self.read_length + else: + while self.read_length <= self.start_byte: + chunk = self._next_chunk() + if chunk is not None: + chunk = chunk[self.start_byte - self.read_length :] + contextual_read_length = self.start_byte + return chunk, contextual_read_length + + def _next(self) -> bytes: + if self.end_reached: + raise StopIteration() + chunk = None + contextual_read_length = self.read_length + if self.read_length == 0: + chunk, contextual_read_length = self._first_iteration() + if chunk is None: + chunk = self._next_chunk() + if self.end_byte is not None and self.read_length >= self.end_byte: + self.end_reached = True + return chunk[: self.end_byte - contextual_read_length] + return chunk + + def __next__(self) -> bytes: + chunk = self._next() + if chunk: + return chunk + self.end_reached = True + raise StopIteration() + + def close(self) -> None: + if hasattr(self.iterable, "close"): + self.iterable.close() + + +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 + :attr:`.Request.max_content_length`. + + When attempting to read after the limit has been reached, :meth:`on_exhausted` is + called. When the limit is a maximum, this raises :exc:`.RequestEntityTooLarge`. + + If reading from the stream returns zero bytes or raises an error, + :meth:`on_disconnect` is called, which raises :exc:`.ClientDisconnected`. When the + limit is a maximum and zero bytes were read, no error is raised, since it may be the + end of the stream. + + If the limit is reached before the underlying stream is exhausted (such as a file + that is too large, or an infinite stream), the remaining contents of the stream + cannot be read safely. Depending on how the server handles this, clients may show a + "connection reset" failure instead of seeing the 413 response. + + :param stream: The stream to read from. Must be a readable binary IO object. + :param limit: The limit in bytes to not read past. Should be either the + ``Content-Length`` header value or ``request.max_content_length``. + :param is_max: Whether the given ``limit`` is ``request.max_content_length`` instead + of the ``Content-Length`` header value. This changes how exhausted and + disconnect events are handled. + + .. versionchanged:: 2.3 + Handle ``max_content_length`` differently than ``Content-Length``. + + .. versionchanged:: 2.3 + Implements ``io.RawIOBase`` rather than ``io.IOBase``. + """ + + def __init__(self, stream: t.IO[bytes], limit: int, is_max: bool = False) -> None: + self._stream = stream + self._pos = 0 + self.limit = limit + self._limit_is_max = is_max + + @property + def is_exhausted(self) -> bool: + """Whether the current stream position has reached the limit.""" + return self._pos >= self.limit + + def on_exhausted(self) -> None: + """Called when attempting to read after the limit has been reached. + + The default behavior is to do nothing, unless the limit is a maximum, in which + case it raises :exc:`.RequestEntityTooLarge`. + + .. versionchanged:: 2.3 + Raises ``RequestEntityTooLarge`` if the limit is a maximum. + + .. versionchanged:: 2.3 + Any return value is ignored. + """ + if self._limit_is_max: + raise RequestEntityTooLarge() + + def on_disconnect(self, error: Exception | None = None) -> None: + """Called when an attempted read receives zero bytes before the limit was + reached. This indicates that the client disconnected before sending the full + request body. + + The default behavior is to raise :exc:`.ClientDisconnected`, unless the limit is + a maximum and no error was raised. + + .. versionchanged:: 2.3 + Added the ``error`` parameter. Do nothing if the limit is a maximum and no + error was raised. + + .. versionchanged:: 2.3 + Any return value is ignored. + """ + if not self._limit_is_max or error is not None: + raise ClientDisconnected() + + # If the limit is a maximum, then we may have read zero bytes because the + # streaming body is complete. There's no way to distinguish that from the + # client disconnecting early. + + def exhaust(self) -> bytes: + """Exhaust the stream by reading until the limit is reached or the client + disconnects, returning the remaining data. + + .. versionchanged:: 2.3 + Return the remaining data. + + .. versionchanged:: 2.2.3 + Handle case where wrapped stream returns fewer bytes than requested. + """ + if not self.is_exhausted: + return self.readall() + + return b"" + + def readinto(self, b: bytearray) -> int | None: # type: ignore[override] + size = len(b) + remaining = self.limit - self._pos + + if remaining <= 0: + self.on_exhausted() + return 0 + + if hasattr(self._stream, "readinto"): + # Use stream.readinto if it's available. + if size <= remaining: + # The size fits in the remaining limit, use the buffer directly. + try: + out_size: int | None = self._stream.readinto(b) + except (OSError, ValueError) as e: + self.on_disconnect(error=e) + return 0 + else: + # Use a temp buffer with the remaining limit as the size. + temp_b = bytearray(remaining) + + try: + out_size = self._stream.readinto(temp_b) + except (OSError, ValueError) as e: + self.on_disconnect(error=e) + return 0 + + if out_size: + b[:out_size] = temp_b + else: + # WSGI requires that stream.read is available. + try: + data = self._stream.read(min(size, remaining)) + except (OSError, ValueError) as e: + self.on_disconnect(error=e) + return 0 + + out_size = len(data) + b[:out_size] = data + + if not out_size: + # Read zero bytes from the stream. + self.on_disconnect() + return 0 + + self._pos += out_size + return out_size + + def readall(self) -> bytes: + if self.is_exhausted: + self.on_exhausted() + return b"" + + out = bytearray() + + # The parent implementation uses "while True", which results in an extra read. + while not self.is_exhausted: + data = self.read(1024 * 64) + + # Stream may return empty before a max limit is reached. + if not data: + break + + out.extend(data) + + return bytes(out) + + def tell(self) -> int: + """Return the current stream position. + + .. versionadded:: 0.9 + """ + return self._pos + + def readable(self) -> bool: + return True diff --git a/venv/Scripts/flask.exe b/venv/Scripts/flask.exe new file mode 100644 index 0000000000000000000000000000000000000000..85010e59e22c9d895511c793424f32e782d62ffb GIT binary patch literal 108452 zcmeFadw5jU)%ZWjWXKQ_P7p@IO-Bic#!G0tBo5RJ%;*`JC{}2xf}+8Qib}(bU_}i* zNt@v~ed)#4zP;$%+PC)dzP-K@u*HN(5-vi(8(ykWyqs}B0W}HN^ZTrQW|Da6`@GNh z?;nrOIeVXdS$plZ*IsMwwRUQ*Tjz4ST&_I+w{4fJg{Suk zDk#k~{i~yk?|JX1Bd28lkG=4tDesa#KJ3?1I@I&=Dc@7ibyGgz`N6)QPkD>ydq35t zw5a^YGUb1mdHz5>zj9mcQfc#FjbLurNVL)nYxs88p%GSZYD=wU2mVCNzLw{@99Q)S$;kf8bu9yca(9kvVm9ml^vrR!I-q`G>GNZ^tcvmFj1Tw`fDZD% z5W|pvewS(+{hSy`MGklppb3cC_!< z@h|$MW%{fb(kD6pOP~L^oj#w3zJ~Vs2kG-#R!FALiJ3n2#KKaqo`{tee@!>``%TYZ zAvWDSs+)%@UX7YtqsdvvwN2d-bF206snTti-qaeKWO__hZf7u%6VXC1N9?vp8HGbt z$J5=q87r;S&34^f$e4|1{5Q7m80e=&PpmHW&kxQE&JTVy_%+?!PrubsGZjsG&H_mA zQ+};HYAVAOZ$}fiR9ee5mn&%QXlmtKAw{$wwpraLZCf`f17340_E;ehEotl68O}?z z_Fyo%={Uuj?4YI}4_CCBFIkf)7FE?&m*#BB1OGwurHJ`#$n3Cu6PQBtS>5cm-c_yd zm7$&vBt6p082K;-_NUj{k+KuI`&jBbOy5(mhdgt;_4`wte(4luajXgG4i5JF>$9DH zLuPx#d`UNVTE7`D<#$S>tLTmKF}kZpFmlFe?$sV{v-Y20jP$OX&jnkAUs(V7XVtyb zD?14U)*?`&hGB*eDs)t|y2JbRvVO)oJ=15@?4VCZW>wIq(@~Mrk@WIydI@Ul!>+o3 z=M=Kzo*MI=be*)8{ISB{9>(!J__N-a=8R&n#W%-gTYRcuDCpB^^s3~-GP@@5&-(G& zdQS_V>w;D8SV2wM8)U9HoOaik`_z>Ep^Rpe3rnjb<}(rV`tpdmg4g@>h`BF#WAKLH zqTs?sEDwi<=6_WPwY&oS9!h@ge4(br)-Q{|OY*#YAspuHyx;~|kASS3FIH@oGSl?L zvQoe8yKukD)zqprHiFKlW%;G=hwx4l;FI%8m&(#zU|j&_bW@ThNpr9D0V}xa)%aIb zI$i2CA2mPU{0nJmK0dxe)dY-`z>ln($ z;r!UXuLDDi42|Zd3Erx&m8GqlFWbIX0V<*Gn6lVNq%gD>gw}da}r}ZQB~ns?p8uy4i0%1Ti$Vt|~OUth4=+yEmPu8{3(w zUDkd@?w?`_J9HBkx&ZF8v{+9phcT@3J8VI~wN7Ez)oJS6^dhb2N;;{RTXB`K*E$64 z3rDqRtY&&*}9yq2oUcvD7K)=@bWqC1X%l0jk)W<5-WBYC(#rn4H5)gp#eHMmwlLJq=^%|*gMQ*pq4VV(QhHA4CGj<;!d8i*#Z8CaN#*>VcCnj~;kkeUa{LUoKxFCaoQ) z(Lz++&x3Lwz;=6UnhwM!MvN17>{Qmb?dwgsTmzkLB~jD#wiGz73hc0bFE|C9KA#|= zH}%FQ>c&Y5z*TJD-<$$Y*WZx>5NNe-E-TfAt1!)%Wc@I;ZuNwxDGGasDIMyUNiVvG zq;Q70PYHcLO=Xgv2698@cJrkun-^>P2}|fMHlm7xaZmE<{&cQtb`{N9zj0bRmpW^T zzQV7oTs0ENHe&mxQ6DI7qd0SU4;3o*2qRd`X1>(=ew})X5Dx zx$lyzZM^emtdsbk^u+xwdSX$lp7h*2CkHCqDohShL)V4hM9k+UQLP(GN-H7!C8gyq zex`xuPQ(!g4}S>0r+CyH+xIAMP9Z&+?BT1!*kA<}dqRn*FwJPGe}l-sw(lGYN1b8} zWQQjQN`9tdtF?#aqMN?wu4E3)qGxzOhwr*vb;kX_%&U*-=KLr0raiGc^x8|=Wqt`N z?L0luR(~BF;DS@~yKDN7|*TJkj*-B%s1{65$`jY_(C#P&^rVi0?Ro4iaFbR)Z2NLxS0 zTL;%Kt22(A8JiL`U$i!iR&zLxx^E%H=*c-=+h@sisygu-_#m4J4LQqB?~vXvP4@yQo0-^oki(PiH+=FZl}&W)S-qI zk>W;2Zl-vl6rbe4X6feZb)l-Mv2oh^5t8q5@(Y-SPoUZ;N<5Tdl!h|=x!1}5)E;}=RcAXJ8(<$^13IV==^rU>wwq$hX3V4iuA0>h< zuxK^)myr=p7a)oeZ+g4u^9(OmpFl8J@{{UJfy=DjAf8lTTD00iSF3Kb9|GdM-PQp)0<* zZkW*V-TPpIXEKDks>&FQ?qoV&Tfa*;TJyB^yJa8xcch+*-cYj6E7HdBX!5)TIXSNM z4C2L57KVd0rioelfI{ELMrb&Y}?h%mk5iSTXrmJ zwlk6qsS{}3<}Uc!G}Wr;Tek1Tym8$SrWokvCzU(FVIAWTEa1pwE zBJ6JdS@$4RFBV*~g^Eo9MAFafx2rt|uRsR%xpNVyj8!g>2u0v=>eO zS~4nHBgR%cVxB-_OwP@%JN(CpY3qHvqsbt-TUGivY2Dr$b+=`6PJSkbWF)!Jn=iZJ zMt}mOG~-m{)L*SV+yRH!c@XR%)K^BqVRh zq&wib)2#d0V3BD*|F5o2J6$vbdJGh`O-30SrMI;e*Y&m8c0Bi^cD-$Daq1haK*i4o zS^0dLE!U;Du-W5i&*6##L30bjy7q7@lQPyCc8<%{>0)|vQlrFG_D_+v^1uh+p+bhA?!)dFEqi$(hoT?=hJt20DQXmOiJ``9LY)@=HE zO1esvSjV70vmITir9t{Om5D&<%?UTa#`5Sp-x@^?6JCK@(Y_-+ye_agHcB_zSUEYe zay}#@o~N5_?G>%q2t<~g3s!Y+G*Mj=P3Zn>mA2=HCm`lzap|)*f|(31R{)36WvAyz zfea$wK&B|2YxO{n>twI{fk3f0YVK4T;XDy#cUe=*$V6#=30zz**pkdJOUUdHcyGKx z={=%tU83}-sM&@LFz=EaBy8m5*VS4ZYhB<>lI{BnIk4cD&H_E|%!spiL(( z$1W0V$;KX^P(?<}XYHqoplpQo7H>!m)d{bdPaLde+h7(tf+ZB(6MxWZnoX6&>|)(q z*DB~wjMmL&u~F-ZIbJ>BJ5ZM6ik)gUbdlBM`Quqove#M~lf*ebB4nBg}NN8q8e!? zVj>HOMJZ@LQzOdvHUSih8gCt%IxvyHLmO^Ea(*!Nd-Zuw>`f87{SkAwbrcIp6hiff zt7^x@FVoBVwDl9eTxT2$))(-5-O9W=qunp;*yvYT{VJ=~FI-x;pN&=5ArA%W0()Z} z=?f87g#Y@j2_ct@T|gzY^?R)mq?NdksZ}7gJW^{18>hCuy{s)%iDWGzC?-DRKLl?l zlnO5zQf3*!v6nJ;)xm`Sjm!6zf=o%-07p#e5?cL}gBtB`Nq!dTtt@<7#(o8m8xm*XOvN65AL(=C_D} zJM9UyYteSSwriu8{DkKl6tSk&09e8kMrjh@N|SS;@9l|6^W@_Q=i{`@$NUzI6|VF> zN{Rev95oVSa&%)ew#+uKZf{3cFg?f64ASokLt$^COgO2#BW71L>H7~o2Zg;=Z|nCM zZ=N18^ET^uY+VpF$K*teqc&2xaTF!LhIKrwGne_WBX+B_9vi@rt2GKHy|kQxSUJ18@{fEswY{>va~$3%JGyYfr29k%@bck16c zdf9Hh?|r@PC`@3R-j=#7868z@m3)O|u0`Iw|bd&(6~U$UMGD@Vncn>Lm}{NqU9US&{gYu`~lU+m1n zi1g$#vC1#v|9B;ObTzhRor!#90$^5b(Gy`buihHrRfjV>-l^6#?Dg3lZ}@PRD|I(> zVcp1Kiyr8xABHMWk$xp&hFzvUhIKbDi1339ve8Ac5ON73NDM}^^I8O?+8zk+GVA0S zG|7G=o9JQQO;-x!z=zz5c@^<{-AWi)tG`b65v40t#CwnzKA}>?+z|q4`eNlNfRXZK%L4$WHQ)8Sgo0 zwE~@9)+4fUIf8fW?9TihJ6Hgttrta)MqB{FTBqxu|CDLzEKWn{Cn*>&wx$DtvzSvC z(4Jr-g8~qe!NL-;BVhBlx}Y;!It5;VT~^q_HdZcH!a^(MA3%zpy!zmpD(NfkvF=9= z6p^lmDSFnrRVn4npverH%%I5(CT}SgTNGB)0sCY%@`7%@lG#4Gt*2;3c3;0E8(QyS zoo-l-h2)DEIh-3t!@^Gefe~>Aq|Sbf{goW=Op7FDAB-5amdpAhatG_BQh1V>p|DF2 zoM~XblmiX(kl0U_veatKBQ+uz9@Z1{N|y`0j<11Sd^JtI@w2S`$mW?%;MWLc4%=HL zi!p2d7Nf9k{=Kw;xt19k$vh+UMEX9C2D?jRP0wn3ihvj zIKqjR_QyB+t|%#l=^@PkY$HlM{<4z$Jve9n{#ZUhYv#%_q#uJnen z7S7e0{d|oCJ_u>EJ_(yUqk*m3cisoGsENRi9?F=l*A~&-*(<$4vm*-sUaFT_dJdnX zrOQM7ERMPl>SbN2|4`NV9yZ$|0jqv#7_|5qM&SK>FdA$Qn}>sahte?IEg|!hNZ-Lw z+2M47yawJ6YgZhmd7`)o7cpN%77HvCf^&@h2FBhy;L2rI>K+Cp6&?pq zlFhyiSR(126>L@rL1c*79q1?uBeI5<%2ZP3K!*8bJ8n5Vkdy&9Re{a#rI- z6fv$Y@#|&(1pg>!eIKW$IeEqD_akO!YCNey`?q5Uh$a^MgG!T#n1>V}I*O@Oh-I-5 z%k{Du%Iw6?)MXzjh?<)@`1%M|Z2fN100q^u)YBKp;(8NX!a7BpNWL}bB60|{!@3IM z&!_-j!}^5^fVs3)8n2d}7M6&L95t6HGcO7O>k8tJiY2gy{mtC0V*s z;mM4hWAvYlP0?$+)i!p-gT`AH%yAiSovz=pXFBCU*-y1#y_wmwf!PgMrEDEyp_Y+h-3$ZW$Ny$8H)g+M&odOm3D+qCuDCyTVF4s8_v zmEyLRLz)cEXCoqszT`H8*!|T3k)9}efv(zxR?xmMPtJ#z>B&Eo77PE!jE`0XJbxM^ zJEbz?Lu5g--#l!-Y#gzXP3G6p>XOps?99>9SjC=T%MY0{>#J9bVPGK(CmAlr@LDVu zdtE8Cwy$lsu#8`O8L={lK%5}c`pb6GjOmh$5gX((WMNF8jU#kU?6HQLb+0+w?hE$3nE@wxIvFA6~zB7QMVyoEeHQuBH-S!>tRw89F zyIi51ALX;4mfyl>Gbw7NUa`Y^`9s-NepV{j;n;E-$Ceyj?qimR?nQpJ7Zt@YCfL5$ zX%(74|FeDDa8Ol;N-078H81eqW|LX(_9$cc`%a*!#=7{V2=)|lNG5a40)v6g4t z01XUUv68UZ2|@vkl?ceW7{YVw!nCy? z+sAnJ?mvd`Ab`J#GpRgV_N#doE}<~&Z?VHb%c3L;ua)NW2qzfhmeh>}dH zGKiE|U&0iVSyyQ$NO;+GkhAqI3{1v-UXl6k&ogShm<+H}bDWf8ZLbv`!7=F`^V*WW z%|fH`g0dA}vmj?dt{;}&QQW)P9h)H{A4EQ&PP7V>>J53l4KOcs^mIW( zWkEdG-lC&N1l;w9;87FIEh#42)wpNXA?u;BStwK2f%x9dIa=c%`6v*^^D7Rdeo3P2 zK9dB;uN>7oyTltCA%$60W`E3W-dBpg zuqcq@x{}^i&v~(2yR)n>8M=s-@@eAy%xR>v4&Y%h*z7^|kj=+ut-*SgnXpUQ2Za%i zw_32)!m77h`9S6v$7W)#c5Gu%xh%>rSYMFAD@|Kh-5MzR0ebF=8}-^F_#pg>cMe^Q z_fFTrqJD?X&Jg+pQE^7T9S;~YZ`N{LIq@lM=%?CSV`D_iRT3c{J=yaikxU5%rHT=TI9ln9_p;9*QY6sX)@dJei;QU6QC|w1dx9PPU z-k*1jcMjN$eZXl0=c@we30H5Z#G4Zf18#{O`?4|fubhbI#LpT6?u0J@S5*J&gl|g| zx>4w6bp!F}L5Qb)5yTF=Q~b_2auNe$u2af-1--x-Y8ugJ)$~A7xqyDQUb~z9yjp?2 zS$2CCh3xpcnb+1EDhBdlycVY?TH-GQhOBi1Em;xS%mih!zz5d%5ZTK)kgI(;YVM1) z9Y?6R=*3Ee3NQqA=9m}0tBfPY>WV^F{KDkb!>u=FvBx{<@$4HF#Ty?(D_|c16@7ar z?3sMj4pkIxD3B@pYY^(UW7-_E@LkG|E4F$T>^}02mQUF3kyHzn_+N+p{xB`ffEMeA9vW5-D%{ zZltI*4Xan_uaQoJoSn85x~zjwdZGe`c|L&8DFe`!Uzz7`w0>!xulJ>+=37i-p5mR> zWl?vJ+1b|P3AuYhVyI7#LAPEYZ87i$tRpmE}@el^F1lN0erixJ1-N#3v0fp0!puf z11^VLsS9qh<=8A zl(KovC21r`^>K0LV;-uDR<&qv-K@mIx|7<^+mo|TDsK^_F=k^064`x9BFi|CeU^vI zA`v->wGlB>5s}S`2Vld*+LS4GWdW#Z9=Ld+EhF-ng5iU)X7A68`i# zO|AEyO~DJK*d*(2vK_TGJ;J(KCFF$1nt-h(v%kz8V%#2jMxD`gWt|!-@k5${77Q@!{4z;ze=7&BScC z{l96Ke7GeU{#P5P(1-)>pb!x>_limI(??L33;=E&UU`S^Xg(o6V~Xzp2+b869oyFB~+oK91m(zDG}-Ce|yro;clXhx0fm zqA!a1;w8|CgOIS{tHtHPM)Qnv&@IQrVjZ>Cz6}8;hEX6s#`+#jXAT>_&8rE)U3h@u(3Rj2wHPF8HLr_+u|u2h!@v|soMqnSEk8Zd`9UErc zRN_h>v@U-yBXM8Ej^Rk$+sR6^P!=M|4(TT&#@8NU-8`?Hjo1~wjxi#DFXslCbHj#H zR5!NB>1Vtka3nsdw|a3-Y^?Qbif>?ajCQZ}h|~?V$4;Z2hvePt!VjWV5kP_Mdzd#2 z(Ya9OE~}OG95vq%MZN6^iVy-|(zl&p4c#oK!g~#g9ul0wCtz5||XBmlcb|@y+~5^oMA2 z%2&t|Z30b#v!su;P0>oP@n%l!68gTFk*t&4-cTiC(g?CTh0XM*M_NA`XrI~P!(S-N zL`<-L&IbV?K2X3qpYwnLW)JqoQsvmwRaiiIOAWlUuFCW7CR}XuDqc-j>a`x<)1Wa~ zw1+(1-L|GuLWkn}HjH3W>Zkjq4e-!WA;hn0iSIXW`S*t~{JgUpYShtg%LoE=slzv~<=K*WA*ElMAxu<+e5ER>PXppG$|uZeA(Temu%&q(p;3AFN2!kq zm=?vfxfpqDEN!LF)Xm0H1wg{HMEXo-l13}ryyuWqH$7J>Xgp69ORBMSo%EOR{GE@T zp6`=69Ftb3=ONylwdwgfFVgK&D$mcnFSmVb{~?FB$0_H`z~O7eOlSLUCm#&_o;kIB z^GO&pU!)Lg-zm3^a<;FL4;!T`wb1X9I%}R0*ioufT+j91NaBu?NMeOwVtj_4-Bj0@ z_j+s0>1Gh!;oi!cvc4Mg&8Yc4=Cmj3w59_z5~=-$9!bpUA~dL*qwByWnz05DbT{~4 z*jZ@K?vDlzYTtT-qUP-5@^1W$cjLZ1m)7`wc?;yk#>sw)Ni$-;5OH_f-AMb*3BElL zTXVmwcEz1Nab&8Q-#V9uW2Z6VdwH||2KhpVBR4w8!{_^EvduYpj=@m1wadC|nCyj2 zt$A%;w3fp&nPJJ87ID86l?_lyq<-5M`#ZFGH^n*bFxrb{B4*!>glHD=IX zaR4E?rmXV`e=Jb3r)umy9O_=}HG_<;wLag>;c-u)&Cx(xabWC&VP!^jmFM&Ib z$EM)|j1Ueju0pu}b54-q=pis$~y&T*+xHtN5ij^Dv z^%7mNlKsbrMJuxz??mDQn__!^I>*gYDhiq>gCh>6y-yP!!np!os_nT!v)geY)f(H$ zMdxVz82saUVjQ{l!Fyx32g`P8jl0P*QX^tlU_Sb?kt&IuWuyvXIfW6 zvj(<2h5p+D2H`EwSwH=TECv*ISR}=U4K0jI?@X;}rSnDnja37_hg1U|)xdV^hSx;N zR_l)tW>JcPb8F@5C~uO{c@SQX_Wc-vx12+X_zdyQjX9DVg;djzhq7W0o z))<;YTY1Kqwi$lJ9G%8d#&=Y2g-5J9EDiLvQu;DVkGayNG;o{qwO{JmzR6Uh$UG@x zPCO=Jtf)bg*6_lp#3+w^Tg=a7c|p*fGtm(jE${gPmO7HD77SR?ytQ3_Bxr`(@-qAT zWfSOxaSdnVed(w}=&i-FC`!Pi=?<=yrTgx#ws#DU@R`1IyXR+k0R7~IY6mXQnIYJ=|Dqf4+{O?83Q*D35 zm~q?{FH`;v)-R{BFDCMi3*t-k>{7fQ)8nw?9TyWqG3`Ursw{KR7s%pMMe3iM)dT*M`1?|}%AZgc@ zX30+IPfbP!7X!AEjBUyvWF0|-nESBQh0Mtj(=rdU9mNVG#;RgmWP&-P(zBuAracc- zp+(j}^q7=iuyEi?+-C&NiI3TU^)U0@n#|Xx-UoNc*6NmU3HqR;Wl%dL zkIaY`kZ}eU*h+@_w{SA-$LNPRs?I`9&yRXRk~$gghBqUHqL4xmtMtVD2F!n`DBU&Y zA@L!Y3w6XoW)F{rN=O!R5%FX>|1Ypcy+BCeYqX6PttY}QV(d8A+D=AhCvAj2I9Ci+ zE_xz1LN~*Y8IN@_s1s-}DbcJjI5vpO#CDDjrv=T!AxN@1Y#t5bfti^9CyoyfXpL_T z2V8Sei{e7KzA*ct9Fu(Nld9;CL z?d=gOO0=h4Y+4Jb!Gh3(cScOi?2L8L!@ zXRz-XiI$JM!z1>gk%aITI}Ha2`#~+lD$VpAZrrCeDp|VeRi;hXLX+MU&wulyCi{V@ zp~_QZXJ}92zB_-Nbp#$k+W_m_M`OPZC+5?&W-o>zKXw6;Mw zPZVMo6>O;(y{(rJ))j>Jj--v{g0^&C9d>R#xu`p+I!;{+20Fvd@~tlHPH#Z}#D#80 zwJKsBYO=M&SD3rt(@+KWTkw{8Sk2`v+CyWht11NA9@xI&HVQx{ji8>XzDsLtBV)te zncQFSH2RmvZZP^+XpO58RW`&kpI(%5tDHnrJ71E)Kc>S>es<7(F(N@%94gfc zt}u%Qr8lQ*gBzd@RpP2l;SukoBN6k<1H@t7b$bS(TH|}1=7p2j`DH3Rgr=l(6PIL> zoLb8o5hMoHL6p-P+JoNWY5<8%Jy_)&dQZbMH@;n1k5gZVSDG59CRwN@mS3YieR+R+ zBAkSWPvs4(spUN{Y+l|!Sg;6&bFUYtQyI6H=HmrUtM0Jb+GO9GuVy+uB51tb7Yv*T zYFD3tL}TJ3oc#GNW=rR=aO>o4-~yYIy{l>KgSZEC^?)4Dv_{}AeTN7(PtHQSsCppR z-O&ueZ%;ojbgn0xqy?c1=D}`fMTVQ+(Hf7#GMidk%E4&NTj|ys)55Ur?JSdKcj|Q# z@lkkIq~gI09sUQhXE1Oi`1G%+0*FVX$zZ^K;H)*Biv-5nT~_VsJQLwR!63B8U?hW)?=-Hdlqq`a)%WG*cKqMfqu&U6`6B@bTa*hHb`MGTvKIJRjs3NL+*6oUu`f zPz-+a;yzVqgUnl|_Ft%7(MqVuf;hXE{lHCF2ZJV3dw8A0ZK9=1GTeu=CHDQBU?IYD zYb`v2rzovi+{2bQ@h4?87jd5uw$%IJMg@8LZ1vzM6o{&c7{V%n5d_#@0$C223kja0 zjv%e6ch#8!Yiyzet6(Ps>o6M6;8nan=LVmWkAUisOgL8(UDj`QAml+b0wtTWQz})) zSJ`rn{zz=D(Z4h{djmEwSX!(^ZPaMhTGKdHXyg77DUCNG*u3gne57pNGR1|dUZ|DD zUz|F?3wuqfM>2#Z)dh{pi{q#ASe1LBs*PR_05B!hk@A>Ki}d9}v5yvdfiOihrQ8wUSumgQPT z^#CeUufkXX@5DLrvx5#hRD)I=NS3K=5*W_V>qWl{rNnBGEPPs!nOv=RtGrjq3z|oz z%TQ`338%qxgAOAc(jbx<>pSsBsbK8L>)Xq6SeSZ@BwFdhWMPA9H$=OVZ%8pZ3SwOU zve7>|_N5K7hM2X<8_siH#wcItPcL%K1u0ta&UGs3R;U zDFUi^?@j0u_Vu&Ua)bjE8WCg%lxXp`R{m?P8%2g!!Sm&i8ysliZz-Pe)W~iKi$2@- z%_3*UuodHBQkRe`Gg%(oKyxZiY$9Kkf}%9HjO|Gs??vP=@Th3JlaO^YUi*R06`J)L zM<&jp6-PabbnTBvoEC@yMN~q%Hte32CG^+Hq!Y-3#Bck`o&Ye^n)8gAcjrS3G3;f# ztlv78_U$6c{iV}g2vq6cNn)6j5UD?NVll)n<{W@3DD~vmQD0afGzl}{o*aCRADki_ z=2bm;e{nE5XBgAp9!e}Kj3yT4)qV7PJvnnErUkw1#M->mWvgOe+8O_dh*2zSE)^88 zHm|BVM?!u%g)5yXB(SvQ%{h1(*lmIK`cKw|O268HNamNIhp(p3)}H)Y zPDp#QH5Ayq^3-4%J5cMD$!OkkaoPKe-}-JTT@VzuHovho{+xMvA)b$wYN|zTDK{_A z!=;ipwz8(>5Q?(SiryT8!!Lqar~p8UnO`j=uM&6I*a>7SB%*^ANS&jk`adDWz7Sx2zfof8}0FuZtes9;}u zB+1-Zal>$baBaxDuX&9iE1ln=o-T=^!RCgr5bsJ~CbW6gB=GQPFj?(4`p2#G(oAxe zKV8Tn{kWAQX$9i_OdFVjLG*L=sG>-tI9wRH1Q$&*H~5=?sf z00n0WnNK)qk3fD%dRC{TQE?y+baCD^r9)P~=SLLO6W>vFO;58*F`ox*%F>k6!x3eP zc{T1$&hc9d;0GDo(7-vRvd2`T@-mUcE?7|-H>ONK0Yq}-H>J~aChwpa{&C^2T`ni| zz*%QM45LVV0&)-tQ>Q{NTp92^7BAbrnT{X= z{9VAVs&sD53A%Sg-2258V;u3+r`FgO<8l;^HMYd#YmI#r=S~9KckScO`lDlr5YJ*H zTi?`7<`$KC)kJX=7tUgxcLwDBKwjd8!cf(cQor`?hg6AB>D0=FrBh?)RW8VhP1ByN z)SlFH0!LQ*%68G_C6fTCp&&2fem+vRBmRkKB$Xxc=k(;|r)@Y%0}Wnp#Qlu=W?q%I zCiOVHU(Drsu?a?sn+Gsw=b_S!Z^?s&q(`@$B9FqBJoJ#Xr)3nW#N~ydM4dP7PTb(t zlMfWb={ATW2Afk+3ssZm9Am&uE$q-@f_UMx1Dod;oX)$GpGoCu2*2&EynoQJ>*{3a zoZ^Vt6|5|YO|SfVPV8Lm$x+&q!JI(%%5kuSFHH)rbqC$g2l1>Ux5m8#4#{F8PY=8VI@V4ed8Ja-K;lqb{X!#!&;aj>ZKK?0ZXiqsqd&(KwQ!=z@*^8i? z#a%onx%!-sH_EUGHPGr3#5%U+M#`Q?w}Uk52@(;DP87;v74K_x_RR*0!>X&5ktlO# zmEzeP1rG74R6Zc)k)ZLcZFSRy+?rG@s)+duS#@ktn@C|03e3*a8spHy20vtI^`9bT z_u`f)O#Ei@b@NBgI_(O!s3JdE!u(*Tcut&)y=WsL6Nwiyyej-%DU2D=c!%rQ?BN9R zn<^_3*dgnGGaw`s2nTI<@3*@soU1iqFLm{L9%O65oe^%}+Em03Ncf~gPHAW7B|LXy z0XAoQ6Q0}EOJTxui@bz$6>16rPWHPuQ*dpY}NlQP&(W~Yj6k}hp_|woF2JBV+Dt3<`-hr%Ezr=pxxW7j1 zQwQya#XN8`!r~?-DhW$G7|LP$7=SE~H0T%rEt}55mQ81YbJ9bhyDkeI2OSDJDZ<&H zfCpc7z{})0@Nt=f179eoSpdWVRPk$8P4*5(N=#E;;=Ie`upgiM9uKzS z@x}&0gFt?wmMqhh0#=h0PTsd*lS2lcL+|pf>WYJ00cC2+LrF&Ku@*@=<3Z4k@6y#! z1HMbnm)Yt|r(a~xO`^ssNf!ar*|t-Y`Oe|QKy0%RQc&v8h?=9KfjzMc^aKlRn{_^f zPOx^2NbYUce~}0pm&&~$NzXK7ifEu4c5>-SK}EYd6hM6C<_M=<>z^`Oj3k*G7N#-` zxyvde%Z#-Cp}s%T3I@_;8$>*}*5a{_4bhZ5PS`}wwZ3Xg`+J=Nw~gilc5$!BBVGAY zD&t7Tcn~`6DR*<+%e&|>X3_gVDM4CAw(lkKjiS9|fHYi7ehib9a)?dYa0xv1kYhY| zK1s8QHID&!cPqsnt$usgt_PNiBC$i=EUeC-oJTG8+^^rP-j9@t9;JJwN>$ z4<-AaP5#qrU)yC(0;$ZBDYK-ka?;jB*)PXZ=Ze?K%?i!Ktb-ew40db_8Q7VV*EtTO zdUh6LWukK?5E%5p%-dPvF~TA|IkI*G{jrh8Wn3>JB}N<@nAM*td3w9`L)w-lniZ-u zc$M{GEz?Alj4g%}{#i}WSxk1qGl~wxM_gCa>p1@eM+n3+@v-S<(TCEr%<+pqQ7xQ? zGQ;jyC|j5B74kB3+(IwtKkA%G?O`f>Qqfnj3f7$OTvI!j;|gTIK$q6|JB8Jn9_vO0 z_@W-;zA>)&S=##f=tfTy!#_^$B-!k5xF6oc-c@rjBk6M~M|wHubj3;$=AMofQ<_AOs>}JJ5>u%(%)41kNIq1IvFKc1K))za8*eVg&hY`m|wpzYQxnde<~ z0>F0FV=72u2bV~!IPY^z3hyaE&K20W0xTUoB(F?-BcLgo=QC)WAQ$vR`^$PY!pZ4@cA({mL4nip57 zdCG^p;&{{ayb!lpWN|AY_dYVga-|DRmxFPw@mJ2*&FX8R`r5DPFlu7wmpdZSrh4hXG*R{@B@?OJgoIBda|NU)=bHI zoUCH*`Sx;vs` zPpS@9wL>DBnYNtN0#XtqD+Z<19QA2O#!3`2H>av3C%Z1K->_Y=GO9r|_0?TF(ug(M zsfVgD>2Z;^IabF9Wh7QDV{@_5e`@_9uF=vT!SfDZzgBP77YHt~taOO48%DIb^uUh$ z`infoEYMh5Eqxxb9)of#dL0(3HGTkLB(HK?r`|5C7LpMKO)@-WK;T8j%OIznZiwbB>UnP8=V#ywX^ z#w%pd#G^D3+yFp;7Y+X%**j9Ug~Lnk%jW3BS_}vJqIQ=_yHuY?brm}Bto2{Fs__T8 z>m`%(QzwTF&)35W3APj?m@{JQo40Vp&ghxSY@oCQu1}i%Y^G~yrc>?!%GwSUbZPtE z`JSM$UpOC{HJjhnCYC-NJ=cy1Hhb%;Dq^GT&FVg(_S`i`KL)?`?}%Bdy1Myqr4=Ft z)m|;AP?7ZW#NlI?Tw^Wh|f_hvJC4dygPAxw|6lgr!oKdcOn%DRBs|th9xAZWd^SbKBpPvt@oi4p4n^m-7BH#T&!dE0YfwmPv zJvr9_xZ&mt8a@SddBG5X^FI&lR@2vs84pvpH}Kr*=JYUg(t6T3t2Vv*z-nBnO6}NE zd7O;h6zmPVa$?uX!^?4*Sy;-w*#D+hP*|`1P)`;;LRIC&r<+@dCU=5$4=m8#=W_95 z9$r6TS8#2ZQPdPShq=FYud1yz-Ugeq!-aNd#NHAyp792bt!@mP??z0FA2Vkw_-1e$ zFc%5V;5y)fhG@XskZJ;5K~{qJfOyyR?QP)%$eys(X!`_~u7!y9`0aNY8C#Pqn;O9) zHV(3XM>dH7)_*;5Za{8E&zB~v(*;JqJMNKpY=6-}Hh^_{2F%S6Fae{5=^|BJ@5~Db z;0P59g7!1|nqyvOS9?e&k39|Qw|(EGD!0KUe^x5=>4YiXF%YJxZn}qQ55!Upy%(K@ z<~L{lgng+3LFW)>Wk^rl5&0K-bTpl5L`;>+E#Q^(V$QsaqM_u^Eyz6-cq3@0gW47Q zgMs~Vq_Bar7K}V#VNjuQ?ySq&@jlx>);I}-OG)PvYaoGb&st}{GXTOlRh~YW`8{XK zCi!O&8%jRv05ItdVe*_@YgZf(29C$6{J#S6FL59%7jaI(AhDDH&{8WCD?)$#0*U1U zif=ejaG`mbg5nn$D88S>9m1==H>n7{S z-m<4;{-#Kz1XZOyO--#9yrgMw?PQ#+F}XR?6Uq7(IU_p z*UZ@^jji`;M$ZZU{z^LEm{a1HU~O|wvH0%FS+3Y}66jWgl5kevkUa$Fb1ZQfV^SBg z)~s7uhAeXr{66iM`zERZg8MVJTQ8v1(eKDRRM39wpb=*f=Yuiz3j0JdaH)}79jJ^bPd-8#dQb7oZ4CAoR2{*B&Yq;uo2y@+8FZ| z&34nQ-JV*`uQN$pq=D`8L=KVU&RjtdF$wI!^$qlh=Qw+LyDFS2pxOY(1!G1jS^{~Dde#<9}X zTh;FEOqiNIfN*GhA@?=5i`;6IJ_CnLzdCeZm;2I%{XJa@R#BtYy#(Fi08_?wT%6?G zN8}q53FEtj9)%%X@jGF|;@92I{Rlhb&r_+EN)QjC6Sr;n9EP5^1?f3rtY%N+B&s8Q?}lkqvyO=}aXDxXS++z+i%7g{o)&7W4e~2kZ8xiz11ICtT@a)-*m*yU3z*{=Nj2(#97} ziWm#jI2HEQwIMUdP)B#a3U7HsY_^}U<6QPH`N6RFKJh_Az5^He)_fo?j;zw zh@gUt2+okp1-!bth#+0e5xU$yV6&)&Ps#-YBe`H;R`bHC_W$92fq$`YA~b*Ib^&%F zE>!r`?E){8MTpQlJRni6ajSa4eYlkuxm}>fdS;i%iRaJzu` zVoHGjGV8n4Qnw3;Kxs9QN|dA@uvYS-CyNe3N`qGm&={u?;>Uo9I@p-VH65YTZICi} zv%tkpyYUL^T;4+5EO0h%kkdNyRjEnVspJk^EHGRpP8A3?|BsqLp_1yMJD&4*Matnt zEF})9GZ#)x%iJsQC@{dU(;I~T8|sCze8 zyG1AOj?}ipd5hImMY>ma&++yK-CC@WV^ufTU+RxU-Cfa&ZQMofY!^9?!vuk08i8-X z!H3;e0@8Arm(o~<@<_EKL~0Rf_nJq|Lj*lNz@F4CYw!}rE4LjkRbiCiR@v?34oJWG zQpoHQk>Cdit{Gem*+P}w0L6@Rhf`1;E(NGG$tfH&5ybcVbQndp_T|1j6XbW!L{L z5{)Z8}}E{XmeqjG2}{hcnqYd6KY8b0_hg z==3`dGPXA}I?Psdn8MBJeAdt7-HbEn^~c8I9Jv$g4tHbS&8T1>TH}X8vj{AB8kt=EsIb%i8orF&A`kcVoopxh&F_8Wyi|68R+Du~Bt( zb?es2VHdX>%N@iYi|=tk^C42IYA$M>dxn28V4+DGYHJ2m)ms_?Q`QmPV9OA-g=r$63(u%WQjm72$7 ze0Ht*G8#Mw+($ej>mYBcEOevu~(tx*WziE6D$ESpc{vf+36xm6@}2>cse zIlMZgm2b_sODzAo8N^7&sr4?a^S{NB;0ipkzgCP?*q_f)!xi4F-BV2~rw=afrTkX> zMyc>4D#&IrLlOydA|~`vLP_yH{^J=CSHj2YcmO0l7;c>Yn&|Iv?+l z>vkfjt)1;H{nm_c#XZ`_yGx4JJg6=*iBF(6Z_Ec&+{x-f=vUE9TBt1{aBB9|UhPTc zPM6TqWAG(!HF}DT*5ct;lo+>qhujjDJ^YmQ4HGKH`Pw_5EA~aH8T?~>3-sDHt~}`s z_dt|(V$s{e^~YItTQS?&iArlGFPV!AwhUv_ve~YhALlLLS&Po88ISOe#h9QEBIf@3 z0M`O@!p0Spjmg(R%Tr-_{P2I?6 zE)41(~C3dM|P)!0etmm?S)~ig9%2R3(F^1wW{Mn8njlaS1+%r9>fqN3|z(K z{=R=hJz-d{-7od_&M_O+kYKyz)!77>&jwoxgh)c=(0e0?hOV{I^5MZtIXFTc6&riw zw|NGeM`r5;xl}diekGFpYEC%0xG&TkDjyzhJP^A%TYv_tXdreCUTrna1=(!s==Nr+ z^h=ehU<3NY`Pq-uxm4;*qRzO%I!=WnRFyiHW~T*j^4D-fM1-5JtoF9gen2=YQAFTa zubuxI(M-*&d8bgITl>y8c*QKbdo?S@{T7|}%k0Xa8??rY_y{z)TH`}VQ_NRUu;I%E zVp=Kp=A}IiOUk{+BDK$8)R8}k=I+oFVM_(da~(Hk<03&1#-SPGwZ`}5{nBS*Mar2J zqflxGImm35Zg+7SuwrZ^8P1VQ5DC}WlAC^j!+_MUD8k4TNHQ`+y9F{dCsvzAGGm;e z#u(=gkngQl`$%2Y{jbGtVq8b=v+bdS(qrQr?q5(4J3Z7qIotBu@Pg*h^x^41gumG~ zLO#bm9qxj383g0>q;AW-ZYj=ae5BQ1(P~VS74Lb3SK7isHX69o(!N#5GDx#Z2Ju+! z;43#hTyUX=A2Roa%ie9ce=#0PyTPnjw;JVq8-LAScSGDubE!Wwcy+pv){LWh4~_-8 z`co)iZ`Pi4&#L^pYxy-?9`v^Mj?mr6@zd()%APv0vU4At(j zlsp@LJ8IrJH(2)iZVPwX8nZ(rQU08rcoxcEdcl^v<(t9}dPH=#eLW;#(FgD=6>zsf zIDvL^Q4b2+%x~KEl^H~G;ZtYW{dQt?xt{t@$~5iSD2p>zgd_f`|0_W*Rs?y=AVG4t z%HK8XhbGS_vo08TCdL7=8yzxNC@&@Q3Us*`VdbO{=6DE`KPprlAI|5z)PK>f(B?mR zX0er_&Akq7f^qc0Ex8%ueBeGsk|S;3$M?#c*7PF^K%kCr0}ai)_p?MAP@}7>n!lI7 zdO=|4+Av(oSqDO@Yr`)ONmgZNw0U0nrRk_paq&R?IB`{@)0Z$+dgo@@3t)h5>$|r= zTY^A(e{mIo3DVQ4>B4N@X33L)Qjh{&FV?;#!cF?jY)`@;2I#sF-*HgtpwJ<0CQ!(r zCh$qj8$mw%=D#z&$4+AIcnuGmuiL)VD#)|n6Q5xHmBSKeC$hTKE1cSu3SyTv`tOYA znQx^32l{xHPpNas#I7*jdXyA<%&Nhv(|=2ObuHwAfkV6-uFu@zi&%j9K{m?4T@p<{ zDBIin-1uqOvNv8yYZb2&czwn|v#CwMQt_(njX&otF!Qc=WpCs_0}^;IYWB$`tI_1l z6=V|_hAi+lcTDE>u^^*V8{WZjl>Hmc~ zud4Qj{MbT9;iS(A8eio8K7#Ij)>>6V0jP_R@5p5JLX8(S|R^)bin<3&Qf2Q-fdM;3B zw|UX(z7!dZ8;RvQ^HOdplAFr5@OL~{6k5CSHg&GO+N5IX1s-JNK|#jR1+l7Cqko|# z8Q)Yv(Y7l+#lF(J3MahWW>{jb_GDYyt8Ln9O~y)rxE9YF?oQ|0EL|rSp781D7ulSM zx@KVJE7fbc&mV907pvDkYj3xjm=@zQECfxjKKNb+r~yl|V>ud-TmRo;y1(qibYB=; zJ0zrgB;B%g(R2J1iRd2X*q#4;ne{PijDW7)|A%mHWz)&}hbyr!`G?YS>T@pKEgOmH z>1g3m!MSi#7aUD2{VJY&xk!ymv8psU0p0NDB{<#kSTGRF9VNAp|L0lZA7gh`7jv*A0o~-iX{SMpf8n=K!@o0r=sbuuu`oJEe|29ViRx#awqL9&lx8u_+ z@!Yj4o;zRoQGeXIi`3{}r8TwFP|I1APS3TwFd@mG$H9KYK0?Iyc76Aev>!wW0@k!E ze5MQRt`L7kCm+3^Qisd7v+L=p`)DT{)O}zesC$VM)QyI6@4~!mh@_fZ9!y?yn2`8u z(pP5#xewf19UhTJHg;kbtv{WcK^UYUo;1B%{6j;x6$VrC2PFkTPUyBduQZwo+P32P zLLY@I24c6*S5qskaR29)fq?C?PQZ4t${P}}t2&wPgk`pVIM41Y*2O-h)C~|XSs)#>ramEx4ajCWvW0r@? zme6R~dlbpWX){LLlK$+s`iXI78+uHIHOn%e%O{D`4wd??3y`I#f>bf<52 z4x;$**dbn0)ln)#D3V@-my3;s=YC4t$DD5SPBmf>P&mty~Xa~TEJa`D33TGJJrR1s&Z z_V1c?L*r~ka1bY=zdj^L{aLA>bxoYD2pEG>_M&#^BND6RcWLZwewT@v;P}e;ql%TM z9|<;8E{hkiHA=cL-3(_aPJfGEzq&>$xK{Rz1KNy>yCkG(g6kFvTN|L83hX(Ot6G8mRfCXYg@Ff(rQ~?S8!`sgy0Ie;ZjYlZJ!vmu~op0{J-bk z=b21Gu=ag_{q^(y{vEhE=ehemcR%;sa~WJG3uH(gFOV^Gq`*~lOM&Q4@c?B8DwJ03 z^E~v7o{p^5r?NCU4B22Yb6441;okU+RW3_dY|64Xj)v8u*Gzi8M>!<(SESc-@M_mV z+jm)kQTEeDaavkCyd7 zcv*PIk9h4jBY0cePdGc}9;KX&9d}2j_*L`%%+uBrKZV?~qEEJdrX%T#f3_~|^BKsH zQV}5)#C$R<7*~#pKO~Jr#z4;bWzeO`-$S@|jy#?gxeMg?IOlfW1F~Q5t1EH4zcAZ{>yl zn!Do*d3B%=tMID>F(0rYOw}909JXxPlvXx-9~{;XHOO9%?u>)z2w<-_*!s!+;Z5=V zpd@TId-oBN?HBrAjja{z@;FKM*v@W`?Tb++FFIgPyuTW3Z5a(G+DOFj2*%c!I6gm&sPu)rv`%3$%p8J;WdZ_xb#PsWZ%U97u#ii?3=^c9SA|t1)zbi1= zR^vw6lx8C(oErmNGnh9hBVC$heh%Td?&{Hy~(g(7P z8mdwFWBuQZSWDA|mt;46eN?WafeJ?JQQEO6R*2L+!KbW-h*{wX@CWN9fnspe^& zRJUt)wh5y_vN-|E*1B6{0Z`#tf0^t{v<|1qFnJhi-a&`c;TV{342w&{bAMY3u03^G z&2aV@={iOUoKQQM{YG|E)r&unHz=}gWmfIq5lvQ%P%<)Qi&VsjV%Z9_E}1aa-q{^( zyPU=vsV54_PIQc(K$q15N<-_hby=n8*ksv%(@YT z`^ywm-NQ`d>}6~PRc0SUpRayGHsLu<<+89@y+-s?!Nsf?yHxfyLf)^pU+HXY-dTN- z_MM&ZXLzQO3aXwRX;akGP)Cbpp3RC-QWb}isyJ5S70^JnZKBf%Da}qtN9cQ;J*{Gi z;B0#SJ({Zeil(Z}W1e|DJ`xyP-J7DSZkr#J9`vH9iree9rm7dTG9Z6gRh6g=)2gbn z*Z-OJ&t6a_;_QqG=n~+Ag9_ACWp9|!_VH(7Jyqx0daAxp9cCUiYN|Z*j?(-6J+xFk z{vuI0TB^$MuD3vd;ma1=P zPcKAz(&N%`TB^30#)O8d_E<9(%Ba}(?x&0d-L+LMZTr+%Mrx~CYP415X>C<`+q|?a zsZPBQ>P=gf-pssg&1R#+u+gQh3iVduUC<&p#-!bgwkkVx4539>@kFYs3cIPQdI(tp zVVCt#RaL0h(pDWilrB|O!u4I%K2ZY>OJy2u9}~`~PTr`ik{!^m@6}T`Jt=Gb!Bv-Q zbyb(>ZPj+6gPqyMB%qrnc`!<-Bmi;BZphQHfB`{vL`T=La-#J}PMN@&uEm?JwQ4$^ zB6MA~?~pnBOI29)Cj@iQdkJlEV4@AmC`Rfhv%febwtc_=!O)Q0_9qZgVRc9>aPo+j zs$NxCJ%o=Fs<8S2ju9%XHp*u?bTCS(zA2w<%I!}Xow}>Ax*VG(pV#=F&xd5%=$({_ zQj0gOGW#E+!b)=~tY&sM(5&q_hI6BBimj{O+UNp1>Z=g(^E4t|tU|{)Yw>F#jqcj3 z{B5j=S-a>hj=$|`omEkX)vNX@z1v|SC=@i>tCqCM5lnc~gH|kO(^Dtj{u%96i;2|T zevw4oK9|3)_AIHFI9M{Gy=tnXx~f75<7{}|HYGEQieza@v>`1RCd))kj4stxM}=w# zsrF&j78jg#ycVmS{w^(6i`GhKz5PU5tgP>F=3=i{&%a4(v@<*Xu3alFDHqJ@ygTo2yml~HLyoN zi`qP4NBeo%JU|@U`-m$U#u|4IzHmkPN+?rb4zm^~w@>OpvOs|-EHhf}gz zVR>kJ5Cm<`uy(rWkvHKW?JZ`&@x_imzSujX5WtEk_LEMrO~l0BmQCN{9-HT3WUA!l zn1jKO{D^#Ur>(O^;^oMCeRPs=HaFl82l+K3mKgzOurL9Q@horcg_$yhIQ#Isxp zle>zYDHmUguVSBeTdmXpNL@+6XqXZI93pA@MAEIZ{^duL_x(md=SX3igA4Y&y^N2zwh!*J33~ ziMY+t82jA)*pPFs297w$X+3=NF@XgV!EG{zp;Er7+7+1OFaAK&LS)UKe@4g=C!ye$ z!oqw>ri>52ujQgIlABaW$@`mz&yl!-4-m1|Pf3(_ApVipIPMD4;qjrpv87L$JEw*+ zS-s1~cHI}uYoxZU{f#258cG^O&aHVSMmKodVKQvjKT>+(Ge}`ibf%m`1);yqTqMj} zK4T;YveJBJqy~>T$OjYlV&yNkq?F}P3yC_Ul$<%DCWfiD#Tqg~8WFd$xb5@DuL(~1 z^#Sd1XQ4J9fyanAOAL(WDuY|}V&^7XKfI>16UEp^Sn5%7Bmo-dBqN|nn~+=h(%<|c z*SZY-AjX9HRjDz-aiJ{lEHCQC11Ymc3FtR#w1Bu-D(eRb_FI49+~XM{lkO)pkT}pC zKu_mB&?WjnQ};|G!{3cITyWwR?46IxSc$y9Tq;6>i7C$?+O%2POX#T?Gq{h~bbYgY z@!o}8@_Wzu=H=!X+@nR9SoYa6S>}a&Zdd_mALaw;%-CR3USqBsb!wk$Fd?$c(z*ZgJO4CKn1LyvCd zE9lu1~A_lJqhsi*}FsNpRhl#m^Aa2vrXxGMQ6#e}ra*+570)b|b_`z@SL`P^QwqFoi zU8V{Y$Qa=!bX~*{L2XiF&sz6NP%}i-b`23%jn;G215qjF~p89@W=ICI5n5pk)Jv7>LOEX)$ zki~kaGY5aXoV_u6L!7^Jujiqu;_{sJQm&pI2KMxTYgWVIz%X_Xzs{;V<_+}WZ{Oe@ z5=q}Z=ONMoPvq&Thar=v;g95^E|c@ay3D>o9!uNR{-L&)wV~V$;dP&xVag&`kP$ z_QWlv43cHmF747h0`quh**()6IB#a(z#Is2mgfof3VxwZC#B$#o{eO9moB^nwCT{E zfD;7SC3czy2<%-V)nU>>kWZ)6HV8X?$%RW%WATY@# zgvUbDp9A9=t(>>9Trv0TWoUb4PwYncChS);7D;;>F$&-Q##yfk4;6t?D2uLk7}N4b zlwa?i;HJY4bxxTcm#uYifH@l`u>OtoXMR|_)L+cGu^*K~wHKil|3iP~ff}ayr>t>L z;@?a;8F@{-AsdcYPbc=-)e2(G)&*^xHIl6OsPg9Q#t|Oy_Gr4SP=W3y8(H1xPrNqB z;(e%vdTC&i^)%?76gtFI%$cz)EA^y&IE=j~lWGP6iUQO92R_p)p={nyL30CEX?oJ_ zOzB6o%#2jzMbg19KmyU89ep|m9bAI3G}UXPityU#g$26XC&=a9pVo@7%13(s{2BIK zHE73y+4NSv%qT}uD;yClb`E6}I!o@z$lN8>?B#CTw*rK1npFqrU9X6ql$lUjzea|; z+=N^56~mcZc>YlA-M5e)V@kbr|-c!U+6=&ZF_U9RBW=FR=671 z9?IIVc8R}nZAVVSvjKPG+M~XQliTC68%vL7Z)9x9KV&^JR~n{g{i(3}waCT#j$rbU zJt`}XA!J6*p+Iy_{1>6;jQ$MR*s9q#W*({j_BWW z*U8zFY*btD&oOWvAo3VEJJiuWH0$slcfd`OiX`9ni2!9*J8~Hvq5MLgL2C9rP8IR? zRdQgW{23#EhRPpL{U=$$hMdff&?}x>c5?n7I)HZC&`a%coQ<_dgF19Xj+6|+v?ogovVvn4w9_vgQoKGHGtTB|qdh>e}B%|#|&{rSa#^c6@@d6V~_LoKT zJllS5)g7{4BMwU6+L`hWR;=}YX?+W;y()>)wBPQ_d@|U_SND8YdtXuU5CiJ=hZePl z60AXWgwz>+jXk8vuq~#}Tk|>bM5XB7Fy_6}V&bM*zSpSBc{hsx* z49{tR#q|rCny=yGKrob$gF=j_I<4^t>NMuGNUaXF`jEkO8R9#TPewX9fozitWN52u zTJ)mH!}7+pFIql!oDgKl^7^$eo)k>xVnz%8zndlJDxHDd#4gjc^;9d24J__AL3I{J zlZ8j5M{ienU;npYQYh!pn4Q6xgb&-J5;~~#oiz73vt*SSIF;=bU^HJ*x;tb6M)4J+ z^j0fI1xI9W$XU`pWV^g+XSbMmZs06wkCEZV^kjs+XhS|8pUV!dZEjrK;#vPwu|PtP zvNn&|L5wQP(;#Akg4PA9IrdpEOi6vWp+=C*KV6mVtN%Ras)_uKY_0zn>GhUb$C#XgCs79%uo<^bz9l^Fg+6P0 zkzCA@`~*kpv>BDG^tbF3Qb<9_rMF{F)&>~Y_F0rZu!@pzK|h&4)t8 znnHOR{%$OFt#?c}1q+_jCK|6GhUD7!xD+jvkXyW)u-rh5ZONIi+sZsuw;49LvgnF# z&B=W4y4Tv#WxlrAZu7+n*&9naF_1Ryt9$1`PHihPR$HW4OMwAJ^|yYtp<*SF4w>HypQ?1Xw6K*2b{e%eZ(gGp%9@*K#HV|)tS9v38 z6?#p5M|NCC1S!lD|lnbb=G&6jm9m2FO z|1J4Hi0IFlx*AaeiTaCu510{lIxBQ*GfpBn4s+^x>$~C)sY&~WX9J%sWt|(I z`O(AQXphbd{hr&M8Dp=T$(1-6>m=aUbS#|#9c6xGlv&-QJmbrwr)avT&b;tHG?u8DGWYjHP3}*Pi2Vsu(+#OQ@>`a~W0csd14u&hrowoz1X4+WRq3 zleJf@EnEf(wTLd-$C35yd@_^JYxa5`-qW7tFPd>+=# z$Mg-{RW#$c<&Ek7`Z(CQdZ+XX*|W}=DJ7@*i@0HSi4;;R=HpEsvsrT9vJUT;e)~OS zni0MsSORjdIUxE55;=Z8*e=0IM63T0*6Q|e>AhI}K9_$+QVFX&dLe6Bn|IQs>wJ-| zBotP(xeKGU&>Rd56gi-N*)SN!(YXULh!u=7d%Hr}#+K>PArA>v$u1f?S&g^KiAn5o zIWf7cHD^Zgpx_wUlK1gE1OcM6GfI!@3lkmoA%Z+hlDhBNvOp%jXDb@>}V@1N_D7B(R?s zdU<|rg)86f-V+^Gk0$Gi}*&?0`6a2LTD zJI}x4-DL0?;FE296!;Kh9p7*`xE-d7i_XR0WBTtG`tRrZ?`Qh&r~2yHO~#8%uPK1HsL%_q6bS${OZwaRKaA&}0M`Jw0AF+etMWz42&;qb&| zAE{LkPg^VWqTnk`!Tm>ITv2co4(6SioSWHlHIH(eLdW~Vgwkby^HIC(!a$UHo&iwp zjdsdkEMuk|bp-l3<=>SI=izl3bSfir6Fy=^e=-CRHJ*W)p`2=RM8;v@a2N}ZiNTm! zOOUeYt+begR$1P3&}{+ye^Atu?V5*E8p#(`m9y< zb;&1akruWdkk}f=%1SC5Rzx#UJ7+W8 zWRbxP9OV!KG~Exr1w7AiJJa~w%%`X*dl`4H)&cJVs0qWhQ%12|Oi_Q6urY=k4K4ZstiwB^m>oh`)LT*Z%PWU>!~~LzRg8X%B}UY>>}ZP(USyDH zc-Od#!V+6$3(r@!#>sM<8`HbAz82EZ35W)lzl$XbT;%5&$#BjO)Y0eSWpzDUBFqad zjF(lI*Wc)C%@Z{)q3n3>IWL6kA$nbW9atU>zDQyt+rGgl92wsx&LZWpw3-LE5ux&= z#>9J4v*WY;>vq)fO*UXrwuz5zS$yY(5>0w}o?U%0GXLkrCre_feC8&LU8>l5#V(C( zWr=;O*jr+6GKK;OY&*pEXz*9L>nuqD=@S8-ddZ~GB(t5$Jih$UU{h{1igCJEkiT=E zQ%Aaj{Pk^75tXDX2)meYB{>yT&{aY8ZEm5dCY&o6uAn$mK^*dgllY4DlO2ClDA7T} zQbDQIMY2>7gd1d%@gdCEKlqZa9v1iA%d6{$+4E{sKh%X(OSqa${p^USpFBG~q3=br=F%riMN739XU|CiOzBh-&#iTr zmeq48*KJ+%HR=5qBwODwNUBw45U+K)LDH;?4U%rtyF`QSssIASbYpqZGCZxPJEU1kw!v7Gs`mg2EpGj_$I;k8(hX0Yq!BS3%7<|9r)doK#c!|MV1z%!tOYl5{cL<(k@S}oH zGq`Yrtu%wX1s`s3{Qyj|!BfRP#^7GTk1i1+m?vf4Gq`@yrPbgW;^#$!%fj1gF}U1; zwH`CLJP2cLHF&k)KR5U)!EZBoo!~bbe1qV12Hzxjz~HwDUS{wz!Iv6*i{J$Y-zs>v z!M6#XVen?bPd9jr;9i687krSxHw*4I_#weRU#!dCDtL#%Ey3S0c!%JJ41QGbXABO< zR9VdimuI`J2MnGp_!fhw3Vyr6y@GEtc$(l122U4!mBBLvuP`{QSY;I&+%Nb-gBJ+y zH~134XBxav@N|Qh2|m`~)q#8tO_fHx-Y=jmH!d)QimkV-sy`(y(zG zn-3RBu`l2S!K7n1=xn}aY%;L<$k;q-j?C1ieG>kSq|d7-Cd4K!?{Yxc%Leb3$*yqKHjM77v|WJerfgMZ%CwH-dc zX;9zg>)!74EMNEOQP0&+vj|3sBTZyy@OQb7INRsE=!5?H4hn|mx~V&J*Y67KZTI+x zvEe(^xeLytta8{ek7tuS#@;XwlMS}Dio_aWRp#ELByibxJkiatelP`ak)V~`YSWy3NOkh&|yL|$KJD&j$KjJV1E{YqKx(^^OzN!8*cc6d$ zX9M8|1H0p*>bEuoQ~p zj8IY|M?0Yd@EE+I*mdC1Etv<_p2nk!T2u24n+brBN{gG97m>yHhLV=xsr?1(RnC8M z8)L?jvp8~g5`x>mbK^PlEsjIKCuxPAM@MjbY=~<}FJ->P!&PLtFIo1iPo)XvHR}9k zzU9$u$?Qg*%eF6M19?>Mfc>7?`~A`TQ2|)fU;JD|-i1}v96U+$jG8WH8hyDYSKOvcxr9gL-+`{B zrr}5Rk^b`&iM26S6l0;`t20F|H~HbfH}T?H%6-PMSUbKcFR z81cflrNl=)>t7PGG$sAaFZ9dT^pfu7Y51;mt)`S~aL}c>LozH5*XTaSUGu-5u6_8m z4>)+S*Ai)G$|~_FchR3W?#W^I<=TCTohiwVzZDWsV{9s(&}|)x^$5}rqz?!>{o^Dwa$C!grV3o9vo=$Lgp%IBNkB(u z%IP|(R#C|{QxZC>^JM|BSK;yb^eb?3@h3yG`C#LJOf0_67x5Bzm^%VUW1|%yg#(^Y z(mIJV^ZCFu-pvw$G5nm0T(4m~j>JQm?O|YN%7eBC_R#YB7=A)YBI4Yc@*~?NnQI5I znNW15z0gjY9ahiv48usxvYph53A*~8(9C(zhxUuAG_s-p91ME#!0Q$JSe%fv0pf`Iy`k-vUY&tiPqL?X zvbdHFYS-%QRTNw0a;_E}ofZE#A@+KUZ!$4dp*1|c4o(ssj&>wkjNm~aX$iNMcV14@ZI|{H zteO#9yn&@U{r+j|$KTficN6^epS51~xY&fSu_`(9-m4Oc$sEe1%lMrkgUjW+tc!5e zgK{8^X`#jX1dbAKLcU~WI1ZN@hgR(%0-TSU^Zzg(+AFW7aED6TPGE$v?$2xWANhN3 zW^=8_`jB8w;_b6g-wYRiU%+k67$s$3wB$Xs=d4%s)FPu#V6f=L>+hd{RBmFN6nK~Q zA^ONfNwq$`Yr+CA|pKr0h>E5yX|AZ((`Y_fSPl*yW&O<`6hpr$o84=fePl5_C zaAEblI|_9p=={%tjKW&}Qy)B05hJb3$n&TS>r9<>y=?g_8$~(U+kv0F5JIzmL=C|Y zZ)J4f@p-JT{x2itfeVp|Ey%yJbBS+bz>^`fePLGA;jI0~kn)bwvfi#>U*yiT&fXvT z4rhDNs-1*Z?WeU??I8oHfTyh&-;zr7G(5#-l0>GH$oZj|R=mf_>Gl0sTV>q8Vl3wn zdnv2JW@#f$u?hH`amgUb2{IfW&n>$;Q@%~zNn~pY1t+^N;^&?Q*%BichZ7V)-sAVM z`bpKsGH=pT&i!vuH0x=%)GL8)31qNbEr*FT7eaVPc5%> zpSU6JKHQejp@j%9+xp|%wukSC2Lw+t^xt&FptzLtz_Eqqf~G!ooqABDH)4e{92UxX zMrX>|0LWzQKOtB?ny+XZb^=4+M+5=f4>c;9Ej z7tu5vdBuH+=f+sr}mV#cafb!(7!3=m#mFD z_fnX*eH*epc{IzneS5Rx3ZQ|aZ|1dqqFdH!WBEMP_8uSFwjBftUrA^ogl_n>2W*^$!WUD&UoL(n6bH?yJyA+6E+Oy7Cl-d z*t+q5LmxrcebPxks(H>oiW7E!(|QSy3YqK)OrF`)cT>_IS*7|zi958qAz7j8nwEO^ z`gOEPNKGP&=L73boh(8E8x%Eb4b zzCsCqKgN_WpON=OB|MFS^ekbfl(0Vzx?I)bW1CPw`Y4B_T@^LCdx;WhZE~8UMWaMK z%03I?P-P1wuh|pXqop@jPoOUXq#rLL1;pD$P4W*WphWe+QQnqt>cn*J%P0?e1f6Rp^+8hqunvz;&Sx6HQKa3hu^Pxm{_Jlp?Umh)V2_!_b2+z(u zcHOpiR_segNsE@x6z*V}0y7Ty&>(SrGz8JD28qn_-zOuCpD~#2Ct1kRYrW2tIXVZ7^q;c=qU}w6z5VCR3nEV6wuJZbuMb_Fh^uaF_0jc?m?bbGyY)f%N3*m#X-rb81yl(n$b5OyH4h^jj z?;S>*F8#NTsyxwu`zS6w^xr;oqkHS{Nd33A(yL}}@yzu+)X;Z7uD%@>8n5(9>nI8; zWWMo*T3Et*8j8u8h>G9nHgK8^|8CpAX~WxX*gzIUq%yV^w8t3upxNUace9#R_-3US>Dy7DPR zH-)(8{clrsI!>Z{|SY-y7{zE zl2~;tT?%o}JK8P^aRFh4xZp84q4Rh&3#GaLe^7{f&ql_}6Dq_-9x>@zw!oTrkqU9s zhtdxIM+$LoB3j;6PL+6iQ;54@oX!^J)DhX;)xaF))?PH z#uF>V{p6=%Li-~X;(l_LPRdb;YgD_+(m1RU_xThA%r=hJ8gZwykYvIM#QW-x#-WCr zrP-G&$h~>GS!8~hg4|gsU@Z$w;;*A1cN5oL-cM+6tUJ4cI~AQfkN}=GnIX}UEB2_!we3-nJ4x(IQ1C9W+|zKfKvd)o z7Kn=6egaXE+eaX(9OYh;s5dHBKPasgRLU>A}1PDexrbo}5QDqzeS^fby<-qp+v|cr^tiSI#wx0<1w^RUtBPDx8gX9O_ES7s zPhJ*YIbNG>tH}N4;mG?&EYL;JRWuG~upaoiA1cE%;+@V$9agpqUSN2^Q-L6iU zbJBmXKT0Ncwkei{jHg-6x4{Sz-MCj}&dMaM+RARaakH`NZGR*eT+%3S#Qtc2eh0L$EcL`h|cCwTyo7meir45qW_ypeM~7y_JZ z!o4-OO5no44Mw7whm8*g&6N^i6-SLi^G4f7iHoo3`o5hAKhi0$yDG)Hg>ww&z#wln z-Dp=k3PBe!lIOQtcTY99OMLa;9Hcz!g{{VA#ti*NEh@III$w@_28a+m&$Pf=7e4g2 zzD+Ychgi++4r?lC-P)rnq~tnE_!fw4nd>A+^}7o%mwhrZr4v)|RLez(rprgOeS6d= zO?WMLNMwkL2;H`bZ@5+L_4@3MX8XmI5|qfxsj}$AfKM?%H|l})Yttw(<>zSf^}rqQ^MA}coYYVK(Q7>GhiUuc z${xCjvd`w&MIU}pfKRhb;XMsMXINmy2i-}^sUw=|1pn$$98FRi2rB9+R;a;6~fxl?~TJ;rMl$xRda5T${3Oy zd3HcHr@kNhl%wU)@8x_Z#hQLecs%;xTy`Fx5_w)|6e>%MdX`6KVIhaWG3nCOEP4Zc zd-0UnYP0|^pHUX&4^3ZECd?_G@4IEMKXdwgzJgU;s0@9;twqtX(*89#du}e1&FB~W zxU)H|w`<`#p%2|cPDbPn;=b1QYjjo68JYvb{1g7l*k-L~rzh%nWP=ro;f$?0Xia_J z-#8hPuJSide|3d)9@zT7Aa5Lph|XG?eXhijZ9Vz`F*e5TE`nKf_5H%GU%lG8>pso5 zueQ!u;?O`358-y-b@osD&mp!Lj`!Y@q{lS*-PTEUI?{PM<>mmKq%`PIU@{W)YAs0C z$Jc33XWO2BVmwWd&(H_br*8Cz`s7b|&mTILd*BOsAgwyT7?G^zK+Y3F`h3yTwO=aW zy#Hbv=Bh?;sNA5NJ!4v#r{NBKfF^>lzq zb$pN|ZU^7_g)Bk$*;kFFs=e0BnN0oS?Gody?T2{karT%c2aoy=41CE?U`<+E@hn+O zlbdqBhBeV6f+J~4DPrg4v@DAOSKpi)vqz59DP*iZW$o<_9b-s=3?DLb$R**>0pE6R zH?fFs=9V4@q$r^4b<9J@lzrO!?$l0sSMxj<5-Zb>m|=n?NT2|_D0xvAH7I0QtdNQO zJ(_tKvOPELAeGLPRQL_P-^s+nJ=g@#ux^GYXpUE{ZwY%4mtMy` zdD-kT#=b{X9jwOZtT&0DvoK!6%*}kuA9^XrlfM`1d(0Ud7u{|%Ik|RN`|DOdG1q6r z1{16?I=LhQ`+2%b^zuJvamYnhSH{cONPldZdayI)YQEYRt-cIG5jmdDW*H}iH2NvA zXgf!$iFMgbydF8^ABJ4ZTij0d*P{@5ob|{8DVHQnpw}3AsEltK@!{1nR%n)CuKi>d2T@PY-k9ymfU~yL<&J9ht@~pg zsbzbf*zY^=DK|Z`I8|Q)#5N!|KM<`AqzObvgjXQiA^fxJ@?7pZ4#J-1X1&T-$G6IG zwWs&6zh2u%wWs3C<-V>x*>NWm*ksh9a3>h2b<*&_(vjDOHIGxx3MDOMLMqg4%m2u< zG{pMJd}m0u7SG_YTUf2_@uAq!aCI78P`uu`56<9JF*em1t$8(4-nZr^QMU)K7yX6e z$OG3;c^em`w#}qp_VU1WdywMw^1$`3MHICA1J`3eavIco(vn!eGQfG;himmbayZOd zF+21mmL+5T*2{mEFA5+U{qO65&=u9G-(S%t(!U9u$k=_u#4Agc&UD^ zGa+fiXkX27H zll;60td$0~ShuqcVcI}V-QM<8lXBOjVC{hjqV&=bm-9K2MXRc$TmK#(B`Ad84-00! zBIKOUPopJ*M<^S2;j|FIWpNa_G4`${Qu5t?qnCl{`BrVg&HY3nNT5$=N+?!)N!!&q z&I0Wm_pbgc>~fOi&LgRM{h@bR*%w$JOb}s2b~jwpjC9GeUhL@tStLxM^@#0~9vNmk z!=bWPtm!2>Ct{ZaWhL_dg=sbxtI`?UY(s{cWdi36hm`YjV#_nu1YR2SRS^ z!Fzhk4da8dp7>^OPI}yycYu#0iI%6cHuUPGL#>Q(>QOw_6w1nva1Rr@{_#58*rSS#BR!2%5`H^JUW8LYM5t6CBi-t*er=)B!pCRzmQ8EXmAzy>l%Hj7up{f%TBR9RMK}mW|MUBQmIAG3NCQ{u z0~@L-=DVK_(`hN3LD;F!`p258yoJnVXF-f+t5AL#Gh)z(``7@hIuwzYQrmR zc)bmOXu~vFnD85H!#*~A?<`~gk?l`SGvA3e9BadwHoVY=SJ-fa4R5#MRvSKL!#8dC zfenw@aKLnv&M7v$(1wLJth8Z+4R5yLW*gpX!-s6R(}pkF@NFA**zi*u#-C}@_1f@s z8=hms`8NEz4XbUq!G@b`xY>sH+VBY*9d$J8PZ0NV)*KN4UhBw&odp7*J z4Ii-K9vi-9!)bOs>dNKMGj=^bWWz&Fy*eIF05^{lrEW?MDl)L}pn=caZD7w}?$3;U z-6_4hNBVaqeXvZvWhs-7X+5lf9K$B+5tt0KOO70fdIn~UFN*aWqGWIRR0(`9SQqm;?N zf}WCJu0`s6O4%h}PJRrmb5 z_^R#UZ!!5O(IxNhvJl^;5x(=Gab-l<1-N(rmV7wrDq5MOr<93bz9l{>hr}cKmhh~6 z{AaIRd3J5ML6z`3-J8$PE68eo_##~X9U$&QBAml&o8Rf zpQNiuOA)`st%y_N!&DM}wIVKwN6jr=rU;`J6a|7cB{=Y#TT^ah(4{O`Qycz*UZo|K zr4bejgXSy0s#5z}5VT=YK;n_`5=P-q;YZ;vNhnuTbWCiYICtOpgv6wNp5*=m1`bLY zJS27KNyCPZIC-RZ)aWr|$DJ}h?bOpIoIY{Vz5Z6Eh{c5UB05M{E90pR#sM3f1{>0 z5WMQ@RjaT0=9;zFUZ>_%)#R)y4;0i?6_-lwuB0s$Q};Erf>Je!mQ1^kQj$ap5>jf{=b z56da_3cf0J|1H;JTV!0~UQU|jxL5G^8rz@ro_O86O#I@n1ovX?Ek%|D6Jgeb?QlKSvM87ZZSbtSekQhK$|E6Kmfdw^aorI%W)CB_Qvr%Ely zPU4d~bxJ1VQx}~kYC5eXZ5dN#%<-x;W`ttCYSgKGEhoN8zNO5PC$W*1AoP?H9Z#uB zokwXwW)6_@Nehb%nXU6Aqp9R;lCE88PfmSL3DqbeZN0_i)ooDPv6H7R z`c6@2h2wMb^VRC}YSQXG#op`G&|wOrhLiuVo}Tn9>9hZx^rnZ?tEP>bHgFYj)extw zIx3*r@jc1un_U!h@;@yc-&fE7<>Xw}N~=gWKpz$gIbYHuom%Wl&8hD*)QoU?z14RW zwJP;xMndV|ReH3LQL~gWQbw&(9fQ-39B9gOMvwL+xsn)Vd@y5MC@_T%IE1|lKfkF|&gSBdxJJjbsld zzrtj*-;$G6{j?eC%Xx7YqY$^PD&X#8`vLjSVtZ@HWyzm5ds&J_Ut+hTu@w7*;9jl0+WuC~8N z+23_;()`k9?#x3GPbjc&-~JeK}L)U`k?&MDuWdjps?}#aHhxMYIGmf zCn`B6CnqOXe$&&5OFVir3YNsV)miE3iwoeNd%e1exeLn*`6;!kdKEu6K6rV-?FP8{ zC!hcMK>_b^|I!!-&A;Q_j<@ksGhgz_+~wSSQ@T(7$RMZxp=D*v4D z-v6|L>tB@XtNnArAK#+?S(|^<10RkcF}imB>egLf-?09MZ*6GY7`n0Prf+Zh&duMw z<<{?g|F$3e@JF}*_$NQze8-(X`}r^Kx_iqne|68jzy8f{xBl0C_doF9Ll1A;{>Y<` zJ^sY+ns@Bnwfo6Edt3HB_4G5(KKK0o0|#Gt@uinvIrQplufOs8H{WXg!`pv+=TCqB zi`DjS`+M(y@YjwH|MvHfK0bWp=qI0k_BpC+{>KcO6Ek4G5`*U7UH*S}`u}74|04$3 ziQP4W?B8AfSk8mxfZq9y;9F$LoF6iZ-M*Xnj$BLJ)Z?4mzunw7_4wuvcsKW(dwhSl z$G1FL8JV6uYZ>`1(kHT}ZpO$-{CTAguW@mCWl7c53j#%fa`>UxFRCrAnYZkU(&9jF z*`q0Mc+_&!}WE8Vq;m+tzW+$!l$R#71V7|Zk0AZqhN6z z>opd21qB-j>P@TLP)8`mvaYPG%X6^@^t?zN?XK!meeS#+g*)&@!_eR(BCFW1F#!gsk>1p~c#u=CgD4_bbS zzeUuG!zXcg%f-};a3_RUA-hr8K?uJ?ILLQ+pNIj<;)4aPup!stnXrRd~ya zDoZL#YrH+n*;RilN&{41dB9s-RZ{A$TJEiOc=Zy~B+^}laek9&Kegm&GVMTeF&Q`6 z)jPkORn>Gb(=trW6Yt8E6X0`$Usb$wOqb8}>qxrm+(r5?Db-CO(vLS-D}-6JaPCBN zVjSsTr#yblcyEzi3TZ`=p-JI*|D(o3+KP&*t0iIy-J>}eq8%5mdyV!;rI&PyYE}fL z!fU;0rB^Xhl`r>}uB;BMKJ_1`w~VG{4`M}Rw77`Y;524wu-=uWE351y!O?b49IZ!G z>4#o*ydC_r1=$O3T{GeF-?yBX^Mk`lj~;vLYw0eEI_K=AGC$QWy_iP0dMW2+GEvno ztu0?!T~T_uGY&5;DX$GI4V*b`Qgw+Lhz*%e_*dfYKhUiPmL#fy(-PFc`JVkr%?Z_S z%rWu;cY2k25|bqY{rsNtD)lDD`R;#Gj5=w`;OdmZLFp1k;@dY$slQ{sW`}VNjaNeh zNopu*3|*L@hEC(VCZ&1k#H8sXcYD;ZKtDC4B#HDBm1k;vO`q17{ZYcqSi>9$aK*={ zc*5XP?MiT|1WM)_6t4zN^Qb{nk~{jfChm`Kc2~z0_9^HuY3(MB0I;MlX}Q(V`6>II zytSOJ)E_VbCvUv(5kq|ahsUbnvs0T*NtAN@Z|uz2brSq&?pKBo0k!)_k5e?W6`fh#p$rBZLH)LSZbkUC%6 zSN9*(M-3`*QwMQU2fDpTxpHSJwFDC`SDz@=XMWU|){ErtGH%9vgn7r#PZaF4AsFYo zHyRe7%Xu-zNvnVVKB_-?>_0_XaD1Udt9!DPdLHxFFGz@AU)`Sis`&YR!uj6j<4k?F zQbRvC(1o6)L|1?1@+K;8Nq^;Cn5?|e#alDHMYWcpDQj(#kqc@`;E{~o8&%x%-G@%@t4 zZify%esd{8`b!yWoIFS!)kLKa9qA@b_Tn{N{Ym@RUni3*Pi z*Oe%BD`usgrpcG-A5I&c%QB(>v%&UL3NH6Iw?yW13TrdLxd&{Xi z1Z14Bavf_KCLDG^j2bX4Ne#F;p}?j4qutMj$D2B&Zim-&)t^JF*RMb`(3L2N?VgA9 zp%WA6D;KF@3k&Ek^VBfc`O4HhnOVblL8e^86V&iPD(zzk?PIVS?i!#>uf$D{iS%#k zb13y`_wVNZCuldnLJs9*1ZA9dWBNP&yu=<)=cjZ;_V?v1xqgNDi=FR@;JYwG>^|U1 zajO)@mK4U86xveCl>W{AkGI?J(BWq=>i>Y5;)K`vC+!l(*@fY8w%OGq|1KF{Ih1e> zaWlsERYMj6skoRm1Nj|E>M^dzzD~6AKg4<7vbFWlUo18OFRcY|4-h zLpxLF(oeRs6M7rtJ|-~{mmaGaqsUL{G`C8fV)sQU7jaO=Rx`VGjSWBk9%BQhD-Oa@ zC#lp)Ds&-^>Y?cgYUH%L)JWIus{3q1qSW>N7}6djeX}2ZGl{;Ls0Q7fT&-!bFrG1h zaey(v_+j26e}l;1p!v2R>d?curTyss>el_Wuh5P$$*F_ITTyR_DWDDny2i$Lh+95aM;2Ttu*(=%LpIGl%Y{gmgvglZ>USHCFLZ%Vv)(e0)u>`AZ3pI2%J zM%s$N{zKwvgRC_e2Zqca*x|GWhenGIDD_9oqc)99AB$K=F#kGzOyb;gkn!mSrCxPt zdNO1E%?Yi2_s2EIR>u@Z7eu8CO}l8(HNOu%GeM1;_KoOquI16awJGl~^7|$2_6My> zJ&keN?TO~TEB~O>Z!yl?XWDWJZTV}xw&fPatuIS=`}<10k8#pVm~)T#81>lyP;k5VVO8qHdferUe&1l`l!_)F}g66srs z^UeCuH8N3+4D?qcOOol+{nW^=G2dS6bQ?cfSp%IYudR~Tp;Hso=s>A!bV-S8^t58v zXxGz7)@6QM zrV8#-&5pb~Ulw+oqq_XqUN!iSe7vE{f8^s09sak;$B%SHii0+};JeN-{GmK{)Qi=G zm<6T6AS@^flr2`*@)gOgg?nc>xN3`{{{b*X*tc{w}+L*u_QVfw@&R z3t%)y6x>0Nv!l^KXP`BFU4aekD>Pi!;#1xt_TfT*hog?g9rEU?5EC__%Kb0~_J{PX8 zE>)T0I;X0#wyL6ZPN1g3#8RU!)%L-f8ki>83 zj#*S$rkg}b&Z=TWzX=Zkh*YWjrJN^pj*8B$%`ROQT(P3Grl6*@7GkJVV&(@bE-t5% ziYgXW!nb0-Gg9pGs;aIGR?mf1E(wrnVG5;+%bcQWO89(N@`42punm8KtTHlJ;YI8{#E8#scxLDh2n=VTL+@7t?@rvs7y&4dY@6qz+O86{UfmROHZWK}9L@ z{F9^e=HwSu(~4eHm z>RPTqEG#FTT1inb^=*565sSsj7oAsCRFYS|tcEKOl=?N@2IiLO_3<~_LlMN!&ee&RkDtBlgoV z^39a1zd26P-%M*d%zWE^femGLk@zpcNZKrZb-0y4FNUc}4acy+)cKcki2pi_M`QpfRX$lAEPCLe`0^%0hIjx93$!7jS+tjW28*aVZ{9vjJT&l6rqn8q07Ja zmwdvXN!NSA-@i6r|F>d4vGASA!HI>x{%_^*U!Tqin}9t_pRfsd|MhwMH>B{tyh#+~ znDv({Dn<_=`)vOY;s5zN-?{T7^`|?nJ2~j=@e9X)?HxMAMNB9cz4rCjyz27Tu6S)q z58sT(FC2Qa^%JGexYmS3RaWPm2w#5t-buC%vurrih8Z@TX2WzFrrFSI!&Do(ZFsbg zq4Rq-Y_;JVHauj*7j3xThR@ir#fH0W*lfecY`D#a57=<44Y%0vHXGh(!v-5V@vpJJ z12(L%VWAC|*wAmo3>&7~@N^q`ZRob)(O6UNzD)S82s(Gz_LdD>ZFtCr`)$}_!)6<9 zwc%zPZnEJj8y4EIz=jz%Ot)d04ZSu@wPCUi-8NJ67^?HGPnht$A)*?=`K|O{LVnuoY>z2TssI^0Ps5CKFk~7 z&j6E9R9ctjQiFiYFk8mDR0%L`2)ujz2%N`-=uO}Sz@=>5mx2pCG*YPtzy-dIkvNr? z^BzpW7?<(_zrZX6SED%3!bn;HVC-n(#NG|e!PJqi==^LH96vV#Cyp_AI&kh-(!#$V z*ou*~1b%OvDeq<=dcbs8fp=rX&lX_9cw?UkoMq!J!23@{R~d0W0PMtkB>6c_snalu z{G1LfJ{=x`&;*z;k>Y_T0#C&hh#%nBXaq~ZmjZWUq%6CE?_wkm9|6xzM=lThEZ{dW zLgzKWUt`42R^Z4plzNPp8@<4DFcNWNV zux2J@!A}4;->+am1XP&M*H9i5q}Ku zo3qhD1il7%6GrmC3HTbDjxy{;R_WCo@+mlQyB`@O@W+4y&nHgsrNA{92`lh+8yEOC zM)IaEpqerJ@t+R#V-A5A058J40bU3!!nA^y0H^06j|-jwtipT*UJZ=TC;!x4B9Lo1 zDj+X#0x!l$9+m+AhLL*z2v`SmOz0`F`cmq0Jn;ZeTS`9#KOOiOW+Ax1GcKp!flmVt zDB_F}96fnzCPw0~SfPi2)u3u>axM>fUYuQ9|L?9lY#vkz?5=hp9-90<9=Ys#%~1v4wH@lX5c3np~L6E zd#*6}y}-;0+8cfXz#n2H4=uoPRkSzoG~ksO$$tQNH%9zy0bT<$@m}yXz)vwP;GYAp zt2KBXFg9RtH*gb1>Pz6+LFyO(Gl36cWc=I)jJe7#FR%mSK9xAd?rPc!xWKqorXIb( zKC7uC?A^dTjFeH}6cji}|C$C|^G(WvAAvu_NdLMW*ol#{h`iJYjFiy}T#MO^|E<7d zn62PyEn4NTC7csuorkQM#|U%Z2AS?*lz+pd6%J23o!p~L)!x2w=fd_2H-x7ghel;ddJ2E zKJZK9U*J2xGGnR0`|mYl<^#ZA{Tf=4*1f>ZzcF))z(W|RFM-LwHMqcCm{$B3Y^7Y7 z_rPxf&fEt7cmiz(*l#=I2zWAZHb&~S8u&a$^0{B|M`<(o*$?dVn2FyDy!CNTeX-vR z{1Zm{y9J#5gu%0b7N!nA0`J=a9~}Gv;Q2eD8+ab@SGy=L_`Sf>c2j=vEMQI>x7rku!F9D8!#o%ec zGK}~an0d&w!A)nZ<0X~Kidx0O@_)*|RpHd&#F9hzx$e8d9Fzz$z2zzv)s?#tM zR_^J@y`#@*O9JJdkKh93uFO`(B7t%bM(hRdwsE-&Blk_jUZC775&r^*es1gqiVVK^ z5h(W^1Q#fG8w3|9_YedZ_%j=qy9jcRK4*h{2a#nJvb@yloP3GDZuz`pea_8lj%S3(5)7nyGI3GBTmuut#BUii0J*caT% z*bRKgB%m^W!5Bk+obSTB7)#w<-|pWs#!(55d-VgjkL&tQeT{D_*>P`v7yrcVe5d`D zZ_4C+Z{picB|G1@{f%)UBK@SF*C zbbZ1z#a+d93Hr}}M_;28wqL;hb-IfF@#7--zz?m(#1Oxd{GSSNb%hl){*5dcS9{tI=(>CkxbK2`N-@Ci)b;ZZe3ZOG^NLIK zW7Y?M&n_<({aEv#O`Iux%=-Gb7mk%a*6nND*f`sp=SaKsr7H2*m3pFDT0k1yQKwuD zpIwl(Xk4e{bJlZ)V>^}QRNR*|<$_!dxh8U5WDlX7yCGYUZzkuRI(5Q(l=0@;Aw$&T zk3X()Gcvwt(ISTyJd#E{MLe>27!Rq-5 zL26lYpxQpGyUIxmQ%h%!Qd<`%s&^LlR|m7kEAO469@rzQ{X3!p_KNDfUsTTzMUDJG zRPa%3yB!xbxIk12^3ao_Mtm!3^a)X;z7sWT)-3hRGta2>^mO&&i!Z7rOO~kR%a^NF zt5&I(Uw&DSZ*Fd`+PrzQwq-kZ>`+JE%2l&}5Vg5T)Z1^rt=@a@J@vr{AE-lz4ymI@ zkE-LxkE<`f_(Bz)KBkWRDC(=PzS44W_Uu`8sqmco`X^CEMMdiB)vH=o$ky9@vCfRd zngBxMx(NFSxey*Gdz~W4sobsnvH3GX2CHe3q!vrM+AQnUQ8{Gnsk#U7o{^aU6XClr z!n!jT=WYN$rU<@()Kkv_emUUR0e%xL%{6fI*DN^bX z;Ew_ROTd2(__Kg71pE~zc;A+o--1f_Fu0b^U4XR?)?Y<$K?x7nQ*x}mk}n1;IhCa3 zT)L8?^-8WBD#PFR9>9A7z8!p)UVx7P{0P9u0)AROCC|23vSP53*OHX%PDh*Tm7G3w z4IcAaWOcyb4)|JtuMK$EpCULzV!2yMul7nt4p#Dbl9CtGm26qBOG9Tw?rj_pHh;vTuJ%{CF}PoIaJ^TZvlK4zz+cYD8Nqz z`~twQ2K+|A?*#n2fJdL{p9B6h;ET%OAHdr|`lE$dwD2@qco{A1MGIe{g`#?*erzx5 z(qK_nl0;oi7xmM6QNJ9LGWh!d-v;mjfPWD1qX9n|@bdt_q@JiX?L}=GENV}ZsQh$M zpRX5n{?IjeSHP14_X7Stz&8baOTa$>_+Iry4R0?hX|SmINoXrwRNi_~#}8eDFK-&G zrOEkbaB%OS-a#Sy)_~UDEt-3Jw(MJ0A*gqFSXf9%cyK^KP>8jSx0hFo=6(A%vpOn- zgyAv%hv7p=h}EYREA;JaaXb%?2o4PoK=Yx&K>@vktPik4ix!?u9nZre0)xVXfwWd>H!OXwacu5s0g_>5?OX4?r+)qZ{<;AlO=VL&2mT?14+_Wgd+Imo z@4#(9YoY*nYd}!M!0_gTLH)=beoQ5Jx`e%l{+Li?va?3Rbd#&x1oF z!otHNLL)rNRlswv*1G-P5h3BB5g`$M?r(IjqYLz4ZROo&0O)7?!MY2>HEU0Ipuj8e zPq*K`M+8_L5gHz$iO@J2{sVt&Nc%vd5FQa>ZPcev{rdGmMA>s|m)os@zM<%1AfDgf zr;nzxaoO|Wx;5(h)oB+R9ufgA_wmqFHtFLKkAUt$K^>~qtF7^5WL!uZF9sohQ_B^O-=XM=yRdc;N z3=e~QLzX*r3J$*Z+-mI|G^j_Nnl-whhhQsP4-2{0zcrY0H>k&5x77^f^WXr8Yq$Xi z|HvN*Sl}J?>I||5M+EeMtd%{+bCdeH)$0}!1U`m2JtqG{bPe3^+A+8XRK@wRZXe?t z9s)rO3+>#!!egiQG0gKHTG=n?c1cbZ#`6~bJtz>H*VU(+xL#z-uF9hSS;SHeO+De z^af0;yX)N5$h|>p-#c7gYuBn#o%%>d!yD(?r^Qm8@_HH_ttc&>500&zD;V? zVEcb@zpXy5?r2uKRxKZ_1DdwDuUca~1xOQIqko^q_jon)@%8ob0{-Z~wU1A8AD^Z^ zn!d(J7Nn+4^`%O!i@o@os0d$KXBU)}CEho6SRZi81F=CAaL?7F$S%r)zke#-255Nf z`s)ku+I3k2C42t=k*-Jmjru0OcOc*E;o;E~C3AD+mbGft!U{;iGLTbnq4D!&$af(d zj^pEhB#}RC7pt2pCGyq{nc{C*xpL*-Fz;+HEG+!y`|rR17IV+Z3l}b&Mp&?NoS5%!*|KFbl;jn+sjHIQ zM~oN|&;<#V@54UgLad`DFE3BN{q|eId@9&?;Rcly6ch;7-@@mhL9hn@;>?*dXF=;% zhYufqY4`5ksl9vm?ud3cPK}WpAlE}CE*#g|j?B;GF`xYfxe0Ou>{WCuE-ps21h3icFqJ;K08 z;Qdp8S-F1w`WaidZk++QFLchFIfD`t6GIj(SP%)nnz*$>_G!?dK}YOuJlvr}2lCE6 zBqYR}?c(zv!vEsMivnLJy;-wnox#He=gyrI?CF;AIX^#NKK=AljVH!OAKb`L{NYat`YOxB-(>RDWR8i+#GkxkX+HCs+0em~_?Qfw#bk`Rm~8Hu@>cMF z>#euuV;nsp6LU$+o;`as4U`Y;Hwni8`%GG1h$D3ZI;_hhM~(pamC43GY?f@F9s=p1dT)85chP3?q`T4b^95Ztaux}>Y zw{I7Wxxi=EI?OTPm|$N`XX3tZ-#($P96NSQeVH*|#((F|o$1Y+H}4D|egW*IU_3Mpv^g9D z@|E;(EQmWlbNs*j@=KlhnLH=Ii7#b=v=DFdpE8>Hok$3NLp<=XNDpkmcSX#g<37-^ zSEMy)X!)jy=NlpmK1Xbzq-1$jkVhSjKWx32O#DqYV`0i$(G%(c<>hZ*iu65(?}U5= z{*wlbL6`SMI)H|RPY~}Y!G8B4k#;+^SYcPRAa9XNKScIWpq&9oyWw; z6-)A;&p9s;mUhND4Lb!LB@JJQgn@=o(2#qhy1jvsFTa#vkK7AA1*`u|M09Y($=>y)K} z1DoWPDLo}?lE1u^(o54o0v=DpkX*{{9jLskOrgAq`~MjZBqMp!S}aBT9F3u-}1W1AlNCS4=ckT_Fl_B zbZr6sA?lo=!_dMp;C$dbCWaQ4#GiI@^Q<6Q_e2lL1`RKRhKz}&_@@+#JbE59oDumO zXrMmRCWXT$8GR-VMxRN8(P!Eu@9i3YoFpoj8dA?VzrfEb%cQ~NigXxVai)WxNdwc? z)NZn2dUsj(cn^7XDrlJOFPDqX$tO@}(qIJ*h_T7vob;JCiTWG_U7$_!gH7W6Y;2NO zo=CG&{43fejX(VR`JFp=_JofzmvqoBvZTzA4*EPSNel0Bt~GucpK-pW&%pFXS~uA; zvzx334LP7;Eoj)WWP)5Ogz!`$oduoo7!yCv&!EBh7+yO|fy;3p_Mi5`$ba|l-CJ@j zOs2jPZ{kMW4K1|&wD(-s&~9?B;@rlxbGDzn4jMMk>@K;}dx#x0RMh8x!zQK22g&^L zy=7i(u*?|~EUAx$$a9k(mM=d!D5SyJ$I$u=o5WNZ{;>C2{(;W|;!eC+5+~wKeITFB zn9#;M`^WT$NF(L{t$DrVP0+9nG;Epy8lVf*XVO4@rcGJ`yGj}sk%n=-<>|4YAtpp- zJ{GEJAfEIwI6oU7qci3=q~FOuZ3gFH`Vq`)*RBZzy1ZntZC3=J%=M+a%5Y!UX%p^dUKLo}kaNkL9N#zyEp((mj)@i+3{qeqXH z#DRF?R7e>O92aLgZbZwkzm~{XyO+w_^MfUCj-PCI=re7S(dX5$Nu*(!gNA=Xjuwm$ zl4oK|X&|E7$AG^*0C@%M)o&v2SCD;PHsqLN{!Pa@hVX=hg#E;o`bZs^Idi6_!CAg4 z(o%6ucuku?UAS5LWbe8%AA|b*4~ITifV-jpL;N}R{rdIeILNE7zN&pM`ZU-dkjaxL zm(g$|I&L&3#D_L%6K&GX^cgfTRl@7aZZ#yIaMorwY^Qj6d5P6(6`aiw`gJKODKc^5 zM43N-zSe7O`DnXCU#K!#$a~HWv{m$l=#O$ez(jq{Uo%Nce#UwuYqFl7oqdcY4t-t( zU0|vVS#uA26?Nh3{}tC7|2A#fbivV{1pHL{dHB(K95ORAWze8Og0n^PkAM6_5L1%p zpMPHC!}UG+O&T~CaGs!CF>?(=8fZ@`hnx$^qrK0C$l+Ir{}tK4dh#_N1G+#TgZfOH zv~+3&(ZA{X3wwXhAQU=_@&j2<$GX2W&cWEB!-8jm-%b?hgz&n5|Nh$V!Fd>MKZ!eW zpugmtNrTCT4wh_>nKEVCrfvOT#VFbFWFY3} zQXhl!vu#GOZ2i|5`Rx5Ia^>>nBN*>0??2!yrOcf>_ae?H$?Vy)FF*Y7!x9%4r}>U` zrIsbqK)v9;4{;+M6=~rf1m|JOJ$)qV3il)Ou%6^S`beB7NJ}LNEcoe5j@KXE|6yCu z-BsA5EO_FHCw?QII5(;@b=uI-x^-*8*)=^5oHHo%^nDB+#FzL}=1C{l1}v!$)Dh}~ z855S2LHZ_U?%~>->u1QzAKL$Hu|u?a~GV_utUYTz7Z(K#Y4P_MOfEX~ujQ*QrX<{)3T0 z`ag!R#HXO3K;wh475ElBRjl;^<1LUkEd!jR$Y<7P<~3=c9VJg`|2J&dAnVqx({tc! zuf3+>xJLj!oq=t81astc;Jyd3w51vX1KPdg{#W-?)DXK0Iy z*X#c%?xa!UZ~TAodoF1(cG1vcXkbZx(>7u5*6Rey6z5uJ{t{PS6Mv44@gW%3q1;oJ z$aCrtY{p{XaVxl&;qNT}v=PqZQQ4S~F7C09963^OE?3L9;kk3kdXy!~I`4B1AnqnU zf;G~LKY_c(pM9A1FXo;FluOP*q=Pz0KGA;A)^R>^9ux9*%a$#&bm>wp&*Znsq?@us z-J##aYsw7U<6Hon`3hdaaI1VL?o4|B!FgUJ{w9+KlW#O8qzPxD^?XGcBMfOHzLc#z z*iO=7aEE`o<`(6>6zgk$_5Kg^ORs-1f6pZ?H*|&HM;+^GUH4^L-Nz?f5J|b?f;Ml&YkpMX#Xe&oR2tnlE++glJ^`3 z`T}Mgcukv6TT45JHHD6Afad=+?xaJ@zq4#qlyh@!^wzngtn-?6I2M$7@|iSJ)*(l~ z!ACfQvEsbSGZuejZX$j+OLwCJ&mjE2%9 z2co%36Z>+2u$UTqdV%`-@_c$%ysz`?xg5#=T(1 z6gnWbGZK5lAOEOPx)BbfwQ-FaHM(MLmjsH#DSUiotZr0{B_~pgA8oVh1&`X>&52M&yT zGJdY*5jZf`#uyLfkufU9IvFQ?2s(na&oL$*oX4^6l7R2ujpN+RY;d5@L7vdJUNHXIU7dVXTiaCd#AFn;P_r>7Ra(i7#?qrJrW}H-ify zxUbB;z%1-%GUki#(#b67m@^KP$6*!Z5$E@{PVg)Z<*`DX&V&6Uo)U$9g<;Nkuy4va zOD4wK7$af4ka4OlFDBkJW_a#rk>mozJb?qx^H3)kvt=^$)32B|ZCXNVYU()ppv(pj z?wgPoOx#~*tcfuW#>yCHVtj*fGRBw~+hB~0@ulgbOY;k}V}&?qc|_dR#J<^{bN{pf z%=B%To$(+p9RJ-<2MFU`8-N4%G`Tm<7z^c*aW2MJ7;DrqE=T_}9_z!dv`!d#^amf1 z%Eq6*8R8I8_}=9(+5)ck>7SEs;zAy)^2OUfYikUl?4;=xRQFcS~*iQ{mi zbY#zKI%djP-CW?ncxi?7JNw7}pL_1PsFz-PX&7Te^v8(@dCqk;*VNoMB_8Aj?Gc|b zF@DQ)07fS&lSkHb$XaEE2YqbpS0rG+C7yg(xNxDid$hZp|7iay3*-l7k$n>vChElZ zCy&a7lOIDTD##;rg7U~zS;t2HxgO^}2lsTzW3I2Di+a69-6T$Ce4NLIWAS!+q!gVx zDurKvd`%vq6O3O|9+%C0h}~B@|HHP@-}Yeth(&xA5J0bPijGa=oeJ{ov(M`F9dV*v zq#h6t@_^$*-dsF$Tt3VgFR#xD(q{pTJZ4VoBOCsmDCbYXIk_fl<=D`xSFeuP@0~_E z5XYP_YSgHA@%?QXKYqO42gVsmy?0^OKIA$1LH=`nM_C|0h=0YzHc{^|WXpN3E!RIU z5EmN?z1QnN&h?DTFt!9gOK|R6({0vY#DkgqARZORhWK&bqV3?i6wP-*rhj*%Izk6?ngB?AWpIFvi0@dVJwuI2MerVP4k#&>tY***`OVGWuwy#9Z|=`1u6dT8VhY z1@z6Oz|E4DKk6XcqHjljb1#=N$h~yNHK-rN)9hVw3}_b!M_kAY+HKlQ;+dD1w-6oI zxmo9UuJs@B+bDdGZW#M!n;d82fcFFl-YXyj2M*M@5eH`0p$@Pf<%WI|?FG*zVlO-c z?Q*S9m89)2?GMh;k7IiU*pug+Pn*No0^V7mW6abG;>Scjm_1(l%Z#beZwC#_(P>rt zCVskqBhTb3Gx1<%f1Ho-js$HRcvh9PQ|35c*mk7}@)*2w z<;qQn(OiLz`X1*({<&t&nsRmrF@Qp}F(ErUTi-{W>RDM?Vc5^!nUj;F$J5MV9B0_{ zkDWVXJPqfcxzG9w+BgCEyF@+Y+L-dX4r`Dh@auT4F#v0;WmpFyZ8dGME@hfNobhLj zPSKWGv2Nm;iF*i#;nsA*xlSbGA1lTW*cWw;c8QttN?4{SEKj%w-dgIC>3fbpmr-t^ zZK56#SNaZ=?Sng3OZtRT-*B{J{l@hp*RNbFaIMk~Yo#T?i?LR&$%#AVpZ&f%Gfew9 z7qJFg2AtUj*Wz4zaW8`FKCV0cudoY{NO7hl%B$4)+nb zW+VSNPMm}22Qh7bDpdPhTz_!w!L<*4U9LCm%byXp!F6~);KA|3yJvKqn>LJmII%ZF zwxs#%^(1|F`t4jdaP2fBy07kINP);&tZVT-Z%Iu})n`N)SL0YxUJU8KBXjF#_L6faj%a>z4fSNK?*EdJAIctWJ?#$165n0d zJf*GXSbn)DQ#Q=*tIu<$+eg(_(Q$SzW(@4ezpaE4||T*KWfz)^XKuU#o^^ zrB{6ST6Mh9h*uW<=CvA*nz%xp+Z{D6`mJj<@oqJ`d&ATaWyRl6yoY57-X1eZ4a9XA z{`SFVrZ=k>!Z&o$|7ei-iDdvEB2~vfpj9r#z>9Q2dlT_%S+OcXjX`U%_(iQ~w49(~ z)CAP-t?RT?e%LPUhTIA7p#kzL9PWsV3PzoHyxk=V^``-wC|pJBK8BzrK4EJXyuT(& zO+uZ~cz0Yhdb6mZDn)-{<-g;rtuAGKhv0q+Y7(bJ;1-KM5~|c@DBh0~4aiA=iUOYO zo2dhS>FZki!*stXPVKhGo7S1z>5}~B=CCVZ$cKr#egayIbE0q}mI0kmKU(*R_hPGN zs6PPLV{}b&CJ{U#6=Oj4M6~()dlop;!T4gO1*1@Pe)(zK207a1$PqU)cDyPbg^D-^ z!X?0)E&(?|Jq9j~23A!cGcJ6{$MWzMxmg)6xw-8E>5W3q2Fq&_#!Yy-8-K-9%25hv zi2`QV#=Tbc-0D4}Qc|MhN5xIEOpT9ANb&cal$_8#W%StS_^6aN@v)nb>CZ zg!t}JDe-M5xA(NfMbH1G~JE`sb_L@(j@c}{a9pj>}32}>X_)1vWL!(L#G0izfU|MdUAA}C60gnJ)=^> z5++X=AD!%JnG_o|dNh7U*WWWHDlR43vsX{wvbM_um$mOy<9EIH^eu;LPv0`40p8Qs z@UvHBP-IX@NZ7!>!-wMoM~Yv{K9+d`zwcC(>6+!16`3_WOS$14)40EH)v;A)Ru!*u z&2Z1~&hX3VmoYqJY{t}#c^Qi{)@E$W*q3oE<4i_zhHIvKCV#o%C}7kow^eDcF3Mh< zy()Wcc5e2z>|NRWvJYk-%RZ5PCc7xRI9u6VZEiMqo5kjBYiIMb1={-AB5lKMkJ`rC z5^Ym$X|{Q`MYhGZRkpRZT-!F=F55oaLEACg30tvE*p_p=Au``IJy z!|jjS$J!I^Q|)Q?dG5Hv2C7KKnuYG5ZPo8GDhv*sgM1bKG*=b1XUD zIqh=%asqSuvpXJEs@l6yy_%#YGzvIyv#+J zi!)bcuFcHN+?Kg3b6@7c%ww8w#hEILymQa8WO-+`%ks(b$g!CR3)5B=W5SntMH9@j`ZjX;s!p^wiji v-t5u4RVz^Ga@#%n-5q$*dnI&g2H;Pc_RtJU=qu9k>n`->x}m~1y5|1?oM}r< literal 0 HcmV?d00001 diff --git a/venv/app.py b/venv/app.py new file mode 100644 index 00000000..e7841891 --- /dev/null +++ b/venv/app.py @@ -0,0 +1,15 @@ +""" +# My first app +Here's our first attempt at using data to create a table: +""" + +import streamlit as st +import pandas as pd + +df = pd.DataFrame({ + 'first column': [1, 2, 3, 4], + 'second column': [10, 20, 30, 40] +}) + +df + diff --git a/venv/flask_project_directory.txt b/venv/flask_project_directory.txt new file mode 100644 index 00000000..e733b9fd --- /dev/null +++ b/venv/flask_project_directory.txt @@ -0,0 +1,6 @@ +flask_project_directory/ +│ +├── app.py +│ +└── templates/ + └── form.html diff --git a/venv/form.html b/venv/form.html new file mode 100644 index 00000000..eb575bc8 --- /dev/null +++ b/venv/form.html @@ -0,0 +1,14 @@ + + + + Form + + +

    Enter your name

    + + + + + + +