From 8a4c594ab553605142c5b3a62037b42d9486076a Mon Sep 17 00:00:00 2001 From: pnymz6gv8 <2350836052@qq.com> Date: Mon, 29 Dec 2025 20:11:52 +0800 Subject: [PATCH 1/2] Initial commit --- README.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..e1ea7ac --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# packets14 + -- 2.34.1 From a289bb0a814a3dcc459269977b07f4710d867290 Mon Sep 17 00:00:00 2001 From: gongxiaowen <2350836052@qq.com> Date: Wed, 31 Dec 2025 15:47:57 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E5=A4=B4=E6=AD=8C?= =?UTF-8?q?=E4=BD=9C=E4=B8=9A=EF=BC=9Apackets14=20=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/Flask泛读报告.docx | Bin 0 -> 21596 bytes src/demo_app.py | 153 ++ .../.devcontainer/devcontainer.json | 17 + .../.devcontainer/on-create-command.sh | 7 + src/flask-main/.editorconfig | 13 + src/flask-main/.gitignore | 8 + src/flask-main/.pre-commit-config.yaml | 18 + src/flask-main/.readthedocs.yaml | 10 + src/flask-main/CHANGES.rst | 1644 ++++++++++++++ src/flask-main/LICENSE.txt | 28 + src/flask-main/README.md | 53 + src/flask-main/docs/Makefile | 20 + src/flask-main/docs/_static/debugger.png | Bin 0 -> 207889 bytes src/flask-main/docs/_static/flask-icon.svg | 15 + src/flask-main/docs/_static/flask-logo.svg | 17 + src/flask-main/docs/_static/flask-name.svg | 23 + .../docs/_static/pycharm-run-config.png | Bin 0 -> 99654 bytes src/flask-main/docs/api.rst | 708 ++++++ src/flask-main/docs/appcontext.rst | 186 ++ src/flask-main/docs/async-await.rst | 125 ++ src/flask-main/docs/blueprints.rst | 315 +++ src/flask-main/docs/changes.rst | 4 + src/flask-main/docs/cli.rst | 556 +++++ src/flask-main/docs/conf.py | 101 + src/flask-main/docs/config.rst | 839 +++++++ src/flask-main/docs/contributing.rst | 8 + src/flask-main/docs/debugging.rst | 99 + .../docs/deploying/apache-httpd.rst | 66 + src/flask-main/docs/deploying/asgi.rst | 27 + src/flask-main/docs/deploying/eventlet.rst | 80 + src/flask-main/docs/deploying/gevent.rst | 80 + src/flask-main/docs/deploying/gunicorn.rst | 130 ++ src/flask-main/docs/deploying/index.rst | 79 + src/flask-main/docs/deploying/mod_wsgi.rst | 94 + src/flask-main/docs/deploying/nginx.rst | 69 + src/flask-main/docs/deploying/proxy_fix.rst | 33 + src/flask-main/docs/deploying/uwsgi.rst | 145 ++ src/flask-main/docs/deploying/waitress.rst | 75 + src/flask-main/docs/design.rst | 229 ++ src/flask-main/docs/errorhandling.rst | 523 +++++ src/flask-main/docs/extensiondev.rst | 305 +++ src/flask-main/docs/extensions.rst | 48 + src/flask-main/docs/index.rst | 88 + src/flask-main/docs/installation.rst | 144 ++ src/flask-main/docs/license.rst | 5 + src/flask-main/docs/lifecycle.rst | 171 ++ src/flask-main/docs/logging.rst | 183 ++ src/flask-main/docs/make.bat | 35 + src/flask-main/docs/patterns/appdispatch.rst | 189 ++ src/flask-main/docs/patterns/appfactories.rst | 118 + src/flask-main/docs/patterns/caching.rst | 16 + src/flask-main/docs/patterns/celery.rst | 242 ++ .../docs/patterns/deferredcallbacks.rst | 44 + src/flask-main/docs/patterns/favicon.rst | 56 + src/flask-main/docs/patterns/fileuploads.rst | 182 ++ src/flask-main/docs/patterns/flashing.rst | 148 ++ src/flask-main/docs/patterns/index.rst | 40 + src/flask-main/docs/patterns/javascript.rst | 259 +++ src/flask-main/docs/patterns/jquery.rst | 6 + src/flask-main/docs/patterns/lazyloading.rst | 109 + .../docs/patterns/methodoverrides.rst | 42 + src/flask-main/docs/patterns/mongoengine.rst | 103 + src/flask-main/docs/patterns/packages.rst | 133 ++ .../docs/patterns/requestchecksum.rst | 55 + .../docs/patterns/singlepageapplications.rst | 24 + src/flask-main/docs/patterns/sqlalchemy.rst | 213 ++ src/flask-main/docs/patterns/sqlite3.rst | 147 ++ src/flask-main/docs/patterns/streaming.rst | 85 + src/flask-main/docs/patterns/subclassing.rst | 17 + .../docs/patterns/templateinheritance.rst | 68 + .../docs/patterns/urlprocessors.rst | 126 ++ .../docs/patterns/viewdecorators.rst | 171 ++ src/flask-main/docs/patterns/wtforms.rst | 126 ++ src/flask-main/docs/quickstart.rst | 858 ++++++++ src/flask-main/docs/reqcontext.rst | 6 + src/flask-main/docs/server.rst | 115 + src/flask-main/docs/shell.rst | 81 + src/flask-main/docs/signals.rst | 166 ++ src/flask-main/docs/templating.rst | 255 +++ src/flask-main/docs/testing.rst | 318 +++ src/flask-main/docs/tutorial/blog.rst | 336 +++ src/flask-main/docs/tutorial/database.rst | 219 ++ src/flask-main/docs/tutorial/deploy.rst | 111 + src/flask-main/docs/tutorial/factory.rst | 162 ++ src/flask-main/docs/tutorial/flaskr_edit.png | Bin 0 -> 13259 bytes src/flask-main/docs/tutorial/flaskr_index.png | Bin 0 -> 11675 bytes src/flask-main/docs/tutorial/flaskr_login.png | Bin 0 -> 7455 bytes src/flask-main/docs/tutorial/index.rst | 64 + src/flask-main/docs/tutorial/install.rst | 89 + src/flask-main/docs/tutorial/layout.rst | 110 + src/flask-main/docs/tutorial/next.rst | 38 + src/flask-main/docs/tutorial/static.rst | 72 + src/flask-main/docs/tutorial/templates.rst | 187 ++ src/flask-main/docs/tutorial/tests.rst | 559 +++++ src/flask-main/docs/tutorial/views.rst | 305 +++ src/flask-main/docs/views.rst | 324 +++ src/flask-main/docs/web-security.rst | 316 +++ src/flask-main/examples/celery/README.md | 27 + src/flask-main/examples/celery/make_celery.py | 4 + src/flask-main/examples/celery/pyproject.toml | 17 + .../examples/celery/requirements.txt | 58 + .../examples/celery/src/task_app/__init__.py | 39 + .../examples/celery/src/task_app/tasks.py | 23 + .../celery/src/task_app/templates/index.html | 108 + .../examples/celery/src/task_app/views.py | 38 + src/flask-main/examples/javascript/.gitignore | 14 + .../examples/javascript/LICENSE.txt | 28 + src/flask-main/examples/javascript/README.rst | 48 + .../javascript/js_example/__init__.py | 5 + .../javascript/js_example/templates/base.html | 33 + .../js_example/templates/fetch.html | 33 + .../js_example/templates/jquery.html | 27 + .../javascript/js_example/templates/xhr.html | 29 + .../examples/javascript/js_example/views.py | 18 + .../examples/javascript/pyproject.toml | 33 + .../examples/javascript/tests/conftest.py | 15 + .../javascript/tests/test_js_example.py | 27 + src/flask-main/examples/tutorial/.gitignore | 14 + src/flask-main/examples/tutorial/LICENSE.txt | 28 + src/flask-main/examples/tutorial/README.rst | 68 + .../examples/tutorial/flaskr/__init__.py | 51 + .../examples/tutorial/flaskr/auth.py | 116 + .../examples/tutorial/flaskr/blog.py | 125 ++ src/flask-main/examples/tutorial/flaskr/db.py | 56 + .../examples/tutorial/flaskr/schema.sql | 20 + .../examples/tutorial/flaskr/static/style.css | 134 ++ .../tutorial/flaskr/templates/auth/login.html | 15 + .../flaskr/templates/auth/register.html | 15 + .../tutorial/flaskr/templates/base.html | 24 + .../flaskr/templates/blog/create.html | 15 + .../tutorial/flaskr/templates/blog/index.html | 28 + .../flaskr/templates/blog/update.html | 19 + .../examples/tutorial/pyproject.toml | 40 + .../examples/tutorial/tests/conftest.py | 62 + .../examples/tutorial/tests/data.sql | 8 + .../examples/tutorial/tests/test_auth.py | 69 + .../examples/tutorial/tests/test_blog.py | 83 + .../examples/tutorial/tests/test_db.py | 29 + .../examples/tutorial/tests/test_factory.py | 12 + src/flask-main/pyproject.toml | 278 +++ src/flask-main/src/flask/__init__.py | 39 + src/flask-main/src/flask/__main__.py | 3 + src/flask-main/src/flask/app.py | 1591 ++++++++++++++ src/flask-main/src/flask/blueprints.py | 128 ++ src/flask-main/src/flask/cli.py | 1127 ++++++++++ src/flask-main/src/flask/config.py | 367 ++++ src/flask-main/src/flask/ctx.py | 516 +++++ src/flask-main/src/flask/debughelpers.py | 179 ++ src/flask-main/src/flask/globals.py | 77 + src/flask-main/src/flask/helpers.py | 635 ++++++ src/flask-main/src/flask/json/__init__.py | 170 ++ src/flask-main/src/flask/json/provider.py | 215 ++ src/flask-main/src/flask/json/tag.py | 327 +++ src/flask-main/src/flask/logging.py | 79 + src/flask-main/src/flask/py.typed | 0 src/flask-main/src/flask/sansio/README.md | 6 + src/flask-main/src/flask/sansio/app.py | 1006 +++++++++ src/flask-main/src/flask/sansio/blueprints.py | 692 ++++++ src/flask-main/src/flask/sansio/scaffold.py | 792 +++++++ src/flask-main/src/flask/sessions.py | 399 ++++ src/flask-main/src/flask/signals.py | 17 + src/flask-main/src/flask/templating.py | 211 ++ src/flask-main/src/flask/testing.py | 298 +++ src/flask-main/src/flask/typing.py | 87 + src/flask-main/src/flask/views.py | 191 ++ src/flask-main/src/flask/wrappers.py | 257 +++ src/flask-main/tests/conftest.py | 129 ++ src/flask-main/tests/static/config.json | 4 + src/flask-main/tests/static/config.toml | 2 + src/flask-main/tests/static/index.html | 1 + src/flask-main/tests/templates/_macro.html | 1 + .../tests/templates/context_template.html | 1 + .../tests/templates/escaping_template.html | 6 + src/flask-main/tests/templates/mail.txt | 1 + .../tests/templates/nested/nested.txt | 1 + .../tests/templates/non_escaping_template.txt | 8 + .../tests/templates/simple_template.html | 1 + .../tests/templates/template_filter.html | 1 + .../tests/templates/template_test.html | 3 + src/flask-main/tests/test_appctx.py | 210 ++ src/flask-main/tests/test_apps/.env | 4 + src/flask-main/tests/test_apps/.flaskenv | 3 + .../tests/test_apps/blueprintapp/__init__.py | 9 + .../test_apps/blueprintapp/apps/__init__.py | 0 .../blueprintapp/apps/admin/__init__.py | 20 + .../apps/admin/static/css/test.css | 1 + .../blueprintapp/apps/admin/static/test.txt | 1 + .../apps/admin/templates/admin/index.html | 1 + .../blueprintapp/apps/frontend/__init__.py | 14 + .../frontend/templates/frontend/index.html | 1 + .../tests/test_apps/cliapp/__init__.py | 0 src/flask-main/tests/test_apps/cliapp/app.py | 3 + .../tests/test_apps/cliapp/factory.py | 13 + .../tests/test_apps/cliapp/importerrorapp.py | 5 + .../tests/test_apps/cliapp/inner1/__init__.py | 3 + .../cliapp/inner1/inner2/__init__.py | 0 .../test_apps/cliapp/inner1/inner2/flask.py | 3 + .../tests/test_apps/cliapp/message.txt | 1 + .../tests/test_apps/cliapp/multiapp.py | 4 + .../tests/test_apps/helloworld/hello.py | 8 + .../tests/test_apps/helloworld/wsgi.py | 1 + .../test_apps/subdomaintestmodule/__init__.py | 3 + .../subdomaintestmodule/static/hello.txt | 1 + src/flask-main/tests/test_async.py | 145 ++ src/flask-main/tests/test_basic.py | 1944 +++++++++++++++++ src/flask-main/tests/test_blueprints.py | 1127 ++++++++++ src/flask-main/tests/test_cli.py | 702 ++++++ src/flask-main/tests/test_config.py | 250 +++ src/flask-main/tests/test_converters.py | 42 + src/flask-main/tests/test_helpers.py | 383 ++++ src/flask-main/tests/test_instance_config.py | 111 + src/flask-main/tests/test_json.py | 346 +++ src/flask-main/tests/test_json_tag.py | 86 + src/flask-main/tests/test_logging.py | 98 + src/flask-main/tests/test_regression.py | 30 + src/flask-main/tests/test_reqctx.py | 326 +++ src/flask-main/tests/test_request.py | 70 + .../tests/test_session_interface.py | 28 + src/flask-main/tests/test_signals.py | 181 ++ src/flask-main/tests/test_subclassing.py | 21 + src/flask-main/tests/test_templating.py | 532 +++++ src/flask-main/tests/test_testing.py | 385 ++++ .../tests/test_user_error_handler.py | 295 +++ src/flask-main/tests/test_views.py | 260 +++ .../tests/type_check/typing_app_decorators.py | 32 + .../tests/type_check/typing_error_handler.py | 33 + .../tests/type_check/typing_route.py | 112 + src/flask-main/uv.lock | 1666 ++++++++++++++ src/flask.zip | Bin 0 -> 820084 bytes 229 files changed, 36001 insertions(+) create mode 100644 doc/Flask泛读报告.docx create mode 100644 src/demo_app.py create mode 100644 src/flask-main/.devcontainer/devcontainer.json create mode 100644 src/flask-main/.devcontainer/on-create-command.sh create mode 100644 src/flask-main/.editorconfig create mode 100644 src/flask-main/.gitignore create mode 100644 src/flask-main/.pre-commit-config.yaml create mode 100644 src/flask-main/.readthedocs.yaml create mode 100644 src/flask-main/CHANGES.rst create mode 100644 src/flask-main/LICENSE.txt create mode 100644 src/flask-main/README.md create mode 100644 src/flask-main/docs/Makefile create mode 100644 src/flask-main/docs/_static/debugger.png create mode 100644 src/flask-main/docs/_static/flask-icon.svg create mode 100644 src/flask-main/docs/_static/flask-logo.svg create mode 100644 src/flask-main/docs/_static/flask-name.svg create mode 100644 src/flask-main/docs/_static/pycharm-run-config.png create mode 100644 src/flask-main/docs/api.rst create mode 100644 src/flask-main/docs/appcontext.rst create mode 100644 src/flask-main/docs/async-await.rst create mode 100644 src/flask-main/docs/blueprints.rst create mode 100644 src/flask-main/docs/changes.rst create mode 100644 src/flask-main/docs/cli.rst create mode 100644 src/flask-main/docs/conf.py create mode 100644 src/flask-main/docs/config.rst create mode 100644 src/flask-main/docs/contributing.rst create mode 100644 src/flask-main/docs/debugging.rst create mode 100644 src/flask-main/docs/deploying/apache-httpd.rst create mode 100644 src/flask-main/docs/deploying/asgi.rst create mode 100644 src/flask-main/docs/deploying/eventlet.rst create mode 100644 src/flask-main/docs/deploying/gevent.rst create mode 100644 src/flask-main/docs/deploying/gunicorn.rst create mode 100644 src/flask-main/docs/deploying/index.rst create mode 100644 src/flask-main/docs/deploying/mod_wsgi.rst create mode 100644 src/flask-main/docs/deploying/nginx.rst create mode 100644 src/flask-main/docs/deploying/proxy_fix.rst create mode 100644 src/flask-main/docs/deploying/uwsgi.rst create mode 100644 src/flask-main/docs/deploying/waitress.rst create mode 100644 src/flask-main/docs/design.rst create mode 100644 src/flask-main/docs/errorhandling.rst create mode 100644 src/flask-main/docs/extensiondev.rst create mode 100644 src/flask-main/docs/extensions.rst create mode 100644 src/flask-main/docs/index.rst create mode 100644 src/flask-main/docs/installation.rst create mode 100644 src/flask-main/docs/license.rst create mode 100644 src/flask-main/docs/lifecycle.rst create mode 100644 src/flask-main/docs/logging.rst create mode 100644 src/flask-main/docs/make.bat create mode 100644 src/flask-main/docs/patterns/appdispatch.rst create mode 100644 src/flask-main/docs/patterns/appfactories.rst create mode 100644 src/flask-main/docs/patterns/caching.rst create mode 100644 src/flask-main/docs/patterns/celery.rst create mode 100644 src/flask-main/docs/patterns/deferredcallbacks.rst create mode 100644 src/flask-main/docs/patterns/favicon.rst create mode 100644 src/flask-main/docs/patterns/fileuploads.rst create mode 100644 src/flask-main/docs/patterns/flashing.rst create mode 100644 src/flask-main/docs/patterns/index.rst create mode 100644 src/flask-main/docs/patterns/javascript.rst create mode 100644 src/flask-main/docs/patterns/jquery.rst create mode 100644 src/flask-main/docs/patterns/lazyloading.rst create mode 100644 src/flask-main/docs/patterns/methodoverrides.rst create mode 100644 src/flask-main/docs/patterns/mongoengine.rst create mode 100644 src/flask-main/docs/patterns/packages.rst create mode 100644 src/flask-main/docs/patterns/requestchecksum.rst create mode 100644 src/flask-main/docs/patterns/singlepageapplications.rst create mode 100644 src/flask-main/docs/patterns/sqlalchemy.rst create mode 100644 src/flask-main/docs/patterns/sqlite3.rst create mode 100644 src/flask-main/docs/patterns/streaming.rst create mode 100644 src/flask-main/docs/patterns/subclassing.rst create mode 100644 src/flask-main/docs/patterns/templateinheritance.rst create mode 100644 src/flask-main/docs/patterns/urlprocessors.rst create mode 100644 src/flask-main/docs/patterns/viewdecorators.rst create mode 100644 src/flask-main/docs/patterns/wtforms.rst create mode 100644 src/flask-main/docs/quickstart.rst create mode 100644 src/flask-main/docs/reqcontext.rst create mode 100644 src/flask-main/docs/server.rst create mode 100644 src/flask-main/docs/shell.rst create mode 100644 src/flask-main/docs/signals.rst create mode 100644 src/flask-main/docs/templating.rst create mode 100644 src/flask-main/docs/testing.rst create mode 100644 src/flask-main/docs/tutorial/blog.rst create mode 100644 src/flask-main/docs/tutorial/database.rst create mode 100644 src/flask-main/docs/tutorial/deploy.rst create mode 100644 src/flask-main/docs/tutorial/factory.rst create mode 100644 src/flask-main/docs/tutorial/flaskr_edit.png create mode 100644 src/flask-main/docs/tutorial/flaskr_index.png create mode 100644 src/flask-main/docs/tutorial/flaskr_login.png create mode 100644 src/flask-main/docs/tutorial/index.rst create mode 100644 src/flask-main/docs/tutorial/install.rst create mode 100644 src/flask-main/docs/tutorial/layout.rst create mode 100644 src/flask-main/docs/tutorial/next.rst create mode 100644 src/flask-main/docs/tutorial/static.rst create mode 100644 src/flask-main/docs/tutorial/templates.rst create mode 100644 src/flask-main/docs/tutorial/tests.rst create mode 100644 src/flask-main/docs/tutorial/views.rst create mode 100644 src/flask-main/docs/views.rst create mode 100644 src/flask-main/docs/web-security.rst create mode 100644 src/flask-main/examples/celery/README.md create mode 100644 src/flask-main/examples/celery/make_celery.py create mode 100644 src/flask-main/examples/celery/pyproject.toml create mode 100644 src/flask-main/examples/celery/requirements.txt create mode 100644 src/flask-main/examples/celery/src/task_app/__init__.py create mode 100644 src/flask-main/examples/celery/src/task_app/tasks.py create mode 100644 src/flask-main/examples/celery/src/task_app/templates/index.html create mode 100644 src/flask-main/examples/celery/src/task_app/views.py create mode 100644 src/flask-main/examples/javascript/.gitignore create mode 100644 src/flask-main/examples/javascript/LICENSE.txt create mode 100644 src/flask-main/examples/javascript/README.rst create mode 100644 src/flask-main/examples/javascript/js_example/__init__.py create mode 100644 src/flask-main/examples/javascript/js_example/templates/base.html create mode 100644 src/flask-main/examples/javascript/js_example/templates/fetch.html create mode 100644 src/flask-main/examples/javascript/js_example/templates/jquery.html create mode 100644 src/flask-main/examples/javascript/js_example/templates/xhr.html create mode 100644 src/flask-main/examples/javascript/js_example/views.py create mode 100644 src/flask-main/examples/javascript/pyproject.toml create mode 100644 src/flask-main/examples/javascript/tests/conftest.py create mode 100644 src/flask-main/examples/javascript/tests/test_js_example.py create mode 100644 src/flask-main/examples/tutorial/.gitignore create mode 100644 src/flask-main/examples/tutorial/LICENSE.txt create mode 100644 src/flask-main/examples/tutorial/README.rst create mode 100644 src/flask-main/examples/tutorial/flaskr/__init__.py create mode 100644 src/flask-main/examples/tutorial/flaskr/auth.py create mode 100644 src/flask-main/examples/tutorial/flaskr/blog.py create mode 100644 src/flask-main/examples/tutorial/flaskr/db.py create mode 100644 src/flask-main/examples/tutorial/flaskr/schema.sql create mode 100644 src/flask-main/examples/tutorial/flaskr/static/style.css create mode 100644 src/flask-main/examples/tutorial/flaskr/templates/auth/login.html create mode 100644 src/flask-main/examples/tutorial/flaskr/templates/auth/register.html create mode 100644 src/flask-main/examples/tutorial/flaskr/templates/base.html create mode 100644 src/flask-main/examples/tutorial/flaskr/templates/blog/create.html create mode 100644 src/flask-main/examples/tutorial/flaskr/templates/blog/index.html create mode 100644 src/flask-main/examples/tutorial/flaskr/templates/blog/update.html create mode 100644 src/flask-main/examples/tutorial/pyproject.toml create mode 100644 src/flask-main/examples/tutorial/tests/conftest.py create mode 100644 src/flask-main/examples/tutorial/tests/data.sql create mode 100644 src/flask-main/examples/tutorial/tests/test_auth.py create mode 100644 src/flask-main/examples/tutorial/tests/test_blog.py create mode 100644 src/flask-main/examples/tutorial/tests/test_db.py create mode 100644 src/flask-main/examples/tutorial/tests/test_factory.py create mode 100644 src/flask-main/pyproject.toml create mode 100644 src/flask-main/src/flask/__init__.py create mode 100644 src/flask-main/src/flask/__main__.py create mode 100644 src/flask-main/src/flask/app.py create mode 100644 src/flask-main/src/flask/blueprints.py create mode 100644 src/flask-main/src/flask/cli.py create mode 100644 src/flask-main/src/flask/config.py create mode 100644 src/flask-main/src/flask/ctx.py create mode 100644 src/flask-main/src/flask/debughelpers.py create mode 100644 src/flask-main/src/flask/globals.py create mode 100644 src/flask-main/src/flask/helpers.py create mode 100644 src/flask-main/src/flask/json/__init__.py create mode 100644 src/flask-main/src/flask/json/provider.py create mode 100644 src/flask-main/src/flask/json/tag.py create mode 100644 src/flask-main/src/flask/logging.py create mode 100644 src/flask-main/src/flask/py.typed create mode 100644 src/flask-main/src/flask/sansio/README.md create mode 100644 src/flask-main/src/flask/sansio/app.py create mode 100644 src/flask-main/src/flask/sansio/blueprints.py create mode 100644 src/flask-main/src/flask/sansio/scaffold.py create mode 100644 src/flask-main/src/flask/sessions.py create mode 100644 src/flask-main/src/flask/signals.py create mode 100644 src/flask-main/src/flask/templating.py create mode 100644 src/flask-main/src/flask/testing.py create mode 100644 src/flask-main/src/flask/typing.py create mode 100644 src/flask-main/src/flask/views.py create mode 100644 src/flask-main/src/flask/wrappers.py create mode 100644 src/flask-main/tests/conftest.py create mode 100644 src/flask-main/tests/static/config.json create mode 100644 src/flask-main/tests/static/config.toml create mode 100644 src/flask-main/tests/static/index.html create mode 100644 src/flask-main/tests/templates/_macro.html create mode 100644 src/flask-main/tests/templates/context_template.html create mode 100644 src/flask-main/tests/templates/escaping_template.html create mode 100644 src/flask-main/tests/templates/mail.txt create mode 100644 src/flask-main/tests/templates/nested/nested.txt create mode 100644 src/flask-main/tests/templates/non_escaping_template.txt create mode 100644 src/flask-main/tests/templates/simple_template.html create mode 100644 src/flask-main/tests/templates/template_filter.html create mode 100644 src/flask-main/tests/templates/template_test.html create mode 100644 src/flask-main/tests/test_appctx.py create mode 100644 src/flask-main/tests/test_apps/.env create mode 100644 src/flask-main/tests/test_apps/.flaskenv create mode 100644 src/flask-main/tests/test_apps/blueprintapp/__init__.py create mode 100644 src/flask-main/tests/test_apps/blueprintapp/apps/__init__.py create mode 100644 src/flask-main/tests/test_apps/blueprintapp/apps/admin/__init__.py create mode 100644 src/flask-main/tests/test_apps/blueprintapp/apps/admin/static/css/test.css create mode 100644 src/flask-main/tests/test_apps/blueprintapp/apps/admin/static/test.txt create mode 100644 src/flask-main/tests/test_apps/blueprintapp/apps/admin/templates/admin/index.html create mode 100644 src/flask-main/tests/test_apps/blueprintapp/apps/frontend/__init__.py create mode 100644 src/flask-main/tests/test_apps/blueprintapp/apps/frontend/templates/frontend/index.html create mode 100644 src/flask-main/tests/test_apps/cliapp/__init__.py create mode 100644 src/flask-main/tests/test_apps/cliapp/app.py create mode 100644 src/flask-main/tests/test_apps/cliapp/factory.py create mode 100644 src/flask-main/tests/test_apps/cliapp/importerrorapp.py create mode 100644 src/flask-main/tests/test_apps/cliapp/inner1/__init__.py create mode 100644 src/flask-main/tests/test_apps/cliapp/inner1/inner2/__init__.py create mode 100644 src/flask-main/tests/test_apps/cliapp/inner1/inner2/flask.py create mode 100644 src/flask-main/tests/test_apps/cliapp/message.txt create mode 100644 src/flask-main/tests/test_apps/cliapp/multiapp.py create mode 100644 src/flask-main/tests/test_apps/helloworld/hello.py create mode 100644 src/flask-main/tests/test_apps/helloworld/wsgi.py create mode 100644 src/flask-main/tests/test_apps/subdomaintestmodule/__init__.py create mode 100644 src/flask-main/tests/test_apps/subdomaintestmodule/static/hello.txt create mode 100644 src/flask-main/tests/test_async.py create mode 100644 src/flask-main/tests/test_basic.py create mode 100644 src/flask-main/tests/test_blueprints.py create mode 100644 src/flask-main/tests/test_cli.py create mode 100644 src/flask-main/tests/test_config.py create mode 100644 src/flask-main/tests/test_converters.py create mode 100644 src/flask-main/tests/test_helpers.py create mode 100644 src/flask-main/tests/test_instance_config.py create mode 100644 src/flask-main/tests/test_json.py create mode 100644 src/flask-main/tests/test_json_tag.py create mode 100644 src/flask-main/tests/test_logging.py create mode 100644 src/flask-main/tests/test_regression.py create mode 100644 src/flask-main/tests/test_reqctx.py create mode 100644 src/flask-main/tests/test_request.py create mode 100644 src/flask-main/tests/test_session_interface.py create mode 100644 src/flask-main/tests/test_signals.py create mode 100644 src/flask-main/tests/test_subclassing.py create mode 100644 src/flask-main/tests/test_templating.py create mode 100644 src/flask-main/tests/test_testing.py create mode 100644 src/flask-main/tests/test_user_error_handler.py create mode 100644 src/flask-main/tests/test_views.py create mode 100644 src/flask-main/tests/type_check/typing_app_decorators.py create mode 100644 src/flask-main/tests/type_check/typing_error_handler.py create mode 100644 src/flask-main/tests/type_check/typing_route.py create mode 100644 src/flask-main/uv.lock create mode 100644 src/flask.zip diff --git a/doc/Flask泛读报告.docx b/doc/Flask泛读报告.docx new file mode 100644 index 0000000000000000000000000000000000000000..00cc69a86fb75a8352378066f1487526c0c0fb1d GIT binary patch literal 21596 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 XM~$J3RC)XeC}ab?q>4JuaM4CM- literal 0 HcmV?d00001 diff --git a/src/demo_app.py b/src/demo_app.py new file mode 100644 index 0000000..2dc5857 --- /dev/null +++ b/src/demo_app.py @@ -0,0 +1,153 @@ +# Flask +from flask import Flask, render_template_string, request, redirect, url_for, session, jsonify, flash, g + +app = Flask(__name__) +app.secret_key = 'demo_secret_key_for_testing' + +# 模拟数据库 +users = {} +messages = [] + +# 首页模板 +HOME_TEMPLATE = ''' + + + + Flask 示例应用 + + + +

Flask 示例应用

+ {% for message in get_flashed_messages() %} +
{{ message }}
+ {% endfor %} + + {% if session.get('username') %} +

欢迎, {{ session['username'] }}! 退出

+ {% else %} +

登录 | 注册

+ {% endif %} + +

留言板

+
+ + +
+ +

所有留言:

+ {% for msg in messages %} +
{{ msg.user }}: {{ msg.content }}
+ {% endfor %} + +
+

API 信息 (JSON)

+ + +''' + +LOGIN_TEMPLATE = ''' + + +登录 + +

登录

+
+

+

+ +
+

返回首页

+ + +''' + +REGISTER_TEMPLATE = ''' + + +注册 + +

注册

+
+

+

+ +
+

返回首页

+ + +''' + +@app.before_request +def before_request(): + """请求前钩子 - 记录请求信息""" + g.request_start = 'Request started' + +@app.route('/') +def index(): + """首页路由""" + return render_template_string(HOME_TEMPLATE, messages=messages) + +@app.route('/login', methods=['GET', 'POST']) +def login(): + """登录路由""" + if request.method == 'POST': + username = request.form['username'] + password = request.form['password'] + if username in users and users[username] == password: + session['username'] = username + flash('登录成功!') + return redirect(url_for('index')) + flash('用户名或密码错误!') + return render_template_string(LOGIN_TEMPLATE) + +@app.route('/register', methods=['GET', 'POST']) +def register(): + """注册路由""" + if request.method == 'POST': + username = request.form['username'] + password = request.form['password'] + if username in users: + flash('用户名已存在!') + else: + users[username] = password + flash('注册成功,请登录!') + return redirect(url_for('login')) + return render_template_string(REGISTER_TEMPLATE) + +@app.route('/logout') +def logout(): + """退出登录""" + session.pop('username', None) + flash('已退出登录') + return redirect(url_for('index')) + +@app.route('/message', methods=['POST']) +def add_message(): + """添加留言""" + content = request.form.get('content', '') + username = session.get('username', '匿名用户') + if content: + messages.append({'user': username, 'content': content}) + return redirect(url_for('index')) + +@app.route('/api/info') +def api_info(): + """API 接口 - 返回 JSON 数据""" + return jsonify({ + 'app_name': 'Flask Demo', + 'version': '1.0', + 'total_users': len(users), + 'total_messages': len(messages) + }) + +if __name__ == '__main__': + print("Flask 示例应用") + print("运行命令: flask run 或 python demo_app.py") + print("访问地址: http://127.0.0.1:5000") + app.run(debug=True) diff --git a/src/flask-main/.devcontainer/devcontainer.json b/src/flask-main/.devcontainer/devcontainer.json new file mode 100644 index 0000000..4519826 --- /dev/null +++ b/src/flask-main/.devcontainer/devcontainer.json @@ -0,0 +1,17 @@ +{ + "name": "pallets/flask", + "image": "mcr.microsoft.com/devcontainers/python:3", + "customizations": { + "vscode": { + "settings": { + "python.defaultInterpreterPath": "${workspaceFolder}/.venv", + "python.terminal.activateEnvInCurrentTerminal": true, + "python.terminal.launchArgs": [ + "-X", + "dev" + ] + } + } + }, + "onCreateCommand": ".devcontainer/on-create-command.sh" +} diff --git a/src/flask-main/.devcontainer/on-create-command.sh b/src/flask-main/.devcontainer/on-create-command.sh new file mode 100644 index 0000000..eaebea6 --- /dev/null +++ b/src/flask-main/.devcontainer/on-create-command.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -e +python3 -m venv --upgrade-deps .venv +. .venv/bin/activate +pip install -r requirements/dev.txt +pip install -e . +pre-commit install --install-hooks diff --git a/src/flask-main/.editorconfig b/src/flask-main/.editorconfig new file mode 100644 index 0000000..2ff985a --- /dev/null +++ b/src/flask-main/.editorconfig @@ -0,0 +1,13 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true +end_of_line = lf +charset = utf-8 +max_line_length = 88 + +[*.{css,html,js,json,jsx,scss,ts,tsx,yaml,yml}] +indent_size = 2 diff --git a/src/flask-main/.gitignore b/src/flask-main/.gitignore new file mode 100644 index 0000000..8441e5a --- /dev/null +++ b/src/flask-main/.gitignore @@ -0,0 +1,8 @@ +.idea/ +.vscode/ +__pycache__/ +dist/ +.coverage* +htmlcov/ +.tox/ +docs/_build/ diff --git a/src/flask-main/.pre-commit-config.yaml b/src/flask-main/.pre-commit-config.yaml new file mode 100644 index 0000000..eeeee43 --- /dev/null +++ b/src/flask-main/.pre-commit-config.yaml @@ -0,0 +1,18 @@ +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: 488940d9de1b658fac229e34c521d75a6ea476f2 # frozen: v0.14.5 + hooks: + - id: ruff + - id: ruff-format + - repo: https://github.com/astral-sh/uv-pre-commit + rev: b6675a113e27a9b18f3d60c05794d62ca80c7ab5 # frozen: 0.9.9 + hooks: + - id: uv-lock + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # frozen: v6.0.0 + hooks: + - id: check-merge-conflict + - id: debug-statements + - id: fix-byte-order-marker + - id: trailing-whitespace + - id: end-of-file-fixer diff --git a/src/flask-main/.readthedocs.yaml b/src/flask-main/.readthedocs.yaml new file mode 100644 index 0000000..acbd83f --- /dev/null +++ b/src/flask-main/.readthedocs.yaml @@ -0,0 +1,10 @@ +version: 2 +build: + os: ubuntu-24.04 + tools: + python: '3.13' + commands: + - asdf plugin add uv + - asdf install uv latest + - asdf global uv latest + - uv run --group docs sphinx-build -W -b dirhtml docs $READTHEDOCS_OUTPUT/html diff --git a/src/flask-main/CHANGES.rst b/src/flask-main/CHANGES.rst new file mode 100644 index 0000000..efd076e --- /dev/null +++ b/src/flask-main/CHANGES.rst @@ -0,0 +1,1644 @@ +Version 3.2.0 +------------- + +Unreleased + +- Drop support for Python 3.9. :pr:`5730` +- Remove previously deprecated code: ``__version__``. :pr:`5648` +- ``RequestContext`` has merged with ``AppContext``. ``RequestContext`` is now + a deprecated alias. If an app context is already pushed, it is not reused + when dispatching a request. This greatly simplifies the internal code for tracking + the active context. :issue:`5639` +- Many ``Flask`` methods involved in request dispatch now take the current + ``AppContext`` as the first parameter, instead of using the proxy objects. + If subclasses were overriding these methods, the old signature is detected, + shows a deprecation warning, and will continue to work during the + deprecation period. :issue:`5815` +- ``template_filter``, ``template_test``, and ``template_global`` decorators + can be used without parentheses. :issue:`5729` + + +Version 3.1.2 +------------- + +Released 2025-08-19 + +- ``stream_with_context`` does not fail inside async views. :issue:`5774` +- When using ``follow_redirects`` in the test client, the final state + of ``session`` is correct. :issue:`5786` +- Relax type hint for passing bytes IO to ``send_file``. :issue:`5776` + + +Version 3.1.1 +------------- + +Released 2025-05-13 + +- Fix signing key selection order when key rotation is enabled via + ``SECRET_KEY_FALLBACKS``. :ghsa:`4grg-w6v8-c28g` +- Fix type hint for ``cli_runner.invoke``. :issue:`5645` +- ``flask --help`` loads the app and plugins first to make sure all commands + are shown. :issue:`5673` +- Mark sans-io base class as being able to handle views that return + ``AsyncIterable``. This is not accurate for Flask, but makes typing easier + for Quart. :pr:`5659` + + +Version 3.1.0 +------------- + +Released 2024-11-13 + +- Drop support for Python 3.8. :pr:`5623` +- Update minimum dependency versions to latest feature releases. + Werkzeug >= 3.1, ItsDangerous >= 2.2, Blinker >= 1.9. :pr:`5624,5633` +- Provide a configuration option to control automatic option + responses. :pr:`5496` +- ``Flask.open_resource``/``open_instance_resource`` and + ``Blueprint.open_resource`` take an ``encoding`` parameter to use when + opening in text mode. It defaults to ``utf-8``. :issue:`5504` +- ``Request.max_content_length`` can be customized per-request instead of only + through the ``MAX_CONTENT_LENGTH`` config. Added + ``MAX_FORM_MEMORY_SIZE`` and ``MAX_FORM_PARTS`` config. Added documentation + about resource limits to the security page. :issue:`5625` +- Add support for the ``Partitioned`` cookie attribute (CHIPS), with the + ``SESSION_COOKIE_PARTITIONED`` config. :issue:`5472` +- ``-e path`` takes precedence over default ``.env`` and ``.flaskenv`` files. + ``load_dotenv`` loads default files in addition to a path unless + ``load_defaults=False`` is passed. :issue:`5628` +- Support key rotation with the ``SECRET_KEY_FALLBACKS`` config, a list of old + secret keys that can still be used for unsigning. Extensions will need to + add support. :issue:`5621` +- Fix how setting ``host_matching=True`` or ``subdomain_matching=False`` + interacts with ``SERVER_NAME``. Setting ``SERVER_NAME`` no longer restricts + requests to only that domain. :issue:`5553` +- ``Request.trusted_hosts`` is checked during routing, and can be set through + the ``TRUSTED_HOSTS`` config. :issue:`5636` + + +Version 3.0.3 +------------- + +Released 2024-04-07 + +- The default ``hashlib.sha1`` may not be available in FIPS builds. Don't + access it at import time so the developer has time to change the default. + :issue:`5448` +- Don't initialize the ``cli`` attribute in the sansio scaffold, but rather in + the ``Flask`` concrete class. :pr:`5270` + + +Version 3.0.2 +------------- + +Released 2024-02-03 + +- Correct type for ``jinja_loader`` property. :issue:`5388` +- Fix error with ``--extra-files`` and ``--exclude-patterns`` CLI options. + :issue:`5391` + + +Version 3.0.1 +------------- + +Released 2024-01-18 + +- Correct type for ``path`` argument to ``send_file``. :issue:`5336` +- Fix a typo in an error message for the ``flask run --key`` option. :pr:`5344` +- Session data is untagged without relying on the built-in ``json.loads`` + ``object_hook``. This allows other JSON providers that don't implement that. + :issue:`5381` +- Address more type findings when using mypy strict mode. :pr:`5383` + + +Version 3.0.0 +------------- + +Released 2023-09-30 + +- Remove previously deprecated code. :pr:`5223` +- Deprecate the ``__version__`` attribute. Use feature detection, or + ``importlib.metadata.version("flask")``, instead. :issue:`5230` +- Restructure the code such that the Flask (app) and Blueprint + classes have Sans-IO bases. :pr:`5127` +- Allow self as an argument to url_for. :pr:`5264` +- Require Werkzeug >= 3.0.0. + + +Version 2.3.3 +------------- + +Released 2023-08-21 + +- Python 3.12 compatibility. +- Require Werkzeug >= 2.3.7. +- Use ``flit_core`` instead of ``setuptools`` as build backend. +- Refactor how an app's root and instance paths are determined. :issue:`5160` + + +Version 2.3.2 +------------- + +Released 2023-05-01 + +- Set ``Vary: Cookie`` header when the session is accessed, modified, or refreshed. +- Update Werkzeug requirement to >=2.3.3 to apply recent bug fixes. + :ghsa:`m2qf-hxjv-5gpq` + + +Version 2.3.1 +------------- + +Released 2023-04-25 + +- Restore deprecated ``from flask import Markup``. :issue:`5084` + + +Version 2.3.0 +------------- + +Released 2023-04-25 + +- Drop support for Python 3.7. :pr:`5072` +- Update minimum requirements to the latest versions: Werkzeug>=2.3.0, Jinja2>3.1.2, + itsdangerous>=2.1.2, click>=8.1.3. +- Remove previously deprecated code. :pr:`4995` + + - The ``push`` and ``pop`` methods of the deprecated ``_app_ctx_stack`` and + ``_request_ctx_stack`` objects are removed. ``top`` still exists to give + extensions more time to update, but it will be removed. + - The ``FLASK_ENV`` environment variable, ``ENV`` config key, and ``app.env`` + property are removed. + - The ``session_cookie_name``, ``send_file_max_age_default``, ``use_x_sendfile``, + ``propagate_exceptions``, and ``templates_auto_reload`` properties on ``app`` + are removed. + - The ``JSON_AS_ASCII``, ``JSON_SORT_KEYS``, ``JSONIFY_MIMETYPE``, and + ``JSONIFY_PRETTYPRINT_REGULAR`` config keys are removed. + - The ``app.before_first_request`` and ``bp.before_app_first_request`` decorators + are removed. + - ``json_encoder`` and ``json_decoder`` attributes on app and blueprint, and the + corresponding ``json.JSONEncoder`` and ``JSONDecoder`` classes, are removed. + - The ``json.htmlsafe_dumps`` and ``htmlsafe_dump`` functions are removed. + - Calling setup methods on blueprints after registration is an error instead of a + warning. :pr:`4997` + +- Importing ``escape`` and ``Markup`` from ``flask`` is deprecated. Import them + directly from ``markupsafe`` instead. :pr:`4996` +- The ``app.got_first_request`` property is deprecated. :pr:`4997` +- The ``locked_cached_property`` decorator is deprecated. Use a lock inside the + decorated function if locking is needed. :issue:`4993` +- Signals are always available. ``blinker>=1.6.2`` is a required dependency. The + ``signals_available`` attribute is deprecated. :issue:`5056` +- Signals support ``async`` subscriber functions. :pr:`5049` +- Remove uses of locks that could cause requests to block each other very briefly. + :issue:`4993` +- Use modern packaging metadata with ``pyproject.toml`` instead of ``setup.cfg``. + :pr:`4947` +- Ensure subdomains are applied with nested blueprints. :issue:`4834` +- ``config.from_file`` can use ``text=False`` to indicate that the parser wants a + binary file instead. :issue:`4989` +- If a blueprint is created with an empty name it raises a ``ValueError``. + :issue:`5010` +- ``SESSION_COOKIE_DOMAIN`` does not fall back to ``SERVER_NAME``. The default is not + to set the domain, which modern browsers interpret as an exact match rather than + a subdomain match. Warnings about ``localhost`` and IP addresses are also removed. + :issue:`5051` +- The ``routes`` command shows each rule's ``subdomain`` or ``host`` when domain + matching is in use. :issue:`5004` +- Use postponed evaluation of annotations. :pr:`5071` + + +Version 2.2.5 +------------- + +Released 2023-05-02 + +- Update for compatibility with Werkzeug 2.3.3. +- Set ``Vary: Cookie`` header when the session is accessed, modified, or refreshed. + + +Version 2.2.4 +------------- + +Released 2023-04-25 + +- Update for compatibility with Werkzeug 2.3. + + +Version 2.2.3 +------------- + +Released 2023-02-15 + +- Autoescape is enabled by default for ``.svg`` template files. :issue:`4831` +- Fix the type of ``template_folder`` to accept ``pathlib.Path``. :issue:`4892` +- Add ``--debug`` option to the ``flask run`` command. :issue:`4777` + + +Version 2.2.2 +------------- + +Released 2022-08-08 + +- Update Werkzeug dependency to >= 2.2.2. This includes fixes related + to the new faster router, header parsing, and the development + server. :pr:`4754` +- Fix the default value for ``app.env`` to be ``"production"``. This + attribute remains deprecated. :issue:`4740` + + +Version 2.2.1 +------------- + +Released 2022-08-03 + +- Setting or accessing ``json_encoder`` or ``json_decoder`` raises a + deprecation warning. :issue:`4732` + + +Version 2.2.0 +------------- + +Released 2022-08-01 + +- Remove previously deprecated code. :pr:`4667` + + - Old names for some ``send_file`` parameters have been removed. + ``download_name`` replaces ``attachment_filename``, ``max_age`` + replaces ``cache_timeout``, and ``etag`` replaces ``add_etags``. + Additionally, ``path`` replaces ``filename`` in + ``send_from_directory``. + - The ``RequestContext.g`` property returning ``AppContext.g`` is + removed. + +- Update Werkzeug dependency to >= 2.2. +- The app and request contexts are managed using Python context vars + directly rather than Werkzeug's ``LocalStack``. This should result + in better performance and memory use. :pr:`4682` + + - Extension maintainers, be aware that ``_app_ctx_stack.top`` + and ``_request_ctx_stack.top`` are deprecated. Store data on + ``g`` instead using a unique prefix, like + ``g._extension_name_attr``. + +- The ``FLASK_ENV`` environment variable and ``app.env`` attribute are + deprecated, removing the distinction between development and debug + mode. Debug mode should be controlled directly using the ``--debug`` + option or ``app.run(debug=True)``. :issue:`4714` +- Some attributes that proxied config keys on ``app`` are deprecated: + ``session_cookie_name``, ``send_file_max_age_default``, + ``use_x_sendfile``, ``propagate_exceptions``, and + ``templates_auto_reload``. Use the relevant config keys instead. + :issue:`4716` +- Add new customization points to the ``Flask`` app object for many + previously global behaviors. + + - ``flask.url_for`` will call ``app.url_for``. :issue:`4568` + - ``flask.abort`` will call ``app.aborter``. + ``Flask.aborter_class`` and ``Flask.make_aborter`` can be used + to customize this aborter. :issue:`4567` + - ``flask.redirect`` will call ``app.redirect``. :issue:`4569` + - ``flask.json`` is an instance of ``JSONProvider``. A different + provider can be set to use a different JSON library. + ``flask.jsonify`` will call ``app.json.response``, other + functions in ``flask.json`` will call corresponding functions in + ``app.json``. :pr:`4692` + +- JSON configuration is moved to attributes on the default + ``app.json`` provider. ``JSON_AS_ASCII``, ``JSON_SORT_KEYS``, + ``JSONIFY_MIMETYPE``, and ``JSONIFY_PRETTYPRINT_REGULAR`` are + deprecated. :pr:`4692` +- Setting custom ``json_encoder`` and ``json_decoder`` classes on the + app or a blueprint, and the corresponding ``json.JSONEncoder`` and + ``JSONDecoder`` classes, are deprecated. JSON behavior can now be + overridden using the ``app.json`` provider interface. :pr:`4692` +- ``json.htmlsafe_dumps`` and ``json.htmlsafe_dump`` are deprecated, + the function is built-in to Jinja now. :pr:`4692` +- Refactor ``register_error_handler`` to consolidate error checking. + Rewrite some error messages to be more consistent. :issue:`4559` +- Use Blueprint decorators and functions intended for setup after + registering the blueprint will show a warning. In the next version, + this will become an error just like the application setup methods. + :issue:`4571` +- ``before_first_request`` is deprecated. Run setup code when creating + the application instead. :issue:`4605` +- Added the ``View.init_every_request`` class attribute. If a view + subclass sets this to ``False``, the view will not create a new + instance on every request. :issue:`2520`. +- A ``flask.cli.FlaskGroup`` Click group can be nested as a + sub-command in a custom CLI. :issue:`3263` +- Add ``--app`` and ``--debug`` options to the ``flask`` CLI, instead + of requiring that they are set through environment variables. + :issue:`2836` +- Add ``--env-file`` option to the ``flask`` CLI. This allows + specifying a dotenv file to load in addition to ``.env`` and + ``.flaskenv``. :issue:`3108` +- It is no longer required to decorate custom CLI commands on + ``app.cli`` or ``blueprint.cli`` with ``@with_appcontext``, an app + context will already be active at that point. :issue:`2410` +- ``SessionInterface.get_expiration_time`` uses a timezone-aware + value. :pr:`4645` +- View functions can return generators directly instead of wrapping + them in a ``Response``. :pr:`4629` +- Add ``stream_template`` and ``stream_template_string`` functions to + render a template as a stream of pieces. :pr:`4629` +- A new implementation of context preservation during debugging and + testing. :pr:`4666` + + - ``request``, ``g``, and other context-locals point to the + correct data when running code in the interactive debugger + console. :issue:`2836` + - Teardown functions are always run at the end of the request, + even if the context is preserved. They are also run after the + preserved context is popped. + - ``stream_with_context`` preserves context separately from a + ``with client`` block. It will be cleaned up when + ``response.get_data()`` or ``response.close()`` is called. + +- Allow returning a list from a view function, to convert it to a + JSON response like a dict is. :issue:`4672` +- When type checking, allow ``TypedDict`` to be returned from view + functions. :pr:`4695` +- Remove the ``--eager-loading/--lazy-loading`` options from the + ``flask run`` command. The app is always eager loaded the first + time, then lazily loaded in the reloader. The reloader always prints + errors immediately but continues serving. Remove the internal + ``DispatchingApp`` middleware used by the previous implementation. + :issue:`4715` + + +Version 2.1.3 +------------- + +Released 2022-07-13 + +- Inline some optional imports that are only used for certain CLI + commands. :pr:`4606` +- Relax type annotation for ``after_request`` functions. :issue:`4600` +- ``instance_path`` for namespace packages uses the path closest to + the imported submodule. :issue:`4610` +- Clearer error message when ``render_template`` and + ``render_template_string`` are used outside an application context. + :pr:`4693` + + +Version 2.1.2 +------------- + +Released 2022-04-28 + +- Fix type annotation for ``json.loads``, it accepts str or bytes. + :issue:`4519` +- The ``--cert`` and ``--key`` options on ``flask run`` can be given + in either order. :issue:`4459` + + +Version 2.1.1 +------------- + +Released on 2022-03-30 + +- Set the minimum required version of importlib_metadata to 3.6.0, + which is required on Python < 3.10. :issue:`4502` + + +Version 2.1.0 +------------- + +Released 2022-03-28 + +- Drop support for Python 3.6. :pr:`4335` +- Update Click dependency to >= 8.0. :pr:`4008` +- Remove previously deprecated code. :pr:`4337` + + - The CLI does not pass ``script_info`` to app factory functions. + - ``config.from_json`` is replaced by + ``config.from_file(name, load=json.load)``. + - ``json`` functions no longer take an ``encoding`` parameter. + - ``safe_join`` is removed, use ``werkzeug.utils.safe_join`` + instead. + - ``total_seconds`` is removed, use ``timedelta.total_seconds`` + instead. + - The same blueprint cannot be registered with the same name. Use + ``name=`` when registering to specify a unique name. + - The test client's ``as_tuple`` parameter is removed. Use + ``response.request.environ`` instead. :pr:`4417` + +- Some parameters in ``send_file`` and ``send_from_directory`` were + renamed in 2.0. The deprecation period for the old names is extended + to 2.2. Be sure to test with deprecation warnings visible. + + - ``attachment_filename`` is renamed to ``download_name``. + - ``cache_timeout`` is renamed to ``max_age``. + - ``add_etags`` is renamed to ``etag``. + - ``filename`` is renamed to ``path``. + +- The ``RequestContext.g`` property is deprecated. Use ``g`` directly + or ``AppContext.g`` instead. :issue:`3898` +- ``copy_current_request_context`` can decorate async functions. + :pr:`4303` +- The CLI uses ``importlib.metadata`` instead of ``pkg_resources`` to + load command entry points. :issue:`4419` +- Overriding ``FlaskClient.open`` will not cause an error on redirect. + :issue:`3396` +- Add an ``--exclude-patterns`` option to the ``flask run`` CLI + command to specify patterns that will be ignored by the reloader. + :issue:`4188` +- When using lazy loading (the default with the debugger), the Click + context from the ``flask run`` command remains available in the + loader thread. :issue:`4460` +- Deleting the session cookie uses the ``httponly`` flag. + :issue:`4485` +- Relax typing for ``errorhandler`` to allow the user to use more + precise types and decorate the same function multiple times. + :issue:`4095, 4295, 4297` +- Fix typing for ``__exit__`` methods for better compatibility with + ``ExitStack``. :issue:`4474` +- From Werkzeug, for redirect responses the ``Location`` header URL + will remain relative, and exclude the scheme and domain, by default. + :pr:`4496` +- Add ``Config.from_prefixed_env()`` to load config values from + environment variables that start with ``FLASK_`` or another prefix. + This parses values as JSON by default, and allows setting keys in + nested dicts. :pr:`4479` + + +Version 2.0.3 +------------- + +Released 2022-02-14 + +- The test client's ``as_tuple`` parameter is deprecated and will be + removed in Werkzeug 2.1. It is now also deprecated in Flask, to be + removed in Flask 2.1, while remaining compatible with both in + 2.0.x. Use ``response.request.environ`` instead. :pr:`4341` +- Fix type annotation for ``errorhandler`` decorator. :issue:`4295` +- Revert a change to the CLI that caused it to hide ``ImportError`` + tracebacks when importing the application. :issue:`4307` +- ``app.json_encoder`` and ``json_decoder`` are only passed to + ``dumps`` and ``loads`` if they have custom behavior. This improves + performance, mainly on PyPy. :issue:`4349` +- Clearer error message when ``after_this_request`` is used outside a + request context. :issue:`4333` + + +Version 2.0.2 +------------- + +Released 2021-10-04 + +- Fix type annotation for ``teardown_*`` methods. :issue:`4093` +- Fix type annotation for ``before_request`` and ``before_app_request`` + decorators. :issue:`4104` +- Fixed the issue where typing requires template global + decorators to accept functions with no arguments. :issue:`4098` +- Support View and MethodView instances with async handlers. :issue:`4112` +- Enhance typing of ``app.errorhandler`` decorator. :issue:`4095` +- Fix registering a blueprint twice with differing names. :issue:`4124` +- Fix the type of ``static_folder`` to accept ``pathlib.Path``. + :issue:`4150` +- ``jsonify`` handles ``decimal.Decimal`` by encoding to ``str``. + :issue:`4157` +- Correctly handle raising deferred errors in CLI lazy loading. + :issue:`4096` +- The CLI loader handles ``**kwargs`` in a ``create_app`` function. + :issue:`4170` +- Fix the order of ``before_request`` and other callbacks that trigger + before the view returns. They are called from the app down to the + closest nested blueprint. :issue:`4229` + + +Version 2.0.1 +------------- + +Released 2021-05-21 + +- Re-add the ``filename`` parameter in ``send_from_directory``. The + ``filename`` parameter has been renamed to ``path``, the old name + is deprecated. :pr:`4019` +- Mark top-level names as exported so type checking understands + imports in user projects. :issue:`4024` +- Fix type annotation for ``g`` and inform mypy that it is a namespace + object that has arbitrary attributes. :issue:`4020` +- Fix some types that weren't available in Python 3.6.0. :issue:`4040` +- Improve typing for ``send_file``, ``send_from_directory``, and + ``get_send_file_max_age``. :issue:`4044`, :pr:`4026` +- Show an error when a blueprint name contains a dot. The ``.`` has + special meaning, it is used to separate (nested) blueprint names and + the endpoint name. :issue:`4041` +- Combine URL prefixes when nesting blueprints that were created with + a ``url_prefix`` value. :issue:`4037` +- Revert a change to the order that URL matching was done. The + URL is again matched after the session is loaded, so the session is + available in custom URL converters. :issue:`4053` +- Re-add deprecated ``Config.from_json``, which was accidentally + removed early. :issue:`4078` +- Improve typing for some functions using ``Callable`` in their type + signatures, focusing on decorator factories. :issue:`4060` +- Nested blueprints are registered with their dotted name. This allows + different blueprints with the same name to be nested at different + locations. :issue:`4069` +- ``register_blueprint`` takes a ``name`` option 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``. Registering the same blueprint with the same name + multiple times is deprecated. :issue:`1091` +- Improve typing for ``stream_with_context``. :issue:`4052` + + +Version 2.0.0 +------------- + +Released 2021-05-11 + +- Drop support for Python 2 and 3.5. +- Bump minimum versions of other Pallets projects: Werkzeug >= 2, + Jinja2 >= 3, MarkupSafe >= 2, ItsDangerous >= 2, Click >= 8. Be sure + to check the change logs for each project. For better compatibility + with other applications (e.g. Celery) that still require Click 7, + there is no hard dependency on Click 8 yet, but using Click 7 will + trigger a DeprecationWarning and Flask 2.1 will depend on Click 8. +- JSON support no longer uses simplejson. To use another JSON module, + override ``app.json_encoder`` and ``json_decoder``. :issue:`3555` +- The ``encoding`` option to JSON functions is deprecated. :pr:`3562` +- Passing ``script_info`` to app factory functions is deprecated. This + was not portable outside the ``flask`` command. Use + ``click.get_current_context().obj`` if it's needed. :issue:`3552` +- The CLI shows better error messages when the app failed to load + when looking up commands. :issue:`2741` +- Add ``SessionInterface.get_cookie_name`` to allow setting the + session cookie name dynamically. :pr:`3369` +- Add ``Config.from_file`` to load config using arbitrary file + loaders, such as ``toml.load`` or ``json.load``. + ``Config.from_json`` is deprecated in favor of this. :pr:`3398` +- The ``flask run`` command will only defer errors on reload. Errors + present during the initial call will cause the server to exit with + the traceback immediately. :issue:`3431` +- ``send_file`` raises a ``ValueError`` when passed an ``io`` object + in text mode. Previously, it would respond with 200 OK and an empty + file. :issue:`3358` +- When using ad-hoc certificates, check for the cryptography library + instead of PyOpenSSL. :pr:`3492` +- When specifying a factory function with ``FLASK_APP``, keyword + argument can be passed. :issue:`3553` +- When loading a ``.env`` or ``.flaskenv`` file, the current working + directory is no longer changed to the location of the file. + :pr:`3560` +- When returning a ``(response, headers)`` tuple from a view, the + headers replace rather than extend existing headers on the response. + For example, this allows setting the ``Content-Type`` for + ``jsonify()``. Use ``response.headers.extend()`` if extending is + desired. :issue:`3628` +- The ``Scaffold`` class provides a common API for the ``Flask`` and + ``Blueprint`` classes. ``Blueprint`` information is stored in + attributes just like ``Flask``, rather than opaque lambda functions. + This is intended to improve consistency and maintainability. + :issue:`3215` +- Include ``samesite`` and ``secure`` options when removing the + session cookie. :pr:`3726` +- Support passing a ``pathlib.Path`` to ``static_folder``. :pr:`3579` +- ``send_file`` and ``send_from_directory`` are wrappers around the + implementations in ``werkzeug.utils``. :pr:`3828` +- Some ``send_file`` parameters have been renamed, the old names are + deprecated. ``attachment_filename`` is renamed to ``download_name``. + ``cache_timeout`` is renamed to ``max_age``. ``add_etags`` is + renamed to ``etag``. :pr:`3828, 3883` +- ``send_file`` passes ``download_name`` even if + ``as_attachment=False`` by using ``Content-Disposition: inline``. + :pr:`3828` +- ``send_file`` sets ``conditional=True`` and ``max_age=None`` by + default. ``Cache-Control`` is set to ``no-cache`` if ``max_age`` is + not set, otherwise ``public``. This tells browsers to validate + conditional requests instead of using a timed cache. :pr:`3828` +- ``helpers.safe_join`` is deprecated. Use + ``werkzeug.utils.safe_join`` instead. :pr:`3828` +- The request context does route matching before opening the session. + This could allow a session interface to change behavior based on + ``request.endpoint``. :issue:`3776` +- Use Jinja's implementation of the ``|tojson`` filter. :issue:`3881` +- Add route decorators for common HTTP methods. For example, + ``@app.post("/login")`` is a shortcut for + ``@app.route("/login", methods=["POST"])``. :pr:`3907` +- Support async views, error handlers, before and after request, and + teardown functions. :pr:`3412` +- Support nesting blueprints. :issue:`593, 1548`, :pr:`3923` +- Set the default encoding to "UTF-8" when loading ``.env`` and + ``.flaskenv`` files to allow to use non-ASCII characters. :issue:`3931` +- ``flask shell`` sets up tab and history completion like the default + ``python`` shell if ``readline`` is installed. :issue:`3941` +- ``helpers.total_seconds()`` is deprecated. Use + ``timedelta.total_seconds()`` instead. :pr:`3962` +- Add type hinting. :pr:`3973`. + + +Version 1.1.4 +------------- + +Released 2021-05-13 + +- Update ``static_folder`` to use ``_compat.fspath`` instead of + ``os.fspath`` to continue supporting Python < 3.6 :issue:`4050` + + +Version 1.1.3 +------------- + +Released 2021-05-13 + +- Set maximum versions of Werkzeug, Jinja, Click, and ItsDangerous. + :issue:`4043` +- Re-add support for passing a ``pathlib.Path`` for ``static_folder``. + :pr:`3579` + + +Version 1.1.2 +------------- + +Released 2020-04-03 + +- Work around an issue when running the ``flask`` command with an + external debugger on Windows. :issue:`3297` +- The static route will not catch all URLs if the ``Flask`` + ``static_folder`` argument ends with a slash. :issue:`3452` + + +Version 1.1.1 +------------- + +Released 2019-07-08 + +- The ``flask.json_available`` flag was added back for compatibility + with some extensions. It will raise a deprecation warning when used, + and will be removed in version 2.0.0. :issue:`3288` + + +Version 1.1.0 +------------- + +Released 2019-07-04 + +- Bump minimum Werkzeug version to >= 0.15. +- Drop support for Python 3.4. +- Error handlers for ``InternalServerError`` or ``500`` will always be + passed an instance of ``InternalServerError``. If they are invoked + due to an unhandled exception, that original exception is now + available as ``e.original_exception`` rather than being passed + directly to the handler. The same is true if the handler is for the + base ``HTTPException``. This makes error handler behavior more + consistent. :pr:`3266` + + - ``Flask.finalize_request`` is called for all unhandled + exceptions even if there is no ``500`` error handler. + +- ``Flask.logger`` takes the same name as ``Flask.name`` (the value + passed as ``Flask(import_name)``. This reverts 1.0's behavior of + always logging to ``"flask.app"``, in order to support multiple apps + in the same process. A warning will be shown if old configuration is + detected that needs to be moved. :issue:`2866` +- ``RequestContext.copy`` includes the current session object in the + request context copy. This prevents ``session`` pointing to an + out-of-date object. :issue:`2935` +- Using built-in RequestContext, unprintable Unicode characters in + Host header will result in a HTTP 400 response and not HTTP 500 as + previously. :pr:`2994` +- ``send_file`` supports ``PathLike`` objects as described in + :pep:`519`, to support ``pathlib`` in Python 3. :pr:`3059` +- ``send_file`` supports ``BytesIO`` partial content. + :issue:`2957` +- ``open_resource`` accepts the "rt" file mode. This still does the + same thing as "r". :issue:`3163` +- The ``MethodView.methods`` attribute set in a base class is used by + subclasses. :issue:`3138` +- ``Flask.jinja_options`` is a ``dict`` instead of an + ``ImmutableDict`` to allow easier configuration. Changes must still + be made before creating the environment. :pr:`3190` +- Flask's ``JSONMixin`` for the request and response wrappers was + moved into Werkzeug. Use Werkzeug's version with Flask-specific + support. This bumps the Werkzeug dependency to >= 0.15. + :issue:`3125` +- The ``flask`` command entry point is simplified to take advantage + of Werkzeug 0.15's better reloader support. This bumps the Werkzeug + dependency to >= 0.15. :issue:`3022` +- Support ``static_url_path`` that ends with a forward slash. + :issue:`3134` +- Support empty ``static_folder`` without requiring setting an empty + ``static_url_path`` as well. :pr:`3124` +- ``jsonify`` supports ``dataclass`` objects. :pr:`3195` +- Allow customizing the ``Flask.url_map_class`` used for routing. + :pr:`3069` +- The development server port can be set to 0, which tells the OS to + pick an available port. :issue:`2926` +- The return value from ``cli.load_dotenv`` is more consistent with + the documentation. It will return ``False`` if python-dotenv is not + installed, or if the given path isn't a file. :issue:`2937` +- Signaling support has a stub for the ``connect_via`` method when + the Blinker library is not installed. :pr:`3208` +- Add an ``--extra-files`` option to the ``flask run`` CLI command to + specify extra files that will trigger the reloader on change. + :issue:`2897` +- Allow returning a dictionary from a view function. Similar to how + returning a string will produce a ``text/html`` response, returning + a dict will call ``jsonify`` to produce a ``application/json`` + response. :pr:`3111` +- Blueprints have a ``cli`` Click group like ``app.cli``. CLI commands + registered with a blueprint will be available as a group under the + ``flask`` command. :issue:`1357`. +- When using the test client as a context manager (``with client:``), + all preserved request contexts are popped when the block exits, + ensuring nested contexts are cleaned up correctly. :pr:`3157` +- Show a better error message when the view return type is not + supported. :issue:`3214` +- ``flask.testing.make_test_environ_builder()`` has been deprecated in + favour of a new class ``flask.testing.EnvironBuilder``. :pr:`3232` +- The ``flask run`` command no longer fails if Python is not built + with SSL support. Using the ``--cert`` option will show an + appropriate error message. :issue:`3211` +- URL matching now occurs after the request context is pushed, rather + than when it's created. This allows custom URL converters to access + the app and request contexts, such as to query a database for an id. + :issue:`3088` + + +Version 1.0.4 +------------- + +Released 2019-07-04 + +- The key information for ``BadRequestKeyError`` is no longer cleared + outside debug mode, so error handlers can still access it. This + requires upgrading to Werkzeug 0.15.5. :issue:`3249` +- ``send_file`` url quotes the ":" and "/" characters for more + compatible UTF-8 filename support in some browsers. :issue:`3074` +- Fixes for :pep:`451` import loaders and pytest 5.x. :issue:`3275` +- Show message about dotenv on stderr instead of stdout. :issue:`3285` + + +Version 1.0.3 +------------- + +Released 2019-05-17 + +- ``send_file`` encodes filenames as ASCII instead of Latin-1 + (ISO-8859-1). This fixes compatibility with Gunicorn, which is + stricter about header encodings than :pep:`3333`. :issue:`2766` +- Allow custom CLIs using ``FlaskGroup`` to set the debug flag without + it always being overwritten based on environment variables. + :pr:`2765` +- ``flask --version`` outputs Werkzeug's version and simplifies the + Python version. :pr:`2825` +- ``send_file`` handles an ``attachment_filename`` that is a native + Python 2 string (bytes) with UTF-8 coded bytes. :issue:`2933` +- A catch-all error handler registered for ``HTTPException`` will not + handle ``RoutingException``, which is used internally during + routing. This fixes the unexpected behavior that had been introduced + in 1.0. :pr:`2986` +- Passing the ``json`` argument to ``app.test_client`` does not + push/pop an extra app context. :issue:`2900` + + +Version 1.0.2 +------------- + +Released 2018-05-02 + +- Fix more backwards compatibility issues with merging slashes between + a blueprint prefix and route. :pr:`2748` +- Fix error with ``flask routes`` command when there are no routes. + :issue:`2751` + + +Version 1.0.1 +------------- + +Released 2018-04-29 + +- Fix registering partials (with no ``__name__``) as view functions. + :pr:`2730` +- Don't treat lists returned from view functions the same as tuples. + Only tuples are interpreted as response data. :issue:`2736` +- Extra slashes between a blueprint's ``url_prefix`` and a route URL + are merged. This fixes some backwards compatibility issues with the + change in 1.0. :issue:`2731`, :issue:`2742` +- Only trap ``BadRequestKeyError`` errors in debug mode, not all + ``BadRequest`` errors. This allows ``abort(400)`` to continue + working as expected. :issue:`2735` +- The ``FLASK_SKIP_DOTENV`` environment variable can be set to ``1`` + to skip automatically loading dotenv files. :issue:`2722` + + +Version 1.0 +----------- + +Released 2018-04-26 + +- Python 2.6 and 3.3 are no longer supported. +- Bump minimum dependency versions to the latest stable versions: + Werkzeug >= 0.14, Jinja >= 2.10, itsdangerous >= 0.24, Click >= 5.1. + :issue:`2586` +- Skip ``app.run`` when a Flask application is run from the command + line. This avoids some behavior that was confusing to debug. +- Change the default for ``JSONIFY_PRETTYPRINT_REGULAR`` to + ``False``. ``~json.jsonify`` returns a compact format by default, + and an indented format in debug mode. :pr:`2193` +- ``Flask.__init__`` accepts the ``host_matching`` argument and sets + it on ``Flask.url_map``. :issue:`1559` +- ``Flask.__init__`` accepts the ``static_host`` argument and passes + it as the ``host`` argument when defining the static route. + :issue:`1559` +- ``send_file`` supports Unicode in ``attachment_filename``. + :pr:`2223` +- Pass ``_scheme`` argument from ``url_for`` to + ``Flask.handle_url_build_error``. :pr:`2017` +- ``Flask.add_url_rule`` accepts the ``provide_automatic_options`` + argument to disable adding the ``OPTIONS`` method. :pr:`1489` +- ``MethodView`` subclasses inherit method handlers from base classes. + :pr:`1936` +- Errors caused while opening the session at the beginning of the + request are handled by the app's error handlers. :pr:`2254` +- Blueprints gained ``Blueprint.json_encoder`` and + ``Blueprint.json_decoder`` attributes to override the app's + encoder and decoder. :pr:`1898` +- ``Flask.make_response`` raises ``TypeError`` instead of + ``ValueError`` for bad response types. The error messages have been + improved to describe why the type is invalid. :pr:`2256` +- Add ``routes`` CLI command to output routes registered on the + application. :pr:`2259` +- Show warning when session cookie domain is a bare hostname or an IP + address, as these may not behave properly in some browsers, such as + Chrome. :pr:`2282` +- Allow IP address as exact session cookie domain. :pr:`2282` +- ``SESSION_COOKIE_DOMAIN`` is set if it is detected through + ``SERVER_NAME``. :pr:`2282` +- Auto-detect zero-argument app factory called ``create_app`` or + ``make_app`` from ``FLASK_APP``. :pr:`2297` +- Factory functions are not required to take a ``script_info`` + parameter to work with the ``flask`` command. If they take a single + parameter or a parameter named ``script_info``, the ``ScriptInfo`` + object will be passed. :pr:`2319` +- ``FLASK_APP`` can be set to an app factory, with arguments if + needed, for example ``FLASK_APP=myproject.app:create_app('dev')``. + :pr:`2326` +- ``FLASK_APP`` can point to local packages that are not installed in + editable mode, although ``pip install -e`` is still preferred. + :pr:`2414` +- The ``View`` class attribute + ``View.provide_automatic_options`` is set in ``View.as_view``, to be + detected by ``Flask.add_url_rule``. :pr:`2316` +- Error handling will try handlers registered for ``blueprint, code``, + ``app, code``, ``blueprint, exception``, ``app, exception``. + :pr:`2314` +- ``Cookie`` is added to the response's ``Vary`` header if the session + is accessed at all during the request (and not deleted). :pr:`2288` +- ``Flask.test_request_context`` accepts ``subdomain`` and + ``url_scheme`` arguments for use when building the base URL. + :pr:`1621` +- Set ``APPLICATION_ROOT`` to ``'/'`` by default. This was already the + implicit default when it was set to ``None``. +- ``TRAP_BAD_REQUEST_ERRORS`` is enabled by default in debug mode. + ``BadRequestKeyError`` has a message with the bad key in debug mode + instead of the generic bad request message. :pr:`2348` +- Allow registering new tags with ``TaggedJSONSerializer`` to support + storing other types in the session cookie. :pr:`2352` +- Only open the session if the request has not been pushed onto the + context stack yet. This allows ``stream_with_context`` generators to + access the same session that the containing view uses. :pr:`2354` +- Add ``json`` keyword argument for the test client request methods. + This will dump the given object as JSON and set the appropriate + content type. :pr:`2358` +- Extract JSON handling to a mixin applied to both the ``Request`` and + ``Response`` classes. This adds the ``Response.is_json`` and + ``Response.get_json`` methods to the response to make testing JSON + response much easier. :pr:`2358` +- Removed error handler caching because it caused unexpected results + for some exception inheritance hierarchies. Register handlers + explicitly for each exception if you want to avoid traversing the + MRO. :pr:`2362` +- Fix incorrect JSON encoding of aware, non-UTC datetimes. :pr:`2374` +- Template auto reloading will honor debug mode even if + ``Flask.jinja_env`` was already accessed. :pr:`2373` +- The following old deprecated code was removed. :issue:`2385` + + - ``flask.ext`` - import extensions directly by their name instead + of through the ``flask.ext`` namespace. For example, + ``import flask.ext.sqlalchemy`` becomes + ``import flask_sqlalchemy``. + - ``Flask.init_jinja_globals`` - extend + ``Flask.create_jinja_environment`` instead. + - ``Flask.error_handlers`` - tracked by + ``Flask.error_handler_spec``, use ``Flask.errorhandler`` + to register handlers. + - ``Flask.request_globals_class`` - use + ``Flask.app_ctx_globals_class`` instead. + - ``Flask.static_path`` - use ``Flask.static_url_path`` instead. + - ``Request.module`` - use ``Request.blueprint`` instead. + +- The ``Request.json`` property is no longer deprecated. :issue:`1421` +- Support passing a ``EnvironBuilder`` or ``dict`` to + ``test_client.open``. :pr:`2412` +- The ``flask`` command and ``Flask.run`` will load environment + variables from ``.env`` and ``.flaskenv`` files if python-dotenv is + installed. :pr:`2416` +- When passing a full URL to the test client, the scheme in the URL is + used instead of ``PREFERRED_URL_SCHEME``. :pr:`2430` +- ``Flask.logger`` has been simplified. ``LOGGER_NAME`` and + ``LOGGER_HANDLER_POLICY`` config was removed. The logger is always + named ``flask.app``. The level is only set on first access, it + doesn't check ``Flask.debug`` each time. Only one format is used, + not different ones depending on ``Flask.debug``. No handlers are + removed, and a handler is only added if no handlers are already + configured. :pr:`2436` +- Blueprint view function names may not contain dots. :pr:`2450` +- Fix a ``ValueError`` caused by invalid ``Range`` requests in some + cases. :issue:`2526` +- The development server uses threads by default. :pr:`2529` +- Loading config files with ``silent=True`` will ignore ``ENOTDIR`` + errors. :pr:`2581` +- Pass ``--cert`` and ``--key`` options to ``flask run`` to run the + development server over HTTPS. :pr:`2606` +- Added ``SESSION_COOKIE_SAMESITE`` to control the ``SameSite`` + attribute on the session cookie. :pr:`2607` +- Added ``Flask.test_cli_runner`` to create a Click runner that can + invoke Flask CLI commands for testing. :pr:`2636` +- Subdomain matching is disabled by default and setting + ``SERVER_NAME`` does not implicitly enable it. It can be enabled by + passing ``subdomain_matching=True`` to the ``Flask`` constructor. + :pr:`2635` +- A single trailing slash is stripped from the blueprint + ``url_prefix`` when it is registered with the app. :pr:`2629` +- ``Request.get_json`` doesn't cache the result if parsing fails when + ``silent`` is true. :issue:`2651` +- ``Request.get_json`` no longer accepts arbitrary encodings. Incoming + JSON should be encoded using UTF-8 per :rfc:`8259`, but Flask will + autodetect UTF-8, -16, or -32. :pr:`2691` +- Added ``MAX_COOKIE_SIZE`` and ``Response.max_cookie_size`` to + control when Werkzeug warns about large cookies that browsers may + ignore. :pr:`2693` +- Updated documentation theme to make docs look better in small + windows. :pr:`2709` +- Rewrote the tutorial docs and example project to take a more + structured approach to help new users avoid common pitfalls. + :pr:`2676` + + +Version 0.12.5 +-------------- + +Released 2020-02-10 + +- Pin Werkzeug to < 1.0.0. :issue:`3497` + + +Version 0.12.4 +-------------- + +Released 2018-04-29 + +- Repackage 0.12.3 to fix package layout issue. :issue:`2728` + + +Version 0.12.3 +-------------- + +Released 2018-04-26 + +- ``Request.get_json`` no longer accepts arbitrary encodings. + Incoming JSON should be encoded using UTF-8 per :rfc:`8259`, but + Flask will autodetect UTF-8, -16, or -32. :issue:`2692` +- Fix a Python warning about imports when using ``python -m flask``. + :issue:`2666` +- Fix a ``ValueError`` caused by invalid ``Range`` requests in some + cases. + + +Version 0.12.2 +-------------- + +Released 2017-05-16 + +- Fix a bug in ``safe_join`` on Windows. + + +Version 0.12.1 +-------------- + +Released 2017-03-31 + +- Prevent ``flask run`` from showing a ``NoAppException`` when an + ``ImportError`` occurs within the imported application module. +- Fix encoding behavior of ``app.config.from_pyfile`` for Python 3. + :issue:`2118` +- Use the ``SERVER_NAME`` config if it is present as default values + for ``app.run``. :issue:`2109`, :pr:`2152` +- Call ``ctx.auto_pop`` with the exception object instead of ``None``, + in the event that a ``BaseException`` such as ``KeyboardInterrupt`` + is raised in a request handler. + + +Version 0.12 +------------ + +Released 2016-12-21, codename Punsch + +- The cli command now responds to ``--version``. +- Mimetype guessing and ETag generation for file-like objects in + ``send_file`` has been removed. :issue:`104`, :pr`1849` +- Mimetype guessing in ``send_file`` now fails loudly and doesn't fall + back to ``application/octet-stream``. :pr:`1988` +- Make ``flask.safe_join`` able to join multiple paths like + ``os.path.join`` :pr:`1730` +- Revert a behavior change that made the dev server crash instead of + returning an Internal Server Error. :pr:`2006` +- Correctly invoke response handlers for both regular request + dispatching as well as error handlers. +- Disable logger propagation by default for the app logger. +- Add support for range requests in ``send_file``. +- ``app.test_client`` includes preset default environment, which can + now be directly set, instead of per ``client.get``. +- Fix crash when running under PyPy3. :pr:`1814` + + +Version 0.11.1 +-------------- + +Released 2016-06-07 + +- Fixed a bug that prevented ``FLASK_APP=foobar/__init__.py`` from + working. :pr:`1872` + + +Version 0.11 +------------ + +Released 2016-05-29, codename Absinthe + +- Added support to serializing top-level arrays to ``jsonify``. This + introduces a security risk in ancient browsers. +- Added before_render_template signal. +- Added ``**kwargs`` to ``Flask.test_client`` to support passing + additional keyword arguments to the constructor of + ``Flask.test_client_class``. +- Added ``SESSION_REFRESH_EACH_REQUEST`` config key that controls the + set-cookie behavior. If set to ``True`` a permanent session will be + refreshed each request and get their lifetime extended, if set to + ``False`` it will only be modified if the session actually modifies. + Non permanent sessions are not affected by this and will always + expire if the browser window closes. +- Made Flask support custom JSON mimetypes for incoming data. +- Added support for returning tuples in the form ``(response, + headers)`` from a view function. +- Added ``Config.from_json``. +- Added ``Flask.config_class``. +- Added ``Config.get_namespace``. +- Templates are no longer automatically reloaded outside of debug + mode. This can be configured with the new ``TEMPLATES_AUTO_RELOAD`` + config key. +- Added a workaround for a limitation in Python 3.3's namespace + loader. +- Added support for explicit root paths when using Python 3.3's + namespace packages. +- Added ``flask`` and the ``flask.cli`` module to start the + local debug server through the click CLI system. This is recommended + over the old ``flask.run()`` method as it works faster and more + reliable due to a different design and also replaces + ``Flask-Script``. +- Error handlers that match specific classes are now checked first, + thereby allowing catching exceptions that are subclasses of HTTP + exceptions (in ``werkzeug.exceptions``). This makes it possible for + an extension author to create exceptions that will by default result + in the HTTP error of their choosing, but may be caught with a custom + error handler if desired. +- Added ``Config.from_mapping``. +- Flask will now log by default even if debug is disabled. The log + format is now hardcoded but the default log handling can be disabled + through the ``LOGGER_HANDLER_POLICY`` configuration key. +- Removed deprecated module functionality. +- Added the ``EXPLAIN_TEMPLATE_LOADING`` config flag which when + enabled will instruct Flask to explain how it locates templates. + This should help users debug when the wrong templates are loaded. +- Enforce blueprint handling in the order they were registered for + template loading. +- Ported test suite to py.test. +- Deprecated ``request.json`` in favour of ``request.get_json()``. +- Add "pretty" and "compressed" separators definitions in jsonify() + method. Reduces JSON response size when + ``JSONIFY_PRETTYPRINT_REGULAR=False`` by removing unnecessary white + space included by default after separators. +- JSON responses are now terminated with a newline character, because + it is a convention that UNIX text files end with a newline and some + clients don't deal well when this newline is missing. :pr:`1262` +- The automatically provided ``OPTIONS`` method is now correctly + disabled if the user registered an overriding rule with the + lowercase-version ``options``. :issue:`1288` +- ``flask.json.jsonify`` now supports the ``datetime.date`` type. + :pr:`1326` +- Don't leak exception info of already caught exceptions to context + teardown handlers. :pr:`1393` +- Allow custom Jinja environment subclasses. :pr:`1422` +- Updated extension dev guidelines. +- ``flask.g`` now has ``pop()`` and ``setdefault`` methods. +- Turn on autoescape for ``flask.templating.render_template_string`` + by default. :pr:`1515` +- ``flask.ext`` is now deprecated. :pr:`1484` +- ``send_from_directory`` now raises BadRequest if the filename is + invalid on the server OS. :pr:`1763` +- Added the ``JSONIFY_MIMETYPE`` configuration variable. :pr:`1728` +- Exceptions during teardown handling will no longer leave bad + application contexts lingering around. +- Fixed broken ``test_appcontext_signals()`` test case. +- Raise an ``AttributeError`` in ``helpers.find_package`` with a + useful message explaining why it is raised when a :pep:`302` import + hook is used without an ``is_package()`` method. +- Fixed an issue causing exceptions raised before entering a request + or app context to be passed to teardown handlers. +- Fixed an issue with query parameters getting removed from requests + in the test client when absolute URLs were requested. +- Made ``@before_first_request`` into a decorator as intended. +- Fixed an etags bug when sending a file streams with a name. +- Fixed ``send_from_directory`` not expanding to the application root + path correctly. +- Changed logic of before first request handlers to flip the flag + after invoking. This will allow some uses that are potentially + dangerous but should probably be permitted. +- Fixed Python 3 bug when a handler from + ``app.url_build_error_handlers`` reraises the ``BuildError``. + + +Version 0.10.1 +-------------- + +Released 2013-06-14 + +- Fixed an issue where ``|tojson`` was not quoting single quotes which + made the filter not work properly in HTML attributes. Now it's + possible to use that filter in single quoted attributes. This should + make using that filter with angular.js easier. +- Added support for byte strings back to the session system. This + broke compatibility with the common case of people putting binary + data for token verification into the session. +- Fixed an issue where registering the same method twice for the same + endpoint would trigger an exception incorrectly. + + +Version 0.10 +------------ + +Released 2013-06-13, codename Limoncello + +- Changed default cookie serialization format from pickle to JSON to + limit the impact an attacker can do if the secret key leaks. +- Added ``template_test`` methods in addition to the already existing + ``template_filter`` method family. +- Added ``template_global`` methods in addition to the already + existing ``template_filter`` method family. +- Set the content-length header for x-sendfile. +- ``tojson`` filter now does not escape script blocks in HTML5 + parsers. +- ``tojson`` used in templates is now safe by default. This was + allowed due to the different escaping behavior. +- Flask will now raise an error if you attempt to register a new + function on an already used endpoint. +- Added wrapper module around simplejson and added default + serialization of datetime objects. This allows much easier + customization of how JSON is handled by Flask or any Flask + extension. +- Removed deprecated internal ``flask.session`` module alias. Use + ``flask.sessions`` instead to get the session module. This is not to + be confused with ``flask.session`` the session proxy. +- Templates can now be rendered without request context. The behavior + is slightly different as the ``request``, ``session`` and ``g`` + objects will not be available and blueprint's context processors are + not called. +- The config object is now available to the template as a real global + and not through a context processor which makes it available even in + imported templates by default. +- Added an option to generate non-ascii encoded JSON which should + result in less bytes being transmitted over the network. It's + disabled by default to not cause confusion with existing libraries + that might expect ``flask.json.dumps`` to return bytes by default. +- ``flask.g`` is now stored on the app context instead of the request + context. +- ``flask.g`` now gained a ``get()`` method for not erroring out on + non existing items. +- ``flask.g`` now can be used with the ``in`` operator to see what's + defined and it now is iterable and will yield all attributes stored. +- ``flask.Flask.request_globals_class`` got renamed to + ``flask.Flask.app_ctx_globals_class`` which is a better name to what + it does since 0.10. +- ``request``, ``session`` and ``g`` are now also added as proxies to + the template context which makes them available in imported + templates. One has to be very careful with those though because + usage outside of macros might cause caching. +- Flask will no longer invoke the wrong error handlers if a proxy + exception is passed through. +- Added a workaround for chrome's cookies in localhost not working as + intended with domain names. +- Changed logic for picking defaults for cookie values from sessions + to work better with Google Chrome. +- Added ``message_flashed`` signal that simplifies flashing testing. +- Added support for copying of request contexts for better working + with greenlets. +- Removed custom JSON HTTP exception subclasses. If you were relying + on them you can reintroduce them again yourself trivially. Using + them however is strongly discouraged as the interface was flawed. +- Python requirements changed: requiring Python 2.6 or 2.7 now to + prepare for Python 3.3 port. +- Changed how the teardown system is informed about exceptions. This + is now more reliable in case something handles an exception halfway + through the error handling process. +- Request context preservation in debug mode now keeps the exception + information around which means that teardown handlers are able to + distinguish error from success cases. +- Added the ``JSONIFY_PRETTYPRINT_REGULAR`` configuration variable. +- Flask now orders JSON keys by default to not trash HTTP caches due + to different hash seeds between different workers. +- Added ``appcontext_pushed`` and ``appcontext_popped`` signals. +- The builtin run method now takes the ``SERVER_NAME`` into account + when picking the default port to run on. +- Added ``flask.request.get_json()`` as a replacement for the old + ``flask.request.json`` property. + + +Version 0.9 +----------- + +Released 2012-07-01, codename Campari + +- The ``Request.on_json_loading_failed`` now returns a JSON formatted + response by default. +- The ``url_for`` function now can generate anchors to the generated + links. +- The ``url_for`` function now can also explicitly generate URL rules + specific to a given HTTP method. +- Logger now only returns the debug log setting if it was not set + explicitly. +- Unregister a circular dependency between the WSGI environment and + the request object when shutting down the request. This means that + environ ``werkzeug.request`` will be ``None`` after the response was + returned to the WSGI server but has the advantage that the garbage + collector is not needed on CPython to tear down the request unless + the user created circular dependencies themselves. +- Session is now stored after callbacks so that if the session payload + is stored in the session you can still modify it in an after request + callback. +- The ``Flask`` class will avoid importing the provided import name if + it can (the required first parameter), to benefit tools which build + Flask instances programmatically. The Flask class will fall back to + using import on systems with custom module hooks, e.g. Google App + Engine, or when the import name is inside a zip archive (usually an + egg) prior to Python 2.7. +- Blueprints now have a decorator to add custom template filters + application wide, ``Blueprint.app_template_filter``. +- The Flask and Blueprint classes now have a non-decorator method for + adding custom template filters application wide, + ``Flask.add_template_filter`` and + ``Blueprint.add_app_template_filter``. +- The ``get_flashed_messages`` function now allows rendering flashed + message categories in separate blocks, through a ``category_filter`` + argument. +- The ``Flask.run`` method now accepts ``None`` for ``host`` and + ``port`` arguments, using default values when ``None``. This allows + for calling run using configuration values, e.g. + ``app.run(app.config.get('MYHOST'), app.config.get('MYPORT'))``, + with proper behavior whether or not a config file is provided. +- The ``render_template`` method now accepts a either an iterable of + template names or a single template name. Previously, it only + accepted a single template name. On an iterable, the first template + found is rendered. +- Added ``Flask.app_context`` which works very similar to the request + context but only provides access to the current application. This + also adds support for URL generation without an active request + context. +- View functions can now return a tuple with the first instance being + an instance of ``Response``. This allows for returning + ``jsonify(error="error msg"), 400`` from a view function. +- ``Flask`` and ``Blueprint`` now provide a ``get_send_file_max_age`` + hook for subclasses to override behavior of serving static files + from Flask when using ``Flask.send_static_file`` (used for the + default static file handler) and ``helpers.send_file``. This hook is + provided a filename, which for example allows changing cache + controls by file extension. The default max-age for ``send_file`` + and static files can be configured through a new + ``SEND_FILE_MAX_AGE_DEFAULT`` configuration variable, which is used + in the default ``get_send_file_max_age`` implementation. +- Fixed an assumption in sessions implementation which could break + message flashing on sessions implementations which use external + storage. +- Changed the behavior of tuple return values from functions. They are + no longer arguments to the response object, they now have a defined + meaning. +- Added ``Flask.request_globals_class`` to allow a specific class to + be used on creation of the ``g`` instance of each request. +- Added ``required_methods`` attribute to view functions to force-add + methods on registration. +- Added ``flask.after_this_request``. +- Added ``flask.stream_with_context`` and the ability to push contexts + multiple times without producing unexpected behavior. + + +Version 0.8.1 +------------- + +Released 2012-07-01 + +- Fixed an issue with the undocumented ``flask.session`` module to not + work properly on Python 2.5. It should not be used but did cause + some problems for package managers. + + +Version 0.8 +----------- + +Released 2011-09-29, codename Rakija + +- Refactored session support into a session interface so that the + implementation of the sessions can be changed without having to + override the Flask class. +- Empty session cookies are now deleted properly automatically. +- View functions can now opt out of getting the automatic OPTIONS + implementation. +- HTTP exceptions and Bad Request errors can now be trapped so that + they show up normally in the traceback. +- Flask in debug mode is now detecting some common problems and tries + to warn you about them. +- Flask in debug mode will now complain with an assertion error if a + view was attached after the first request was handled. This gives + earlier feedback when users forget to import view code ahead of + time. +- Added the ability to register callbacks that are only triggered once + at the beginning of the first request with + ``Flask.before_first_request``. +- Malformed JSON data will now trigger a bad request HTTP exception + instead of a value error which usually would result in a 500 + internal server error if not handled. This is a backwards + incompatible change. +- Applications now not only have a root path where the resources and + modules are located but also an instance path which is the + designated place to drop files that are modified at runtime (uploads + etc.). Also this is conceptually only instance depending and outside + version control so it's the perfect place to put configuration files + etc. +- Added the ``APPLICATION_ROOT`` configuration variable. +- Implemented ``TestClient.session_transaction`` to easily modify + sessions from the test environment. +- Refactored test client internally. The ``APPLICATION_ROOT`` + configuration variable as well as ``SERVER_NAME`` are now properly + used by the test client as defaults. +- Added ``View.decorators`` to support simpler decorating of pluggable + (class-based) views. +- Fixed an issue where the test client if used with the "with" + statement did not trigger the execution of the teardown handlers. +- Added finer control over the session cookie parameters. +- HEAD requests to a method view now automatically dispatch to the + ``get`` method if no handler was implemented. +- Implemented the virtual ``flask.ext`` package to import extensions + from. +- The context preservation on exceptions is now an integral component + of Flask itself and no longer of the test client. This cleaned up + some internal logic and lowers the odds of runaway request contexts + in unittests. +- Fixed the Jinja environment's ``list_templates`` method not + returning the correct names when blueprints or modules were + involved. + + +Version 0.7.2 +------------- + +Released 2011-07-06 + +- Fixed an issue with URL processors not properly working on + blueprints. + + +Version 0.7.1 +------------- + +Released 2011-06-29 + +- Added missing future import that broke 2.5 compatibility. +- Fixed an infinite redirect issue with blueprints. + + +Version 0.7 +----------- + +Released 2011-06-28, codename Grappa + +- Added ``Flask.make_default_options_response`` which can be used by + subclasses to alter the default behavior for ``OPTIONS`` responses. +- Unbound locals now raise a proper ``RuntimeError`` instead of an + ``AttributeError``. +- Mimetype guessing and etag support based on file objects is now + deprecated for ``send_file`` because it was unreliable. Pass + filenames instead or attach your own etags and provide a proper + mimetype by hand. +- Static file handling for modules now requires the name of the static + folder to be supplied explicitly. The previous autodetection was not + reliable and caused issues on Google's App Engine. Until 1.0 the old + behavior will continue to work but issue dependency warnings. +- Fixed a problem for Flask to run on jython. +- Added a ``PROPAGATE_EXCEPTIONS`` configuration variable that can be + used to flip the setting of exception propagation which previously + was linked to ``DEBUG`` alone and is now linked to either ``DEBUG`` + or ``TESTING``. +- Flask no longer internally depends on rules being added through the + ``add_url_rule`` function and can now also accept regular werkzeug + rules added to the url map. +- Added an ``endpoint`` method to the flask application object which + allows one to register a callback to an arbitrary endpoint with a + decorator. +- Use Last-Modified for static file sending instead of Date which was + incorrectly introduced in 0.6. +- Added ``create_jinja_loader`` to override the loader creation + process. +- Implemented a silent flag for ``config.from_pyfile``. +- Added ``teardown_request`` decorator, for functions that should run + at the end of a request regardless of whether an exception occurred. + Also the behavior for ``after_request`` was changed. It's now no + longer executed when an exception is raised. +- Implemented ``has_request_context``. +- Deprecated ``init_jinja_globals``. Override the + ``Flask.create_jinja_environment`` method instead to achieve the + same functionality. +- Added ``safe_join``. +- The automatic JSON request data unpacking now looks at the charset + mimetype parameter. +- Don't modify the session on ``get_flashed_messages`` if there are no + messages in the session. +- ``before_request`` handlers are now able to abort requests with + errors. +- It is not possible to define user exception handlers. That way you + can provide custom error messages from a central hub for certain + errors that might occur during request processing (for instance + database connection errors, timeouts from remote resources etc.). +- Blueprints can provide blueprint specific error handlers. +- Implemented generic class-based views. + + +Version 0.6.1 +------------- + +Released 2010-12-31 + +- Fixed an issue where the default ``OPTIONS`` response was not + exposing all valid methods in the ``Allow`` header. +- Jinja template loading syntax now allows "./" in front of a + template load path. Previously this caused issues with module + setups. +- Fixed an issue where the subdomain setting for modules was ignored + for the static folder. +- Fixed a security problem that allowed clients to download arbitrary + files if the host server was a windows based operating system and + the client uses backslashes to escape the directory the files where + exposed from. + + +Version 0.6 +----------- + +Released 2010-07-27, codename Whisky + +- After request functions are now called in reverse order of + registration. +- OPTIONS is now automatically implemented by Flask unless the + application explicitly adds 'OPTIONS' as method to the URL rule. In + this case no automatic OPTIONS handling kicks in. +- Static rules are now even in place if there is no static folder for + the module. This was implemented to aid GAE which will remove the + static folder if it's part of a mapping in the .yml file. +- ``Flask.config`` is now available in the templates as ``config``. +- Context processors will no longer override values passed directly to + the render function. +- Added the ability to limit the incoming request data with the new + ``MAX_CONTENT_LENGTH`` configuration value. +- The endpoint for the ``Module.add_url_rule`` method is now optional + to be consistent with the function of the same name on the + application object. +- Added a ``make_response`` function that simplifies creating response + object instances in views. +- Added signalling support based on blinker. This feature is currently + optional and supposed to be used by extensions and applications. If + you want to use it, make sure to have ``blinker`` installed. +- Refactored the way URL adapters are created. This process is now + fully customizable with the ``Flask.create_url_adapter`` method. +- Modules can now register for a subdomain instead of just an URL + prefix. This makes it possible to bind a whole module to a + configurable subdomain. + + +Version 0.5.2 +------------- + +Released 2010-07-15 + +- Fixed another issue with loading templates from directories when + modules were used. + + +Version 0.5.1 +------------- + +Released 2010-07-06 + +- Fixes an issue with template loading from directories when modules + where used. + + +Version 0.5 +----------- + +Released 2010-07-06, codename Calvados + +- Fixed a bug with subdomains that was caused by the inability to + specify the server name. The server name can now be set with the + ``SERVER_NAME`` config key. This key is now also used to set the + session cookie cross-subdomain wide. +- Autoescaping is no longer active for all templates. Instead it is + only active for ``.html``, ``.htm``, ``.xml`` and ``.xhtml``. Inside + templates this behavior can be changed with the ``autoescape`` tag. +- Refactored Flask internally. It now consists of more than a single + file. +- ``send_file`` now emits etags and has the ability to do conditional + responses builtin. +- (temporarily) dropped support for zipped applications. This was a + rarely used feature and led to some confusing behavior. +- Added support for per-package template and static-file directories. +- Removed support for ``create_jinja_loader`` which is no longer used + in 0.5 due to the improved module support. +- Added a helper function to expose files from any directory. + + +Version 0.4 +----------- + +Released 2010-06-18, codename Rakia + +- Added the ability to register application wide error handlers from + modules. +- ``Flask.after_request`` handlers are now also invoked if the request + dies with an exception and an error handling page kicks in. +- Test client has not the ability to preserve the request context for + a little longer. This can also be used to trigger custom requests + that do not pop the request stack for testing. +- Because the Python standard library caches loggers, the name of the + logger is configurable now to better support unittests. +- Added ``TESTING`` switch that can activate unittesting helpers. +- The logger switches to ``DEBUG`` mode now if debug is enabled. + + +Version 0.3.1 +------------- + +Released 2010-05-28 + +- Fixed a error reporting bug with ``Config.from_envvar``. +- Removed some unused code. +- Release does no longer include development leftover files (.git + folder for themes, built documentation in zip and pdf file and some + .pyc files) + + +Version 0.3 +----------- + +Released 2010-05-28, codename Schnaps + +- Added support for categories for flashed messages. +- The application now configures a ``logging.Handler`` and will log + request handling exceptions to that logger when not in debug mode. + This makes it possible to receive mails on server errors for + example. +- Added support for context binding that does not require the use of + the with statement for playing in the console. +- The request context is now available within the with statement + making it possible to further push the request context or pop it. +- Added support for configurations. + + +Version 0.2 +----------- + +Released 2010-05-12, codename J?germeister + +- Various bugfixes +- Integrated JSON support +- Added ``get_template_attribute`` helper function. +- ``Flask.add_url_rule`` can now also register a view function. +- Refactored internal request dispatching. +- Server listens on 127.0.0.1 by default now to fix issues with + chrome. +- Added external URL support. +- Added support for ``send_file``. +- Module support and internal request handling refactoring to better + support pluggable applications. +- Sessions can be set to be permanent now on a per-session basis. +- Better error reporting on missing secret keys. +- Added support for Google Appengine. + + +Version 0.1 +----------- + +Released 2010-04-16 + +- First public preview release. diff --git a/src/flask-main/LICENSE.txt b/src/flask-main/LICENSE.txt new file mode 100644 index 0000000..9d227a0 --- /dev/null +++ b/src/flask-main/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/src/flask-main/README.md b/src/flask-main/README.md new file mode 100644 index 0000000..64f56ca --- /dev/null +++ b/src/flask-main/README.md @@ -0,0 +1,53 @@ +
+ +# 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/ + +## 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) +``` + +## 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 + +## Contributing + +See our [detailed contributing documentation][contrib] for many ways to +contribute, including reporting issues, requesting features, asking or answering +questions, and making PRs. + +[contrib]: https://palletsprojects.com/contributing/ diff --git a/src/flask-main/docs/Makefile b/src/flask-main/docs/Makefile new file mode 100644 index 0000000..d4bb2cb --- /dev/null +++ b/src/flask-main/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/src/flask-main/docs/_static/debugger.png b/src/flask-main/docs/_static/debugger.png new file mode 100644 index 0000000000000000000000000000000000000000..7d4181f6a41dfe2ab698c18834c8d64f5ccb1a8f GIT binary patch literal 207889 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

E};U;Vfh@)}ZW{g4y6nuks2h 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 TDA80s!Z-|Xm|QQ_v48PDCmEx^ literal 0 HcmV?d00001 diff --git a/src/flask-main/docs/_static/flask-icon.svg b/src/flask-main/docs/_static/flask-icon.svg new file mode 100644 index 0000000..c802da9 --- /dev/null +++ b/src/flask-main/docs/_static/flask-icon.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/flask-main/docs/_static/flask-logo.svg b/src/flask-main/docs/_static/flask-logo.svg new file mode 100644 index 0000000..c216b61 --- /dev/null +++ b/src/flask-main/docs/_static/flask-logo.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/flask-main/docs/_static/flask-name.svg b/src/flask-main/docs/_static/flask-name.svg new file mode 100644 index 0000000..b46782d --- /dev/null +++ b/src/flask-main/docs/_static/flask-name.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + diff --git a/src/flask-main/docs/_static/pycharm-run-config.png b/src/flask-main/docs/_static/pycharm-run-config.png new file mode 100644 index 0000000000000000000000000000000000000000..ad025545a7ff00c6366a6146d808d875c5eb6a0a GIT binary patch literal 99654 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 literal 0 HcmV?d00001 diff --git a/src/flask-main/docs/api.rst b/src/flask-main/docs/api.rst new file mode 100644 index 0000000..d3c517f --- /dev/null +++ b/src/flask-main/docs/api.rst @@ -0,0 +1,708 @@ +API +=== + +.. module:: flask + +This part of the documentation covers all the interfaces of Flask. For +parts where Flask depends on external libraries, we document the most +important right here and provide links to the canonical documentation. + + +Application Object +------------------ + +.. autoclass:: Flask + :members: + :inherited-members: + + +Blueprint Objects +----------------- + +.. autoclass:: Blueprint + :members: + :inherited-members: + +Incoming Request Data +--------------------- + +.. autoclass:: Request + :members: + :inherited-members: + :exclude-members: json_module + +.. data:: request + + A proxy to the request data for the current request, an instance of + :class:`.Request`. + + This is only available when a :doc:`request context ` is + active. + + This is a proxy. See :ref:`context-visibility` for more information. + + +Response Objects +---------------- + +.. autoclass:: flask.Response + :members: + :inherited-members: + :exclude-members: json_module + +Sessions +-------- + +If you have set :attr:`Flask.secret_key` (or configured it from +:data:`SECRET_KEY`) you can use sessions in Flask applications. A session makes +it possible to remember information from one request to another. The way Flask +does this is by using a signed cookie. The user can look at the session +contents, but can't modify it unless they know the secret key, so make sure to +set that to something complex and unguessable. + +To access the current session you can use the :data:`.session` proxy. + +.. data:: session + + A proxy to the session data for the current request, an instance of + :class:`.SessionMixin`. + + This is only available when a :doc:`request context ` is + active. + + This is a proxy. See :ref:`context-visibility` for more information. + + The session object works like a dict but tracks assignment and access to its + keys. It cannot track modifications to mutable values, you need to set + :attr:`~.SessionMixin.modified` manually when modifying a list, dict, etc. + + .. code-block:: python + + # appending to a list is not detected + session["numbers"].append(42) + # so mark it as modified yourself + session.modified = True + + The session is persisted across requests using a cookie. By default the + users's browser will clear the cookie when it is closed. Set + :attr:`~.SessionMixin.permanent` to ``True`` to persist the cookie for + :data:`PERMANENT_SESSION_LIFETIME`. + + +Session Interface +----------------- + +.. versionadded:: 0.8 + +The session interface provides a simple way to replace the session +implementation that Flask is using. + +.. currentmodule:: flask.sessions + +.. autoclass:: SessionInterface + :members: + +.. autoclass:: SecureCookieSessionInterface + :members: + +.. autoclass:: SecureCookieSession + :members: + +.. autoclass:: NullSession + :members: + +.. autoclass:: SessionMixin + :members: + +.. admonition:: Notice + + The :data:`PERMANENT_SESSION_LIFETIME` config can be an integer or ``timedelta``. + The :attr:`~flask.Flask.permanent_session_lifetime` attribute is always a + ``timedelta``. + + +Test Client +----------- + +.. currentmodule:: flask.testing + +.. autoclass:: FlaskClient + :members: + + +Test CLI Runner +--------------- + +.. currentmodule:: flask.testing + +.. autoclass:: FlaskCliRunner + :members: + + +Application Globals +------------------- + +.. currentmodule:: flask + +To share data that is valid for one request only from one function to +another, a global variable is not good enough because it would break in +threaded environments. Flask provides you with a special object that +ensures it is only valid for the active request and that will return +different values for each request. In a nutshell: it does the right +thing, like it does for :data:`.request` and :data:`.session`. + +.. data:: g + + A proxy to a namespace object used to store data during a single request or + app context. An instance of :attr:`.Flask.app_ctx_globals_class`, which + defaults to :class:`._AppCtxGlobals`. + + This is a good place to store resources during a request. For example, a + :meth:`~.Flask.before_request` function could load a user object from a + session id, then set ``g.user`` to be used in the view function. + + This is only available when an :doc:`app context ` is active. + + This is a proxy. See :ref:`context-visibility` for more information. + + .. versionchanged:: 0.10 + Bound to the application context instead of the request context. + +.. autoclass:: flask.ctx._AppCtxGlobals + :members: + + +Useful Functions and Classes +---------------------------- + +.. data:: current_app + + A proxy to the :class:`.Flask` application handling the current request or + other activity. + + This is useful to access the application without needing to import it, or if + it can't be imported, such as when using the application factory pattern or + in blueprints and extensions. + + This is only available when an :doc:`app context ` is active. + + This is a proxy. See :ref:`context-visibility` for more information. + +.. autofunction:: has_request_context + +.. autofunction:: copy_current_request_context + +.. autofunction:: has_app_context + +.. autofunction:: url_for + +.. autofunction:: abort + +.. autofunction:: redirect + +.. autofunction:: make_response + +.. autofunction:: after_this_request + +.. autofunction:: send_file + +.. autofunction:: send_from_directory + + +Message Flashing +---------------- + +.. autofunction:: flash + +.. autofunction:: get_flashed_messages + + +JSON Support +------------ + +.. module:: flask.json + +Flask uses Python's built-in :mod:`json` module for handling JSON by +default. The JSON implementation can be changed by assigning a different +provider to :attr:`flask.Flask.json_provider_class` or +:attr:`flask.Flask.json`. The functions provided by ``flask.json`` will +use methods on ``app.json`` if an app context is active. + +Jinja's ``|tojson`` filter is configured to use the app's JSON provider. +The filter marks the output with ``|safe``. Use it to render data inside +HTML `` + +.. autofunction:: jsonify + +.. autofunction:: dumps + +.. autofunction:: dump + +.. autofunction:: loads + +.. autofunction:: load + +.. autoclass:: flask.json.provider.JSONProvider + :members: + :member-order: bysource + +.. autoclass:: flask.json.provider.DefaultJSONProvider + :members: + :member-order: bysource + +.. automodule:: flask.json.tag + + +Template Rendering +------------------ + +.. currentmodule:: flask + +.. autofunction:: render_template + +.. autofunction:: render_template_string + +.. autofunction:: stream_template + +.. autofunction:: stream_template_string + +.. autofunction:: get_template_attribute + +Configuration +------------- + +.. autoclass:: Config + :members: + + +Stream Helpers +-------------- + +.. autofunction:: stream_with_context + +Useful Internals +---------------- + +.. autoclass:: flask.ctx.AppContext + :members: + +.. data:: flask.globals.app_ctx + + A proxy to the active :class:`.AppContext`. + + This is an internal object that is essential to how Flask handles requests. + Accessing this should not be needed in most cases. Most likely you want + :data:`.current_app`, :data:`.g`, :data:`.request`, and :data:`.session` instead. + + This is only available when a :doc:`request context ` is + active. + + This is a proxy. See :ref:`context-visibility` for more information. + +.. class:: flask.ctx.RequestContext + + .. deprecated:: 3.2 + Merged with :class:`AppContext`. This alias will be removed in Flask 4.0. + +.. data:: flask.globals.request_ctx + + .. deprecated:: 3.2 + Merged with :data:`.app_ctx`. This alias will be removed in Flask 4.0. + +.. autoclass:: flask.blueprints.BlueprintSetupState + :members: + +.. _core-signals-list: + +Signals +------- + +Signals are provided by the `Blinker`_ library. See :doc:`signals` for an introduction. + +.. _blinker: https://blinker.readthedocs.io/ + +.. data:: template_rendered + + This signal is sent when a template was successfully rendered. The + signal is invoked with the instance of the template as `template` + and the context as dictionary (named `context`). + + Example subscriber:: + + def log_template_renders(sender, template, context, **extra): + sender.logger.debug('Rendering template "%s" with context %s', + template.name or 'string template', + context) + + from flask import template_rendered + template_rendered.connect(log_template_renders, app) + +.. data:: flask.before_render_template + :noindex: + + This signal is sent before template rendering process. The + signal is invoked with the instance of the template as `template` + and the context as dictionary (named `context`). + + Example subscriber:: + + def log_template_renders(sender, template, context, **extra): + sender.logger.debug('Rendering template "%s" with context %s', + template.name or 'string template', + context) + + from flask import before_render_template + before_render_template.connect(log_template_renders, app) + +.. data:: request_started + + This signal is sent when the request context is set up, before + any request processing happens. Because the request context is already + bound, the subscriber can access the request with the standard global + proxies such as :class:`~flask.request`. + + Example subscriber:: + + def log_request(sender, **extra): + sender.logger.debug('Request context is set up') + + from flask import request_started + request_started.connect(log_request, app) + +.. data:: request_finished + + This signal is sent right before the response is sent to the client. + It is passed the response to be sent named `response`. + + Example subscriber:: + + def log_response(sender, response, **extra): + sender.logger.debug('Request context is about to close down. ' + 'Response: %s', response) + + from flask import request_finished + request_finished.connect(log_response, app) + +.. data:: got_request_exception + + This signal is sent when an unhandled exception happens during + request processing, including when debugging. The exception is + passed to the subscriber as ``exception``. + + This signal is not sent for + :exc:`~werkzeug.exceptions.HTTPException`, or other exceptions that + have error handlers registered, unless the exception was raised from + an error handler. + + This example shows how to do some extra logging if a theoretical + ``SecurityException`` was raised: + + .. code-block:: python + + from flask import got_request_exception + + def log_security_exception(sender, exception, **extra): + if not isinstance(exception, SecurityException): + return + + security_logger.exception( + f"SecurityException at {request.url!r}", + exc_info=exception, + ) + + got_request_exception.connect(log_security_exception, app) + +.. data:: request_tearing_down + + This signal is sent when the request is tearing down. This is always + called, even if an exception is caused. Currently functions listening + to this signal are called after the regular teardown handlers, but this + is not something you can rely on. + + Example subscriber:: + + def close_db_connection(sender, **extra): + session.close() + + from flask import request_tearing_down + request_tearing_down.connect(close_db_connection, app) + + As of Flask 0.9, this will also be passed an `exc` keyword argument + that has a reference to the exception that caused the teardown if + there was one. + +.. data:: appcontext_tearing_down + + This signal is sent when the app context is tearing down. This is always + called, even if an exception is caused. Currently functions listening + to this signal are called after the regular teardown handlers, but this + is not something you can rely on. + + Example subscriber:: + + def close_db_connection(sender, **extra): + session.close() + + from flask import appcontext_tearing_down + appcontext_tearing_down.connect(close_db_connection, app) + + This will also be passed an `exc` keyword argument that has a reference + to the exception that caused the teardown if there was one. + +.. data:: appcontext_pushed + + This signal is sent when an application context is pushed. The sender + is the application. This is usually useful for unittests in order to + temporarily hook in information. For instance it can be used to + set a resource early onto the `g` object. + + Example usage:: + + from contextlib import contextmanager + from flask import appcontext_pushed + + @contextmanager + def user_set(app, user): + def handler(sender, **kwargs): + g.user = user + with appcontext_pushed.connected_to(handler, app): + yield + + And in the testcode:: + + def test_user_me(self): + with user_set(app, 'john'): + c = app.test_client() + resp = c.get('/users/me') + assert resp.data == 'username=john' + + .. versionadded:: 0.10 + +.. data:: appcontext_popped + + This signal is sent when an application context is popped. The sender + is the application. This usually falls in line with the + :data:`appcontext_tearing_down` signal. + + .. versionadded:: 0.10 + +.. data:: message_flashed + + This signal is sent when the application is flashing a message. The + messages is sent as `message` keyword argument and the category as + `category`. + + Example subscriber:: + + recorded = [] + def record(sender, message, category, **extra): + recorded.append((message, category)) + + from flask import message_flashed + message_flashed.connect(record, app) + + .. versionadded:: 0.10 + + +Class-Based Views +----------------- + +.. versionadded:: 0.7 + +.. currentmodule:: None + +.. autoclass:: flask.views.View + :members: + +.. autoclass:: flask.views.MethodView + :members: + +.. _url-route-registrations: + +URL Route Registrations +----------------------- + +Generally there are three ways to define rules for the routing system: + +1. You can use the :meth:`flask.Flask.route` decorator. +2. You can use the :meth:`flask.Flask.add_url_rule` function. +3. You can directly access the underlying Werkzeug routing system + which is exposed as :attr:`flask.Flask.url_map`. + +Variable parts in the route can be specified with angular brackets +(``/user/``). By default a variable part in the URL accepts any +string without a slash however a different converter can be specified as +well by using ````. + +Variable parts are passed to the view function as keyword arguments. + +The following converters are available: + +=========== =============================================== +`string` accepts any text without a slash (the default) +`int` accepts integers +`float` like `int` but for floating point values +`path` like the default but also accepts slashes +`any` matches one of the items provided +`uuid` accepts UUID strings +=========== =============================================== + +Custom converters can be defined using :attr:`flask.Flask.url_map`. + +Here are some examples:: + + @app.route('/') + def index(): + pass + + @app.route('/') + def show_user(username): + pass + + @app.route('/post/') + def show_post(post_id): + pass + +An important detail to keep in mind is how Flask deals with trailing +slashes. The idea is to keep each URL unique so the following rules +apply: + +1. If a rule ends with a slash and is requested without a slash by the + user, the user is automatically redirected to the same page with a + trailing slash attached. +2. If a rule does not end with a trailing slash and the user requests the + page with a trailing slash, a 404 not found is raised. + +This is consistent with how web servers deal with static files. This +also makes it possible to use relative link targets safely. + +You can also define multiple rules for the same function. They have to be +unique however. Defaults can also be specified. Here for example is a +definition for a URL that accepts an optional page:: + + @app.route('/users/', defaults={'page': 1}) + @app.route('/users/page/') + def show_users(page): + pass + +This specifies that ``/users/`` will be the URL for page one and +``/users/page/N`` will be the URL for page ``N``. + +If a URL contains a default value, it will be redirected to its simpler +form with a 301 redirect. In the above example, ``/users/page/1`` will +be redirected to ``/users/``. If your route handles ``GET`` and ``POST`` +requests, make sure the default route only handles ``GET``, as redirects +can't preserve form data. :: + + @app.route('/region/', defaults={'id': 1}) + @app.route('/region/', methods=['GET', 'POST']) + def region(id): + pass + +Here are the parameters that :meth:`~flask.Flask.route` and +:meth:`~flask.Flask.add_url_rule` accept. The only difference is that +with the route parameter the view function is defined with the decorator +instead of the `view_func` parameter. + +=============== ========================================================== +`rule` the URL rule as string +`endpoint` the endpoint for the registered URL rule. Flask itself + assumes that the name of the view function is the name + of the endpoint if not explicitly stated. +`view_func` the function to call when serving a request to the + provided endpoint. If this is not provided one can + specify the function later by storing it in the + :attr:`~flask.Flask.view_functions` dictionary with the + endpoint as key. +`defaults` A dictionary with defaults for this rule. See the + example above for how defaults work. +`subdomain` specifies the rule for the subdomain in case subdomain + matching is in use. If not specified the default + subdomain is assumed. +`**options` the options to be forwarded to the underlying + :class:`~werkzeug.routing.Rule` object. A change to + Werkzeug is handling of method options. methods is a list + of methods this rule should be limited to (``GET``, ``POST`` + etc.). By default a rule just listens for ``GET`` (and + implicitly ``HEAD``). Starting with Flask 0.6, ``OPTIONS`` is + implicitly added and handled by the standard request + handling. They have to be specified as keyword arguments. +=============== ========================================================== + + +View Function Options +--------------------- + +For internal usage the view functions can have some attributes attached to +customize behavior the view function would normally not have control over. +The following attributes can be provided optionally to either override +some defaults to :meth:`~flask.Flask.add_url_rule` or general behavior: + +- `__name__`: The name of a function is by default used as endpoint. If + endpoint is provided explicitly this value is used. Additionally this + will be prefixed with the name of the blueprint by default which + cannot be customized from the function itself. + +- `methods`: If methods are not provided when the URL rule is added, + Flask will look on the view function object itself if a `methods` + attribute exists. If it does, it will pull the information for the + methods from there. + +- `provide_automatic_options`: if this attribute is set Flask will + either force enable or disable the automatic implementation of the + HTTP ``OPTIONS`` response. This can be useful when working with + decorators that want to customize the ``OPTIONS`` response on a per-view + basis. + +- `required_methods`: if this attribute is set, Flask will always add + these methods when registering a URL rule even if the methods were + explicitly overridden in the ``route()`` call. + +Full example:: + + def index(): + if request.method == 'OPTIONS': + # custom options handling here + ... + return 'Hello World!' + index.provide_automatic_options = False + index.methods = ['GET', 'OPTIONS'] + + app.add_url_rule('/', index) + +.. versionadded:: 0.8 + The `provide_automatic_options` functionality was added. + +Command Line Interface +---------------------- + +.. currentmodule:: flask.cli + +.. autoclass:: FlaskGroup + :members: + +.. autoclass:: AppGroup + :members: + +.. autoclass:: ScriptInfo + :members: + +.. autofunction:: load_dotenv + +.. autofunction:: with_appcontext + +.. autofunction:: pass_script_info + + Marks a function so that an instance of :class:`ScriptInfo` is passed + as first argument to the click callback. + +.. autodata:: run_command + +.. autodata:: shell_command diff --git a/src/flask-main/docs/appcontext.rst b/src/flask-main/docs/appcontext.rst new file mode 100644 index 0000000..81b73b4 --- /dev/null +++ b/src/flask-main/docs/appcontext.rst @@ -0,0 +1,186 @@ +The App and Request Context +=========================== + +The context keeps track of data and objects during a request, CLI command, or +other activity. Rather than passing this data around to every function, the +:data:`.current_app`, :data:`.g`, :data:`.request`, and :data:`.session` proxies +are accessed instead. + +When handling a request, the context is referred to as the "request context" +because it contains request data in addition to application data. Otherwise, +such as during a CLI command, it is referred to as the "app context". During an +app context, :data:`.current_app` and :data:`.g` are available, while during a +request context :data:`.request` and :data:`.session` are also available. + + +Purpose of the Context +---------------------- + +The context and proxies help solve two development issues: circular imports, and +passing around global data during a request. + +The :class:`.Flask` application object has attributes, such as +:attr:`~.Flask.config`, that are useful to access within views and other +functions. However, importing the ``app`` instance within the modules in your +project is prone to circular import issues. When using the +:doc:`app factory pattern ` or writing reusable +:doc:`blueprints ` or :doc:`extensions ` there won't +be an ``app`` instance to import at all. + +When the application handles a request, it creates a :class:`.Request` object. +Because a *worker* handles only one request at a time, the request data can be +considered global to that worker during that request. Passing it as an argument +through every function during the request becomes verbose and redundant. + +Flask solves these issues with the *active context* pattern. Rather than +importing an ``app`` directly, or having to pass it and the request through to +every single function, you import and access the proxies, which point to the +currently active application and request data. This is sometimes referred to +as "context local" data. + + +Context During Setup +-------------------- + +If you try to access :data:`.current_app`, :data:`.g`, or anything that uses it, +outside an app context, you'll get this error message: + +.. code-block:: pytb + + RuntimeError: Working outside of application context. + + Attempted to use functionality that expected a current application to be + set. To solve this, set up an app context using 'with app.app_context()'. + See the documentation on app context for more information. + +If you see that error while configuring your application, such as when +initializing an extension, you can push a context manually since you have direct +access to the ``app``. Use :meth:`.Flask.app_context` in a ``with`` block. + +.. code-block:: python + + def create_app(): + app = Flask(__name__) + + with app.app_context(): + init_db() + + return app + +If you see that error somewhere else in your code not related to setting up the +application, it most likely indicates that you should move that code into a view +function or CLI command. + + +Context During Testing +---------------------- + +See :doc:`/testing` for detailed information about managing the context during +tests. + +If you try to access :data:`.request`, :data:`.session`, or anything that uses +it, outside a request context, you'll get this error message: + +.. code-block:: pytb + + RuntimeError: Working outside of request context. + + Attempted to use functionality that expected an active HTTP request. See the + documentation on request context for more information. + +This will probably only happen during tests. If you see that error somewhere +else in your code not related to testing, it most likely indicates that you +should move that code into a view function. + +The primary way to solve this is to use :meth:`.Flask.test_client` to simulate +a full request. + +If you only want to unit test one function, rather than a full request, use +:meth:`.Flask.test_request_context` in a ``with`` block. + +.. code-block:: python + + def generate_report(year): + format = request.args.get("format") + ... + + with app.test_request_context( + "/make_report/2017", query_string={"format": "short"} + ): + generate_report() + + +.. _context-visibility: + +Visibility of the Context +------------------------- + +The context will have the same lifetime as an activity, such as a request, CLI +command, or ``with`` block. Various callbacks and signals registered with the +app will be run during the context. + +When a Flask application handles a request, it pushes a requet context +to set the active application and request data. When it handles a CLI command, +it pushes an app context to set the active application. When the activity ends, +it pops that context. Proxy objects like :data:`.request`, :data:`.session`, +:data:`.g`, and :data:`.current_app`, are accessible while the context is pushed +and active, and are not accessible after the context is popped. + +The context is unique to each thread (or other worker type). The proxies cannot +be passed to another worker, which has a different context space and will not +know about the active context in the parent's space. + +Besides being scoped to each worker, the proxy object has a separate type and +identity than the proxied real object. In some cases you'll need access to the +real object, rather than the proxy. Use the +:meth:`~.LocalProxy._get_current_object` method in those cases. + +.. code-block:: python + + app = current_app._get_current_object() + my_signal.send(app) + + +Lifecycle of the Context +------------------------ + +Flask dispatches a request in multiple stages which can affect the request, +response, and how errors are handled. See :doc:`/lifecycle` for a list of all +the steps, callbacks, and signals during each request. The following are the +steps directly related to the context. + +- The app context is pushed, the proxies are available. +- The :data:`.appcontext_pushed` signal is sent. +- The request is dispatched. +- Any :meth:`.Flask.teardown_request` decorated functions are called. +- The :data:`.request_tearing_down` signal is sent. +- Any :meth:`.Flask.teardown_appcontext` decorated functions are called. +- The :data:`.appcontext_tearing_down` signal is sent. +- The app context is popped, the proxies are no longer available. +- The :data:`.appcontext_popped` signal is sent. + +The teardown callbacks are called by the context when it is popped. They are +called even if there is an unhandled exception during dispatch. They may be +called multiple times in some test scenarios. This means there is no guarantee +that any other parts of the request dispatch have run. Be sure to write these +functions in a way that does not depend on other callbacks and will not fail. + + +How the Context Works +--------------------- + +Context locals are implemented using Python's :mod:`contextvars` and Werkzeug's +:class:`~werkzeug.local.LocalProxy`. Python's contextvars are a low level +structure to manage data local to a thread or coroutine. ``LocalProxy`` wraps +the contextvar so that access to any attributes and methods is forwarded to the +object stored in the contextvar. + +The context is tracked like a stack, with the active context at the top of the +stack. Flask manages pushing and popping contexts during requests, CLI commands, +testing, ``with`` blocks, etc. The proxies access attributes on the active +context. + +Because it is a stack, other contexts may be pushed to change the proxies during +an already active context. This is not a common pattern, but can be used in +advanced use cases. For example, a Flask application can be used as WSGI +middleware, calling another wrapped Flask app from a view. diff --git a/src/flask-main/docs/async-await.rst b/src/flask-main/docs/async-await.rst new file mode 100644 index 0000000..16b6194 --- /dev/null +++ b/src/flask-main/docs/async-await.rst @@ -0,0 +1,125 @@ +.. _async_await: + +Using ``async`` and ``await`` +============================= + +.. versionadded:: 2.0 + +Routes, error handlers, before request, after request, and teardown +functions can all be coroutine functions if Flask is installed with the +``async`` extra (``pip install flask[async]``). This allows views to be +defined with ``async def`` and use ``await``. + +.. code-block:: python + + @app.route("/get-data") + async def get_data(): + data = await async_db_query(...) + return jsonify(data) + +Pluggable class-based views also support handlers that are implemented as +coroutines. This applies to the :meth:`~flask.views.View.dispatch_request` +method in views that inherit from the :class:`flask.views.View` class, as +well as all the HTTP method handlers in views that inherit from the +:class:`flask.views.MethodView` class. + +.. admonition:: Using ``async`` with greenlet + + When using gevent or eventlet to serve an application or patch the + runtime, greenlet>=1.0 is required. When using PyPy, PyPy>=7.3.7 is + required. + + +Performance +----------- + +Async functions require an event loop to run. Flask, as a WSGI +application, uses one worker to handle one request/response cycle. +When a request comes in to an async view, Flask will start an event loop +in a thread, run the view function there, then return the result. + +Each request still ties up one worker, even for async views. The upside +is that you can run async code within a view, for example to make +multiple concurrent database queries, HTTP requests to an external API, +etc. However, the number of requests your application can handle at one +time will remain the same. + +**Async is not inherently faster than sync code.** Async is beneficial +when performing concurrent IO-bound tasks, but will probably not improve +CPU-bound tasks. Traditional Flask views will still be appropriate for +most use cases, but Flask's async support enables writing and using +code that wasn't possible natively before. + + +Background tasks +---------------- + +Async functions will run in an event loop until they complete, at +which stage the event loop will stop. This means any additional +spawned tasks that haven't completed when the async function completes +will be cancelled. Therefore you cannot spawn background tasks, for +example via ``asyncio.create_task``. + +If you wish to use background tasks it is best to use a task queue to +trigger background work, rather than spawn tasks in a view +function. With that in mind you can spawn asyncio tasks by serving +Flask with an ASGI server and utilising the asgiref WsgiToAsgi adapter +as described in :doc:`deploying/asgi`. This works as the adapter creates +an event loop that runs continually. + + +When to use Quart instead +------------------------- + +Flask's async support is less performant than async-first frameworks due +to the way it is implemented. If you have a mainly async codebase it +would make sense to consider `Quart`_. Quart is a reimplementation of +Flask based on the `ASGI`_ standard instead of WSGI. This allows it to +handle many concurrent requests, long running requests, and websockets +without requiring multiple worker processes or threads. + +It has also already been possible to run Flask with Gevent or Eventlet +to get many of the benefits of async request handling. These libraries +patch low-level Python functions to accomplish this, whereas ``async``/ +``await`` and ASGI use standard, modern Python capabilities. Deciding +whether you should use Flask, Quart, or something else is ultimately up +to understanding the specific needs of your project. + +.. _Quart: https://github.com/pallets/quart +.. _ASGI: https://asgi.readthedocs.io/en/latest/ + + +Extensions +---------- + +Flask extensions predating Flask's async support do not expect async views. +If they provide decorators to add functionality to views, those will probably +not work with async views because they will not await the function or be +awaitable. Other functions they provide will not be awaitable either and +will probably be blocking if called within an async view. + +Extension authors can support async functions by utilising the +:meth:`flask.Flask.ensure_sync` method. For example, if the extension +provides a view function decorator add ``ensure_sync`` before calling +the decorated function, + +.. code-block:: python + + def extension(func): + @wraps(func) + def wrapper(*args, **kwargs): + ... # Extension logic + return current_app.ensure_sync(func)(*args, **kwargs) + + return wrapper + +Check the changelog of the extension you want to use to see if they've +implemented async support, or make a feature request or PR to them. + + +Other event loops +----------------- + +At the moment Flask only supports :mod:`asyncio`. It's possible to +override :meth:`flask.Flask.ensure_sync` to change how async functions +are wrapped to use a different library. diff --git a/src/flask-main/docs/blueprints.rst b/src/flask-main/docs/blueprints.rst new file mode 100644 index 0000000..d5cf3d8 --- /dev/null +++ b/src/flask-main/docs/blueprints.rst @@ -0,0 +1,315 @@ +Modular Applications with Blueprints +==================================== + +.. currentmodule:: flask + +.. versionadded:: 0.7 + +Flask uses a concept of *blueprints* for making application components and +supporting common patterns within an application or across applications. +Blueprints can greatly simplify how large applications work and provide a +central means for Flask extensions to register operations on applications. +A :class:`Blueprint` object works similarly to a :class:`Flask` +application object, but it is not actually an application. Rather it is a +*blueprint* of how to construct or extend an application. + +Why Blueprints? +--------------- + +Blueprints in Flask are intended for these cases: + +* Factor an application into a set of blueprints. This is ideal for + larger applications; a project could instantiate an application object, + initialize several extensions, and register a collection of blueprints. +* Register a blueprint on an application at a URL prefix and/or subdomain. + Parameters in the URL prefix/subdomain become common view arguments + (with defaults) across all view functions in the blueprint. +* Register a blueprint multiple times on an application with different URL + rules. +* Provide template filters, static files, templates, and other utilities + through blueprints. A blueprint does not have to implement applications + or view functions. +* Register a blueprint on an application for any of these cases when + initializing a Flask extension. + +A blueprint in Flask is not a pluggable app because it is not actually an +application -- it's a set of operations which can be registered on an +application, even multiple times. Why not have multiple application +objects? You can do that (see :doc:`/patterns/appdispatch`), but your +applications will have separate configs and will be managed at the WSGI +layer. + +Blueprints instead provide separation at the Flask level, share +application config, and can change an application object as necessary with +being registered. The downside is that you cannot unregister a blueprint +once an application was created without having to destroy the whole +application object. + +The Concept of Blueprints +------------------------- + +The basic concept of blueprints is that they record operations to execute +when registered on an application. Flask associates view functions with +blueprints when dispatching requests and generating URLs from one endpoint +to another. + +My First Blueprint +------------------ + +This is what a very basic blueprint looks like. In this case we want to +implement a blueprint that does simple rendering of static templates:: + + from flask import Blueprint, render_template, abort + from jinja2 import TemplateNotFound + + simple_page = Blueprint('simple_page', __name__, + template_folder='templates') + + @simple_page.route('/', defaults={'page': 'index'}) + @simple_page.route('/') + def show(page): + try: + return render_template(f'pages/{page}.html') + except TemplateNotFound: + abort(404) + +When you bind a function with the help of the ``@simple_page.route`` +decorator, the blueprint will record the intention of registering the +function ``show`` on the application when it's later registered. +Additionally it will prefix the endpoint of the function with the +name of the blueprint which was given to the :class:`Blueprint` +constructor (in this case also ``simple_page``). The blueprint's name +does not modify the URL, only the endpoint. + +Registering Blueprints +---------------------- + +So how do you register that blueprint? Like this:: + + from flask import Flask + from yourapplication.simple_page import simple_page + + app = Flask(__name__) + app.register_blueprint(simple_page) + +If you check the rules registered on the application, you will find +these:: + + >>> app.url_map + Map([' (HEAD, OPTIONS, GET) -> static>, + ' (HEAD, OPTIONS, GET) -> simple_page.show>, + simple_page.show>]) + +The first one is obviously from the application itself for the static +files. The other two are for the `show` function of the ``simple_page`` +blueprint. As you can see, they are also prefixed with the name of the +blueprint and separated by a dot (``.``). + +Blueprints however can also be mounted at different locations:: + + app.register_blueprint(simple_page, url_prefix='/pages') + +And sure enough, these are the generated rules:: + + >>> app.url_map + Map([' (HEAD, OPTIONS, GET) -> static>, + ' (HEAD, OPTIONS, GET) -> simple_page.show>, + simple_page.show>]) + +On top of that you can register blueprints multiple times though not every +blueprint might respond properly to that. In fact it depends on how the +blueprint is implemented if it can be mounted more than once. + +Nesting Blueprints +------------------ + +It is possible to register a blueprint on another blueprint. + +.. code-block:: python + + parent = Blueprint('parent', __name__, url_prefix='/parent') + child = Blueprint('child', __name__, url_prefix='/child') + parent.register_blueprint(child) + app.register_blueprint(parent) + +The child blueprint will gain the parent's name as a prefix to its +name, and child URLs will be prefixed with the parent's URL prefix. + +.. code-block:: python + + url_for('parent.child.create') + /parent/child/create + +In addition a child blueprint's will gain their parent's subdomain, +with their subdomain as prefix if present i.e. + +.. code-block:: python + + parent = Blueprint('parent', __name__, subdomain='parent') + child = Blueprint('child', __name__, subdomain='child') + parent.register_blueprint(child) + app.register_blueprint(parent) + + url_for('parent.child.create', _external=True) + "child.parent.domain.tld" + +Blueprint-specific before request functions, etc. registered with the +parent will trigger for the child. If a child does not have an error +handler that can handle a given exception, the parent's will be tried. + + +Blueprint Resources +------------------- + +Blueprints can provide resources as well. Sometimes you might want to +introduce a blueprint only for the resources it provides. + +Blueprint Resource Folder +````````````````````````` + +Like for regular applications, blueprints are considered to be contained +in a folder. While multiple blueprints can originate from the same folder, +it does not have to be the case and it's usually not recommended. + +The folder is inferred from the second argument to :class:`Blueprint` which +is usually `__name__`. This argument specifies what logical Python +module or package corresponds to the blueprint. If it points to an actual +Python package that package (which is a folder on the filesystem) is the +resource folder. If it's a module, the package the module is contained in +will be the resource folder. You can access the +:attr:`Blueprint.root_path` property to see what the resource folder is:: + + >>> simple_page.root_path + '/Users/username/TestProject/yourapplication' + +To quickly open sources from this folder you can use the +:meth:`~Blueprint.open_resource` function:: + + with simple_page.open_resource('static/style.css') as f: + code = f.read() + +Static Files +```````````` + +A blueprint can expose a folder with static files by providing the path +to the folder on the filesystem with the ``static_folder`` argument. +It is either an absolute path or relative to the blueprint's location:: + + admin = Blueprint('admin', __name__, static_folder='static') + +By default the rightmost part of the path is where it is exposed on the +web. This can be changed with the ``static_url_path`` argument. Because the +folder is called ``static`` here it will be available at the +``url_prefix`` of the blueprint + ``/static``. If the blueprint +has the prefix ``/admin``, the static URL will be ``/admin/static``. + +The endpoint is named ``blueprint_name.static``. You can generate URLs +to it with :func:`url_for` like you would with the static folder of the +application:: + + url_for('admin.static', filename='style.css') + +However, if the blueprint does not have a ``url_prefix``, it is not +possible to access the blueprint's static folder. This is because the +URL would be ``/static`` in this case, and the application's ``/static`` +route takes precedence. Unlike template folders, blueprint static +folders are not searched if the file does not exist in the application +static folder. + +Templates +````````` + +If you want the blueprint to expose templates you can do that by providing +the `template_folder` parameter to the :class:`Blueprint` constructor:: + + admin = Blueprint('admin', __name__, template_folder='templates') + +For static files, the path can be absolute or relative to the blueprint +resource folder. + +The template folder is added to the search path of templates but with a lower +priority than the actual application's template folder. That way you can +easily override templates that a blueprint provides in the actual application. +This also means that if you don't want a blueprint template to be accidentally +overridden, make sure that no other blueprint or actual application template +has the same relative path. When multiple blueprints provide the same relative +template path the first blueprint registered takes precedence over the others. + + +So if you have a blueprint in the folder ``yourapplication/admin`` and you +want to render the template ``'admin/index.html'`` and you have provided +``templates`` as a `template_folder` you will have to create a file like +this: :file:`yourapplication/admin/templates/admin/index.html`. The reason +for the extra ``admin`` folder is to avoid getting our template overridden +by a template named ``index.html`` in the actual application template +folder. + +To further reiterate this: if you have a blueprint named ``admin`` and you +want to render a template called :file:`index.html` which is specific to this +blueprint, the best idea is to lay out your templates like this:: + + yourpackage/ + blueprints/ + admin/ + templates/ + admin/ + index.html + __init__.py + +And then when you want to render the template, use :file:`admin/index.html` as +the name to look up the template by. If you encounter problems loading +the correct templates enable the ``EXPLAIN_TEMPLATE_LOADING`` config +variable which will instruct Flask to print out the steps it goes through +to locate templates on every ``render_template`` call. + +Building URLs +------------- + +If you want to link from one page to another you can use the +:func:`url_for` function just like you normally would do just that you +prefix the URL endpoint with the name of the blueprint and a dot (``.``):: + + url_for('admin.index') + +Additionally if you are in a view function of a blueprint or a rendered +template and you want to link to another endpoint of the same blueprint, +you can use relative redirects by prefixing the endpoint with a dot only:: + + url_for('.index') + +This will link to ``admin.index`` for instance in case the current request +was dispatched to any other admin blueprint endpoint. + + +Blueprint Error Handlers +------------------------ + +Blueprints support the ``errorhandler`` decorator just like the :class:`Flask` +application object, so it is easy to make Blueprint-specific custom error +pages. + +Here is an example for a "404 Page Not Found" exception:: + + @simple_page.errorhandler(404) + def page_not_found(e): + return render_template('pages/404.html') + +Most errorhandlers will simply work as expected; however, there is a caveat +concerning handlers for 404 and 405 exceptions. These errorhandlers are only +invoked from an appropriate ``raise`` statement or a call to ``abort`` in another +of the blueprint's view functions; they are not invoked by, e.g., an invalid URL +access. This is because the blueprint does not "own" a certain URL space, so +the application instance has no way of knowing which blueprint error handler it +should run if given an invalid URL. If you would like to execute different +handling strategies for these errors based on URL prefixes, they may be defined +at the application level using the ``request`` proxy object:: + + @app.errorhandler(404) + @app.errorhandler(405) + def _handle_api_error(ex): + if request.path.startswith('/api/'): + return jsonify(error=str(ex)), ex.code + else: + return ex + +See :doc:`/errorhandling`. diff --git a/src/flask-main/docs/changes.rst b/src/flask-main/docs/changes.rst new file mode 100644 index 0000000..955deaf --- /dev/null +++ b/src/flask-main/docs/changes.rst @@ -0,0 +1,4 @@ +Changes +======= + +.. include:: ../CHANGES.rst diff --git a/src/flask-main/docs/cli.rst b/src/flask-main/docs/cli.rst new file mode 100644 index 0000000..a72e6d5 --- /dev/null +++ b/src/flask-main/docs/cli.rst @@ -0,0 +1,556 @@ +.. currentmodule:: flask + +Command Line Interface +====================== + +Installing Flask installs the ``flask`` script, a `Click`_ command line +interface, in your virtualenv. Executed from the terminal, this script gives +access to built-in, extension, and application-defined commands. The ``--help`` +option will give more information about any commands and options. + +.. _Click: https://click.palletsprojects.com/ + + +Application Discovery +--------------------- + +The ``flask`` command is installed by Flask, not your application; it must be +told where to find your application in order to use it. The ``--app`` +option is used to specify how to load the application. + +While ``--app`` supports a variety of options for specifying your +application, most use cases should be simple. Here are the typical values: + +(nothing) + The name "app" or "wsgi" is imported (as a ".py" file, or package), + automatically detecting an app (``app`` or ``application``) or + factory (``create_app`` or ``make_app``). + +``--app hello`` + The given name is imported, automatically detecting an app (``app`` + or ``application``) or factory (``create_app`` or ``make_app``). + +---- + +``--app`` has three parts: an optional path that sets the current working +directory, a Python file or dotted import path, and an optional variable +name of the instance or factory. If the name is a factory, it can optionally +be followed by arguments in parentheses. The following values demonstrate these +parts: + +``--app src/hello`` + Sets the current working directory to ``src`` then imports ``hello``. + +``--app hello.web`` + Imports the path ``hello.web``. + +``--app hello:app2`` + Uses the ``app2`` Flask instance in ``hello``. + +``--app 'hello:create_app("dev")'`` + The ``create_app`` factory in ``hello`` is called with the string ``'dev'`` + as the argument. + +If ``--app`` is not set, the command will try to import "app" or +"wsgi" (as a ".py" file, or package) and try to detect an application +instance or factory. + +Within the given import, the command looks for an application instance named +``app`` or ``application``, then any application instance. If no instance is +found, the command looks for a factory function named ``create_app`` or +``make_app`` that returns an instance. + +If parentheses follow the factory name, their contents are parsed as +Python literals and passed as arguments and keyword arguments to the +function. This means that strings must still be in quotes. + + +Run the Development Server +-------------------------- + +The :func:`run ` command will start the development server. It +replaces the :meth:`Flask.run` method in most cases. :: + + $ flask --app hello run + * Serving Flask app "hello" + * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) + +.. warning:: Do not use this command to run your application in production. + Only use the development server during development. The development server + is provided for convenience, but is not designed to be particularly secure, + stable, or efficient. See :doc:`/deploying/index` for how to run in production. + +If another program is already using port 5000, you'll see +``OSError: [Errno 98]`` or ``OSError: [WinError 10013]`` when the +server tries to start. See :ref:`address-already-in-use` for how to +handle that. + + +Debug Mode +~~~~~~~~~~ + +In debug mode, the ``flask run`` command will enable the interactive debugger and the +reloader by default, and make errors easier to see and debug. To enable debug mode, use +the ``--debug`` option. + +.. code-block:: console + + $ flask --app hello run --debug + * Serving Flask app "hello" + * Debug mode: on + * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) + * Restarting with inotify reloader + * Debugger is active! + * Debugger PIN: 223-456-919 + +The ``--debug`` option can also be passed to the top level ``flask`` command to enable +debug mode for any command. The following two ``run`` calls are equivalent. + +.. code-block:: console + + $ flask --app hello --debug run + $ flask --app hello run --debug + + +Watch and Ignore Files with the Reloader +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When using debug mode, the reloader will trigger whenever your Python code or imported +modules change. The reloader can watch additional files with the ``--extra-files`` +option. Multiple paths are separated with ``:``, or ``;`` on Windows. + +.. code-block:: text + + $ flask run --extra-files file1:dirA/file2:dirB/ + * Running on http://127.0.0.1:8000/ + * Detected change in '/path/to/file1', reloading + +The reloader can also ignore files using :mod:`fnmatch` patterns with the +``--exclude-patterns`` option. Multiple patterns are separated with ``:``, or ``;`` on +Windows. + + +Open a Shell +------------ + +To explore the data in your application, you can start an interactive Python +shell with the :func:`shell ` command. An application +context will be active, and the app instance will be imported. :: + + $ flask shell + Python 3.10.0 (default, Oct 27 2021, 06:59:51) [GCC 11.1.0] on linux + App: example [production] + Instance: /home/david/Projects/pallets/flask/instance + >>> + +Use :meth:`~Flask.shell_context_processor` to add other automatic imports. + + +.. _dotenv: + +Environment Variables From dotenv +--------------------------------- + +The ``flask`` command supports setting any option for any command with +environment variables. The variables are named like ``FLASK_OPTION`` or +``FLASK_COMMAND_OPTION``, for example ``FLASK_APP`` or +``FLASK_RUN_PORT``. + +Rather than passing options every time you run a command, or environment +variables every time you open a new terminal, you can use Flask's dotenv +support to set environment variables automatically. + +If `python-dotenv`_ is installed, running the ``flask`` command will set +environment variables defined in the files ``.env`` and ``.flaskenv``. +You can also specify an extra file to load with the ``--env-file`` +option. Dotenv files can be used to avoid having to set ``--app`` or +``FLASK_APP`` manually, and to set configuration using environment +variables similar to how some deployment services work. + +Variables set on the command line are used over those set in :file:`.env`, +which are used over those set in :file:`.flaskenv`. :file:`.flaskenv` should be +used for public variables, such as ``FLASK_APP``, while :file:`.env` should not +be committed to your repository so that it can set private variables. + +Directories are scanned upwards from the directory you call ``flask`` +from to locate the files. + +The files are only loaded by the ``flask`` command or calling +:meth:`~Flask.run`. If you would like to load these files when running in +production, you should call :func:`~cli.load_dotenv` manually. + +.. _python-dotenv: https://github.com/theskumar/python-dotenv#readme + + +Setting Command Options +~~~~~~~~~~~~~~~~~~~~~~~ + +Click is configured to load default values for command options from +environment variables. The variables use the pattern +``FLASK_COMMAND_OPTION``. For example, to set the port for the run +command, instead of ``flask run --port 8000``: + +.. tabs:: + + .. group-tab:: Bash + + .. code-block:: text + + $ export FLASK_RUN_PORT=8000 + $ flask run + * Running on http://127.0.0.1:8000/ + + .. group-tab:: Fish + + .. code-block:: text + + $ set -x FLASK_RUN_PORT 8000 + $ flask run + * Running on http://127.0.0.1:8000/ + + .. group-tab:: CMD + + .. code-block:: text + + > set FLASK_RUN_PORT=8000 + > flask run + * Running on http://127.0.0.1:8000/ + + .. group-tab:: Powershell + + .. code-block:: text + + > $env:FLASK_RUN_PORT = 8000 + > flask run + * Running on http://127.0.0.1:8000/ + +These can be added to the ``.flaskenv`` file just like ``FLASK_APP`` to +control default command options. + + +Disable dotenv +~~~~~~~~~~~~~~ + +The ``flask`` command will show a message if it detects dotenv files but +python-dotenv is not installed. + +.. code-block:: bash + + $ flask run + * Tip: There are .env files present. Do "pip install python-dotenv" to use them. + +You can tell Flask not to load dotenv files even when python-dotenv is +installed by setting the ``FLASK_SKIP_DOTENV`` environment variable. +This can be useful if you want to load them manually, or if you're using +a project runner that loads them already. Keep in mind that the +environment variables must be set before the app loads or it won't +configure as expected. + +.. tabs:: + + .. group-tab:: Bash + + .. code-block:: text + + $ export FLASK_SKIP_DOTENV=1 + $ flask run + + .. group-tab:: Fish + + .. code-block:: text + + $ set -x FLASK_SKIP_DOTENV 1 + $ flask run + + .. group-tab:: CMD + + .. code-block:: text + + > set FLASK_SKIP_DOTENV=1 + > flask run + + .. group-tab:: Powershell + + .. code-block:: text + + > $env:FLASK_SKIP_DOTENV = 1 + > flask run + + +Environment Variables From virtualenv +------------------------------------- + +If you do not want to install dotenv support, you can still set environment +variables by adding them to the end of the virtualenv's :file:`activate` +script. Activating the virtualenv will set the variables. + +.. tabs:: + + .. group-tab:: Bash + + Unix Bash, :file:`.venv/bin/activate`:: + + $ export FLASK_APP=hello + + .. group-tab:: Fish + + Fish, :file:`.venv/bin/activate.fish`:: + + $ set -x FLASK_APP hello + + .. group-tab:: CMD + + Windows CMD, :file:`.venv\\Scripts\\activate.bat`:: + + > set FLASK_APP=hello + + .. group-tab:: Powershell + + Windows Powershell, :file:`.venv\\Scripts\\activate.ps1`:: + + > $env:FLASK_APP = "hello" + +It is preferred to use dotenv support over this, since :file:`.flaskenv` can be +committed to the repository so that it works automatically wherever the project +is checked out. + + +Custom Commands +--------------- + +The ``flask`` command is implemented using `Click`_. See that project's +documentation for full information about writing commands. + +This example adds the command ``create-user`` that takes the argument +``name``. :: + + import click + from flask import Flask + + app = Flask(__name__) + + @app.cli.command("create-user") + @click.argument("name") + def create_user(name): + ... + +:: + + $ flask create-user admin + +This example adds the same command, but as ``user create``, a command in a +group. This is useful if you want to organize multiple related commands. :: + + import click + from flask import Flask + from flask.cli import AppGroup + + app = Flask(__name__) + user_cli = AppGroup('user') + + @user_cli.command('create') + @click.argument('name') + def create_user(name): + ... + + app.cli.add_command(user_cli) + +:: + + $ flask user create demo + +See :ref:`testing-cli` for an overview of how to test your custom +commands. + + +Registering Commands with Blueprints +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If your application uses blueprints, you can optionally register CLI +commands directly onto them. When your blueprint is registered onto your +application, the associated commands will be available to the ``flask`` +command. By default, those commands will be nested in a group matching +the name of the blueprint. + +.. code-block:: python + + from flask import Blueprint + + bp = Blueprint('students', __name__) + + @bp.cli.command('create') + @click.argument('name') + def create(name): + ... + + app.register_blueprint(bp) + +.. code-block:: text + + $ flask students create alice + +You can alter the group name by specifying the ``cli_group`` parameter +when creating the :class:`Blueprint` object, or later with +:meth:`app.register_blueprint(bp, cli_group='...') `. +The following are equivalent: + +.. code-block:: python + + bp = Blueprint('students', __name__, cli_group='other') + # or + app.register_blueprint(bp, cli_group='other') + +.. code-block:: text + + $ flask other create alice + +Specifying ``cli_group=None`` will remove the nesting and merge the +commands directly to the application's level: + +.. code-block:: python + + bp = Blueprint('students', __name__, cli_group=None) + # or + app.register_blueprint(bp, cli_group=None) + +.. code-block:: text + + $ flask create alice + + +Application Context +~~~~~~~~~~~~~~~~~~~ + +Commands added using the Flask app's :attr:`~Flask.cli` or +:class:`~flask.cli.FlaskGroup` :meth:`~cli.AppGroup.command` decorator +will be executed with an application context pushed, so your custom +commands and parameters have access to the app and its configuration. The +:func:`~cli.with_appcontext` decorator can be used to get the same +behavior, but is not needed in most cases. + +.. code-block:: python + + import click + from flask.cli import with_appcontext + + @click.command() + @with_appcontext + def do_work(): + ... + + app.cli.add_command(do_work) + + +Plugins +------- + +Flask will automatically load commands specified in the ``flask.commands`` +`entry point`_. This is useful for extensions that want to add commands when +they are installed. Entry points are specified in :file:`pyproject.toml`: + +.. code-block:: toml + + [project.entry-points."flask.commands"] + my-command = "my_extension.commands:cli" + +.. _entry point: https://packaging.python.org/tutorials/packaging-projects/#entry-points + +Inside :file:`my_extension/commands.py` you can then export a Click +object:: + + import click + + @click.command() + def cli(): + ... + +Once that package is installed in the same virtualenv as your Flask project, +you can run ``flask my-command`` to invoke the command. + + +.. _custom-scripts: + +Custom Scripts +-------------- + +When you are using the app factory pattern, it may be more convenient to define +your own Click script. Instead of using ``--app`` and letting Flask load +your application, you can create your own Click object and export it as a +`console script`_ entry point. + +Create an instance of :class:`~cli.FlaskGroup` and pass it the factory:: + + import click + from flask import Flask + from flask.cli import FlaskGroup + + def create_app(): + app = Flask('wiki') + # other setup + return app + + @click.group(cls=FlaskGroup, create_app=create_app) + def cli(): + """Management script for the Wiki application.""" + +Define the entry point in :file:`pyproject.toml`: + +.. code-block:: toml + + [project.scripts] + wiki = "wiki:cli" + +Install the application in the virtualenv in editable mode and the custom +script is available. Note that you don't need to set ``--app``. :: + + $ pip install -e . + $ wiki run + +.. admonition:: Errors in Custom Scripts + + When using a custom script, if you introduce an error in your + module-level code, the reloader will fail because it can no longer + load the entry point. + + The ``flask`` command, being separate from your code, does not have + this issue and is recommended in most cases. + +.. _console script: https://packaging.python.org/tutorials/packaging-projects/#console-scripts + + +PyCharm Integration +------------------- + +PyCharm Professional provides a special Flask run configuration to run the development +server. For the Community Edition, and for other commands besides ``run``, you need to +create a custom run configuration. These instructions should be similar for any other +IDE you use. + +In PyCharm, with your project open, click on *Run* from the menu bar and go to *Edit +Configurations*. You'll see a screen similar to this: + +.. image:: _static/pycharm-run-config.png + :align: center + :class: screenshot + :alt: Screenshot of PyCharm run configuration. + +Once you create a configuration for the ``flask run``, you can copy and change it to +call any other command. + +Click the *+ (Add New Configuration)* button and select *Python*. Give the configuration +a name such as "flask run". + +Click the *Script path* dropdown and change it to *Module name*, then input ``flask``. + +The *Parameters* field is set to the CLI command to execute along with any arguments. +This example uses ``--app hello run --debug``, which will run the development server in +debug mode. ``--app hello`` should be the import or file with your Flask app. + +If you installed your project as a package in your virtualenv, you may uncheck the +*PYTHONPATH* options. This will more accurately match how you deploy later. + +Click *OK* to save and close the configuration. Select the configuration in the main +PyCharm window and click the play button next to it to run the server. + +Now that you have a configuration for ``flask run``, you can copy that configuration and +change the *Parameters* argument to run a different CLI command. diff --git a/src/flask-main/docs/conf.py b/src/flask-main/docs/conf.py new file mode 100644 index 0000000..af8adf0 --- /dev/null +++ b/src/flask-main/docs/conf.py @@ -0,0 +1,101 @@ +import packaging.version +from pallets_sphinx_themes import get_version +from pallets_sphinx_themes import ProjectLink + +# Project -------------------------------------------------------------- + +project = "Flask" +copyright = "2010 Pallets" +author = "Pallets" +release, version = get_version("Flask") + +# General -------------------------------------------------------------- + +default_role = "code" +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.extlinks", + "sphinx.ext.intersphinx", + "sphinxcontrib.log_cabinet", + "sphinx_tabs.tabs", + "pallets_sphinx_themes", +] +autodoc_member_order = "bysource" +autodoc_typehints = "description" +autodoc_preserve_defaults = True +extlinks = { + "issue": ("https://github.com/pallets/flask/issues/%s", "#%s"), + "pr": ("https://github.com/pallets/flask/pull/%s", "#%s"), + "ghsa": ("https://github.com/pallets/flask/security/advisories/GHSA-%s", "GHSA-%s"), +} +intersphinx_mapping = { + "python": ("https://docs.python.org/3/", None), + "werkzeug": ("https://werkzeug.palletsprojects.com/", None), + "click": ("https://click.palletsprojects.com/", None), + "jinja": ("https://jinja.palletsprojects.com/", None), + "itsdangerous": ("https://itsdangerous.palletsprojects.com/", None), + "sqlalchemy": ("https://docs.sqlalchemy.org/", None), + "wtforms": ("https://wtforms.readthedocs.io/", None), + "blinker": ("https://blinker.readthedocs.io/", None), +} + +# HTML ----------------------------------------------------------------- + +html_theme = "flask" +html_theme_options = {"index_sidebar_logo": False} +html_context = { + "project_links": [ + ProjectLink("Donate", "https://palletsprojects.com/donate"), + ProjectLink("PyPI Releases", "https://pypi.org/project/Flask/"), + ProjectLink("Source Code", "https://github.com/pallets/flask/"), + ProjectLink("Issue Tracker", "https://github.com/pallets/flask/issues/"), + ProjectLink("Chat", "https://discord.gg/pallets"), + ] +} +html_sidebars = { + "index": ["project.html", "localtoc.html", "searchbox.html", "ethicalads.html"], + "**": ["localtoc.html", "relations.html", "searchbox.html", "ethicalads.html"], +} +singlehtml_sidebars = {"index": ["project.html", "localtoc.html", "ethicalads.html"]} +html_static_path = ["_static"] +html_favicon = "_static/flask-icon.svg" +html_logo = "_static/flask-logo.svg" +html_title = f"Flask Documentation ({version})" +html_show_sourcelink = False + +gettext_uuid = True +gettext_compact = False + +# Local Extensions ----------------------------------------------------- + + +def github_link(name, rawtext, text, lineno, inliner, options=None, content=None): + app = inliner.document.settings.env.app + release = app.config.release + base_url = "https://github.com/pallets/flask/tree/" + + if text.endswith(">"): + words, text = text[:-1].rsplit("<", 1) + words = words.strip() + else: + words = None + + if packaging.version.parse(release).is_devrelease: + url = f"{base_url}main/{text}" + else: + url = f"{base_url}{release}/{text}" + + if words is None: + words = url + + from docutils.nodes import reference + from docutils.parsers.rst.roles import set_classes + + options = options or {} + set_classes(options) + node = reference(rawtext, words, refuri=url, **options) + return [node], [] + + +def setup(app): + app.add_role("gh", github_link) diff --git a/src/flask-main/docs/config.rst b/src/flask-main/docs/config.rst new file mode 100644 index 0000000..e7d4410 --- /dev/null +++ b/src/flask-main/docs/config.rst @@ -0,0 +1,839 @@ +Configuration Handling +====================== + +Applications need some kind of configuration. There are different settings +you might want to change depending on the application environment like +toggling the debug mode, setting the secret key, and other such +environment-specific things. + +The way Flask is designed usually requires the configuration to be +available when the application starts up. You can hard code the +configuration in the code, which for many small applications is not +actually that bad, but there are better ways. + +Independent of how you load your config, there is a config object +available which holds the loaded configuration values: +The :attr:`~flask.Flask.config` attribute of the :class:`~flask.Flask` +object. This is the place where Flask itself puts certain configuration +values and also where extensions can put their configuration values. But +this is also where you can have your own configuration. + + +Configuration Basics +-------------------- + +The :attr:`~flask.Flask.config` is actually a subclass of a dictionary and +can be modified just like any dictionary:: + + app = Flask(__name__) + app.config['TESTING'] = True + +Certain configuration values are also forwarded to the +:attr:`~flask.Flask` object so you can read and write them from there:: + + app.testing = True + +To update multiple keys at once you can use the :meth:`dict.update` +method:: + + app.config.update( + TESTING=True, + SECRET_KEY='192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf' + ) + + +Debug Mode +---------- + +The :data:`DEBUG` config value is special because it may behave inconsistently if +changed after the app has begun setting up. In order to set debug mode reliably, use the +``--debug`` option on the ``flask`` or ``flask run`` command. ``flask run`` will use the +interactive debugger and reloader by default in debug mode. + +.. code-block:: text + + $ flask --app hello run --debug + +Using the option is recommended. While it is possible to set :data:`DEBUG` in your +config or code, this is strongly discouraged. It can't be read early by the +``flask run`` command, and some systems or extensions may have already configured +themselves based on a previous value. + + +Builtin Configuration Values +---------------------------- + +The following configuration values are used internally by Flask: + +.. py:data:: DEBUG + + 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. The :attr:`~flask.Flask.debug` attribute + maps to this config key. This is set with the ``FLASK_DEBUG`` environment variable. + It may not behave as expected if set in code. + + **Do not enable debug mode when deploying in production.** + + Default: ``False`` + +.. py:data:: TESTING + + Enable testing mode. Exceptions are propagated rather than handled by the + the app's error handlers. Extensions may also change their behavior to + facilitate easier testing. You should enable this in your own tests. + + Default: ``False`` + +.. py:data:: PROPAGATE_EXCEPTIONS + + Exceptions are re-raised rather than being handled by the app's error + handlers. If not set, this is implicitly true if ``TESTING`` or ``DEBUG`` + is enabled. + + Default: ``None`` + +.. py:data:: TRAP_HTTP_EXCEPTIONS + + If there is no handler for an ``HTTPException``-type exception, re-raise it + to be handled by the interactive debugger instead of returning it as a + simple error response. + + Default: ``False`` + +.. py:data:: TRAP_BAD_REQUEST_ERRORS + + Trying to access a key that doesn't exist from request dicts like ``args`` + and ``form`` will return a 400 Bad Request error page. Enable this to treat + the error as an unhandled exception instead so that you get the interactive + debugger. This is a more specific version of ``TRAP_HTTP_EXCEPTIONS``. If + unset, it is enabled in debug mode. + + Default: ``None`` + +.. py:data:: SECRET_KEY + + A secret key that will be used for securely signing the session cookie + and can be used for any other security related needs by extensions or your + application. It should be a long random ``bytes`` or ``str``. For + example, copy the output of this to your config:: + + $ python -c 'import secrets; print(secrets.token_hex())' + '192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf' + + **Do not reveal the secret key when posting questions or committing code.** + + Default: ``None`` + +.. py:data:: SECRET_KEY_FALLBACKS + + A list of old secret keys that can still be used for unsigning. This allows + a project to implement key rotation without invalidating active sessions or + other recently-signed secrets. + + Keys should be removed after an appropriate period of time, as checking each + additional key adds some overhead. + + Order should not matter, but the default implementation will test the last + key in the list first, so it might make sense to order oldest to newest. + + Flask's built-in secure cookie session supports this. Extensions that use + :data:`SECRET_KEY` may not support this yet. + + Default: ``None`` + + .. versionadded:: 3.1 + +.. py:data:: SESSION_COOKIE_NAME + + The name of the session cookie. Can be changed in case you already have a + cookie with the same name. + + Default: ``'session'`` + +.. py:data:: SESSION_COOKIE_DOMAIN + + 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. + + Not setting this value is more restricted and secure than setting it. + + Default: ``None`` + + .. warning:: + If this is changed after the browser created a cookie is created with + one setting, it may result in another being created. Browsers may send + send both in an undefined order. In that case, you may want to change + :data:`SESSION_COOKIE_NAME` as well or otherwise invalidate old sessions. + + .. versionchanged:: 2.3 + Not set by default, does not fall back to ``SERVER_NAME``. + +.. py:data:: SESSION_COOKIE_PATH + + The path that the session cookie will be valid for. If not set, the cookie + will be valid underneath ``APPLICATION_ROOT`` or ``/`` if that is not set. + + Default: ``None`` + +.. py:data:: SESSION_COOKIE_HTTPONLY + + Browsers will not allow JavaScript access to cookies marked as "HTTP only" + for security. + + Default: ``True`` + +.. py:data:: SESSION_COOKIE_SECURE + + Browsers will only send cookies with requests over HTTPS if the cookie is + marked "secure". The application must be served over HTTPS for this to make + sense. + + Default: ``False`` + +.. py:data:: SESSION_COOKIE_PARTITIONED + + Browsers will send cookies based on the top-level document's domain, rather + than only the domain of the document setting the cookie. This prevents third + party cookies set in iframes from "leaking" between separate sites. + + Browsers are beginning to disallow non-partitioned third party cookies, so + you need to mark your cookies partitioned if you expect them to work in such + embedded situations. + + Enabling this implicitly enables :data:`SESSION_COOKIE_SECURE` as well, as + it is only valid when served over HTTPS. + + Default: ``False`` + + .. versionadded:: 3.1 + +.. py:data:: SESSION_COOKIE_SAMESITE + + Restrict how cookies are sent with requests from external sites. Can + be set to ``'Lax'`` (recommended) or ``'Strict'``. + See :ref:`security-cookie`. + + Default: ``None`` + + .. versionadded:: 1.0 + +.. py:data:: PERMANENT_SESSION_LIFETIME + + If ``session.permanent`` is true, the cookie's expiration will be set this + number of seconds in the future. Can either be a + :class:`datetime.timedelta` or an ``int``. + + Flask's default cookie implementation validates that the cryptographic + signature is not older than this value. + + Default: ``timedelta(days=31)`` (``2678400`` seconds) + +.. py:data:: SESSION_REFRESH_EACH_REQUEST + + Control whether the cookie is sent with every response when + ``session.permanent`` is true. Sending the cookie every time (the default) + can more reliably keep the session from expiring, but uses more bandwidth. + Non-permanent sessions are not affected. + + Default: ``True`` + +.. py:data:: USE_X_SENDFILE + + When serving files, set the ``X-Sendfile`` header instead of serving the + data with Flask. Some web servers, such as Apache, recognize this and serve + the data more efficiently. This only makes sense when using such a server. + + Default: ``False`` + +.. py:data:: SEND_FILE_MAX_AGE_DEFAULT + + When serving files, set the cache control max age to this number of + seconds. Can be a :class:`datetime.timedelta` or an ``int``. + Override this value on a per-file basis using + :meth:`~flask.Flask.get_send_file_max_age` on the application or + blueprint. + + If ``None``, ``send_file`` tells the browser to use conditional + requests will be used instead of a timed cache, which is usually + preferable. + + Default: ``None`` + +.. py:data:: TRUSTED_HOSTS + + Validate :attr:`.Request.host` and other attributes that use it against + these trusted values. Raise a :exc:`~werkzeug.exceptions.SecurityError` if + the host is invalid, which results in a 400 error. If it is ``None``, all + hosts are valid. Each value is either an exact match, or can start with + a dot ``.`` to match any subdomain. + + Validation is done during routing against this value. ``before_request`` and + ``after_request`` callbacks will still be called. + + Default: ``None`` + + .. versionadded:: 3.1 + +.. py:data:: SERVER_NAME + + Inform the application what host and port it is bound to. + + Must be set if ``subdomain_matching`` is enabled, to be able to extract the + subdomain from the request. + + Must be set for ``url_for`` to generate external URLs outside of a + request context. + + Default: ``None`` + + .. versionchanged:: 3.1 + Does not restrict requests to only this domain, for both + ``subdomain_matching`` and ``host_matching``. + + .. versionchanged:: 1.0 + Does not implicitly enable ``subdomain_matching``. + + .. versionchanged:: 2.3 + Does not affect ``SESSION_COOKIE_DOMAIN``. + +.. py:data:: APPLICATION_ROOT + + Inform the application what path it is mounted under by the application / + web server. This is used for generating URLs outside the context of a + request (inside a request, the dispatcher is responsible for setting + ``SCRIPT_NAME`` instead; see :doc:`/patterns/appdispatch` + for examples of dispatch configuration). + + Will be used for the session cookie path if ``SESSION_COOKIE_PATH`` is not + set. + + Default: ``'/'`` + +.. py:data:: PREFERRED_URL_SCHEME + + Use this scheme for generating external URLs when not in a request context. + + Default: ``'http'`` + +.. py:data:: MAX_CONTENT_LENGTH + + The maximum number of bytes that will be read during this request. If + this limit is exceeded, a 413 :exc:`~werkzeug.exceptions.RequestEntityTooLarge` + error is raised. If it is set to ``None``, no limit is enforced at the + Flask application level. However, if it is ``None`` and the request has no + ``Content-Length`` header and the WSGI server does not indicate that it + terminates the stream, then no data is read to avoid an infinite stream. + + Each request defaults to this config. It can be set on a specific + :attr:`.Request.max_content_length` to apply the limit to that specific + view. This should be set appropriately based on an application's or view's + specific needs. + + Default: ``None`` + + .. versionadded:: 0.6 + +.. py:data:: MAX_FORM_MEMORY_SIZE + + The maximum size in bytes any non-file form field may be in a + ``multipart/form-data`` body. If this limit is exceeded, a 413 + :exc:`~werkzeug.exceptions.RequestEntityTooLarge` error is raised. If it is + set to ``None``, no limit is enforced at the Flask application level. + + Each request defaults to this config. It can be set on a specific + :attr:`.Request.max_form_memory_parts` to apply the limit to that specific + view. This should be set appropriately based on an application's or view's + specific needs. + + Default: ``500_000`` + + .. versionadded:: 3.1 + +.. py:data:: MAX_FORM_PARTS + + The maximum number of fields that may be present in a + ``multipart/form-data`` body. If this limit is exceeded, a 413 + :exc:`~werkzeug.exceptions.RequestEntityTooLarge` error is raised. If it + is set to ``None``, no limit is enforced at the Flask application level. + + Each request defaults to this config. It can be set on a specific + :attr:`.Request.max_form_parts` to apply the limit to that specific view. + This should be set appropriately based on an application's or view's + specific needs. + + Default: ``1_000`` + + .. versionadded:: 3.1 + +.. py:data:: TEMPLATES_AUTO_RELOAD + + Reload templates when they are changed. If not set, it will be enabled in + debug mode. + + Default: ``None`` + +.. py:data:: EXPLAIN_TEMPLATE_LOADING + + Log debugging information tracing how a template file was loaded. This can + be useful to figure out why a template was not loaded or the wrong file + appears to be loaded. + + Default: ``False`` + +.. py:data:: MAX_COOKIE_SIZE + + Warn if cookie headers are larger than this many bytes. Defaults to + ``4093``. Larger cookies may be silently ignored by browsers. Set to + ``0`` to disable the warning. + +.. py:data:: PROVIDE_AUTOMATIC_OPTIONS + + Set to ``False`` to disable the automatic addition of OPTIONS + responses. This can be overridden per route by altering the + ``provide_automatic_options`` attribute. + +.. versionadded:: 0.4 + ``LOGGER_NAME`` + +.. versionadded:: 0.5 + ``SERVER_NAME`` + +.. versionadded:: 0.6 + ``MAX_CONTENT_LENGTH`` + +.. versionadded:: 0.7 + ``PROPAGATE_EXCEPTIONS``, ``PRESERVE_CONTEXT_ON_EXCEPTION`` + +.. versionadded:: 0.8 + ``TRAP_BAD_REQUEST_ERRORS``, ``TRAP_HTTP_EXCEPTIONS``, + ``APPLICATION_ROOT``, ``SESSION_COOKIE_DOMAIN``, + ``SESSION_COOKIE_PATH``, ``SESSION_COOKIE_HTTPONLY``, + ``SESSION_COOKIE_SECURE`` + +.. versionadded:: 0.9 + ``PREFERRED_URL_SCHEME`` + +.. versionadded:: 0.10 + ``JSON_AS_ASCII``, ``JSON_SORT_KEYS``, ``JSONIFY_PRETTYPRINT_REGULAR`` + +.. versionadded:: 0.11 + ``SESSION_REFRESH_EACH_REQUEST``, ``TEMPLATES_AUTO_RELOAD``, + ``LOGGER_HANDLER_POLICY``, ``EXPLAIN_TEMPLATE_LOADING`` + +.. versionchanged:: 1.0 + ``LOGGER_NAME`` and ``LOGGER_HANDLER_POLICY`` were removed. See + :doc:`/logging` for information about configuration. + + Added :data:`ENV` to reflect the :envvar:`FLASK_ENV` environment + variable. + + Added :data:`SESSION_COOKIE_SAMESITE` to control the session + cookie's ``SameSite`` option. + + Added :data:`MAX_COOKIE_SIZE` to control a warning from Werkzeug. + +.. versionchanged:: 2.2 + Removed ``PRESERVE_CONTEXT_ON_EXCEPTION``. + +.. versionchanged:: 2.3 + ``JSON_AS_ASCII``, ``JSON_SORT_KEYS``, ``JSONIFY_MIMETYPE``, and + ``JSONIFY_PRETTYPRINT_REGULAR`` were removed. The default ``app.json`` provider has + equivalent attributes instead. + +.. versionchanged:: 2.3 + ``ENV`` was removed. + +.. versionadded:: 3.10 + Added :data:`PROVIDE_AUTOMATIC_OPTIONS` to control the default + addition of autogenerated OPTIONS responses. + + +Configuring from Python Files +----------------------------- + +Configuration becomes more useful if you can store it in a separate file, ideally +located outside the actual application package. You can deploy your application, then +separately configure it for the specific deployment. + +A common pattern is this:: + + app = Flask(__name__) + app.config.from_object('yourapplication.default_settings') + app.config.from_envvar('YOURAPPLICATION_SETTINGS') + +This first loads the configuration from the +`yourapplication.default_settings` module and then overrides the values +with the contents of the file the :envvar:`YOURAPPLICATION_SETTINGS` +environment variable points to. This environment variable can be set +in the shell before starting the server: + +.. tabs:: + + .. group-tab:: Bash + + .. code-block:: text + + $ export YOURAPPLICATION_SETTINGS=/path/to/settings.cfg + $ flask run + * Running on http://127.0.0.1:5000/ + + .. group-tab:: Fish + + .. code-block:: text + + $ set -x YOURAPPLICATION_SETTINGS /path/to/settings.cfg + $ flask run + * Running on http://127.0.0.1:5000/ + + .. group-tab:: CMD + + .. code-block:: text + + > set YOURAPPLICATION_SETTINGS=\path\to\settings.cfg + > flask run + * Running on http://127.0.0.1:5000/ + + .. group-tab:: Powershell + + .. code-block:: text + + > $env:YOURAPPLICATION_SETTINGS = "\path\to\settings.cfg" + > flask run + * Running on http://127.0.0.1:5000/ + +The configuration files themselves are actual Python files. Only values +in uppercase are actually stored in the config object later on. So make +sure to use uppercase letters for your config keys. + +Here is an example of a configuration file:: + + # Example configuration + SECRET_KEY = '192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf' + +Make sure to load the configuration very early on, so that extensions have +the ability to access the configuration when starting up. There are other +methods on the config object as well to load from individual files. For a +complete reference, read the :class:`~flask.Config` object's +documentation. + + +Configuring from Data Files +--------------------------- + +It is also possible to load configuration from a file in a format of +your choice using :meth:`~flask.Config.from_file`. For example to load +from a TOML file: + +.. code-block:: python + + import tomllib + app.config.from_file("config.toml", load=tomllib.load, text=False) + +Or from a JSON file: + +.. code-block:: python + + import json + app.config.from_file("config.json", load=json.load) + + +Configuring from Environment Variables +-------------------------------------- + +In addition to pointing to configuration files using environment +variables, you may find it useful (or necessary) to control your +configuration values directly from the environment. Flask can be +instructed to load all environment variables starting with a specific +prefix into the config using :meth:`~flask.Config.from_prefixed_env`. + +Environment variables can be set in the shell before starting the +server: + +.. tabs:: + + .. group-tab:: Bash + + .. code-block:: text + + $ export FLASK_SECRET_KEY="5f352379324c22463451387a0aec5d2f" + $ export FLASK_MAIL_ENABLED=false + $ flask run + * Running on http://127.0.0.1:5000/ + + .. group-tab:: Fish + + .. code-block:: text + + $ set -x FLASK_SECRET_KEY "5f352379324c22463451387a0aec5d2f" + $ set -x FLASK_MAIL_ENABLED false + $ flask run + * Running on http://127.0.0.1:5000/ + + .. group-tab:: CMD + + .. code-block:: text + + > set FLASK_SECRET_KEY="5f352379324c22463451387a0aec5d2f" + > set FLASK_MAIL_ENABLED=false + > flask run + * Running on http://127.0.0.1:5000/ + + .. group-tab:: Powershell + + .. code-block:: text + + > $env:FLASK_SECRET_KEY = "5f352379324c22463451387a0aec5d2f" + > $env:FLASK_MAIL_ENABLED = "false" + > flask run + * Running on http://127.0.0.1:5000/ + +The variables can then be loaded and accessed via the config with a key +equal to the environment variable name without the prefix i.e. + +.. code-block:: python + + app.config.from_prefixed_env() + app.config["SECRET_KEY"] # Is "5f352379324c22463451387a0aec5d2f" + +The prefix is ``FLASK_`` by default. This is configurable via the +``prefix`` argument of :meth:`~flask.Config.from_prefixed_env`. + +Values will be parsed to attempt to convert them to a more specific type +than strings. By default :func:`json.loads` is used, so any valid JSON +value is possible, including lists and dicts. This is configurable via +the ``loads`` argument of :meth:`~flask.Config.from_prefixed_env`. + +When adding a boolean value with the default JSON parsing, only "true" +and "false", lowercase, are valid values. Keep in mind that any +non-empty string is considered ``True`` by Python. + +It is possible to set keys in nested dictionaries by separating the +keys with double underscore (``__``). Any intermediate keys that don't +exist on the parent dict will be initialized to an empty dict. + +.. code-block:: text + + $ export FLASK_MYAPI__credentials__username=user123 + +.. code-block:: python + + app.config["MYAPI"]["credentials"]["username"] # Is "user123" + +On Windows, environment variable keys are always uppercase, therefore +the above example would end up as ``MYAPI__CREDENTIALS__USERNAME``. + +For even more config loading features, including merging and +case-insensitive Windows support, try a dedicated library such as +Dynaconf_, which includes integration with Flask. + +.. _Dynaconf: https://www.dynaconf.com/ + + +Configuration Best Practices +---------------------------- + +The downside with the approach mentioned earlier is that it makes testing +a little harder. There is no single 100% solution for this problem in +general, but there are a couple of things you can keep in mind to improve +that experience: + +1. Create your application in a function and register blueprints on it. + That way you can create multiple instances of your application with + different configurations attached which makes unit testing a lot + easier. You can use this to pass in configuration as needed. + +2. Do not write code that needs the configuration at import time. If you + limit yourself to request-only accesses to the configuration you can + reconfigure the object later on as needed. + +3. Make sure to load the configuration very early on, so that + extensions can access the configuration when calling ``init_app``. + + +.. _config-dev-prod: + +Development / Production +------------------------ + +Most applications need more than one configuration. There should be at +least separate configurations for the production server and the one used +during development. The easiest way to handle this is to use a default +configuration that is always loaded and part of the version control, and a +separate configuration that overrides the values as necessary as mentioned +in the example above:: + + app = Flask(__name__) + app.config.from_object('yourapplication.default_settings') + app.config.from_envvar('YOURAPPLICATION_SETTINGS') + +Then you just have to add a separate :file:`config.py` file and export +``YOURAPPLICATION_SETTINGS=/path/to/config.py`` and you are done. However +there are alternative ways as well. For example you could use imports or +subclassing. + +What is very popular in the Django world is to make the import explicit in +the config file by adding ``from yourapplication.default_settings +import *`` to the top of the file and then overriding the changes by hand. +You could also inspect an environment variable like +``YOURAPPLICATION_MODE`` and set that to `production`, `development` etc +and import different hard-coded files based on that. + +An interesting pattern is also to use classes and inheritance for +configuration:: + + class Config(object): + TESTING = False + + class ProductionConfig(Config): + DATABASE_URI = 'mysql://user@localhost/foo' + + class DevelopmentConfig(Config): + DATABASE_URI = "sqlite:////tmp/foo.db" + + class TestingConfig(Config): + DATABASE_URI = 'sqlite:///:memory:' + TESTING = True + +To enable such a config you just have to call into +:meth:`~flask.Config.from_object`:: + + app.config.from_object('configmodule.ProductionConfig') + +Note that :meth:`~flask.Config.from_object` does not instantiate the class +object. If you need to instantiate the class, such as to access a property, +then you must do so before calling :meth:`~flask.Config.from_object`:: + + from configmodule import ProductionConfig + app.config.from_object(ProductionConfig()) + + # Alternatively, import via string: + from werkzeug.utils import import_string + cfg = import_string('configmodule.ProductionConfig')() + app.config.from_object(cfg) + +Instantiating the configuration object allows you to use ``@property`` in +your configuration classes:: + + class Config(object): + """Base config, uses staging database server.""" + TESTING = False + DB_SERVER = '192.168.1.56' + + @property + def DATABASE_URI(self): # Note: all caps + return f"mysql://user@{self.DB_SERVER}/foo" + + class ProductionConfig(Config): + """Uses production database server.""" + DB_SERVER = '192.168.19.32' + + class DevelopmentConfig(Config): + DB_SERVER = 'localhost' + + class TestingConfig(Config): + DB_SERVER = 'localhost' + DATABASE_URI = 'sqlite:///:memory:' + +There are many different ways and it's up to you how you want to manage +your configuration files. However here a list of good recommendations: + +- Keep a default configuration in version control. Either populate the + config with this default configuration or import it in your own + configuration files before overriding values. +- Use an environment variable to switch between the configurations. + This can be done from outside the Python interpreter and makes + development and deployment much easier because you can quickly and + easily switch between different configs without having to touch the + code at all. If you are working often on different projects you can + even create your own script for sourcing that activates a virtualenv + and exports the development configuration for you. +- Use a tool like `fabric`_ to push code and configuration separately + to the production server(s). + +.. _fabric: https://www.fabfile.org/ + + +.. _instance-folders: + +Instance Folders +---------------- + +.. versionadded:: 0.8 + +Flask 0.8 introduces instance folders. Flask for a long time made it +possible to refer to paths relative to the application's folder directly +(via :attr:`Flask.root_path`). This was also how many developers loaded +configurations stored next to the application. Unfortunately however this +only works well if applications are not packages in which case the root +path refers to the contents of the package. + +With Flask 0.8 a new attribute was introduced: +:attr:`Flask.instance_path`. It refers to a new concept called the +“instance folder”. The instance folder is designed to not be under +version control and be deployment specific. It's the perfect place to +drop things that either change at runtime or configuration files. + +You can either explicitly provide the path of the instance folder when +creating the Flask application or you can let Flask autodetect the +instance folder. For explicit configuration use the `instance_path` +parameter:: + + app = Flask(__name__, instance_path='/path/to/instance/folder') + +Please keep in mind that this path *must* be absolute when provided. + +If the `instance_path` parameter is not provided the following default +locations are used: + +- Uninstalled module:: + + /myapp.py + /instance + +- Uninstalled package:: + + /myapp + /__init__.py + /instance + +- Installed module or package:: + + $PREFIX/lib/pythonX.Y/site-packages/myapp + $PREFIX/var/myapp-instance + + ``$PREFIX`` is the prefix of your Python installation. This can be + ``/usr`` or the path to your virtualenv. You can print the value of + ``sys.prefix`` to see what the prefix is set to. + +Since the config object provided loading of configuration files from +relative filenames we made it possible to change the loading via filenames +to be relative to the instance path if wanted. The behavior of relative +paths in config files can be flipped between “relative to the application +root” (the default) to “relative to instance folder” via the +`instance_relative_config` switch to the application constructor:: + + app = Flask(__name__, instance_relative_config=True) + +Here is a full example of how to configure Flask to preload the config +from a module and then override the config from a file in the instance +folder if it exists:: + + app = Flask(__name__, instance_relative_config=True) + app.config.from_object('yourapplication.default_settings') + app.config.from_pyfile('application.cfg', silent=True) + +The path to the instance folder can be found via the +:attr:`Flask.instance_path`. Flask also provides a shortcut to open a +file from the instance folder with :meth:`Flask.open_instance_resource`. + +Example usage for both:: + + filename = os.path.join(app.instance_path, 'application.cfg') + with open(filename) as f: + config = f.read() + + # or via open_instance_resource: + with app.open_instance_resource('application.cfg') as f: + config = f.read() diff --git a/src/flask-main/docs/contributing.rst b/src/flask-main/docs/contributing.rst new file mode 100644 index 0000000..ca4b3ae --- /dev/null +++ b/src/flask-main/docs/contributing.rst @@ -0,0 +1,8 @@ +Contributing +============ + +See the Pallets `detailed contributing documentation `_ for many ways +to contribute, including reporting issues, requesting features, asking or +answering questions, and making PRs. + +.. _contrib: https://palletsprojects.com/contributing/ diff --git a/src/flask-main/docs/debugging.rst b/src/flask-main/docs/debugging.rst new file mode 100644 index 0000000..f6b56ca --- /dev/null +++ b/src/flask-main/docs/debugging.rst @@ -0,0 +1,99 @@ +Debugging Application Errors +============================ + + +In Production +------------- + +**Do not run the development server, or enable the built-in debugger, in +a production environment.** The debugger allows executing arbitrary +Python code from the browser. It's protected by a pin, but that should +not be relied on for security. + +Use an error logging tool, such as Sentry, as described in +:ref:`error-logging-tools`, or enable logging and notifications as +described in :doc:`/logging`. + +If you have access to the server, you could add some code to start an +external debugger if ``request.remote_addr`` matches your IP. Some IDE +debuggers also have a remote mode so breakpoints on the server can be +interacted with locally. Only enable a debugger temporarily. + + +The Built-In Debugger +--------------------- + +The built-in Werkzeug development server provides a debugger which shows +an interactive traceback in the browser when an unhandled error occurs +during a request. This debugger should only be used during development. + +.. image:: _static/debugger.png + :align: center + :class: screenshot + :alt: screenshot of debugger in action + +.. warning:: + + The debugger allows executing arbitrary Python code from the + browser. It is protected by a pin, but still represents a major + security risk. Do not run the development server or debugger in a + production environment. + +The debugger is enabled by default when the development server is run in debug mode. + +.. code-block:: text + + $ flask --app hello run --debug + +When running from Python code, passing ``debug=True`` enables debug mode, which is +mostly equivalent. + +.. code-block:: python + + app.run(debug=True) + +:doc:`/server` and :doc:`/cli` have more information about running the debugger and +debug mode. More information about the debugger can be found in the `Werkzeug +documentation `__. + + +External Debuggers +------------------ + +External debuggers, such as those provided by IDEs, can offer a more +powerful debugging experience than the built-in debugger. They can also +be used to step through code during a request before an error is raised, +or if no error is raised. Some even have a remote mode so you can debug +code running on another machine. + +When using an external debugger, the app should still be in debug mode, otherwise Flask +turns unhandled errors into generic 500 error pages. However, the built-in debugger and +reloader should be disabled so they don't interfere with the external debugger. + +.. code-block:: text + + $ flask --app hello run --debug --no-debugger --no-reload + +When running from Python: + +.. code-block:: python + + app.run(debug=True, use_debugger=False, use_reloader=False) + +Disabling these isn't required, an external debugger will continue to work with the +following caveats. + +- If the built-in debugger is not disabled, it will catch unhandled exceptions before + the external debugger can. +- If the reloader is not disabled, it could cause an unexpected reload if code changes + during a breakpoint. +- The development server will still catch unhandled exceptions if the built-in + debugger is disabled, otherwise it would crash on any error. If you want that (and + usually you don't) pass ``passthrough_errors=True`` to ``app.run``. + + .. code-block:: python + + app.run( + debug=True, passthrough_errors=True, + use_debugger=False, use_reloader=False + ) diff --git a/src/flask-main/docs/deploying/apache-httpd.rst b/src/flask-main/docs/deploying/apache-httpd.rst new file mode 100644 index 0000000..bdeaf62 --- /dev/null +++ b/src/flask-main/docs/deploying/apache-httpd.rst @@ -0,0 +1,66 @@ +Apache httpd +============ + +`Apache httpd`_ is a fast, production level HTTP server. When serving +your application with one of the WSGI servers listed in :doc:`index`, it +is often good or necessary to put a dedicated HTTP server in front of +it. This "reverse proxy" can handle incoming requests, TLS, and other +security and performance concerns better than the WSGI server. + +httpd can be installed using your system package manager, or a pre-built +executable for Windows. Installing and running httpd itself is outside +the scope of this doc. This page outlines the basics of configuring +httpd to proxy your application. Be sure to read its documentation to +understand what features are available. + +.. _Apache httpd: https://httpd.apache.org/ + + +Domain Name +----------- + +Acquiring and configuring a domain name is outside the scope of this +doc. In general, you will buy a domain name from a registrar, pay for +server space with a hosting provider, and then point your registrar +at the hosting provider's name servers. + +To simulate this, you can also edit your ``hosts`` file, located at +``/etc/hosts`` on Linux. Add a line that associates a name with the +local IP. + +Modern Linux systems may be configured to treat any domain name that +ends with ``.localhost`` like this without adding it to the ``hosts`` +file. + +.. code-block:: python + :caption: ``/etc/hosts`` + + 127.0.0.1 hello.localhost + + +Configuration +------------- + +The httpd configuration is located at ``/etc/httpd/conf/httpd.conf`` on +Linux. It may be different depending on your operating system. Check the +docs and look for ``httpd.conf``. + +Remove or comment out any existing ``DocumentRoot`` directive. Add the +config lines below. We'll assume the WSGI server is listening locally at +``http://127.0.0.1:8000``. + +.. code-block:: apache + :caption: ``/etc/httpd/conf/httpd.conf`` + + LoadModule proxy_module modules/mod_proxy.so + LoadModule proxy_http_module modules/mod_proxy_http.so + ProxyPass / http://127.0.0.1:8000/ + RequestHeader set X-Forwarded-Proto http + RequestHeader set X-Forwarded-Prefix / + +The ``LoadModule`` lines might already exist. If so, make sure they are +uncommented instead of adding them manually. + +Then :doc:`proxy_fix` so that your application uses the ``X-Forwarded`` +headers. ``X-Forwarded-For`` and ``X-Forwarded-Host`` are automatically +set by ``ProxyPass``. diff --git a/src/flask-main/docs/deploying/asgi.rst b/src/flask-main/docs/deploying/asgi.rst new file mode 100644 index 0000000..1dc0aa2 --- /dev/null +++ b/src/flask-main/docs/deploying/asgi.rst @@ -0,0 +1,27 @@ +ASGI +==== + +If you'd like to use an ASGI server you will need to utilise WSGI to +ASGI middleware. The asgiref +`WsgiToAsgi `_ +adapter is recommended as it integrates with the event loop used for +Flask's :ref:`async_await` support. You can use the adapter by +wrapping the Flask app, + +.. code-block:: python + + from asgiref.wsgi import WsgiToAsgi + from flask import Flask + + app = Flask(__name__) + + ... + + asgi_app = WsgiToAsgi(app) + +and then serving the ``asgi_app`` with the ASGI server, e.g. using +`Hypercorn `_, + +.. sourcecode:: text + + $ hypercorn module:asgi_app diff --git a/src/flask-main/docs/deploying/eventlet.rst b/src/flask-main/docs/deploying/eventlet.rst new file mode 100644 index 0000000..8a718b2 --- /dev/null +++ b/src/flask-main/docs/deploying/eventlet.rst @@ -0,0 +1,80 @@ +eventlet +======== + +Prefer using :doc:`gunicorn` with eventlet workers rather than using +`eventlet`_ directly. Gunicorn provides a much more configurable and +production-tested server. + +`eventlet`_ allows writing asynchronous, coroutine-based code that looks +like standard synchronous Python. It uses `greenlet`_ to enable task +switching without writing ``async/await`` or using ``asyncio``. + +:doc:`gevent` is another library that does the same thing. Certain +dependencies you have, or other considerations, may affect which of the +two you choose to use. + +eventlet provides a WSGI server that can handle many connections at once +instead of one per worker process. You must actually use eventlet in +your own code to see any benefit to using the server. + +.. _eventlet: https://eventlet.net/ +.. _greenlet: https://greenlet.readthedocs.io/en/latest/ + + +Installing +---------- + +When using eventlet, greenlet>=1.0 is required, otherwise context locals +such as ``request`` will not work as expected. When using PyPy, +PyPy>=7.3.7 is required. + +Create a virtualenv, install your application, then install +``eventlet``. + +.. code-block:: text + + $ cd hello-app + $ python -m venv .venv + $ . .venv/bin/activate + $ pip install . # install your application + $ pip install eventlet + + +Running +------- + +To use eventlet to serve your application, write a script that imports +its ``wsgi.server``, as well as your app or app factory. + +.. code-block:: python + :caption: ``wsgi.py`` + + import eventlet + from eventlet import wsgi + from hello import create_app + + app = create_app() + wsgi.server(eventlet.listen(("127.0.0.1", 8000)), app) + +.. code-block:: text + + $ python wsgi.py + (x) wsgi starting up on http://127.0.0.1:8000 + + +Binding Externally +------------------ + +eventlet should not be run as root because it would cause your +application code to run as root, which is not secure. However, this +means it will not be possible to bind to port 80 or 443. Instead, a +reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used +in front of eventlet. + +You can bind to all external IPs on a non-privileged port by using +``0.0.0.0`` in the server arguments shown in the previous section. +Don't do this when using a reverse proxy setup, otherwise it will be +possible to bypass the proxy. + +``0.0.0.0`` is not a valid address to navigate to, you'd use a specific +IP address in your browser. diff --git a/src/flask-main/docs/deploying/gevent.rst b/src/flask-main/docs/deploying/gevent.rst new file mode 100644 index 0000000..448b93e --- /dev/null +++ b/src/flask-main/docs/deploying/gevent.rst @@ -0,0 +1,80 @@ +gevent +====== + +Prefer using :doc:`gunicorn` or :doc:`uwsgi` with gevent workers rather +than using `gevent`_ directly. Gunicorn and uWSGI provide much more +configurable and production-tested servers. + +`gevent`_ allows writing asynchronous, coroutine-based code that looks +like standard synchronous Python. It uses `greenlet`_ to enable task +switching without writing ``async/await`` or using ``asyncio``. + +:doc:`eventlet` is another library that does the same thing. Certain +dependencies you have, or other considerations, may affect which of the +two you choose to use. + +gevent provides a WSGI server that can handle many connections at once +instead of one per worker process. You must actually use gevent in your +own code to see any benefit to using the server. + +.. _gevent: https://www.gevent.org/ +.. _greenlet: https://greenlet.readthedocs.io/en/latest/ + + +Installing +---------- + +When using gevent, greenlet>=1.0 is required, otherwise context locals +such as ``request`` will not work as expected. When using PyPy, +PyPy>=7.3.7 is required. + +Create a virtualenv, install your application, then install ``gevent``. + +.. code-block:: text + + $ cd hello-app + $ python -m venv .venv + $ . .venv/bin/activate + $ pip install . # install your application + $ pip install gevent + + +Running +------- + +To use gevent to serve your application, write a script that imports its +``WSGIServer``, as well as your app or app factory. + +.. code-block:: python + :caption: ``wsgi.py`` + + from gevent.pywsgi import WSGIServer + from hello import create_app + + app = create_app() + http_server = WSGIServer(("127.0.0.1", 8000), app) + http_server.serve_forever() + +.. code-block:: text + + $ python wsgi.py + +No output is shown when the server starts. + + +Binding Externally +------------------ + +gevent should not be run as root because it would cause your +application code to run as root, which is not secure. However, this +means it will not be possible to bind to port 80 or 443. Instead, a +reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used +in front of gevent. + +You can bind to all external IPs on a non-privileged port by using +``0.0.0.0`` in the server arguments shown in the previous section. Don't +do this when using a reverse proxy setup, otherwise it will be possible +to bypass the proxy. + +``0.0.0.0`` is not a valid address to navigate to, you'd use a specific +IP address in your browser. diff --git a/src/flask-main/docs/deploying/gunicorn.rst b/src/flask-main/docs/deploying/gunicorn.rst new file mode 100644 index 0000000..c50edc2 --- /dev/null +++ b/src/flask-main/docs/deploying/gunicorn.rst @@ -0,0 +1,130 @@ +Gunicorn +======== + +`Gunicorn`_ is a pure Python WSGI server with simple configuration and +multiple worker implementations for performance tuning. + +* It tends to integrate easily with hosting platforms. +* It does not support Windows (but does run on WSL). +* It is easy to install as it does not require additional dependencies + or compilation. +* It has built-in async worker support using gevent or eventlet. + +This page outlines the basics of running Gunicorn. Be sure to read its +`documentation`_ and use ``gunicorn --help`` to understand what features +are available. + +.. _Gunicorn: https://gunicorn.org/ +.. _documentation: https://docs.gunicorn.org/ + + +Installing +---------- + +Gunicorn is easy to install, as it does not require external +dependencies or compilation. It runs on Windows only under WSL. + +Create a virtualenv, install your application, then install +``gunicorn``. + +.. code-block:: text + + $ cd hello-app + $ python -m venv .venv + $ . .venv/bin/activate + $ pip install . # install your application + $ pip install gunicorn + + +Running +------- + +The only required argument to Gunicorn tells it how to load your Flask +application. The syntax is ``{module_import}:{app_variable}``. +``module_import`` is the dotted import name to the module with your +application. ``app_variable`` is the variable with the application. It +can also be a function call (with any arguments) if you're using the +app factory pattern. + +.. code-block:: text + + # equivalent to 'from hello import app' + $ gunicorn -w 4 'hello:app' + + # equivalent to 'from hello import create_app; create_app()' + $ gunicorn -w 4 'hello:create_app()' + + Starting gunicorn 20.1.0 + Listening at: http://127.0.0.1:8000 (x) + Using worker: sync + Booting worker with pid: x + Booting worker with pid: x + Booting worker with pid: x + Booting worker with pid: x + +The ``-w`` option specifies the number of processes to run; a starting +value could be ``CPU * 2``. The default is only 1 worker, which is +probably not what you want for the default worker type. + +Logs for each request aren't shown by default, only worker info and +errors are shown. To show access logs on stdout, use the +``--access-logfile=-`` option. + + +Binding Externally +------------------ + +Gunicorn should not be run as root because it would cause your +application code to run as root, which is not secure. However, this +means it will not be possible to bind to port 80 or 443. Instead, a +reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used +in front of Gunicorn. + +You can bind to all external IPs on a non-privileged port using the +``-b 0.0.0.0`` option. Don't do this when using a reverse proxy setup, +otherwise it will be possible to bypass the proxy. + +.. code-block:: text + + $ gunicorn -w 4 -b 0.0.0.0 'hello:create_app()' + Listening at: http://0.0.0.0:8000 (x) + +``0.0.0.0`` is not a valid address to navigate to, you'd use a specific +IP address in your browser. + + +Async with gevent or eventlet +----------------------------- + +The default sync worker is appropriate for many use cases. If you need +asynchronous support, Gunicorn provides workers using either `gevent`_ +or `eventlet`_. This is not the same as Python's ``async/await``, or the +ASGI server spec. You must actually use gevent/eventlet in your own code +to see any benefit to using the workers. + +When using either gevent or eventlet, greenlet>=1.0 is required, +otherwise context locals such as ``request`` will not work as expected. +When using PyPy, PyPy>=7.3.7 is required. + +To use gevent: + +.. code-block:: text + + $ gunicorn -k gevent 'hello:create_app()' + Starting gunicorn 20.1.0 + Listening at: http://127.0.0.1:8000 (x) + Using worker: gevent + Booting worker with pid: x + +To use eventlet: + +.. code-block:: text + + $ gunicorn -k eventlet 'hello:create_app()' + Starting gunicorn 20.1.0 + Listening at: http://127.0.0.1:8000 (x) + Using worker: eventlet + Booting worker with pid: x + +.. _gevent: https://www.gevent.org/ +.. _eventlet: https://eventlet.net/ diff --git a/src/flask-main/docs/deploying/index.rst b/src/flask-main/docs/deploying/index.rst new file mode 100644 index 0000000..4135596 --- /dev/null +++ b/src/flask-main/docs/deploying/index.rst @@ -0,0 +1,79 @@ +Deploying to Production +======================= + +After developing your application, you'll want to make it available +publicly to other users. When you're developing locally, you're probably +using the built-in development server, debugger, and reloader. These +should not be used in production. Instead, you should use a dedicated +WSGI server or hosting platform, some of which will be described here. + +"Production" means "not development", which applies whether you're +serving your application publicly to millions of users or privately / +locally to a single user. **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 secure, stable, or +efficient.** + +Self-Hosted Options +------------------- + +Flask is a WSGI *application*. A WSGI *server* is used to run the +application, converting incoming HTTP requests to the standard WSGI +environ, and converting outgoing WSGI responses to HTTP responses. + +The primary goal of these docs is to familiarize you with the concepts +involved in running a WSGI application using a production WSGI server +and HTTP server. There are many WSGI servers and HTTP servers, with many +configuration possibilities. The pages below discuss the most common +servers, and show the basics of running each one. The next section +discusses platforms that can manage this for you. + +.. toctree:: + :maxdepth: 1 + + gunicorn + waitress + mod_wsgi + uwsgi + gevent + eventlet + asgi + +WSGI servers have HTTP servers built-in. However, a dedicated HTTP +server may be safer, more efficient, or more capable. Putting an HTTP +server in front of the WSGI server is called a "reverse proxy." + +.. toctree:: + :maxdepth: 1 + + proxy_fix + nginx + apache-httpd + +This list is not exhaustive, and you should evaluate these and other +servers based on your application's needs. Different servers will have +different capabilities, configuration, and support. + + +Hosting Platforms +----------------- + +There are many services available for hosting web applications without +needing to maintain your own server, networking, domain, etc. Some +services may have a free tier up to a certain time or bandwidth. Many of +these services use one of the WSGI servers described above, or a similar +interface. The links below are for some of the most common platforms, +which have instructions for Flask, WSGI, or Python. + +- `PythonAnywhere `_ +- `Google App Engine `_ +- `Google Cloud Run `_ +- `AWS Elastic Beanstalk `_ +- `Microsoft Azure `_ + +This list is not exhaustive, and you should evaluate these and other +services based on your application's needs. Different services will have +different capabilities, configuration, pricing, and support. + +You'll probably need to :doc:`proxy_fix` when using most hosting +platforms. diff --git a/src/flask-main/docs/deploying/mod_wsgi.rst b/src/flask-main/docs/deploying/mod_wsgi.rst new file mode 100644 index 0000000..23e8227 --- /dev/null +++ b/src/flask-main/docs/deploying/mod_wsgi.rst @@ -0,0 +1,94 @@ +mod_wsgi +======== + +`mod_wsgi`_ is a WSGI server integrated with the `Apache httpd`_ server. +The modern `mod_wsgi-express`_ command makes it easy to configure and +start the server without needing to write Apache httpd configuration. + +* Tightly integrated with Apache httpd. +* Supports Windows directly. +* Requires a compiler and the Apache development headers to install. +* Does not require a reverse proxy setup. + +This page outlines the basics of running mod_wsgi-express, not the more +complex installation and configuration with httpd. Be sure to read the +`mod_wsgi-express`_, `mod_wsgi`_, and `Apache httpd`_ documentation to +understand what features are available. + +.. _mod_wsgi-express: https://pypi.org/project/mod-wsgi/ +.. _mod_wsgi: https://modwsgi.readthedocs.io/ +.. _Apache httpd: https://httpd.apache.org/ + + +Installing +---------- + +Installing mod_wsgi requires a compiler and the Apache server and +development headers installed. You will get an error if they are not. +How to install them depends on the OS and package manager that you use. + +Create a virtualenv, install your application, then install +``mod_wsgi``. + +.. code-block:: text + + $ cd hello-app + $ python -m venv .venv + $ . .venv/bin/activate + $ pip install . # install your application + $ pip install mod_wsgi + + +Running +------- + +The only argument to ``mod_wsgi-express`` specifies a script containing +your Flask application, which must be called ``application``. You can +write a small script to import your app with this name, or to create it +if using the app factory pattern. + +.. code-block:: python + :caption: ``wsgi.py`` + + from hello import app + + application = app + +.. code-block:: python + :caption: ``wsgi.py`` + + from hello import create_app + + application = create_app() + +Now run the ``mod_wsgi-express start-server`` command. + +.. code-block:: text + + $ mod_wsgi-express start-server wsgi.py --processes 4 + +The ``--processes`` option specifies the number of worker processes to +run; a starting value could be ``CPU * 2``. + +Logs for each request aren't show in the terminal. If an error occurs, +its information is written to the error log file shown when starting the +server. + + +Binding Externally +------------------ + +Unlike the other WSGI servers in these docs, mod_wsgi can be run as +root to bind to privileged ports like 80 and 443. However, it must be +configured to drop permissions to a different user and group for the +worker processes. + +For example, if you created a ``hello`` user and group, you should +install your virtualenv and application as that user, then tell +mod_wsgi to drop to that user after starting. + +.. code-block:: text + + $ sudo /home/hello/.venv/bin/mod_wsgi-express start-server \ + /home/hello/wsgi.py \ + --user hello --group hello --port 80 --processes 4 diff --git a/src/flask-main/docs/deploying/nginx.rst b/src/flask-main/docs/deploying/nginx.rst new file mode 100644 index 0000000..6b25c07 --- /dev/null +++ b/src/flask-main/docs/deploying/nginx.rst @@ -0,0 +1,69 @@ +nginx +===== + +`nginx`_ is a fast, production level HTTP server. When serving your +application with one of the WSGI servers listed in :doc:`index`, it is +often good or necessary to put a dedicated HTTP server in front of it. +This "reverse proxy" can handle incoming requests, TLS, and other +security and performance concerns better than the WSGI server. + +Nginx can be installed using your system package manager, or a pre-built +executable for Windows. Installing and running Nginx itself is outside +the scope of this doc. This page outlines the basics of configuring +Nginx to proxy your application. Be sure to read its documentation to +understand what features are available. + +.. _nginx: https://nginx.org/ + + +Domain Name +----------- + +Acquiring and configuring a domain name is outside the scope of this +doc. In general, you will buy a domain name from a registrar, pay for +server space with a hosting provider, and then point your registrar +at the hosting provider's name servers. + +To simulate this, you can also edit your ``hosts`` file, located at +``/etc/hosts`` on Linux. Add a line that associates a name with the +local IP. + +Modern Linux systems may be configured to treat any domain name that +ends with ``.localhost`` like this without adding it to the ``hosts`` +file. + +.. code-block:: python + :caption: ``/etc/hosts`` + + 127.0.0.1 hello.localhost + + +Configuration +------------- + +The nginx configuration is located at ``/etc/nginx/nginx.conf`` on +Linux. It may be different depending on your operating system. Check the +docs and look for ``nginx.conf``. + +Remove or comment out any existing ``server`` section. Add a ``server`` +section and use the ``proxy_pass`` directive to point to the address the +WSGI server is listening on. We'll assume the WSGI server is listening +locally at ``http://127.0.0.1:8000``. + +.. code-block:: nginx + :caption: ``/etc/nginx.conf`` + + server { + listen 80; + server_name _; + + location / { + proxy_pass http://127.0.0.1:8000/; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Prefix /; + } + } + +Then :doc:`proxy_fix` so that your application uses these headers. diff --git a/src/flask-main/docs/deploying/proxy_fix.rst b/src/flask-main/docs/deploying/proxy_fix.rst new file mode 100644 index 0000000..e2c42e8 --- /dev/null +++ b/src/flask-main/docs/deploying/proxy_fix.rst @@ -0,0 +1,33 @@ +Tell Flask it is Behind a Proxy +=============================== + +When using a reverse proxy, or many Python hosting platforms, the proxy +will intercept and forward all external requests to the local WSGI +server. + +From the WSGI server and Flask application's perspectives, requests are +now coming from the HTTP server to the local address, rather than from +the remote address to the external server address. + +HTTP servers should set ``X-Forwarded-`` headers to pass on the real +values to the application. The application can then be told to trust and +use those values by wrapping it with the +:doc:`werkzeug:middleware/proxy_fix` middleware provided by Werkzeug. + +This middleware should only be used if the application is actually +behind 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. + +.. code-block:: python + + from werkzeug.middleware.proxy_fix import ProxyFix + + app.wsgi_app = ProxyFix( + app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1 + ) + +Remember, only apply this middleware if you are behind a proxy, and set +the correct number of proxies that set each header. It can be a security +issue if you get this configuration wrong. diff --git a/src/flask-main/docs/deploying/uwsgi.rst b/src/flask-main/docs/deploying/uwsgi.rst new file mode 100644 index 0000000..1f9d5ec --- /dev/null +++ b/src/flask-main/docs/deploying/uwsgi.rst @@ -0,0 +1,145 @@ +uWSGI +===== + +`uWSGI`_ is a fast, compiled server suite with extensive configuration +and capabilities beyond a basic server. + +* It can be very performant due to being a compiled program. +* It is complex to configure beyond the basic application, and has so + many options that it can be difficult for beginners to understand. +* It does not support Windows (but does run on WSL). +* It requires a compiler to install in some cases. + +This page outlines the basics of running uWSGI. Be sure to read its +documentation to understand what features are available. + +.. _uWSGI: https://uwsgi-docs.readthedocs.io/en/latest/ + + +Installing +---------- + +uWSGI has multiple ways to install it. The most straightforward is to +install the ``pyuwsgi`` package, which provides precompiled wheels for +common platforms. However, it does not provide SSL support, which can be +provided with a reverse proxy instead. + +Create a virtualenv, install your application, then install ``pyuwsgi``. + +.. code-block:: text + + $ cd hello-app + $ python -m venv .venv + $ . .venv/bin/activate + $ pip install . # install your application + $ pip install pyuwsgi + +If you have a compiler available, you can install the ``uwsgi`` package +instead. Or install the ``pyuwsgi`` package from sdist instead of wheel. +Either method will include SSL support. + +.. code-block:: text + + $ pip install uwsgi + + # or + $ pip install --no-binary pyuwsgi pyuwsgi + + +Running +------- + +The most basic way to run uWSGI is to tell it to start an HTTP server +and import your application. + +.. code-block:: text + + $ uwsgi --http 127.0.0.1:8000 --master -p 4 -w hello:app + + *** Starting uWSGI 2.0.20 (64bit) on [x] *** + *** Operational MODE: preforking *** + mounting hello:app on / + spawned uWSGI master process (pid: x) + spawned uWSGI worker 1 (pid: x, cores: 1) + spawned uWSGI worker 2 (pid: x, cores: 1) + spawned uWSGI worker 3 (pid: x, cores: 1) + spawned uWSGI worker 4 (pid: x, cores: 1) + spawned uWSGI http 1 (pid: x) + +If you're using the app factory pattern, you'll need to create a small +Python file to create the app, then point uWSGI at that. + +.. code-block:: python + :caption: ``wsgi.py`` + + from hello import create_app + + app = create_app() + +.. code-block:: text + + $ uwsgi --http 127.0.0.1:8000 --master -p 4 -w wsgi:app + +The ``--http`` option starts an HTTP server at 127.0.0.1 port 8000. The +``--master`` option specifies the standard worker manager. The ``-p`` +option starts 4 worker processes; a starting value could be ``CPU * 2``. +The ``-w`` option tells uWSGI how to import your application + + +Binding Externally +------------------ + +uWSGI should not be run as root with the configuration shown in this doc +because it would cause your application code to run as root, which is +not secure. However, this means it will not be possible to bind to port +80 or 443. Instead, a reverse proxy such as :doc:`nginx` or +:doc:`apache-httpd` should be used in front of uWSGI. It is possible to +run uWSGI as root securely, but that is beyond the scope of this doc. + +uWSGI has optimized integration with `Nginx uWSGI`_ and +`Apache mod_proxy_uwsgi`_, and possibly other servers, instead of using +a standard HTTP proxy. That configuration is beyond the scope of this +doc, see the links for more information. + +.. _Nginx uWSGI: https://uwsgi-docs.readthedocs.io/en/latest/Nginx.html +.. _Apache mod_proxy_uwsgi: https://uwsgi-docs.readthedocs.io/en/latest/Apache.html#mod-proxy-uwsgi + +You can bind to all external IPs on a non-privileged port using the +``--http 0.0.0.0:8000`` option. Don't do this when using a reverse proxy +setup, otherwise it will be possible to bypass the proxy. + +.. code-block:: text + + $ uwsgi --http 0.0.0.0:8000 --master -p 4 -w wsgi:app + +``0.0.0.0`` is not a valid address to navigate to, you'd use a specific +IP address in your browser. + + +Async with gevent +----------------- + +The default sync worker is appropriate for many use cases. If you need +asynchronous support, uWSGI provides a `gevent`_ worker. This is not the +same as Python's ``async/await``, or the ASGI server spec. You must +actually use gevent in your own code to see any benefit to using the +worker. + +When using gevent, greenlet>=1.0 is required, otherwise context locals +such as ``request`` will not work as expected. When using PyPy, +PyPy>=7.3.7 is required. + +.. code-block:: text + + $ uwsgi --http 127.0.0.1:8000 --master --gevent 100 -w wsgi:app + + *** Starting uWSGI 2.0.20 (64bit) on [x] *** + *** Operational MODE: async *** + mounting hello:app on / + spawned uWSGI master process (pid: x) + spawned uWSGI worker 1 (pid: x, cores: 100) + spawned uWSGI http 1 (pid: x) + *** running gevent loop engine [addr:x] *** + + +.. _gevent: https://www.gevent.org/ diff --git a/src/flask-main/docs/deploying/waitress.rst b/src/flask-main/docs/deploying/waitress.rst new file mode 100644 index 0000000..7bdd695 --- /dev/null +++ b/src/flask-main/docs/deploying/waitress.rst @@ -0,0 +1,75 @@ +Waitress +======== + +`Waitress`_ is a pure Python WSGI server. + +* It is easy to configure. +* It supports Windows directly. +* It is easy to install as it does not require additional dependencies + or compilation. +* It does not support streaming requests, full request data is always + buffered. +* It uses a single process with multiple thread workers. + +This page outlines the basics of running Waitress. Be sure to read its +documentation and ``waitress-serve --help`` to understand what features +are available. + +.. _Waitress: https://docs.pylonsproject.org/projects/waitress/ + + +Installing +---------- + +Create a virtualenv, install your application, then install +``waitress``. + +.. code-block:: text + + $ cd hello-app + $ python -m venv .venv + $ . .venv/bin/activate + $ pip install . # install your application + $ pip install waitress + + +Running +------- + +The only required argument to ``waitress-serve`` tells it how to load +your Flask application. The syntax is ``{module}:{app}``. ``module`` is +the dotted import name to the module with your application. ``app`` is +the variable with the application. If you're using the app factory +pattern, use ``--call {module}:{factory}`` instead. + +.. code-block:: text + + # equivalent to 'from hello import app' + $ waitress-serve --host 127.0.0.1 hello:app + + # equivalent to 'from hello import create_app; create_app()' + $ waitress-serve --host 127.0.0.1 --call hello:create_app + + Serving on http://127.0.0.1:8080 + +The ``--host`` option binds the server to local ``127.0.0.1`` only. + +Logs for each request aren't shown, only errors are shown. Logging can +be configured through the Python interface instead of the command line. + + +Binding Externally +------------------ + +Waitress should not be run as root because it would cause your +application code to run as root, which is not secure. However, this +means it will not be possible to bind to port 80 or 443. Instead, a +reverse proxy such as :doc:`nginx` or :doc:`apache-httpd` should be used +in front of Waitress. + +You can bind to all external IPs on a non-privileged port by not +specifying the ``--host`` option. Don't do this when using a reverse +proxy setup, otherwise it will be possible to bypass the proxy. + +``0.0.0.0`` is not a valid address to navigate to, you'd use a specific +IP address in your browser. diff --git a/src/flask-main/docs/design.rst b/src/flask-main/docs/design.rst new file mode 100644 index 0000000..d8776a9 --- /dev/null +++ b/src/flask-main/docs/design.rst @@ -0,0 +1,229 @@ +Design Decisions in Flask +========================= + +If you are curious why Flask does certain things the way it does and not +differently, this section is for you. This should give you an idea about +some of the design decisions that may appear arbitrary and surprising at +first, especially in direct comparison with other frameworks. + + +The Explicit Application Object +------------------------------- + +A Python web application based on WSGI has to have one central callable +object that implements the actual application. In Flask this is an +instance of the :class:`~flask.Flask` class. Each Flask application has +to create an instance of this class itself and pass it the name of the +module, but why can't Flask do that itself? + +Without such an explicit application object the following code:: + + from flask import Flask + app = Flask(__name__) + + @app.route('/') + def index(): + return 'Hello World!' + +Would look like this instead:: + + from hypothetical_flask import route + + @route('/') + def index(): + return 'Hello World!' + +There are three major reasons for this. The most important one is that +implicit application objects require that there may only be one instance at +the time. There are ways to fake multiple applications with a single +application object, like maintaining a stack of applications, but this +causes some problems I won't outline here in detail. Now the question is: +when does a microframework need more than one application at the same +time? A good example for this is unit testing. When you want to test +something it can be very helpful to create a minimal application to test +specific behavior. When the application object is deleted everything it +allocated will be freed again. + +Another thing that becomes possible when you have an explicit object lying +around in your code is that you can subclass the base class +(:class:`~flask.Flask`) to alter specific behavior. This would not be +possible without hacks if the object were created ahead of time for you +based on a class that is not exposed to you. + +But there is another very important reason why Flask depends on an +explicit instantiation of that class: the package name. Whenever you +create a Flask instance you usually pass it `__name__` as package name. +Flask depends on that information to properly load resources relative +to your module. With Python's outstanding support for reflection it can +then access the package to figure out where the templates and static files +are stored (see :meth:`~flask.Flask.open_resource`). Now obviously there +are frameworks around that do not need any configuration and will still be +able to load templates relative to your application module. But they have +to use the current working directory for that, which is a very unreliable +way to determine where the application is. The current working directory +is process-wide and if you are running multiple applications in one +process (which could happen in a webserver without you knowing) the paths +will be off. Worse: many webservers do not set the working directory to +the directory of your application but to the document root which does not +have to be the same folder. + +The third reason is "explicit is better than implicit". That object is +your WSGI application, you don't have to remember anything else. If you +want to apply a WSGI middleware, just wrap it and you're done (though +there are better ways to do that so that you do not lose the reference +to the application object :meth:`~flask.Flask.wsgi_app`). + +Furthermore this design makes it possible to use a factory function to +create the application which is very helpful for unit testing and similar +things (:doc:`/patterns/appfactories`). + +The Routing System +------------------ + +Flask uses the Werkzeug routing system which was designed to +automatically order routes by complexity. This means that you can declare +routes in arbitrary order and they will still work as expected. This is a +requirement if you want to properly implement decorator based routing +since decorators could be fired in undefined order when the application is +split into multiple modules. + +Another design decision with the Werkzeug routing system is that routes +in Werkzeug try to ensure that URLs are unique. Werkzeug will go quite far +with that in that it will automatically redirect to a canonical URL if a route +is ambiguous. + + +One Template Engine +------------------- + +Flask decides on one template engine: Jinja. Why doesn't Flask have a +pluggable template engine interface? You can obviously use a different +template engine, but Flask will still configure Jinja for you. While +that limitation that Jinja is *always* configured will probably go away, +the decision to bundle one template engine and use that will not. + +Template engines are like programming languages and each of those engines +has a certain understanding about how things work. On the surface they +all work the same: you tell the engine to evaluate a template with a set +of variables and take the return value as string. + +But that's about where similarities end. Jinja for example has an +extensive filter system, a certain way to do template inheritance, +support for reusable blocks (macros) that can be used from inside +templates and also from Python code, supports iterative template +rendering, configurable syntax and more. On the other hand an engine +like Genshi is based on XML stream evaluation, template inheritance by +taking the availability of XPath into account and more. Mako on the +other hand treats templates similar to Python modules. + +When it comes to connecting a template engine with an application or +framework there is more than just rendering templates. For instance, +Flask uses Jinja's extensive autoescaping support. Also it provides +ways to access macros from Jinja templates. + +A template abstraction layer that would not take the unique features of +the template engines away is a science on its own and a too large +undertaking for a microframework like Flask. + +Furthermore extensions can then easily depend on one template language +being present. You can easily use your own templating language, but an +extension could still depend on Jinja itself. + + +What does "micro" mean? +----------------------- + +“Micro” does not mean that your whole web application has to fit into a single +Python file (although it certainly can), nor does it mean that Flask is lacking +in functionality. The "micro" in microframework means Flask aims to keep the +core simple but extensible. Flask won't make many decisions for you, such as +what database to use. Those decisions that it does make, such as what +templating engine to use, are easy to change. Everything else is up to you, so +that Flask can be everything you need and nothing you don't. + +By default, Flask does not include a database abstraction layer, form +validation or anything else where different libraries already exist that can +handle that. Instead, Flask supports extensions to add such functionality to +your application as if it was implemented in Flask itself. Numerous extensions +provide database integration, form validation, upload handling, various open +authentication technologies, and more. Flask may be "micro", but it's ready for +production use on a variety of needs. + +Why does Flask call itself a microframework and yet it depends on two +libraries (namely Werkzeug and Jinja). Why shouldn't it? If we look +over to the Ruby side of web development there we have a protocol very +similar to WSGI. Just that it's called Rack there, but besides that it +looks very much like a WSGI rendition for Ruby. But nearly all +applications in Ruby land do not work with Rack directly, but on top of a +library with the same name. This Rack library has two equivalents in +Python: WebOb (formerly Paste) and Werkzeug. Paste is still around but +from my understanding it's sort of deprecated in favour of WebOb. The +development of WebOb and Werkzeug started side by side with similar ideas +in mind: be a good implementation of WSGI for other applications to take +advantage. + +Flask is a framework that takes advantage of the work already done by +Werkzeug to properly interface WSGI (which can be a complex task at +times). Thanks to recent developments in the Python package +infrastructure, packages with dependencies are no longer an issue and +there are very few reasons against having libraries that depend on others. + + +Context Locals +-------------- + +Flask uses special context locals and proxies to provide access to the +current app and request data to any code running during a request, CLI command, +etc. Context locals are specific to the worker handling the activity, such as a +thread, process, coroutine, or greenlet. + +The context and proxies help solve two development issues: circular imports, and +passing around global data. :data:`.current_app` can be used to access the +application object without needing to import the app object directly, avoiding +circular import issues. :data:`.request`, :data:`.session`, and :data:`.g` can +be imported to access the current data for the request, rather than needing to +pass them as arguments through every single function in your project. + + +Async/await and ASGI support +---------------------------- + +Flask supports ``async`` coroutines for view functions by executing the +coroutine on a separate thread instead of using an event loop on the +main thread as an async-first (ASGI) framework would. This is necessary +for Flask to remain backwards compatible with extensions and code built +before ``async`` was introduced into Python. This compromise introduces +a performance cost compared with the ASGI frameworks, due to the +overhead of the threads. + +Due to how tied to WSGI Flask's code is, it's not clear if it's possible +to make the ``Flask`` class support ASGI and WSGI at the same time. Work +is currently being done in Werkzeug to work with ASGI, which may +eventually enable support in Flask as well. + +See :doc:`/async-await` for more discussion. + + +What Flask is, What Flask is Not +-------------------------------- + +Flask will never have a database layer. It will not have a form library +or anything else in that direction. Flask itself just bridges to Werkzeug +to implement a proper WSGI application and to Jinja to handle templating. +It also binds to a few common standard library packages such as logging. +Everything else is up for extensions. + +Why is this the case? Because people have different preferences and +requirements and Flask could not meet those if it would force any of this +into the core. The majority of web applications will need a template +engine in some sort. However not every application needs a SQL database. + +As your codebase grows, you are free to make the design decisions appropriate +for your project. Flask will continue to provide a very simple glue layer to +the best that Python has to offer. You can implement advanced patterns in +SQLAlchemy or another database tool, introduce non-relational data persistence +as appropriate, and take advantage of framework-agnostic tools built for WSGI, +the Python web interface. + +The idea of Flask is to build a good foundation for all applications. +Everything else is up to you or extensions. diff --git a/src/flask-main/docs/errorhandling.rst b/src/flask-main/docs/errorhandling.rst new file mode 100644 index 0000000..faca58c --- /dev/null +++ b/src/flask-main/docs/errorhandling.rst @@ -0,0 +1,523 @@ +Handling Application Errors +=========================== + +Applications fail, servers fail. Sooner or later you will see an exception +in production. Even if your code is 100% correct, you will still see +exceptions from time to time. Why? Because everything else involved will +fail. Here are some situations where perfectly fine code can lead to server +errors: + +- the client terminated the request early and the application was still + reading from the incoming data +- the database server was overloaded and could not handle the query +- a filesystem is full +- a harddrive crashed +- a backend server overloaded +- a programming error in a library you are using +- network connection of the server to another system failed + +And that's just a small sample of issues you could be facing. So how do we +deal with that sort of problem? By default if your application runs in +production mode, and an exception is raised Flask will display a very simple +page for you and log the exception to the :attr:`~flask.Flask.logger`. + +But there is more you can do, and we will cover some better setups to deal +with errors including custom exceptions and 3rd party tools. + + +.. _error-logging-tools: + +Error Logging Tools +------------------- + +Sending error mails, even if just for critical ones, can become +overwhelming if enough users are hitting the error and log files are +typically never looked at. This is why we recommend using `Sentry +`_ for dealing with application errors. It's +available as a source-available project `on GitHub +`_ and is also available as a `hosted version +`_ which you can try for free. Sentry +aggregates duplicate errors, captures the full stack trace and local +variables for debugging, and sends you mails based on new errors or +frequency thresholds. + +To use Sentry you need to install the ``sentry-sdk`` client with extra +``flask`` dependencies. + +.. code-block:: text + + $ pip install sentry-sdk[flask] + +And then add this to your Flask app: + +.. code-block:: python + + import sentry_sdk + from sentry_sdk.integrations.flask import FlaskIntegration + + sentry_sdk.init('YOUR_DSN_HERE', integrations=[FlaskIntegration()]) + +The ``YOUR_DSN_HERE`` value needs to be replaced with the DSN value you +get from your Sentry installation. + +After installation, failures leading to an Internal Server Error +are automatically reported to Sentry and from there you can +receive error notifications. + +See also: + +- Sentry also supports catching errors from a worker queue + (RQ, Celery, etc.) in a similar fashion. See the `Python SDK docs + `__ for more information. +- `Flask-specific documentation `__ + + +Error Handlers +-------------- + +When an error occurs in Flask, an appropriate `HTTP status code +`__ will be +returned. 400-499 indicate errors with the client's request data, or +about the data requested. 500-599 indicate errors with the server or +application itself. + +You might want to show custom error pages to the user when an error occurs. +This can be done by registering error handlers. + +An error handler is a function that returns a response when a type of error is +raised, similar to how a view is a function that returns a response when a +request URL is matched. It is passed the instance of the error being handled, +which is most likely a :exc:`~werkzeug.exceptions.HTTPException`. + +The status code of the response will not be set to the handler's code. Make +sure to provide the appropriate HTTP status code when returning a response from +a handler. + + +Registering +``````````` + +Register handlers by decorating a function with +:meth:`~flask.Flask.errorhandler`. Or use +:meth:`~flask.Flask.register_error_handler` to register the function later. +Remember to set the error code when returning the response. + +.. code-block:: python + + @app.errorhandler(werkzeug.exceptions.BadRequest) + def handle_bad_request(e): + return 'bad request!', 400 + + # or, without the decorator + app.register_error_handler(400, handle_bad_request) + +:exc:`werkzeug.exceptions.HTTPException` subclasses like +:exc:`~werkzeug.exceptions.BadRequest` and their HTTP codes are interchangeable +when registering handlers. (``BadRequest.code == 400``) + +Non-standard HTTP codes cannot be registered by code because they are not known +by Werkzeug. Instead, define a subclass of +:class:`~werkzeug.exceptions.HTTPException` with the appropriate code and +register and raise that exception class. + +.. code-block:: python + + class InsufficientStorage(werkzeug.exceptions.HTTPException): + code = 507 + description = 'Not enough storage space.' + + app.register_error_handler(InsufficientStorage, handle_507) + + raise InsufficientStorage() + +Handlers can be registered for any exception class, not just +:exc:`~werkzeug.exceptions.HTTPException` subclasses or HTTP status +codes. Handlers can be registered for a specific class, or for all subclasses +of a parent class. + + +Handling +```````` + +When building a Flask application you *will* run into exceptions. If some part +of your code breaks while handling a request (and you have no error handlers +registered), a "500 Internal Server Error" +(:exc:`~werkzeug.exceptions.InternalServerError`) will be returned by default. +Similarly, "404 Not Found" +(:exc:`~werkzeug.exceptions.NotFound`) error will occur if a request is sent to an unregistered route. +If a route receives an unallowed request method, a "405 Method Not Allowed" +(:exc:`~werkzeug.exceptions.MethodNotAllowed`) will be raised. These are all +subclasses of :class:`~werkzeug.exceptions.HTTPException` and are provided by +default in Flask. + +Flask gives you the ability to raise any HTTP exception registered by +Werkzeug. However, the default HTTP exceptions return simple exception +pages. You might want to show custom error pages to the user when an error occurs. +This can be done by registering error handlers. + +When Flask catches an exception while handling a request, it is first looked up by code. +If no handler is registered for the code, Flask looks up the error by its class hierarchy; the most specific handler is chosen. +If no handler is registered, :class:`~werkzeug.exceptions.HTTPException` subclasses show a +generic message about their code, while other exceptions are converted to a +generic "500 Internal Server Error". + +For example, if an instance of :exc:`ConnectionRefusedError` is raised, +and a handler is registered for :exc:`ConnectionError` and +:exc:`ConnectionRefusedError`, the more specific :exc:`ConnectionRefusedError` +handler is called with the exception instance to generate the response. + +Handlers registered on the blueprint take precedence over those registered +globally on the application, assuming a blueprint is handling the request that +raises the exception. However, the blueprint cannot handle 404 routing errors +because the 404 occurs at the routing level before the blueprint can be +determined. + + +Generic Exception Handlers +`````````````````````````` + +It is possible to register error handlers for very generic base classes +such as ``HTTPException`` or even ``Exception``. However, be aware that +these will catch more than you might expect. + +For example, an error handler for ``HTTPException`` might be useful for turning +the default HTML errors pages into JSON. However, this +handler will trigger for things you don't cause directly, such as 404 +and 405 errors during routing. Be sure to craft your handler carefully +so you don't lose information about the HTTP error. + +.. code-block:: python + + from flask import json + from werkzeug.exceptions import HTTPException + + @app.errorhandler(HTTPException) + def handle_exception(e): + """Return JSON instead of HTML for HTTP errors.""" + # start with the correct headers and status code from the error + response = e.get_response() + # replace the body with JSON + response.data = json.dumps({ + "code": e.code, + "name": e.name, + "description": e.description, + }) + response.content_type = "application/json" + return response + +An error handler for ``Exception`` might seem useful for changing how +all errors, even unhandled ones, are presented to the user. However, +this is similar to doing ``except Exception:`` in Python, it will +capture *all* otherwise unhandled errors, including all HTTP status +codes. + +In most cases it will be safer to register handlers for more +specific exceptions. Since ``HTTPException`` instances are valid WSGI +responses, you could also pass them through directly. + +.. code-block:: python + + from werkzeug.exceptions import HTTPException + + @app.errorhandler(Exception) + def handle_exception(e): + # pass through HTTP errors + if isinstance(e, HTTPException): + return e + + # now you're handling non-HTTP exceptions only + return render_template("500_generic.html", e=e), 500 + +Error handlers still respect the exception class hierarchy. If you +register handlers for both ``HTTPException`` and ``Exception``, the +``Exception`` handler will not handle ``HTTPException`` subclasses +because the ``HTTPException`` handler is more specific. + + +Unhandled Exceptions +```````````````````` + +When there is no error handler registered for an exception, a 500 +Internal Server Error will be returned instead. See +:meth:`flask.Flask.handle_exception` for information about this +behavior. + +If there is an error handler registered for ``InternalServerError``, +this will be invoked. As of Flask 1.1.0, this error handler will always +be passed an instance of ``InternalServerError``, not the original +unhandled error. + +The original error is available as ``e.original_exception``. + +An error handler for "500 Internal Server Error" will be passed uncaught +exceptions in addition to explicit 500 errors. In debug mode, a handler +for "500 Internal Server Error" will not be used. Instead, the +interactive debugger will be shown. + + +Custom Error Pages +------------------ + +Sometimes when building a Flask application, you might want to raise a +:exc:`~werkzeug.exceptions.HTTPException` to signal to the user that +something is wrong with the request. Fortunately, Flask comes with a handy +:func:`~flask.abort` function that aborts a request with a HTTP error from +werkzeug as desired. It will also provide a plain black and white error page +for you with a basic description, but nothing fancy. + +Depending on the error code it is less or more likely for the user to +actually see such an error. + +Consider the code below, we might have a user profile route, and if the user +fails to pass a username we can raise a "400 Bad Request". If the user passes a +username and we can't find it, we raise a "404 Not Found". + +.. code-block:: python + + from flask import abort, render_template, request + + # a username needs to be supplied in the query args + # a successful request would be like /profile?username=jack + @app.route("/profile") + def user_profile(): + username = request.arg.get("username") + # if a username isn't supplied in the request, return a 400 bad request + if username is None: + abort(400) + + user = get_user(username=username) + # if a user can't be found by their username, return 404 not found + if user is None: + abort(404) + + return render_template("profile.html", user=user) + +Here is another example implementation for a "404 Page Not Found" exception: + +.. code-block:: python + + from flask import render_template + + @app.errorhandler(404) + def page_not_found(e): + # note that we set the 404 status explicitly + return render_template('404.html'), 404 + +When using :doc:`/patterns/appfactories`: + +.. code-block:: python + + from flask import Flask, render_template + + def page_not_found(e): + return render_template('404.html'), 404 + + def create_app(config_filename): + app = Flask(__name__) + app.register_error_handler(404, page_not_found) + return app + +An example template might be this: + +.. code-block:: html+jinja + + {% extends "layout.html" %} + {% block title %}Page Not Found{% endblock %} + {% block body %} +

Page Not Found

+

What you were looking for is just not there. +

go somewhere nice + {% endblock %} + + +Further Examples +```````````````` + +The above examples wouldn't actually be an improvement on the default +exception pages. We can create a custom 500.html template like this: + +.. code-block:: html+jinja + + {% extends "layout.html" %} + {% block title %}Internal Server Error{% endblock %} + {% block body %} +

Internal Server Error

+

Oops... we seem to have made a mistake, sorry!

+

Go somewhere nice instead + {% endblock %} + +It can be implemented by rendering the template on "500 Internal Server Error": + +.. code-block:: python + + from flask import render_template + + @app.errorhandler(500) + def internal_server_error(e): + # note that we set the 500 status explicitly + return render_template('500.html'), 500 + +When using :doc:`/patterns/appfactories`: + +.. code-block:: python + + from flask import Flask, render_template + + def internal_server_error(e): + return render_template('500.html'), 500 + + def create_app(): + app = Flask(__name__) + app.register_error_handler(500, internal_server_error) + return app + +When using :doc:`/blueprints`: + +.. code-block:: python + + from flask import Blueprint + + blog = Blueprint('blog', __name__) + + # as a decorator + @blog.errorhandler(500) + def internal_server_error(e): + return render_template('500.html'), 500 + + # or with register_error_handler + blog.register_error_handler(500, internal_server_error) + + +Blueprint Error Handlers +------------------------ + +In :doc:`/blueprints`, most error handlers will work as expected. +However, there is a caveat concerning handlers for 404 and 405 +exceptions. These error handlers are only invoked from an appropriate +``raise`` statement or a call to ``abort`` in another of the blueprint's +view functions; they are not invoked by, e.g., an invalid URL access. + +This is because the blueprint does not "own" a certain URL space, so +the application instance has no way of knowing which blueprint error +handler it should run if given an invalid URL. If you would like to +execute different handling strategies for these errors based on URL +prefixes, they may be defined at the application level using the +``request`` proxy object. + +.. code-block:: python + + from flask import jsonify, render_template + + # at the application level + # not the blueprint level + @app.errorhandler(404) + def page_not_found(e): + # if a request is in our blog URL space + if request.path.startswith('/blog/'): + # we return a custom blog 404 page + return render_template("blog/404.html"), 404 + else: + # otherwise we return our generic site-wide 404 page + return render_template("404.html"), 404 + + @app.errorhandler(405) + def method_not_allowed(e): + # if a request has the wrong method to our API + if request.path.startswith('/api/'): + # we return a json saying so + return jsonify(message="Method Not Allowed"), 405 + else: + # otherwise we return a generic site-wide 405 page + return render_template("405.html"), 405 + + +Returning API Errors as JSON +---------------------------- + +When building APIs in Flask, some developers realise that the built-in +exceptions are not expressive enough for APIs and that the content type of +:mimetype:`text/html` they are emitting is not very useful for API consumers. + +Using the same techniques as above and :func:`~flask.json.jsonify` we can return JSON +responses to API errors. :func:`~flask.abort` is called +with a ``description`` parameter. The error handler will +use that as the JSON error message, and set the status code to 404. + +.. code-block:: python + + from flask import abort, jsonify + + @app.errorhandler(404) + def resource_not_found(e): + return jsonify(error=str(e)), 404 + + @app.route("/cheese") + def get_one_cheese(): + resource = get_resource() + + if resource is None: + abort(404, description="Resource not found") + + return jsonify(resource) + +We can also create custom exception classes. For instance, we can +introduce a new custom exception for an API that can take a proper human readable message, +a status code for the error and some optional payload to give more context +for the error. + +This is a simple example: + +.. code-block:: python + + from flask import jsonify, request + + class InvalidAPIUsage(Exception): + status_code = 400 + + def __init__(self, message, status_code=None, payload=None): + super().__init__() + self.message = message + if status_code is not None: + self.status_code = status_code + self.payload = payload + + def to_dict(self): + rv = dict(self.payload or ()) + rv['message'] = self.message + return rv + + @app.errorhandler(InvalidAPIUsage) + def invalid_api_usage(e): + return jsonify(e.to_dict()), e.status_code + + # an API app route for getting user information + # a correct request might be /api/user?user_id=420 + @app.route("/api/user") + def user_api(user_id): + user_id = request.arg.get("user_id") + if not user_id: + raise InvalidAPIUsage("No user id provided!") + + user = get_user(user_id=user_id) + if not user: + raise InvalidAPIUsage("No such user!", status_code=404) + + return jsonify(user.to_dict()) + +A view can now raise that exception with an error message. Additionally +some extra payload can be provided as a dictionary through the `payload` +parameter. + + +Logging +------- + +See :doc:`/logging` for information about how to log exceptions, such as +by emailing them to admins. + + +Debugging +--------- + +See :doc:`/debugging` for information about how to debug errors in +development and production. diff --git a/src/flask-main/docs/extensiondev.rst b/src/flask-main/docs/extensiondev.rst new file mode 100644 index 0000000..ae5ed33 --- /dev/null +++ b/src/flask-main/docs/extensiondev.rst @@ -0,0 +1,305 @@ +Flask Extension Development +=========================== + +.. currentmodule:: flask + +Extensions are extra packages that add functionality to a Flask +application. While `PyPI`_ contains many Flask extensions, you may not +find one that fits your need. If this is the case, you can create your +own, and publish it for others to use as well. + +This guide will show how to create a Flask extension, and some of the +common patterns and requirements involved. Since extensions can do +anything, this guide won't be able to cover every possibility. + +The best ways to learn about extensions are to look at how other +extensions you use are written, and discuss with others. Discuss your +design ideas with others on our `Discord Chat`_ or +`GitHub Discussions`_. + +The best extensions share common patterns, so that anyone familiar with +using one extension won't feel completely lost with another. This can +only work if collaboration happens early. + + +Naming +------ + +A Flask extension typically has ``flask`` in its name as a prefix or +suffix. If it wraps another library, it should include the library name +as well. This makes it easy to search for extensions, and makes their +purpose clearer. + +A general Python packaging recommendation is that the install name from +the package index and the name used in ``import`` statements should be +related. The import name is lowercase, with words separated by +underscores (``_``). The install name is either lower case or title +case, with words separated by dashes (``-``). If it wraps another +library, prefer using the same case as that library's name. + +Here are some example install and import names: + +- ``Flask-Name`` imported as ``flask_name`` +- ``flask-name-lower`` imported as ``flask_name_lower`` +- ``Flask-ComboName`` imported as ``flask_comboname`` +- ``Name-Flask`` imported as ``name_flask`` + + +The Extension Class and Initialization +-------------------------------------- + +All extensions will need some entry point that initializes the +extension with the application. The most common pattern is to create a +class that represents the extension's configuration and behavior, with +an ``init_app`` method to apply the extension instance to the given +application instance. + +.. code-block:: python + + class HelloExtension: + def __init__(self, app=None): + if app is not None: + self.init_app(app) + + def init_app(self, app): + app.before_request(...) + +It is important that the app is not stored on the extension, don't do +``self.app = app``. The only time the extension should have direct +access to an app is during ``init_app``, otherwise it should use +:data:`.current_app`. + +This allows the extension to support the application factory pattern, +avoids circular import issues when importing the extension instance +elsewhere in a user's code, and makes testing with different +configurations easier. + +.. code-block:: python + + hello = HelloExtension() + + def create_app(): + app = Flask(__name__) + hello.init_app(app) + return app + +Above, the ``hello`` extension instance exists independently of the +application. This means that other modules in a user's project can do +``from project import hello`` and use the extension in blueprints before +the app exists. + +The :attr:`Flask.extensions` dict can be used to store a reference to +the extension on the application, or some other state specific to the +application. Be aware that this is a single namespace, so use a name +unique to your extension, such as the extension's name without the +"flask" prefix. + + +Adding Behavior +--------------- + +There are many ways that an extension can add behavior. Any setup +methods that are available on the :class:`Flask` object can be used +during an extension's ``init_app`` method. + +A common pattern is to use :meth:`~Flask.before_request` to initialize +some data or a connection at the beginning of each request, then +:meth:`~Flask.teardown_request` to clean it up at the end. This can be +stored on :data:`.g`, discussed more below. + +A more lazy approach is to provide a method that initializes and caches +the data or connection. For example, a ``ext.get_db`` method could +create a database connection the first time it's called, so that a view +that doesn't use the database doesn't create a connection. + +Besides doing something before and after every view, your extension +might want to add some specific views as well. In this case, you could +define a :class:`Blueprint`, then call :meth:`~Flask.register_blueprint` +during ``init_app`` to add the blueprint to the app. + + +Configuration Techniques +------------------------ + +There can be multiple levels and sources of configuration for an +extension. You should consider what parts of your extension fall into +each one. + +- Configuration per application instance, through ``app.config`` + values. This is configuration that could reasonably change for each + deployment of an application. A common example is a URL to an + external resource, such as a database. Configuration keys should + start with the extension's name so that they don't interfere with + other extensions. +- Configuration per extension instance, through ``__init__`` + arguments. This configuration usually affects how the extension + is used, such that it wouldn't make sense to change it per + deployment. +- Configuration per extension instance, through instance attributes + and decorator methods. It might be more ergonomic to assign to + ``ext.value``, or use a ``@ext.register`` decorator to register a + function, after the extension instance has been created. +- Global configuration through class attributes. Changing a class + attribute like ``Ext.connection_class`` can customize default + behavior without making a subclass. This could be combined + per-extension configuration to override defaults. +- Subclassing and overriding methods and attributes. Making the API of + the extension itself something that can be overridden provides a + very powerful tool for advanced customization. + +The :class:`~flask.Flask` object itself uses all of these techniques. + +It's up to you to decide what configuration is appropriate for your +extension, based on what you need and what you want to support. + +Configuration should not be changed after the application setup phase is +complete and the server begins handling requests. Configuration is +global, any changes to it are not guaranteed to be visible to other +workers. + + +Data During a Request +--------------------- + +When writing a Flask application, the :data:`~flask.g` object is used to +store information during a request. For example the +:doc:`tutorial ` stores a connection to a SQLite +database as ``g.db``. Extensions can also use this, with some care. +Since ``g`` is a single global namespace, extensions must use unique +names that won't collide with user data. For example, use the extension +name as a prefix, or as a namespace. + +.. code-block:: python + + # an internal prefix with the extension name + g._hello_user_id = 2 + + # or an internal prefix as a namespace + from types import SimpleNamespace + g._hello = SimpleNamespace() + g._hello.user_id = 2 + +The data in ``g`` lasts for an application context. An application context is +active during a request, CLI command, or ``with app.app_context()`` block. If +you're storing something that should be closed, use +:meth:`~flask.Flask.teardown_appcontext` to ensure that it gets closed when the +app context ends. If it should only be valid during a request, or would not be +used in the CLI outside a request, use :meth:`~flask.Flask.teardown_request`. + + +Views and Models +---------------- + +Your extension views might want to interact with specific models in your +database, or some other extension or data connected to your application. +For example, let's consider a ``Flask-SimpleBlog`` extension that works +with Flask-SQLAlchemy to provide a ``Post`` model and views to write +and read posts. + +The ``Post`` model needs to subclass the Flask-SQLAlchemy ``db.Model`` +object, but that's only available once you've created an instance of +that extension, not when your extension is defining its views. So how +can the view code, defined before the model exists, access the model? + +One method could be to use :doc:`views`. During ``__init__``, create +the model, then create the views by passing the model to the view +class's :meth:`~views.View.as_view` method. + +.. code-block:: python + + class PostAPI(MethodView): + def __init__(self, model): + self.model = model + + def get(self, id): + post = self.model.query.get(id) + return jsonify(post.to_json()) + + class BlogExtension: + def __init__(self, db): + class Post(db.Model): + id = db.Column(primary_key=True) + title = db.Column(db.String, nullable=False) + + self.post_model = Post + + def init_app(self, app): + api_view = PostAPI.as_view(model=self.post_model) + + db = SQLAlchemy() + blog = BlogExtension(db) + db.init_app(app) + blog.init_app(app) + +Another technique could be to use an attribute on the extension, such as +``self.post_model`` from above. Add the extension to ``app.extensions`` +in ``init_app``, then access +``current_app.extensions["simple_blog"].post_model`` from views. + +You may also want to provide base classes so that users can provide +their own ``Post`` model that conforms to the API your extension +expects. So they could implement ``class Post(blog.BasePost)``, then +set it as ``blog.post_model``. + +As you can see, this can get a bit complex. Unfortunately, there's no +perfect solution here, only different strategies and tradeoffs depending +on your needs and how much customization you want to offer. Luckily, +this sort of resource dependency is not a common need for most +extensions. Remember, if you need help with design, ask on our +`Discord Chat`_ or `GitHub Discussions`_. + + +Recommended Extension Guidelines +-------------------------------- + +Flask previously had the concept of "approved extensions", where the +Flask maintainers evaluated the quality, support, and compatibility of +the extensions before listing them. While the list became too difficult +to maintain over time, the guidelines are still relevant to all +extensions maintained and developed today, as they help the Flask +ecosystem remain consistent and compatible. + +1. An extension requires a maintainer. In the event an extension author + would like to move beyond the project, the project should find a new + maintainer and transfer access to the repository, documentation, + PyPI, and any other services. The `Pallets-Eco`_ organization on + GitHub allows for community maintenance with oversight from the + Pallets maintainers. +2. The naming scheme is *Flask-ExtensionName* or *ExtensionName-Flask*. + It must provide exactly one package or module named + ``flask_extension_name``. +3. The extension must use an open source license. The Python web + ecosystem tends to prefer BSD or MIT. It must be open source and + publicly available. +4. The extension's API must have the following characteristics: + + - It must support multiple applications running in the same Python + process. Use ``current_app`` instead of ``self.app``, store + configuration and state per application instance. + - It must be possible to use the factory pattern for creating + applications. Use the ``ext.init_app()`` pattern. + +5. From a clone of the repository, an extension with its dependencies + must be installable in editable mode with ``pip install -e .``. +6. It must ship tests that can be invoked with a common tool like + ``tox -e py``, ``nox -s test`` or ``pytest``. If not using ``tox``, + the test dependencies should be specified in a requirements file. + The tests must be part of the sdist distribution. +7. A link to the documentation or project website must be in the PyPI + metadata or the readme. The documentation should use the Flask theme + from the `Official Pallets Themes`_. +8. The extension's dependencies should not use upper bounds or assume + any particular version scheme, but should use lower bounds to + indicate minimum compatibility support. For example, + ``sqlalchemy>=1.4``. +9. Indicate the versions of Python supported using ``python_requires=">=version"``. + Flask and Pallets policy is to support all Python versions that are not + within six months of end of life (EOL). See Python's `EOL calendar`_ for + timing. + +.. _PyPI: https://pypi.org/search/?c=Framework+%3A%3A+Flask +.. _Discord Chat: https://discord.gg/pallets +.. _GitHub Discussions: https://github.com/pallets/flask/discussions +.. _Official Pallets Themes: https://pypi.org/project/Pallets-Sphinx-Themes/ +.. _Pallets-Eco: https://github.com/pallets-eco +.. _EOL calendar: https://devguide.python.org/versions/ diff --git a/src/flask-main/docs/extensions.rst b/src/flask-main/docs/extensions.rst new file mode 100644 index 0000000..4713ec8 --- /dev/null +++ b/src/flask-main/docs/extensions.rst @@ -0,0 +1,48 @@ +Extensions +========== + +Extensions are extra packages that add functionality to a Flask +application. For example, an extension might add support for sending +email or connecting to a database. Some extensions add entire new +frameworks to help build certain types of applications, like a REST API. + + +Finding Extensions +------------------ + +Flask extensions are usually named "Flask-Foo" or "Foo-Flask". You can +search PyPI for packages tagged with `Framework :: Flask `_. + + +Using Extensions +---------------- + +Consult each extension's documentation for installation, configuration, +and usage instructions. Generally, extensions pull their own +configuration from :attr:`app.config ` and are +passed an application instance during initialization. For example, +an extension called "Flask-Foo" might be used like this:: + + from flask_foo import Foo + + foo = Foo() + + app = Flask(__name__) + app.config.update( + FOO_BAR='baz', + FOO_SPAM='eggs', + ) + + foo.init_app(app) + + +Building Extensions +------------------- + +While `PyPI `_ contains many Flask extensions, you may not find +an extension that fits your need. If this is the case, you can create +your own, and publish it for others to use as well. Read +:doc:`extensiondev` to develop your own Flask extension. + + +.. _pypi: https://pypi.org/search/?c=Framework+%3A%3A+Flask diff --git a/src/flask-main/docs/index.rst b/src/flask-main/docs/index.rst new file mode 100644 index 0000000..63fdb86 --- /dev/null +++ b/src/flask-main/docs/index.rst @@ -0,0 +1,88 @@ +.. rst-class:: hide-header + +Welcome to Flask +================ + +.. image:: _static/flask-name.svg + :align: center + :height: 200px + +Welcome to Flask's documentation. 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. + +Get started with :doc:`installation` +and then get an overview with the :doc:`quickstart`. There is also a +more detailed :doc:`tutorial/index` that shows how to create a small but +complete application with Flask. Common patterns are described in the +:doc:`patterns/index` section. The rest of the docs describe each +component of Flask in detail, with a full reference in the :doc:`api` +section. + +Flask depends on the `Werkzeug`_ WSGI toolkit, the `Jinja`_ template engine, and the +`Click`_ CLI toolkit. Be sure to check their documentation as well as Flask's when +looking for information. + +.. _Werkzeug: https://werkzeug.palletsprojects.com +.. _Jinja: https://jinja.palletsprojects.com +.. _Click: https://click.palletsprojects.com + + +User's Guide +------------ + +Flask provides configuration and conventions, with sensible defaults, to get started. +This section of the documentation explains the different parts of the Flask framework +and how they can be used, customized, and extended. Beyond Flask itself, look for +community-maintained extensions to add even more functionality. + +.. toctree:: + :maxdepth: 2 + + installation + quickstart + tutorial/index + templating + testing + errorhandling + debugging + logging + config + signals + views + lifecycle + appcontext + blueprints + extensions + cli + server + shell + patterns/index + web-security + deploying/index + async-await + + +API Reference +------------- + +If you are looking for information on a specific function, class or +method, this part of the documentation is for you. + +.. toctree:: + :maxdepth: 2 + + api + + +Additional Notes +---------------- + +.. toctree:: + :maxdepth: 2 + + design + extensiondev + contributing + license + changes diff --git a/src/flask-main/docs/installation.rst b/src/flask-main/docs/installation.rst new file mode 100644 index 0000000..aed389e --- /dev/null +++ b/src/flask-main/docs/installation.rst @@ -0,0 +1,144 @@ +Installation +============ + + +Python Version +-------------- + +We recommend using the latest version of Python. Flask supports Python 3.10 and newer. + + +Dependencies +------------ + +These distributions will be installed automatically when installing Flask. + +* `Werkzeug`_ implements WSGI, the standard Python interface between + applications and servers. +* `Jinja`_ is a template language that renders the pages your application + serves. +* `MarkupSafe`_ comes with Jinja. It escapes untrusted input when rendering + templates to avoid injection attacks. +* `ItsDangerous`_ securely signs data to ensure its integrity. This is used + to protect Flask's session cookie. +* `Click`_ is a framework for writing command line applications. It provides + the ``flask`` command and allows adding custom management commands. +* `Blinker`_ provides support for :doc:`signals`. + +.. _Werkzeug: https://palletsprojects.com/p/werkzeug/ +.. _Jinja: https://palletsprojects.com/p/jinja/ +.. _MarkupSafe: https://palletsprojects.com/p/markupsafe/ +.. _ItsDangerous: https://palletsprojects.com/p/itsdangerous/ +.. _Click: https://palletsprojects.com/p/click/ +.. _Blinker: https://blinker.readthedocs.io/ + + +Optional dependencies +~~~~~~~~~~~~~~~~~~~~~ + +These distributions will not be installed automatically. Flask will detect and +use them if you install them. + +* `python-dotenv`_ enables support for :ref:`dotenv` when running ``flask`` + commands. +* `Watchdog`_ provides a faster, more efficient reloader for the development + server. + +.. _python-dotenv: https://github.com/theskumar/python-dotenv#readme +.. _watchdog: https://pythonhosted.org/watchdog/ + + +greenlet +~~~~~~~~ + +You may choose to use gevent or eventlet with your application. In this +case, greenlet>=1.0 is required. When using PyPy, PyPy>=7.3.7 is +required. + +These are not minimum supported versions, they only indicate the first +versions that added necessary features. You should use the latest +versions of each. + + +Virtual environments +-------------------- + +Use a virtual environment to manage the dependencies for your project, both in +development and in production. + +What problem does a virtual environment solve? The more Python projects you +have, the more likely it is that you need to work with different versions of +Python libraries, or even Python itself. Newer versions of libraries for one +project can break compatibility in another project. + +Virtual environments are independent groups of Python libraries, one for each +project. Packages installed for one project will not affect other projects or +the operating system's packages. + +Python comes bundled with the :mod:`venv` module to create virtual +environments. + + +.. _install-create-env: + +Create an environment +~~~~~~~~~~~~~~~~~~~~~ + +Create a project folder and a :file:`.venv` folder within: + +.. tabs:: + + .. group-tab:: macOS/Linux + + .. code-block:: text + + $ mkdir myproject + $ cd myproject + $ python3 -m venv .venv + + .. group-tab:: Windows + + .. code-block:: text + + > mkdir myproject + > cd myproject + > py -3 -m venv .venv + + +.. _install-activate-env: + +Activate the environment +~~~~~~~~~~~~~~~~~~~~~~~~ + +Before you work on your project, activate the corresponding environment: + +.. tabs:: + + .. group-tab:: macOS/Linux + + .. code-block:: text + + $ . .venv/bin/activate + + .. group-tab:: Windows + + .. code-block:: text + + > .venv\Scripts\activate + +Your shell prompt will change to show the name of the activated +environment. + + +Install Flask +------------- + +Within the activated environment, use the following command to install +Flask: + +.. code-block:: sh + + $ pip install Flask + +Flask is now installed. Check out the :doc:`/quickstart` or go to the +:doc:`Documentation Overview `. diff --git a/src/flask-main/docs/license.rst b/src/flask-main/docs/license.rst new file mode 100644 index 0000000..2a445f9 --- /dev/null +++ b/src/flask-main/docs/license.rst @@ -0,0 +1,5 @@ +BSD-3-Clause License +==================== + +.. literalinclude:: ../LICENSE.txt + :language: text diff --git a/src/flask-main/docs/lifecycle.rst b/src/flask-main/docs/lifecycle.rst new file mode 100644 index 0000000..37d45cd --- /dev/null +++ b/src/flask-main/docs/lifecycle.rst @@ -0,0 +1,171 @@ +Application Structure and Lifecycle +=================================== + +Flask makes it pretty easy to write a web application. But there are quite a few +different parts to an application and to each request it handles. Knowing what happens +during application setup, serving, and handling requests will help you know what's +possible in Flask and how to structure your application. + + +Application Setup +----------------- + +The first step in creating a Flask application is creating the application object. Each +Flask application is an instance of the :class:`.Flask` class, which collects all +configuration, extensions, and views. + +.. code-block:: python + + from flask import Flask + + app = Flask(__name__) + app.config.from_mapping( + SECRET_KEY="dev", + ) + app.config.from_prefixed_env() + + @app.route("/") + def index(): + return "Hello, World!" + +This is known as the "application setup phase", it's the code you write that's outside +any view functions or other handlers. It can be split up between different modules and +sub-packages, but all code that you want to be part of your application must be imported +in order for it to be registered. + +All application setup must be completed before you start serving your application and +handling requests. This is because WSGI servers divide work between multiple workers, or +can be distributed across multiple machines. If the configuration changed in one worker, +there's no way for Flask to ensure consistency between other workers. + +Flask tries to help developers catch some of these setup ordering issues by showing an +error if setup-related methods are called after requests are handled. In that case +you'll see this error: + + The setup method 'route' can no longer be called on the application. It has already + handled its first request, any changes will not be applied consistently. + Make sure all imports, decorators, functions, etc. needed to set up the application + are done before running it. + +However, it is not possible for Flask to detect all cases of out-of-order setup. In +general, don't do anything to modify the ``Flask`` app object and ``Blueprint`` objects +from within view functions that run during requests. This includes: + +- Adding routes, view functions, and other request handlers with ``@app.route``, + ``@app.errorhandler``, ``@app.before_request``, etc. +- Registering blueprints. +- Loading configuration with ``app.config``. +- Setting up the Jinja template environment with ``app.jinja_env``. +- Setting a session interface, instead of the default itsdangerous cookie. +- Setting a JSON provider with ``app.json``, instead of the default provider. +- Creating and initializing Flask extensions. + + +Serving the Application +----------------------- + +Flask is a WSGI application framework. The other half of WSGI is the WSGI server. During +development, Flask, through Werkzeug, provides a development WSGI server with the +``flask run`` CLI command. When you are done with development, use a production server +to serve your application, see :doc:`deploying/index`. + +Regardless of what server you're using, it will follow the :pep:`3333` WSGI spec. The +WSGI server will be told how to access your Flask application object, which is the WSGI +application. Then it will start listening for HTTP requests, translate the request data +into a WSGI environ, and call the WSGI application with that data. The WSGI application +will return data that is translated into an HTTP response. + +#. Browser or other client makes HTTP request. +#. WSGI server receives request. +#. WSGI server converts HTTP data to WSGI ``environ`` dict. +#. WSGI server calls WSGI application with the ``environ``. +#. Flask, the WSGI application, does all its internal processing to route the request + to a view function, handle errors, etc. +#. Flask translates View function return into WSGI response data, passes it to WSGI + server. +#. WSGI server creates and send an HTTP response. +#. Client receives the HTTP response. + + +Middleware +~~~~~~~~~~ + +The WSGI application above is a callable that behaves in a certain way. Middleware +is a WSGI application that wraps another WSGI application. It's a similar concept to +Python decorators. The outermost middleware will be called by the server. It can modify +the data passed to it, then call the WSGI application (or further middleware) that it +wraps, and so on. And it can take the return value of that call and modify it further. + +From the WSGI server's perspective, there is one WSGI application, the one it calls +directly. Typically, Flask is the "real" application at the end of the chain of +middleware. But even Flask can call further WSGI applications, although that's an +advanced, uncommon use case. + +A common middleware you'll see used with Flask is Werkzeug's +:class:`~werkzeug.middleware.proxy_fix.ProxyFix`, which modifies the request to look +like it came directly from a client even if it passed through HTTP proxies on the way. +There are other middleware that can handle serving static files, authentication, etc. + + +How a Request is Handled +------------------------ + +For us, the interesting part of the steps above is when Flask gets called by the WSGI +server (or middleware). At that point, it will do quite a lot to handle the request and +generate the response. At the most basic, it will match the URL to a view function, call +the view function, and pass the return value back to the server. But there are many more +parts that you can use to customize its behavior. + +#. WSGI server calls the Flask object, which calls :meth:`.Flask.wsgi_app`. +#. An :class:`.AppContext` object is created. This converts the WSGI ``environ`` + dict into a :class:`.Request` object. +#. The :doc:`app context ` is pushed, which makes + :data:`.current_app`, :data:`.g`, :data:`.request`, and :data:`.session` + available. +#. The :data:`.appcontext_pushed` signal is sent. +#. The URL is matched against the URL rules registered with the :meth:`~.Flask.route` + decorator during application setup. If there is no match, the error - usually a 404, + 405, or redirect - is stored to be handled later. +#. The :data:`.request_started` signal is sent. +#. Any :meth:`~.Flask.url_value_preprocessor` decorated functions are called. +#. Any :meth:`~.Flask.before_request` decorated functions are called. If any of + these function returns a value it is treated as the response immediately. +#. If the URL didn't match a route a few steps ago, that error is raised now. +#. The :meth:`~.Flask.route` decorated view function associated with the matched URL + is called and returns a value to be used as the response. +#. If any step so far raised an exception, and there is an :meth:`~.Flask.errorhandler` + decorated function that matches the exception class or HTTP error code, it is + called to handle the error and return a response. +#. Whatever returned a response value - a before request function, the view, or an + error handler, that value is converted to a :class:`.Response` object. +#. Any :func:`~.after_this_request` decorated functions are called, which can modify + the response object. They are then cleared. +#. Any :meth:`~.Flask.after_request` decorated functions are called, which can modify + the response object. +#. The session is saved, persisting any modified session data using the app's + :attr:`~.Flask.session_interface`. +#. The :data:`.request_finished` signal is sent. +#. If any step so far raised an exception, and it was not handled by an error handler + function, it is handled now. HTTP exceptions are treated as responses with their + corresponding status code, other exceptions are converted to a generic 500 response. + The :data:`.got_request_exception` signal is sent. +#. The response object's status, headers, and body are returned to the WSGI server. +#. Any :meth:`~.Flask.teardown_request` decorated functions are called. +#. The :data:`.request_tearing_down` signal is sent. +#. Any :meth:`~.Flask.teardown_appcontext` decorated functions are called. +#. The :data:`.appcontext_tearing_down` signal is sent. +#. The app context is popped, :data:`.current_app`, :data:`.g`, :data:`.request`, + and :data:`.session` are no longer available. +#. The :data:`.appcontext_popped` signal is sent. + +When executing a CLI command or plain app context without request data, the same +order of steps is followed, omitting the steps that refer to the request. + +A :class:`Blueprint` can add handlers for these events that are specific to the +blueprint. The handlers for a blueprint will run if the blueprint +owns the route that matches the request. + +There are even more decorators and customization points than this, but that aren't part +of every request lifecycle. They're more specific to certain things you might use during +a request, such as templates, building URLs, or handling JSON data. See the rest of this +documentation, as well as the :doc:`api` to explore further. diff --git a/src/flask-main/docs/logging.rst b/src/flask-main/docs/logging.rst new file mode 100644 index 0000000..3958824 --- /dev/null +++ b/src/flask-main/docs/logging.rst @@ -0,0 +1,183 @@ +Logging +======= + +Flask uses standard Python :mod:`logging`. Messages about your Flask +application are logged with :meth:`app.logger `, +which takes the same name as :attr:`app.name `. This +logger can also be used to log your own messages. + +.. code-block:: python + + @app.route('/login', methods=['POST']) + def login(): + user = get_user(request.form['username']) + + if user.check_password(request.form['password']): + login_user(user) + app.logger.info('%s logged in successfully', user.username) + return redirect(url_for('index')) + else: + app.logger.info('%s failed to log in', user.username) + abort(401) + +If you don't configure logging, Python's default log level is usually +'warning'. Nothing below the configured level will be visible. + + +Basic Configuration +------------------- + +When you want to configure logging for your project, you should do it as soon +as possible when the program starts. If :meth:`app.logger ` +is accessed before logging is configured, it will add a default handler. If +possible, configure logging before creating the application object. + +This example uses :func:`~logging.config.dictConfig` to create a logging +configuration similar to Flask's default, except for all logs:: + + from logging.config import dictConfig + + dictConfig({ + 'version': 1, + 'formatters': {'default': { + 'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s', + }}, + 'handlers': {'wsgi': { + 'class': 'logging.StreamHandler', + 'stream': 'ext://flask.logging.wsgi_errors_stream', + 'formatter': 'default' + }}, + 'root': { + 'level': 'INFO', + 'handlers': ['wsgi'] + } + }) + + app = Flask(__name__) + + +Default Configuration +````````````````````` + +If you do not configure logging yourself, Flask will add a +:class:`~logging.StreamHandler` to :meth:`app.logger ` +automatically. During requests, it will write to the stream specified by the +WSGI server in ``environ['wsgi.errors']`` (which is usually +:data:`sys.stderr`). Outside a request, it will log to :data:`sys.stderr`. + + +Removing the Default Handler +```````````````````````````` + +If you configured logging after accessing +:meth:`app.logger `, and need to remove the default +handler, you can import and remove it:: + + from flask.logging import default_handler + + app.logger.removeHandler(default_handler) + + +Email Errors to Admins +---------------------- + +When running the application on a remote server for production, you probably +won't be looking at the log messages very often. The WSGI server will probably +send log messages to a file, and you'll only check that file if a user tells +you something went wrong. + +To be proactive about discovering and fixing bugs, you can configure a +:class:`logging.handlers.SMTPHandler` to send an email when errors and higher +are logged. :: + + import logging + from logging.handlers import SMTPHandler + + mail_handler = SMTPHandler( + mailhost='127.0.0.1', + fromaddr='server-error@example.com', + toaddrs=['admin@example.com'], + subject='Application Error' + ) + mail_handler.setLevel(logging.ERROR) + mail_handler.setFormatter(logging.Formatter( + '[%(asctime)s] %(levelname)s in %(module)s: %(message)s' + )) + + if not app.debug: + app.logger.addHandler(mail_handler) + +This requires that you have an SMTP server set up on the same server. See the +Python docs for more information about configuring the handler. + + +Injecting Request Information +----------------------------- + +Seeing more information about the request, such as the IP address, may help +debugging some errors. You can subclass :class:`logging.Formatter` to inject +your own fields that can be used in messages. You can change the formatter for +Flask's default handler, the mail handler defined above, or any other +handler. :: + + from flask import has_request_context, request + from flask.logging import default_handler + + class RequestFormatter(logging.Formatter): + def format(self, record): + if has_request_context(): + record.url = request.url + record.remote_addr = request.remote_addr + else: + record.url = None + record.remote_addr = None + + return super().format(record) + + formatter = RequestFormatter( + '[%(asctime)s] %(remote_addr)s requested %(url)s\n' + '%(levelname)s in %(module)s: %(message)s' + ) + default_handler.setFormatter(formatter) + mail_handler.setFormatter(formatter) + + +Other Libraries +--------------- + +Other libraries may use logging extensively, and you want to see relevant +messages from those logs too. The simplest way to do this is to add handlers +to the root logger instead of only the app logger. :: + + from flask.logging import default_handler + + root = logging.getLogger() + root.addHandler(default_handler) + root.addHandler(mail_handler) + +Depending on your project, it may be more useful to configure each logger you +care about separately, instead of configuring only the root logger. :: + + for logger in ( + logging.getLogger(app.name), + logging.getLogger('sqlalchemy'), + logging.getLogger('other_package'), + ): + logger.addHandler(default_handler) + logger.addHandler(mail_handler) + + +Werkzeug +```````` + +Werkzeug logs basic request/response information to the ``'werkzeug'`` logger. +If the root logger has no handlers configured, Werkzeug adds a +:class:`~logging.StreamHandler` to its logger. + + +Flask Extensions +```````````````` + +Depending on the situation, an extension may choose to log to +:meth:`app.logger ` or its own named logger. Consult each +extension's documentation for details. diff --git a/src/flask-main/docs/make.bat b/src/flask-main/docs/make.bat new file mode 100644 index 0000000..922152e --- /dev/null +++ b/src/flask-main/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/src/flask-main/docs/patterns/appdispatch.rst b/src/flask-main/docs/patterns/appdispatch.rst new file mode 100644 index 0000000..f22c806 --- /dev/null +++ b/src/flask-main/docs/patterns/appdispatch.rst @@ -0,0 +1,189 @@ +Application Dispatching +======================= + +Application dispatching is the process of combining multiple Flask +applications on the WSGI level. You can combine not only Flask +applications but any WSGI application. This would allow you to run a +Django and a Flask application in the same interpreter side by side if +you want. The usefulness of this depends on how the applications work +internally. + +The fundamental difference from :doc:`packages` is that in this case you +are running the same or different Flask applications that are entirely +isolated from each other. They run different configurations and are +dispatched on the WSGI level. + + +Working with this Document +-------------------------- + +Each of the techniques and examples below results in an ``application`` +object that can be run with any WSGI server. For development, use the +``flask run`` command to start a development server. For production, see +:doc:`/deploying/index`. + +.. code-block:: python + + from flask import Flask + + app = Flask(__name__) + + @app.route('/') + def hello_world(): + return 'Hello World!' + + +Combining Applications +---------------------- + +If you have entirely separated applications and you want them to work next +to each other in the same Python interpreter process you can take +advantage of the :class:`werkzeug.wsgi.DispatcherMiddleware`. The idea +here is that each Flask application is a valid WSGI application and they +are combined by the dispatcher middleware into a larger one that is +dispatched based on prefix. + +For example you could have your main application run on ``/`` and your +backend interface on ``/backend``. + +.. code-block:: python + + from werkzeug.middleware.dispatcher import DispatcherMiddleware + from frontend_app import application as frontend + from backend_app import application as backend + + application = DispatcherMiddleware(frontend, { + '/backend': backend + }) + + +Dispatch by Subdomain +--------------------- + +Sometimes you might want to use multiple instances of the same application +with different configurations. Assuming the application is created inside +a function and you can call that function to instantiate it, that is +really easy to implement. In order to develop your application to support +creating new instances in functions have a look at the +:doc:`appfactories` pattern. + +A very common example would be creating applications per subdomain. For +instance you configure your webserver to dispatch all requests for all +subdomains to your application and you then use the subdomain information +to create user-specific instances. Once you have your server set up to +listen on all subdomains you can use a very simple WSGI application to do +the dynamic application creation. + +The perfect level for abstraction in that regard is the WSGI layer. You +write your own WSGI application that looks at the request that comes and +delegates it to your Flask application. If that application does not +exist yet, it is dynamically created and remembered. + +.. code-block:: python + + from threading import Lock + + class SubdomainDispatcher: + + def __init__(self, domain, create_app): + self.domain = domain + self.create_app = create_app + self.lock = Lock() + self.instances = {} + + def get_application(self, host): + host = host.split(':')[0] + assert host.endswith(self.domain), 'Configuration error' + subdomain = host[:-len(self.domain)].rstrip('.') + with self.lock: + app = self.instances.get(subdomain) + if app is None: + app = self.create_app(subdomain) + self.instances[subdomain] = app + return app + + def __call__(self, environ, start_response): + app = self.get_application(environ['HTTP_HOST']) + return app(environ, start_response) + + +This dispatcher can then be used like this: + +.. code-block:: python + + from myapplication import create_app, get_user_for_subdomain + from werkzeug.exceptions import NotFound + + def make_app(subdomain): + user = get_user_for_subdomain(subdomain) + if user is None: + # if there is no user for that subdomain we still have + # to return a WSGI application that handles that request. + # We can then just return the NotFound() exception as + # application which will render a default 404 page. + # You might also redirect the user to the main page then + return NotFound() + + # otherwise create the application for the specific user + return create_app(user) + + application = SubdomainDispatcher('example.com', make_app) + + +Dispatch by Path +---------------- + +Dispatching by a path on the URL is very similar. Instead of looking at +the ``Host`` header to figure out the subdomain one simply looks at the +request path up to the first slash. + +.. code-block:: python + + from threading import Lock + from wsgiref.util import shift_path_info + + class PathDispatcher: + + def __init__(self, default_app, create_app): + self.default_app = default_app + self.create_app = create_app + self.lock = Lock() + self.instances = {} + + def get_application(self, prefix): + with self.lock: + app = self.instances.get(prefix) + if app is None: + app = self.create_app(prefix) + if app is not None: + self.instances[prefix] = app + return app + + def __call__(self, environ, start_response): + app = self.get_application(_peek_path_info(environ)) + if app is not None: + shift_path_info(environ) + else: + app = self.default_app + return app(environ, start_response) + + def _peek_path_info(environ): + segments = environ.get("PATH_INFO", "").lstrip("/").split("/", 1) + if segments: + return segments[0] + + return None + +The big difference between this and the subdomain one is that this one +falls back to another application if the creator function returns ``None``. + +.. code-block:: python + + from myapplication import create_app, default_app, get_user_for_prefix + + def make_app(prefix): + user = get_user_for_prefix(prefix) + if user is not None: + return create_app(user) + + application = PathDispatcher(default_app, make_app) diff --git a/src/flask-main/docs/patterns/appfactories.rst b/src/flask-main/docs/patterns/appfactories.rst new file mode 100644 index 0000000..0f24878 --- /dev/null +++ b/src/flask-main/docs/patterns/appfactories.rst @@ -0,0 +1,118 @@ +Application Factories +===================== + +If you are already using packages and blueprints for your application +(:doc:`/blueprints`) there are a couple of really nice ways to further improve +the experience. A common pattern is creating the application object when +the blueprint is imported. But if you move the creation of this object +into a function, you can then create multiple instances of this app later. + +So why would you want to do this? + +1. Testing. You can have instances of the application with different + settings to test every case. +2. Multiple instances. Imagine you want to run different versions of the + same application. Of course you could have multiple instances with + different configs set up in your webserver, but if you use factories, + you can have multiple instances of the same application running in the + same application process which can be handy. + +So how would you then actually implement that? + +Basic Factories +--------------- + +The idea is to set up the application in a function. Like this:: + + def create_app(config_filename): + app = Flask(__name__) + app.config.from_pyfile(config_filename) + + from yourapplication.model import db + db.init_app(app) + + from yourapplication.views.admin import admin + from yourapplication.views.frontend import frontend + app.register_blueprint(admin) + app.register_blueprint(frontend) + + return app + +The downside is that you cannot use the application object in the blueprints +at import time. You can however use it from within a request. How do you +get access to the application with the config? Use +:data:`~flask.current_app`:: + + from flask import current_app, Blueprint, render_template + admin = Blueprint('admin', __name__, url_prefix='/admin') + + @admin.route('/') + def index(): + return render_template(current_app.config['INDEX_TEMPLATE']) + +Here we look up the name of a template in the config. + +Factories & Extensions +---------------------- + +It's preferable to create your extensions and app factories so that the +extension object does not initially get bound to the application. + +Using `Flask-SQLAlchemy `_, +as an example, you should not do something along those lines:: + + def create_app(config_filename): + app = Flask(__name__) + app.config.from_pyfile(config_filename) + + db = SQLAlchemy(app) + +But, rather, in model.py (or equivalent):: + + db = SQLAlchemy() + +and in your application.py (or equivalent):: + + def create_app(config_filename): + app = Flask(__name__) + app.config.from_pyfile(config_filename) + + from yourapplication.model import db + db.init_app(app) + +Using this design pattern, no application-specific state is stored on the +extension object, so one extension object can be used for multiple apps. +For more information about the design of extensions refer to :doc:`/extensiondev`. + +Using Applications +------------------ + +To run such an application, you can use the :command:`flask` command: + +.. code-block:: text + + $ flask --app hello run + +Flask will automatically detect the factory if it is named +``create_app`` or ``make_app`` in ``hello``. You can also pass arguments +to the factory like this: + +.. code-block:: text + + $ flask --app 'hello:create_app(local_auth=True)' run + +Then the ``create_app`` factory in ``hello`` is called with the keyword +argument ``local_auth=True``. See :doc:`/cli` for more detail. + +Factory Improvements +-------------------- + +The factory function above is not very clever, but you can improve it. +The following changes are straightforward to implement: + +1. Make it possible to pass in configuration values for unit tests so that + you don't have to create config files on the filesystem. +2. Call a function from a blueprint when the application is setting up so + that you have a place to modify attributes of the application (like + hooking in before/after request handlers etc.) +3. Add in WSGI middlewares when the application is being created if necessary. diff --git a/src/flask-main/docs/patterns/caching.rst b/src/flask-main/docs/patterns/caching.rst new file mode 100644 index 0000000..9bf7b72 --- /dev/null +++ b/src/flask-main/docs/patterns/caching.rst @@ -0,0 +1,16 @@ +Caching +======= + +When your application runs slow, throw some caches in. Well, at least +it's the easiest way to speed up things. What does a cache do? Say you +have a function that takes some time to complete but the results would +still be good enough if they were 5 minutes old. So then the idea is that +you actually put the result of that calculation into a cache for some +time. + +Flask itself does not provide caching for you, but `Flask-Caching`_, an +extension for Flask does. Flask-Caching supports various backends, and it is +even possible to develop your own caching backend. + + +.. _Flask-Caching: https://flask-caching.readthedocs.io/en/latest/ diff --git a/src/flask-main/docs/patterns/celery.rst b/src/flask-main/docs/patterns/celery.rst new file mode 100644 index 0000000..2e9a43a --- /dev/null +++ b/src/flask-main/docs/patterns/celery.rst @@ -0,0 +1,242 @@ +Background Tasks with Celery +============================ + +If your application has a long running task, such as processing some uploaded data or +sending email, you don't want to wait for it to finish during a request. Instead, use a +task queue to send the necessary data to another process that will run the task in the +background while the request returns immediately. + +`Celery`_ is a powerful task queue that can be used for simple background tasks as well +as complex multi-stage programs and schedules. This guide will show you how to configure +Celery using Flask. Read Celery's `First Steps with Celery`_ guide to learn how to use +Celery itself. + +.. _Celery: https://celery.readthedocs.io +.. _First Steps with Celery: https://celery.readthedocs.io/en/latest/getting-started/first-steps-with-celery.html + +The Flask repository contains `an example `_ +based on the information on this page, which also shows how to use JavaScript to submit +tasks and poll for progress and results. + + +Install +------- + +Install Celery from PyPI, for example using pip: + +.. code-block:: text + + $ pip install celery + + +Integrate Celery with Flask +--------------------------- + +You can use Celery without any integration with Flask, but it's convenient to configure +it through Flask's config, and to let tasks access the Flask application. + +Celery uses similar ideas to Flask, with a ``Celery`` app object that has configuration +and registers tasks. While creating a Flask app, use the following code to create and +configure a Celery app as well. + +.. code-block:: python + + from celery import Celery, Task + + def celery_init_app(app: Flask) -> Celery: + class FlaskTask(Task): + def __call__(self, *args: object, **kwargs: object) -> object: + with app.app_context(): + return self.run(*args, **kwargs) + + celery_app = Celery(app.name, task_cls=FlaskTask) + celery_app.config_from_object(app.config["CELERY"]) + celery_app.set_default() + app.extensions["celery"] = celery_app + return celery_app + +This creates and returns a ``Celery`` app object. Celery `configuration`_ is taken from +the ``CELERY`` key in the Flask configuration. The Celery app is set as the default, so +that it is seen during each request. The ``Task`` subclass automatically runs task +functions with a Flask app context active, so that services like your database +connections are available. + +.. _configuration: https://celery.readthedocs.io/en/stable/userguide/configuration.html + +Here's a basic ``example.py`` that configures Celery to use Redis for communication. We +enable a result backend, but ignore results by default. This allows us to store results +only for tasks where we care about the result. + +.. code-block:: python + + from flask import Flask + + app = Flask(__name__) + app.config.from_mapping( + CELERY=dict( + broker_url="redis://localhost", + result_backend="redis://localhost", + task_ignore_result=True, + ), + ) + celery_app = celery_init_app(app) + +Point the ``celery worker`` command at this and it will find the ``celery_app`` object. + +.. code-block:: text + + $ celery -A example worker --loglevel INFO + +You can also run the ``celery beat`` command to run tasks on a schedule. See Celery's +docs for more information about defining schedules. + +.. code-block:: text + + $ celery -A example beat --loglevel INFO + + +Application Factory +------------------- + +When using the Flask application factory pattern, call the ``celery_init_app`` function +inside the factory. It sets ``app.extensions["celery"]`` to the Celery app object, which +can be used to get the Celery app from the Flask app returned by the factory. + +.. code-block:: python + + def create_app() -> Flask: + app = Flask(__name__) + app.config.from_mapping( + CELERY=dict( + broker_url="redis://localhost", + result_backend="redis://localhost", + task_ignore_result=True, + ), + ) + app.config.from_prefixed_env() + celery_init_app(app) + return app + +To use ``celery`` commands, Celery needs an app object, but that's no longer directly +available. Create a ``make_celery.py`` file that calls the Flask app factory and gets +the Celery app from the returned Flask app. + +.. code-block:: python + + from example import create_app + + flask_app = create_app() + celery_app = flask_app.extensions["celery"] + +Point the ``celery`` command to this file. + +.. code-block:: text + + $ celery -A make_celery worker --loglevel INFO + $ celery -A make_celery beat --loglevel INFO + + +Defining Tasks +-------------- + +Using ``@celery_app.task`` to decorate task functions requires access to the +``celery_app`` object, which won't be available when using the factory pattern. It also +means that the decorated tasks are tied to the specific Flask and Celery app instances, +which could be an issue during testing if you change configuration for a test. + +Instead, use Celery's ``@shared_task`` decorator. This creates task objects that will +access whatever the "current app" is, which is a similar concept to Flask's blueprints +and app context. This is why we called ``celery_app.set_default()`` above. + +Here's an example task that adds two numbers together and returns the result. + +.. code-block:: python + + from celery import shared_task + + @shared_task(ignore_result=False) + def add_together(a: int, b: int) -> int: + return a + b + +Earlier, we configured Celery to ignore task results by default. Since we want to know +the return value of this task, we set ``ignore_result=False``. On the other hand, a task +that didn't need a result, such as sending an email, wouldn't set this. + + +Calling Tasks +------------- + +The decorated function becomes a task object with methods to call it in the background. +The simplest way is to use the ``delay(*args, **kwargs)`` method. See Celery's docs for +more methods. + +A Celery worker must be running to run the task. Starting a worker is shown in the +previous sections. + +.. code-block:: python + + from flask import request + + @app.post("/add") + def start_add() -> dict[str, object]: + a = request.form.get("a", type=int) + b = request.form.get("b", type=int) + result = add_together.delay(a, b) + return {"result_id": result.id} + +The route doesn't get the task's result immediately. That would defeat the purpose by +blocking the response. Instead, we return the running task's result id, which we can use +later to get the result. + + +Getting Results +--------------- + +To fetch the result of the task we started above, we'll add another route that takes the +result id we returned before. We return whether the task is finished (ready), whether it +finished successfully, and what the return value (or error) was if it is finished. + +.. code-block:: python + + from celery.result import AsyncResult + + @app.get("/result/") + def task_result(id: str) -> dict[str, object]: + result = AsyncResult(id) + return { + "ready": result.ready(), + "successful": result.successful(), + "value": result.result if result.ready() else None, + } + +Now you can start the task using the first route, then poll for the result using the +second route. This keeps the Flask request workers from being blocked waiting for tasks +to finish. + +The Flask repository contains `an example `_ +using JavaScript to submit tasks and poll for progress and results. + + +Passing Data to Tasks +--------------------- + +The "add" task above took two integers as arguments. To pass arguments to tasks, Celery +has to serialize them to a format that it can pass to other processes. Therefore, +passing complex objects is not recommended. For example, it would be impossible to pass +a SQLAlchemy model object, since that object is probably not serializable and is tied to +the session that queried it. + +Pass the minimal amount of data necessary to fetch or recreate any complex data within +the task. Consider a task that will run when the logged in user asks for an archive of +their data. The Flask request knows the logged in user, and has the user object queried +from the database. It got that by querying the database for a given id, so the task can +do the same thing. Pass the user's id rather than the user object. + +.. code-block:: python + + @shared_task + def generate_user_archive(user_id: str) -> None: + user = db.session.get(User, user_id) + ... + + generate_user_archive.delay(current_user.id) diff --git a/src/flask-main/docs/patterns/deferredcallbacks.rst b/src/flask-main/docs/patterns/deferredcallbacks.rst new file mode 100644 index 0000000..4ff8814 --- /dev/null +++ b/src/flask-main/docs/patterns/deferredcallbacks.rst @@ -0,0 +1,44 @@ +Deferred Request Callbacks +========================== + +One of the design principles of Flask is that response objects are created and +passed down a chain of potential callbacks that can modify them or replace +them. When the request handling starts, there is no response object yet. It is +created as necessary either by a view function or by some other component in +the system. + +What happens if you want to modify the response at a point where the response +does not exist yet? A common example for that would be a +:meth:`~flask.Flask.before_request` callback that wants to set a cookie on the +response object. + +One way is to avoid the situation. Very often that is possible. For instance +you can try to move that logic into a :meth:`~flask.Flask.after_request` +callback instead. However, sometimes moving code there makes it +more complicated or awkward to reason about. + +As an alternative, you can use :func:`~flask.after_this_request` to register +callbacks that will execute after only the current request. This way you can +defer code execution from anywhere in the application, based on the current +request. + +At any time during a request, we can register a function to be called at the +end of the request. For example you can remember the current language of the +user in a cookie in a :meth:`~flask.Flask.before_request` callback:: + + from flask import request, after_this_request + + @app.before_request + def detect_user_language(): + language = request.cookies.get('user_lang') + + if language is None: + language = guess_language_from_request() + + # when the response exists, set a cookie with the language + @after_this_request + def remember_language(response): + response.set_cookie('user_lang', language) + return response + + g.language = language diff --git a/src/flask-main/docs/patterns/favicon.rst b/src/flask-main/docs/patterns/favicon.rst new file mode 100644 index 0000000..b867854 --- /dev/null +++ b/src/flask-main/docs/patterns/favicon.rst @@ -0,0 +1,56 @@ +Adding a favicon +================ + +A "favicon" is an icon used by browsers for tabs and bookmarks. This helps +to distinguish your website and to give it a unique brand. + +A common question is how to add a favicon to a Flask application. First, of +course, you need an icon. It should be 16 × 16 pixels and in the ICO file +format. This is not a requirement but a de-facto standard supported by all +relevant browsers. Put the icon in your static directory as +:file:`favicon.ico`. + +Now, to get browsers to find your icon, the correct way is to add a link +tag in your HTML. So, for example: + +.. sourcecode:: html+jinja + + + +That's all you need for most browsers, however some really old ones do not +support this standard. The old de-facto standard is to serve this file, +with this name, at the website root. If your application is not mounted at +the root path of the domain you either need to configure the web server to +serve the icon at the root or if you can't do that you're out of luck. If +however your application is the root you can simply route a redirect:: + + app.add_url_rule( + "/favicon.ico", + endpoint="favicon", + redirect_to=url_for("static", filename="favicon.ico"), + ) + +If you want to save the extra redirect request you can also write a view +using :func:`~flask.send_from_directory`:: + + import os + from flask import send_from_directory + + @app.route('/favicon.ico') + def favicon(): + return send_from_directory(os.path.join(app.root_path, 'static'), + 'favicon.ico', mimetype='image/vnd.microsoft.icon') + +We can leave out the explicit mimetype and it will be guessed, but we may +as well specify it to avoid the extra guessing, as it will always be the +same. + +The above will serve the icon via your application and if possible it's +better to configure your dedicated web server to serve it; refer to the +web server's documentation. + +See also +-------- + +* The `Favicon `_ article on + Wikipedia diff --git a/src/flask-main/docs/patterns/fileuploads.rst b/src/flask-main/docs/patterns/fileuploads.rst new file mode 100644 index 0000000..304f57d --- /dev/null +++ b/src/flask-main/docs/patterns/fileuploads.rst @@ -0,0 +1,182 @@ +Uploading Files +=============== + +Ah yes, the good old problem of file uploads. The basic idea of file +uploads is actually quite simple. It basically works like this: + +1. A ``

`` tag is marked with ``enctype=multipart/form-data`` + and an ```` is placed in that form. +2. The application accesses the file from the :attr:`~flask.request.files` + dictionary on the request object. +3. use the :meth:`~werkzeug.datastructures.FileStorage.save` method of the file to save + the file permanently somewhere on the filesystem. + +A Gentle Introduction +--------------------- + +Let's start with a very basic application that uploads a file to a +specific upload folder and displays a file to the user. Let's look at the +bootstrapping code for our application:: + + import os + from flask import Flask, flash, request, redirect, url_for + from werkzeug.utils import secure_filename + + UPLOAD_FOLDER = '/path/to/the/uploads' + ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'} + + app = Flask(__name__) + app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER + +So first we need a couple of imports. Most should be straightforward, the +:func:`werkzeug.secure_filename` is explained a little bit later. The +``UPLOAD_FOLDER`` is where we will store the uploaded files and the +``ALLOWED_EXTENSIONS`` is the set of allowed file extensions. + +Why do we limit the extensions that are allowed? You probably don't want +your users to be able to upload everything there if the server is directly +sending out the data to the client. That way you can make sure that users +are not able to upload HTML files that would cause XSS problems (see +:ref:`security-xss`). Also make sure to disallow ``.php`` files if the server +executes them, but who has PHP installed on their server, right? :) + +Next the functions that check if an extension is valid and that uploads +the file and redirects the user to the URL for the uploaded file:: + + def allowed_file(filename): + return '.' in filename and \ + filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS + + @app.route('/', methods=['GET', 'POST']) + def upload_file(): + if request.method == 'POST': + # check if the post request has the file part + if 'file' not in request.files: + flash('No file part') + return redirect(request.url) + file = request.files['file'] + # If the user does not select a file, the browser submits an + # empty file without a filename. + if file.filename == '': + flash('No selected file') + return redirect(request.url) + if file and allowed_file(file.filename): + filename = secure_filename(file.filename) + file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) + return redirect(url_for('download_file', name=filename)) + return ''' + + Upload new File +

Upload new File

+ + + +
+ ''' + +So what does that :func:`~werkzeug.utils.secure_filename` function actually do? +Now the problem is that there is that principle called "never trust user +input". This is also true for the filename of an uploaded file. All +submitted form data can be forged, and filenames can be dangerous. For +the moment just remember: always use that function to secure a filename +before storing it directly on the filesystem. + +.. admonition:: Information for the Pros + + So you're interested in what that :func:`~werkzeug.utils.secure_filename` + function does and what the problem is if you're not using it? So just + imagine someone would send the following information as `filename` to + your application:: + + filename = "../../../../home/username/.bashrc" + + Assuming the number of ``../`` is correct and you would join this with + the ``UPLOAD_FOLDER`` the user might have the ability to modify a file on + the server's filesystem he or she should not modify. This does require some + knowledge about how the application looks like, but trust me, hackers + are patient :) + + Now let's look how that function works: + + >>> secure_filename('../../../../home/username/.bashrc') + 'home_username_.bashrc' + +We want to be able to serve the uploaded files so they can be downloaded +by users. We'll define a ``download_file`` view to serve files in the +upload folder by name. ``url_for("download_file", name=name)`` generates +download URLs. + +.. code-block:: python + + from flask import send_from_directory + + @app.route('/uploads/') + def download_file(name): + return send_from_directory(app.config["UPLOAD_FOLDER"], name) + +If you're using middleware or the HTTP server to serve files, you can +register the ``download_file`` endpoint as ``build_only`` so ``url_for`` +will work without a view function. + +.. code-block:: python + + app.add_url_rule( + "/uploads/", endpoint="download_file", build_only=True + ) + + +Improving Uploads +----------------- + +.. versionadded:: 0.6 + +So how exactly does Flask handle uploads? Well it will store them in the +webserver's memory if the files are reasonably small, otherwise in a +temporary location (as returned by :func:`tempfile.gettempdir`). But how +do you specify the maximum file size after which an upload is aborted? By +default Flask will happily accept file uploads with an unlimited amount of +memory, but you can limit that by setting the ``MAX_CONTENT_LENGTH`` +config key:: + + from flask import Flask, Request + + app = Flask(__name__) + app.config['MAX_CONTENT_LENGTH'] = 16 * 1000 * 1000 + +The code above will limit the maximum allowed payload to 16 megabytes. +If a larger file is transmitted, Flask will raise a +:exc:`~werkzeug.exceptions.RequestEntityTooLarge` exception. + +.. admonition:: Connection Reset Issue + + When using the local development server, you may get a connection + reset error instead of a 413 response. You will get the correct + status response when running the app with a production WSGI server. + +This feature was added in Flask 0.6 but can be achieved in older versions +as well by subclassing the request object. For more information on that +consult the Werkzeug documentation on file handling. + + +Upload Progress Bars +-------------------- + +A while ago many developers had the idea to read the incoming file in +small chunks and store the upload progress in the database to be able to +poll the progress with JavaScript from the client. The client asks the +server every 5 seconds how much it has transmitted, but this is +something it should already know. + +An Easier Solution +------------------ + +Now there are better solutions that work faster and are more reliable. There +are JavaScript libraries like jQuery_ that have form plugins to ease the +construction of progress bar. + +Because the common pattern for file uploads exists almost unchanged in all +applications dealing with uploads, there are also some Flask extensions that +implement a full fledged upload mechanism that allows controlling which +file extensions are allowed to be uploaded. + +.. _jQuery: https://jquery.com/ diff --git a/src/flask-main/docs/patterns/flashing.rst b/src/flask-main/docs/patterns/flashing.rst new file mode 100644 index 0000000..8eb6b3a --- /dev/null +++ b/src/flask-main/docs/patterns/flashing.rst @@ -0,0 +1,148 @@ +Message Flashing +================ + +Good applications and user interfaces are all about feedback. If the user +does not get enough feedback they will probably end up hating the +application. Flask provides a really simple way to give feedback to a +user with the flashing system. The flashing system basically makes it +possible to record a message at the end of a request and access it next +request and only next request. This is usually combined with a layout +template that does this. Note that browsers and sometimes web servers enforce +a limit on cookie sizes. This means that flashing messages that are too +large for session cookies causes message flashing to fail silently. + +Simple Flashing +--------------- + +So here is a full example:: + + from flask import Flask, flash, redirect, render_template, \ + request, url_for + + app = Flask(__name__) + app.secret_key = b'_5#y2L"F4Q8z\n\xec]/' + + @app.route('/') + def index(): + return render_template('index.html') + + @app.route('/login', methods=['GET', 'POST']) + def login(): + error = None + if request.method == 'POST': + if request.form['username'] != 'admin' or \ + request.form['password'] != 'secret': + error = 'Invalid credentials' + else: + flash('You were successfully logged in') + return redirect(url_for('index')) + return render_template('login.html', error=error) + +And here is the :file:`layout.html` template which does the magic: + +.. sourcecode:: html+jinja + + + My Application + {% with messages = get_flashed_messages() %} + {% if messages %} +
    + {% for message in messages %} +
  • {{ message }}
  • + {% endfor %} +
+ {% endif %} + {% endwith %} + {% block body %}{% endblock %} + +Here is the :file:`index.html` template which inherits from :file:`layout.html`: + +.. sourcecode:: html+jinja + + {% extends "layout.html" %} + {% block body %} +

Overview

+

Do you want to log in? + {% endblock %} + +And here is the :file:`login.html` template which also inherits from +:file:`layout.html`: + +.. sourcecode:: html+jinja + + {% extends "layout.html" %} + {% block body %} +

Login

+ {% if error %} +

Error: {{ error }} + {% endif %} +

+
+
Username: +
+
Password: +
+
+

+

+ {% endblock %} + +Flashing With Categories +------------------------ + +.. versionadded:: 0.3 + +It is also possible to provide categories when flashing a message. The +default category if nothing is provided is ``'message'``. Alternative +categories can be used to give the user better feedback. For example +error messages could be displayed with a red background. + +To flash a message with a different category, just use the second argument +to the :func:`~flask.flash` function:: + + flash('Invalid password provided', 'error') + +Inside the template you then have to tell the +:func:`~flask.get_flashed_messages` function to also return the +categories. The loop looks slightly different in that situation then: + +.. sourcecode:: html+jinja + + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} +
    + {% for category, message in messages %} +
  • {{ message }}
  • + {% endfor %} +
+ {% endif %} + {% endwith %} + +This is just one example of how to render these flashed messages. One +might also use the category to add a prefix such as +``Error:`` to the message. + +Filtering Flash Messages +------------------------ + +.. versionadded:: 0.9 + +Optionally you can pass a list of categories which filters the results of +:func:`~flask.get_flashed_messages`. This is useful if you wish to +render each category in a separate block. + +.. sourcecode:: html+jinja + + {% with errors = get_flashed_messages(category_filter=["error"]) %} + {% if errors %} +
+ × +
    + {%- for msg in errors %} +
  • {{ msg }}
  • + {% endfor -%} +
+
+ {% endif %} + {% endwith %} diff --git a/src/flask-main/docs/patterns/index.rst b/src/flask-main/docs/patterns/index.rst new file mode 100644 index 0000000..1f2c07d --- /dev/null +++ b/src/flask-main/docs/patterns/index.rst @@ -0,0 +1,40 @@ +Patterns for Flask +================== + +Certain features and interactions are common enough that you will find +them in most web applications. For example, many applications use a +relational database and user authentication. They will open a database +connection at the beginning of the request and get the information for +the logged in user. At the end of the request, the database connection +is closed. + +These types of patterns may be a bit outside the scope of Flask itself, +but Flask makes it easy to implement them. Some common patterns are +collected in the following pages. + +.. toctree:: + :maxdepth: 2 + + packages + appfactories + appdispatch + urlprocessors + sqlite3 + sqlalchemy + fileuploads + caching + viewdecorators + wtforms + templateinheritance + flashing + javascript + lazyloading + mongoengine + favicon + streaming + deferredcallbacks + methodoverrides + requestchecksum + celery + subclassing + singlepageapplications diff --git a/src/flask-main/docs/patterns/javascript.rst b/src/flask-main/docs/patterns/javascript.rst new file mode 100644 index 0000000..d58a3eb --- /dev/null +++ b/src/flask-main/docs/patterns/javascript.rst @@ -0,0 +1,259 @@ +JavaScript, ``fetch``, and JSON +=============================== + +You may want to make your HTML page dynamic, by changing data without +reloading the entire page. Instead of submitting an HTML ``
`` and +performing a redirect to re-render the template, you can add +`JavaScript`_ that calls |fetch|_ and replaces content on the page. + +|fetch|_ is the modern, built-in JavaScript solution to making +requests from a page. You may have heard of other "AJAX" methods and +libraries, such as |XHR|_ or `jQuery`_. These are no longer needed in +modern browsers, although you may choose to use them or another library +depending on your application's requirements. These docs will only focus +on built-in JavaScript features. + +.. _JavaScript: https://developer.mozilla.org/Web/JavaScript +.. |fetch| replace:: ``fetch()`` +.. _fetch: https://developer.mozilla.org/Web/API/Fetch_API +.. |XHR| replace:: ``XMLHttpRequest()`` +.. _XHR: https://developer.mozilla.org/Web/API/XMLHttpRequest +.. _jQuery: https://jquery.com/ + + +Rendering Templates +------------------- + +It is important to understand the difference between templates and +JavaScript. Templates are rendered on the server, before the response is +sent to the user's browser. JavaScript runs in the user's browser, after +the template is rendered and sent. Therefore, it is impossible to use +JavaScript to affect how the Jinja template is rendered, but it is +possible to render data into the JavaScript that will run. + +To provide data to JavaScript when rendering the template, use the +:func:`~jinja-filters.tojson` filter in a `` + +A less common pattern is to add the data to a ``data-`` attribute on an +HTML tag. In this case, you must use single quotes around the value, not +double quotes, otherwise you will produce invalid or unsafe HTML. + +.. code-block:: jinja + +
+ + +Generating URLs +--------------- + +The other way to get data from the server to JavaScript is to make a +request for it. First, you need to know the URL to request. + +The simplest way to generate URLs is to continue to use +:func:`~flask.url_for` when rendering the template. For example: + +.. code-block:: javascript + + const user_url = {{ url_for("user", id=current_user.id)|tojson }} + fetch(user_url).then(...) + +However, you might need to generate a URL based on information you only +know in JavaScript. As discussed above, JavaScript runs in the user's +browser, not as part of the template rendering, so you can't use +``url_for`` at that point. + +In this case, you need to know the "root URL" under which your +application is served. In simple setups, this is ``/``, but it might +also be something else, like ``https://example.com/myapp/``. + +A simple way to tell your JavaScript code about this root is to set it +as a global variable when rendering the template. Then you can use it +when generating URLs from JavaScript. + +.. code-block:: javascript + + const SCRIPT_ROOT = {{ request.script_root|tojson }} + let user_id = ... // do something to get a user id from the page + let user_url = `${SCRIPT_ROOT}/user/${user_id}` + fetch(user_url).then(...) + + +Making a Request with ``fetch`` +------------------------------- + +|fetch|_ takes two arguments, a URL and an object with other options, +and returns a |Promise|_. We won't cover all the available options, and +will only use ``then()`` on the promise, not other callbacks or +``await`` syntax. Read the linked MDN docs for more information about +those features. + +By default, the GET method is used. If the response contains JSON, it +can be used with a ``then()`` callback chain. + +.. code-block:: javascript + + const room_url = {{ url_for("room_detail", id=room.id)|tojson }} + fetch(room_url) + .then(response => response.json()) + .then(data => { + // data is a parsed JSON object + }) + +To send data, use a data method such as POST, and pass the ``body`` +option. The most common types for data are form data or JSON data. + +To send form data, pass a populated |FormData|_ object. This uses the +same format as an HTML form, and would be accessed with ``request.form`` +in a Flask view. + +.. code-block:: javascript + + let data = new FormData() + data.append("name", "Flask Room") + data.append("description", "Talk about Flask here.") + fetch(room_url, { + "method": "POST", + "body": data, + }).then(...) + +In general, prefer sending request data as form data, as would be used +when submitting an HTML form. JSON can represent more complex data, but +unless you need that it's better to stick with the simpler format. When +sending JSON data, the ``Content-Type: application/json`` header must be +sent as well, otherwise Flask will return a 400 error. + +.. code-block:: javascript + + let data = { + "name": "Flask Room", + "description": "Talk about Flask here.", + } + fetch(room_url, { + "method": "POST", + "headers": {"Content-Type": "application/json"}, + "body": JSON.stringify(data), + }).then(...) + +.. |Promise| replace:: ``Promise`` +.. _Promise: https://developer.mozilla.org/Web/JavaScript/Reference/Global_Objects/Promise +.. |FormData| replace:: ``FormData`` +.. _FormData: https://developer.mozilla.org/en-US/docs/Web/API/FormData + + +Following Redirects +------------------- + +A response might be a redirect, for example if you logged in with +JavaScript instead of a traditional HTML form, and your view returned +a redirect instead of JSON. JavaScript requests do follow redirects, but +they don't change the page. If you want to make the page change you can +inspect the response and apply the redirect manually. + +.. code-block:: javascript + + fetch("/login", {"body": ...}).then( + response => { + if (response.redirected) { + window.location = response.url + } else { + showLoginError() + } + } + ) + + +Replacing Content +----------------- + +A response might be new HTML, either a new section of the page to add or +replace, or an entirely new page. In general, if you're returning the +entire page, it would be better to handle that with a redirect as shown +in the previous section. The following example shows how to replace a +``
`` with the HTML returned by a request. + +.. code-block:: html + +
+ {{ include "geology_fact.html" }} +
+ + + +Return JSON from Views +---------------------- + +To return a JSON object from your API view, you can directly return a +dict from the view. It will be serialized to JSON automatically. + +.. code-block:: python + + @app.route("/user/") + def user_detail(id): + user = User.query.get_or_404(id) + return { + "username": User.username, + "email": User.email, + "picture": url_for("static", filename=f"users/{id}/profile.png"), + } + +If you want to return another JSON type, use the +:func:`~flask.json.jsonify` function, which creates a response object +with the given data serialized to JSON. + +.. code-block:: python + + from flask import jsonify + + @app.route("/users") + def user_list(): + users = User.query.order_by(User.name).all() + return jsonify([u.to_json() for u in users]) + +It is usually not a good idea to return file data in a JSON response. +JSON cannot represent binary data directly, so it must be base64 +encoded, which can be slow, takes more bandwidth to send, and is not as +easy to cache. Instead, serve files using one view, and generate a URL +to the desired file to include in the JSON. Then the client can make a +separate request to get the linked resource after getting the JSON. + + +Receiving JSON in Views +----------------------- + +Use the :attr:`~flask.Request.json` property of the +:data:`~flask.request` object to decode the request's body as JSON. If +the body is not valid JSON, or the ``Content-Type`` header is not set to +``application/json``, a 400 Bad Request error will be raised. + +.. code-block:: python + + from flask import request + + @app.post("/user/") + def user_update(id): + user = User.query.get_or_404(id) + user.update_from_json(request.json) + db.session.commit() + return user.to_json() diff --git a/src/flask-main/docs/patterns/jquery.rst b/src/flask-main/docs/patterns/jquery.rst new file mode 100644 index 0000000..7ac6856 --- /dev/null +++ b/src/flask-main/docs/patterns/jquery.rst @@ -0,0 +1,6 @@ +:orphan: + +AJAX with jQuery +================ + +Obsolete, see :doc:`/patterns/javascript` instead. diff --git a/src/flask-main/docs/patterns/lazyloading.rst b/src/flask-main/docs/patterns/lazyloading.rst new file mode 100644 index 0000000..658a1cd --- /dev/null +++ b/src/flask-main/docs/patterns/lazyloading.rst @@ -0,0 +1,109 @@ +Lazily Loading Views +==================== + +Flask is usually used with the decorators. Decorators are simple and you +have the URL right next to the function that is called for that specific +URL. However there is a downside to this approach: it means all your code +that uses decorators has to be imported upfront or Flask will never +actually find your function. + +This can be a problem if your application has to import quick. It might +have to do that on systems like Google's App Engine or other systems. So +if you suddenly notice that your application outgrows this approach you +can fall back to a centralized URL mapping. + +The system that enables having a central URL map is the +:meth:`~flask.Flask.add_url_rule` function. Instead of using decorators, +you have a file that sets up the application with all URLs. + +Converting to Centralized URL Map +--------------------------------- + +Imagine the current application looks somewhat like this:: + + from flask import Flask + app = Flask(__name__) + + @app.route('/') + def index(): + pass + + @app.route('/user/') + def user(username): + pass + +Then, with the centralized approach you would have one file with the views +(:file:`views.py`) but without any decorator:: + + def index(): + pass + + def user(username): + pass + +And then a file that sets up an application which maps the functions to +URLs:: + + from flask import Flask + from yourapplication import views + app = Flask(__name__) + app.add_url_rule('/', view_func=views.index) + app.add_url_rule('/user/', view_func=views.user) + +Loading Late +------------ + +So far we only split up the views and the routing, but the module is still +loaded upfront. The trick is to actually load the view function as needed. +This can be accomplished with a helper class that behaves just like a +function but internally imports the real function on first use:: + + from werkzeug.utils import import_string, cached_property + + class LazyView(object): + + def __init__(self, import_name): + self.__module__, self.__name__ = import_name.rsplit('.', 1) + self.import_name = import_name + + @cached_property + def view(self): + return import_string(self.import_name) + + def __call__(self, *args, **kwargs): + return self.view(*args, **kwargs) + +What's important here is is that `__module__` and `__name__` are properly +set. This is used by Flask internally to figure out how to name the +URL rules in case you don't provide a name for the rule yourself. + +Then you can define your central place to combine the views like this:: + + from flask import Flask + from yourapplication.helpers import LazyView + app = Flask(__name__) + app.add_url_rule('/', + view_func=LazyView('yourapplication.views.index')) + app.add_url_rule('/user/', + view_func=LazyView('yourapplication.views.user')) + +You can further optimize this in terms of amount of keystrokes needed to +write this by having a function that calls into +:meth:`~flask.Flask.add_url_rule` by prefixing a string with the project +name and a dot, and by wrapping `view_func` in a `LazyView` as needed. :: + + def url(import_name, url_rules=[], **options): + view = LazyView(f"yourapplication.{import_name}") + for url_rule in url_rules: + app.add_url_rule(url_rule, view_func=view, **options) + + # add a single route to the index view + url('views.index', ['/']) + + # add two routes to a single function endpoint + url_rules = ['/user/','/user/'] + url('views.user', url_rules) + +One thing to keep in mind is that before and after request handlers have +to be in a file that is imported upfront to work properly on the first +request. The same goes for any kind of remaining decorator. diff --git a/src/flask-main/docs/patterns/methodoverrides.rst b/src/flask-main/docs/patterns/methodoverrides.rst new file mode 100644 index 0000000..45dbb87 --- /dev/null +++ b/src/flask-main/docs/patterns/methodoverrides.rst @@ -0,0 +1,42 @@ +Adding HTTP Method Overrides +============================ + +Some HTTP proxies do not support arbitrary HTTP methods or newer HTTP +methods (such as PATCH). In that case it's possible to "proxy" HTTP +methods through another HTTP method in total violation of the protocol. + +The way this works is by letting the client do an HTTP POST request and +set the ``X-HTTP-Method-Override`` header. Then the method is replaced +with the header value before being passed to Flask. + +This can be accomplished with an HTTP middleware:: + + class HTTPMethodOverrideMiddleware(object): + allowed_methods = frozenset([ + 'GET', + 'HEAD', + 'POST', + 'DELETE', + 'PUT', + 'PATCH', + 'OPTIONS' + ]) + bodyless_methods = frozenset(['GET', 'HEAD', 'OPTIONS', 'DELETE']) + + def __init__(self, app): + self.app = app + + def __call__(self, environ, start_response): + method = environ.get('HTTP_X_HTTP_METHOD_OVERRIDE', '').upper() + if method in self.allowed_methods: + environ['REQUEST_METHOD'] = method + if method in self.bodyless_methods: + environ['CONTENT_LENGTH'] = '0' + return self.app(environ, start_response) + +To use this with Flask, wrap the app object with the middleware:: + + from flask import Flask + + app = Flask(__name__) + app.wsgi_app = HTTPMethodOverrideMiddleware(app.wsgi_app) diff --git a/src/flask-main/docs/patterns/mongoengine.rst b/src/flask-main/docs/patterns/mongoengine.rst new file mode 100644 index 0000000..624988e --- /dev/null +++ b/src/flask-main/docs/patterns/mongoengine.rst @@ -0,0 +1,103 @@ +MongoDB with MongoEngine +======================== + +Using a document database like MongoDB is a common alternative to +relational SQL databases. This pattern shows how to use +`MongoEngine`_, a document mapper library, to integrate with MongoDB. + +A running MongoDB server and `Flask-MongoEngine`_ are required. :: + + pip install flask-mongoengine + +.. _MongoEngine: http://mongoengine.org +.. _Flask-MongoEngine: https://flask-mongoengine.readthedocs.io + + +Configuration +------------- + +Basic setup can be done by defining ``MONGODB_SETTINGS`` on +``app.config`` and creating a ``MongoEngine`` instance. :: + + from flask import Flask + from flask_mongoengine import MongoEngine + + app = Flask(__name__) + app.config['MONGODB_SETTINGS'] = { + "db": "myapp", + } + db = MongoEngine(app) + + +Mapping Documents +----------------- + +To declare a model that represents a Mongo document, create a class that +inherits from ``Document`` and declare each of the fields. :: + + import mongoengine as me + + class Movie(me.Document): + title = me.StringField(required=True) + year = me.IntField() + rated = me.StringField() + director = me.StringField() + actors = me.ListField() + +If the document has nested fields, use ``EmbeddedDocument`` to +defined the fields of the embedded document and +``EmbeddedDocumentField`` to declare it on the parent document. :: + + class Imdb(me.EmbeddedDocument): + imdb_id = me.StringField() + rating = me.DecimalField() + votes = me.IntField() + + class Movie(me.Document): + ... + imdb = me.EmbeddedDocumentField(Imdb) + + +Creating Data +------------- + +Instantiate your document class with keyword arguments for the fields. +You can also assign values to the field attributes after instantiation. +Then call ``doc.save()``. :: + + bttf = Movie(title="Back To The Future", year=1985) + bttf.actors = [ + "Michael J. Fox", + "Christopher Lloyd" + ] + bttf.imdb = Imdb(imdb_id="tt0088763", rating=8.5) + bttf.save() + + +Queries +------- + +Use the class ``objects`` attribute to make queries. A keyword argument +looks for an equal value on the field. :: + + bttf = Movie.objects(title="Back To The Future").get_or_404() + +Query operators may be used by concatenating them with the field name +using a double-underscore. ``objects``, and queries returned by +calling it, are iterable. :: + + some_theron_movie = Movie.objects(actors__in=["Charlize Theron"]).first() + + for recents in Movie.objects(year__gte=2017): + print(recents.title) + + +Documentation +------------- + +There are many more ways to define and query documents with MongoEngine. +For more information, check out the `official documentation +`_. + +Flask-MongoEngine adds helpful utilities on top of MongoEngine. Check +out their `documentation `_ as well. diff --git a/src/flask-main/docs/patterns/packages.rst b/src/flask-main/docs/patterns/packages.rst new file mode 100644 index 0000000..90fa8a8 --- /dev/null +++ b/src/flask-main/docs/patterns/packages.rst @@ -0,0 +1,133 @@ +Large Applications as Packages +============================== + +Imagine a simple flask application structure that looks like this:: + + /yourapplication + yourapplication.py + /static + style.css + /templates + layout.html + index.html + login.html + ... + +While this is fine for small applications, for larger applications +it's a good idea to use a package instead of a module. +The :doc:`/tutorial/index` is structured to use the package pattern, +see the :gh:`example code `. + +Simple Packages +--------------- + +To convert that into a larger one, just create a new folder +:file:`yourapplication` inside the existing one and move everything below it. +Then rename :file:`yourapplication.py` to :file:`__init__.py`. (Make sure to delete +all ``.pyc`` files first, otherwise things would most likely break) + +You should then end up with something like that:: + + /yourapplication + /yourapplication + __init__.py + /static + style.css + /templates + layout.html + index.html + login.html + ... + +But how do you run your application now? The naive ``python +yourapplication/__init__.py`` will not work. Let's just say that Python +does not want modules in packages to be the startup file. But that is not +a big problem, just add a new file called :file:`pyproject.toml` next to the inner +:file:`yourapplication` folder with the following contents: + +.. code-block:: toml + + [project] + name = "yourapplication" + dependencies = [ + "flask", + ] + + [build-system] + requires = ["flit_core<4"] + build-backend = "flit_core.buildapi" + +Install your application so it is importable: + +.. code-block:: text + + $ pip install -e . + +To use the ``flask`` command and run your application you need to set +the ``--app`` option that tells Flask where to find the application +instance: + +.. code-block:: text + + $ flask --app yourapplication run + +What did we gain from this? Now we can restructure the application a bit +into multiple modules. The only thing you have to remember is the +following quick checklist: + +1. the `Flask` application object creation has to be in the + :file:`__init__.py` file. That way each module can import it safely and the + `__name__` variable will resolve to the correct package. +2. all the view functions (the ones with a :meth:`~flask.Flask.route` + decorator on top) have to be imported in the :file:`__init__.py` file. + Not the object itself, but the module it is in. Import the view module + **after the application object is created**. + +Here's an example :file:`__init__.py`:: + + from flask import Flask + app = Flask(__name__) + + import yourapplication.views + +And this is what :file:`views.py` would look like:: + + from yourapplication import app + + @app.route('/') + def index(): + return 'Hello World!' + +You should then end up with something like that:: + + /yourapplication + pyproject.toml + /yourapplication + __init__.py + views.py + /static + style.css + /templates + layout.html + index.html + login.html + ... + +.. admonition:: Circular Imports + + Every Python programmer hates them, and yet we just added some: + circular imports (That's when two modules depend on each other. In this + case :file:`views.py` depends on :file:`__init__.py`). Be advised that this is a + bad idea in general but here it is actually fine. The reason for this is + that we are not actually using the views in :file:`__init__.py` and just + ensuring the module is imported and we are doing that at the bottom of + the file. + + +Working with Blueprints +----------------------- + +If you have larger applications it's recommended to divide them into +smaller groups where each group is implemented with the help of a +blueprint. For a gentle introduction into this topic refer to the +:doc:`/blueprints` chapter of the documentation. diff --git a/src/flask-main/docs/patterns/requestchecksum.rst b/src/flask-main/docs/patterns/requestchecksum.rst new file mode 100644 index 0000000..25bc38b --- /dev/null +++ b/src/flask-main/docs/patterns/requestchecksum.rst @@ -0,0 +1,55 @@ +Request Content Checksums +========================= + +Various pieces of code can consume the request data and preprocess it. +For instance JSON data ends up on the request object already read and +processed, form data ends up there as well but goes through a different +code path. This seems inconvenient when you want to calculate the +checksum of the incoming request data. This is necessary sometimes for +some APIs. + +Fortunately this is however very simple to change by wrapping the input +stream. + +The following example calculates the SHA1 checksum of the incoming data as +it gets read and stores it in the WSGI environment:: + + import hashlib + + class ChecksumCalcStream(object): + + def __init__(self, stream): + self._stream = stream + self._hash = hashlib.sha1() + + def read(self, bytes): + rv = self._stream.read(bytes) + self._hash.update(rv) + return rv + + def readline(self, size_hint): + rv = self._stream.readline(size_hint) + self._hash.update(rv) + return rv + + def generate_checksum(request): + env = request.environ + stream = ChecksumCalcStream(env['wsgi.input']) + env['wsgi.input'] = stream + return stream._hash + +To use this, all you need to do is to hook the calculating stream in +before the request starts consuming data. (Eg: be careful accessing +``request.form`` or anything of that nature. ``before_request_handlers`` +for instance should be careful not to access it). + +Example usage:: + + @app.route('/special-api', methods=['POST']) + def special_api(): + hash = generate_checksum(request) + # Accessing this parses the input stream + files = request.files + # At this point the hash is fully constructed. + checksum = hash.hexdigest() + return f"Hash was: {checksum}" diff --git a/src/flask-main/docs/patterns/singlepageapplications.rst b/src/flask-main/docs/patterns/singlepageapplications.rst new file mode 100644 index 0000000..1cb779b --- /dev/null +++ b/src/flask-main/docs/patterns/singlepageapplications.rst @@ -0,0 +1,24 @@ +Single-Page Applications +======================== + +Flask can be used to serve Single-Page Applications (SPA) by placing static +files produced by your frontend framework in a subfolder inside of your +project. You will also need to create a catch-all endpoint that routes all +requests to your SPA. + +The following example demonstrates how to serve an SPA along with an API:: + + from flask import Flask, jsonify + + app = Flask(__name__, static_folder='app', static_url_path="/app") + + + @app.route("/heartbeat") + def heartbeat(): + return jsonify({"status": "healthy"}) + + + @app.route('/', defaults={'path': ''}) + @app.route('/') + def catch_all(path): + return app.send_static_file("index.html") diff --git a/src/flask-main/docs/patterns/sqlalchemy.rst b/src/flask-main/docs/patterns/sqlalchemy.rst new file mode 100644 index 0000000..9e9afe4 --- /dev/null +++ b/src/flask-main/docs/patterns/sqlalchemy.rst @@ -0,0 +1,213 @@ +SQLAlchemy in Flask +=================== + +Many people prefer `SQLAlchemy`_ for database access. In this case it's +encouraged to use a package instead of a module for your flask application +and drop the models into a separate module (:doc:`packages`). While that +is not necessary, it makes a lot of sense. + +There are four very common ways to use SQLAlchemy. I will outline each +of them here: + +Flask-SQLAlchemy Extension +-------------------------- + +Because SQLAlchemy is a common database abstraction layer and object +relational mapper that requires a little bit of configuration effort, +there is a Flask extension that handles that for you. This is recommended +if you want to get started quickly. + +You can download `Flask-SQLAlchemy`_ from `PyPI +`_. + +.. _Flask-SQLAlchemy: https://flask-sqlalchemy.palletsprojects.com/ + + +Declarative +----------- + +The declarative extension in SQLAlchemy is the most recent method of using +SQLAlchemy. It allows you to define tables and models in one go, similar +to how Django works. In addition to the following text I recommend the +official documentation on the `declarative`_ extension. + +Here's the example :file:`database.py` module for your application:: + + from sqlalchemy import create_engine + from sqlalchemy.orm import scoped_session, sessionmaker, declarative_base + + engine = create_engine('sqlite:////tmp/test.db') + db_session = scoped_session(sessionmaker(autocommit=False, + autoflush=False, + bind=engine)) + Base = declarative_base() + Base.query = db_session.query_property() + + def init_db(): + # import all modules here that might define models so that + # they will be registered properly on the metadata. Otherwise + # you will have to import them first before calling init_db() + import yourapplication.models + Base.metadata.create_all(bind=engine) + +To define your models, just subclass the `Base` class that was created by +the code above. If you are wondering why we don't have to care about +threads here (like we did in the SQLite3 example above with the +:data:`~flask.g` object): that's because SQLAlchemy does that for us +already with the :class:`~sqlalchemy.orm.scoped_session`. + +To use SQLAlchemy in a declarative way with your application, you just +have to put the following code into your application module. Flask will +automatically remove database sessions at the end of the request or +when the application shuts down:: + + from yourapplication.database import db_session + + @app.teardown_appcontext + def shutdown_session(exception=None): + db_session.remove() + +Here is an example model (put this into :file:`models.py`, e.g.):: + + from sqlalchemy import Column, Integer, String + from yourapplication.database import Base + + class User(Base): + __tablename__ = 'users' + id = Column(Integer, primary_key=True) + name = Column(String(50), unique=True) + email = Column(String(120), unique=True) + + def __init__(self, name=None, email=None): + self.name = name + self.email = email + + def __repr__(self): + return f'' + +To create the database you can use the `init_db` function: + +>>> from yourapplication.database import init_db +>>> init_db() + +You can insert entries into the database like this: + +>>> from yourapplication.database import db_session +>>> from yourapplication.models import User +>>> u = User('admin', 'admin@localhost') +>>> db_session.add(u) +>>> db_session.commit() + +Querying is simple as well: + +>>> User.query.all() +[] +>>> User.query.filter(User.name == 'admin').first() + + +.. _SQLAlchemy: https://www.sqlalchemy.org/ +.. _declarative: https://docs.sqlalchemy.org/en/latest/orm/extensions/declarative/ + +Manual Object Relational Mapping +-------------------------------- + +Manual object relational mapping has a few upsides and a few downsides +versus the declarative approach from above. The main difference is that +you define tables and classes separately and map them together. It's more +flexible but a little more to type. In general it works like the +declarative approach, so make sure to also split up your application into +multiple modules in a package. + +Here is an example :file:`database.py` module for your application:: + + from sqlalchemy import create_engine, MetaData + from sqlalchemy.orm import scoped_session, sessionmaker + + engine = create_engine('sqlite:////tmp/test.db') + metadata = MetaData() + db_session = scoped_session(sessionmaker(autocommit=False, + autoflush=False, + bind=engine)) + def init_db(): + metadata.create_all(bind=engine) + +As in the declarative approach, you need to close the session after each app +context. Put this into your application module:: + + from yourapplication.database import db_session + + @app.teardown_appcontext + def shutdown_session(exception=None): + db_session.remove() + +Here is an example table and model (put this into :file:`models.py`):: + + from sqlalchemy import Table, Column, Integer, String + from sqlalchemy.orm import mapper + from yourapplication.database import metadata, db_session + + class User(object): + query = db_session.query_property() + + def __init__(self, name=None, email=None): + self.name = name + self.email = email + + def __repr__(self): + return f'' + + users = Table('users', metadata, + Column('id', Integer, primary_key=True), + Column('name', String(50), unique=True), + Column('email', String(120), unique=True) + ) + mapper(User, users) + +Querying and inserting works exactly the same as in the example above. + + +SQL Abstraction Layer +--------------------- + +If you just want to use the database system (and SQL) abstraction layer +you basically only need the engine:: + + from sqlalchemy import create_engine, MetaData, Table + + engine = create_engine('sqlite:////tmp/test.db') + metadata = MetaData(bind=engine) + +Then you can either declare the tables in your code like in the examples +above, or automatically load them:: + + from sqlalchemy import Table + + users = Table('users', metadata, autoload=True) + +To insert data you can use the `insert` method. We have to get a +connection first so that we can use a transaction: + +>>> con = engine.connect() +>>> con.execute(users.insert(), name='admin', email='admin@localhost') + +SQLAlchemy will automatically commit for us. + +To query your database, you use the engine directly or use a connection: + +>>> users.select(users.c.id == 1).execute().first() +(1, 'admin', 'admin@localhost') + +These results are also dict-like tuples: + +>>> r = users.select(users.c.id == 1).execute().first() +>>> r['name'] +'admin' + +You can also pass strings of SQL statements to the +:meth:`~sqlalchemy.engine.base.Connection.execute` method: + +>>> engine.execute('select * from users where id = :1', [1]).first() +(1, 'admin', 'admin@localhost') + +For more information about SQLAlchemy, head over to the +`website `_. diff --git a/src/flask-main/docs/patterns/sqlite3.rst b/src/flask-main/docs/patterns/sqlite3.rst new file mode 100644 index 0000000..f42e0f8 --- /dev/null +++ b/src/flask-main/docs/patterns/sqlite3.rst @@ -0,0 +1,147 @@ +Using SQLite 3 with Flask +========================= + +You can implement a few functions to work with a SQLite connection during a +request context. The connection is created the first time it's accessed, +reused on subsequent access, until it is closed when the request context ends. + +Here is a simple example of how you can use SQLite 3 with Flask:: + + import sqlite3 + from flask import g + + DATABASE = '/path/to/database.db' + + def get_db(): + db = getattr(g, '_database', None) + if db is None: + db = g._database = sqlite3.connect(DATABASE) + return db + + @app.teardown_appcontext + def close_connection(exception): + db = getattr(g, '_database', None) + if db is not None: + db.close() + +Now, to use the database, the application must either have an active +application context (which is always true if there is a request in flight) +or create an application context itself. At that point the ``get_db`` +function can be used to get the current database connection. Whenever the +context is destroyed the database connection will be terminated. + +Example:: + + @app.route('/') + def index(): + cur = get_db().cursor() + ... + + +.. note:: + + Please keep in mind that the teardown request and appcontext functions + are always executed, even if a before-request handler failed or was + never executed. Because of this we have to make sure here that the + database is there before we close it. + +Connect on Demand +----------------- + +The upside of this approach (connecting on first use) is that this will +only open the connection if truly necessary. If you want to use this +code outside a request context you can use it in a Python shell by opening +the application context by hand:: + + with app.app_context(): + # now you can use get_db() + + +Easy Querying +------------- + +Now in each request handling function you can access `get_db()` to get the +current open database connection. To simplify working with SQLite, a +row factory function is useful. It is executed for every result returned +from the database to convert the result. For instance, in order to get +dictionaries instead of tuples, this could be inserted into the ``get_db`` +function we created above:: + + def make_dicts(cursor, row): + return dict((cursor.description[idx][0], value) + for idx, value in enumerate(row)) + + db.row_factory = make_dicts + +This will make the sqlite3 module return dicts for this database connection, which are much nicer to deal with. Even more simply, we could place this in ``get_db`` instead:: + + db.row_factory = sqlite3.Row + +This would use Row objects rather than dicts to return the results of queries. These are ``namedtuple`` s, so we can access them either by index or by key. For example, assuming we have a ``sqlite3.Row`` called ``r`` for the rows ``id``, ``FirstName``, ``LastName``, and ``MiddleInitial``:: + + >>> # You can get values based on the row's name + >>> r['FirstName'] + John + >>> # Or, you can get them based on index + >>> r[1] + John + # Row objects are also iterable: + >>> for value in r: + ... print(value) + 1 + John + Doe + M + +Additionally, it is a good idea to provide a query function that combines +getting the cursor, executing and fetching the results:: + + def query_db(query, args=(), one=False): + cur = get_db().execute(query, args) + rv = cur.fetchall() + cur.close() + return (rv[0] if rv else None) if one else rv + +This handy little function, in combination with a row factory, makes +working with the database much more pleasant than it is by just using the +raw cursor and connection objects. + +Here is how you can use it:: + + for user in query_db('select * from users'): + print(user['username'], 'has the id', user['user_id']) + +Or if you just want a single result:: + + user = query_db('select * from users where username = ?', + [the_username], one=True) + if user is None: + print('No such user') + else: + print(the_username, 'has the id', user['user_id']) + +To pass variable parts to the SQL statement, use a question mark in the +statement and pass in the arguments as a list. Never directly add them to +the SQL statement with string formatting because this makes it possible +to attack the application using `SQL Injections +`_. + +Initial Schemas +--------------- + +Relational databases need schemas, so applications often ship a +`schema.sql` file that creates the database. It's a good idea to provide +a function that creates the database based on that schema. This function +can do that for you:: + + def init_db(): + with app.app_context(): + db = get_db() + with app.open_resource('schema.sql', mode='r') as f: + db.cursor().executescript(f.read()) + db.commit() + +You can then create such a database from the Python shell: + +>>> from yourapplication import init_db +>>> init_db() diff --git a/src/flask-main/docs/patterns/streaming.rst b/src/flask-main/docs/patterns/streaming.rst new file mode 100644 index 0000000..ff35631 --- /dev/null +++ b/src/flask-main/docs/patterns/streaming.rst @@ -0,0 +1,85 @@ +Streaming Contents +================== + +Sometimes you want to send an enormous amount of data to the client, much +more than you want to keep in memory. When you are generating the data on +the fly though, how do you send that back to the client without the +roundtrip to the filesystem? + +The answer is by using generators and direct responses. + +Basic Usage +----------- + +This is a basic view function that generates a lot of CSV data on the fly. +The trick is to have an inner function that uses a generator to generate +data and to then invoke that function and pass it to a response object:: + + @app.route('/large.csv') + def generate_large_csv(): + def generate(): + for row in iter_all_rows(): + yield f"{','.join(row)}\n" + return generate(), {"Content-Type": "text/csv"} + +Each ``yield`` expression is directly sent to the browser. Note though +that some WSGI middlewares might break streaming, so be careful there in +debug environments with profilers and other things you might have enabled. + +Streaming from Templates +------------------------ + +The Jinja template engine supports rendering a template piece by +piece, returning an iterator of strings. Flask provides the +:func:`~flask.stream_template` and :func:`~flask.stream_template_string` +functions to make this easier to use. + +.. code-block:: python + + from flask import stream_template + + @app.get("/timeline") + def timeline(): + return stream_template("timeline.html") + +The parts yielded by the render stream tend to match statement blocks in +the template. + + +Streaming with Context +---------------------- + +The :data:`.request` proxy will not be active while the generator is +running, because the app has already returned control to the WSGI server at that +point. If you try to access ``request``, you'll get a ``RuntimeError``. + +If your generator function relies on data in ``request``, use the +:func:`.stream_with_context` wrapper. This will keep the request context active +during the generator. + +.. code-block:: python + + from flask import stream_with_context, request + from markupsafe import escape + + @app.route('/stream') + def streamed_response(): + def generate(): + yield '

Hello ' + yield escape(request.args['name']) + yield '!

' + return stream_with_context(generate()) + +It can also be used as a decorator. + +.. code-block:: python + + @stream_with_context + def generate(): + ... + + return generate() + +The :func:`~flask.stream_template` and +:func:`~flask.stream_template_string` functions automatically +use :func:`~flask.stream_with_context` if a request is active. diff --git a/src/flask-main/docs/patterns/subclassing.rst b/src/flask-main/docs/patterns/subclassing.rst new file mode 100644 index 0000000..d8de233 --- /dev/null +++ b/src/flask-main/docs/patterns/subclassing.rst @@ -0,0 +1,17 @@ +Subclassing Flask +================= + +The :class:`~flask.Flask` class is designed for subclassing. + +For example, you may want to override how request parameters are handled to preserve their order:: + + from flask import Flask, Request + from werkzeug.datastructures import ImmutableOrderedMultiDict + class MyRequest(Request): + """Request subclass to override request parameter storage""" + parameter_storage_class = ImmutableOrderedMultiDict + class MyFlask(Flask): + """Flask subclass using the custom request class""" + request_class = MyRequest + +This is the recommended approach for overriding or augmenting Flask's internal functionality. diff --git a/src/flask-main/docs/patterns/templateinheritance.rst b/src/flask-main/docs/patterns/templateinheritance.rst new file mode 100644 index 0000000..bb5cba2 --- /dev/null +++ b/src/flask-main/docs/patterns/templateinheritance.rst @@ -0,0 +1,68 @@ +Template Inheritance +==================== + +The most powerful part of Jinja is template inheritance. Template inheritance +allows you to build a base "skeleton" template that contains all the common +elements of your site and defines **blocks** that child templates can override. + +Sounds complicated but is very basic. It's easiest to understand it by starting +with an example. + + +Base Template +------------- + +This template, which we'll call :file:`layout.html`, defines a simple HTML skeleton +document that you might use for a simple two-column page. It's the job of +"child" templates to fill the empty blocks with content: + +.. sourcecode:: html+jinja + + + + + {% block head %} + + {% block title %}{% endblock %} - My Webpage + {% endblock %} + + +
{% block content %}{% endblock %}
+ + + + +In this example, the ``{% block %}`` tags define four blocks that child templates +can fill in. All the `block` tag does is tell the template engine that a +child template may override those portions of the template. + +Child Template +-------------- + +A child template might look like this: + +.. sourcecode:: html+jinja + + {% extends "layout.html" %} + {% block title %}Index{% endblock %} + {% block head %} + {{ super() }} + + {% endblock %} + {% block content %} +

Index

+

+ Welcome on my awesome homepage. + {% endblock %} + +The ``{% extends %}`` tag is the key here. It tells the template engine that +this template "extends" another template. When the template system evaluates +this template, first it locates the parent. The extends tag must be the +first tag in the template. To render the contents of a block defined in +the parent template, use ``{{ super() }}``. diff --git a/src/flask-main/docs/patterns/urlprocessors.rst b/src/flask-main/docs/patterns/urlprocessors.rst new file mode 100644 index 0000000..0d74320 --- /dev/null +++ b/src/flask-main/docs/patterns/urlprocessors.rst @@ -0,0 +1,126 @@ +Using URL Processors +==================== + +.. versionadded:: 0.7 + +Flask 0.7 introduces the concept of URL processors. The idea is that you +might have a bunch of resources with common parts in the URL that you +don't always explicitly want to provide. For instance you might have a +bunch of URLs that have the language code in it but you don't want to have +to handle it in every single function yourself. + +URL processors are especially helpful when combined with blueprints. We +will handle both application specific URL processors here as well as +blueprint specifics. + +Internationalized Application URLs +---------------------------------- + +Consider an application like this:: + + from flask import Flask, g + + app = Flask(__name__) + + @app.route('//') + def index(lang_code): + g.lang_code = lang_code + ... + + @app.route('//about') + def about(lang_code): + g.lang_code = lang_code + ... + +This is an awful lot of repetition as you have to handle the language code +setting on the :data:`~flask.g` object yourself in every single function. +Sure, a decorator could be used to simplify this, but if you want to +generate URLs from one function to another you would have to still provide +the language code explicitly which can be annoying. + +For the latter, this is where :func:`~flask.Flask.url_defaults` functions +come in. They can automatically inject values into a call to +:func:`~flask.url_for`. The code below checks if the +language code is not yet in the dictionary of URL values and if the +endpoint wants a value named ``'lang_code'``:: + + @app.url_defaults + def add_language_code(endpoint, values): + if 'lang_code' in values or not g.lang_code: + return + if app.url_map.is_endpoint_expecting(endpoint, 'lang_code'): + values['lang_code'] = g.lang_code + +The method :meth:`~werkzeug.routing.Map.is_endpoint_expecting` of the URL +map can be used to figure out if it would make sense to provide a language +code for the given endpoint. + +The reverse of that function are +:meth:`~flask.Flask.url_value_preprocessor`\s. They are executed right +after the request was matched and can execute code based on the URL +values. The idea is that they pull information out of the values +dictionary and put it somewhere else:: + + @app.url_value_preprocessor + def pull_lang_code(endpoint, values): + g.lang_code = values.pop('lang_code', None) + +That way you no longer have to do the `lang_code` assignment to +:data:`~flask.g` in every function. You can further improve that by +writing your own decorator that prefixes URLs with the language code, but +the more beautiful solution is using a blueprint. Once the +``'lang_code'`` is popped from the values dictionary and it will no longer +be forwarded to the view function reducing the code to this:: + + from flask import Flask, g + + app = Flask(__name__) + + @app.url_defaults + def add_language_code(endpoint, values): + if 'lang_code' in values or not g.lang_code: + return + if app.url_map.is_endpoint_expecting(endpoint, 'lang_code'): + values['lang_code'] = g.lang_code + + @app.url_value_preprocessor + def pull_lang_code(endpoint, values): + g.lang_code = values.pop('lang_code', None) + + @app.route('//') + def index(): + ... + + @app.route('//about') + def about(): + ... + +Internationalized Blueprint URLs +-------------------------------- + +Because blueprints can automatically prefix all URLs with a common string +it's easy to automatically do that for every function. Furthermore +blueprints can have per-blueprint URL processors which removes a whole lot +of logic from the :meth:`~flask.Flask.url_defaults` function because it no +longer has to check if the URL is really interested in a ``'lang_code'`` +parameter:: + + from flask import Blueprint, g + + bp = Blueprint('frontend', __name__, url_prefix='/') + + @bp.url_defaults + def add_language_code(endpoint, values): + values.setdefault('lang_code', g.lang_code) + + @bp.url_value_preprocessor + def pull_lang_code(endpoint, values): + g.lang_code = values.pop('lang_code') + + @bp.route('/') + def index(): + ... + + @bp.route('/about') + def about(): + ... diff --git a/src/flask-main/docs/patterns/viewdecorators.rst b/src/flask-main/docs/patterns/viewdecorators.rst new file mode 100644 index 0000000..0b0479e --- /dev/null +++ b/src/flask-main/docs/patterns/viewdecorators.rst @@ -0,0 +1,171 @@ +View Decorators +=============== + +Python has a really interesting feature called function decorators. This +allows some really neat things for web applications. Because each view in +Flask is a function, decorators can be used to inject additional +functionality to one or more functions. The :meth:`~flask.Flask.route` +decorator is the one you probably used already. But there are use cases +for implementing your own decorator. For instance, imagine you have a +view that should only be used by people that are logged in. If a user +goes to the site and is not logged in, they should be redirected to the +login page. This is a good example of a use case where a decorator is an +excellent solution. + +Login Required Decorator +------------------------ + +So let's implement such a decorator. A decorator is a function that +wraps and replaces another function. Since the original function is +replaced, you need to remember to copy the original function's information +to the new function. Use :func:`functools.wraps` to handle this for you. + +This example assumes that the login page is called ``'login'`` and that +the current user is stored in ``g.user`` and is ``None`` if there is no-one +logged in. :: + + from functools import wraps + from flask import g, request, redirect, url_for + + def login_required(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if g.user is None: + return redirect(url_for('login', next=request.url)) + return f(*args, **kwargs) + return decorated_function + +To use the decorator, apply it as innermost decorator to a view function. +When applying further decorators, always remember +that the :meth:`~flask.Flask.route` decorator is the outermost. :: + + @app.route('/secret_page') + @login_required + def secret_page(): + pass + +.. note:: + The ``next`` value will exist in ``request.args`` after a ``GET`` request for + the login page. You'll have to pass it along when sending the ``POST`` request + from the login form. You can do this with a hidden input tag, then retrieve it + from ``request.form`` when logging the user in. :: + + + + +Caching Decorator +----------------- + +Imagine you have a view function that does an expensive calculation and +because of that you would like to cache the generated results for a +certain amount of time. A decorator would be nice for that. We're +assuming you have set up a cache like mentioned in :doc:`caching`. + +Here is an example cache function. It generates the cache key from a +specific prefix (actually a format string) and the current path of the +request. Notice that we are using a function that first creates the +decorator that then decorates the function. Sounds awful? Unfortunately +it is a little bit more complex, but the code should still be +straightforward to read. + +The decorated function will then work as follows + +1. get the unique cache key for the current request based on the current + path. +2. get the value for that key from the cache. If the cache returned + something we will return that value. +3. otherwise the original function is called and the return value is + stored in the cache for the timeout provided (by default 5 minutes). + +Here the code:: + + from functools import wraps + from flask import request + + def cached(timeout=5 * 60, key='view/{}'): + def decorator(f): + @wraps(f) + def decorated_function(*args, **kwargs): + cache_key = key.format(request.path) + rv = cache.get(cache_key) + if rv is not None: + return rv + rv = f(*args, **kwargs) + cache.set(cache_key, rv, timeout=timeout) + return rv + return decorated_function + return decorator + +Notice that this assumes an instantiated ``cache`` object is available, see +:doc:`caching`. + + +Templating Decorator +-------------------- + +A common pattern invented by the TurboGears guys a while back is a +templating decorator. The idea of that decorator is that you return a +dictionary with the values passed to the template from the view function +and the template is automatically rendered. With that, the following +three examples do exactly the same:: + + @app.route('/') + def index(): + return render_template('index.html', value=42) + + @app.route('/') + @templated('index.html') + def index(): + return dict(value=42) + + @app.route('/') + @templated() + def index(): + return dict(value=42) + +As you can see, if no template name is provided it will use the endpoint +of the URL map with dots converted to slashes + ``'.html'``. Otherwise +the provided template name is used. When the decorated function returns, +the dictionary returned is passed to the template rendering function. If +``None`` is returned, an empty dictionary is assumed, if something else than +a dictionary is returned we return it from the function unchanged. That +way you can still use the redirect function or return simple strings. + +Here is the code for that decorator:: + + from functools import wraps + from flask import request, render_template + + def templated(template=None): + def decorator(f): + @wraps(f) + def decorated_function(*args, **kwargs): + template_name = template + if template_name is None: + template_name = f"{request.endpoint.replace('.', '/')}.html" + ctx = f(*args, **kwargs) + if ctx is None: + ctx = {} + elif not isinstance(ctx, dict): + return ctx + return render_template(template_name, **ctx) + return decorated_function + return decorator + + +Endpoint Decorator +------------------ + +When you want to use the werkzeug routing system for more flexibility you +need to map the endpoint as defined in the :class:`~werkzeug.routing.Rule` +to a view function. This is possible with this decorator. For example:: + + from flask import Flask + from werkzeug.routing import Rule + + app = Flask(__name__) + app.url_map.add(Rule('/', endpoint='index')) + + @app.endpoint('index') + def my_index(): + return "Hello world" diff --git a/src/flask-main/docs/patterns/wtforms.rst b/src/flask-main/docs/patterns/wtforms.rst new file mode 100644 index 0000000..cb1208f --- /dev/null +++ b/src/flask-main/docs/patterns/wtforms.rst @@ -0,0 +1,126 @@ +Form Validation with WTForms +============================ + +When you have to work with form data submitted by a browser view, code +quickly becomes very hard to read. There are libraries out there designed +to make this process easier to manage. One of them is `WTForms`_ which we +will handle here. If you find yourself in the situation of having many +forms, you might want to give it a try. + +When you are working with WTForms you have to define your forms as classes +first. I recommend breaking up the application into multiple modules +(:doc:`packages`) for that and adding a separate module for the +forms. + +.. admonition:: Getting the most out of WTForms with an Extension + + The `Flask-WTF`_ extension expands on this pattern and adds a + few little helpers that make working with forms and Flask more + fun. You can get it from `PyPI + `_. + +.. _Flask-WTF: https://flask-wtf.readthedocs.io/ + +The Forms +--------- + +This is an example form for a typical registration page:: + + from wtforms import Form, BooleanField, StringField, PasswordField, validators + + class RegistrationForm(Form): + username = StringField('Username', [validators.Length(min=4, max=25)]) + email = StringField('Email Address', [validators.Length(min=6, max=35)]) + password = PasswordField('New Password', [ + validators.DataRequired(), + validators.EqualTo('confirm', message='Passwords must match') + ]) + confirm = PasswordField('Repeat Password') + accept_tos = BooleanField('I accept the TOS', [validators.DataRequired()]) + +In the View +----------- + +In the view function, the usage of this form looks like this:: + + @app.route('/register', methods=['GET', 'POST']) + def register(): + form = RegistrationForm(request.form) + if request.method == 'POST' and form.validate(): + user = User(form.username.data, form.email.data, + form.password.data) + db_session.add(user) + flash('Thanks for registering') + return redirect(url_for('login')) + return render_template('register.html', form=form) + +Notice we're implying that the view is using SQLAlchemy here +(:doc:`sqlalchemy`), but that's not a requirement, of course. Adapt +the code as necessary. + +Things to remember: + +1. create the form from the request :attr:`~flask.request.form` value if + the data is submitted via the HTTP ``POST`` method and + :attr:`~flask.request.args` if the data is submitted as ``GET``. +2. to validate the data, call the :func:`~wtforms.form.Form.validate` + method, which will return ``True`` if the data validates, ``False`` + otherwise. +3. to access individual values from the form, access `form..data`. + +Forms in Templates +------------------ + +Now to the template side. When you pass the form to the templates, you can +easily render them there. Look at the following example template to see +how easy this is. WTForms does half the form generation for us already. +To make it even nicer, we can write a macro that renders a field with +label and a list of errors if there are any. + +Here's an example :file:`_formhelpers.html` template with such a macro: + +.. sourcecode:: html+jinja + + {% macro render_field(field) %} +

{{ field.label }} +
{{ field(**kwargs)|safe }} + {% if field.errors %} +
    + {% for error in field.errors %} +
  • {{ error }}
  • + {% endfor %} +
+ {% endif %} +
+ {% endmacro %} + +This macro accepts a couple of keyword arguments that are forwarded to +WTForm's field function, which renders the field for us. The keyword +arguments will be inserted as HTML attributes. So, for example, you can +call ``render_field(form.username, class='username')`` to add a class to +the input element. Note that WTForms returns standard Python strings, +so we have to tell Jinja that this data is already HTML-escaped with +the ``|safe`` filter. + +Here is the :file:`register.html` template for the function we used above, which +takes advantage of the :file:`_formhelpers.html` template: + +.. sourcecode:: html+jinja + + {% from "_formhelpers.html" import render_field %} + +
+ {{ render_field(form.username) }} + {{ render_field(form.email) }} + {{ render_field(form.password) }} + {{ render_field(form.confirm) }} + {{ render_field(form.accept_tos) }} +
+

+

+ +For more information about WTForms, head over to the `WTForms +website`_. + +.. _WTForms: https://wtforms.readthedocs.io/ +.. _WTForms website: https://wtforms.readthedocs.io/ diff --git a/src/flask-main/docs/quickstart.rst b/src/flask-main/docs/quickstart.rst new file mode 100644 index 0000000..6af09eb --- /dev/null +++ b/src/flask-main/docs/quickstart.rst @@ -0,0 +1,858 @@ +Quickstart +========== + +Eager to get started? This page gives a good introduction to Flask. +Follow :doc:`installation` to set up a project and install Flask first. + + +A Minimal Application +--------------------- + +A minimal Flask application looks something like this: + +.. code-block:: python + + from flask import Flask + + app = Flask(__name__) + + @app.route("/") + def hello_world(): + return "

Hello, World!

" + +So what did that code do? + +1. First we imported the :class:`~flask.Flask` class. An instance of + this class will be our WSGI application. +2. Next we create an instance of this class. The first argument is the + name of the application's module or package. ``__name__`` is a + convenient shortcut for this that is appropriate for most cases. + This is needed so that Flask knows where to look for resources such + as templates and static files. +3. We then use the :meth:`~flask.Flask.route` decorator to tell Flask + what URL should trigger our function. +4. The function returns the message we want to display in the user's + browser. The default content type is HTML, so HTML in the string + will be rendered by the browser. + +Save it as :file:`hello.py` or something similar. Make sure to not call +your application :file:`flask.py` because this would conflict with Flask +itself. + +To run the application, use the ``flask`` command or +``python -m flask``. You need to tell the Flask where your application +is with the ``--app`` option. + +.. code-block:: text + + $ flask --app hello run + * Serving Flask app 'hello' + * Running on http://127.0.0.1:5000 (Press CTRL+C to quit) + +.. admonition:: Application Discovery Behavior + + As a shortcut, if the file is named ``app.py`` or ``wsgi.py``, you + don't have to use ``--app``. See :doc:`/cli` for more details. + +This launches a very simple builtin server, which is good enough for +testing but probably not what you want to use in production. For +deployment options see :doc:`deploying/index`. + +Now head over to http://127.0.0.1:5000/, and you should see your hello +world greeting. + +If another program is already using port 5000, you'll see +``OSError: [Errno 98]`` or ``OSError: [WinError 10013]`` when the +server tries to start. See :ref:`address-already-in-use` for how to +handle that. + +.. _public-server: + +.. admonition:: Externally Visible Server + + If you run the server you will notice that the server is only accessible + from your own computer, not from any other in the network. This is the + default because in debugging mode a user of the application can execute + arbitrary Python code on your computer. + + If you have the debugger disabled or trust the users on your network, + you can make the server publicly available simply by adding + ``--host=0.0.0.0`` to the command line:: + + $ flask run --host=0.0.0.0 + + This tells your operating system to listen on all public IPs. + + +Debug Mode +---------- + +The ``flask run`` command can do more than just start the development +server. By enabling debug mode, the server will automatically reload if +code changes, and will show an interactive debugger in the browser if an +error occurs during a request. + +.. image:: _static/debugger.png + :align: center + :class: screenshot + :alt: The interactive debugger in action. + +.. warning:: + + The debugger allows executing arbitrary Python code from the + browser. It is protected by a pin, but still represents a major + security risk. Do not run the development server or debugger in a + production environment. + +To enable debug mode, use the ``--debug`` option. + +.. code-block:: text + + $ flask --app hello run --debug + * Serving Flask app 'hello' + * Debug mode: on + * Running on http://127.0.0.1:5000 (Press CTRL+C to quit) + * Restarting with stat + * Debugger is active! + * Debugger PIN: nnn-nnn-nnn + +See also: + +- :doc:`/server` and :doc:`/cli` for information about running in debug mode. +- :doc:`/debugging` for information about using the built-in debugger + and other debuggers. +- :doc:`/logging` and :doc:`/errorhandling` to log errors and display + nice error pages. + + +HTML Escaping +------------- + +When returning HTML (the default response type in Flask), any +user-provided values rendered in the output must be escaped to protect +from injection attacks. HTML templates rendered with Jinja, introduced +later, will do this automatically. + +:func:`~markupsafe.escape`, shown here, can be used manually. It is +omitted in most examples for brevity, but you should always be aware of +how you're using untrusted data. + +.. code-block:: python + + from flask import request + from markupsafe import escape + + @app.route("/hello") + def hello(): + name = request.args.get("name", "Flask") + return f"Hello, {escape(name)}!" + +If a user submits ``/hello?name=``, escaping causes +it to be rendered as text, rather than running the script in the user's browser. + + +Routing +------- + +Modern web applications use meaningful URLs to help users. Users are more +likely to like a page and come back if the page uses a meaningful URL they can +remember and use to directly visit a page. + +Use the :meth:`~flask.Flask.route` decorator to bind a function to a URL. :: + + @app.route('/') + def index(): + return 'Index Page' + + @app.route('/hello') + def hello(): + return 'Hello, World' + +You can do more! You can make parts of the URL dynamic and attach multiple +rules to a function. + +Variable Rules +`````````````` + +You can add variable sections to a URL by marking sections with +````. Your function then receives the ```` +as a keyword argument. Optionally, you can use a converter to specify the type +of the argument like ````. :: + + from markupsafe import escape + + @app.route('/user/') + def show_user_profile(username): + # show the user profile for that user + return f'User {escape(username)}' + + @app.route('/post/') + def show_post(post_id): + # show the post with the given id, the id is an integer + return f'Post {post_id}' + + @app.route('/path/') + def show_subpath(subpath): + # show the subpath after /path/ + return f'Subpath {escape(subpath)}' + +Converter types: + +========== ========================================== +``string`` (default) accepts any text without a slash +``int`` accepts positive integers +``float`` accepts positive floating point values +``path`` like ``string`` but also accepts slashes +``uuid`` accepts UUID strings +========== ========================================== + + +Unique URLs / Redirection Behavior +`````````````````````````````````` + +The following two rules differ in their use of a trailing slash. :: + + @app.route('/projects/') + def projects(): + return 'The project page' + + @app.route('/about') + def about(): + return 'The about page' + +The canonical URL for the ``projects`` endpoint has a trailing slash. +It's similar to a folder in a file system. If you access the URL without +a trailing slash (``/projects``), Flask redirects you to the canonical URL +with the trailing slash (``/projects/``). + +The canonical URL for the ``about`` endpoint does not have a trailing +slash. It's similar to the pathname of a file. Accessing the URL with a +trailing slash (``/about/``) produces a 404 "Not Found" error. This helps +keep URLs unique for these resources, which helps search engines avoid +indexing the same page twice. + + +.. _url-building: + +URL Building +```````````` + +To build a URL to a specific function, use the :func:`~flask.url_for` function. +It accepts the name of the function as its first argument and any number of +keyword arguments, each corresponding to a variable part of the URL rule. +Unknown variable parts are appended to the URL as query parameters. + +Why would you want to build URLs using the URL reversing function +:func:`~flask.url_for` instead of hard-coding them into your templates? + +1. Reversing is often more descriptive than hard-coding the URLs. +2. You can change your URLs in one go instead of needing to remember to + manually change hard-coded URLs. +3. URL building handles escaping of special characters transparently. +4. The generated paths are always absolute, avoiding unexpected behavior + of relative paths in browsers. +5. If your application is placed outside the URL root, for example, in + ``/myapplication`` instead of ``/``, :func:`~flask.url_for` properly + handles that for you. + +For example, here we use the :meth:`~flask.Flask.test_request_context` method +to try out :func:`~flask.url_for`. :meth:`~flask.Flask.test_request_context` +tells Flask to behave as though it's handling a request even while we use a +Python shell. See :doc:`/appcontext`. + +.. code-block:: python + + from flask import url_for + + @app.route('/') + def index(): + return 'index' + + @app.route('/login') + def login(): + return 'login' + + @app.route('/user/') + def profile(username): + return f'{username}\'s profile' + + with app.test_request_context(): + print(url_for('index')) + print(url_for('login')) + print(url_for('login', next='/')) + print(url_for('profile', username='John Doe')) + +.. code-block:: text + + / + /login + /login?next=/ + /user/John%20Doe + + +HTTP Methods +```````````` + +Web applications use different HTTP methods when accessing URLs. You should +familiarize yourself with the HTTP methods as you work with Flask. By default, +a route only answers to ``GET`` requests. You can use the ``methods`` argument +of the :meth:`~flask.Flask.route` decorator to handle different HTTP methods. +:: + + from flask import request + + @app.route('/login', methods=['GET', 'POST']) + def login(): + if request.method == 'POST': + return do_the_login() + else: + return show_the_login_form() + +The example above keeps all methods for the route within one function, +which can be useful if each part uses some common data. + +You can also separate views for different methods into different +functions. Flask provides a shortcut for decorating such routes with +:meth:`~flask.Flask.get`, :meth:`~flask.Flask.post`, etc. for each +common HTTP method. + +.. code-block:: python + + @app.get('/login') + def login_get(): + return show_the_login_form() + + @app.post('/login') + def login_post(): + return do_the_login() + +If ``GET`` is present, Flask automatically adds support for the ``HEAD`` method +and handles ``HEAD`` requests according to the `HTTP RFC`_. Likewise, +``OPTIONS`` is automatically implemented for you. + +.. _HTTP RFC: https://www.ietf.org/rfc/rfc2068.txt + +Static Files +------------ + +Dynamic web applications also need static files. That's usually where +the CSS and JavaScript files are coming from. Ideally your web server is +configured to serve them for you, but during development Flask can do that +as well. Just create a folder called :file:`static` in your package or next to +your module and it will be available at ``/static`` on the application. + +To generate URLs for static files, use the special ``'static'`` endpoint name:: + + url_for('static', filename='style.css') + +The file has to be stored on the filesystem as :file:`static/style.css`. + +Rendering Templates +------------------- + +Generating HTML from within Python is not fun, and actually pretty +cumbersome because you have to do the HTML escaping on your own to keep +the application secure. Because of that Flask configures the `Jinja +`_ template engine for you automatically. + +Templates can be used to generate any type of text file. For web applications, you'll +primarily be generating HTML pages, but you can also generate markdown, plain text for +emails, and anything else. + +For a reference to HTML, CSS, and other web APIs, use the `MDN Web Docs`_. + +.. _MDN Web Docs: https://developer.mozilla.org/ + +To render a template you can use the :func:`~flask.render_template` +method. All you have to do is provide the name of the template and the +variables you want to pass to the template engine as keyword arguments. +Here's a simple example of how to render a template:: + + from flask import render_template + + @app.route('/hello/') + @app.route('/hello/') + def hello(name=None): + return render_template('hello.html', person=name) + +Flask will look for templates in the :file:`templates` folder. So if your +application is a module, this folder is next to that module, if it's a +package it's actually inside your package: + +**Case 1**: a module:: + + /application.py + /templates + /hello.html + +**Case 2**: a package:: + + /application + /__init__.py + /templates + /hello.html + +For templates you can use the full power of Jinja templates. Head over +to the official `Jinja Template Documentation +`_ for more information. + +Here is an example template: + +.. sourcecode:: html+jinja + + + Hello from Flask + {% if person %} +

Hello {{ person }}!

+ {% else %} +

Hello, World!

+ {% endif %} + +Inside templates you also have access to the :data:`~flask.Flask.config`, +:class:`~flask.request`, :class:`~flask.session` and :class:`~flask.g` [#]_ objects +as well as the :func:`~flask.url_for` and :func:`~flask.get_flashed_messages` functions. + +Templates are especially useful if inheritance is used. If you want to +know how that works, see :doc:`patterns/templateinheritance`. Basically +template inheritance makes it possible to keep certain elements on each +page (like header, navigation and footer). + +Automatic escaping is enabled, so if ``person`` contains HTML it will be escaped +automatically. If you can trust a variable and you know that it will be +safe HTML (for example because it came from a module that converts wiki +markup to HTML) you can mark it as safe by using the +:class:`~markupsafe.Markup` class or by using the ``|safe`` filter in the +template. Head over to the Jinja 2 documentation for more examples. + +Here is a basic introduction to how the :class:`~markupsafe.Markup` class works:: + + >>> from markupsafe import Markup + >>> Markup('Hello %s!') % 'hacker' + Markup('Hello <blink>hacker</blink>!') + >>> Markup.escape('hacker') + Markup('<blink>hacker</blink>') + >>> Markup('Marked up » HTML').striptags() + 'Marked up » HTML' + +.. versionchanged:: 0.5 + + Autoescaping is no longer enabled for all templates. The following + extensions for templates trigger autoescaping: ``.html``, ``.htm``, + ``.xml``, ``.xhtml``. Templates loaded from a string will have + autoescaping disabled. + +.. [#] Unsure what that :class:`~flask.g` object is? It's something in which + you can store information for your own needs. See the documentation + for :class:`flask.g` and :doc:`patterns/sqlite3`. + + +Accessing Request Data +---------------------- + +For web applications it's crucial to react to the data a client sends to the +server. In Flask this information is provided by the global :data:`.request` +object, which is an instance of :class:`.Request`. This object has many +attributes and methods to work with the incoming request data, but here is a +broad overview. First it needs to be imported. + +.. code-block:: python + + from flask import request + +If you have some experience with Python you might be wondering how that object +can be global when Flask handles multiple requests at a time. The answer is +that :data:`.request` is actually a proxy, pointing at whatever request is +currently being handled by a given worker, which is managed interanlly by Flask +and Python. See :doc:`/appcontext` for much more information. + +The current request method is available in the :attr:`~.Request.method` +attribute. To access form data (data transmitted in a ``POST`` or ``PUT`` +request), use the :attr:`~flask.Request.form` attribute, which behaves like a +dict. + +.. code-block:: python + + @app.route("/login", methods=["GET", "POST"]) + def login(): + error = None + + if request.method == "POST": + if valid_login(request.form["username"], request.form["password"]): + return store_login(request.form["username"]) + else: + error = "Invalid username or password" + + # Executed if the request method was GET or the credentials were invalid. + return render_template("login.html", error=error) + +If the key does not exist in ``form``, a special :exc:`KeyError` is raised. You +can catch it like a normal ``KeyError``, otherwise it will return a HTTP 400 +Bad Request error page. You can also use the +:meth:`~werkzeug.datastructures.MultiDict.get` method to get a default +instead of an error. + +To access parameters submitted in the URL (``?key=value``), use the +:attr:`~.Request.args` attribute. Key errors behave the same as ``form``, +returning a 400 response if not caught. + +.. code-block:: python + + searchword = request.args.get('key', '') + +For a full list of methods and attributes of the request object, see the +:class:`~.Request` documentation. + + +File Uploads +```````````` + +You can handle uploaded files with Flask easily. Just make sure not to +forget to set the ``enctype="multipart/form-data"`` attribute on your HTML +form, otherwise the browser will not transmit your files at all. + +Uploaded files are stored in memory or at a temporary location on the +filesystem. You can access those files by looking at the +:attr:`~flask.request.files` attribute on the request object. Each +uploaded file is stored in that dictionary. It behaves just like a +standard Python :class:`file` object, but it also has a +:meth:`~werkzeug.datastructures.FileStorage.save` method that +allows you to store that file on the filesystem of the server. +Here is a simple example showing how that works:: + + from flask import request + + @app.route('/upload', methods=['GET', 'POST']) + def upload_file(): + if request.method == 'POST': + f = request.files['the_file'] + f.save('/var/www/uploads/uploaded_file.txt') + ... + +If you want to know how the file was named on the client before it was +uploaded to your application, you can access the +:attr:`~werkzeug.datastructures.FileStorage.filename` attribute. +However please keep in mind that this value can be forged +so never ever trust that value. If you want to use the filename +of the client to store the file on the server, pass it through the +:func:`~werkzeug.utils.secure_filename` function that +Werkzeug provides for you:: + + from werkzeug.utils import secure_filename + + @app.route('/upload', methods=['GET', 'POST']) + def upload_file(): + if request.method == 'POST': + file = request.files['the_file'] + file.save(f"/var/www/uploads/{secure_filename(file.filename)}") + ... + +For some better examples, see :doc:`patterns/fileuploads`. + +Cookies +``````` + +To access cookies you can use the :attr:`~flask.Request.cookies` +attribute. To set cookies you can use the +:attr:`~flask.Response.set_cookie` method of response objects. The +:attr:`~flask.Request.cookies` attribute of request objects is a +dictionary with all the cookies the client transmits. If you want to use +sessions, do not use the cookies directly but instead use the +:ref:`sessions` in Flask that add some security on top of cookies for you. + +Reading cookies:: + + from flask import request + + @app.route('/') + def index(): + username = request.cookies.get('username') + # use cookies.get(key) instead of cookies[key] to not get a + # KeyError if the cookie is missing. + +Storing cookies:: + + from flask import make_response + + @app.route('/') + def index(): + resp = make_response(render_template(...)) + resp.set_cookie('username', 'the username') + return resp + +Note that cookies are set on response objects. Since you normally +just return strings from the view functions Flask will convert them into +response objects for you. If you explicitly want to do that you can use +the :meth:`~flask.make_response` function and then modify it. + +Sometimes you might want to set a cookie at a point where the response +object does not exist yet. This is possible by utilizing the +:doc:`patterns/deferredcallbacks` pattern. + +For this also see :ref:`about-responses`. + +Redirects and Errors +-------------------- + +To redirect a user to another endpoint, use the :func:`~flask.redirect` +function; to abort a request early with an error code, use the +:func:`~flask.abort` function:: + + from flask import abort, redirect, url_for + + @app.route('/') + def index(): + return redirect(url_for('login')) + + @app.route('/login') + def login(): + abort(401) + this_is_never_executed() + +This is a rather pointless example because a user will be redirected from +the index to a page they cannot access (401 means access denied) but it +shows how that works. + +By default a black and white error page is shown for each error code. If +you want to customize the error page, you can use the +:meth:`~flask.Flask.errorhandler` decorator:: + + from flask import render_template + + @app.errorhandler(404) + def page_not_found(error): + return render_template('page_not_found.html'), 404 + +Note the ``404`` after the :func:`~flask.render_template` call. This +tells Flask that the status code of that page should be 404 which means +not found. By default 200 is assumed which translates to: all went well. + +See :doc:`errorhandling` for more details. + +.. _about-responses: + +About Responses +--------------- + +The return value from a view function is automatically converted into +a response object for you. If the return value is a string it's +converted into a response object with the string as response body, a +``200 OK`` status code and a :mimetype:`text/html` mimetype. If the +return value is a dict or list, :func:`jsonify` is called to produce a +response. The logic that Flask applies to converting return values into +response objects is as follows: + +1. If a response object of the correct type is returned it's directly + returned from the view. +2. If it's a string, a response object is created with that data and + the default parameters. +3. If it's an iterator or generator returning strings or bytes, it is + treated as a streaming response. +4. If it's a dict or list, a response object is created using + :func:`~flask.json.jsonify`. +5. If a tuple is returned the items in the tuple can provide extra + information. Such tuples have to be in the form + ``(response, status)``, ``(response, headers)``, or + ``(response, status, headers)``. The ``status`` value will override + the status code and ``headers`` can be a list or dictionary of + additional header values. +6. If none of that works, Flask will assume the return value is a + valid WSGI application and convert that into a response object. + +If you want to get hold of the resulting response object inside the view +you can use the :func:`~flask.make_response` function. + +Imagine you have a view like this:: + + from flask import render_template + + @app.errorhandler(404) + def not_found(error): + return render_template('error.html'), 404 + +You just need to wrap the return expression with +:func:`~flask.make_response` and get the response object to modify it, then +return it:: + + from flask import make_response + + @app.errorhandler(404) + def not_found(error): + resp = make_response(render_template('error.html'), 404) + resp.headers['X-Something'] = 'A value' + return resp + + +APIs with JSON +`````````````` + +A common response format when writing an API is JSON. It's easy to get +started writing such an API with Flask. If you return a ``dict`` or +``list`` from a view, it will be converted to a JSON response. + +.. code-block:: python + + @app.route("/me") + def me_api(): + user = get_current_user() + return { + "username": user.username, + "theme": user.theme, + "image": url_for("user_image", filename=user.image), + } + + @app.route("/users") + def users_api(): + users = get_all_users() + return [user.to_json() for user in users] + +This is a shortcut to passing the data to the +:func:`~flask.json.jsonify` function, which will serialize any supported +JSON data type. That means that all the data in the dict or list must be +JSON serializable. + +For complex types such as database models, you'll want to use a +serialization library to convert the data to valid JSON types first. +There are many serialization libraries and Flask API extensions +maintained by the community that support more complex applications. + + +.. _sessions: + +Sessions +-------- + +In addition to the request object there is also a second object called +:class:`~flask.session` which allows you to store information specific to a +user from one request to the next. This is implemented on top of cookies +for you and signs the cookies cryptographically. What this means is that +the user could look at the contents of your cookie but not modify it, +unless they know the secret key used for signing. + +In order to use sessions you have to set a secret key. Here is how +sessions work:: + + from flask import session + + # Set the secret key to some random bytes. Keep this really secret! + app.secret_key = b'_5#y2L"F4Q8z\n\xec]/' + + @app.route('/') + def index(): + if 'username' in session: + return f'Logged in as {session["username"]}' + return 'You are not logged in' + + @app.route('/login', methods=['GET', 'POST']) + def login(): + if request.method == 'POST': + session['username'] = request.form['username'] + return redirect(url_for('index')) + return ''' +
+

+

+

+ ''' + + @app.route('/logout') + def logout(): + # remove the username from the session if it's there + session.pop('username', None) + return redirect(url_for('index')) + +.. admonition:: How to generate good secret keys + + A secret key should be as random as possible. Your operating system has + ways to generate pretty random data based on a cryptographic random + generator. Use the following command to quickly generate a value for + :attr:`Flask.secret_key` (or :data:`SECRET_KEY`):: + + $ python -c 'import secrets; print(secrets.token_hex())' + '192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf' + +A note on cookie-based sessions: Flask will take the values you put into the +session object and serialize them into a cookie. If you are finding some +values do not persist across requests, cookies are indeed enabled, and you are +not getting a clear error message, check the size of the cookie in your page +responses compared to the size supported by web browsers. + +Besides the default client-side based sessions, if you want to handle +sessions on the server-side instead, there are several +Flask extensions that support this. + +Message Flashing +---------------- + +Good applications and user interfaces are all about feedback. If the user +does not get enough feedback they will probably end up hating the +application. Flask provides a really simple way to give feedback to a +user with the flashing system. The flashing system basically makes it +possible to record a message at the end of a request and access it on the next +(and only the next) request. This is usually combined with a layout +template to expose the message. + +To flash a message use the :func:`~flask.flash` method, to get hold of the +messages you can use :func:`~flask.get_flashed_messages` which is also +available in the templates. See :doc:`patterns/flashing` for a full +example. + +Logging +------- + +.. versionadded:: 0.3 + +Sometimes you might be in a situation where you deal with data that +should be correct, but actually is not. For example you may have +some client-side code that sends an HTTP request to the server +but it's obviously malformed. This might be caused by a user tampering +with the data, or the client code failing. Most of the time it's okay +to reply with ``400 Bad Request`` in that situation, but sometimes +that won't do and the code has to continue working. + +You may still want to log that something fishy happened. This is where +loggers come in handy. As of Flask 0.3 a logger is preconfigured for you +to use. + +Here are some example log calls:: + + app.logger.debug('A value for debugging') + app.logger.warning('A warning occurred (%d apples)', 42) + app.logger.error('An error occurred') + +The attached :attr:`~flask.Flask.logger` is a standard logging +:class:`~logging.Logger`, so head over to the official :mod:`logging` +docs for more information. + +See :doc:`errorhandling`. + + +Hooking in WSGI Middleware +-------------------------- + +To add WSGI middleware to your Flask application, wrap the application's +``wsgi_app`` attribute. For example, to apply Werkzeug's +:class:`~werkzeug.middleware.proxy_fix.ProxyFix` middleware for running +behind Nginx: + +.. code-block:: python + + from werkzeug.middleware.proxy_fix import ProxyFix + app.wsgi_app = ProxyFix(app.wsgi_app) + +Wrapping ``app.wsgi_app`` instead of ``app`` means that ``app`` still +points at your Flask application, not at the middleware, so you can +continue to use and configure ``app`` directly. + +Using Flask Extensions +---------------------- + +Extensions are packages that help you accomplish common tasks. For +example, Flask-SQLAlchemy provides SQLAlchemy support that makes it simple +and easy to use with Flask. + +For more on Flask extensions, see :doc:`extensions`. + +Deploying to a Web Server +------------------------- + +Ready to deploy your new Flask app? See :doc:`deploying/index`. diff --git a/src/flask-main/docs/reqcontext.rst b/src/flask-main/docs/reqcontext.rst new file mode 100644 index 0000000..6660671 --- /dev/null +++ b/src/flask-main/docs/reqcontext.rst @@ -0,0 +1,6 @@ +:orphan: + +The Request Context +=================== + +Obsolete, see :doc:`/appcontext` instead. diff --git a/src/flask-main/docs/server.rst b/src/flask-main/docs/server.rst new file mode 100644 index 0000000..d6beb1d --- /dev/null +++ b/src/flask-main/docs/server.rst @@ -0,0 +1,115 @@ +.. currentmodule:: flask + +Development Server +================== + +Flask provides a ``run`` command to run the application with a development server. In +debug mode, this server provides an interactive debugger and will reload when code is +changed. + +.. 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. + + See :doc:`/deploying/index` for deployment options. + +Command Line +------------ + +The ``flask run`` CLI command is the recommended way to run the development server. Use +the ``--app`` option to point to your application, and the ``--debug`` option to enable +debug mode. + +.. code-block:: text + + $ flask --app hello run --debug + +This enables debug mode, including the interactive debugger and reloader, and then +starts the server on http://localhost:5000/. Use ``flask run --help`` to see the +available options, and :doc:`/cli` for detailed instructions about configuring and using +the CLI. + + +.. _address-already-in-use: + +Address already in use +~~~~~~~~~~~~~~~~~~~~~~ + +If another program is already using port 5000, you'll see an ``OSError`` +when the server tries to start. It may have one of the following +messages: + +- ``OSError: [Errno 98] Address already in use`` +- ``OSError: [WinError 10013] An attempt was made to access a socket + in a way forbidden by its access permissions`` + +Either identify and stop the other program, or use +``flask run --port 5001`` to pick a different port. + +You can use ``netstat`` or ``lsof`` to identify what process id is using +a port, then use other operating system tools stop that process. The +following example shows that process id 6847 is using port 5000. + +.. tabs:: + + .. tab:: ``netstat`` (Linux) + + .. code-block:: text + + $ netstat -nlp | grep 5000 + tcp 0 0 127.0.0.1:5000 0.0.0.0:* LISTEN 6847/python + + .. tab:: ``lsof`` (macOS / Linux) + + .. code-block:: text + + $ lsof -P -i :5000 + Python 6847 IPv4 TCP localhost:5000 (LISTEN) + + .. tab:: ``netstat`` (Windows) + + .. code-block:: text + + > netstat -ano | findstr 5000 + TCP 127.0.0.1:5000 0.0.0.0:0 LISTENING 6847 + +macOS Monterey and later automatically starts a service that uses port +5000. You can choose to disable this service instead of using a different port by +searching for "AirPlay Receiver" in System Settings and toggling it off. + + +Deferred Errors on Reload +~~~~~~~~~~~~~~~~~~~~~~~~~ + +When using the ``flask run`` command with the reloader, the server will +continue to run even if you introduce syntax errors or other +initialization errors into the code. Accessing the site will show the +interactive debugger for the error, rather than crashing the server. + +If a syntax error is already present when calling ``flask run``, it will +fail immediately and show the traceback rather than waiting until the +site is accessed. This is intended to make errors more visible initially +while still allowing the server to handle errors on reload. + + +In Code +------- + +The development server can also be started from Python with the :meth:`Flask.run` +method. This method takes arguments similar to the CLI options to control the server. +The main difference from the CLI command is that the server will crash if there are +errors when reloading. ``debug=True`` can be passed to enable debug mode. + +Place the call in a main block, otherwise it will interfere when trying to import and +run the application with a production server later. + +.. code-block:: python + + if __name__ == "__main__": + app.run(debug=True) + +.. code-block:: text + + $ python hello.py diff --git a/src/flask-main/docs/shell.rst b/src/flask-main/docs/shell.rst new file mode 100644 index 0000000..d8821e2 --- /dev/null +++ b/src/flask-main/docs/shell.rst @@ -0,0 +1,81 @@ +Working with the Shell +====================== + +One of the reasons everybody loves Python is the interactive shell. It allows +you to play around with code in real time and immediately get results back. +Flask provides the ``flask shell`` CLI command to start an interactive Python +shell with some setup done to make working with the Flask app easier. + +.. code-block:: text + + $ flask shell + +Creating a Request Context +-------------------------- + +``flask shell`` pushes an app context automatically, so :data:`.current_app` and +:data:`.g` are already available. However, there is no HTTP request being +handled in the shell, so :data:`.request` and :data:`.session` are not yet +available. + +The easiest way to create a proper request context from the shell is by +using the :attr:`~flask.Flask.test_request_context` method which creates +us a :class:`~flask.ctx.RequestContext`: + +>>> ctx = app.test_request_context() + +Normally you would use the ``with`` statement to make this context active, but +in the shell it's easier to call :meth:`~.RequestContext.push` and +:meth:`~.RequestContext.pop` manually: + +>>> ctx.push() + +From that point onwards you can work with the request object until you call +``pop``: + +>>> ctx.pop() + +Firing Before/After Request +--------------------------- + +By just creating a request context, you still don't have run the code that +is normally run before a request. This might result in your database +being unavailable if you are connecting to the database in a +before-request callback or the current user not being stored on the +:data:`~flask.g` object etc. + +This however can easily be done yourself. Just call +:meth:`~flask.Flask.preprocess_request`: + +>>> ctx = app.test_request_context() +>>> ctx.push() +>>> app.preprocess_request() + +Keep in mind that the :meth:`~flask.Flask.preprocess_request` function +might return a response object, in that case just ignore it. + +To shutdown a request, you need to trick a bit before the after request +functions (triggered by :meth:`~flask.Flask.process_response`) operate on +a response object: + +>>> app.process_response(app.response_class()) + +>>> ctx.pop() + +The functions registered as :meth:`~flask.Flask.teardown_request` are +automatically called when the context is popped. So this is the perfect +place to automatically tear down resources that were needed by the request +context (such as database connections). + + +Further Improving the Shell Experience +-------------------------------------- + +If you like the idea of experimenting in a shell, create yourself a module +with stuff you want to star import into your interactive session. There +you could also define some more helper methods for common things such as +initializing the database, dropping tables etc. + +Just put them into a module (like `shelltools`) and import from there: + +>>> from shelltools import * diff --git a/src/flask-main/docs/signals.rst b/src/flask-main/docs/signals.rst new file mode 100644 index 0000000..7ca81a9 --- /dev/null +++ b/src/flask-main/docs/signals.rst @@ -0,0 +1,166 @@ +Signals +======= + +Signals are a lightweight way to notify subscribers of certain events during the +lifecycle of the application and each request. When an event occurs, it emits the +signal, which calls each subscriber. + +Signals are implemented by the `Blinker`_ library. See its documentation for detailed +information. Flask provides some built-in signals. Extensions may provide their own. + +Many signals mirror Flask's decorator-based callbacks with similar names. For example, +the :data:`.request_started` signal is similar to the :meth:`~.Flask.before_request` +decorator. The advantage of signals over handlers is that they can be subscribed to +temporarily, and can't directly affect the application. This is useful for testing, +metrics, auditing, and more. For example, if you want to know what templates were +rendered at what parts of what requests, there is a signal that will notify you of that +information. + + +Core Signals +------------ + +See :ref:`core-signals-list` for a list of all built-in signals. The :doc:`lifecycle` +page also describes the order that signals and decorators execute. + + +Subscribing to Signals +---------------------- + +To subscribe to a signal, you can use the +:meth:`~blinker.base.Signal.connect` method of a signal. The first +argument is the function that should be called when the signal is emitted, +the optional second argument specifies a sender. To unsubscribe from a +signal, you can use the :meth:`~blinker.base.Signal.disconnect` method. + +For all core Flask signals, the sender is the application that issued the +signal. When you subscribe to a signal, be sure to also provide a sender +unless you really want to listen for signals from all applications. This is +especially true if you are developing an extension. + +For example, here is a helper context manager that can be used in a unit test +to determine which templates were rendered and what variables were passed +to the template:: + + from flask import template_rendered + from contextlib import contextmanager + + @contextmanager + def captured_templates(app): + recorded = [] + def record(sender, template, context, **extra): + recorded.append((template, context)) + template_rendered.connect(record, app) + try: + yield recorded + finally: + template_rendered.disconnect(record, app) + +This can now easily be paired with a test client:: + + with captured_templates(app) as templates: + rv = app.test_client().get('/') + assert rv.status_code == 200 + assert len(templates) == 1 + template, context = templates[0] + assert template.name == 'index.html' + assert len(context['items']) == 10 + +Make sure to subscribe with an extra ``**extra`` argument so that your +calls don't fail if Flask introduces new arguments to the signals. + +All the template rendering in the code issued by the application `app` +in the body of the ``with`` block will now be recorded in the `templates` +variable. Whenever a template is rendered, the template object as well as +context are appended to it. + +Additionally there is a convenient helper method +(:meth:`~blinker.base.Signal.connected_to`) that allows you to +temporarily subscribe a function to a signal with a context manager on +its own. Because the return value of the context manager cannot be +specified that way, you have to pass the list in as an argument:: + + from flask import template_rendered + + def captured_templates(app, recorded, **extra): + def record(sender, template, context): + recorded.append((template, context)) + return template_rendered.connected_to(record, app) + +The example above would then look like this:: + + templates = [] + with captured_templates(app, templates, **extra): + ... + template, context = templates[0] + +Creating Signals +---------------- + +If you want to use signals in your own application, you can use the +blinker library directly. The most common use case are named signals in a +custom :class:`~blinker.base.Namespace`. This is what is recommended +most of the time:: + + from blinker import Namespace + my_signals = Namespace() + +Now you can create new signals like this:: + + model_saved = my_signals.signal('model-saved') + +The name for the signal here makes it unique and also simplifies +debugging. You can access the name of the signal with the +:attr:`~blinker.base.NamedSignal.name` attribute. + +.. _signals-sending: + +Sending Signals +--------------- + +If you want to emit a signal, you can do so by calling the +:meth:`~blinker.base.Signal.send` method. It accepts a sender as first +argument and optionally some keyword arguments that are forwarded to the +signal subscribers:: + + class Model(object): + ... + + def save(self): + model_saved.send(self) + +Try to always pick a good sender. If you have a class that is emitting a +signal, pass ``self`` as sender. If you are emitting a signal from a random +function, you can pass ``current_app._get_current_object()`` as sender. + +.. admonition:: Passing Proxies as Senders + + Never pass :data:`~flask.current_app` as sender to a signal. Use + ``current_app._get_current_object()`` instead. The reason for this is + that :data:`~flask.current_app` is a proxy and not the real application + object. + + +Signals and Flask's Request Context +----------------------------------- + +Context-local proxies are available between :data:`~flask.request_started` and +:data:`~flask.request_finished`, so you can rely on :class:`flask.g` and others +as needed. Note the limitations described in :ref:`signals-sending` and the +:data:`~flask.request_tearing_down` signal. + + +Decorator Based Signal Subscriptions +------------------------------------ + +You can also easily subscribe to signals by using the +:meth:`~blinker.base.NamedSignal.connect_via` decorator:: + + from flask import template_rendered + + @template_rendered.connect_via(app) + def when_template_rendered(sender, template, context, **extra): + print(f'Template {template.name} is rendered with {context}') + + +.. _blinker: https://pypi.org/project/blinker/ diff --git a/src/flask-main/docs/templating.rst b/src/flask-main/docs/templating.rst new file mode 100644 index 0000000..1bde59d --- /dev/null +++ b/src/flask-main/docs/templating.rst @@ -0,0 +1,255 @@ +Templates +========= + +Flask leverages Jinja as its template engine. You are obviously free to use +a different template engine, but you still have to install Jinja to run +Flask itself. This requirement is necessary to enable rich extensions. +An extension can depend on Jinja being present. + +This section only gives a very quick introduction into how Jinja +is integrated into Flask. If you want information on the template +engine's syntax itself, head over to the official `Jinja Template +Documentation `_ for +more information. + +Jinja Setup +----------- + +Unless customized, Jinja is configured by Flask as follows: + +- autoescaping is enabled for all templates ending in ``.html``, + ``.htm``, ``.xml``, ``.xhtml``, as well as ``.svg`` when using + :func:`~flask.templating.render_template`. +- autoescaping is enabled for all strings when using + :func:`~flask.templating.render_template_string`. +- a template has the ability to opt in/out autoescaping with the + ``{% autoescape %}`` tag. +- Flask inserts a couple of global functions and helpers into the + Jinja context, additionally to the values that are present by + default. + +Standard Context +---------------- + +The following global variables are available within Jinja templates +by default: + +.. data:: config + :noindex: + + The current configuration object (:data:`flask.Flask.config`) + + .. versionadded:: 0.6 + + .. versionchanged:: 0.10 + This is now always available, even in imported templates. + +.. data:: request + :noindex: + + The current request object (:class:`flask.request`). This variable is + unavailable if the template was rendered without an active request + context. + +.. data:: session + :noindex: + + The current session object (:class:`flask.session`). This variable + is unavailable if the template was rendered without an active request + context. + +.. data:: g + :noindex: + + The request-bound object for global variables (:data:`flask.g`). This + variable is unavailable if the template was rendered without an active + request context. + +.. function:: url_for + :noindex: + + The :func:`flask.url_for` function. + +.. function:: get_flashed_messages + :noindex: + + The :func:`flask.get_flashed_messages` function. + +.. admonition:: The Jinja Context Behavior + + These variables are added to the context of variables, they are not + global variables. The difference is that by default these will not + show up in the context of imported templates. This is partially caused + by performance considerations, partially to keep things explicit. + + What does this mean for you? If you have a macro you want to import, + that needs to access the request object you have two possibilities: + + 1. you explicitly pass the request to the macro as parameter, or + the attribute of the request object you are interested in. + 2. you import the macro "with context". + + Importing with context looks like this: + + .. sourcecode:: jinja + + {% from '_helpers.html' import my_macro with context %} + + +Controlling Autoescaping +------------------------ + +Autoescaping is the concept of automatically escaping special characters +for you. Special characters in the sense of HTML (or XML, and thus XHTML) +are ``&``, ``>``, ``<``, ``"`` as well as ``'``. Because these characters +carry specific meanings in documents on their own you have to replace them +by so called "entities" if you want to use them for text. Not doing so +would not only cause user frustration by the inability to use these +characters in text, but can also lead to security problems. (see +:ref:`security-xss`) + +Sometimes however you will need to disable autoescaping in templates. +This can be the case if you want to explicitly inject HTML into pages, for +example if they come from a system that generates secure HTML like a +markdown to HTML converter. + +There are three ways to accomplish that: + +- In the Python code, wrap the HTML string in a :class:`~markupsafe.Markup` + object before passing it to the template. This is in general the + recommended way. +- Inside the template, use the ``|safe`` filter to explicitly mark a + string as safe HTML (``{{ myvariable|safe }}``) +- Temporarily disable the autoescape system altogether. + +To disable the autoescape system in templates, you can use the ``{% +autoescape %}`` block: + +.. sourcecode:: html+jinja + + {% autoescape false %} +

autoescaping is disabled here +

{{ will_not_be_escaped }} + {% endautoescape %} + +Whenever you do this, please be very cautious about the variables you are +using in this block. + +.. _registering-filters: + +Registering Filters, Tests, and Globals +--------------------------------------- + +The Flask app and blueprints provide decorators and methods to register your own +filters, tests, and global functions for use in Jinja templates. They all follow +the same pattern, so the following examples only discuss filters. + +Decorate a function with :meth:`~.Flask.template_filter` to register it as a +template filter. + +.. code-block:: python + + @app.template_filter + def reverse(s): + return reversed(s) + +.. code-block:: jinja + + {% for item in data | reverse %} + {% endfor %} + +By default it will use the name of the function as the name of the filter, but +that can be changed by passing a name to the decorator. + +.. code-block:: python + + @app.template_filter("reverse") + def reverse_filter(s): + return reversed(s) + +A filter can be registered separately using :meth:`~.Flask.add_template_filter`. +The name is optional and will use the function name if not given. + +.. code-block:: python + + def reverse_filter(s): + return reversed(s) + + app.add_template_filter(reverse_filter, "reverse") + +For template tests, use the :meth:`~.Flask.template_test` decorator or +:meth:`~.Flask.add_template_test` method. For template global functions, use the +:meth:`~.Flask.template_global` decorator or :meth:`~.Flask.add_template_global` +method. + +The same methods also exist on :class:`.Blueprint`, prefixed with ``app_`` to +indicate that the registered functions will be avaialble to all templates, not +only when rendering from within the blueprint. + +The Jinja environment is also available as :attr:`~.Flask.jinja_env`. It may be +modified directly, as you would when using Jinja outside Flask. + + +Context Processors +------------------ + +To inject new variables automatically into the context of a template, +context processors exist in Flask. Context processors run before the +template is rendered and have the ability to inject new values into the +template context. A context processor is a function that returns a +dictionary. The keys and values of this dictionary are then merged with +the template context, for all templates in the app:: + + @app.context_processor + def inject_user(): + return dict(user=g.user) + +The context processor above makes a variable called `user` available in +the template with the value of `g.user`. This example is not very +interesting because `g` is available in templates anyways, but it gives an +idea how this works. + +Variables are not limited to values; a context processor can also make +functions available to templates (since Python allows passing around +functions):: + + @app.context_processor + def utility_processor(): + def format_price(amount, currency="€"): + return f"{amount:.2f}{currency}" + return dict(format_price=format_price) + +The context processor above makes the `format_price` function available to all +templates:: + + {{ format_price(0.33) }} + +You could also build `format_price` as a template filter (see +:ref:`registering-filters`), but this demonstrates how to pass functions in a +context processor. + +Streaming +--------- + +It can be useful to not render the whole template as one complete +string, instead render it as a stream, yielding smaller incremental +strings. This can be used for streaming HTML in chunks to speed up +initial page load, or to save memory when rendering a very large +template. + +The Jinja template engine supports rendering a template piece +by piece, returning an iterator of strings. Flask provides the +:func:`~flask.stream_template` and :func:`~flask.stream_template_string` +functions to make this easier to use. + +.. code-block:: python + + from flask import stream_template + + @app.get("/timeline") + def timeline(): + return stream_template("timeline.html") + +These functions automatically apply the +:func:`~flask.stream_with_context` wrapper if a request is active, so +that it remains available in the template. diff --git a/src/flask-main/docs/testing.rst b/src/flask-main/docs/testing.rst new file mode 100644 index 0000000..c171abd --- /dev/null +++ b/src/flask-main/docs/testing.rst @@ -0,0 +1,318 @@ +Testing Flask Applications +========================== + +Flask provides utilities for testing an application. This documentation +goes over techniques for working with different parts of the application +in tests. + +We will use the `pytest`_ framework to set up and run our tests. + +.. code-block:: text + + $ pip install pytest + +.. _pytest: https://docs.pytest.org/ + +The :doc:`tutorial ` goes over how to write tests for +100% coverage of the sample Flaskr blog application. See +:doc:`the tutorial on tests ` for a detailed +explanation of specific tests for an application. + + +Identifying Tests +----------------- + +Tests are typically located in the ``tests`` folder. Tests are functions +that start with ``test_``, in Python modules that start with ``test_``. +Tests can also be further grouped in classes that start with ``Test``. + +It can be difficult to know what to test. Generally, try to test the +code that you write, not the code of libraries that you use, since they +are already tested. Try to extract complex behaviors as separate +functions to test individually. + + +Fixtures +-------- + +Pytest *fixtures* allow writing pieces of code that are reusable across +tests. A simple fixture returns a value, but a fixture can also do +setup, yield a value, then do teardown. Fixtures for the application, +test client, and CLI runner are shown below, they can be placed in +``tests/conftest.py``. + +If you're using an +:doc:`application factory `, define an ``app`` +fixture to create and configure an app instance. You can add code before +and after the ``yield`` to set up and tear down other resources, such as +creating and clearing a database. + +If you're not using a factory, you already have an app object you can +import and configure directly. You can still use an ``app`` fixture to +set up and tear down resources. + +.. code-block:: python + + import pytest + from my_project import create_app + + @pytest.fixture() + def app(): + app = create_app() + app.config.update({ + "TESTING": True, + }) + + # other setup can go here + + yield app + + # clean up / reset resources here + + + @pytest.fixture() + def client(app): + return app.test_client() + + + @pytest.fixture() + def runner(app): + return app.test_cli_runner() + + +Sending Requests with the Test Client +------------------------------------- + +The test client makes requests to the application without running a live +server. Flask's client extends +:doc:`Werkzeug's client `, see those docs for additional +information. + +The ``client`` has methods that match the common HTTP request methods, +such as ``client.get()`` and ``client.post()``. They take many arguments +for building the request; you can find the full documentation in +:class:`~werkzeug.test.EnvironBuilder`. Typically you'll use ``path``, +``query_string``, ``headers``, and ``data`` or ``json``. + +To make a request, call the method the request should use with the path +to the route to test. A :class:`~werkzeug.test.TestResponse` is returned +to examine the response data. It has all the usual properties of a +response object. You'll usually look at ``response.data``, which is the +bytes returned by the view. If you want to use text, Werkzeug 2.1 +provides ``response.text``, or use ``response.get_data(as_text=True)``. + +.. code-block:: python + + def test_request_example(client): + response = client.get("/posts") + assert b"

Hello, World!

" in response.data + + +Pass a dict ``query_string={"key": "value", ...}`` to set arguments in +the query string (after the ``?`` in the URL). Pass a dict +``headers={}`` to set request headers. + +To send a request body in a POST or PUT request, pass a value to +``data``. If raw bytes are passed, that exact body is used. Usually, +you'll pass a dict to set form data. + + +Form Data +~~~~~~~~~ + +To send form data, pass a dict to ``data``. The ``Content-Type`` header +will be set to ``multipart/form-data`` or +``application/x-www-form-urlencoded`` automatically. + +If a value is a file object opened for reading bytes (``"rb"`` mode), it +will be treated as an uploaded file. To change the detected filename and +content type, pass a ``(file, filename, content_type)`` tuple. File +objects will be closed after making the request, so they do not need to +use the usual ``with open() as f:`` pattern. + +It can be useful to store files in a ``tests/resources`` folder, then +use ``pathlib.Path`` to get files relative to the current test file. + +.. code-block:: python + + from pathlib import Path + + # get the resources folder in the tests folder + resources = Path(__file__).parent / "resources" + + def test_edit_user(client): + response = client.post("/user/2/edit", data={ + "name": "Flask", + "theme": "dark", + "picture": (resources / "picture.png").open("rb"), + }) + assert response.status_code == 200 + + +JSON Data +~~~~~~~~~ + +To send JSON data, pass an object to ``json``. The ``Content-Type`` +header will be set to ``application/json`` automatically. + +Similarly, if the response contains JSON data, the ``response.json`` +attribute will contain the deserialized object. + +.. code-block:: python + + def test_json_data(client): + response = client.post("/graphql", json={ + "query": """ + query User($id: String!) { + user(id: $id) { + name + theme + picture_url + } + } + """, + variables={"id": 2}, + }) + assert response.json["data"]["user"]["name"] == "Flask" + + +Following Redirects +------------------- + +By default, the client does not make additional requests if the response +is a redirect. By passing ``follow_redirects=True`` to a request method, +the client will continue to make requests until a non-redirect response +is returned. + +:attr:`TestResponse.history ` is +a tuple of the responses that led up to the final response. Each +response has a :attr:`~werkzeug.test.TestResponse.request` attribute +which records the request that produced that response. + +.. code-block:: python + + def test_logout_redirect(client): + response = client.get("/logout", follow_redirects=True) + # Check that there was one redirect response. + assert len(response.history) == 1 + # Check that the second request was to the index page. + assert response.request.path == "/index" + + +Accessing and Modifying the Session +----------------------------------- + +To access Flask's context variables, mainly +:data:`~flask.session`, use the client in a ``with`` statement. +The app and request context will remain active *after* making a request, +until the ``with`` block ends. + +.. code-block:: python + + from flask import session + + def test_access_session(client): + with client: + client.post("/auth/login", data={"username": "flask"}) + # session is still accessible + assert session["user_id"] == 1 + + # session is no longer accessible + +If you want to access or set a value in the session *before* making a +request, use the client's +:meth:`~flask.testing.FlaskClient.session_transaction` method in a +``with`` statement. It returns a session object, and will save the +session once the block ends. + +.. code-block:: python + + from flask import session + + def test_modify_session(client): + with client.session_transaction() as session: + # set a user id without going through the login route + session["user_id"] = 1 + + # session is saved now + + response = client.get("/users/me") + assert response.json["username"] == "flask" + + +.. _testing-cli: + +Running Commands with the CLI Runner +------------------------------------ + +Flask provides :meth:`~flask.Flask.test_cli_runner` to create a +:class:`~flask.testing.FlaskCliRunner`, which runs CLI commands in +isolation and captures the output in a :class:`~click.testing.Result` +object. Flask's runner extends :doc:`Click's runner `, +see those docs for additional information. + +Use the runner's :meth:`~flask.testing.FlaskCliRunner.invoke` method to +call commands in the same way they would be called with the ``flask`` +command from the command line. + +.. code-block:: python + + import click + + @app.cli.command("hello") + @click.option("--name", default="World") + def hello_command(name): + click.echo(f"Hello, {name}!") + + def test_hello_command(runner): + result = runner.invoke(args="hello") + assert "World" in result.output + + result = runner.invoke(args=["hello", "--name", "Flask"]) + assert "Flask" in result.output + + +Tests that depend on an Active Context +-------------------------------------- + +You may have functions that are called from views or commands, that expect an +active :doc:`app context ` because they access :data:`.request`, +:data:`.session`, :data:`.g`, or :data:`.current_app`. Rather than testing them by +making a request or invoking the command, you can create and activate a context +directly. + +Use ``with app.app_context()`` to push an application context. For +example, database extensions usually require an active app context to +make queries. + +.. code-block:: python + + def test_db_post_model(app): + with app.app_context(): + post = db.session.query(Post).get(1) + +Use ``with app.test_request_context()`` to push a request context. It +takes the same arguments as the test client's request methods. + +.. code-block:: python + + def test_validate_user_edit(app): + with app.test_request_context( + "/user/2/edit", method="POST", data={"name": ""} + ): + # call a function that accesses `request` + messages = validate_edit_user() + + assert messages["name"][0] == "Name cannot be empty." + +Creating a test request context doesn't run any of the Flask dispatching +code, so ``before_request`` functions are not called. If you need to +call these, usually it's better to make a full request instead. However, +it's possible to call them manually. + +.. code-block:: python + + def test_auth_token(app): + with app.test_request_context("/user/2/edit", headers={"X-Auth-Token": "1"}): + app.preprocess_request() + assert g.user.name == "Flask" diff --git a/src/flask-main/docs/tutorial/blog.rst b/src/flask-main/docs/tutorial/blog.rst new file mode 100644 index 0000000..6418f5f --- /dev/null +++ b/src/flask-main/docs/tutorial/blog.rst @@ -0,0 +1,336 @@ +.. currentmodule:: flask + +Blog Blueprint +============== + +You'll use the same techniques you learned about when writing the +authentication blueprint to write the blog blueprint. The blog should +list all posts, allow logged in users to create posts, and allow the +author of a post to edit or delete it. + +As you implement each view, keep the development server running. As you +save your changes, try going to the URL in your browser and testing them +out. + +The Blueprint +------------- + +Define the blueprint and register it in the application factory. + +.. code-block:: python + :caption: ``flaskr/blog.py`` + + from flask import ( + Blueprint, flash, g, redirect, render_template, request, url_for + ) + from werkzeug.exceptions import abort + + from flaskr.auth import login_required + from flaskr.db import get_db + + bp = Blueprint('blog', __name__) + +Import and register the blueprint from the factory using +:meth:`app.register_blueprint() `. Place the +new code at the end of the factory function before returning the app. + +.. code-block:: python + :caption: ``flaskr/__init__.py`` + + def create_app(): + app = ... + # existing code omitted + + from . import blog + app.register_blueprint(blog.bp) + app.add_url_rule('/', endpoint='index') + + return app + + +Unlike the auth blueprint, the blog blueprint does not have a +``url_prefix``. So the ``index`` view will be at ``/``, the ``create`` +view at ``/create``, and so on. The blog is the main feature of Flaskr, +so it makes sense that the blog index will be the main index. + +However, the endpoint for the ``index`` view defined below will be +``blog.index``. Some of the authentication views referred to a plain +``index`` endpoint. :meth:`app.add_url_rule() ` +associates the endpoint name ``'index'`` with the ``/`` url so that +``url_for('index')`` or ``url_for('blog.index')`` will both work, +generating the same ``/`` URL either way. + +In another application you might give the blog blueprint a +``url_prefix`` and define a separate ``index`` view in the application +factory, similar to the ``hello`` view. Then the ``index`` and +``blog.index`` endpoints and URLs would be different. + + +Index +----- + +The index will show all of the posts, most recent first. A ``JOIN`` is +used so that the author information from the ``user`` table is +available in the result. + +.. code-block:: python + :caption: ``flaskr/blog.py`` + + @bp.route('/') + def index(): + db = get_db() + posts = db.execute( + 'SELECT p.id, title, body, created, author_id, username' + ' FROM post p JOIN user u ON p.author_id = u.id' + ' ORDER BY created DESC' + ).fetchall() + return render_template('blog/index.html', posts=posts) + +.. code-block:: html+jinja + :caption: ``flaskr/templates/blog/index.html`` + + {% extends 'base.html' %} + + {% block header %} +

{% block title %}Posts{% endblock %}

+ {% if g.user %} + New + {% endif %} + {% endblock %} + + {% block content %} + {% for post in posts %} +
+
+
+

{{ post['title'] }}

+
by {{ post['username'] }} on {{ post['created'].strftime('%Y-%m-%d') }}
+
+ {% if g.user['id'] == post['author_id'] %} + Edit + {% endif %} +
+

{{ post['body'] }}

+
+ {% if not loop.last %} +
+ {% endif %} + {% endfor %} + {% endblock %} + +When a user is logged in, the ``header`` block adds a link to the +``create`` view. When the user is the author of a post, they'll see an +"Edit" link to the ``update`` view for that post. ``loop.last`` is a +special variable available inside `Jinja for loops`_. It's used to +display a line after each post except the last one, to visually separate +them. + +.. _Jinja for loops: https://jinja.palletsprojects.com/templates/#for + + +Create +------ + +The ``create`` view works the same as the auth ``register`` view. Either +the form is displayed, or the posted data is validated and the post is +added to the database or an error is shown. + +The ``login_required`` decorator you wrote earlier is used on the blog +views. A user must be logged in to visit these views, otherwise they +will be redirected to the login page. + +.. code-block:: python + :caption: ``flaskr/blog.py`` + + @bp.route('/create', methods=('GET', 'POST')) + @login_required + def create(): + if request.method == 'POST': + title = request.form['title'] + body = request.form['body'] + error = None + + if not title: + error = 'Title is required.' + + if error is not None: + flash(error) + else: + db = get_db() + db.execute( + 'INSERT INTO post (title, body, author_id)' + ' VALUES (?, ?, ?)', + (title, body, g.user['id']) + ) + db.commit() + return redirect(url_for('blog.index')) + + return render_template('blog/create.html') + +.. code-block:: html+jinja + :caption: ``flaskr/templates/blog/create.html`` + + {% extends 'base.html' %} + + {% block header %} +

{% block title %}New Post{% endblock %}

+ {% endblock %} + + {% block content %} +
+ + + + + +
+ {% endblock %} + + +Update +------ + +Both the ``update`` and ``delete`` views will need to fetch a ``post`` +by ``id`` and check if the author matches the logged in user. To avoid +duplicating code, you can write a function to get the ``post`` and call +it from each view. + +.. code-block:: python + :caption: ``flaskr/blog.py`` + + def get_post(id, check_author=True): + post = get_db().execute( + 'SELECT p.id, title, body, created, author_id, username' + ' FROM post p JOIN user u ON p.author_id = u.id' + ' WHERE p.id = ?', + (id,) + ).fetchone() + + if post is None: + abort(404, f"Post id {id} doesn't exist.") + + if check_author and post['author_id'] != g.user['id']: + abort(403) + + return post + +:func:`abort` will raise a special exception that returns an HTTP status +code. It takes an optional message to show with the error, otherwise a +default message is used. ``404`` means "Not Found", and ``403`` means +"Forbidden". (``401`` means "Unauthorized", but you redirect to the +login page instead of returning that status.) + +The ``check_author`` argument is defined so that the function can be +used to get a ``post`` without checking the author. This would be useful +if you wrote a view to show an individual post on a page, where the user +doesn't matter because they're not modifying the post. + +.. code-block:: python + :caption: ``flaskr/blog.py`` + + @bp.route('//update', methods=('GET', 'POST')) + @login_required + def update(id): + post = get_post(id) + + if request.method == 'POST': + title = request.form['title'] + body = request.form['body'] + error = None + + if not title: + error = 'Title is required.' + + if error is not None: + flash(error) + else: + db = get_db() + db.execute( + 'UPDATE post SET title = ?, body = ?' + ' WHERE id = ?', + (title, body, id) + ) + db.commit() + return redirect(url_for('blog.index')) + + return render_template('blog/update.html', post=post) + +Unlike the views you've written so far, the ``update`` function takes +an argument, ``id``. That corresponds to the ```` in the route. +A real URL will look like ``/1/update``. Flask will capture the ``1``, +ensure it's an :class:`int`, and pass it as the ``id`` argument. If you +don't specify ``int:`` and instead do ````, it will be a string. +To generate a URL to the update page, :func:`url_for` needs to be passed +the ``id`` so it knows what to fill in: +``url_for('blog.update', id=post['id'])``. This is also in the +``index.html`` file above. + +The ``create`` and ``update`` views look very similar. The main +difference is that the ``update`` view uses a ``post`` object and an +``UPDATE`` query instead of an ``INSERT``. With some clever refactoring, +you could use one view and template for both actions, but for the +tutorial it's clearer to keep them separate. + +.. code-block:: html+jinja + :caption: ``flaskr/templates/blog/update.html`` + + {% extends 'base.html' %} + + {% block header %} +

{% block title %}Edit "{{ post['title'] }}"{% endblock %}

+ {% endblock %} + + {% block content %} +
+ + + + + +
+
+
+ +
+ {% endblock %} + +This template has two forms. The first posts the edited data to the +current page (``//update``). The other form contains only a button +and specifies an ``action`` attribute that posts to the delete view +instead. The button uses some JavaScript to show a confirmation dialog +before submitting. + +The pattern ``{{ request.form['title'] or post['title'] }}`` is used to +choose what data appears in the form. When the form hasn't been +submitted, the original ``post`` data appears, but if invalid form data +was posted you want to display that so the user can fix the error, so +``request.form`` is used instead. :data:`.request` is another variable +that's automatically available in templates. + + +Delete +------ + +The delete view doesn't have its own template, the delete button is part +of ``update.html`` and posts to the ``//delete`` URL. Since there +is no template, it will only handle the ``POST`` method and then redirect +to the ``index`` view. + +.. code-block:: python + :caption: ``flaskr/blog.py`` + + @bp.route('//delete', methods=('POST',)) + @login_required + def delete(id): + get_post(id) + db = get_db() + db.execute('DELETE FROM post WHERE id = ?', (id,)) + db.commit() + return redirect(url_for('blog.index')) + +Congratulations, you've now finished writing your application! Take some +time to try out everything in the browser. However, there's still more +to do before the project is complete. + +Continue to :doc:`install`. diff --git a/src/flask-main/docs/tutorial/database.rst b/src/flask-main/docs/tutorial/database.rst new file mode 100644 index 0000000..cf13260 --- /dev/null +++ b/src/flask-main/docs/tutorial/database.rst @@ -0,0 +1,219 @@ +.. currentmodule:: flask + +Define and Access the Database +============================== + +The application will use a `SQLite`_ database to store users and posts. +Python comes with built-in support for SQLite in the :mod:`sqlite3` +module. + +SQLite is convenient because it doesn't require setting up a separate +database server and is built-in to Python. However, if concurrent +requests try to write to the database at the same time, they will slow +down as each write happens sequentially. Small applications won't notice +this. Once you become big, you may want to switch to a different +database. + +The tutorial doesn't go into detail about SQL. If you are not familiar +with it, the SQLite docs describe the `language`_. + +.. _SQLite: https://sqlite.org/about.html +.. _language: https://sqlite.org/lang.html + + +Connect to the Database +----------------------- + +The first thing to do when working with a SQLite database (and most +other Python database libraries) is to create a connection to it. Any +queries and operations are performed using the connection, which is +closed after the work is finished. + +In web applications this connection is typically tied to the request. It +is created at some point when handling a request, and closed before the +response is sent. + +.. code-block:: python + :caption: ``flaskr/db.py`` + + import sqlite3 + from datetime import datetime + + import click + from flask import current_app, g + + + def get_db(): + if 'db' not in g: + g.db = sqlite3.connect( + current_app.config['DATABASE'], + detect_types=sqlite3.PARSE_DECLTYPES + ) + g.db.row_factory = sqlite3.Row + + return g.db + + + def close_db(e=None): + db = g.pop('db', None) + + if db is not None: + db.close() + +:data:`.g` is a special object that is unique for each request. It is +used to store data that might be accessed by multiple functions during +the request. The connection is stored and reused instead of creating a +new connection if ``get_db`` is called a second time in the same +request. + +:data:`.current_app` is another special object that points to the Flask +application handling the request. Since you used an application factory, +there is no application object when writing the rest of your code. +``get_db`` will be called when the application has been created and is +handling a request, so :data:`.current_app` can be used. + +:func:`sqlite3.connect` establishes a connection to the file pointed at +by the ``DATABASE`` configuration key. This file doesn't have to exist +yet, and won't until you initialize the database later. + +:class:`sqlite3.Row` tells the connection to return rows that behave +like dicts. This allows accessing the columns by name. + +``close_db`` checks if a connection was created by checking if ``g.db`` +was set. If the connection exists, it is closed. Further down you will +tell your application about the ``close_db`` function in the application +factory so that it is called after each request. + + +Create the Tables +----------------- + +In SQLite, data is stored in *tables* and *columns*. These need to be +created before you can store and retrieve data. Flaskr will store users +in the ``user`` table, and posts in the ``post`` table. Create a file +with the SQL commands needed to create empty tables: + +.. code-block:: sql + :caption: ``flaskr/schema.sql`` + + DROP TABLE IF EXISTS user; + DROP TABLE IF EXISTS post; + + CREATE TABLE user ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT UNIQUE NOT NULL, + password TEXT NOT NULL + ); + + CREATE TABLE post ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + author_id INTEGER NOT NULL, + created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + title TEXT NOT NULL, + body TEXT NOT NULL, + FOREIGN KEY (author_id) REFERENCES user (id) + ); + +Add the Python functions that will run these SQL commands to the +``db.py`` file: + +.. code-block:: python + :caption: ``flaskr/db.py`` + + def init_db(): + db = get_db() + + with current_app.open_resource('schema.sql') as f: + db.executescript(f.read().decode('utf8')) + + + @click.command('init-db') + def init_db_command(): + """Clear the existing data and create new tables.""" + init_db() + click.echo('Initialized the database.') + + + sqlite3.register_converter( + "timestamp", lambda v: datetime.fromisoformat(v.decode()) + ) + +:meth:`open_resource() ` opens a file relative to +the ``flaskr`` package, which is useful since you won't necessarily know +where that location is when deploying the application later. ``get_db`` +returns a database connection, which is used to execute the commands +read from the file. + +:func:`click.command` defines a command line command called ``init-db`` +that calls the ``init_db`` function and shows a success message to the +user. You can read :doc:`/cli` to learn more about writing commands. + +The call to :func:`sqlite3.register_converter` tells Python how to +interpret timestamp values in the database. We convert the value to a +:class:`datetime.datetime`. + + +Register with the Application +----------------------------- + +The ``close_db`` and ``init_db_command`` functions need to be registered +with the application instance; otherwise, they won't be used by the +application. However, since you're using a factory function, that +instance isn't available when writing the functions. Instead, write a +function that takes an application and does the registration. + +.. code-block:: python + :caption: ``flaskr/db.py`` + + def init_app(app): + app.teardown_appcontext(close_db) + app.cli.add_command(init_db_command) + +:meth:`app.teardown_appcontext() ` tells +Flask to call that function when cleaning up after returning the +response. + +:meth:`app.cli.add_command() ` adds a new +command that can be called with the ``flask`` command. + +Import and call this function from the factory. Place the new code at +the end of the factory function before returning the app. + +.. code-block:: python + :caption: ``flaskr/__init__.py`` + + def create_app(): + app = ... + # existing code omitted + + from . import db + db.init_app(app) + + return app + + +Initialize the Database File +---------------------------- + +Now that ``init-db`` has been registered with the app, it can be called +using the ``flask`` command, similar to the ``run`` command from the +previous page. + +.. note:: + + If you're still running the server from the previous page, you can + either stop the server, or run this command in a new terminal. If + you use a new terminal, remember to change to your project directory + and activate the env as described in :doc:`/installation`. + +Run the ``init-db`` command: + +.. code-block:: none + + $ flask --app flaskr init-db + Initialized the database. + +There will now be a ``flaskr.sqlite`` file in the ``instance`` folder in +your project. + +Continue to :doc:`views`. diff --git a/src/flask-main/docs/tutorial/deploy.rst b/src/flask-main/docs/tutorial/deploy.rst new file mode 100644 index 0000000..eb3a53a --- /dev/null +++ b/src/flask-main/docs/tutorial/deploy.rst @@ -0,0 +1,111 @@ +Deploy to Production +==================== + +This part of the tutorial assumes you have a server that you want to +deploy your application to. It gives an overview of how to create the +distribution file and install it, but won't go into specifics about +what server or software to use. You can set up a new environment on your +development computer to try out the instructions below, but probably +shouldn't use it for hosting a real public application. See +:doc:`/deploying/index` for a list of many different ways to host your +application. + + +Build and Install +----------------- + +When you want to deploy your application elsewhere, you build a *wheel* +(``.whl``) file. Install and use the ``build`` tool to do this. + +.. code-block:: none + + $ pip install build + $ python -m build --wheel + +You can find the file in ``dist/flaskr-1.0.0-py3-none-any.whl``. The +file name is in the format of {project name}-{version}-{python tag} +-{abi tag}-{platform tag}. + +Copy this file to another machine, +:ref:`set up a new virtualenv `, then install the +file with ``pip``. + +.. code-block:: none + + $ pip install flaskr-1.0.0-py3-none-any.whl + +Pip will install your project along with its dependencies. + +Since this is a different machine, you need to run ``init-db`` again to +create the database in the instance folder. + + .. code-block:: text + + $ flask --app flaskr init-db + +When Flask detects that it's installed (not in editable mode), it uses +a different directory for the instance folder. You can find it at +``.venv/var/flaskr-instance`` instead. + + +Configure the Secret Key +------------------------ + +In the beginning of the tutorial that you gave a default value for +:data:`SECRET_KEY`. This should be changed to some random bytes in +production. Otherwise, attackers could use the public ``'dev'`` key to +modify the session cookie, or anything else that uses the secret key. + +You can use the following command to output a random secret key: + +.. code-block:: none + + $ python -c 'import secrets; print(secrets.token_hex())' + + '192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf' + +Create the ``config.py`` file in the instance folder, which the factory +will read from if it exists. Copy the generated value into it. + +.. code-block:: python + :caption: ``.venv/var/flaskr-instance/config.py`` + + SECRET_KEY = '192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf' + +You can also set any other necessary configuration here, although +``SECRET_KEY`` is the only one needed for Flaskr. + + +Run with a Production Server +---------------------------- + +When running publicly rather than in development, you should not use the +built-in development server (``flask run``). The development server is +provided by Werkzeug for convenience, but is not designed to be +particularly efficient, stable, or secure. + +Instead, use a production WSGI server. For example, to use `Waitress`_, +first install it in the virtual environment: + +.. code-block:: none + + $ pip install waitress + +You need to tell Waitress about your application, but it doesn't use +``--app`` like ``flask run`` does. You need to tell it to import and +call the application factory to get an application object. + +.. code-block:: none + + $ waitress-serve --call 'flaskr:create_app' + + Serving on http://0.0.0.0:8080 + +See :doc:`/deploying/index` for a list of many different ways to host +your application. Waitress is just an example, chosen for the tutorial +because it supports both Windows and Linux. There are many more WSGI +servers and deployment options that you may choose for your project. + +.. _Waitress: https://docs.pylonsproject.org/projects/waitress/en/stable/ + +Continue to :doc:`next`. diff --git a/src/flask-main/docs/tutorial/factory.rst b/src/flask-main/docs/tutorial/factory.rst new file mode 100644 index 0000000..39febd1 --- /dev/null +++ b/src/flask-main/docs/tutorial/factory.rst @@ -0,0 +1,162 @@ +.. currentmodule:: flask + +Application Setup +================= + +A Flask application is an instance of the :class:`Flask` class. +Everything about the application, such as configuration and URLs, will +be registered with this class. + +The most straightforward way to create a Flask application is to create +a global :class:`Flask` instance directly at the top of your code, like +how the "Hello, World!" example did on the previous page. While this is +simple and useful in some cases, it can cause some tricky issues as the +project grows. + +Instead of creating a :class:`Flask` instance globally, you will create +it inside a function. This function is known as the *application +factory*. Any configuration, registration, and other setup the +application needs will happen inside the function, then the application +will be returned. + + +The Application Factory +----------------------- + +It's time to start coding! Create the ``flaskr`` directory and add the +``__init__.py`` file. The ``__init__.py`` serves double duty: it will +contain the application factory, and it tells Python that the ``flaskr`` +directory should be treated as a package. + +.. code-block:: none + + $ mkdir flaskr + +.. code-block:: python + :caption: ``flaskr/__init__.py`` + + import os + + from flask import Flask + + + def create_app(test_config=None): + # create and configure the app + app = Flask(__name__, instance_relative_config=True) + app.config.from_mapping( + SECRET_KEY='dev', + DATABASE=os.path.join(app.instance_path, 'flaskr.sqlite'), + ) + + if test_config is None: + # load the instance config, if it exists, when not testing + app.config.from_pyfile('config.py', silent=True) + else: + # load the test config if passed in + app.config.from_mapping(test_config) + + # ensure the instance folder exists + try: + os.makedirs(app.instance_path) + except OSError: + pass + + # a simple page that says hello + @app.route('/hello') + def hello(): + return 'Hello, World!' + + return app + +``create_app`` is the application factory function. You'll add to it +later in the tutorial, but it already does a lot. + +#. ``app = Flask(__name__, instance_relative_config=True)`` creates the + :class:`Flask` instance. + + * ``__name__`` is the name of the current Python module. The app + needs to know where it's located to set up some paths, and + ``__name__`` is a convenient way to tell it that. + + * ``instance_relative_config=True`` tells the app that + configuration files are relative to the + :ref:`instance folder `. The instance folder + is located outside the ``flaskr`` package and can hold local + data that shouldn't be committed to version control, such as + configuration secrets and the database file. + +#. :meth:`app.config.from_mapping() ` sets + some default configuration that the app will use: + + * :data:`SECRET_KEY` is used by Flask and extensions to keep data + safe. It's set to ``'dev'`` to provide a convenient value + during development, but it should be overridden with a random + value when deploying. + + * ``DATABASE`` is the path where the SQLite database file will be + saved. It's under + :attr:`app.instance_path `, which is the + path that Flask has chosen for the instance folder. You'll learn + more about the database in the next section. + +#. :meth:`app.config.from_pyfile() ` overrides + the default configuration with values taken from the ``config.py`` + file in the instance folder if it exists. For example, when + deploying, this can be used to set a real ``SECRET_KEY``. + + * ``test_config`` can also be passed to the factory, and will be + used instead of the instance configuration. This is so the tests + you'll write later in the tutorial can be configured + independently of any development values you have configured. + +#. :func:`os.makedirs` ensures that + :attr:`app.instance_path ` exists. Flask + doesn't create the instance folder automatically, but it needs to be + created because your project will create the SQLite database file + there. + +#. :meth:`@app.route() ` creates a simple route so you can + see the application working before getting into the rest of the + tutorial. It creates a connection between the URL ``/hello`` and a + function that returns a response, the string ``'Hello, World!'`` in + this case. + + +Run The Application +------------------- + +Now you can run your application using the ``flask`` command. From the +terminal, tell Flask where to find your application, then run it in +debug mode. Remember, you should still be in the top-level +``flask-tutorial`` directory, not the ``flaskr`` package. + +Debug mode shows an interactive debugger whenever a page raises an +exception, and restarts the server whenever you make changes to the +code. You can leave it running and just reload the browser page as you +follow the tutorial. + +.. code-block:: text + + $ flask --app flaskr run --debug + +You'll see output similar to this: + +.. code-block:: text + + * Serving Flask app "flaskr" + * Debug mode: on + * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) + * Restarting with stat + * Debugger is active! + * Debugger PIN: nnn-nnn-nnn + +Visit http://127.0.0.1:5000/hello in a browser and you should see the +"Hello, World!" message. Congratulations, you're now running your Flask +web application! + +If another program is already using port 5000, you'll see +``OSError: [Errno 98]`` or ``OSError: [WinError 10013]`` when the +server tries to start. See :ref:`address-already-in-use` for how to +handle that. + +Continue to :doc:`database`. diff --git a/src/flask-main/docs/tutorial/flaskr_edit.png b/src/flask-main/docs/tutorial/flaskr_edit.png new file mode 100644 index 0000000000000000000000000000000000000000..6cd6e3980fbf1b5b619d2e013723368d137fafdc GIT binary patch literal 13259 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 literal 0 HcmV?d00001 diff --git a/src/flask-main/docs/tutorial/flaskr_index.png b/src/flask-main/docs/tutorial/flaskr_index.png new file mode 100644 index 0000000000000000000000000000000000000000..aa2b50f552ee094df735372fcf534a5c14d0c325 GIT binary patch literal 11675 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 literal 0 HcmV?d00001 diff --git a/src/flask-main/docs/tutorial/index.rst b/src/flask-main/docs/tutorial/index.rst new file mode 100644 index 0000000..d5dc5b3 --- /dev/null +++ b/src/flask-main/docs/tutorial/index.rst @@ -0,0 +1,64 @@ +Tutorial +======== + +.. toctree:: + :caption: Contents: + :maxdepth: 1 + + layout + factory + database + views + templates + static + blog + install + tests + deploy + next + +This tutorial will walk you through creating a basic blog application +called Flaskr. Users will be able to register, log in, create posts, +and edit or delete their own posts. You will be able to package and +install the application on other computers. + +.. image:: flaskr_index.png + :align: center + :class: screenshot + :alt: screenshot of index page + +It's assumed that you're already familiar with Python. The `official +tutorial`_ in the Python docs is a great way to learn or review first. + +.. _official tutorial: https://docs.python.org/3/tutorial/ + +While it's designed to give a good starting point, the tutorial doesn't +cover all of Flask's features. Check out the :doc:`/quickstart` for an +overview of what Flask can do, then dive into the docs to find out more. +The tutorial only uses what's provided by Flask and Python. In another +project, you might decide to use :doc:`/extensions` or other libraries +to make some tasks simpler. + +.. image:: flaskr_login.png + :align: center + :class: screenshot + :alt: screenshot of login page + +Flask is flexible. It doesn't require you to use any particular project +or code layout. However, when first starting, it's helpful to use a more +structured approach. This means that the tutorial will require a bit of +boilerplate up front, but it's done to avoid many common pitfalls that +new developers encounter, and it creates a project that's easy to expand +on. Once you become more comfortable with Flask, you can step out of +this structure and take full advantage of Flask's flexibility. + +.. image:: flaskr_edit.png + :align: center + :class: screenshot + :alt: screenshot of edit page + +:gh:`The tutorial project is available as an example in the Flask +repository `, if you want to compare your project +with the final product as you follow the tutorial. + +Continue to :doc:`layout`. diff --git a/src/flask-main/docs/tutorial/install.rst b/src/flask-main/docs/tutorial/install.rst new file mode 100644 index 0000000..db83e10 --- /dev/null +++ b/src/flask-main/docs/tutorial/install.rst @@ -0,0 +1,89 @@ +Make the Project Installable +============================ + +Making your project installable means that you can build a *wheel* file and install that +in another environment, just like you installed Flask in your project's environment. +This makes deploying your project the same as installing any other library, so you're +using all the standard Python tools to manage everything. + +Installing also comes with other benefits that might not be obvious from +the tutorial or as a new Python user, including: + +* Currently, Python and Flask understand how to use the ``flaskr`` + package only because you're running from your project's directory. + Installing means you can import it no matter where you run from. + +* You can manage your project's dependencies just like other packages + do, so ``pip install yourproject.whl`` installs them. + +* Test tools can isolate your test environment from your development + environment. + +.. note:: + This is being introduced late in the tutorial, but in your future + projects you should always start with this. + + +Describe the Project +-------------------- + +The ``pyproject.toml`` file describes your project and how to build it. + +.. code-block:: toml + :caption: ``pyproject.toml`` + + [project] + name = "flaskr" + version = "1.0.0" + description = "The basic blog app built in the Flask tutorial." + dependencies = [ + "flask", + ] + + [build-system] + requires = ["flit_core<4"] + build-backend = "flit_core.buildapi" + +See the official `Packaging tutorial `_ for more +explanation of the files and options used. + +.. _packaging tutorial: https://packaging.python.org/tutorials/packaging-projects/ + + +Install the Project +------------------- + +Use ``pip`` to install your project in the virtual environment. + +.. code-block:: none + + $ pip install -e . + +This tells pip to find ``pyproject.toml`` in the current directory and install the +project in *editable* or *development* mode. Editable mode means that as you make +changes to your local code, you'll only need to re-install if you change the metadata +about the project, such as its dependencies. + +You can observe that the project is now installed with ``pip list``. + +.. code-block:: none + + $ pip list + + Package Version Location + -------------- --------- ---------------------------------- + click 6.7 + Flask 1.0 + flaskr 1.0.0 /home/user/Projects/flask-tutorial + itsdangerous 0.24 + Jinja2 2.10 + MarkupSafe 1.0 + pip 9.0.3 + Werkzeug 0.14.1 + +Nothing changes from how you've been running your project so far. +``--app`` is still set to ``flaskr`` and ``flask run`` still runs +the application, but you can call it from anywhere, not just the +``flask-tutorial`` directory. + +Continue to :doc:`tests`. diff --git a/src/flask-main/docs/tutorial/layout.rst b/src/flask-main/docs/tutorial/layout.rst new file mode 100644 index 0000000..6f8e59f --- /dev/null +++ b/src/flask-main/docs/tutorial/layout.rst @@ -0,0 +1,110 @@ +Project Layout +============== + +Create a project directory and enter it: + +.. code-block:: none + + $ mkdir flask-tutorial + $ cd flask-tutorial + +Then follow the :doc:`installation instructions ` to set +up a Python virtual environment and install Flask for your project. + +The tutorial will assume you're working from the ``flask-tutorial`` +directory from now on. The file names at the top of each code block are +relative to this directory. + +---- + +A Flask application can be as simple as a single file. + +.. code-block:: python + :caption: ``hello.py`` + + from flask import Flask + + app = Flask(__name__) + + + @app.route('/') + def hello(): + return 'Hello, World!' + +However, as a project gets bigger, it becomes overwhelming to keep all +the code in one file. Python projects use *packages* to organize code +into multiple modules that can be imported where needed, and the +tutorial will do this as well. + +The project directory will contain: + +* ``flaskr/``, a Python package containing your application code and + files. +* ``tests/``, a directory containing test modules. +* ``.venv/``, a Python virtual environment where Flask and other + dependencies are installed. +* Installation files telling Python how to install your project. +* Version control config, such as `git`_. You should make a habit of + using some type of version control for all your projects, no matter + the size. +* Any other project files you might add in the future. + +.. _git: https://git-scm.com/ + +By the end, your project layout will look like this: + +.. code-block:: none + + /home/user/Projects/flask-tutorial + ├── flaskr/ + │ ├── __init__.py + │ ├── db.py + │ ├── schema.sql + │ ├── auth.py + │ ├── blog.py + │ ├── templates/ + │ │ ├── base.html + │ │ ├── auth/ + │ │ │ ├── login.html + │ │ │ └── register.html + │ │ └── blog/ + │ │ ├── create.html + │ │ ├── index.html + │ │ └── update.html + │ └── static/ + │ └── style.css + ├── tests/ + │ ├── conftest.py + │ ├── data.sql + │ ├── test_factory.py + │ ├── test_db.py + │ ├── test_auth.py + │ └── test_blog.py + ├── .venv/ + ├── pyproject.toml + └── MANIFEST.in + +If you're using version control, the following files that are generated +while running your project should be ignored. There may be other files +based on the editor you use. In general, ignore files that you didn't +write. For example, with git: + +.. code-block:: none + :caption: ``.gitignore`` + + .venv/ + + *.pyc + __pycache__/ + + instance/ + + .pytest_cache/ + .coverage + htmlcov/ + + dist/ + build/ + *.egg-info/ + +Continue to :doc:`factory`. diff --git a/src/flask-main/docs/tutorial/next.rst b/src/flask-main/docs/tutorial/next.rst new file mode 100644 index 0000000..d41e8ef --- /dev/null +++ b/src/flask-main/docs/tutorial/next.rst @@ -0,0 +1,38 @@ +Keep Developing! +================ + +You've learned about quite a few Flask and Python concepts throughout +the tutorial. Go back and review the tutorial and compare your code with +the steps you took to get there. Compare your project to the +:gh:`example project `, which might look a bit +different due to the step-by-step nature of the tutorial. + +There's a lot more to Flask than what you've seen so far. Even so, +you're now equipped to start developing your own web applications. Check +out the :doc:`/quickstart` for an overview of what Flask can do, then +dive into the docs to keep learning. Flask uses `Jinja`_, `Click`_, +`Werkzeug`_, and `ItsDangerous`_ behind the scenes, and they all have +their own documentation too. You'll also be interested in +:doc:`/extensions` which make tasks like working with the database or +validating form data easier and more powerful. + +If you want to keep developing your Flaskr project, here are some ideas +for what to try next: + +* A detail view to show a single post. Click a post's title to go to + its page. +* Like / unlike a post. +* Comments. +* Tags. Clicking a tag shows all the posts with that tag. +* A search box that filters the index page by name. +* Paged display. Only show 5 posts per page. +* Upload an image to go along with a post. +* Format posts using Markdown. +* An RSS feed of new posts. + +Have fun and make awesome applications! + +.. _Jinja: https://palletsprojects.com/p/jinja/ +.. _Click: https://palletsprojects.com/p/click/ +.. _Werkzeug: https://palletsprojects.com/p/werkzeug/ +.. _ItsDangerous: https://palletsprojects.com/p/itsdangerous/ diff --git a/src/flask-main/docs/tutorial/static.rst b/src/flask-main/docs/tutorial/static.rst new file mode 100644 index 0000000..8e76c40 --- /dev/null +++ b/src/flask-main/docs/tutorial/static.rst @@ -0,0 +1,72 @@ +Static Files +============ + +The authentication views and templates work, but they look very plain +right now. Some `CSS`_ can be added to add style to the HTML layout you +constructed. The style won't change, so it's a *static* file rather than +a template. + +Flask automatically adds a ``static`` view that takes a path relative +to the ``flaskr/static`` directory and serves it. The ``base.html`` +template already has a link to the ``style.css`` file: + +.. code-block:: html+jinja + + {{ url_for('static', filename='style.css') }} + +Besides CSS, other types of static files might be files with JavaScript +functions, or a logo image. They are all placed under the +``flaskr/static`` directory and referenced with +``url_for('static', filename='...')``. + +This tutorial isn't focused on how to write CSS, so you can just copy +the following into the ``flaskr/static/style.css`` file: + +.. code-block:: css + :caption: ``flaskr/static/style.css`` + + html { font-family: sans-serif; background: #eee; padding: 1rem; } + body { max-width: 960px; margin: 0 auto; background: white; } + h1 { font-family: serif; color: #377ba8; margin: 1rem 0; } + a { color: #377ba8; } + hr { border: none; border-top: 1px solid lightgray; } + nav { background: lightgray; display: flex; align-items: center; padding: 0 0.5rem; } + nav h1 { flex: auto; margin: 0; } + nav h1 a { text-decoration: none; padding: 0.25rem 0.5rem; } + nav ul { display: flex; list-style: none; margin: 0; padding: 0; } + nav ul li a, nav ul li span, header .action { display: block; padding: 0.5rem; } + .content { padding: 0 1rem 1rem; } + .content > header { border-bottom: 1px solid lightgray; display: flex; align-items: flex-end; } + .content > header h1 { flex: auto; margin: 1rem 0 0.25rem 0; } + .flash { margin: 1em 0; padding: 1em; background: #cae6f6; border: 1px solid #377ba8; } + .post > header { display: flex; align-items: flex-end; font-size: 0.85em; } + .post > header > div:first-of-type { flex: auto; } + .post > header h1 { font-size: 1.5em; margin-bottom: 0; } + .post .about { color: slategray; font-style: italic; } + .post .body { white-space: pre-line; } + .content:last-child { margin-bottom: 0; } + .content form { margin: 1em 0; display: flex; flex-direction: column; } + .content label { font-weight: bold; margin-bottom: 0.5em; } + .content input, .content textarea { margin-bottom: 1em; } + .content textarea { min-height: 12em; resize: vertical; } + input.danger { color: #cc2f2e; } + input[type=submit] { align-self: start; min-width: 10em; } + +You can find a less compact version of ``style.css`` in the +:gh:`example code `. + +Go to http://127.0.0.1:5000/auth/login and the page should look like the +screenshot below. + +.. image:: flaskr_login.png + :align: center + :class: screenshot + :alt: screenshot of login page + +You can read more about CSS from `Mozilla's documentation `_. If +you change a static file, refresh the browser page. If the change +doesn't show up, try clearing your browser's cache. + +.. _CSS: https://developer.mozilla.org/docs/Web/CSS + +Continue to :doc:`blog`. diff --git a/src/flask-main/docs/tutorial/templates.rst b/src/flask-main/docs/tutorial/templates.rst new file mode 100644 index 0000000..ca9d4b3 --- /dev/null +++ b/src/flask-main/docs/tutorial/templates.rst @@ -0,0 +1,187 @@ +.. currentmodule:: flask + +Templates +========= + +You've written the authentication views for your application, but if +you're running the server and try to go to any of the URLs, you'll see a +``TemplateNotFound`` error. That's because the views are calling +:func:`render_template`, but you haven't written the templates yet. +The template files will be stored in the ``templates`` directory inside +the ``flaskr`` package. + +Templates are files that contain static data as well as placeholders +for dynamic data. A template is rendered with specific data to produce a +final document. Flask uses the `Jinja`_ template library to render +templates. + +In your application, you will use templates to render `HTML`_ which +will display in the user's browser. In Flask, Jinja is configured to +*autoescape* any data that is rendered in HTML templates. This means +that it's safe to render user input; any characters they've entered that +could mess with the HTML, such as ``<`` and ``>`` will be *escaped* with +*safe* values that look the same in the browser but don't cause unwanted +effects. + +Jinja looks and behaves mostly like Python. Special delimiters are used +to distinguish Jinja syntax from the static data in the template. +Anything between ``{{`` and ``}}`` is an expression that will be output +to the final document. ``{%`` and ``%}`` denotes a control flow +statement like ``if`` and ``for``. Unlike Python, blocks are denoted +by start and end tags rather than indentation since static text within +a block could change indentation. + +.. _Jinja: https://jinja.palletsprojects.com/templates/ +.. _HTML: https://developer.mozilla.org/docs/Web/HTML + + +The Base Layout +--------------- + +Each page in the application will have the same basic layout around a +different body. Instead of writing the entire HTML structure in each +template, each template will *extend* a base template and override +specific sections. + +.. code-block:: html+jinja + :caption: ``flaskr/templates/base.html`` + + + {% block title %}{% endblock %} - Flaskr + + +
+
+ {% block header %}{% endblock %} +
+ {% for message in get_flashed_messages() %} +
{{ message }}
+ {% endfor %} + {% block content %}{% endblock %} +
+ +:data:`.g` is automatically available in templates. Based on if +``g.user`` is set (from ``load_logged_in_user``), either the username +and a log out link are displayed, or links to register and log in +are displayed. :func:`url_for` is also automatically available, and is +used to generate URLs to views instead of writing them out manually. + +After the page title, and before the content, the template loops over +each message returned by :func:`get_flashed_messages`. You used +:func:`flash` in the views to show error messages, and this is the code +that will display them. + +There are three blocks defined here that will be overridden in the other +templates: + +#. ``{% block title %}`` will change the title displayed in the + browser's tab and window title. + +#. ``{% block header %}`` is similar to ``title`` but will change the + title displayed on the page. + +#. ``{% block content %}`` is where the content of each page goes, such + as the login form or a blog post. + +The base template is directly in the ``templates`` directory. To keep +the others organized, the templates for a blueprint will be placed in a +directory with the same name as the blueprint. + + +Register +-------- + +.. code-block:: html+jinja + :caption: ``flaskr/templates/auth/register.html`` + + {% extends 'base.html' %} + + {% block header %} +

{% block title %}Register{% endblock %}

+ {% endblock %} + + {% block content %} +
+ + + + + +
+ {% endblock %} + +``{% extends 'base.html' %}`` tells Jinja that this template should +replace the blocks from the base template. All the rendered content must +appear inside ``{% block %}`` tags that override blocks from the base +template. + +A useful pattern used here is to place ``{% block title %}`` inside +``{% block header %}``. This will set the title block and then output +the value of it into the header block, so that both the window and page +share the same title without writing it twice. + +The ``input`` tags are using the ``required`` attribute here. This tells +the browser not to submit the form until those fields are filled in. If +the user is using an older browser that doesn't support that attribute, +or if they are using something besides a browser to make requests, you +still want to validate the data in the Flask view. It's important to +always fully validate the data on the server, even if the client does +some validation as well. + + +Log In +------ + +This is identical to the register template except for the title and +submit button. + +.. code-block:: html+jinja + :caption: ``flaskr/templates/auth/login.html`` + + {% extends 'base.html' %} + + {% block header %} +

{% block title %}Log In{% endblock %}

+ {% endblock %} + + {% block content %} +
+ + + + + +
+ {% endblock %} + + +Register A User +--------------- + +Now that the authentication templates are written, you can register a +user. Make sure the server is still running (``flask run`` if it's not), +then go to http://127.0.0.1:5000/auth/register. + +Try clicking the "Register" button without filling out the form and see +that the browser shows an error message. Try removing the ``required`` +attributes from the ``register.html`` template and click "Register" +again. Instead of the browser showing an error, the page will reload and +the error from :func:`flash` in the view will be shown. + +Fill out a username and password and you'll be redirected to the login +page. Try entering an incorrect username, or the correct username and +incorrect password. If you log in you'll get an error because there's +no ``index`` view to redirect to yet. + +Continue to :doc:`static`. diff --git a/src/flask-main/docs/tutorial/tests.rst b/src/flask-main/docs/tutorial/tests.rst new file mode 100644 index 0000000..8958e77 --- /dev/null +++ b/src/flask-main/docs/tutorial/tests.rst @@ -0,0 +1,559 @@ +.. currentmodule:: flask + +Test Coverage +============= + +Writing unit tests for your application lets you check that the code +you wrote works the way you expect. Flask provides a test client that +simulates requests to the application and returns the response data. + +You should test as much of your code as possible. Code in functions only +runs when the function is called, and code in branches, such as ``if`` +blocks, only runs when the condition is met. You want to make sure that +each function is tested with data that covers each branch. + +The closer you get to 100% coverage, the more comfortable you can be +that making a change won't unexpectedly change other behavior. However, +100% coverage doesn't guarantee that your application doesn't have bugs. +In particular, it doesn't test how the user interacts with the +application in the browser. Despite this, test coverage is an important +tool to use during development. + +.. note:: + This is being introduced late in the tutorial, but in your future + projects you should test as you develop. + +You'll use `pytest`_ and `coverage`_ to test and measure your code. +Install them both: + +.. code-block:: none + + $ pip install pytest coverage + +.. _pytest: https://pytest.readthedocs.io/ +.. _coverage: https://coverage.readthedocs.io/ + + +Setup and Fixtures +------------------ + +The test code is located in the ``tests`` directory. This directory is +*next to* the ``flaskr`` package, not inside it. The +``tests/conftest.py`` file contains setup functions called *fixtures* +that each test will use. Tests are in Python modules that start with +``test_``, and each test function in those modules also starts with +``test_``. + +Each test will create a new temporary database file and populate some +data that will be used in the tests. Write a SQL file to insert that +data. + +.. code-block:: sql + :caption: ``tests/data.sql`` + + INSERT INTO user (username, password) + VALUES + ('test', 'pbkdf2:sha256:50000$TCI4GzcX$0de171a4f4dac32e3364c7ddc7c14f3e2fa61f2d17574483f7ffbb431b4acb2f'), + ('other', 'pbkdf2:sha256:50000$kJPKsz6N$d2d4784f1b030a9761f5ccaeeaca413f27f2ecb76d6168407af962ddce849f79'); + + INSERT INTO post (title, body, author_id, created) + VALUES + ('test title', 'test' || x'0a' || 'body', 1, '2018-01-01 00:00:00'); + +The ``app`` fixture will call the factory and pass ``test_config`` to +configure the application and database for testing instead of using your +local development configuration. + +.. code-block:: python + :caption: ``tests/conftest.py`` + + import os + import tempfile + + import pytest + from flaskr import create_app + from flaskr.db import get_db, init_db + + with open(os.path.join(os.path.dirname(__file__), 'data.sql'), 'rb') as f: + _data_sql = f.read().decode('utf8') + + + @pytest.fixture + def app(): + db_fd, db_path = tempfile.mkstemp() + + app = create_app({ + 'TESTING': True, + 'DATABASE': db_path, + }) + + with app.app_context(): + init_db() + get_db().executescript(_data_sql) + + yield app + + os.close(db_fd) + os.unlink(db_path) + + + @pytest.fixture + def client(app): + return app.test_client() + + + @pytest.fixture + def runner(app): + return app.test_cli_runner() + +:func:`tempfile.mkstemp` creates and opens a temporary file, returning +the file descriptor and the path to it. The ``DATABASE`` path is +overridden so it points to this temporary path instead of the instance +folder. After setting the path, the database tables are created and the +test data is inserted. After the test is over, the temporary file is +closed and removed. + +:data:`TESTING` tells Flask that the app is in test mode. Flask changes +some internal behavior so it's easier to test, and other extensions can +also use the flag to make testing them easier. + +The ``client`` fixture calls +:meth:`app.test_client() ` with the application +object created by the ``app`` fixture. Tests will use the client to make +requests to the application without running the server. + +The ``runner`` fixture is similar to ``client``. +:meth:`app.test_cli_runner() ` creates a runner +that can call the Click commands registered with the application. + +Pytest uses fixtures by matching their function names with the names +of arguments in the test functions. For example, the ``test_hello`` +function you'll write next takes a ``client`` argument. Pytest matches +that with the ``client`` fixture function, calls it, and passes the +returned value to the test function. + + +Factory +------- + +There's not much to test about the factory itself. Most of the code will +be executed for each test already, so if something fails the other tests +will notice. + +The only behavior that can change is passing test config. If config is +not passed, there should be some default configuration, otherwise the +configuration should be overridden. + +.. code-block:: python + :caption: ``tests/test_factory.py`` + + from flaskr import create_app + + + def test_config(): + assert not create_app().testing + assert create_app({'TESTING': True}).testing + + + def test_hello(client): + response = client.get('/hello') + assert response.data == b'Hello, World!' + +You added the ``hello`` route as an example when writing the factory at +the beginning of the tutorial. It returns "Hello, World!", so the test +checks that the response data matches. + + +Database +-------- + +Within an application context, ``get_db`` should return the same +connection each time it's called. After the context, the connection +should be closed. + +.. code-block:: python + :caption: ``tests/test_db.py`` + + import sqlite3 + + import pytest + from flaskr.db import get_db + + + def test_get_close_db(app): + with app.app_context(): + db = get_db() + assert db is get_db() + + with pytest.raises(sqlite3.ProgrammingError) as e: + db.execute('SELECT 1') + + assert 'closed' in str(e.value) + +The ``init-db`` command should call the ``init_db`` function and output +a message. + +.. code-block:: python + :caption: ``tests/test_db.py`` + + def test_init_db_command(runner, monkeypatch): + class Recorder(object): + called = False + + def fake_init_db(): + Recorder.called = True + + monkeypatch.setattr('flaskr.db.init_db', fake_init_db) + result = runner.invoke(args=['init-db']) + assert 'Initialized' in result.output + assert Recorder.called + +This test uses Pytest's ``monkeypatch`` fixture to replace the +``init_db`` function with one that records that it's been called. The +``runner`` fixture you wrote above is used to call the ``init-db`` +command by name. + + +Authentication +-------------- + +For most of the views, a user needs to be logged in. The easiest way to +do this in tests is to make a ``POST`` request to the ``login`` view +with the client. Rather than writing that out every time, you can write +a class with methods to do that, and use a fixture to pass it the client +for each test. + +.. code-block:: python + :caption: ``tests/conftest.py`` + + class AuthActions(object): + def __init__(self, client): + self._client = client + + def login(self, username='test', password='test'): + return self._client.post( + '/auth/login', + data={'username': username, 'password': password} + ) + + def logout(self): + return self._client.get('/auth/logout') + + + @pytest.fixture + def auth(client): + return AuthActions(client) + +With the ``auth`` fixture, you can call ``auth.login()`` in a test to +log in as the ``test`` user, which was inserted as part of the test +data in the ``app`` fixture. + +The ``register`` view should render successfully on ``GET``. On ``POST`` +with valid form data, it should redirect to the login URL and the user's +data should be in the database. Invalid data should display error +messages. + +.. code-block:: python + :caption: ``tests/test_auth.py`` + + import pytest + from flask import g, session + from flaskr.db import get_db + + + def test_register(client, app): + assert client.get('/auth/register').status_code == 200 + response = client.post( + '/auth/register', data={'username': 'a', 'password': 'a'} + ) + assert response.headers["Location"] == "/auth/login" + + with app.app_context(): + assert get_db().execute( + "SELECT * FROM user WHERE username = 'a'", + ).fetchone() is not None + + + @pytest.mark.parametrize(('username', 'password', 'message'), ( + ('', '', b'Username is required.'), + ('a', '', b'Password is required.'), + ('test', 'test', b'already registered'), + )) + def test_register_validate_input(client, username, password, message): + response = client.post( + '/auth/register', + data={'username': username, 'password': password} + ) + assert message in response.data + +:meth:`client.get() ` makes a ``GET`` request +and returns the :class:`Response` object returned by Flask. Similarly, +:meth:`client.post() ` makes a ``POST`` +request, converting the ``data`` dict into form data. + +To test that the page renders successfully, a simple request is made and +checked for a ``200 OK`` :attr:`~Response.status_code`. If +rendering failed, Flask would return a ``500 Internal Server Error`` +code. + +:attr:`~Response.headers` will have a ``Location`` header with the login +URL when the register view redirects to the login view. + +:attr:`~Response.data` contains the body of the response as bytes. If +you expect a certain value to render on the page, check that it's in +``data``. Bytes must be compared to bytes. If you want to compare text, +use :meth:`get_data(as_text=True) ` +instead. + +``pytest.mark.parametrize`` tells Pytest to run the same test function +with different arguments. You use it here to test different invalid +input and error messages without writing the same code three times. + +The tests for the ``login`` view are very similar to those for +``register``. Rather than testing the data in the database, +:data:`.session` should have ``user_id`` set after logging in. + +.. code-block:: python + :caption: ``tests/test_auth.py`` + + def test_login(client, auth): + assert client.get('/auth/login').status_code == 200 + response = auth.login() + assert response.headers["Location"] == "/" + + with client: + client.get('/') + assert session['user_id'] == 1 + assert g.user['username'] == 'test' + + + @pytest.mark.parametrize(('username', 'password', 'message'), ( + ('a', 'test', b'Incorrect username.'), + ('test', 'a', b'Incorrect password.'), + )) + def test_login_validate_input(auth, username, password, message): + response = auth.login(username, password) + assert message in response.data + +Using ``client`` in a ``with`` block allows accessing context variables +such as :data:`.session` after the response is returned. Normally, +accessing ``session`` outside of a request would raise an error. + +Testing ``logout`` is the opposite of ``login``. :data:`.session` should +not contain ``user_id`` after logging out. + +.. code-block:: python + :caption: ``tests/test_auth.py`` + + def test_logout(client, auth): + auth.login() + + with client: + auth.logout() + assert 'user_id' not in session + + +Blog +---- + +All the blog views use the ``auth`` fixture you wrote earlier. Call +``auth.login()`` and subsequent requests from the client will be logged +in as the ``test`` user. + +The ``index`` view should display information about the post that was +added with the test data. When logged in as the author, there should be +a link to edit the post. + +You can also test some more authentication behavior while testing the +``index`` view. When not logged in, each page shows links to log in or +register. When logged in, there's a link to log out. + +.. code-block:: python + :caption: ``tests/test_blog.py`` + + import pytest + from flaskr.db import get_db + + + def test_index(client, auth): + response = client.get('/') + assert b"Log In" in response.data + assert b"Register" in response.data + + auth.login() + response = client.get('/') + assert b'Log Out' in response.data + assert b'test title' in response.data + assert b'by test on 2018-01-01' in response.data + assert b'test\nbody' in response.data + assert b'href="/1/update"' in response.data + +A user must be logged in to access the ``create``, ``update``, and +``delete`` views. The logged in user must be the author of the post to +access ``update`` and ``delete``, otherwise a ``403 Forbidden`` status +is returned. If a ``post`` with the given ``id`` doesn't exist, +``update`` and ``delete`` should return ``404 Not Found``. + +.. code-block:: python + :caption: ``tests/test_blog.py`` + + @pytest.mark.parametrize('path', ( + '/create', + '/1/update', + '/1/delete', + )) + def test_login_required(client, path): + response = client.post(path) + assert response.headers["Location"] == "/auth/login" + + + def test_author_required(app, client, auth): + # change the post author to another user + with app.app_context(): + db = get_db() + db.execute('UPDATE post SET author_id = 2 WHERE id = 1') + db.commit() + + auth.login() + # current user can't modify other user's post + assert client.post('/1/update').status_code == 403 + assert client.post('/1/delete').status_code == 403 + # current user doesn't see edit link + assert b'href="/1/update"' not in client.get('/').data + + + @pytest.mark.parametrize('path', ( + '/2/update', + '/2/delete', + )) + def test_exists_required(client, auth, path): + auth.login() + assert client.post(path).status_code == 404 + +The ``create`` and ``update`` views should render and return a +``200 OK`` status for a ``GET`` request. When valid data is sent in a +``POST`` request, ``create`` should insert the new post data into the +database, and ``update`` should modify the existing data. Both pages +should show an error message on invalid data. + +.. code-block:: python + :caption: ``tests/test_blog.py`` + + def test_create(client, auth, app): + auth.login() + assert client.get('/create').status_code == 200 + client.post('/create', data={'title': 'created', 'body': ''}) + + with app.app_context(): + db = get_db() + count = db.execute('SELECT COUNT(id) FROM post').fetchone()[0] + assert count == 2 + + + def test_update(client, auth, app): + auth.login() + assert client.get('/1/update').status_code == 200 + client.post('/1/update', data={'title': 'updated', 'body': ''}) + + with app.app_context(): + db = get_db() + post = db.execute('SELECT * FROM post WHERE id = 1').fetchone() + assert post['title'] == 'updated' + + + @pytest.mark.parametrize('path', ( + '/create', + '/1/update', + )) + def test_create_update_validate(client, auth, path): + auth.login() + response = client.post(path, data={'title': '', 'body': ''}) + assert b'Title is required.' in response.data + +The ``delete`` view should redirect to the index URL and the post should +no longer exist in the database. + +.. code-block:: python + :caption: ``tests/test_blog.py`` + + def test_delete(client, auth, app): + auth.login() + response = client.post('/1/delete') + assert response.headers["Location"] == "/" + + with app.app_context(): + db = get_db() + post = db.execute('SELECT * FROM post WHERE id = 1').fetchone() + assert post is None + + +Running the Tests +----------------- + +Some extra configuration, which is not required but makes running tests with coverage +less verbose, can be added to the project's ``pyproject.toml`` file. + +.. code-block:: toml + :caption: ``pyproject.toml`` + + [tool.pytest.ini_options] + testpaths = ["tests"] + + [tool.coverage.run] + branch = true + source = ["flaskr"] + +To run the tests, use the ``pytest`` command. It will find and run all +the test functions you've written. + +.. code-block:: none + + $ pytest + + ========================= test session starts ========================== + platform linux -- Python 3.6.4, pytest-3.5.0, py-1.5.3, pluggy-0.6.0 + rootdir: /home/user/Projects/flask-tutorial + collected 23 items + + tests/test_auth.py ........ [ 34%] + tests/test_blog.py ............ [ 86%] + tests/test_db.py .. [ 95%] + tests/test_factory.py .. [100%] + + ====================== 24 passed in 0.64 seconds ======================= + +If any tests fail, pytest will show the error that was raised. You can +run ``pytest -v`` to get a list of each test function rather than dots. + +To measure the code coverage of your tests, use the ``coverage`` command +to run pytest instead of running it directly. + +.. code-block:: none + + $ coverage run -m pytest + +You can either view a simple coverage report in the terminal: + +.. code-block:: none + + $ coverage report + + Name Stmts Miss Branch BrPart Cover + ------------------------------------------------------ + flaskr/__init__.py 21 0 2 0 100% + flaskr/auth.py 54 0 22 0 100% + flaskr/blog.py 54 0 16 0 100% + flaskr/db.py 24 0 4 0 100% + ------------------------------------------------------ + TOTAL 153 0 44 0 100% + +An HTML report allows you to see which lines were covered in each file: + +.. code-block:: none + + $ coverage html + +This generates files in the ``htmlcov`` directory. Open +``htmlcov/index.html`` in your browser to see the report. + +Continue to :doc:`deploy`. diff --git a/src/flask-main/docs/tutorial/views.rst b/src/flask-main/docs/tutorial/views.rst new file mode 100644 index 0000000..6626628 --- /dev/null +++ b/src/flask-main/docs/tutorial/views.rst @@ -0,0 +1,305 @@ +.. currentmodule:: flask + +Blueprints and Views +==================== + +A view function is the code you write to respond to requests to your +application. Flask uses patterns to match the incoming request URL to +the view that should handle it. The view returns data that Flask turns +into an outgoing response. Flask can also go the other direction and +generate a URL to a view based on its name and arguments. + + +Create a Blueprint +------------------ + +A :class:`Blueprint` is a way to organize a group of related views and +other code. Rather than registering views and other code directly with +an application, they are registered with a blueprint. Then the blueprint +is registered with the application when it is available in the factory +function. + +Flaskr will have two blueprints, one for authentication functions and +one for the blog posts functions. The code for each blueprint will go +in a separate module. Since the blog needs to know about authentication, +you'll write the authentication one first. + +.. code-block:: python + :caption: ``flaskr/auth.py`` + + import functools + + from flask import ( + Blueprint, flash, g, redirect, render_template, request, session, url_for + ) + from werkzeug.security import check_password_hash, generate_password_hash + + from flaskr.db import get_db + + bp = Blueprint('auth', __name__, url_prefix='/auth') + +This creates a :class:`Blueprint` named ``'auth'``. Like the application +object, the blueprint needs to know where it's defined, so ``__name__`` +is passed as the second argument. The ``url_prefix`` will be prepended +to all the URLs associated with the blueprint. + +Import and register the blueprint from the factory using +:meth:`app.register_blueprint() `. Place the +new code at the end of the factory function before returning the app. + +.. code-block:: python + :caption: ``flaskr/__init__.py`` + + def create_app(): + app = ... + # existing code omitted + + from . import auth + app.register_blueprint(auth.bp) + + return app + +The authentication blueprint will have views to register new users and +to log in and log out. + + +The First View: Register +------------------------ + +When the user visits the ``/auth/register`` URL, the ``register`` view +will return `HTML`_ with a form for them to fill out. When they submit +the form, it will validate their input and either show the form again +with an error message or create the new user and go to the login page. + +.. _HTML: https://developer.mozilla.org/docs/Web/HTML + +For now you will just write the view code. On the next page, you'll +write templates to generate the HTML form. + +.. code-block:: python + :caption: ``flaskr/auth.py`` + + @bp.route('/register', methods=('GET', 'POST')) + def register(): + if request.method == 'POST': + username = request.form['username'] + password = request.form['password'] + db = get_db() + error = None + + if not username: + error = 'Username is required.' + elif not password: + error = 'Password is required.' + + if error is None: + try: + db.execute( + "INSERT INTO user (username, password) VALUES (?, ?)", + (username, generate_password_hash(password)), + ) + db.commit() + except db.IntegrityError: + error = f"User {username} is already registered." + else: + return redirect(url_for("auth.login")) + + flash(error) + + return render_template('auth/register.html') + +Here's what the ``register`` view function is doing: + +#. :meth:`@bp.route ` associates the URL ``/register`` + with the ``register`` view function. When Flask receives a request + to ``/auth/register``, it will call the ``register`` view and use + the return value as the response. + +#. If the user submitted the form, + :attr:`request.method ` will be ``'POST'``. In this + case, start validating the input. + +#. :attr:`request.form ` is a special type of + :class:`dict` mapping submitted form keys and values. The user will + input their ``username`` and ``password``. + +#. Validate that ``username`` and ``password`` are not empty. + +#. If validation succeeds, insert the new user data into the database. + + - :meth:`db.execute ` takes a SQL + query with ``?`` placeholders for any user input, and a tuple of + values to replace the placeholders with. The database library + will take care of escaping the values so you are not vulnerable + to a *SQL injection attack*. + + - For security, passwords should never be stored in the database + directly. Instead, + :func:`~werkzeug.security.generate_password_hash` is used to + securely hash the password, and that hash is stored. Since this + query modifies data, + :meth:`db.commit() ` needs to be + called afterwards to save the changes. + + - An :exc:`sqlite3.IntegrityError` will occur if the username + already exists, which should be shown to the user as another + validation error. + +#. After storing the user, they are redirected to the login page. + :func:`url_for` generates the URL for the login view based on its + name. This is preferable to writing the URL directly as it allows + you to change the URL later without changing all code that links to + it. :func:`redirect` generates a redirect response to the generated + URL. + +#. If validation fails, the error is shown to the user. :func:`flash` + stores messages that can be retrieved when rendering the template. + +#. When the user initially navigates to ``auth/register``, or + there was a validation error, an HTML page with the registration + form should be shown. :func:`render_template` will render a template + containing the HTML, which you'll write in the next step of the + tutorial. + + +Login +----- + +This view follows the same pattern as the ``register`` view above. + +.. code-block:: python + :caption: ``flaskr/auth.py`` + + @bp.route('/login', methods=('GET', 'POST')) + def login(): + if request.method == 'POST': + username = request.form['username'] + password = request.form['password'] + db = get_db() + error = None + user = db.execute( + 'SELECT * FROM user WHERE username = ?', (username,) + ).fetchone() + + if user is None: + error = 'Incorrect username.' + elif not check_password_hash(user['password'], password): + error = 'Incorrect password.' + + if error is None: + session.clear() + session['user_id'] = user['id'] + return redirect(url_for('index')) + + flash(error) + + return render_template('auth/login.html') + +There are a few differences from the ``register`` view: + +#. The user is queried first and stored in a variable for later use. + + :meth:`~sqlite3.Cursor.fetchone` returns one row from the query. + If the query returned no results, it returns ``None``. Later, + :meth:`~sqlite3.Cursor.fetchall` will be used, which returns a list + of all results. + +#. :func:`~werkzeug.security.check_password_hash` hashes the submitted + password in the same way as the stored hash and securely compares + them. If they match, the password is valid. + +#. :data:`.session` is a :class:`dict` that stores data across requests. + When validation succeeds, the user's ``id`` is stored in a new + session. The data is stored in a *cookie* that is sent to the + browser, and the browser then sends it back with subsequent requests. + Flask securely *signs* the data so that it can't be tampered with. + +Now that the user's ``id`` is stored in the :data:`.session`, it will be +available on subsequent requests. At the beginning of each request, if +a user is logged in their information should be loaded and made +available to other views. + +.. code-block:: python + :caption: ``flaskr/auth.py`` + + @bp.before_app_request + def load_logged_in_user(): + user_id = session.get('user_id') + + if user_id is None: + g.user = None + else: + g.user = get_db().execute( + 'SELECT * FROM user WHERE id = ?', (user_id,) + ).fetchone() + +:meth:`bp.before_app_request() ` registers +a function that runs before the view function, no matter what URL is +requested. ``load_logged_in_user`` checks if a user id is stored in the +:data:`.session` and gets that user's data from the database, storing it +on :data:`g.user `, which lasts for the length of the request. If +there is no user id, or if the id doesn't exist, ``g.user`` will be +``None``. + + +Logout +------ + +To log out, you need to remove the user id from the :data:`.session`. +Then ``load_logged_in_user`` won't load a user on subsequent requests. + +.. code-block:: python + :caption: ``flaskr/auth.py`` + + @bp.route('/logout') + def logout(): + session.clear() + return redirect(url_for('index')) + + +Require Authentication in Other Views +------------------------------------- + +Creating, editing, and deleting blog posts will require a user to be +logged in. A *decorator* can be used to check this for each view it's +applied to. + +.. code-block:: python + :caption: ``flaskr/auth.py`` + + def login_required(view): + @functools.wraps(view) + def wrapped_view(**kwargs): + if g.user is None: + return redirect(url_for('auth.login')) + + return view(**kwargs) + + return wrapped_view + +This decorator returns a new view function that wraps the original view +it's applied to. The new function checks if a user is loaded and +redirects to the login page otherwise. If a user is loaded the original +view is called and continues normally. You'll use this decorator when +writing the blog views. + +Endpoints and URLs +------------------ + +The :func:`url_for` function generates the URL to a view based on a name +and arguments. The name associated with a view is also called the +*endpoint*, and by default it's the same as the name of the view +function. + +For example, the ``hello()`` view that was added to the app +factory earlier in the tutorial has the name ``'hello'`` and can be +linked to with ``url_for('hello')``. If it took an argument, which +you'll see later, it would be linked to using +``url_for('hello', who='World')``. + +When using a blueprint, the name of the blueprint is prepended to the +name of the function, so the endpoint for the ``login`` function you +wrote above is ``'auth.login'`` because you added it to the ``'auth'`` +blueprint. + +Continue to :doc:`templates`. diff --git a/src/flask-main/docs/views.rst b/src/flask-main/docs/views.rst new file mode 100644 index 0000000..f221027 --- /dev/null +++ b/src/flask-main/docs/views.rst @@ -0,0 +1,324 @@ +Class-based Views +================= + +.. currentmodule:: flask.views + +This page introduces using the :class:`View` and :class:`MethodView` +classes to write class-based views. + +A class-based view is a class that acts as a view function. Because it +is a class, different instances of the class can be created with +different arguments, to change the behavior of the view. This is also +known as generic, reusable, or pluggable views. + +An example of where this is useful is defining a class that creates an +API based on the database model it is initialized with. + +For more complex API behavior and customization, look into the various +API extensions for Flask. + + +Basic Reusable View +------------------- + +Let's walk through an example converting a view function to a view +class. We start with a view function that queries a list of users then +renders a template to show the list. + +.. code-block:: python + + @app.route("/users/") + def user_list(): + users = User.query.all() + return render_template("users.html", users=users) + +This works for the user model, but let's say you also had more models +that needed list pages. You'd need to write another view function for +each model, even though the only thing that would change is the model +and template name. + +Instead, you can write a :class:`View` subclass that will query a model +and render a template. As the first step, we'll convert the view to a +class without any customization. + +.. code-block:: python + + from flask.views import View + + class UserList(View): + def dispatch_request(self): + users = User.query.all() + return render_template("users.html", objects=users) + + app.add_url_rule("/users/", view_func=UserList.as_view("user_list")) + +The :meth:`View.dispatch_request` method is the equivalent of the view +function. Calling :meth:`View.as_view` method will create a view +function that can be registered on the app with its +:meth:`~flask.Flask.add_url_rule` method. The first argument to +``as_view`` is the name to use to refer to the view with +:func:`~flask.url_for`. + +.. note:: + + You can't decorate the class with ``@app.route()`` the way you'd + do with a basic view function. + +Next, we need to be able to register the same view class for different +models and templates, to make it more useful than the original function. +The class will take two arguments, the model and template, and store +them on ``self``. Then ``dispatch_request`` can reference these instead +of hard-coded values. + +.. code-block:: python + + class ListView(View): + def __init__(self, model, template): + self.model = model + self.template = template + + def dispatch_request(self): + items = self.model.query.all() + return render_template(self.template, items=items) + +Remember, we create the view function with ``View.as_view()`` instead of +creating the class directly. Any extra arguments passed to ``as_view`` +are then passed when creating the class. Now we can register the same +view to handle multiple models. + +.. code-block:: python + + app.add_url_rule( + "/users/", + view_func=ListView.as_view("user_list", User, "users.html"), + ) + app.add_url_rule( + "/stories/", + view_func=ListView.as_view("story_list", Story, "stories.html"), + ) + + +URL Variables +------------- + +Any variables captured by the URL are passed as keyword arguments to the +``dispatch_request`` method, as they would be for a regular view +function. + +.. code-block:: python + + class DetailView(View): + def __init__(self, model): + self.model = model + self.template = f"{model.__name__.lower()}/detail.html" + + def dispatch_request(self, id) + item = self.model.query.get_or_404(id) + return render_template(self.template, item=item) + + app.add_url_rule( + "/users/", + view_func=DetailView.as_view("user_detail", User) + ) + + +View Lifetime and ``self`` +-------------------------- + +By default, a new instance of the view class is created every time a +request is handled. This means that it is safe to write other data to +``self`` during the request, since the next request will not see it, +unlike other forms of global state. + +However, if your view class needs to do a lot of complex initialization, +doing it for every request is unnecessary and can be inefficient. To +avoid this, set :attr:`View.init_every_request` to ``False``, which will +only create one instance of the class and use it for every request. In +this case, writing to ``self`` is not safe. If you need to store data +during the request, use :data:`~flask.g` instead. + +In the ``ListView`` example, nothing writes to ``self`` during the +request, so it is more efficient to create a single instance. + +.. code-block:: python + + class ListView(View): + init_every_request = False + + def __init__(self, model, template): + self.model = model + self.template = template + + def dispatch_request(self): + items = self.model.query.all() + return render_template(self.template, items=items) + +Different instances will still be created each for each ``as_view`` +call, but not for each request to those views. + + +View Decorators +--------------- + +The view class itself is not the view function. View decorators need to +be applied to the view function returned by ``as_view``, not the class +itself. Set :attr:`View.decorators` to a list of decorators to apply. + +.. code-block:: python + + class UserList(View): + decorators = [cache(minutes=2), login_required] + + app.add_url_rule('/users/', view_func=UserList.as_view()) + +If you didn't set ``decorators``, you could apply them manually instead. +This is equivalent to: + +.. code-block:: python + + view = UserList.as_view("users_list") + view = cache(minutes=2)(view) + view = login_required(view) + app.add_url_rule('/users/', view_func=view) + +Keep in mind that order matters. If you're used to ``@decorator`` style, +this is equivalent to: + +.. code-block:: python + + @app.route("/users/") + @login_required + @cache(minutes=2) + def user_list(): + ... + + +Method Hints +------------ + +A common pattern is to register a view with ``methods=["GET", "POST"]``, +then check ``request.method == "POST"`` to decide what to do. Setting +:attr:`View.methods` is equivalent to passing the list of methods to +``add_url_rule`` or ``route``. + +.. code-block:: python + + class MyView(View): + methods = ["GET", "POST"] + + def dispatch_request(self): + if request.method == "POST": + ... + ... + + app.add_url_rule('/my-view', view_func=MyView.as_view('my-view')) + +This is equivalent to the following, except further subclasses can +inherit or change the methods. + +.. code-block:: python + + app.add_url_rule( + "/my-view", + view_func=MyView.as_view("my-view"), + methods=["GET", "POST"], + ) + + +Method Dispatching and APIs +--------------------------- + +For APIs it can be helpful to use a different function for each HTTP +method. :class:`MethodView` extends the basic :class:`View` to dispatch +to different methods of the class based on the request method. Each HTTP +method maps to a method of the class with the same (lowercase) name. + +:class:`MethodView` automatically sets :attr:`View.methods` based on the +methods defined by the class. It even knows how to handle subclasses +that override or define other methods. + +We can make a generic ``ItemAPI`` class that provides get (detail), +patch (edit), and delete methods for a given model. A ``GroupAPI`` can +provide get (list) and post (create) methods. + +.. code-block:: python + + from flask.views import MethodView + + class ItemAPI(MethodView): + init_every_request = False + + def __init__(self, model): + self.model = model + self.validator = generate_validator(model) + + def _get_item(self, id): + return self.model.query.get_or_404(id) + + def get(self, id): + item = self._get_item(id) + return jsonify(item.to_json()) + + def patch(self, id): + item = self._get_item(id) + errors = self.validator.validate(item, request.json) + + if errors: + return jsonify(errors), 400 + + item.update_from_json(request.json) + db.session.commit() + return jsonify(item.to_json()) + + def delete(self, id): + item = self._get_item(id) + db.session.delete(item) + db.session.commit() + return "", 204 + + class GroupAPI(MethodView): + init_every_request = False + + def __init__(self, model): + self.model = model + self.validator = generate_validator(model, create=True) + + def get(self): + items = self.model.query.all() + return jsonify([item.to_json() for item in items]) + + def post(self): + errors = self.validator.validate(request.json) + + if errors: + return jsonify(errors), 400 + + db.session.add(self.model.from_json(request.json)) + db.session.commit() + return jsonify(item.to_json()) + + def register_api(app, model, name): + item = ItemAPI.as_view(f"{name}-item", model) + group = GroupAPI.as_view(f"{name}-group", model) + app.add_url_rule(f"/{name}/", view_func=item) + app.add_url_rule(f"/{name}/", view_func=group) + + register_api(app, User, "users") + register_api(app, Story, "stories") + +This produces the following views, a standard REST API! + +================= ========== =================== +URL Method Description +----------------- ---------- ------------------- +``/users/`` ``GET`` List all users +``/users/`` ``POST`` Create a new user +``/users/`` ``GET`` Show a single user +``/users/`` ``PATCH`` Update a user +``/users/`` ``DELETE`` Delete a user +``/stories/`` ``GET`` List all stories +``/stories/`` ``POST`` Create a new story +``/stories/`` ``GET`` Show a single story +``/stories/`` ``PATCH`` Update a story +``/stories/`` ``DELETE`` Delete a story +================= ========== =================== diff --git a/src/flask-main/docs/web-security.rst b/src/flask-main/docs/web-security.rst new file mode 100644 index 0000000..4118b5e --- /dev/null +++ b/src/flask-main/docs/web-security.rst @@ -0,0 +1,316 @@ +Security Considerations +======================= + +Web applications face many types of potential security problems, and it can be +hard to get everything right, or even to know what "right" is in general. Flask +tries to solve a few of these things by default, but there are other parts you +may have to take care of yourself. Many of these solutions are tradeoffs, and +will depend on each application's specific needs and threat model. Many hosting +platforms may take care of certain types of problems without the need for the +Flask application to handle them. + +Resource Use +------------ + +A common category of attacks is "Denial of Service" (DoS or DDoS). This is a +very broad category, and different variants target different layers in a +deployed application. In general, something is done to increase how much +processing time or memory is used to handle each request, to the point where +there are not enough resources to handle legitimate requests. + +Flask provides a few configuration options to handle resource use. They can +also be set on individual requests to customize only that request. The +documentation for each goes into more detail. + +- :data:`MAX_CONTENT_LENGTH` or :attr:`.Request.max_content_length` controls + how much data will be read from a request. It is not set by default, + although it will still block truly unlimited streams unless the WSGI server + indicates support. +- :data:`MAX_FORM_MEMORY_SIZE` or :attr:`.Request.max_form_memory_size` + controls how large any non-file ``multipart/form-data`` field can be. It is + set to 500kB by default. +- :data:`MAX_FORM_PARTS` or :attr:`.Request.max_form_parts` controls how many + ``multipart/form-data`` fields can be parsed. It is set to 1000 by default. + Combined with the default `max_form_memory_size`, this means that a form + will occupy at most 500MB of memory. + +Regardless of these settings, you should also review what settings are available +from your operating system, container deployment (Docker etc), WSGI server, HTTP +server, and hosting platform. They typically have ways to set process resource +limits, timeouts, and other checks regardless of how Flask is configured. + +.. _security-xss: + +Cross-Site Scripting (XSS) +-------------------------- + +Cross site scripting is the concept of injecting arbitrary HTML (and with +it JavaScript) into the context of a website. To remedy this, developers +have to properly escape text so that it cannot include arbitrary HTML +tags. For more information on that have a look at the Wikipedia article +on `Cross-Site Scripting +`_. + +Flask configures Jinja to automatically escape all values unless +explicitly told otherwise. This should rule out all XSS problems caused +in templates, but there are still other places where you have to be +careful: + +- generating HTML without the help of Jinja +- calling :class:`~markupsafe.Markup` on data submitted by users +- sending out HTML from uploaded files, never do that, use the + ``Content-Disposition: attachment`` header to prevent that problem. +- sending out textfiles from uploaded files. Some browsers are using + content-type guessing based on the first few bytes so users could + trick a browser to execute HTML. + +Another thing that is very important are unquoted attributes. While +Jinja can protect you from XSS issues by escaping HTML, there is one +thing it cannot protect you from: XSS by attribute injection. To counter +this possible attack vector, be sure to always quote your attributes with +either double or single quotes when using Jinja expressions in them: + +.. sourcecode:: html+jinja + + + +Why is this necessary? Because if you would not be doing that, an +attacker could easily inject custom JavaScript handlers. For example an +attacker could inject this piece of HTML+JavaScript: + +.. sourcecode:: html + + onmouseover=alert(document.cookie) + +When the user would then move with the mouse over the input, the cookie +would be presented to the user in an alert window. But instead of showing +the cookie to the user, a good attacker might also execute any other +JavaScript code. In combination with CSS injections the attacker might +even make the element fill out the entire page so that the user would +just have to have the mouse anywhere on the page to trigger the attack. + +There is one class of XSS issues that Jinja's escaping does not protect +against. The ``a`` tag's ``href`` attribute can contain a `javascript:` URI, +which the browser will execute when clicked if not secured properly. + +.. sourcecode:: html + + click here + click here + +To prevent this, you'll need to set the :ref:`security-csp` response header. + +Cross-Site Request Forgery (CSRF) +--------------------------------- + +Another big problem is CSRF. This is a very complex topic and I won't +outline it here in detail just mention what it is and how to theoretically +prevent it. + +If your authentication information is stored in cookies, you have implicit +state management. The state of "being logged in" is controlled by a +cookie, and that cookie is sent with each request to a page. +Unfortunately that includes requests triggered by 3rd party sites. If you +don't keep that in mind, some people might be able to trick your +application's users with social engineering to do stupid things without +them knowing. + +Say you have a specific URL that, when you sent ``POST`` requests to will +delete a user's profile (say ``http://example.com/user/delete``). If an +attacker now creates a page that sends a post request to that page with +some JavaScript they just have to trick some users to load that page and +their profiles will end up being deleted. + +Imagine you were to run Facebook with millions of concurrent users and +someone would send out links to images of little kittens. When users +would go to that page, their profiles would get deleted while they are +looking at images of fluffy cats. + +How can you prevent that? Basically for each request that modifies +content on the server you would have to either use a one-time token and +store that in the cookie **and** also transmit it with the form data. +After receiving the data on the server again, you would then have to +compare the two tokens and ensure they are equal. + +Why does Flask not do that for you? The ideal place for this to happen is +the form validation framework, which does not exist in Flask. + +.. _security-json: + +JSON Security +------------- + +In Flask 0.10 and lower, :func:`~flask.jsonify` did not serialize top-level +arrays to JSON. This was because of a security vulnerability in ECMAScript 4. + +ECMAScript 5 closed this vulnerability, so only extremely old browsers are +still vulnerable. All of these browsers have `other more serious +vulnerabilities +`_, so +this behavior was changed and :func:`~flask.jsonify` now supports serializing +arrays. + +Security Headers +---------------- + +Browsers recognize various response headers in order to control security. We +recommend reviewing each of the headers below for use in your application. +The `Flask-Talisman`_ extension can be used to manage HTTPS and the security +headers for you. + +.. _Flask-Talisman: https://github.com/wntrblm/flask-talisman + +HTTP Strict Transport Security (HSTS) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Tells the browser to convert all HTTP requests to HTTPS, preventing +man-in-the-middle (MITM) attacks. :: + + response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains' + +- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security + +.. _security-csp: + +Content Security Policy (CSP) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Tell the browser where it can load various types of resource from. This header +should be used whenever possible, but requires some work to define the correct +policy for your site. A very strict policy would be:: + + response.headers['Content-Security-Policy'] = "default-src 'self'" + +- https://csp.withgoogle.com/docs/index.html +- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy + +X-Content-Type-Options +~~~~~~~~~~~~~~~~~~~~~~ + +Forces the browser to honor the response content type instead of trying to +detect it, which can be abused to generate a cross-site scripting (XSS) +attack. :: + + response.headers['X-Content-Type-Options'] = 'nosniff' + +- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options + +X-Frame-Options +~~~~~~~~~~~~~~~ + +Prevents external sites from embedding your site in an ``iframe``. This +prevents a class of attacks where clicks in the outer frame can be translated +invisibly to clicks on your page's elements. This is also known as +"clickjacking". :: + + response.headers['X-Frame-Options'] = 'SAMEORIGIN' + +- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options + +.. _security-cookie: + +Set-Cookie options +~~~~~~~~~~~~~~~~~~ + +These options can be added to a ``Set-Cookie`` header to improve their +security. Flask has configuration options to set these on the session cookie. +They can be set on other cookies too. + +- ``Secure`` limits cookies to HTTPS traffic only. +- ``HttpOnly`` protects the contents of cookies from being read with + JavaScript. +- ``SameSite`` restricts how cookies are sent with requests from + external sites. Can be set to ``'Lax'`` (recommended) or ``'Strict'``. + ``Lax`` prevents sending cookies with CSRF-prone requests from + external sites, such as submitting a form. ``Strict`` prevents sending + cookies with all external requests, including following regular links. + +:: + + app.config.update( + SESSION_COOKIE_SECURE=True, + SESSION_COOKIE_HTTPONLY=True, + SESSION_COOKIE_SAMESITE='Lax', + ) + + response.set_cookie('username', 'flask', secure=True, httponly=True, samesite='Lax') + +Specifying ``Expires`` or ``Max-Age`` options, will remove the cookie after +the given time, or the current time plus the age, respectively. If neither +option is set, the cookie will be removed when the browser is closed. :: + + # cookie expires after 10 minutes + response.set_cookie('snakes', '3', max_age=600) + +For the session cookie, if :attr:`session.permanent ` +is set, then :data:`PERMANENT_SESSION_LIFETIME` is used to set the expiration. +Flask's default cookie implementation validates that the cryptographic +signature is not older than this value. Lowering this value may help mitigate +replay attacks, where intercepted cookies can be sent at a later time. :: + + app.config.update( + PERMANENT_SESSION_LIFETIME=600 + ) + + @app.route('/login', methods=['POST']) + def login(): + ... + session.clear() + session['user_id'] = user.id + session.permanent = True + ... + +Use :class:`itsdangerous.TimedSerializer` to sign and validate other cookie +values (or any values that need secure signatures). + +- https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies +- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie + +.. _samesite_support: https://caniuse.com/#feat=same-site-cookie-attribute + + +Host Header Validation +---------------------- + +The ``Host`` header is used by the client to indicate what host name the request +was made to. This is used, for example, by ``url_for(..., _external=True)`` to +generate full URLs, for use in email or other messages outside the browser +window. + +By default the app doesn't know what host(s) it is allowed to be accessed +through, and assumes any host is valid. Although browsers do not allow setting +the ``Host`` header, requests made by attackers in other scenarios could set +the ``Host`` header to a value they want. + +When deploying your application, set :data:`TRUSTED_HOSTS` to restrict what +values the ``Host`` header may be. + +The ``Host`` header may be modified by proxies in between the client and your +application. See :doc:`deploying/proxy_fix` to tell your app which proxy values +to trust. + + +Copy/Paste to Terminal +---------------------- + +Hidden characters such as the backspace character (``\b``, ``^H``) can +cause text to render differently in HTML than how it is interpreted if +`pasted into a terminal `__. + +For example, ``import y\bose\bm\bi\bt\be\b`` renders as +``import yosemite`` in HTML, but the backspaces are applied when pasted +into a terminal, and it becomes ``import os``. + +If you expect users to copy and paste untrusted code from your site, +such as from comments posted by users on a technical blog, consider +applying extra filtering, such as replacing all ``\b`` characters. + +.. code-block:: python + + body = body.replace("\b", "") + +Most modern terminals will warn about and remove hidden characters when +pasting, so this isn't strictly necessary. It's also possible to craft +dangerous commands in other ways that aren't possible to filter. +Depending on your site's use case, it may be good to show a warning +about copying code in general. diff --git a/src/flask-main/examples/celery/README.md b/src/flask-main/examples/celery/README.md new file mode 100644 index 0000000..038eb51 --- /dev/null +++ b/src/flask-main/examples/celery/README.md @@ -0,0 +1,27 @@ +Background Tasks with Celery +============================ + +This example shows how to configure Celery with Flask, how to set up an API for +submitting tasks and polling results, and how to use that API with JavaScript. See +[Flask's documentation about Celery](https://flask.palletsprojects.com/patterns/celery/). + +From this directory, create a virtualenv and install the application into it. Then run a +Celery worker. + +```shell +$ python3 -m venv .venv +$ . ./.venv/bin/activate +$ pip install -r requirements.txt && pip install -e . +$ celery -A make_celery worker --loglevel INFO +``` + +In a separate terminal, activate the virtualenv and run the Flask development server. + +```shell +$ . ./.venv/bin/activate +$ flask -A task_app run --debug +``` + +Go to http://localhost:5000/ and use the forms to submit tasks. You can see the polling +requests in the browser dev tools and the Flask logs. You can see the tasks submitting +and completing in the Celery logs. diff --git a/src/flask-main/examples/celery/make_celery.py b/src/flask-main/examples/celery/make_celery.py new file mode 100644 index 0000000..f7d138e --- /dev/null +++ b/src/flask-main/examples/celery/make_celery.py @@ -0,0 +1,4 @@ +from task_app import create_app + +flask_app = create_app() +celery_app = flask_app.extensions["celery"] diff --git a/src/flask-main/examples/celery/pyproject.toml b/src/flask-main/examples/celery/pyproject.toml new file mode 100644 index 0000000..cca36d8 --- /dev/null +++ b/src/flask-main/examples/celery/pyproject.toml @@ -0,0 +1,17 @@ +[project] +name = "flask-example-celery" +version = "1.0.0" +description = "Example Flask application with Celery background tasks." +readme = "README.md" +classifiers = ["Private :: Do Not Upload"] +dependencies = ["flask", "celery[redis]"] + +[build-system] +requires = ["flit_core<4"] +build-backend = "flit_core.buildapi" + +[tool.flit.module] +name = "task_app" + +[tool.ruff] +src = ["src"] diff --git a/src/flask-main/examples/celery/requirements.txt b/src/flask-main/examples/celery/requirements.txt new file mode 100644 index 0000000..29075ab --- /dev/null +++ b/src/flask-main/examples/celery/requirements.txt @@ -0,0 +1,58 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --resolver=backtracking pyproject.toml +# +amqp==5.1.1 + # via kombu +async-timeout==4.0.2 + # via redis +billiard==3.6.4.0 + # via celery +blinker==1.6.2 + # via flask +celery[redis]==5.2.7 + # via flask-example-celery (pyproject.toml) +click==8.1.3 + # via + # celery + # click-didyoumean + # click-plugins + # click-repl + # flask +click-didyoumean==0.3.0 + # via celery +click-plugins==1.1.1 + # via celery +click-repl==0.2.0 + # via celery +flask==2.3.2 + # via flask-example-celery (pyproject.toml) +itsdangerous==2.1.2 + # via flask +jinja2==3.1.2 + # via flask +kombu==5.2.4 + # via celery +markupsafe==2.1.2 + # via + # jinja2 + # werkzeug +prompt-toolkit==3.0.38 + # via click-repl +pytz==2023.3 + # via celery +redis==4.5.4 + # via celery +six==1.16.0 + # via click-repl +vine==5.0.0 + # via + # amqp + # celery + # kombu +wcwidth==0.2.6 + # via prompt-toolkit +werkzeug==2.3.3 + # via flask diff --git a/src/flask-main/examples/celery/src/task_app/__init__.py b/src/flask-main/examples/celery/src/task_app/__init__.py new file mode 100644 index 0000000..dafff8a --- /dev/null +++ b/src/flask-main/examples/celery/src/task_app/__init__.py @@ -0,0 +1,39 @@ +from celery import Celery +from celery import Task +from flask import Flask +from flask import render_template + + +def create_app() -> Flask: + app = Flask(__name__) + app.config.from_mapping( + CELERY=dict( + broker_url="redis://localhost", + result_backend="redis://localhost", + task_ignore_result=True, + ), + ) + app.config.from_prefixed_env() + celery_init_app(app) + + @app.route("/") + def index() -> str: + return render_template("index.html") + + from . import views + + app.register_blueprint(views.bp) + return app + + +def celery_init_app(app: Flask) -> Celery: + class FlaskTask(Task): + def __call__(self, *args: object, **kwargs: object) -> object: + with app.app_context(): + return self.run(*args, **kwargs) + + celery_app = Celery(app.name, task_cls=FlaskTask) + celery_app.config_from_object(app.config["CELERY"]) + celery_app.set_default() + app.extensions["celery"] = celery_app + return celery_app diff --git a/src/flask-main/examples/celery/src/task_app/tasks.py b/src/flask-main/examples/celery/src/task_app/tasks.py new file mode 100644 index 0000000..b6b3595 --- /dev/null +++ b/src/flask-main/examples/celery/src/task_app/tasks.py @@ -0,0 +1,23 @@ +import time + +from celery import shared_task +from celery import Task + + +@shared_task(ignore_result=False) +def add(a: int, b: int) -> int: + return a + b + + +@shared_task() +def block() -> None: + time.sleep(5) + + +@shared_task(bind=True, ignore_result=False) +def process(self: Task, total: int) -> object: + for i in range(total): + self.update_state(state="PROGRESS", meta={"current": i + 1, "total": total}) + time.sleep(1) + + return {"current": total, "total": total} diff --git a/src/flask-main/examples/celery/src/task_app/templates/index.html b/src/flask-main/examples/celery/src/task_app/templates/index.html new file mode 100644 index 0000000..4e1145c --- /dev/null +++ b/src/flask-main/examples/celery/src/task_app/templates/index.html @@ -0,0 +1,108 @@ + + + + + Celery Example + + +

Celery Example

+Execute background tasks with Celery. Submits tasks and shows results using JavaScript. + +
+

Add

+

Start a task to add two numbers, then poll for the result. +

+
+
+ +
+

Result:

+ +
+

Block

+

Start a task that takes 5 seconds. However, the response will return immediately. +

+ +
+

+ +
+

Process

+

Start a task that counts, waiting one second each time, showing progress. +

+
+ +
+

+ + + + diff --git a/src/flask-main/examples/celery/src/task_app/views.py b/src/flask-main/examples/celery/src/task_app/views.py new file mode 100644 index 0000000..99cf92d --- /dev/null +++ b/src/flask-main/examples/celery/src/task_app/views.py @@ -0,0 +1,38 @@ +from celery.result import AsyncResult +from flask import Blueprint +from flask import request + +from . import tasks + +bp = Blueprint("tasks", __name__, url_prefix="/tasks") + + +@bp.get("/result/") +def result(id: str) -> dict[str, object]: + result = AsyncResult(id) + ready = result.ready() + return { + "ready": ready, + "successful": result.successful() if ready else None, + "value": result.get() if ready else result.result, + } + + +@bp.post("/add") +def add() -> dict[str, object]: + a = request.form.get("a", type=int) + b = request.form.get("b", type=int) + result = tasks.add.delay(a, b) + return {"result_id": result.id} + + +@bp.post("/block") +def block() -> dict[str, object]: + result = tasks.block.delay() + return {"result_id": result.id} + + +@bp.post("/process") +def process() -> dict[str, object]: + result = tasks.process.delay(total=request.form.get("total", type=int)) + return {"result_id": result.id} diff --git a/src/flask-main/examples/javascript/.gitignore b/src/flask-main/examples/javascript/.gitignore new file mode 100644 index 0000000..a306afb --- /dev/null +++ b/src/flask-main/examples/javascript/.gitignore @@ -0,0 +1,14 @@ +.venv/ +*.pyc +__pycache__/ +instance/ +.cache/ +.pytest_cache/ +.coverage +htmlcov/ +dist/ +build/ +*.egg-info/ +.idea/ +*.swp +*~ diff --git a/src/flask-main/examples/javascript/LICENSE.txt b/src/flask-main/examples/javascript/LICENSE.txt new file mode 100644 index 0000000..9d227a0 --- /dev/null +++ b/src/flask-main/examples/javascript/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/src/flask-main/examples/javascript/README.rst b/src/flask-main/examples/javascript/README.rst new file mode 100644 index 0000000..f5f6691 --- /dev/null +++ b/src/flask-main/examples/javascript/README.rst @@ -0,0 +1,48 @@ +JavaScript Ajax Example +======================= + +Demonstrates how to post form data and process a JSON response using +JavaScript. This allows making requests without navigating away from the +page. Demonstrates using |fetch|_, |XMLHttpRequest|_, and +|jQuery.ajax|_. See the `Flask docs`_ about JavaScript and Ajax. + +.. |fetch| replace:: ``fetch`` +.. _fetch: https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch + +.. |XMLHttpRequest| replace:: ``XMLHttpRequest`` +.. _XMLHttpRequest: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest + +.. |jQuery.ajax| replace:: ``jQuery.ajax`` +.. _jQuery.ajax: https://api.jquery.com/jQuery.ajax/ + +.. _Flask docs: https://flask.palletsprojects.com/patterns/javascript/ + + +Install +------- + +.. code-block:: text + + $ python3 -m venv .venv + $ . .venv/bin/activate + $ pip install -e . + + +Run +--- + +.. code-block:: text + + $ flask --app js_example run + +Open http://127.0.0.1:5000 in a browser. + + +Test +---- + +.. code-block:: text + + $ pip install -e '.[test]' + $ coverage run -m pytest + $ coverage report diff --git a/src/flask-main/examples/javascript/js_example/__init__.py b/src/flask-main/examples/javascript/js_example/__init__.py new file mode 100644 index 0000000..0ec3ca2 --- /dev/null +++ b/src/flask-main/examples/javascript/js_example/__init__.py @@ -0,0 +1,5 @@ +from flask import Flask + +app = Flask(__name__) + +from js_example import views # noqa: E402, F401 diff --git a/src/flask-main/examples/javascript/js_example/templates/base.html b/src/flask-main/examples/javascript/js_example/templates/base.html new file mode 100644 index 0000000..a4d35bd --- /dev/null +++ b/src/flask-main/examples/javascript/js_example/templates/base.html @@ -0,0 +1,33 @@ + +JavaScript Example + + + + +
+

{% block intro %}{% endblock %}

+
+
+ + + + + +
+= +{% block script %}{% endblock %} diff --git a/src/flask-main/examples/javascript/js_example/templates/fetch.html b/src/flask-main/examples/javascript/js_example/templates/fetch.html new file mode 100644 index 0000000..e2944b8 --- /dev/null +++ b/src/flask-main/examples/javascript/js_example/templates/fetch.html @@ -0,0 +1,33 @@ +{% extends 'base.html' %} + +{% block intro %} + fetch + is the modern plain JavaScript way to make requests. It's + supported in all modern browsers. +{% endblock %} + +{% block script %} + +{% endblock %} diff --git a/src/flask-main/examples/javascript/js_example/templates/jquery.html b/src/flask-main/examples/javascript/js_example/templates/jquery.html new file mode 100644 index 0000000..48f0c11 --- /dev/null +++ b/src/flask-main/examples/javascript/js_example/templates/jquery.html @@ -0,0 +1,27 @@ +{% extends 'base.html' %} + +{% block intro %} + jQuery is a popular library that + adds cross browser APIs for common tasks. However, it requires loading + an extra library. +{% endblock %} + +{% block script %} + + +{% endblock %} diff --git a/src/flask-main/examples/javascript/js_example/templates/xhr.html b/src/flask-main/examples/javascript/js_example/templates/xhr.html new file mode 100644 index 0000000..1672d4d --- /dev/null +++ b/src/flask-main/examples/javascript/js_example/templates/xhr.html @@ -0,0 +1,29 @@ +{% extends 'base.html' %} + +{% block intro %} + XMLHttpRequest + is the original JavaScript way to make requests. It's natively supported + by all browsers, but has been superseded by + fetch. +{% endblock %} + +{% block script %} + +{% endblock %} diff --git a/src/flask-main/examples/javascript/js_example/views.py b/src/flask-main/examples/javascript/js_example/views.py new file mode 100644 index 0000000..9f0d26c --- /dev/null +++ b/src/flask-main/examples/javascript/js_example/views.py @@ -0,0 +1,18 @@ +from flask import jsonify +from flask import render_template +from flask import request + +from . import app + + +@app.route("/", defaults={"js": "fetch"}) +@app.route("/") +def index(js): + return render_template(f"{js}.html", js=js) + + +@app.route("/add", methods=["POST"]) +def add(): + a = request.form.get("a", 0, type=float) + b = request.form.get("b", 0, type=float) + return jsonify(result=a + b) diff --git a/src/flask-main/examples/javascript/pyproject.toml b/src/flask-main/examples/javascript/pyproject.toml new file mode 100644 index 0000000..ea0efab --- /dev/null +++ b/src/flask-main/examples/javascript/pyproject.toml @@ -0,0 +1,33 @@ +[project] +name = "js_example" +version = "1.1.0" +description = "Demonstrates making AJAX requests to Flask." +readme = "README.rst" +license = {file = "LICENSE.txt"} +maintainers = [{name = "Pallets", email = "contact@palletsprojects.com"}] +classifiers = ["Private :: Do Not Upload"] +dependencies = ["flask"] + +[project.urls] +Documentation = "https://flask.palletsprojects.com/patterns/javascript/" + +[project.optional-dependencies] +test = ["pytest"] + +[build-system] +requires = ["flit_core<4"] +build-backend = "flit_core.buildapi" + +[tool.flit.module] +name = "js_example" + +[tool.pytest.ini_options] +testpaths = ["tests"] +filterwarnings = ["error"] + +[tool.coverage.run] +branch = true +source = ["js_example", "tests"] + +[tool.ruff] +src = ["src"] diff --git a/src/flask-main/examples/javascript/tests/conftest.py b/src/flask-main/examples/javascript/tests/conftest.py new file mode 100644 index 0000000..e0cabbf --- /dev/null +++ b/src/flask-main/examples/javascript/tests/conftest.py @@ -0,0 +1,15 @@ +import pytest + +from js_example import app + + +@pytest.fixture(name="app") +def fixture_app(): + app.testing = True + yield app + app.testing = False + + +@pytest.fixture +def client(app): + return app.test_client() diff --git a/src/flask-main/examples/javascript/tests/test_js_example.py b/src/flask-main/examples/javascript/tests/test_js_example.py new file mode 100644 index 0000000..856f5f7 --- /dev/null +++ b/src/flask-main/examples/javascript/tests/test_js_example.py @@ -0,0 +1,27 @@ +import pytest +from flask import template_rendered + + +@pytest.mark.parametrize( + ("path", "template_name"), + ( + ("/", "fetch.html"), + ("/plain", "xhr.html"), + ("/fetch", "fetch.html"), + ("/jquery", "jquery.html"), + ), +) +def test_index(app, client, path, template_name): + def check(sender, template, context): + assert template.name == template_name + + with template_rendered.connected_to(check, app): + client.get(path) + + +@pytest.mark.parametrize( + ("a", "b", "result"), ((2, 3, 5), (2.5, 3, 5.5), (2, None, 2), (2, "b", 2)) +) +def test_add(client, a, b, result): + response = client.post("/add", data={"a": a, "b": b}) + assert response.get_json()["result"] == result diff --git a/src/flask-main/examples/tutorial/.gitignore b/src/flask-main/examples/tutorial/.gitignore new file mode 100644 index 0000000..a306afb --- /dev/null +++ b/src/flask-main/examples/tutorial/.gitignore @@ -0,0 +1,14 @@ +.venv/ +*.pyc +__pycache__/ +instance/ +.cache/ +.pytest_cache/ +.coverage +htmlcov/ +dist/ +build/ +*.egg-info/ +.idea/ +*.swp +*~ diff --git a/src/flask-main/examples/tutorial/LICENSE.txt b/src/flask-main/examples/tutorial/LICENSE.txt new file mode 100644 index 0000000..9d227a0 --- /dev/null +++ b/src/flask-main/examples/tutorial/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/src/flask-main/examples/tutorial/README.rst b/src/flask-main/examples/tutorial/README.rst new file mode 100644 index 0000000..653c216 --- /dev/null +++ b/src/flask-main/examples/tutorial/README.rst @@ -0,0 +1,68 @@ +Flaskr +====== + +The basic blog app built in the Flask `tutorial`_. + +.. _tutorial: https://flask.palletsprojects.com/tutorial/ + + +Install +------- + +**Be sure to use the same version of the code as the version of the docs +you're reading.** You probably want the latest tagged version, but the +default Git version is the main branch. :: + + # clone the repository + $ git clone https://github.com/pallets/flask + $ cd flask + # checkout the correct version + $ git tag # shows the tagged versions + $ git checkout latest-tag-found-above + $ cd examples/tutorial + +Create a virtualenv and activate it:: + + $ python3 -m venv .venv + $ . .venv/bin/activate + +Or on Windows cmd:: + + $ py -3 -m venv .venv + $ .venv\Scripts\activate.bat + +Install Flaskr:: + + $ pip install -e . + +Or if you are using the main branch, install Flask from source before +installing Flaskr:: + + $ pip install -e ../.. + $ pip install -e . + + +Run +--- + +.. code-block:: text + + $ flask --app flaskr init-db + $ flask --app flaskr run --debug + +Open http://127.0.0.1:5000 in a browser. + + +Test +---- + +:: + + $ pip install '.[test]' + $ pytest + +Run with coverage report:: + + $ coverage run -m pytest + $ coverage report + $ coverage html # open htmlcov/index.html in a browser diff --git a/src/flask-main/examples/tutorial/flaskr/__init__.py b/src/flask-main/examples/tutorial/flaskr/__init__.py new file mode 100644 index 0000000..e35934d --- /dev/null +++ b/src/flask-main/examples/tutorial/flaskr/__init__.py @@ -0,0 +1,51 @@ +import os + +from flask import Flask + + +def create_app(test_config=None): + """Create and configure an instance of the Flask application.""" + app = Flask(__name__, instance_relative_config=True) + app.config.from_mapping( + # a default secret that should be overridden by instance config + SECRET_KEY="dev", + # store the database in the instance folder + DATABASE=os.path.join(app.instance_path, "flaskr.sqlite"), + ) + + if test_config is None: + # load the instance config, if it exists, when not testing + app.config.from_pyfile("config.py", silent=True) + else: + # load the test config if passed in + app.config.update(test_config) + + # ensure the instance folder exists + try: + os.makedirs(app.instance_path) + except OSError: + pass + + @app.route("/hello") + def hello(): + return "Hello, World!" + + # register the database commands + from . import db + + db.init_app(app) + + # apply the blueprints to the app + from . import auth + from . import blog + + app.register_blueprint(auth.bp) + app.register_blueprint(blog.bp) + + # make url_for('index') == url_for('blog.index') + # in another app, you might define a separate main index here with + # app.route, while giving the blog blueprint a url_prefix, but for + # the tutorial the blog will be the main index + app.add_url_rule("/", endpoint="index") + + return app diff --git a/src/flask-main/examples/tutorial/flaskr/auth.py b/src/flask-main/examples/tutorial/flaskr/auth.py new file mode 100644 index 0000000..34c03a2 --- /dev/null +++ b/src/flask-main/examples/tutorial/flaskr/auth.py @@ -0,0 +1,116 @@ +import functools + +from flask import Blueprint +from flask import flash +from flask import g +from flask import redirect +from flask import render_template +from flask import request +from flask import session +from flask import url_for +from werkzeug.security import check_password_hash +from werkzeug.security import generate_password_hash + +from .db import get_db + +bp = Blueprint("auth", __name__, url_prefix="/auth") + + +def login_required(view): + """View decorator that redirects anonymous users to the login page.""" + + @functools.wraps(view) + def wrapped_view(**kwargs): + if g.user is None: + return redirect(url_for("auth.login")) + + return view(**kwargs) + + return wrapped_view + + +@bp.before_app_request +def load_logged_in_user(): + """If a user id is stored in the session, load the user object from + the database into ``g.user``.""" + user_id = session.get("user_id") + + if user_id is None: + g.user = None + else: + g.user = ( + get_db().execute("SELECT * FROM user WHERE id = ?", (user_id,)).fetchone() + ) + + +@bp.route("/register", methods=("GET", "POST")) +def register(): + """Register a new user. + + Validates that the username is not already taken. Hashes the + password for security. + """ + if request.method == "POST": + username = request.form["username"] + password = request.form["password"] + db = get_db() + error = None + + if not username: + error = "Username is required." + elif not password: + error = "Password is required." + + if error is None: + try: + db.execute( + "INSERT INTO user (username, password) VALUES (?, ?)", + (username, generate_password_hash(password)), + ) + db.commit() + except db.IntegrityError: + # The username was already taken, which caused the + # commit to fail. Show a validation error. + error = f"User {username} is already registered." + else: + # Success, go to the login page. + return redirect(url_for("auth.login")) + + flash(error) + + return render_template("auth/register.html") + + +@bp.route("/login", methods=("GET", "POST")) +def login(): + """Log in a registered user by adding the user id to the session.""" + if request.method == "POST": + username = request.form["username"] + password = request.form["password"] + db = get_db() + error = None + user = db.execute( + "SELECT * FROM user WHERE username = ?", (username,) + ).fetchone() + + if user is None: + error = "Incorrect username." + elif not check_password_hash(user["password"], password): + error = "Incorrect password." + + if error is None: + # store the user id in a new session and return to the index + session.clear() + session["user_id"] = user["id"] + return redirect(url_for("index")) + + flash(error) + + return render_template("auth/login.html") + + +@bp.route("/logout") +def logout(): + """Clear the current session, including the stored user id.""" + session.clear() + return redirect(url_for("index")) diff --git a/src/flask-main/examples/tutorial/flaskr/blog.py b/src/flask-main/examples/tutorial/flaskr/blog.py new file mode 100644 index 0000000..be0d92c --- /dev/null +++ b/src/flask-main/examples/tutorial/flaskr/blog.py @@ -0,0 +1,125 @@ +from flask import Blueprint +from flask import flash +from flask import g +from flask import redirect +from flask import render_template +from flask import request +from flask import url_for +from werkzeug.exceptions import abort + +from .auth import login_required +from .db import get_db + +bp = Blueprint("blog", __name__) + + +@bp.route("/") +def index(): + """Show all the posts, most recent first.""" + db = get_db() + posts = db.execute( + "SELECT p.id, title, body, created, author_id, username" + " FROM post p JOIN user u ON p.author_id = u.id" + " ORDER BY created DESC" + ).fetchall() + return render_template("blog/index.html", posts=posts) + + +def get_post(id, check_author=True): + """Get a post and its author by id. + + Checks that the id exists and optionally that the current user is + the author. + + :param id: id of post to get + :param check_author: require the current user to be the author + :return: the post with author information + :raise 404: if a post with the given id doesn't exist + :raise 403: if the current user isn't the author + """ + post = ( + get_db() + .execute( + "SELECT p.id, title, body, created, author_id, username" + " FROM post p JOIN user u ON p.author_id = u.id" + " WHERE p.id = ?", + (id,), + ) + .fetchone() + ) + + if post is None: + abort(404, f"Post id {id} doesn't exist.") + + if check_author and post["author_id"] != g.user["id"]: + abort(403) + + return post + + +@bp.route("/create", methods=("GET", "POST")) +@login_required +def create(): + """Create a new post for the current user.""" + if request.method == "POST": + title = request.form["title"] + body = request.form["body"] + error = None + + if not title: + error = "Title is required." + + if error is not None: + flash(error) + else: + db = get_db() + db.execute( + "INSERT INTO post (title, body, author_id) VALUES (?, ?, ?)", + (title, body, g.user["id"]), + ) + db.commit() + return redirect(url_for("blog.index")) + + return render_template("blog/create.html") + + +@bp.route("//update", methods=("GET", "POST")) +@login_required +def update(id): + """Update a post if the current user is the author.""" + post = get_post(id) + + if request.method == "POST": + title = request.form["title"] + body = request.form["body"] + error = None + + if not title: + error = "Title is required." + + if error is not None: + flash(error) + else: + db = get_db() + db.execute( + "UPDATE post SET title = ?, body = ? WHERE id = ?", (title, body, id) + ) + db.commit() + return redirect(url_for("blog.index")) + + return render_template("blog/update.html", post=post) + + +@bp.route("//delete", methods=("POST",)) +@login_required +def delete(id): + """Delete a post. + + Ensures that the post exists and that the logged in user is the + author of the post. + """ + get_post(id) + db = get_db() + db.execute("DELETE FROM post WHERE id = ?", (id,)) + db.commit() + return redirect(url_for("blog.index")) diff --git a/src/flask-main/examples/tutorial/flaskr/db.py b/src/flask-main/examples/tutorial/flaskr/db.py new file mode 100644 index 0000000..dec22fd --- /dev/null +++ b/src/flask-main/examples/tutorial/flaskr/db.py @@ -0,0 +1,56 @@ +import sqlite3 +from datetime import datetime + +import click +from flask import current_app +from flask import g + + +def get_db(): + """Connect to the application's configured database. The connection + is unique for each request and will be reused if this is called + again. + """ + if "db" not in g: + g.db = sqlite3.connect( + current_app.config["DATABASE"], detect_types=sqlite3.PARSE_DECLTYPES + ) + g.db.row_factory = sqlite3.Row + + return g.db + + +def close_db(e=None): + """If this request connected to the database, close the + connection. + """ + db = g.pop("db", None) + + if db is not None: + db.close() + + +def init_db(): + """Clear existing data and create new tables.""" + db = get_db() + + with current_app.open_resource("schema.sql") as f: + db.executescript(f.read().decode("utf8")) + + +@click.command("init-db") +def init_db_command(): + """Clear existing data and create new tables.""" + init_db() + click.echo("Initialized the database.") + + +sqlite3.register_converter("timestamp", lambda v: datetime.fromisoformat(v.decode())) + + +def init_app(app): + """Register database functions with the Flask app. This is called by + the application factory. + """ + app.teardown_appcontext(close_db) + app.cli.add_command(init_db_command) diff --git a/src/flask-main/examples/tutorial/flaskr/schema.sql b/src/flask-main/examples/tutorial/flaskr/schema.sql new file mode 100644 index 0000000..dd4c866 --- /dev/null +++ b/src/flask-main/examples/tutorial/flaskr/schema.sql @@ -0,0 +1,20 @@ +-- Initialize the database. +-- Drop any existing data and create empty tables. + +DROP TABLE IF EXISTS user; +DROP TABLE IF EXISTS post; + +CREATE TABLE user ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT UNIQUE NOT NULL, + password TEXT NOT NULL +); + +CREATE TABLE post ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + author_id INTEGER NOT NULL, + created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + title TEXT NOT NULL, + body TEXT NOT NULL, + FOREIGN KEY (author_id) REFERENCES user (id) +); diff --git a/src/flask-main/examples/tutorial/flaskr/static/style.css b/src/flask-main/examples/tutorial/flaskr/static/style.css new file mode 100644 index 0000000..2f1f4d0 --- /dev/null +++ b/src/flask-main/examples/tutorial/flaskr/static/style.css @@ -0,0 +1,134 @@ +html { + font-family: sans-serif; + background: #eee; + padding: 1rem; +} + +body { + max-width: 960px; + margin: 0 auto; + background: white; +} + +h1, h2, h3, h4, h5, h6 { + font-family: serif; + color: #377ba8; + margin: 1rem 0; +} + +a { + color: #377ba8; +} + +hr { + border: none; + border-top: 1px solid lightgray; +} + +nav { + background: lightgray; + display: flex; + align-items: center; + padding: 0 0.5rem; +} + +nav h1 { + flex: auto; + margin: 0; +} + +nav h1 a { + text-decoration: none; + padding: 0.25rem 0.5rem; +} + +nav ul { + display: flex; + list-style: none; + margin: 0; + padding: 0; +} + +nav ul li a, nav ul li span, header .action { + display: block; + padding: 0.5rem; +} + +.content { + padding: 0 1rem 1rem; +} + +.content > header { + border-bottom: 1px solid lightgray; + display: flex; + align-items: flex-end; +} + +.content > header h1 { + flex: auto; + margin: 1rem 0 0.25rem 0; +} + +.flash { + margin: 1em 0; + padding: 1em; + background: #cae6f6; + border: 1px solid #377ba8; +} + +.post > header { + display: flex; + align-items: flex-end; + font-size: 0.85em; +} + +.post > header > div:first-of-type { + flex: auto; +} + +.post > header h1 { + font-size: 1.5em; + margin-bottom: 0; +} + +.post .about { + color: slategray; + font-style: italic; +} + +.post .body { + white-space: pre-line; +} + +.content:last-child { + margin-bottom: 0; +} + +.content form { + margin: 1em 0; + display: flex; + flex-direction: column; +} + +.content label { + font-weight: bold; + margin-bottom: 0.5em; +} + +.content input, .content textarea { + margin-bottom: 1em; +} + +.content textarea { + min-height: 12em; + resize: vertical; +} + +input.danger { + color: #cc2f2e; +} + +input[type=submit] { + align-self: start; + min-width: 10em; +} diff --git a/src/flask-main/examples/tutorial/flaskr/templates/auth/login.html b/src/flask-main/examples/tutorial/flaskr/templates/auth/login.html new file mode 100644 index 0000000..b326b5a --- /dev/null +++ b/src/flask-main/examples/tutorial/flaskr/templates/auth/login.html @@ -0,0 +1,15 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %}Log In{% endblock %}

+{% endblock %} + +{% block content %} +
+ + + + + +
+{% endblock %} diff --git a/src/flask-main/examples/tutorial/flaskr/templates/auth/register.html b/src/flask-main/examples/tutorial/flaskr/templates/auth/register.html new file mode 100644 index 0000000..4320e17 --- /dev/null +++ b/src/flask-main/examples/tutorial/flaskr/templates/auth/register.html @@ -0,0 +1,15 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %}Register{% endblock %}

+{% endblock %} + +{% block content %} +
+ + + + + +
+{% endblock %} diff --git a/src/flask-main/examples/tutorial/flaskr/templates/base.html b/src/flask-main/examples/tutorial/flaskr/templates/base.html new file mode 100644 index 0000000..f09e926 --- /dev/null +++ b/src/flask-main/examples/tutorial/flaskr/templates/base.html @@ -0,0 +1,24 @@ + +{% block title %}{% endblock %} - Flaskr + + +
+
+ {% block header %}{% endblock %} +
+ {% for message in get_flashed_messages() %} +
{{ message }}
+ {% endfor %} + {% block content %}{% endblock %} +
diff --git a/src/flask-main/examples/tutorial/flaskr/templates/blog/create.html b/src/flask-main/examples/tutorial/flaskr/templates/blog/create.html new file mode 100644 index 0000000..88e31e4 --- /dev/null +++ b/src/flask-main/examples/tutorial/flaskr/templates/blog/create.html @@ -0,0 +1,15 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %}New Post{% endblock %}

+{% endblock %} + +{% block content %} +
+ + + + + +
+{% endblock %} diff --git a/src/flask-main/examples/tutorial/flaskr/templates/blog/index.html b/src/flask-main/examples/tutorial/flaskr/templates/blog/index.html new file mode 100644 index 0000000..3481b8e --- /dev/null +++ b/src/flask-main/examples/tutorial/flaskr/templates/blog/index.html @@ -0,0 +1,28 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %}Posts{% endblock %}

+ {% if g.user %} + New + {% endif %} +{% endblock %} + +{% block content %} + {% for post in posts %} +
+
+
+

{{ post['title'] }}

+
by {{ post['username'] }} on {{ post['created'].strftime('%Y-%m-%d') }}
+
+ {% if g.user['id'] == post['author_id'] %} + Edit + {% endif %} +
+

{{ post['body'] }}

+
+ {% if not loop.last %} +
+ {% endif %} + {% endfor %} +{% endblock %} diff --git a/src/flask-main/examples/tutorial/flaskr/templates/blog/update.html b/src/flask-main/examples/tutorial/flaskr/templates/blog/update.html new file mode 100644 index 0000000..2c405e6 --- /dev/null +++ b/src/flask-main/examples/tutorial/flaskr/templates/blog/update.html @@ -0,0 +1,19 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %}Edit "{{ post['title'] }}"{% endblock %}

+{% endblock %} + +{% block content %} +
+ + + + + +
+
+
+ +
+{% endblock %} diff --git a/src/flask-main/examples/tutorial/pyproject.toml b/src/flask-main/examples/tutorial/pyproject.toml new file mode 100644 index 0000000..ca31e53 --- /dev/null +++ b/src/flask-main/examples/tutorial/pyproject.toml @@ -0,0 +1,40 @@ +[project] +name = "flaskr" +version = "1.0.0" +description = "The basic blog app built in the Flask tutorial." +readme = "README.rst" +license = {file = "LICENSE.txt"} +maintainers = [{name = "Pallets", email = "contact@palletsprojects.com"}] +classifiers = ["Private :: Do Not Upload"] +dependencies = [ + "flask", +] + +[project.urls] +Documentation = "https://flask.palletsprojects.com/tutorial/" + +[project.optional-dependencies] +test = ["pytest"] + +[build-system] +requires = ["flit_core<4"] +build-backend = "flit_core.buildapi" + +[tool.flit.module] +name = "flaskr" + +[tool.flit.sdist] +include = [ + "tests/", +] + +[tool.pytest.ini_options] +testpaths = ["tests"] +filterwarnings = ["error"] + +[tool.coverage.run] +branch = true +source = ["flaskr", "tests"] + +[tool.ruff] +src = ["src"] diff --git a/src/flask-main/examples/tutorial/tests/conftest.py b/src/flask-main/examples/tutorial/tests/conftest.py new file mode 100644 index 0000000..6bf62f0 --- /dev/null +++ b/src/flask-main/examples/tutorial/tests/conftest.py @@ -0,0 +1,62 @@ +import os +import tempfile + +import pytest + +from flaskr import create_app +from flaskr.db import get_db +from flaskr.db import init_db + +# read in SQL for populating test data +with open(os.path.join(os.path.dirname(__file__), "data.sql"), "rb") as f: + _data_sql = f.read().decode("utf8") + + +@pytest.fixture +def app(): + """Create and configure a new app instance for each test.""" + # create a temporary file to isolate the database for each test + db_fd, db_path = tempfile.mkstemp() + # create the app with common test config + app = create_app({"TESTING": True, "DATABASE": db_path}) + + # create the database and load test data + with app.app_context(): + init_db() + get_db().executescript(_data_sql) + + yield app + + # close and remove the temporary database + os.close(db_fd) + os.unlink(db_path) + + +@pytest.fixture +def client(app): + """A test client for the app.""" + return app.test_client() + + +@pytest.fixture +def runner(app): + """A test runner for the app's Click commands.""" + return app.test_cli_runner() + + +class AuthActions: + def __init__(self, client): + self._client = client + + def login(self, username="test", password="test"): + return self._client.post( + "/auth/login", data={"username": username, "password": password} + ) + + def logout(self): + return self._client.get("/auth/logout") + + +@pytest.fixture +def auth(client): + return AuthActions(client) diff --git a/src/flask-main/examples/tutorial/tests/data.sql b/src/flask-main/examples/tutorial/tests/data.sql new file mode 100644 index 0000000..9b68006 --- /dev/null +++ b/src/flask-main/examples/tutorial/tests/data.sql @@ -0,0 +1,8 @@ +INSERT INTO user (username, password) +VALUES + ('test', 'pbkdf2:sha256:50000$TCI4GzcX$0de171a4f4dac32e3364c7ddc7c14f3e2fa61f2d17574483f7ffbb431b4acb2f'), + ('other', 'pbkdf2:sha256:50000$kJPKsz6N$d2d4784f1b030a9761f5ccaeeaca413f27f2ecb76d6168407af962ddce849f79'); + +INSERT INTO post (title, body, author_id, created) +VALUES + ('test title', 'test' || x'0a' || 'body', 1, '2018-01-01 00:00:00'); diff --git a/src/flask-main/examples/tutorial/tests/test_auth.py b/src/flask-main/examples/tutorial/tests/test_auth.py new file mode 100644 index 0000000..76db62f --- /dev/null +++ b/src/flask-main/examples/tutorial/tests/test_auth.py @@ -0,0 +1,69 @@ +import pytest +from flask import g +from flask import session + +from flaskr.db import get_db + + +def test_register(client, app): + # test that viewing the page renders without template errors + assert client.get("/auth/register").status_code == 200 + + # test that successful registration redirects to the login page + response = client.post("/auth/register", data={"username": "a", "password": "a"}) + assert response.headers["Location"] == "/auth/login" + + # test that the user was inserted into the database + with app.app_context(): + assert ( + get_db().execute("SELECT * FROM user WHERE username = 'a'").fetchone() + is not None + ) + + +@pytest.mark.parametrize( + ("username", "password", "message"), + ( + ("", "", b"Username is required."), + ("a", "", b"Password is required."), + ("test", "test", b"already registered"), + ), +) +def test_register_validate_input(client, username, password, message): + response = client.post( + "/auth/register", data={"username": username, "password": password} + ) + assert message in response.data + + +def test_login(client, auth): + # test that viewing the page renders without template errors + assert client.get("/auth/login").status_code == 200 + + # test that successful login redirects to the index page + response = auth.login() + assert response.headers["Location"] == "/" + + # login request set the user_id in the session + # check that the user is loaded from the session + with client: + client.get("/") + assert session["user_id"] == 1 + assert g.user["username"] == "test" + + +@pytest.mark.parametrize( + ("username", "password", "message"), + (("a", "test", b"Incorrect username."), ("test", "a", b"Incorrect password.")), +) +def test_login_validate_input(auth, username, password, message): + response = auth.login(username, password) + assert message in response.data + + +def test_logout(client, auth): + auth.login() + + with client: + auth.logout() + assert "user_id" not in session diff --git a/src/flask-main/examples/tutorial/tests/test_blog.py b/src/flask-main/examples/tutorial/tests/test_blog.py new file mode 100644 index 0000000..55c769d --- /dev/null +++ b/src/flask-main/examples/tutorial/tests/test_blog.py @@ -0,0 +1,83 @@ +import pytest + +from flaskr.db import get_db + + +def test_index(client, auth): + response = client.get("/") + assert b"Log In" in response.data + assert b"Register" in response.data + + auth.login() + response = client.get("/") + assert b"test title" in response.data + assert b"by test on 2018-01-01" in response.data + assert b"test\nbody" in response.data + assert b'href="/1/update"' in response.data + + +@pytest.mark.parametrize("path", ("/create", "/1/update", "/1/delete")) +def test_login_required(client, path): + response = client.post(path) + assert response.headers["Location"] == "/auth/login" + + +def test_author_required(app, client, auth): + # change the post author to another user + with app.app_context(): + db = get_db() + db.execute("UPDATE post SET author_id = 2 WHERE id = 1") + db.commit() + + auth.login() + # current user can't modify other user's post + assert client.post("/1/update").status_code == 403 + assert client.post("/1/delete").status_code == 403 + # current user doesn't see edit link + assert b'href="/1/update"' not in client.get("/").data + + +@pytest.mark.parametrize("path", ("/2/update", "/2/delete")) +def test_exists_required(client, auth, path): + auth.login() + assert client.post(path).status_code == 404 + + +def test_create(client, auth, app): + auth.login() + assert client.get("/create").status_code == 200 + client.post("/create", data={"title": "created", "body": ""}) + + with app.app_context(): + db = get_db() + count = db.execute("SELECT COUNT(id) FROM post").fetchone()[0] + assert count == 2 + + +def test_update(client, auth, app): + auth.login() + assert client.get("/1/update").status_code == 200 + client.post("/1/update", data={"title": "updated", "body": ""}) + + with app.app_context(): + db = get_db() + post = db.execute("SELECT * FROM post WHERE id = 1").fetchone() + assert post["title"] == "updated" + + +@pytest.mark.parametrize("path", ("/create", "/1/update")) +def test_create_update_validate(client, auth, path): + auth.login() + response = client.post(path, data={"title": "", "body": ""}) + assert b"Title is required." in response.data + + +def test_delete(client, auth, app): + auth.login() + response = client.post("/1/delete") + assert response.headers["Location"] == "/" + + with app.app_context(): + db = get_db() + post = db.execute("SELECT * FROM post WHERE id = 1").fetchone() + assert post is None diff --git a/src/flask-main/examples/tutorial/tests/test_db.py b/src/flask-main/examples/tutorial/tests/test_db.py new file mode 100644 index 0000000..2363bf8 --- /dev/null +++ b/src/flask-main/examples/tutorial/tests/test_db.py @@ -0,0 +1,29 @@ +import sqlite3 + +import pytest + +from flaskr.db import get_db + + +def test_get_close_db(app): + with app.app_context(): + db = get_db() + assert db is get_db() + + with pytest.raises(sqlite3.ProgrammingError) as e: + db.execute("SELECT 1") + + assert "closed" in str(e.value) + + +def test_init_db_command(runner, monkeypatch): + class Recorder: + called = False + + def fake_init_db(): + Recorder.called = True + + monkeypatch.setattr("flaskr.db.init_db", fake_init_db) + result = runner.invoke(args=["init-db"]) + assert "Initialized" in result.output + assert Recorder.called diff --git a/src/flask-main/examples/tutorial/tests/test_factory.py b/src/flask-main/examples/tutorial/tests/test_factory.py new file mode 100644 index 0000000..9b7ca57 --- /dev/null +++ b/src/flask-main/examples/tutorial/tests/test_factory.py @@ -0,0 +1,12 @@ +from flaskr import create_app + + +def test_config(): + """Test create_app without passing test config.""" + assert not create_app().testing + assert create_app({"TESTING": True}).testing + + +def test_hello(client): + response = client.get("/hello") + assert response.data == b"Hello, World!" diff --git a/src/flask-main/pyproject.toml b/src/flask-main/pyproject.toml new file mode 100644 index 0000000..1a4e6be --- /dev/null +++ b/src/flask-main/pyproject.toml @@ -0,0 +1,278 @@ +[project] +name = "Flask" +version = "3.2.0.dev" +description = "A simple framework for building complex web applications." +readme = "README.md" +license = "BSD-3-Clause" +license-files = ["LICENSE.txt"] +maintainers = [{name = "Pallets", email = "contact@palletsprojects.com"}] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Web Environment", + "Framework :: Flask", + "Intended Audience :: Developers", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Topic :: Internet :: WWW/HTTP :: Dynamic Content", + "Topic :: Internet :: WWW/HTTP :: WSGI", + "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", + "Topic :: Software Development :: Libraries :: Application Frameworks", + "Typing :: Typed", +] +requires-python = ">=3.10" +dependencies = [ + "blinker>=1.9.0", + "click>=8.1.3", + "itsdangerous>=2.2.0", + "jinja2>=3.1.2", + "markupsafe>=2.1.1", + "werkzeug>=3.1.0", +] + +[project.optional-dependencies] +async = ["asgiref>=3.2"] +dotenv = ["python-dotenv"] + +[dependency-groups] +dev = [ + "ruff", + "tox", + "tox-uv", +] +docs = [ + "pallets-sphinx-themes", + "sphinx", + "sphinx-tabs", + "sphinxcontrib-log-cabinet", +] +docs-auto = [ + "sphinx-autobuild", +] +gha-update = [ + "gha-update ; python_full_version >= '3.12'", +] +pre-commit = [ + "pre-commit", + "pre-commit-uv", +] +tests = [ + "asgiref", + "greenlet", + "pytest", + "python-dotenv", +] +typing = [ + "asgiref", + "cryptography", + "mypy", + "pyright", + "pytest", + "python-dotenv", + "types-contextvars", + "types-dataclasses", +] + +[project.urls] +Donate = "https://palletsprojects.com/donate" +Documentation = "https://flask.palletsprojects.com/" +Changes = "https://flask.palletsprojects.com/page/changes/" +Source = "https://github.com/pallets/flask/" +Chat = "https://discord.gg/pallets" + +[project.scripts] +flask = "flask.cli:main" + +[build-system] +requires = ["flit_core<4"] +build-backend = "flit_core.buildapi" + +[tool.flit.module] +name = "flask" + +[tool.flit.sdist] +include = [ + "docs/", + "examples/", + "tests/", + "CHANGES.rst", + "uv.lock" +] +exclude = [ + "docs/_build/", +] + +[tool.uv] +default-groups = ["dev", "pre-commit", "tests", "typing"] + +[tool.pytest.ini_options] +testpaths = ["tests"] +filterwarnings = [ + "error", +] + +[tool.coverage.run] +branch = true +source = ["flask", "tests"] + +[tool.coverage.paths] +source = ["src", "*/site-packages"] + +[tool.coverage.report] +exclude_also = [ + "if t.TYPE_CHECKING", + "raise NotImplementedError", + ": \\.{3}", +] + +[tool.mypy] +python_version = "3.10" +files = ["src", "tests/type_check"] +show_error_codes = true +pretty = true +strict = true + +[[tool.mypy.overrides]] +module = [ + "asgiref.*", + "dotenv.*", + "cryptography.*", + "importlib_metadata", +] +ignore_missing_imports = true + +[tool.pyright] +pythonVersion = "3.10" +include = ["src", "tests/type_check"] +typeCheckingMode = "basic" + +[tool.ruff] +src = ["src"] +fix = true +show-fixes = true +output-format = "full" + +[tool.ruff.lint] +select = [ + "B", # flake8-bugbear + "E", # pycodestyle error + "F", # pyflakes + "I", # isort + "UP", # pyupgrade + "W", # pycodestyle warning +] +ignore = [ + "UP038", # keep isinstance tuple +] + +[tool.ruff.lint.isort] +force-single-line = true +order-by-type = false + +[tool.tox] +env_list = [ + "py3.14", "py3.14t", "py3.13", "py3.13t", + "py3.12", "py3.11", "py3.10", + "pypy3.11", + "tests-min", "tests-dev", + "style", + "typing", + "docs", +] + +[tool.tox.env_run_base] +description = "pytest on latest dependency versions" +runner = "uv-venv-lock-runner" +package = "wheel" +wheel_build_env = ".pkg" +constrain_package_deps = true +use_frozen_constraints = true +dependency_groups = ["tests"] +env_tmp_dir = "{toxworkdir}/tmp/{envname}" +commands = [[ + "pytest", "-v", "--tb=short", "--basetemp={env_tmp_dir}", + {replace = "posargs", default = [], extend = true}, +]] + +[tool.tox.env.tests-min] +description = "pytest on minimum dependency versions" +base_python = ["3.14"] +commands = [ + [ + "uv", "pip", "install", + "blinker==1.9.0", + "click==8.1.3", + "itsdangerous==2.2.0", + "jinja2==3.1.2", + "markupsafe==2.1.1", + "werkzeug==3.1.0", + ], + [ + "pytest", "-v", "--tb=short", "--basetemp={env_tmp_dir}", + {replace = "posargs", default = [], extend = true}, + ], +] + +[tool.tox.env.tests-dev] +description = "pytest on development dependency versions (git main branch)" +base_python = ["3.10"] +commands = [ + [ + "uv", "pip", "install", + "https://github.com/pallets-eco/blinker/archive/refs/heads/main.tar.gz", + "https://github.com/pallets/click/archive/refs/heads/main.tar.gz", + "https://github.com/pallets/itsdangerous/archive/refs/heads/main.tar.gz", + "https://github.com/pallets/jinja/archive/refs/heads/main.tar.gz", + "https://github.com/pallets/markupsafe/archive/refs/heads/main.tar.gz", + "https://github.com/pallets/werkzeug/archive/refs/heads/main.tar.gz", + ], + [ + "pytest", "-v", "--tb=short", "--basetemp={env_tmp_dir}", + {replace = "posargs", default = [], extend = true}, + ], +] + +[tool.tox.env.style] +description = "run all pre-commit hooks on all files" +dependency_groups = ["pre-commit"] +skip_install = true +commands = [["pre-commit", "run", "--all-files"]] + +[tool.tox.env.typing] +description = "run static type checkers" +dependency_groups = ["typing"] +commands = [ + ["mypy"], + ["pyright"], +] + +[tool.tox.env.docs] +description = "build docs" +dependency_groups = ["docs"] +commands = [["sphinx-build", "-E", "-W", "-b", "dirhtml", "docs", "docs/_build/dirhtml"]] + +[tool.tox.env.docs-auto] +description = "continuously rebuild docs and start a local server" +dependency_groups = ["docs", "docs-auto"] +commands = [["sphinx-autobuild", "-W", "-b", "dirhtml", "--watch", "src", "docs", "docs/_build/dirhtml"]] + +[tool.tox.env.update-actions] +description = "update GitHub Actions pins" +labels = ["update"] +dependency_groups = ["gha-update"] +skip_install = true +commands = [["gha-update"]] + +[tool.tox.env.update-pre_commit] +description = "update pre-commit pins" +labels = ["update"] +dependency_groups = ["pre-commit"] +skip_install = true +commands = [["pre-commit", "autoupdate", "--freeze", "-j4"]] + +[tool.tox.env.update-requirements] +description = "update uv lock" +labels = ["update"] +dependency_groups = [] +no_default_groups = true +skip_install = true +commands = [["uv", "lock", {replace = "posargs", default = ["-U"], extend = true}]] diff --git a/src/flask-main/src/flask/__init__.py b/src/flask-main/src/flask/__init__.py new file mode 100644 index 0000000..30dce6f --- /dev/null +++ b/src/flask-main/src/flask/__init__.py @@ -0,0 +1,39 @@ +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 diff --git a/src/flask-main/src/flask/__main__.py b/src/flask-main/src/flask/__main__.py new file mode 100644 index 0000000..4e28416 --- /dev/null +++ b/src/flask-main/src/flask/__main__.py @@ -0,0 +1,3 @@ +from .cli import main + +main() diff --git a/src/flask-main/src/flask/app.py b/src/flask-main/src/flask/app.py new file mode 100644 index 0000000..e0c193d --- /dev/null +++ b/src/flask-main/src/flask/app.py @@ -0,0 +1,1591 @@ +from __future__ import annotations + +import collections.abc as cabc +import inspect +import os +import sys +import typing as t +import weakref +from datetime import timedelta +from functools import update_wrapper +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 werkzeug.wsgi import get_host + +from . import cli +from . import typing as ft +from .ctx import AppContext +from .globals import _cv_app +from .globals import app_ctx +from .globals import g +from .globals import request +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 .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 + from .typing import HeadersValue + +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) + + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) + + +# Other methods may call the overridden method with the new ctx arg. Remove it +# and call the method with the remaining args. +def remove_ctx(f: F) -> F: + def wrapper(self: Flask, *args: t.Any, **kwargs: t.Any) -> t.Any: + if args and isinstance(args[0], AppContext): + args = args[1:] + + return f(self, *args, **kwargs) + + return update_wrapper(wrapper, f) # type: ignore[return-value] + + +# The overridden method may call super().base_method without the new ctx arg. +# Add it to the args for the call. +def add_ctx(f: F) -> F: + def wrapper(self: Flask, *args: t.Any, **kwargs: t.Any) -> t.Any: + if not args: + args = (app_ctx._get_current_object(),) + elif not isinstance(args[0], AppContext): + args = (app_ctx._get_current_object(), *args) + + return f(self, *args, **kwargs) + + return update_wrapper(wrapper, f) # type: ignore[return-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, + "SECRET_KEY_FALLBACKS": None, + "PERMANENT_SESSION_LIFETIME": timedelta(days=31), + "USE_X_SENDFILE": False, + "TRUSTED_HOSTS": None, + "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_PARTITIONED": False, + "SESSION_COOKIE_SAMESITE": None, + "SESSION_REFRESH_EACH_REQUEST": True, + "MAX_CONTENT_LENGTH": None, + "MAX_FORM_MEMORY_SIZE": 500_000, + "MAX_FORM_PARTS": 1_000, + "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, + "PROVIDE_AUTOMATIC_OPTIONS": True, + } + ) + + #: 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_subclass__(cls, **kwargs: t.Any) -> None: + import warnings + + # These method signatures were updated to take a ctx param. Detect + # overridden methods in subclasses that still have the old signature. + # Show a deprecation warning and wrap to call with correct args. + for method in ( + cls.handle_http_exception, + cls.handle_user_exception, + cls.handle_exception, + cls.log_exception, + cls.dispatch_request, + cls.full_dispatch_request, + cls.finalize_request, + cls.make_default_options_response, + cls.preprocess_request, + cls.process_response, + cls.do_teardown_request, + cls.do_teardown_appcontext, + ): + base_method = getattr(Flask, method.__name__) + + if method is base_method: + # not overridden + continue + + # get the second parameter (first is self) + iter_params = iter(inspect.signature(method).parameters.values()) + next(iter_params) + param = next(iter_params, None) + + # must have second parameter named ctx or annotated AppContext + if param is None or not ( + # no annotation, match name + (param.annotation is inspect.Parameter.empty and param.name == "ctx") + or ( + # string annotation, access path ends with AppContext + isinstance(param.annotation, str) + and param.annotation.rpartition(".")[2] == "AppContext" + ) + or ( + # class annotation + inspect.isclass(param.annotation) + and issubclass(param.annotation, AppContext) + ) + ): + warnings.warn( + f"The '{method.__name__}' method now takes 'ctx: AppContext'" + " as the first parameter. The old signature is deprecated" + " and will not be supported in Flask 4.0.", + DeprecationWarning, + stacklevel=2, + ) + setattr(cls, method.__name__, remove_ctx(method)) + setattr(Flask, method.__name__, add_ctx(base_method)) + + 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 = self.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", encoding: str | None = None + ) -> 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"``. + :param encoding: Open the file with this encoding when opening in text + mode. This is ignored when opening in binary mode. + + .. versionchanged:: 3.1 + Added the ``encoding`` parameter. + """ + if mode not in {"r", "rt", "rb"}: + raise ValueError("Resources can only be opened for reading.") + + path = os.path.join(self.root_path, resource) + + if mode == "rb": + return open(path, mode) # pyright: ignore + + return open(path, mode, encoding=encoding) + + def open_instance_resource( + self, resource: str, mode: str = "rb", encoding: str | None = "utf-8" + ) -> t.IO[t.AnyStr]: + """Open a resource file relative to the application's instance folder + :attr:`instance_path`. Unlike :meth:`open_resource`, files in the + instance folder can be opened for writing. + + :param resource: Path to the resource relative to :attr:`instance_path`. + :param mode: Open the file in this mode. + :param encoding: Open the file with this encoding when opening in text + mode. This is ignored when opening in binary mode. + + .. versionchanged:: 3.1 + Added the ``encoding`` parameter. + """ + path = os.path.join(self.instance_path, resource) + + if "b" in mode: + return open(path, mode) + + return open(path, mode, encoding=encoding) + + 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. + + .. versionchanged:: 3.1 + If :data:`SERVER_NAME` is set, it does not restrict requests to + only that domain, for both ``subdomain_matching`` and + ``host_matching``. + + .. versionchanged:: 1.0 + :data:`SERVER_NAME` no longer implicitly enables subdomain + matching. Use :attr:`subdomain_matching` instead. + + .. versionchanged:: 0.9 + This can be called outside a request when the URL adapter is created + for an application context. + + .. versionadded:: 0.6 + """ + if request is not None: + if (trusted_hosts := self.config["TRUSTED_HOSTS"]) is not None: + request.trusted_hosts = trusted_hosts + + # Check trusted_hosts here until bind_to_environ does. + request.host = get_host(request.environ, request.trusted_hosts) # pyright: ignore + subdomain = None + server_name = self.config["SERVER_NAME"] + + if self.url_map.host_matching: + # Don't pass SERVER_NAME, otherwise it's used and the actual + # host is ignored, which breaks host matching. + server_name = None + elif not self.subdomain_matching: + # Werkzeug doesn't implement subdomain matching yet. Until then, + # disable it by forcing the current subdomain to the default, or + # the empty string. + subdomain = self.url_map.default_subdomain or "" + + return self.url_map.bind_to_environ( + request.environ, server_name=server_name, subdomain=subdomain + ) + + # Need at least SERVER_NAME to match/build outside a request. + 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, ctx: AppContext, 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 ctx.has_request: + names = chain(names, reversed(ctx.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, ctx: AppContext, 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, ctx.request.blueprints) + if handler is None: + return e + return self.ensure_sync(handler)(e) # type: ignore[no-any-return] + + def handle_user_exception( + self, ctx: AppContext, 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(ctx, e) + + handler = self._find_error_handler(e, ctx.request.blueprints) + + if handler is None: + raise + + return self.ensure_sync(handler)(e) # type: ignore[no-any-return] + + def handle_exception(self, ctx: AppContext, 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(ctx, exc_info) + server_error: InternalServerError | ft.ResponseReturnValue + server_error = InternalServerError(original_exception=e) + handler = self._find_error_handler(server_error, ctx.request.blueprints) + + if handler is not None: + server_error = self.ensure_sync(handler)(server_error) + + return self.finalize_request(ctx, server_error, from_error_handler=True) + + def log_exception( + self, + ctx: AppContext, + 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 {ctx.request.path} [{ctx.request.method}]", exc_info=exc_info + ) + + def dispatch_request(self, ctx: AppContext) -> 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 = 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(ctx) + # 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, ctx: AppContext) -> 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(ctx) + if rv is None: + rv = self.dispatch_request(ctx) + except Exception as e: + rv = self.handle_user_exception(ctx, e) + return self.finalize_request(ctx, rv) + + def finalize_request( + self, + ctx: AppContext, + 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(ctx, 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, ctx: AppContext) -> 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 + """ + methods = ctx.url_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. + """ + if (ctx := _cv_app.get(None)) is not None and ctx.has_request: + url_adapter = ctx.url_adapter + blueprint_name = 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: + # 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 ctx is not None: + url_adapter = 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: int | None = None + headers: HeadersValue | None = 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 # pyright: ignore + 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, # pyright: ignore + 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) + + return rv + + def preprocess_request(self, ctx: AppContext) -> 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. + """ + req = ctx.request + names = (None, *reversed(req.blueprints)) + + for name in names: + if name in self.url_value_preprocessors: + for url_func in self.url_value_preprocessors[name]: + url_func(req.endpoint, req.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, ctx: AppContext, 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`. + """ + for func in ctx._after_request_functions: + response = self.ensure_sync(func)(response) + + for name in chain(ctx.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, ctx: AppContext, exc: BaseException | None = None + ) -> None: + """Called after the request is dispatched and the response is finalized, + right before the request context is popped. Called by + :meth:`.AppContext.pop`. + + 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. + + :param exc: An unhandled exception raised while dispatching the request. + Passed to each teardown function. + + .. versionchanged:: 0.9 + Added the ``exc`` argument. + """ + for name in chain(ctx.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, ctx: AppContext, exc: BaseException | None = None + ) -> None: + """Called right before the application context is popped. Called by + :meth:`.AppContext.pop`. + + This calls all functions decorated with :meth:`teardown_appcontext`. + Then the :data:`appcontext_tearing_down` signal is sent. + + :param exc: An unhandled exception raised while the context was active. + Passed to each teardown function. + + .. versionadded:: 0.9 + """ + 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:`.AppContext`. When the context is pushed, + :data:`.current_app` and :data:`.g` become available. + + A context is automatically pushed when handling each request, and when + running any ``flask`` CLI command. Use this as a ``with`` block to + manually push a context outside of those situations, such as during + setup or testing. + + .. code-block:: python + + with app.app_context(): + init_db() + + See :doc:`/appcontext`. + + .. versionadded:: 0.9 + """ + return AppContext(self) + + def request_context(self, environ: WSGIEnvironment) -> AppContext: + """Create an :class:`.AppContext` with request information representing + the given WSGI environment. A context is automatically pushed when + handling each request. When the context is pushed, :data:`.request`, + :data:`.session`, :data:`g:, and :data:`.current_app` become available. + + This method should not be used in your own code. Creating a valid WSGI + environ is not trivial. Use :meth:`test_request_context` to correctly + create a WSGI environ and request context instead. + + See :doc:`/appcontext`. + + :param environ: A WSGI environment. + """ + return AppContext.from_environ(self, environ) + + def test_request_context(self, *args: t.Any, **kwargs: t.Any) -> AppContext: + """Create an :class:`.AppContext` with request information created from + the given arguments. When the context is pushed, :data:`.request`, + :data:`.session`, :data:`g:, and :data:`.current_app` become available. + + This is useful during testing to run a function that uses request data + without dispatching a full request. Use this as a ``with`` block to push + a context. + + .. code-block:: python + + with app.test_request_context(...): + generate_report() + + See :doc:`/appcontext`. + + 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 prepend to :data:`SERVER_NAME`. + :param url_scheme: Scheme to use instead of + :data:`PREFERRED_URL_SCHEME`. + :param data: The request body text or bytes,or a dict of form data. + :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: + environ = builder.get_environ() + finally: + builder.close() + + return self.request_context(environ) + + 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. + + :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(ctx) + except Exception as e: + error = e + response = self.handle_exception(ctx, 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"](ctx) + + 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/src/flask-main/src/flask/blueprints.py b/src/flask-main/src/flask/blueprints.py new file mode 100644 index 0000000..b6d4e43 --- /dev/null +++ b/src/flask-main/src/flask/blueprints.py @@ -0,0 +1,128 @@ +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", encoding: str | None = "utf-8" + ) -> t.IO[t.AnyStr]: + """Open a resource file relative to :attr:`root_path` for reading. The + blueprint-relative equivalent of the app's :meth:`~.Flask.open_resource` + method. + + :param resource: Path to the resource relative to :attr:`root_path`. + :param mode: Open the file in this mode. Only reading is supported, + valid values are ``"r"`` (or ``"rt"``) and ``"rb"``. + :param encoding: Open the file with this encoding when opening in text + mode. This is ignored when opening in binary mode. + + .. versionchanged:: 3.1 + Added the ``encoding`` parameter. + """ + if mode not in {"r", "rt", "rb"}: + raise ValueError("Resources can only be opened for reading.") + + path = os.path.join(self.root_path, resource) + + if mode == "rb": + return open(path, mode) # pyright: ignore + + return open(path, mode, encoding=encoding) diff --git a/src/flask-main/src/flask/cli.py b/src/flask-main/src/flask/cli.py new file mode 100644 index 0000000..3fa65cf --- /dev/null +++ b/src/flask-main/src/flask/cli.py @@ -0,0 +1,1127 @@ +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. + + .. versionchanged:: 3.1 + Added the ``load_dotenv_defaults`` parameter and attribute. + """ + + def __init__( + self, + app_import_path: str | None = None, + create_app: t.Callable[..., Flask] | None = None, + set_debug_flag: bool = True, + load_dotenv_defaults: 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.load_dotenv_defaults = get_load_dotenv(load_dotenv_defaults) + """Whether default ``.flaskenv`` and ``.env`` files should be loaded. + + ``ScriptInfo`` doesn't load anything, this is for reference when doing + the load elsewhere during processing. + + .. versionadded:: 3.1 + """ + + 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 + app: Flask | None = None + if self.create_app is not None: + app = 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: + try: + import dotenv # noqa: F401 + except ImportError: + # Only show an error if a value was passed, otherwise we still want to + # call load_dotenv and show a message without exiting. + if value is not None: + raise click.BadParameter( + "python-dotenv must be installed to load an env file.", + ctx=ctx, + param=param, + ) from None + + # Load if a value was passed, or we want to load default files, or both. + if value is not None or ctx.obj.load_dotenv_defaults: + load_dotenv(value, load_defaults=ctx.obj.load_dotenv_defaults) + + 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, taking precedence over" + " those set by '.env' and '.flaskenv'. Variables set directly in the" + " environment take highest precedence. 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:: 3.1 + ``-e path`` takes precedence over default ``.env`` and ``.flaskenv`` files. + + .. 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[click.Parameter] = 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 + + for ep in importlib.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: + 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" + + 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, + load_dotenv_defaults=self.load_dotenv, + ) + + 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) or ( + len(args) == 1 and args[0] in self.get_help_option_names(ctx) + ): + # 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, load_defaults: bool = True +) -> bool: + """Load "dotenv" files to set environment variables. A given path takes + precedence over ``.env``, which takes precedence over ``.flaskenv``. After + loading and combining these files, values are only set if the key is not + already set in ``os.environ``. + + 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. + :param load_defaults: Search for and load the default ``.flaskenv`` and + ``.env`` files. + :return: ``True`` if at least one env var was loaded. + + .. versionchanged:: 3.1 + Added the ``load_defaults`` parameter. A given path takes precedence + over default files. + + .. 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 files present. Install python-dotenv" + " to use them.", + fg="yellow", + err=True, + ) + + return False + + data: dict[str, str | None] = {} + + if load_defaults: + for default_name in (".flaskenv", ".env"): + if not (default_path := dotenv.find_dotenv(default_name, usecwd=True)): + continue + + data |= dotenv.dotenv_values(default_path, encoding="utf-8") + + if path is not None and os.path.isfile(path): + data |= dotenv.dotenv_values(path, encoding="utf-8") + + for key, value in data.items(): + if key in os.environ or value is None: + continue + + os.environ[key] = value + + return bool(data) # True if at least one env var was 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() # pyright: ignore + 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/src/flask-main/src/flask/config.py b/src/flask-main/src/flask/config.py new file mode 100644 index 0000000..34ef1a5 --- /dev/null +++ b/src/flask-main/src/flask/config.py @@ -0,0 +1,367 @@ +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}_" + + for key in sorted(os.environ): + if not key.startswith(prefix): + continue + + value = os.environ[key] + key = key.removeprefix(prefix) + + try: + value = loads(value) + except Exception: + # Keep the value as a string if loading failed. + pass + + 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/src/flask-main/src/flask/ctx.py b/src/flask-main/src/flask/ctx.py new file mode 100644 index 0000000..ba72b17 --- /dev/null +++ b/src/flask-main/src/flask/ctx.py @@ -0,0 +1,516 @@ +from __future__ import annotations + +import contextvars +import typing as t +from functools import update_wrapper +from types import TracebackType + +from werkzeug.exceptions import HTTPException +from werkzeug.routing import MapAdapter + +from . import typing as ft +from .globals import _cv_app +from .signals import appcontext_popped +from .signals import appcontext_pushed + +if t.TYPE_CHECKING: + import typing_extensions as te + 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]: + """Decorate a function to run after the current request. The behavior is the + same as :meth:`.Flask.after_request`, except it only applies to the current + request, rather than every request. Therefore, it must be used within a + request context, rather than during setup. + + .. code-block:: python + + @app.route("/") + def index(): + @after_this_request + def add_header(response): + response.headers["X-Foo"] = "Parachute" + return response + + return "Hello, World!" + + .. versionadded:: 0.9 + """ + ctx = _cv_app.get(None) + + if ctx is None or not ctx.has_request: + 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: + """Decorate a function to run inside the current request context. This can + be used when starting a background task, otherwise it will not see the app + and request objects that were active in the parent. + + .. warning:: + + Due to the following caveats, it is often safer (and simpler) to pass + the data you need when starting the task, rather than using this and + relying on the context objects. + + In order to avoid execution switching partially though reading data, either + read the request body (access ``form``, ``json``, ``data``, etc) before + starting the task, or use a lock. This can be an issue when using threading, + but shouldn't be an issue when using greenlet/gevent or asyncio. + + If the task will access ``session``, be sure to do so in the parent as well + so that the ``Vary: cookie`` header will be set. Modifying ``session`` in + the task should be avoided, as it may execute after the response cookie has + already been written. + + .. code-block:: python + + 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_app.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: + return ctx.app.ensure_sync(f)(*args, **kwargs) + + return update_wrapper(wrapper, f) # type: ignore[return-value] + + +def has_request_context() -> bool: + """Test if an app context is active and if it has request information. + + .. code-block:: python + + from flask import has_request_context, request + + if has_request_context(): + remote_addr = request.remote_addr + + If a request context is active, the :data:`.request` and :data:`.session` + context proxies will available and ``True``, otherwise ``False``. You can + use that to test the data you use, rather than using this function. + + .. code-block:: python + + from flask import request + + if request: + remote_addr = request.remote_addr + + .. versionadded:: 0.7 + """ + return (ctx := _cv_app.get(None)) is not None and ctx.has_request + + +def has_app_context() -> bool: + """Test if an app context is active. Unlike :func:`has_request_context` + this can be true outside a request, such as in a CLI command. + + .. code-block:: python + + from flask import has_app_context, g + + if has_app_context(): + g.cached_data = ... + + If an app context is active, the :data:`.g` and :data:`.current_app` context + proxies will available and ``True``, otherwise ``False``. You can use that + to test the data you use, rather than using this function. + + from flask import g + + if g: + g.cached_data = ... + + .. versionadded:: 0.9 + """ + return _cv_app.get(None) is not None + + +class AppContext: + """An app context contains information about an app, and about the request + when handling a request. A context is pushed at the beginning of each + request and CLI command, and popped at the end. The context is referred to + as a "request context" if it has request information, and an "app context" + if not. + + Do not use this class directly. Use :meth:`.Flask.app_context` to create an + app context if needed during setup, and :meth:`.Flask.test_request_context` + to create a request context if needed during tests. + + When the context is popped, it will evaluate all the teardown functions + registered with :meth:`~flask.Flask.teardown_request` (if handling a + request) then :meth:`.Flask.teardown_appcontext`. + + 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, and will run again when + the restored context is popped. + + :param app: The application this context represents. + :param request: The request data this context represents. + :param session: The session data this context represents. If not given, + loaded from the request on first access. + + .. versionchanged:: 3.2 + Merged with ``RequestContext``. The ``RequestContext`` alias will be + removed in Flask 4.0. + + .. versionchanged:: 3.2 + A combined app and request context is pushed for every request and CLI + command, rather than trying to detect if an app context is already + pushed. + + .. versionchanged:: 3.2 + The session is loaded the first time it is accessed, rather than when + the context is pushed. + """ + + def __init__( + self, + app: Flask, + *, + request: Request | None = None, + session: SessionMixin | None = None, + ) -> None: + self.app = app + """The application represented by this context. Accessed through + :data:`.current_app`. + """ + + self.g: _AppCtxGlobals = app.app_ctx_globals_class() + """The global data for this context. Accessed through :data:`.g`.""" + + self.url_adapter: MapAdapter | None = None + """The URL adapter bound to the request, or the app if not in a request. + May be ``None`` if binding failed. + """ + + self._request: Request | None = request + self._session: SessionMixin | None = session + self._flashes: list[tuple[str, str]] | None = None + self._after_request_functions: list[ft.AfterRequestCallable[t.Any]] = [] + + try: + self.url_adapter = app.create_url_adapter(self._request) + except HTTPException as e: + if self._request is not None: + self._request.routing_exception = e + + self._cv_token: contextvars.Token[AppContext] | None = None + """The previous state to restore when popping.""" + + self._push_count: int = 0 + """Track nested pushes of this context. Cleanup will only run once the + original push has been popped. + """ + + @classmethod + def from_environ(cls, app: Flask, environ: WSGIEnvironment, /) -> te.Self: + """Create an app context with request data from the given WSGI environ. + + :param app: The application this context represents. + :param environ: The request data this context represents. + """ + request = app.request_class(environ) + request.json_module = app.json + return cls(app, request=request) + + @property + def has_request(self) -> bool: + """True if this context was created with request data.""" + return self._request is not None + + def copy(self) -> te.Self: + """Create a new context with the same data objects as this context. See + :func:`.copy_current_request_context`. + + .. versionchanged:: 1.1 + The current session data is used instead of reloading the original data. + + .. versionadded:: 0.10 + """ + return self.__class__( + self.app, + request=self._request, + session=self._session, + ) + + @property + def request(self) -> Request: + """The request object associated with this context. Accessed through + :data:`.request`. Only available in request contexts, otherwise raises + :exc:`RuntimeError`. + """ + if self._request is None: + raise RuntimeError("There is no request in this context.") + + return self._request + + @property + def session(self) -> SessionMixin: + """The session object associated with this context. Accessed through + :data:`.session`. Only available in request contexts, otherwise raises + :exc:`RuntimeError`. + """ + if self._request is None: + raise RuntimeError("There is no request in this context.") + + if self._session is None: + si = self.app.session_interface + self._session = si.open_session(self.app, self.request) + + if self._session is None: + self._session = si.make_null_session(self.app) + + return self._session + + def match_request(self) -> None: + """Apply routing to the current request, storing either the matched + endpoint and args, or a routing exception. + """ + try: + result = self.url_adapter.match(return_rule=True) # type: ignore[union-attr] + except HTTPException as e: + self._request.routing_exception = e # type: ignore[union-attr] + else: + self._request.url_rule, self._request.view_args = result # type: ignore[union-attr] + + def push(self) -> None: + """Push this context so that it is the active context. If this is a + request context, calls :meth:`match_request` to perform routing with + the context active. + + Typically, this is not used directly. Instead, use a ``with`` block + to manage the context. + + In some situations, such as streaming or testing, the context may be + pushed multiple times. It will only trigger matching and signals if it + is not currently pushed. + """ + self._push_count += 1 + + if self._cv_token is not None: + return + + self._cv_token = _cv_app.set(self) + appcontext_pushed.send(self.app, _async_wrapper=self.app.ensure_sync) + + if self._request is not None and self.url_adapter is not None: + self.match_request() + + def pop(self, exc: BaseException | None = None) -> None: + """Pop this context so that it is no longer the active context. Then + call teardown functions and signals. + + Typically, this is not used directly. Instead, use a ``with`` block + to manage the context. + + This context must currently be the active context, otherwise a + :exc:`RuntimeError` is raised. In some situations, such as streaming or + testing, the context may have been pushed multiple times. It will only + trigger cleanup once it has been popped as many times as it was pushed. + Until then, it will remain the active context. + + :param exc: An unhandled exception that was raised while the context was + active. Passed to teardown functions. + + .. versionchanged:: 0.9 + Added the ``exc`` argument. + """ + if self._cv_token is None: + raise RuntimeError(f"Cannot pop this context ({self!r}), it is not pushed.") + + ctx = _cv_app.get(None) + + if ctx is None or self._cv_token is None: + raise RuntimeError( + f"Cannot pop this context ({self!r}), there is no active context." + ) + + if ctx is not self: + raise RuntimeError( + f"Cannot pop this context ({self!r}), it is not the active" + f" context ({ctx!r})." + ) + + self._push_count -= 1 + + if self._push_count > 0: + return + + try: + if self._request is not None: + self.app.do_teardown_request(self, exc) + self._request.close() + finally: + self.app.do_teardown_appcontext(self, exc) + _cv_app.reset(self._cv_token) + self._cv_token = None + appcontext_popped.send(self.app, _async_wrapper=self.app.ensure_sync) + + def __enter__(self) -> te.Self: + self.push() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> None: + self.pop(exc_value) + + def __repr__(self) -> str: + if self._request is not None: + return ( + f"<{type(self).__name__} {id(self)} of {self.app.name}," + f" {self.request.method} {self.request.url!r}>" + ) + + return f"<{type(self).__name__} {id(self)} of {self.app.name}>" + + +def __getattr__(name: str) -> t.Any: + import warnings + + if name == "RequestContext": + warnings.warn( + "'RequestContext' has merged with 'AppContext', and will be removed" + " in Flask 4.0. Use 'AppContext' instead.", + DeprecationWarning, + stacklevel=2, + ) + return AppContext + + raise AttributeError(name) diff --git a/src/flask-main/src/flask/debughelpers.py b/src/flask-main/src/flask/debughelpers.py new file mode 100644 index 0000000..61884e1 --- /dev/null +++ b/src/flask-main/src/flask/debughelpers.py @@ -0,0 +1,179 @@ +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 _cv_app +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 (ctx := _cv_app.get(None)) is not None and ctx.has_request: + blueprint = 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/src/flask-main/src/flask/globals.py b/src/flask-main/src/flask/globals.py new file mode 100644 index 0000000..f4a7298 --- /dev/null +++ b/src/flask-main/src/flask/globals.py @@ -0,0 +1,77 @@ +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 .sessions import SessionMixin + from .wrappers import Request + + T = t.TypeVar("T", covariant=True) + + class ProxyMixin(t.Protocol[T]): + def _get_current_object(self) -> T: ... + + # These subclasses inform type checkers that the proxy objects look like the + # proxied type along with the _get_current_object method. + class FlaskProxy(ProxyMixin[Flask], Flask): ... + + class AppContextProxy(ProxyMixin[AppContext], AppContext): ... + + class _AppCtxGlobalsProxy(ProxyMixin[_AppCtxGlobals], _AppCtxGlobals): ... + + class RequestProxy(ProxyMixin[Request], Request): ... + + class SessionMixinProxy(ProxyMixin[SessionMixin], SessionMixin): ... + + +_no_app_msg = """\ +Working outside of application context. + +Attempted to use functionality that expected a current application to be set. To +solve this, set up an app context using 'with app.app_context()'. See the +documentation on app context for more information.\ +""" +_cv_app: ContextVar[AppContext] = ContextVar("flask.app_ctx") +app_ctx: AppContextProxy = LocalProxy( # type: ignore[assignment] + _cv_app, unbound_message=_no_app_msg +) +current_app: FlaskProxy = LocalProxy( # type: ignore[assignment] + _cv_app, "app", unbound_message=_no_app_msg +) +g: _AppCtxGlobalsProxy = LocalProxy( # type: ignore[assignment] + _cv_app, "g", unbound_message=_no_app_msg +) + +_no_req_msg = """\ +Working outside of request context. + +Attempted to use functionality that expected an active HTTP request. See the +documentation on request context for more information.\ +""" +request: RequestProxy = LocalProxy( # type: ignore[assignment] + _cv_app, "request", unbound_message=_no_req_msg +) +session: SessionMixinProxy = LocalProxy( # type: ignore[assignment] + _cv_app, "session", unbound_message=_no_req_msg +) + + +def __getattr__(name: str) -> t.Any: + import warnings + + if name == "request_ctx": + warnings.warn( + "'request_ctx' has merged with 'app_ctx', and will be removed" + " in Flask 4.0. Use 'app_ctx' instead.", + DeprecationWarning, + stacklevel=2, + ) + return app_ctx + + raise AttributeError(name) diff --git a/src/flask-main/src/flask/helpers.py b/src/flask-main/src/flask/helpers.py new file mode 100644 index 0000000..31167c2 --- /dev/null +++ b/src/flask-main/src/flask/helpers.py @@ -0,0 +1,635 @@ +from __future__ import annotations + +import importlib.util +import os +import sys +import typing as t +from datetime import datetime +from functools import 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_app +from .globals import app_ctx +from .globals import current_app +from .globals import request +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") + + +@t.overload +def stream_with_context( + generator_or_function: t.Iterator[t.AnyStr], +) -> t.Iterator[t.AnyStr]: ... + + +@t.overload +def stream_with_context( + generator_or_function: t.Callable[..., t.Iterator[t.AnyStr]], +) -> t.Callable[[t.Iterator[t.AnyStr]], t.Iterator[t.AnyStr]]: ... + + +def stream_with_context( + generator_or_function: t.Iterator[t.AnyStr] | t.Callable[..., t.Iterator[t.AnyStr]], +) -> t.Iterator[t.AnyStr] | t.Callable[[t.Iterator[t.AnyStr]], t.Iterator[t.AnyStr]]: + """Wrap a response generator function so that it runs inside the current + request context. This keeps :data:`.request`, :data:`.session`, and :data:`.g` + available, even though at the point the generator runs the request context + will typically have ended. + + Use it as a decorator on a generator function: + + .. code-block:: python + + from flask import stream_with_context, request, Response + + @app.get("/stream") + def streamed_response(): + @stream_with_context + def generate(): + yield "Hello " + yield request.args["name"] + yield "!" + + return Response(generate()) + + Or use it as a wrapper around a created generator: + + .. code-block:: python + + from flask import stream_with_context, request, Response + + @app.get("/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]: + if (ctx := _cv_app.get(None)) 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: + yield None # type: ignore[misc] + + try: + yield from gen + finally: + # Clean up in case the user wrapped a WSGI iterator. + if hasattr(gen, "close"): + gen.close() + + # Execute the generator to the sentinel value. This captures the current + # context and pushes it to preserve it. Further iteration will yield from + # the original iterator. + wrapped_g = generator() + next(wrapped_g) + return wrapped_g + + +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 (ctx := _cv_app.get(None)) is not None: + return ctx.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 (ctx := _cv_app.get(None)) is not None: + ctx.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() + 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 = app_ctx._flashes + if flashes is None: + flashes = session.pop("_flashes") if "_flashes" in session else [] + app_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]: + ctx = app_ctx._get_current_object() + + if kwargs.get("max_age") is None: + kwargs["max_age"] = ctx.app.get_send_file_max_age + + kwargs.update( + environ=ctx.request.environ, + use_x_sendfile=ctx.app.config["USE_X_SENDFILE"], + response_class=ctx.app.response_class, + _root_path=ctx.app.root_path, + ) + return kwargs + + +def send_file( + path_or_file: os.PathLike[t.AnyStr] | str | t.IO[bytes], + 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. 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``. + :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) # pyright: ignore + 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] + + +@cache +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/src/flask-main/src/flask/json/__init__.py b/src/flask-main/src/flask/json/__init__.py new file mode 100644 index 0000000..742812f --- /dev/null +++ b/src/flask-main/src/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 app 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/src/flask-main/src/flask/json/provider.py b/src/flask-main/src/flask/json/provider.py new file mode 100644 index 0000000..f37cb7b --- /dev/null +++ b/src/flask-main/src/flask/json/provider.py @@ -0,0 +1,215 @@ +from __future__ import annotations + +import dataclasses +import decimal +import json +import typing as t +import uuid +import weakref +from datetime import date + +from werkzeug.http import http_date + +if t.TYPE_CHECKING: # pragma: no cover + from werkzeug.sansio.response import Response + + from ..sansio.app import App + + +class JSONProvider: + """A standard set of JSON operations for an application. Subclasses + of this can be used to customize JSON behavior or use different + JSON libraries. + + To implement a provider for a specific library, subclass this base + class and implement at least :meth:`dumps` and :meth:`loads`. All + other methods have default implementations. + + To use a different provider, either subclass ``Flask`` and set + :attr:`~flask.Flask.json_provider_class` to a provider class, or set + :attr:`app.json ` 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) # type: ignore[arg-type] + + 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) + """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/src/flask-main/src/flask/json/tag.py b/src/flask-main/src/flask/json/tag.py new file mode 100644 index 0000000..8dc3629 --- /dev/null +++ b/src/flask-main/src/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/src/flask-main/src/flask/logging.py b/src/flask-main/src/flask/logging.py new file mode 100644 index 0000000..0cb8f43 --- /dev/null +++ b/src/flask-main/src/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/src/flask-main/src/flask/py.typed b/src/flask-main/src/flask/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/src/flask-main/src/flask/sansio/README.md b/src/flask-main/src/flask/sansio/README.md new file mode 100644 index 0000000..623ac19 --- /dev/null +++ b/src/flask-main/src/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/src/flask-main/src/flask/sansio/app.py b/src/flask-main/src/flask/sansio/app.py new file mode 100644 index 0000000..43d2052 --- /dev/null +++ b/src/flask-main/src/flask/sansio/app.py @@ -0,0 +1,1006 @@ +from __future__ import annotations + +import logging +import os +import sys +import typing as t +from datetime import timedelta +from itertools import chain + +from werkzeug.exceptions import Aborter +from werkzeug.exceptions import BadRequest +from werkzeug.exceptions import BadRequestKeyError +from werkzeug.routing import BuildError +from werkzeug.routing import Map +from werkzeug.routing import Rule +from werkzeug.sansio.response import Response +from werkzeug.utils import cached_property +from werkzeug.utils import redirect as _wz_redirect + +from .. import typing as ft +from ..config import Config +from ..config import ConfigAttribute +from ..ctx import _AppCtxGlobals +from ..helpers import _split_blueprint_path +from ..helpers import get_debug_flag +from ..json.provider import DefaultJSONProvider +from ..json.provider import JSONProvider +from ..logging import create_logger +from ..templating import DispatchingJinjaLoader +from ..templating import Environment +from .scaffold import _endpoint_from_view_func +from .scaffold import find_package +from .scaffold import Scaffold +from .scaffold import setupmethod + +if t.TYPE_CHECKING: # pragma: no cover + from werkzeug.wrappers import Response as BaseResponse + + from ..testing import FlaskClient + from ..testing import FlaskCliRunner + from .blueprints import Blueprint + +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 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. + #: + #: .. versionadded:: 0.10 + #: Renamed from ``request_globals_class`. + 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[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, + ) -> 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: + """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 Jinja 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[str] = 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 and self.config["PROVIDE_AUTOMATIC_OPTIONS"]: + 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 + + @t.overload + def template_filter(self, name: T_template_filter) -> T_template_filter: ... + @t.overload + def template_filter( + self, name: str | None = None + ) -> t.Callable[[T_template_filter], T_template_filter]: ... + @setupmethod + def template_filter( + self, name: T_template_filter | str | None = None + ) -> T_template_filter | t.Callable[[T_template_filter], T_template_filter]: + """Decorate a function to register it as a custom Jinja filter. The name + is optional. The decorator may be used without parentheses. + + .. code-block:: python + + @app.template_filter("reverse") + def reverse_filter(s): + return reversed(s) + + The :meth:`add_template_filter` method may be used to register a + function later rather than decorating. + + :param name: The name to register the filter as. If not given, uses the + function's name. + """ + if callable(name): + self.add_template_filter(name) + return name + + 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 function to use as a custom Jinja filter. + + The :meth:`template_filter` decorator can be used to register a function + by decorating instead. + + :param f: The function to register. + :param name: The name to register the filter as. If not given, uses the + function's name. + """ + self.jinja_env.filters[name or f.__name__] = f + + @t.overload + def template_test(self, name: T_template_test) -> T_template_test: ... + @t.overload + def template_test( + self, name: str | None = None + ) -> t.Callable[[T_template_test], T_template_test]: ... + @setupmethod + def template_test( + self, name: T_template_test | str | None = None + ) -> T_template_test | t.Callable[[T_template_test], T_template_test]: + """Decorate a function to register it as a custom Jinja test. The name + is optional. The decorator may be used without parentheses. + + .. code-block:: python + + @app.template_test("prime") + def is_prime_test(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 + + The :meth:`add_template_test` method may be used to register a function + later rather than decorating. + + :param name: The name to register the filter as. If not given, uses the + function's name. + + .. versionadded:: 0.10 + """ + if callable(name): + self.add_template_test(name) + return name + + 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 function to use as a custom Jinja test. + + The :meth:`template_test` decorator can be used to register a function + by decorating instead. + + :param f: The function to register. + :param name: The name to register the test as. If not given, uses the + function's name. + + .. versionadded:: 0.10 + """ + self.jinja_env.tests[name or f.__name__] = f + + @t.overload + def template_global(self, name: T_template_global) -> T_template_global: ... + @t.overload + def template_global( + self, name: str | None = None + ) -> t.Callable[[T_template_global], T_template_global]: ... + @setupmethod + def template_global( + self, name: T_template_global | str | None = None + ) -> T_template_global | t.Callable[[T_template_global], T_template_global]: + """Decorate a function to register it as a custom Jinja global. The name + is optional. The decorator may be used without parentheses. + + .. code-block:: python + + @app.template_global + def double(n): + return 2 * n + + The :meth:`add_template_global` method may be used to register a + function later rather than decorating. + + :param name: The name to register the global as. If not given, uses the + function's name. + + .. versionadded:: 0.10 + """ + if callable(name): + self.add_template_global(name) + return name + + 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 function to use as a custom Jinja global. + + The :meth:`template_global` decorator can be used to register a function + by decorating instead. + + :param f: The function to register. + :param name: The name to register the global as. If not given, uses the + function's name. + + .. versionadded:: 0.10 + """ + 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 app context is popped. The + context is popped at the end of a request, CLI command, or manual ``with`` + block. + + .. 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. + + 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/src/flask-main/src/flask/sansio/blueprints.py b/src/flask-main/src/flask/sansio/blueprints.py new file mode 100644 index 0000000..665816e --- /dev/null +++ b/src/flask-main/src/flask/sansio/blueprints.py @@ -0,0 +1,692 @@ +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, + ) + ) + + @t.overload + def app_template_filter(self, name: T_template_filter) -> T_template_filter: ... + @t.overload + def app_template_filter( + self, name: str | None = None + ) -> t.Callable[[T_template_filter], T_template_filter]: ... + @setupmethod + def app_template_filter( + self, name: T_template_filter | str | None = None + ) -> T_template_filter | t.Callable[[T_template_filter], T_template_filter]: + """Decorate a function to register it as a custom Jinja filter. The name + is optional. The decorator may be used without parentheses. + + The :meth:`add_app_template_filter` method may be used to register a + function later rather than decorating. + + The filter is available in all templates, not only those under this + blueprint. Equivalent to :meth:`.Flask.template_filter`. + + :param name: The name to register the filter as. If not given, uses the + function's name. + """ + if callable(name): + self.add_app_template_filter(name) + return name + + 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 function to use as a custom Jinja filter. + + The :meth:`app_template_filter` decorator can be used to register a + function by decorating instead. + + The filter is available in all templates, not only those under this + blueprint. Equivalent to :meth:`.Flask.add_template_filter`. + + :param f: The function to register. + :param name: The name to register the filter as. If not given, uses the + function's name. + """ + + def register_template_filter(state: BlueprintSetupState) -> None: + state.app.add_template_filter(f, name=name) + + self.record_once(register_template_filter) + + @t.overload + def app_template_test(self, name: T_template_test) -> T_template_test: ... + @t.overload + def app_template_test( + self, name: str | None = None + ) -> t.Callable[[T_template_test], T_template_test]: ... + @setupmethod + def app_template_test( + self, name: T_template_test | str | None = None + ) -> T_template_test | t.Callable[[T_template_test], T_template_test]: + """Decorate a function to register it as a custom Jinja test. The name + is optional. The decorator may be used without parentheses. + + The :meth:`add_app_template_test` method may be used to register a + function later rather than decorating. + + The test is available in all templates, not only those under this + blueprint. Equivalent to :meth:`.Flask.template_test`. + + :param name: The name to register the filter as. If not given, uses the + function's name. + + .. versionadded:: 0.10 + """ + if callable(name): + self.add_app_template_test(name) + return name + + 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 function to use as a custom Jinja test. + + The :meth:`app_template_test` decorator can be used to register a + function by decorating instead. + + The test is available in all templates, not only those under this + blueprint. Equivalent to :meth:`.Flask.add_template_test`. + + :param f: The function to register. + :param name: The name to register the test as. If not given, uses the + function's name. + + .. versionadded:: 0.10 + """ + + def register_template_test(state: BlueprintSetupState) -> None: + state.app.add_template_test(f, name=name) + + self.record_once(register_template_test) + + @t.overload + def app_template_global(self, name: T_template_global) -> T_template_global: ... + @t.overload + def app_template_global( + self, name: str | None = None + ) -> t.Callable[[T_template_global], T_template_global]: ... + @setupmethod + def app_template_global( + self, name: T_template_global | str | None = None + ) -> T_template_global | t.Callable[[T_template_global], T_template_global]: + """Decorate a function to register it as a custom Jinja global. The name + is optional. The decorator may be used without parentheses. + + The :meth:`add_app_template_global` method may be used to register a + function later rather than decorating. + + The global is available in all templates, not only those under this + blueprint. Equivalent to :meth:`.Flask.template_global`. + + :param name: The name to register the global as. If not given, uses the + function's name. + + .. versionadded:: 0.10 + """ + if callable(name): + self.add_app_template_global(name) + return name + + 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 function to use as a custom Jinja global. + + The :meth:`app_template_global` decorator can be used to register a function + by decorating instead. + + The global is available in all templates, not only those under this + blueprint. Equivalent to :meth:`.Flask.add_template_global`. + + :param f: The function to register. + :param name: The name to register the global as. If not given, uses the + function's name. + + .. versionadded:: 0.10 + """ + + def register_template_global(state: BlueprintSetupState) -> None: + state.app.add_template_global(f, name=name) + + self.record_once(register_template_global) + + @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/src/flask-main/src/flask/sansio/scaffold.py b/src/flask-main/src/flask/sansio/scaffold.py new file mode 100644 index 0000000..a04c38a --- /dev/null +++ b/src/flask-main/src/flask/sansio/scaffold.py @@ -0,0 +1,792 @@ +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 + 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 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 _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 package_path.is_relative_to(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 pathlib.PurePath(package_path).is_relative_to(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/src/flask-main/src/flask/sessions.py b/src/flask-main/src/flask/sessions.py new file mode 100644 index 0000000..c1d978d --- /dev/null +++ b/src/flask-main/src/flask/sessions.py @@ -0,0 +1,399 @@ +from __future__ import annotations + +import collections.abc as c +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 + + +class SessionMixin(MutableMapping[str, t.Any]): + """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) # type: ignore[no-any-return] + + @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 + + +class SecureCookieSession(CallbackDict[str, t.Any], SessionMixin): + """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: c.Mapping[str, t.Any] | c.Iterable[tuple[str, t.Any]] | None = 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 # 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_cookie_partitioned(self, app: Flask) -> bool: + """Returns True if the cookie should be partitioned. By default, uses + the value of :data:`SESSION_COOKIE_PARTITIONED`. + + .. versionadded:: 3.1 + """ + return app.config["SESSION_COOKIE_PARTITIONED"] # 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 + + keys: list[str | bytes] = [] + + if fallbacks := app.config["SECRET_KEY_FALLBACKS"]: + keys.extend(fallbacks) + + keys.append(app.secret_key) # itsdangerous expects current key at top + return URLSafeTimedSerializer( + keys, # type: ignore[arg-type] + salt=self.salt, + serializer=self.serializer, + signer_kwargs={ + "key_derivation": self.key_derivation, + "digest_method": self.digest_method, + }, + ) + + 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) + partitioned = self.get_cookie_partitioned(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, + partitioned=partitioned, + 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[union-attr] + response.set_cookie( + name, + val, + expires=expires, + httponly=httponly, + domain=domain, + path=path, + secure=secure, + partitioned=partitioned, + samesite=samesite, + ) + response.vary.add("Cookie") diff --git a/src/flask-main/src/flask/signals.py b/src/flask-main/src/flask/signals.py new file mode 100644 index 0000000..444fda9 --- /dev/null +++ b/src/flask-main/src/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/src/flask-main/src/flask/templating.py b/src/flask-main/src/flask/templating.py new file mode 100644 index 0000000..4bb86d5 --- /dev/null +++ b/src/flask-main/src/flask/templating.py @@ -0,0 +1,211 @@ +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 .ctx import AppContext +from .globals import app_ctx +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 .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`. + """ + ctx = app_ctx._get_current_object() + rv: dict[str, t.Any] = {"g": ctx.g} + + if ctx.has_request: + rv["request"] = ctx.request + rv["session"] = ctx.session + + return rv + + +class Environment(BaseEnvironment): + """Works like a regular Jinja 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(ctx: AppContext, template: Template, context: dict[str, t.Any]) -> str: + app = ctx.app + app.update_template_context(ctx, 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. + """ + ctx = app_ctx._get_current_object() + template = ctx.app.jinja_env.get_or_select_template(template_name_or_list) + return _render(ctx, 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. + """ + ctx = app_ctx._get_current_object() + template = ctx.app.jinja_env.from_string(source) + return _render(ctx, template, context) + + +def _stream( + ctx: AppContext, template: Template, context: dict[str, t.Any] +) -> t.Iterator[str]: + app = ctx.app + app.update_template_context(ctx, 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 + ) + + return stream_with_context(generate()) + + +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 + """ + ctx = app_ctx._get_current_object() + template = ctx.app.jinja_env.get_or_select_template(template_name_or_list) + return _stream(ctx, 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 + """ + ctx = app_ctx._get_current_object() + template = ctx.app.jinja_env.from_string(source) + return _stream(ctx, template, context) diff --git a/src/flask-main/src/flask/testing.py b/src/flask-main/src/flask/testing.py new file mode 100644 index 0000000..68b1ab4 --- /dev/null +++ b/src/flask-main/src/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 click.testing import Result +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: + path = f"{path}?{url.query}" + + self.app = app + super().__init__(path, base_url, *args, **kwargs) + + def json_dumps(self, obj: t.Any, **kwargs: t.Any) -> str: + """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, with additional behavior for + Flask. Can 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. + for cm in self._new_contexts: + self._context_stack.enter_context(cm) + + self._new_contexts.clear() + 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 + ) -> Result: + """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/src/flask-main/src/flask/typing.py b/src/flask-main/src/flask/typing.py new file mode 100644 index 0000000..5495061 --- /dev/null +++ b/src/flask-main/src/flask/typing.py @@ -0,0 +1,87 @@ +from __future__ import annotations + +import collections.abc as cabc +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, + list[t.Any], + # Only dict is actually accepted, but Mapping allows for TypedDict. + t.Mapping[str, t.Any], + t.Iterator[str], + t.Iterator[bytes], + cabc.AsyncIterable[str], # for Quart, until App is generic. + cabc.AsyncIterable[bytes], +] + +# the possible types for an individual HTTP header +HeaderValue = str | list[str] | tuple[str, ...] + +# the possible types for HTTP headers +HeadersValue = t.Union[ + "Headers", + t.Mapping[str, HeaderValue], + t.Sequence[tuple[str, HeaderValue]], +] + +# The possible types returned by a route function. +ResponseReturnValue = t.Union[ + ResponseValue, + tuple[ResponseValue, HeadersValue], + tuple[ResponseValue, int], + 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 = str | None # The App key is None, whereas blueprints are named +AfterRequestCallable = ( + t.Callable[[ResponseClass], ResponseClass] + | t.Callable[[ResponseClass], t.Awaitable[ResponseClass]] +) +BeforeFirstRequestCallable = t.Callable[[], None] | t.Callable[[], t.Awaitable[None]] +BeforeRequestCallable = ( + t.Callable[[], ResponseReturnValue | None] + | t.Callable[[], t.Awaitable[ResponseReturnValue | None]] +) +ShellContextProcessorCallable = t.Callable[[], dict[str, t.Any]] +TeardownCallable = ( + t.Callable[[BaseException | None], None] + | t.Callable[[BaseException | None], t.Awaitable[None]] +) +TemplateContextProcessorCallable = ( + t.Callable[[], dict[str, t.Any]] | t.Callable[[], t.Awaitable[dict[str, t.Any]]] +) +TemplateFilterCallable = t.Callable[..., t.Any] +TemplateGlobalCallable = t.Callable[..., t.Any] +TemplateTestCallable = t.Callable[..., bool] +URLDefaultCallable = t.Callable[[str, dict[str, t.Any]], None] +URLValuePreprocessorCallable = t.Callable[[str | None, dict[str, t.Any] | None], 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.Callable[[t.Any], ResponseReturnValue] + | t.Callable[[t.Any], t.Awaitable[ResponseReturnValue]] +) + +RouteCallable = ( + t.Callable[..., ResponseReturnValue] + | t.Callable[..., t.Awaitable[ResponseReturnValue]] +) diff --git a/src/flask-main/src/flask/views.py b/src/flask-main/src/flask/views.py new file mode 100644 index 0000000..53fe976 --- /dev/null +++ b/src/flask-main/src/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[..., t.Any]]] = [] + + #: 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) # pyright: ignore + + 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/src/flask-main/src/flask/wrappers.py b/src/flask-main/src/flask/wrappers.py new file mode 100644 index 0000000..bab6102 --- /dev/null +++ b/src/flask-main/src/flask/wrappers.py @@ -0,0 +1,257 @@ +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 + + _max_content_length: int | None = None + _max_form_memory_size: int | None = None + _max_form_parts: int | None = None + + @property + def max_content_length(self) -> int | None: + """The maximum number of bytes that will be read during this request. If + this limit is exceeded, a 413 :exc:`~werkzeug.exceptions.RequestEntityTooLarge` + error is raised. If it is set to ``None``, no limit is enforced at the + Flask application level. However, if it is ``None`` and the request has + no ``Content-Length`` header and the WSGI server does not indicate that + it terminates the stream, then no data is read to avoid an infinite + stream. + + Each request defaults to the :data:`MAX_CONTENT_LENGTH` config, which + defaults to ``None``. It can be set on a specific ``request`` to apply + the limit to that specific view. This should be set appropriately based + on an application's or view's specific needs. + + .. versionchanged:: 3.1 + This can be set per-request. + + .. versionchanged:: 0.6 + This is configurable through Flask config. + """ + if self._max_content_length is not None: + return self._max_content_length + + if not current_app: + return super().max_content_length + + return current_app.config["MAX_CONTENT_LENGTH"] # type: ignore[no-any-return] + + @max_content_length.setter + def max_content_length(self, value: int | None) -> None: + self._max_content_length = value + + @property + def max_form_memory_size(self) -> int | None: + """The maximum size in bytes any non-file form field may be in a + ``multipart/form-data`` body. If this limit is exceeded, a 413 + :exc:`~werkzeug.exceptions.RequestEntityTooLarge` error is raised. If it + is set to ``None``, no limit is enforced at the Flask application level. + + Each request defaults to the :data:`MAX_FORM_MEMORY_SIZE` config, which + defaults to ``500_000``. It can be set on a specific ``request`` to + apply the limit to that specific view. This should be set appropriately + based on an application's or view's specific needs. + + .. versionchanged:: 3.1 + This is configurable through Flask config. + """ + if self._max_form_memory_size is not None: + return self._max_form_memory_size + + if not current_app: + return super().max_form_memory_size + + return current_app.config["MAX_FORM_MEMORY_SIZE"] # type: ignore[no-any-return] + + @max_form_memory_size.setter + def max_form_memory_size(self, value: int | None) -> None: + self._max_form_memory_size = value + + @property # type: ignore[override] + def max_form_parts(self) -> int | None: + """The maximum number of fields that may be present in a + ``multipart/form-data`` body. If this limit is exceeded, a 413 + :exc:`~werkzeug.exceptions.RequestEntityTooLarge` error is raised. If it + is set to ``None``, no limit is enforced at the Flask application level. + + Each request defaults to the :data:`MAX_FORM_PARTS` config, which + defaults to ``1_000``. It can be set on a specific ``request`` to apply + the limit to that specific view. This should be set appropriately based + on an application's or view's specific needs. + + .. versionchanged:: 3.1 + This is configurable through Flask config. + """ + if self._max_form_parts is not None: + return self._max_form_parts + + if not current_app: + return super().max_form_parts + + return current_app.config["MAX_FORM_PARTS"] # type: ignore[no-any-return] + + @max_form_parts.setter + def max_form_parts(self, value: int | None) -> None: + self._max_form_parts = value + + @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 # type: ignore[no-any-return] + + 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 ebr: + if current_app and current_app.debug: + raise + + raise BadRequest() from ebr + + +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/src/flask-main/tests/conftest.py b/src/flask-main/tests/conftest.py new file mode 100644 index 0000000..0414b9e --- /dev/null +++ b/src/flask-main/tests/conftest.py @@ -0,0 +1,129 @@ +import os +import sys + +import pytest +from _pytest import monkeypatch + +from flask import Flask +from flask.globals import app_ctx as _app_ctx + + +@pytest.fixture(scope="session", autouse=True) +def _standard_os_environ(): + """Set up ``os.environ`` at the start of the test session to have + standard values. Returns a list of operations that is used by + :func:`._reset_os_environ` after each test. + """ + mp = monkeypatch.MonkeyPatch() + out = ( + (os.environ, "FLASK_ENV_FILE", monkeypatch.notset), + (os.environ, "FLASK_APP", monkeypatch.notset), + (os.environ, "FLASK_DEBUG", monkeypatch.notset), + (os.environ, "FLASK_RUN_FROM_CLI", monkeypatch.notset), + (os.environ, "WERKZEUG_RUN_MAIN", monkeypatch.notset), + ) + + for _, key, value in out: + if value is monkeypatch.notset: + mp.delenv(key, False) + else: + mp.setenv(key, value) + + yield out + mp.undo() + + +@pytest.fixture(autouse=True) +def _reset_os_environ(monkeypatch, _standard_os_environ): + """Reset ``os.environ`` to the standard environ after each test, + in case a test changed something without cleaning up. + """ + monkeypatch._setitem.extend(_standard_os_environ) + + +@pytest.fixture +def app(): + app = Flask("flask_test", root_path=os.path.dirname(__file__)) + app.config.update( + TESTING=True, + SECRET_KEY="test key", + ) + return app + + +@pytest.fixture +def app_ctx(app): + with app.app_context() as ctx: + yield ctx + + +@pytest.fixture +def req_ctx(app): + with app.test_request_context() as ctx: + yield ctx + + +@pytest.fixture +def client(app): + return app.test_client() + + +@pytest.fixture +def test_apps(monkeypatch): + monkeypatch.syspath_prepend(os.path.join(os.path.dirname(__file__), "test_apps")) + original_modules = set(sys.modules.keys()) + + yield + + # Remove any imports cached during the test. Otherwise "import app" + # will work in the next test even though it's no longer on the path. + for key in sys.modules.keys() - original_modules: + sys.modules.pop(key) + + +@pytest.fixture(autouse=True) +def leak_detector(): + """Fails if any app contexts are still pushed when a test ends. Pops all + contexts so subsequent tests are not affected. + """ + yield + leaks = [] + + while _app_ctx: + leaks.append(_app_ctx._get_current_object()) + _app_ctx.pop() + + assert not leaks + + +@pytest.fixture +def modules_tmp_path(tmp_path, monkeypatch): + """A temporary directory added to sys.path.""" + rv = tmp_path / "modules_tmp" + rv.mkdir() + monkeypatch.syspath_prepend(os.fspath(rv)) + return rv + + +@pytest.fixture +def modules_tmp_path_prefix(modules_tmp_path, monkeypatch): + monkeypatch.setattr(sys, "prefix", os.fspath(modules_tmp_path)) + return modules_tmp_path + + +@pytest.fixture +def site_packages(modules_tmp_path, monkeypatch): + """Create a fake site-packages.""" + py_dir = f"python{sys.version_info.major}.{sys.version_info.minor}" + rv = modules_tmp_path / "lib" / py_dir / "site-packages" + rv.mkdir(parents=True) + monkeypatch.syspath_prepend(os.fspath(rv)) + return rv + + +@pytest.fixture +def purge_module(request): + def inner(name): + request.addfinalizer(lambda: sys.modules.pop(name, None)) + + return inner diff --git a/src/flask-main/tests/static/config.json b/src/flask-main/tests/static/config.json new file mode 100644 index 0000000..4eedab1 --- /dev/null +++ b/src/flask-main/tests/static/config.json @@ -0,0 +1,4 @@ +{ + "TEST_KEY": "foo", + "SECRET_KEY": "config" +} diff --git a/src/flask-main/tests/static/config.toml b/src/flask-main/tests/static/config.toml new file mode 100644 index 0000000..64acdbd --- /dev/null +++ b/src/flask-main/tests/static/config.toml @@ -0,0 +1,2 @@ +TEST_KEY="foo" +SECRET_KEY="config" diff --git a/src/flask-main/tests/static/index.html b/src/flask-main/tests/static/index.html new file mode 100644 index 0000000..de8b69b --- /dev/null +++ b/src/flask-main/tests/static/index.html @@ -0,0 +1 @@ +

Hello World!

diff --git a/src/flask-main/tests/templates/_macro.html b/src/flask-main/tests/templates/_macro.html new file mode 100644 index 0000000..3460ae2 --- /dev/null +++ b/src/flask-main/tests/templates/_macro.html @@ -0,0 +1 @@ +{% macro hello(name) %}Hello {{ name }}!{% endmacro %} diff --git a/src/flask-main/tests/templates/context_template.html b/src/flask-main/tests/templates/context_template.html new file mode 100644 index 0000000..fadf3e5 --- /dev/null +++ b/src/flask-main/tests/templates/context_template.html @@ -0,0 +1 @@ +

{{ value }}|{{ injected_value }} diff --git a/src/flask-main/tests/templates/escaping_template.html b/src/flask-main/tests/templates/escaping_template.html new file mode 100644 index 0000000..dc47644 --- /dev/null +++ b/src/flask-main/tests/templates/escaping_template.html @@ -0,0 +1,6 @@ +{{ text }} +{{ html }} +{% autoescape false %}{{ text }} +{{ html }}{% endautoescape %} +{% autoescape true %}{{ text }} +{{ html }}{% endautoescape %} diff --git a/src/flask-main/tests/templates/mail.txt b/src/flask-main/tests/templates/mail.txt new file mode 100644 index 0000000..d6cb92e --- /dev/null +++ b/src/flask-main/tests/templates/mail.txt @@ -0,0 +1 @@ +{{ foo}} Mail diff --git a/src/flask-main/tests/templates/nested/nested.txt b/src/flask-main/tests/templates/nested/nested.txt new file mode 100644 index 0000000..2c8634f --- /dev/null +++ b/src/flask-main/tests/templates/nested/nested.txt @@ -0,0 +1 @@ +I'm nested diff --git a/src/flask-main/tests/templates/non_escaping_template.txt b/src/flask-main/tests/templates/non_escaping_template.txt new file mode 100644 index 0000000..542864e --- /dev/null +++ b/src/flask-main/tests/templates/non_escaping_template.txt @@ -0,0 +1,8 @@ +{{ text }} +{{ html }} +{% autoescape false %}{{ text }} +{{ html }}{% endautoescape %} +{% autoescape true %}{{ text }} +{{ html }}{% endautoescape %} +{{ text }} +{{ html }} diff --git a/src/flask-main/tests/templates/simple_template.html b/src/flask-main/tests/templates/simple_template.html new file mode 100644 index 0000000..c24612c --- /dev/null +++ b/src/flask-main/tests/templates/simple_template.html @@ -0,0 +1 @@ +

{{ whiskey }}

diff --git a/src/flask-main/tests/templates/template_filter.html b/src/flask-main/tests/templates/template_filter.html new file mode 100644 index 0000000..d51506a --- /dev/null +++ b/src/flask-main/tests/templates/template_filter.html @@ -0,0 +1 @@ +{{ value|super_reverse }} diff --git a/src/flask-main/tests/templates/template_test.html b/src/flask-main/tests/templates/template_test.html new file mode 100644 index 0000000..92d5561 --- /dev/null +++ b/src/flask-main/tests/templates/template_test.html @@ -0,0 +1,3 @@ +{% if value is boolean %} + Success! +{% endif %} diff --git a/src/flask-main/tests/test_appctx.py b/src/flask-main/tests/test_appctx.py new file mode 100644 index 0000000..a537b05 --- /dev/null +++ b/src/flask-main/tests/test_appctx.py @@ -0,0 +1,210 @@ +import pytest + +import flask +from flask.globals import app_ctx + + +def test_basic_url_generation(app): + app.config["SERVER_NAME"] = "localhost" + app.config["PREFERRED_URL_SCHEME"] = "https" + + @app.route("/") + def index(): + pass + + with app.app_context(): + rv = flask.url_for("index") + assert rv == "https://localhost/" + + +def test_url_generation_requires_server_name(app): + with app.app_context(): + with pytest.raises(RuntimeError): + flask.url_for("index") + + +def test_url_generation_without_context_fails(): + with pytest.raises(RuntimeError): + flask.url_for("index") + + +def test_request_context_means_app_context(app): + with app.test_request_context(): + assert flask.current_app._get_current_object() is app + assert not flask.current_app + + +def test_app_context_provides_current_app(app): + with app.app_context(): + assert flask.current_app._get_current_object() is app + assert not flask.current_app + + +def test_app_tearing_down(app): + cleanup_stuff = [] + + @app.teardown_appcontext + def cleanup(exception): + cleanup_stuff.append(exception) + + with app.app_context(): + pass + + assert cleanup_stuff == [None] + + +def test_app_tearing_down_with_previous_exception(app): + cleanup_stuff = [] + + @app.teardown_appcontext + def cleanup(exception): + cleanup_stuff.append(exception) + + try: + raise Exception("dummy") + except Exception: + pass + + with app.app_context(): + pass + + assert cleanup_stuff == [None] + + +def test_app_tearing_down_with_handled_exception_by_except_block(app): + cleanup_stuff = [] + + @app.teardown_appcontext + def cleanup(exception): + cleanup_stuff.append(exception) + + with app.app_context(): + try: + raise Exception("dummy") + except Exception: + pass + + assert cleanup_stuff == [None] + + +def test_app_tearing_down_with_handled_exception_by_app_handler(app, client): + app.config["PROPAGATE_EXCEPTIONS"] = True + cleanup_stuff = [] + + @app.teardown_appcontext + def cleanup(exception): + cleanup_stuff.append(exception) + + @app.route("/") + def index(): + raise Exception("dummy") + + @app.errorhandler(Exception) + def handler(f): + return flask.jsonify(str(f)) + + with app.app_context(): + client.get("/") + + # teardown request context, and with block context + assert cleanup_stuff == [None, None] + + +def test_app_tearing_down_with_unhandled_exception(app, client): + app.config["PROPAGATE_EXCEPTIONS"] = True + cleanup_stuff = [] + + @app.teardown_appcontext + def cleanup(exception): + cleanup_stuff.append(exception) + + @app.route("/") + def index(): + raise ValueError("dummy") + + with pytest.raises(ValueError, match="dummy"): + with app.app_context(): + client.get("/") + + assert len(cleanup_stuff) == 2 + assert isinstance(cleanup_stuff[0], ValueError) + assert str(cleanup_stuff[0]) == "dummy" + # exception propagated, seen by request context and with block context + assert cleanup_stuff[0] is cleanup_stuff[1] + + +def test_app_ctx_globals_methods(app, app_ctx): + # get + assert flask.g.get("foo") is None + assert flask.g.get("foo", "bar") == "bar" + # __contains__ + assert "foo" not in flask.g + flask.g.foo = "bar" + assert "foo" in flask.g + # setdefault + flask.g.setdefault("bar", "the cake is a lie") + flask.g.setdefault("bar", "hello world") + assert flask.g.bar == "the cake is a lie" + # pop + assert flask.g.pop("bar") == "the cake is a lie" + with pytest.raises(KeyError): + flask.g.pop("bar") + assert flask.g.pop("bar", "more cake") == "more cake" + # __iter__ + assert list(flask.g) == ["foo"] + # __repr__ + assert repr(flask.g) == "" + + +def test_custom_app_ctx_globals_class(app): + class CustomRequestGlobals: + def __init__(self): + self.spam = "eggs" + + app.app_ctx_globals_class = CustomRequestGlobals + with app.app_context(): + assert flask.render_template_string("{{ g.spam }}") == "eggs" + + +def test_context_refcounts(app, client): + called = [] + + @app.teardown_request + def teardown_req(error=None): + called.append("request") + + @app.teardown_appcontext + def teardown_app(error=None): + called.append("app") + + @app.route("/") + def index(): + with app_ctx: + pass + + assert flask.request.environ["werkzeug.request"] is not None + return "" + + res = client.get("/") + assert res.status_code == 200 + assert res.data == b"" + assert called == ["request", "app"] + + +def test_clean_pop(app): + app.testing = False + called = [] + + @app.teardown_request + def teardown_req(error=None): + raise ZeroDivisionError + + @app.teardown_appcontext + def teardown_app(error=None): + called.append("TEARDOWN") + + with app.app_context(): + called.append(flask.current_app.name) + + assert called == ["flask_test", "TEARDOWN"] + assert not flask.current_app diff --git a/src/flask-main/tests/test_apps/.env b/src/flask-main/tests/test_apps/.env new file mode 100644 index 0000000..0890b61 --- /dev/null +++ b/src/flask-main/tests/test_apps/.env @@ -0,0 +1,4 @@ +FOO=env +SPAM=1 +EGGS=2 +HAM=火腿 diff --git a/src/flask-main/tests/test_apps/.flaskenv b/src/flask-main/tests/test_apps/.flaskenv new file mode 100644 index 0000000..59f96af --- /dev/null +++ b/src/flask-main/tests/test_apps/.flaskenv @@ -0,0 +1,3 @@ +FOO=flaskenv +BAR=bar +EGGS=0 diff --git a/src/flask-main/tests/test_apps/blueprintapp/__init__.py b/src/flask-main/tests/test_apps/blueprintapp/__init__.py new file mode 100644 index 0000000..ad594cf --- /dev/null +++ b/src/flask-main/tests/test_apps/blueprintapp/__init__.py @@ -0,0 +1,9 @@ +from flask import Flask + +app = Flask(__name__) +app.config["DEBUG"] = True +from blueprintapp.apps.admin import admin # noqa: E402 +from blueprintapp.apps.frontend import frontend # noqa: E402 + +app.register_blueprint(admin) +app.register_blueprint(frontend) diff --git a/src/flask-main/tests/test_apps/blueprintapp/apps/__init__.py b/src/flask-main/tests/test_apps/blueprintapp/apps/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/flask-main/tests/test_apps/blueprintapp/apps/admin/__init__.py b/src/flask-main/tests/test_apps/blueprintapp/apps/admin/__init__.py new file mode 100644 index 0000000..b197fad --- /dev/null +++ b/src/flask-main/tests/test_apps/blueprintapp/apps/admin/__init__.py @@ -0,0 +1,20 @@ +from flask import Blueprint +from flask import render_template + +admin = Blueprint( + "admin", + __name__, + url_prefix="/admin", + template_folder="templates", + static_folder="static", +) + + +@admin.route("/") +def index(): + return render_template("admin/index.html") + + +@admin.route("/index2") +def index2(): + return render_template("./admin/index.html") diff --git a/src/flask-main/tests/test_apps/blueprintapp/apps/admin/static/css/test.css b/src/flask-main/tests/test_apps/blueprintapp/apps/admin/static/css/test.css new file mode 100644 index 0000000..b9f564d --- /dev/null +++ b/src/flask-main/tests/test_apps/blueprintapp/apps/admin/static/css/test.css @@ -0,0 +1 @@ +/* nested file */ diff --git a/src/flask-main/tests/test_apps/blueprintapp/apps/admin/static/test.txt b/src/flask-main/tests/test_apps/blueprintapp/apps/admin/static/test.txt new file mode 100644 index 0000000..f220d22 --- /dev/null +++ b/src/flask-main/tests/test_apps/blueprintapp/apps/admin/static/test.txt @@ -0,0 +1 @@ +Admin File diff --git a/src/flask-main/tests/test_apps/blueprintapp/apps/admin/templates/admin/index.html b/src/flask-main/tests/test_apps/blueprintapp/apps/admin/templates/admin/index.html new file mode 100644 index 0000000..eeec199 --- /dev/null +++ b/src/flask-main/tests/test_apps/blueprintapp/apps/admin/templates/admin/index.html @@ -0,0 +1 @@ +Hello from the Admin diff --git a/src/flask-main/tests/test_apps/blueprintapp/apps/frontend/__init__.py b/src/flask-main/tests/test_apps/blueprintapp/apps/frontend/__init__.py new file mode 100644 index 0000000..7cc5cd8 --- /dev/null +++ b/src/flask-main/tests/test_apps/blueprintapp/apps/frontend/__init__.py @@ -0,0 +1,14 @@ +from flask import Blueprint +from flask import render_template + +frontend = Blueprint("frontend", __name__, template_folder="templates") + + +@frontend.route("/") +def index(): + return render_template("frontend/index.html") + + +@frontend.route("/missing") +def missing_template(): + return render_template("missing_template.html") diff --git a/src/flask-main/tests/test_apps/blueprintapp/apps/frontend/templates/frontend/index.html b/src/flask-main/tests/test_apps/blueprintapp/apps/frontend/templates/frontend/index.html new file mode 100644 index 0000000..a062d71 --- /dev/null +++ b/src/flask-main/tests/test_apps/blueprintapp/apps/frontend/templates/frontend/index.html @@ -0,0 +1 @@ +Hello from the Frontend diff --git a/src/flask-main/tests/test_apps/cliapp/__init__.py b/src/flask-main/tests/test_apps/cliapp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/flask-main/tests/test_apps/cliapp/app.py b/src/flask-main/tests/test_apps/cliapp/app.py new file mode 100644 index 0000000..017ce28 --- /dev/null +++ b/src/flask-main/tests/test_apps/cliapp/app.py @@ -0,0 +1,3 @@ +from flask import Flask + +testapp = Flask("testapp") diff --git a/src/flask-main/tests/test_apps/cliapp/factory.py b/src/flask-main/tests/test_apps/cliapp/factory.py new file mode 100644 index 0000000..1d27396 --- /dev/null +++ b/src/flask-main/tests/test_apps/cliapp/factory.py @@ -0,0 +1,13 @@ +from flask import Flask + + +def create_app(): + return Flask("app") + + +def create_app2(foo, bar): + return Flask("_".join(["app2", foo, bar])) + + +def no_app(): + pass diff --git a/src/flask-main/tests/test_apps/cliapp/importerrorapp.py b/src/flask-main/tests/test_apps/cliapp/importerrorapp.py new file mode 100644 index 0000000..2c96c9b --- /dev/null +++ b/src/flask-main/tests/test_apps/cliapp/importerrorapp.py @@ -0,0 +1,5 @@ +from flask import Flask + +raise ImportError() + +testapp = Flask("testapp") diff --git a/src/flask-main/tests/test_apps/cliapp/inner1/__init__.py b/src/flask-main/tests/test_apps/cliapp/inner1/__init__.py new file mode 100644 index 0000000..8330f6e --- /dev/null +++ b/src/flask-main/tests/test_apps/cliapp/inner1/__init__.py @@ -0,0 +1,3 @@ +from flask import Flask + +application = Flask(__name__) diff --git a/src/flask-main/tests/test_apps/cliapp/inner1/inner2/__init__.py b/src/flask-main/tests/test_apps/cliapp/inner1/inner2/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/flask-main/tests/test_apps/cliapp/inner1/inner2/flask.py b/src/flask-main/tests/test_apps/cliapp/inner1/inner2/flask.py new file mode 100644 index 0000000..d7562aa --- /dev/null +++ b/src/flask-main/tests/test_apps/cliapp/inner1/inner2/flask.py @@ -0,0 +1,3 @@ +from flask import Flask + +app = Flask(__name__) diff --git a/src/flask-main/tests/test_apps/cliapp/message.txt b/src/flask-main/tests/test_apps/cliapp/message.txt new file mode 100644 index 0000000..fc2b2cf --- /dev/null +++ b/src/flask-main/tests/test_apps/cliapp/message.txt @@ -0,0 +1 @@ +So long, and thanks for all the fish. diff --git a/src/flask-main/tests/test_apps/cliapp/multiapp.py b/src/flask-main/tests/test_apps/cliapp/multiapp.py new file mode 100644 index 0000000..4ed0f32 --- /dev/null +++ b/src/flask-main/tests/test_apps/cliapp/multiapp.py @@ -0,0 +1,4 @@ +from flask import Flask + +app1 = Flask("app1") +app2 = Flask("app2") diff --git a/src/flask-main/tests/test_apps/helloworld/hello.py b/src/flask-main/tests/test_apps/helloworld/hello.py new file mode 100644 index 0000000..71a2f90 --- /dev/null +++ b/src/flask-main/tests/test_apps/helloworld/hello.py @@ -0,0 +1,8 @@ +from flask import Flask + +app = Flask(__name__) + + +@app.route("/") +def hello(): + return "Hello World!" diff --git a/src/flask-main/tests/test_apps/helloworld/wsgi.py b/src/flask-main/tests/test_apps/helloworld/wsgi.py new file mode 100644 index 0000000..ab2d6e9 --- /dev/null +++ b/src/flask-main/tests/test_apps/helloworld/wsgi.py @@ -0,0 +1 @@ +from hello import app # noqa: F401 diff --git a/src/flask-main/tests/test_apps/subdomaintestmodule/__init__.py b/src/flask-main/tests/test_apps/subdomaintestmodule/__init__.py new file mode 100644 index 0000000..b4ce4b1 --- /dev/null +++ b/src/flask-main/tests/test_apps/subdomaintestmodule/__init__.py @@ -0,0 +1,3 @@ +from flask import Module + +mod = Module(__name__, "foo", subdomain="foo") diff --git a/src/flask-main/tests/test_apps/subdomaintestmodule/static/hello.txt b/src/flask-main/tests/test_apps/subdomaintestmodule/static/hello.txt new file mode 100644 index 0000000..12e23c1 --- /dev/null +++ b/src/flask-main/tests/test_apps/subdomaintestmodule/static/hello.txt @@ -0,0 +1 @@ +Hello Subdomain diff --git a/src/flask-main/tests/test_async.py b/src/flask-main/tests/test_async.py new file mode 100644 index 0000000..f52b049 --- /dev/null +++ b/src/flask-main/tests/test_async.py @@ -0,0 +1,145 @@ +import asyncio + +import pytest + +from flask import Blueprint +from flask import Flask +from flask import request +from flask.views import MethodView +from flask.views import View + +pytest.importorskip("asgiref") + + +class AppError(Exception): + pass + + +class BlueprintError(Exception): + pass + + +class AsyncView(View): + methods = ["GET", "POST"] + + async def dispatch_request(self): + await asyncio.sleep(0) + return request.method + + +class AsyncMethodView(MethodView): + async def get(self): + await asyncio.sleep(0) + return "GET" + + async def post(self): + await asyncio.sleep(0) + return "POST" + + +@pytest.fixture(name="async_app") +def _async_app(): + app = Flask(__name__) + + @app.route("/", methods=["GET", "POST"]) + @app.route("/home", methods=["GET", "POST"]) + async def index(): + await asyncio.sleep(0) + return request.method + + @app.errorhandler(AppError) + async def handle(_): + return "", 412 + + @app.route("/error") + async def error(): + raise AppError() + + blueprint = Blueprint("bp", __name__) + + @blueprint.route("/", methods=["GET", "POST"]) + async def bp_index(): + await asyncio.sleep(0) + return request.method + + @blueprint.errorhandler(BlueprintError) + async def bp_handle(_): + return "", 412 + + @blueprint.route("/error") + async def bp_error(): + raise BlueprintError() + + app.register_blueprint(blueprint, url_prefix="/bp") + + app.add_url_rule("/view", view_func=AsyncView.as_view("view")) + app.add_url_rule("/methodview", view_func=AsyncMethodView.as_view("methodview")) + + return app + + +@pytest.mark.parametrize("path", ["/", "/home", "/bp/", "/view", "/methodview"]) +def test_async_route(path, async_app): + test_client = async_app.test_client() + response = test_client.get(path) + assert b"GET" in response.get_data() + response = test_client.post(path) + assert b"POST" in response.get_data() + + +@pytest.mark.parametrize("path", ["/error", "/bp/error"]) +def test_async_error_handler(path, async_app): + test_client = async_app.test_client() + response = test_client.get(path) + assert response.status_code == 412 + + +def test_async_before_after_request(): + app_before_called = False + app_after_called = False + bp_before_called = False + bp_after_called = False + + app = Flask(__name__) + + @app.route("/") + def index(): + return "" + + @app.before_request + async def before(): + nonlocal app_before_called + app_before_called = True + + @app.after_request + async def after(response): + nonlocal app_after_called + app_after_called = True + return response + + blueprint = Blueprint("bp", __name__) + + @blueprint.route("/") + def bp_index(): + return "" + + @blueprint.before_request + async def bp_before(): + nonlocal bp_before_called + bp_before_called = True + + @blueprint.after_request + async def bp_after(response): + nonlocal bp_after_called + bp_after_called = True + return response + + app.register_blueprint(blueprint, url_prefix="/bp") + + test_client = app.test_client() + test_client.get("/") + assert app_before_called + assert app_after_called + test_client.get("/bp/") + assert bp_before_called + assert bp_after_called diff --git a/src/flask-main/tests/test_basic.py b/src/flask-main/tests/test_basic.py new file mode 100644 index 0000000..c372a91 --- /dev/null +++ b/src/flask-main/tests/test_basic.py @@ -0,0 +1,1944 @@ +import gc +import re +import typing as t +import uuid +import warnings +import weakref +from contextlib import nullcontext +from datetime import datetime +from datetime import timezone +from platform import python_implementation + +import pytest +import werkzeug.serving +from markupsafe import Markup +from werkzeug.exceptions import BadRequest +from werkzeug.exceptions import Forbidden +from werkzeug.exceptions import NotFound +from werkzeug.http import parse_date +from werkzeug.routing import BuildError +from werkzeug.routing import RequestRedirect + +import flask + +require_cpython_gc = pytest.mark.skipif( + python_implementation() != "CPython", + reason="Requires CPython GC behavior", +) + + +def test_options_work(app, client): + @app.route("/", methods=["GET", "POST"]) + def index(): + return "Hello World" + + rv = client.open("/", method="OPTIONS") + assert sorted(rv.allow) == ["GET", "HEAD", "OPTIONS", "POST"] + assert rv.data == b"" + + +def test_options_on_multiple_rules(app, client): + @app.route("/", methods=["GET", "POST"]) + def index(): + return "Hello World" + + @app.route("/", methods=["PUT"]) + def index_put(): + return "Aha!" + + rv = client.open("/", method="OPTIONS") + assert sorted(rv.allow) == ["GET", "HEAD", "OPTIONS", "POST", "PUT"] + + +@pytest.mark.parametrize("method", ["get", "post", "put", "delete", "patch"]) +def test_method_route(app, client, method): + method_route = getattr(app, method) + client_method = getattr(client, method) + + @method_route("/") + def hello(): + return "Hello" + + assert client_method("/").data == b"Hello" + + +def test_method_route_no_methods(app): + with pytest.raises(TypeError): + app.get("/", methods=["GET", "POST"]) + + +def test_provide_automatic_options_attr(): + app = flask.Flask(__name__) + + def index(): + return "Hello World!" + + index.provide_automatic_options = False + app.route("/")(index) + rv = app.test_client().open("/", method="OPTIONS") + assert rv.status_code == 405 + + app = flask.Flask(__name__) + + def index2(): + return "Hello World!" + + index2.provide_automatic_options = True + app.route("/", methods=["OPTIONS"])(index2) + rv = app.test_client().open("/", method="OPTIONS") + assert sorted(rv.allow) == ["OPTIONS"] + + +def test_provide_automatic_options_kwarg(app, client): + def index(): + return flask.request.method + + def more(): + return flask.request.method + + app.add_url_rule("/", view_func=index, provide_automatic_options=False) + app.add_url_rule( + "/more", + view_func=more, + methods=["GET", "POST"], + provide_automatic_options=False, + ) + assert client.get("/").data == b"GET" + + rv = client.post("/") + assert rv.status_code == 405 + assert sorted(rv.allow) == ["GET", "HEAD"] + + rv = client.open("/", method="OPTIONS") + assert rv.status_code == 405 + + rv = client.head("/") + assert rv.status_code == 200 + assert not rv.data # head truncates + assert client.post("/more").data == b"POST" + assert client.get("/more").data == b"GET" + + rv = client.delete("/more") + assert rv.status_code == 405 + assert sorted(rv.allow) == ["GET", "HEAD", "POST"] + + rv = client.open("/more", method="OPTIONS") + assert rv.status_code == 405 + + +def test_request_dispatching(app, client): + @app.route("/") + def index(): + return flask.request.method + + @app.route("/more", methods=["GET", "POST"]) + def more(): + return flask.request.method + + assert client.get("/").data == b"GET" + rv = client.post("/") + assert rv.status_code == 405 + assert sorted(rv.allow) == ["GET", "HEAD", "OPTIONS"] + rv = client.head("/") + assert rv.status_code == 200 + assert not rv.data # head truncates + assert client.post("/more").data == b"POST" + assert client.get("/more").data == b"GET" + rv = client.delete("/more") + assert rv.status_code == 405 + assert sorted(rv.allow) == ["GET", "HEAD", "OPTIONS", "POST"] + + +def test_disallow_string_for_allowed_methods(app): + with pytest.raises(TypeError): + app.add_url_rule("/", methods="GET POST", endpoint="test") + + +def test_url_mapping(app, client): + random_uuid4 = "7eb41166-9ebf-4d26-b771-ea3f54f8b383" + + def index(): + return flask.request.method + + def more(): + return flask.request.method + + def options(): + return random_uuid4 + + app.add_url_rule("/", "index", index) + app.add_url_rule("/more", "more", more, methods=["GET", "POST"]) + + # Issue 1288: Test that automatic options are not added + # when non-uppercase 'options' in methods + app.add_url_rule("/options", "options", options, methods=["options"]) + + assert client.get("/").data == b"GET" + rv = client.post("/") + assert rv.status_code == 405 + assert sorted(rv.allow) == ["GET", "HEAD", "OPTIONS"] + rv = client.head("/") + assert rv.status_code == 200 + assert not rv.data # head truncates + assert client.post("/more").data == b"POST" + assert client.get("/more").data == b"GET" + rv = client.delete("/more") + assert rv.status_code == 405 + assert sorted(rv.allow) == ["GET", "HEAD", "OPTIONS", "POST"] + rv = client.open("/options", method="OPTIONS") + assert rv.status_code == 200 + assert random_uuid4 in rv.data.decode("utf-8") + + +def test_werkzeug_routing(app, client): + from werkzeug.routing import Rule + from werkzeug.routing import Submount + + app.url_map.add( + Submount("/foo", [Rule("/bar", endpoint="bar"), Rule("/", endpoint="index")]) + ) + + def bar(): + return "bar" + + def index(): + return "index" + + app.view_functions["bar"] = bar + app.view_functions["index"] = index + + assert client.get("/foo/").data == b"index" + assert client.get("/foo/bar").data == b"bar" + + +def test_endpoint_decorator(app, client): + from werkzeug.routing import Rule + from werkzeug.routing import Submount + + app.url_map.add( + Submount("/foo", [Rule("/bar", endpoint="bar"), Rule("/", endpoint="index")]) + ) + + @app.endpoint("bar") + def bar(): + return "bar" + + @app.endpoint("index") + def index(): + return "index" + + assert client.get("/foo/").data == b"index" + assert client.get("/foo/bar").data == b"bar" + + +def test_session(app, client): + @app.route("/set", methods=["POST"]) + def set(): + assert not flask.session.accessed + assert not flask.session.modified + flask.session["value"] = flask.request.form["value"] + assert flask.session.accessed + assert flask.session.modified + return "value set" + + @app.route("/get") + def get(): + assert not flask.session.accessed + assert not flask.session.modified + v = flask.session.get("value", "None") + assert flask.session.accessed + assert not flask.session.modified + return v + + assert client.post("/set", data={"value": "42"}).data == b"value set" + assert client.get("/get").data == b"42" + + +def test_session_path(app, client): + app.config.update(APPLICATION_ROOT="/foo") + + @app.route("/") + def index(): + flask.session["testing"] = 42 + return "Hello World" + + rv = client.get("/", "http://example.com:8080/foo") + assert "path=/foo" in rv.headers["set-cookie"].lower() + + +def test_session_using_application_root(app, client): + class PrefixPathMiddleware: + def __init__(self, app, prefix): + self.app = app + self.prefix = prefix + + def __call__(self, environ, start_response): + environ["SCRIPT_NAME"] = self.prefix + return self.app(environ, start_response) + + app.wsgi_app = PrefixPathMiddleware(app.wsgi_app, "/bar") + app.config.update(APPLICATION_ROOT="/bar") + + @app.route("/") + def index(): + flask.session["testing"] = 42 + return "Hello World" + + rv = client.get("/", "http://example.com:8080/") + assert "path=/bar" in rv.headers["set-cookie"].lower() + + +def test_session_using_session_settings(app, client): + app.config.update( + SERVER_NAME="www.example.com:8080", + APPLICATION_ROOT="/test", + SESSION_COOKIE_DOMAIN=".example.com", + SESSION_COOKIE_HTTPONLY=False, + SESSION_COOKIE_SECURE=True, + SESSION_COOKIE_PARTITIONED=True, + SESSION_COOKIE_SAMESITE="Lax", + SESSION_COOKIE_PATH="/", + ) + + @app.route("/") + def index(): + flask.session["testing"] = 42 + return "Hello World" + + @app.route("/clear") + def clear(): + flask.session.pop("testing", None) + return "Goodbye World" + + rv = client.get("/", "http://www.example.com:8080/test/") + cookie = rv.headers["set-cookie"].lower() + # or condition for Werkzeug < 2.3 + assert "domain=example.com" in cookie or "domain=.example.com" in cookie + assert "path=/" in cookie + assert "secure" in cookie + assert "httponly" not in cookie + assert "samesite" in cookie + assert "partitioned" in cookie + + rv = client.get("/clear", "http://www.example.com:8080/test/") + cookie = rv.headers["set-cookie"].lower() + assert "session=;" in cookie + # or condition for Werkzeug < 2.3 + assert "domain=example.com" in cookie or "domain=.example.com" in cookie + assert "path=/" in cookie + assert "secure" in cookie + assert "samesite" in cookie + assert "partitioned" in cookie + + +def test_session_using_samesite_attribute(app, client): + @app.route("/") + def index(): + flask.session["testing"] = 42 + return "Hello World" + + app.config.update(SESSION_COOKIE_SAMESITE="invalid") + + with pytest.raises(ValueError): + client.get("/") + + app.config.update(SESSION_COOKIE_SAMESITE=None) + rv = client.get("/") + cookie = rv.headers["set-cookie"].lower() + assert "samesite" not in cookie + + app.config.update(SESSION_COOKIE_SAMESITE="Strict") + rv = client.get("/") + cookie = rv.headers["set-cookie"].lower() + assert "samesite=strict" in cookie + + app.config.update(SESSION_COOKIE_SAMESITE="Lax") + rv = client.get("/") + cookie = rv.headers["set-cookie"].lower() + assert "samesite=lax" in cookie + + +def test_missing_session(app): + app.secret_key = None + + def expect_exception(f, *args, **kwargs): + e = pytest.raises(RuntimeError, f, *args, **kwargs) + assert e.value.args and "session is unavailable" in e.value.args[0] + + with app.test_request_context(): + assert flask.session.get("missing_key") is None + expect_exception(flask.session.__setitem__, "foo", 42) + expect_exception(flask.session.pop, "foo") + + +def test_session_secret_key_fallbacks(app, client) -> None: + @app.post("/") + def set_session() -> str: + flask.session["a"] = 1 + return "" + + @app.get("/") + def get_session() -> dict[str, t.Any]: + return dict(flask.session) + + # Set session with initial secret key, and two valid expiring keys + app.secret_key, app.config["SECRET_KEY_FALLBACKS"] = ( + "0 key", + ["-1 key", "-2 key"], + ) + client.post() + assert client.get().json == {"a": 1} + # Change secret key, session can't be loaded and appears empty + app.secret_key = "? key" + assert client.get().json == {} + # Rotate the valid keys, session can be loaded + app.secret_key, app.config["SECRET_KEY_FALLBACKS"] = ( + "+1 key", + ["0 key", "-1 key"], + ) + assert client.get().json == {"a": 1} + + +def test_session_expiration(app, client): + permanent = True + + @app.route("/") + def index(): + flask.session["test"] = 42 + flask.session.permanent = permanent + return "" + + @app.route("/test") + def test(): + return str(flask.session.permanent) + + rv = client.get("/") + assert "set-cookie" in rv.headers + match = re.search(r"(?i)\bexpires=([^;]+)", rv.headers["set-cookie"]) + expires = parse_date(match.group()) + expected = datetime.now(timezone.utc) + app.permanent_session_lifetime + assert expires.year == expected.year + assert expires.month == expected.month + assert expires.day == expected.day + + rv = client.get("/test") + assert rv.data == b"True" + + permanent = False + rv = client.get("/") + assert "set-cookie" in rv.headers + match = re.search(r"\bexpires=([^;]+)", rv.headers["set-cookie"]) + assert match is None + + +def test_session_stored_last(app, client): + @app.after_request + def modify_session(response): + flask.session["foo"] = 42 + return response + + @app.route("/") + def dump_session_contents(): + return repr(flask.session.get("foo")) + + assert client.get("/").data == b"None" + assert client.get("/").data == b"42" + + +def test_session_special_types(app, client): + now = datetime.now(timezone.utc).replace(microsecond=0) + the_uuid = uuid.uuid4() + + @app.route("/") + def dump_session_contents(): + flask.session["t"] = (1, 2, 3) + flask.session["b"] = b"\xff" + flask.session["m"] = Markup("") + flask.session["u"] = the_uuid + flask.session["d"] = now + flask.session["t_tag"] = {" t": "not-a-tuple"} + flask.session["di_t_tag"] = {" t__": "not-a-tuple"} + flask.session["di_tag"] = {" di": "not-a-dict"} + return "", 204 + + with client: + client.get("/") + s = flask.session + assert s["t"] == (1, 2, 3) + assert type(s["b"]) is bytes # noqa: E721 + assert s["b"] == b"\xff" + assert type(s["m"]) is Markup # noqa: E721 + assert s["m"] == Markup("") + assert s["u"] == the_uuid + assert s["d"] == now + assert s["t_tag"] == {" t": "not-a-tuple"} + assert s["di_t_tag"] == {" t__": "not-a-tuple"} + assert s["di_tag"] == {" di": "not-a-dict"} + + +def test_session_cookie_setting(app): + is_permanent = True + + @app.route("/bump") + def bump(): + rv = flask.session["foo"] = flask.session.get("foo", 0) + 1 + flask.session.permanent = is_permanent + return str(rv) + + @app.route("/read") + def read(): + return str(flask.session.get("foo", 0)) + + def run_test(expect_header): + with app.test_client() as c: + assert c.get("/bump").data == b"1" + assert c.get("/bump").data == b"2" + assert c.get("/bump").data == b"3" + + rv = c.get("/read") + set_cookie = rv.headers.get("set-cookie") + assert (set_cookie is not None) == expect_header + assert rv.data == b"3" + + is_permanent = True + app.config["SESSION_REFRESH_EACH_REQUEST"] = True + run_test(expect_header=True) + + is_permanent = True + app.config["SESSION_REFRESH_EACH_REQUEST"] = False + run_test(expect_header=False) + + is_permanent = False + app.config["SESSION_REFRESH_EACH_REQUEST"] = True + run_test(expect_header=False) + + is_permanent = False + app.config["SESSION_REFRESH_EACH_REQUEST"] = False + run_test(expect_header=False) + + +def test_session_vary_cookie(app, client): + @app.route("/set") + def set_session(): + flask.session["test"] = "test" + return "" + + @app.route("/get") + def get(): + return flask.session.get("test") + + @app.route("/getitem") + def getitem(): + return flask.session["test"] + + @app.route("/setdefault") + def setdefault(): + return flask.session.setdefault("test", "default") + + @app.route("/clear") + def clear(): + flask.session.clear() + return "" + + @app.route("/vary-cookie-header-set") + def vary_cookie_header_set(): + response = flask.Response() + response.vary.add("Cookie") + flask.session["test"] = "test" + return response + + @app.route("/vary-header-set") + def vary_header_set(): + response = flask.Response() + response.vary.update(("Accept-Encoding", "Accept-Language")) + flask.session["test"] = "test" + return response + + @app.route("/no-vary-header") + def no_vary_header(): + return "" + + def expect(path, header_value="Cookie"): + rv = client.get(path) + + if header_value: + # The 'Vary' key should exist in the headers only once. + assert len(rv.headers.get_all("Vary")) == 1 + assert rv.headers["Vary"] == header_value + else: + assert "Vary" not in rv.headers + + expect("/set") + expect("/get") + expect("/getitem") + expect("/setdefault") + expect("/clear") + expect("/vary-cookie-header-set") + expect("/vary-header-set", "Accept-Encoding, Accept-Language, Cookie") + expect("/no-vary-header", None) + + +def test_session_refresh_vary(app, client): + @app.get("/login") + def login(): + flask.session["user_id"] = 1 + flask.session.permanent = True + return "" + + @app.get("/ignored") + def ignored(): + return "" + + rv = client.get("/login") + assert rv.headers["Vary"] == "Cookie" + rv = client.get("/ignored") + assert rv.headers["Vary"] == "Cookie" + + +def test_flashes(app, req_ctx): + assert not flask.session.modified + flask.flash("Zap") + flask.session.modified = False + flask.flash("Zip") + assert flask.session.modified + assert list(flask.get_flashed_messages()) == ["Zap", "Zip"] + + +def test_extended_flashing(app): + # Be sure app.testing=True below, else tests can fail silently. + # + # Specifically, if app.testing is not set to True, the AssertionErrors + # in the view functions will cause a 500 response to the test client + # instead of propagating exceptions. + + @app.route("/") + def index(): + flask.flash("Hello World") + flask.flash("Hello World", "error") + flask.flash(Markup("Testing"), "warning") + return "" + + @app.route("/test/") + def test(): + messages = flask.get_flashed_messages() + assert list(messages) == [ + "Hello World", + "Hello World", + Markup("Testing"), + ] + return "" + + @app.route("/test_with_categories/") + def test_with_categories(): + messages = flask.get_flashed_messages(with_categories=True) + assert len(messages) == 3 + assert list(messages) == [ + ("message", "Hello World"), + ("error", "Hello World"), + ("warning", Markup("Testing")), + ] + return "" + + @app.route("/test_filter/") + def test_filter(): + messages = flask.get_flashed_messages( + category_filter=["message"], with_categories=True + ) + assert list(messages) == [("message", "Hello World")] + return "" + + @app.route("/test_filters/") + def test_filters(): + messages = flask.get_flashed_messages( + category_filter=["message", "warning"], with_categories=True + ) + assert list(messages) == [ + ("message", "Hello World"), + ("warning", Markup("Testing")), + ] + return "" + + @app.route("/test_filters_without_returning_categories/") + def test_filters2(): + messages = flask.get_flashed_messages(category_filter=["message", "warning"]) + assert len(messages) == 2 + assert messages[0] == "Hello World" + assert messages[1] == Markup("Testing") + return "" + + # Create new test client on each test to clean flashed messages. + + client = app.test_client() + client.get("/") + client.get("/test_with_categories/") + + client = app.test_client() + client.get("/") + client.get("/test_filter/") + + client = app.test_client() + client.get("/") + client.get("/test_filters/") + + client = app.test_client() + client.get("/") + client.get("/test_filters_without_returning_categories/") + + +def test_request_processing(app, client): + evts = [] + + @app.before_request + def before_request(): + evts.append("before") + + @app.after_request + def after_request(response): + response.data += b"|after" + evts.append("after") + return response + + @app.route("/") + def index(): + assert "before" in evts + assert "after" not in evts + return "request" + + assert "after" not in evts + rv = client.get("/").data + assert "after" in evts + assert rv == b"request|after" + + +def test_request_preprocessing_early_return(app, client): + evts = [] + + @app.before_request + def before_request1(): + evts.append(1) + + @app.before_request + def before_request2(): + evts.append(2) + return "hello" + + @app.before_request + def before_request3(): + evts.append(3) + return "bye" + + @app.route("/") + def index(): + evts.append("index") + return "damnit" + + rv = client.get("/").data.strip() + assert rv == b"hello" + assert evts == [1, 2] + + +def test_after_request_processing(app, client): + @app.route("/") + def index(): + @flask.after_this_request + def foo(response): + response.headers["X-Foo"] = "a header" + return response + + return "Test" + + resp = client.get("/") + assert resp.status_code == 200 + assert resp.headers["X-Foo"] == "a header" + + +def test_teardown_request_handler(app, client): + called = [] + + @app.teardown_request + def teardown_request(exc): + called.append(True) + return "Ignored" + + @app.route("/") + def root(): + return "Response" + + rv = client.get("/") + assert rv.status_code == 200 + assert b"Response" in rv.data + assert len(called) == 1 + + +def test_teardown_request_handler_debug_mode(app, client): + called = [] + + @app.teardown_request + def teardown_request(exc): + called.append(True) + return "Ignored" + + @app.route("/") + def root(): + return "Response" + + rv = client.get("/") + assert rv.status_code == 200 + assert b"Response" in rv.data + assert len(called) == 1 + + +def test_teardown_request_handler_error(app, client): + called = [] + app.testing = False + + @app.teardown_request + def teardown_request1(exc): + assert type(exc) is ZeroDivisionError + called.append(True) + # This raises a new error and blows away sys.exc_info(), so we can + # test that all teardown_requests get passed the same original + # exception. + try: + raise TypeError() + except Exception: + pass + + @app.teardown_request + def teardown_request2(exc): + assert type(exc) is ZeroDivisionError + called.append(True) + # This raises a new error and blows away sys.exc_info(), so we can + # test that all teardown_requests get passed the same original + # exception. + try: + raise TypeError() + except Exception: + pass + + @app.route("/") + def fails(): + raise ZeroDivisionError + + rv = client.get("/") + assert rv.status_code == 500 + assert b"Internal Server Error" in rv.data + assert len(called) == 2 + + +def test_before_after_request_order(app, client): + called = [] + + @app.before_request + def before1(): + called.append(1) + + @app.before_request + def before2(): + called.append(2) + + @app.after_request + def after1(response): + called.append(4) + return response + + @app.after_request + def after2(response): + called.append(3) + return response + + @app.teardown_request + def finish1(exc): + called.append(6) + + @app.teardown_request + def finish2(exc): + called.append(5) + + @app.route("/") + def index(): + return "42" + + rv = client.get("/") + assert rv.data == b"42" + assert called == [1, 2, 3, 4, 5, 6] + + +def test_error_handling(app, client): + app.testing = False + + @app.errorhandler(404) + def not_found(e): + return "not found", 404 + + @app.errorhandler(500) + def internal_server_error(e): + return "internal server error", 500 + + @app.errorhandler(Forbidden) + def forbidden(e): + return "forbidden", 403 + + @app.route("/") + def index(): + flask.abort(404) + + @app.route("/error") + def error(): + raise ZeroDivisionError + + @app.route("/forbidden") + def error2(): + flask.abort(403) + + rv = client.get("/") + assert rv.status_code == 404 + assert rv.data == b"not found" + rv = client.get("/error") + assert rv.status_code == 500 + assert b"internal server error" == rv.data + rv = client.get("/forbidden") + assert rv.status_code == 403 + assert b"forbidden" == rv.data + + +def test_error_handling_processing(app, client): + app.testing = False + + @app.errorhandler(500) + def internal_server_error(e): + return "internal server error", 500 + + @app.route("/") + def broken_func(): + raise ZeroDivisionError + + @app.after_request + def after_request(resp): + resp.mimetype = "text/x-special" + return resp + + resp = client.get("/") + assert resp.mimetype == "text/x-special" + assert resp.data == b"internal server error" + + +def test_baseexception_error_handling(app, client): + app.testing = False + + @app.route("/") + def broken_func(): + raise KeyboardInterrupt() + + with pytest.raises(KeyboardInterrupt): + client.get("/") + + +def test_before_request_and_routing_errors(app, client): + @app.before_request + def attach_something(): + flask.g.something = "value" + + @app.errorhandler(404) + def return_something(error): + return flask.g.something, 404 + + rv = client.get("/") + assert rv.status_code == 404 + assert rv.data == b"value" + + +def test_user_error_handling(app, client): + class MyException(Exception): + pass + + @app.errorhandler(MyException) + def handle_my_exception(e): + assert isinstance(e, MyException) + return "42" + + @app.route("/") + def index(): + raise MyException() + + assert client.get("/").data == b"42" + + +def test_http_error_subclass_handling(app, client): + class ForbiddenSubclass(Forbidden): + pass + + @app.errorhandler(ForbiddenSubclass) + def handle_forbidden_subclass(e): + assert isinstance(e, ForbiddenSubclass) + return "banana" + + @app.errorhandler(403) + def handle_403(e): + assert not isinstance(e, ForbiddenSubclass) + assert isinstance(e, Forbidden) + return "apple" + + @app.route("/1") + def index1(): + raise ForbiddenSubclass() + + @app.route("/2") + def index2(): + flask.abort(403) + + @app.route("/3") + def index3(): + raise Forbidden() + + assert client.get("/1").data == b"banana" + assert client.get("/2").data == b"apple" + assert client.get("/3").data == b"apple" + + +def test_errorhandler_precedence(app, client): + class E1(Exception): + pass + + class E2(Exception): + pass + + class E3(E1, E2): + pass + + @app.errorhandler(E2) + def handle_e2(e): + return "E2" + + @app.errorhandler(Exception) + def handle_exception(e): + return "Exception" + + @app.route("/E1") + def raise_e1(): + raise E1 + + @app.route("/E3") + def raise_e3(): + raise E3 + + rv = client.get("/E1") + assert rv.data == b"Exception" + + rv = client.get("/E3") + assert rv.data == b"E2" + + +@pytest.mark.parametrize( + ("debug", "trap", "expect_key", "expect_abort"), + [(False, None, True, True), (True, None, False, True), (False, True, False, False)], +) +def test_trap_bad_request_key_error(app, client, debug, trap, expect_key, expect_abort): + app.config["DEBUG"] = debug + app.config["TRAP_BAD_REQUEST_ERRORS"] = trap + + @app.route("/key") + def fail(): + flask.request.form["missing_key"] + + @app.route("/abort") + def allow_abort(): + flask.abort(400) + + if expect_key: + rv = client.get("/key") + assert rv.status_code == 400 + assert b"missing_key" not in rv.data + else: + with pytest.raises(KeyError) as exc_info: + client.get("/key") + + assert exc_info.errisinstance(BadRequest) + assert "missing_key" in exc_info.value.get_description() + + if expect_abort: + rv = client.get("/abort") + assert rv.status_code == 400 + else: + with pytest.raises(BadRequest): + client.get("/abort") + + +def test_trapping_of_all_http_exceptions(app, client): + app.config["TRAP_HTTP_EXCEPTIONS"] = True + + @app.route("/fail") + def fail(): + flask.abort(404) + + with pytest.raises(NotFound): + client.get("/fail") + + +def test_error_handler_after_processor_error(app, client): + app.testing = False + + @app.before_request + def before_request(): + if _trigger == "before": + raise ZeroDivisionError + + @app.after_request + def after_request(response): + if _trigger == "after": + raise ZeroDivisionError + + return response + + @app.route("/") + def index(): + return "Foo" + + @app.errorhandler(500) + def internal_server_error(e): + return "Hello Server Error", 500 + + for _trigger in "before", "after": + rv = client.get("/") + assert rv.status_code == 500 + assert rv.data == b"Hello Server Error" + + +def test_enctype_debug_helper(app, client): + from flask.debughelpers import DebugFilesKeyError + + app.debug = True + + @app.route("/fail", methods=["POST"]) + def index(): + return flask.request.files["foo"].filename + + with pytest.raises(DebugFilesKeyError) as e: + client.post("/fail", data={"foo": "index.txt"}) + assert "no file contents were transmitted" in str(e.value) + assert "This was submitted: 'index.txt'" in str(e.value) + + +def test_response_types(app, client): + @app.route("/text") + def from_text(): + return "Hällo Wörld" + + @app.route("/bytes") + def from_bytes(): + return "Hällo Wörld".encode() + + @app.route("/full_tuple") + def from_full_tuple(): + return ( + "Meh", + 400, + {"X-Foo": "Testing", "Content-Type": "text/plain; charset=utf-8"}, + ) + + @app.route("/text_headers") + def from_text_headers(): + return "Hello", {"X-Foo": "Test", "Content-Type": "text/plain; charset=utf-8"} + + @app.route("/text_status") + def from_text_status(): + return "Hi, status!", 400 + + @app.route("/response_headers") + def from_response_headers(): + return ( + flask.Response( + "Hello world", 404, {"Content-Type": "text/html", "X-Foo": "Baz"} + ), + {"Content-Type": "text/plain", "X-Foo": "Bar", "X-Bar": "Foo"}, + ) + + @app.route("/response_status") + def from_response_status(): + return app.response_class("Hello world", 400), 500 + + @app.route("/wsgi") + def from_wsgi(): + return NotFound() + + @app.route("/dict") + def from_dict(): + return {"foo": "bar"}, 201 + + @app.route("/list") + def from_list(): + return ["foo", "bar"], 201 + + assert client.get("/text").data == "Hällo Wörld".encode() + assert client.get("/bytes").data == "Hällo Wörld".encode() + + rv = client.get("/full_tuple") + assert rv.data == b"Meh" + assert rv.headers["X-Foo"] == "Testing" + assert rv.status_code == 400 + assert rv.mimetype == "text/plain" + + rv = client.get("/text_headers") + assert rv.data == b"Hello" + assert rv.headers["X-Foo"] == "Test" + assert rv.status_code == 200 + assert rv.mimetype == "text/plain" + + rv = client.get("/text_status") + assert rv.data == b"Hi, status!" + assert rv.status_code == 400 + assert rv.mimetype == "text/html" + + rv = client.get("/response_headers") + assert rv.data == b"Hello world" + assert rv.content_type == "text/plain" + assert rv.headers.getlist("X-Foo") == ["Bar"] + assert rv.headers["X-Bar"] == "Foo" + assert rv.status_code == 404 + + rv = client.get("/response_status") + assert rv.data == b"Hello world" + assert rv.status_code == 500 + + rv = client.get("/wsgi") + assert b"Not Found" in rv.data + assert rv.status_code == 404 + + rv = client.get("/dict") + assert rv.json == {"foo": "bar"} + assert rv.status_code == 201 + + rv = client.get("/list") + assert rv.json == ["foo", "bar"] + assert rv.status_code == 201 + + +def test_response_type_errors(): + app = flask.Flask(__name__) + app.testing = True + + @app.route("/none") + def from_none(): + pass + + @app.route("/small_tuple") + def from_small_tuple(): + return ("Hello",) + + @app.route("/large_tuple") + def from_large_tuple(): + return "Hello", 234, {"X-Foo": "Bar"}, "???" + + @app.route("/bad_type") + def from_bad_type(): + return True + + @app.route("/bad_wsgi") + def from_bad_wsgi(): + return lambda: None + + c = app.test_client() + + with pytest.raises(TypeError) as e: + c.get("/none") + + assert "returned None" in str(e.value) + assert "from_none" in str(e.value) + + with pytest.raises(TypeError) as e: + c.get("/small_tuple") + + assert "tuple must have the form" in str(e.value) + + with pytest.raises(TypeError): + c.get("/large_tuple") + + with pytest.raises(TypeError) as e: + c.get("/bad_type") + + assert "it was a bool" in str(e.value) + + with pytest.raises(TypeError): + c.get("/bad_wsgi") + + +def test_make_response(app, req_ctx): + rv = flask.make_response() + assert rv.status_code == 200 + assert rv.data == b"" + assert rv.mimetype == "text/html" + + rv = flask.make_response("Awesome") + assert rv.status_code == 200 + assert rv.data == b"Awesome" + assert rv.mimetype == "text/html" + + rv = flask.make_response("W00t", 404) + assert rv.status_code == 404 + assert rv.data == b"W00t" + assert rv.mimetype == "text/html" + + rv = flask.make_response(c for c in "Hello") + assert rv.status_code == 200 + assert rv.data == b"Hello" + assert rv.mimetype == "text/html" + + +def test_make_response_with_response_instance(app, req_ctx): + rv = flask.make_response(flask.jsonify({"msg": "W00t"}), 400) + assert rv.status_code == 400 + assert rv.data == b'{"msg":"W00t"}\n' + assert rv.mimetype == "application/json" + + rv = flask.make_response(flask.Response(""), 400) + assert rv.status_code == 400 + assert rv.data == b"" + assert rv.mimetype == "text/html" + + rv = flask.make_response( + flask.Response("", headers={"Content-Type": "text/html"}), + 400, + [("X-Foo", "bar")], + ) + assert rv.status_code == 400 + assert rv.headers["Content-Type"] == "text/html" + assert rv.headers["X-Foo"] == "bar" + + +@pytest.mark.parametrize("compact", [True, False]) +def test_jsonify_no_prettyprint(app, compact): + app.json.compact = compact + rv = app.json.response({"msg": {"submsg": "W00t"}, "msg2": "foobar"}) + data = rv.data.strip() + assert (b" " not in data) is compact + assert (b"\n" not in data) is compact + + +def test_jsonify_mimetype(app, req_ctx): + app.json.mimetype = "application/vnd.api+json" + msg = {"msg": {"submsg": "W00t"}} + rv = flask.make_response(flask.jsonify(msg), 200) + assert rv.mimetype == "application/vnd.api+json" + + +def test_json_dump_dataclass(app, req_ctx): + from dataclasses import make_dataclass + + Data = make_dataclass("Data", [("name", str)]) + value = app.json.dumps(Data("Flask")) + value = app.json.loads(value) + assert value == {"name": "Flask"} + + +def test_jsonify_args_and_kwargs_check(app, req_ctx): + with pytest.raises(TypeError) as e: + flask.jsonify("fake args", kwargs="fake") + assert "args or kwargs" in str(e.value) + + +def test_url_generation(app, req_ctx): + @app.route("/hello/", methods=["POST"]) + def hello(): + pass + + assert flask.url_for("hello", name="test x") == "/hello/test%20x" + assert ( + flask.url_for("hello", name="test x", _external=True) + == "http://localhost/hello/test%20x" + ) + + +def test_build_error_handler(app): + # Test base case, a URL which results in a BuildError. + with app.test_request_context(): + pytest.raises(BuildError, flask.url_for, "spam") + + # Verify the error is re-raised if not the current exception. + try: + with app.test_request_context(): + flask.url_for("spam") + except BuildError as err: + error = err + try: + raise RuntimeError("Test case where BuildError is not current.") + except RuntimeError: + pytest.raises(BuildError, app.handle_url_build_error, error, "spam", {}) + + # Test a custom handler. + def handler(error, endpoint, values): + # Just a test. + return "/test_handler/" + + app.url_build_error_handlers.append(handler) + with app.test_request_context(): + assert flask.url_for("spam") == "/test_handler/" + + +def test_build_error_handler_reraise(app): + # Test a custom handler which reraises the BuildError + def handler_raises_build_error(error, endpoint, values): + raise error + + app.url_build_error_handlers.append(handler_raises_build_error) + + with app.test_request_context(): + pytest.raises(BuildError, flask.url_for, "not.existing") + + +def test_url_for_passes_special_values_to_build_error_handler(app): + @app.url_build_error_handlers.append + def handler(error, endpoint, values): + assert values == { + "_external": False, + "_anchor": None, + "_method": None, + "_scheme": None, + } + return "handled" + + with app.test_request_context(): + flask.url_for("/") + + +def test_static_files(app, client): + rv = client.get("/static/index.html") + assert rv.status_code == 200 + assert rv.data.strip() == b"

Hello World!

" + with app.test_request_context(): + assert flask.url_for("static", filename="index.html") == "/static/index.html" + rv.close() + + +def test_static_url_path(): + app = flask.Flask(__name__, static_url_path="/foo") + app.testing = True + rv = app.test_client().get("/foo/index.html") + assert rv.status_code == 200 + rv.close() + + with app.test_request_context(): + assert flask.url_for("static", filename="index.html") == "/foo/index.html" + + +def test_static_url_path_with_ending_slash(): + app = flask.Flask(__name__, static_url_path="/foo/") + app.testing = True + rv = app.test_client().get("/foo/index.html") + assert rv.status_code == 200 + rv.close() + + with app.test_request_context(): + assert flask.url_for("static", filename="index.html") == "/foo/index.html" + + +def test_static_url_empty_path(app): + app = flask.Flask(__name__, static_folder="", static_url_path="") + rv = app.test_client().open("/static/index.html", method="GET") + assert rv.status_code == 200 + rv.close() + + +def test_static_url_empty_path_default(app): + app = flask.Flask(__name__, static_folder="") + rv = app.test_client().open("/static/index.html", method="GET") + assert rv.status_code == 200 + rv.close() + + +def test_static_folder_with_pathlib_path(app): + from pathlib import Path + + app = flask.Flask(__name__, static_folder=Path("static")) + rv = app.test_client().open("/static/index.html", method="GET") + assert rv.status_code == 200 + rv.close() + + +def test_static_folder_with_ending_slash(): + app = flask.Flask(__name__, static_folder="static/") + + @app.route("/") + def catch_all(path): + return path + + rv = app.test_client().get("/catch/all") + assert rv.data == b"catch/all" + + +def test_static_route_with_host_matching(): + app = flask.Flask(__name__, host_matching=True, static_host="example.com") + c = app.test_client() + rv = c.get("http://example.com/static/index.html") + assert rv.status_code == 200 + rv.close() + with app.test_request_context(): + rv = flask.url_for("static", filename="index.html", _external=True) + assert rv == "http://example.com/static/index.html" + # Providing static_host without host_matching=True should error. + with pytest.raises(AssertionError): + flask.Flask(__name__, static_host="example.com") + # Providing host_matching=True with static_folder + # but without static_host should error. + with pytest.raises(AssertionError): + flask.Flask(__name__, host_matching=True) + # Providing host_matching=True without static_host + # but with static_folder=None should not error. + flask.Flask(__name__, host_matching=True, static_folder=None) + + +def test_request_locals(): + assert repr(flask.g) == "" + assert not flask.g + + +@pytest.mark.parametrize( + ("subdomain_matching", "host_matching", "expect_base", "expect_abc", "expect_xyz"), + [ + (False, False, "default", "default", "default"), + (True, False, "default", "abc", ""), + (False, True, "default", "abc", "default"), + ], +) +def test_server_name_matching( + subdomain_matching: bool, + host_matching: bool, + expect_base: str, + expect_abc: str, + expect_xyz: str, +) -> None: + app = flask.Flask( + __name__, + subdomain_matching=subdomain_matching, + host_matching=host_matching, + static_host="example.test" if host_matching else None, + ) + app.config["SERVER_NAME"] = "example.test" + + @app.route("/", defaults={"name": "default"}, host="") + @app.route("/", subdomain="", host=".example.test") + def index(name: str) -> str: + return name + + client = app.test_client() + + r = client.get(base_url="http://example.test") + assert r.text == expect_base + + r = client.get(base_url="http://abc.example.test") + assert r.text == expect_abc + + with pytest.warns() if subdomain_matching else nullcontext(): + r = client.get(base_url="http://xyz.other.test") + + assert r.text == expect_xyz + + +def test_server_name_subdomain(): + app = flask.Flask(__name__, subdomain_matching=True) + client = app.test_client() + + @app.route("/") + def index(): + return "default" + + @app.route("/", subdomain="foo") + def subdomain(): + return "subdomain" + + app.config["SERVER_NAME"] = "dev.local:5000" + rv = client.get("/") + assert rv.data == b"default" + + rv = client.get("/", "http://dev.local:5000") + assert rv.data == b"default" + + rv = client.get("/", "https://dev.local:5000") + assert rv.data == b"default" + + app.config["SERVER_NAME"] = "dev.local:443" + rv = client.get("/", "https://dev.local") + + # Werkzeug 1.0 fixes matching https scheme with 443 port + if rv.status_code != 404: + assert rv.data == b"default" + + app.config["SERVER_NAME"] = "dev.local" + rv = client.get("/", "https://dev.local") + assert rv.data == b"default" + + # suppress Werkzeug 0.15 warning about name mismatch + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "Current server name", UserWarning, "flask.app" + ) + rv = client.get("/", "http://foo.localhost") + assert rv.status_code == 404 + + rv = client.get("/", "http://foo.dev.local") + assert rv.data == b"subdomain" + + +@pytest.mark.parametrize("key", ["TESTING", "PROPAGATE_EXCEPTIONS", "DEBUG", None]) +def test_exception_propagation(app, client, key): + app.testing = False + + @app.route("/") + def index(): + raise ZeroDivisionError + + if key is not None: + app.config[key] = True + + with pytest.raises(ZeroDivisionError): + client.get("/") + else: + assert client.get("/").status_code == 500 + + +@pytest.mark.parametrize("debug", [True, False]) +@pytest.mark.parametrize("use_debugger", [True, False]) +@pytest.mark.parametrize("use_reloader", [True, False]) +@pytest.mark.parametrize("propagate_exceptions", [None, True, False]) +def test_werkzeug_passthrough_errors( + monkeypatch, debug, use_debugger, use_reloader, propagate_exceptions, app +): + rv = {} + + # Mocks werkzeug.serving.run_simple method + def run_simple_mock(*args, **kwargs): + rv["passthrough_errors"] = kwargs.get("passthrough_errors") + + monkeypatch.setattr(werkzeug.serving, "run_simple", run_simple_mock) + app.config["PROPAGATE_EXCEPTIONS"] = propagate_exceptions + app.run(debug=debug, use_debugger=use_debugger, use_reloader=use_reloader) + + +def test_url_processors(app, client): + @app.url_defaults + def add_language_code(endpoint, values): + if flask.g.lang_code is not None and app.url_map.is_endpoint_expecting( + endpoint, "lang_code" + ): + values.setdefault("lang_code", flask.g.lang_code) + + @app.url_value_preprocessor + def pull_lang_code(endpoint, values): + flask.g.lang_code = values.pop("lang_code", None) + + @app.route("//") + def index(): + return flask.url_for("about") + + @app.route("//about") + def about(): + return flask.url_for("something_else") + + @app.route("/foo") + def something_else(): + return flask.url_for("about", lang_code="en") + + assert client.get("/de/").data == b"/de/about" + assert client.get("/de/about").data == b"/foo" + assert client.get("/foo").data == b"/en/about" + + +def test_inject_blueprint_url_defaults(app): + bp = flask.Blueprint("foo", __name__, template_folder="template") + + @bp.url_defaults + def bp_defaults(endpoint, values): + values["page"] = "login" + + @bp.route("/") + def view(page): + pass + + app.register_blueprint(bp) + + values = dict() + app.inject_url_defaults("foo.view", values) + expected = dict(page="login") + assert values == expected + + with app.test_request_context("/somepage"): + url = flask.url_for("foo.view") + expected = "/login" + assert url == expected + + +def test_nonascii_pathinfo(app, client): + @app.route("/киртест") + def index(): + return "Hello World!" + + rv = client.get("/киртест") + assert rv.data == b"Hello World!" + + +def test_no_setup_after_first_request(app, client): + app.debug = True + + @app.route("/") + def index(): + return "Awesome" + + assert client.get("/").data == b"Awesome" + + with pytest.raises(AssertionError) as exc_info: + app.add_url_rule("/foo", endpoint="late") + + assert "setup method 'add_url_rule'" in str(exc_info.value) + + +def test_routing_redirect_debugging(monkeypatch, app, client): + app.config["DEBUG"] = True + + @app.route("/user/", methods=["GET", "POST"]) + def user(): + return flask.request.form["status"] + + # default redirect code preserves form data + rv = client.post("/user", data={"status": "success"}, follow_redirects=True) + assert rv.data == b"success" + + # 301 and 302 raise error + monkeypatch.setattr(RequestRedirect, "code", 301) + + with client, pytest.raises(AssertionError) as exc_info: + client.post("/user", data={"status": "error"}, follow_redirects=True) + + assert "canonical URL 'http://localhost/user/'" in str(exc_info.value) + + +def test_route_decorator_custom_endpoint(app, client): + app.debug = True + + @app.route("/foo/") + def foo(): + return flask.request.endpoint + + @app.route("/bar/", endpoint="bar") + def for_bar(): + return flask.request.endpoint + + @app.route("/bar/123", endpoint="123") + def for_bar_foo(): + return flask.request.endpoint + + with app.test_request_context(): + assert flask.url_for("foo") == "/foo/" + assert flask.url_for("bar") == "/bar/" + assert flask.url_for("123") == "/bar/123" + + assert client.get("/foo/").data == b"foo" + assert client.get("/bar/").data == b"bar" + assert client.get("/bar/123").data == b"123" + + +def test_get_method_on_g(app_ctx): + assert flask.g.get("x") is None + assert flask.g.get("x", 11) == 11 + flask.g.x = 42 + assert flask.g.get("x") == 42 + assert flask.g.x == 42 + + +def test_g_iteration_protocol(app_ctx): + flask.g.foo = 23 + flask.g.bar = 42 + assert "foo" in flask.g + assert "foos" not in flask.g + assert sorted(flask.g) == ["bar", "foo"] + + +def test_subdomain_basic_support(): + app = flask.Flask(__name__, subdomain_matching=True) + app.config["SERVER_NAME"] = "localhost.localdomain" + client = app.test_client() + + @app.route("/") + def normal_index(): + return "normal index" + + @app.route("/", subdomain="test") + def test_index(): + return "test index" + + rv = client.get("/", "http://localhost.localdomain/") + assert rv.data == b"normal index" + + rv = client.get("/", "http://test.localhost.localdomain/") + assert rv.data == b"test index" + + +def test_subdomain_matching(): + app = flask.Flask(__name__, subdomain_matching=True) + client = app.test_client() + app.config["SERVER_NAME"] = "localhost.localdomain" + + @app.route("/", subdomain="") + def index(user): + return f"index for {user}" + + rv = client.get("/", "http://mitsuhiko.localhost.localdomain/") + assert rv.data == b"index for mitsuhiko" + + +def test_subdomain_matching_with_ports(): + app = flask.Flask(__name__, subdomain_matching=True) + app.config["SERVER_NAME"] = "localhost.localdomain:3000" + client = app.test_client() + + @app.route("/", subdomain="") + def index(user): + return f"index for {user}" + + rv = client.get("/", "http://mitsuhiko.localhost.localdomain:3000/") + assert rv.data == b"index for mitsuhiko" + + +@pytest.mark.parametrize("matching", (False, True)) +def test_subdomain_matching_other_name(matching): + app = flask.Flask(__name__, subdomain_matching=matching) + app.config["SERVER_NAME"] = "localhost.localdomain:3000" + client = app.test_client() + + @app.route("/") + def index(): + return "", 204 + + # suppress Werkzeug 0.15 warning about name mismatch + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "Current server name", UserWarning, "flask.app" + ) + # ip address can't match name + rv = client.get("/", "http://127.0.0.1:3000/") + assert rv.status_code == 404 if matching else 204 + + # allow all subdomains if matching is disabled + rv = client.get("/", "http://www.localhost.localdomain:3000/") + assert rv.status_code == 404 if matching else 204 + + +def test_multi_route_rules(app, client): + @app.route("/") + @app.route("//") + def index(test="a"): + return test + + rv = client.open("/") + assert rv.data == b"a" + rv = client.open("/b/") + assert rv.data == b"b" + + +def test_multi_route_class_views(app, client): + class View: + def __init__(self, app): + app.add_url_rule("/", "index", self.index) + app.add_url_rule("//", "index", self.index) + + def index(self, test="a"): + return test + + _ = View(app) + rv = client.open("/") + assert rv.data == b"a" + rv = client.open("/b/") + assert rv.data == b"b" + + +def test_run_defaults(monkeypatch, app): + rv = {} + + # Mocks werkzeug.serving.run_simple method + def run_simple_mock(*args, **kwargs): + rv["result"] = "running..." + + monkeypatch.setattr(werkzeug.serving, "run_simple", run_simple_mock) + app.run() + assert rv["result"] == "running..." + + +def test_run_server_port(monkeypatch, app): + rv = {} + + # Mocks werkzeug.serving.run_simple method + def run_simple_mock(hostname, port, application, *args, **kwargs): + rv["result"] = f"running on {hostname}:{port} ..." + + monkeypatch.setattr(werkzeug.serving, "run_simple", run_simple_mock) + hostname, port = "localhost", 8000 + app.run(hostname, port, debug=True) + assert rv["result"] == f"running on {hostname}:{port} ..." + + +@pytest.mark.parametrize( + "host,port,server_name,expect_host,expect_port", + ( + (None, None, "pocoo.org:8080", "pocoo.org", 8080), + ("localhost", None, "pocoo.org:8080", "localhost", 8080), + (None, 80, "pocoo.org:8080", "pocoo.org", 80), + ("localhost", 80, "pocoo.org:8080", "localhost", 80), + ("localhost", 0, "localhost:8080", "localhost", 0), + (None, None, "localhost:8080", "localhost", 8080), + (None, None, "localhost:0", "localhost", 0), + ), +) +def test_run_from_config( + monkeypatch, host, port, server_name, expect_host, expect_port, app +): + def run_simple_mock(hostname, port, *args, **kwargs): + assert hostname == expect_host + assert port == expect_port + + monkeypatch.setattr(werkzeug.serving, "run_simple", run_simple_mock) + app.config["SERVER_NAME"] = server_name + app.run(host, port) + + +def test_max_cookie_size(app, client, recwarn): + app.config["MAX_COOKIE_SIZE"] = 100 + + # outside app context, default to Werkzeug static value, + # which is also the default config + response = flask.Response() + default = flask.Flask.default_config["MAX_COOKIE_SIZE"] + assert response.max_cookie_size == default + + # inside app context, use app config + with app.app_context(): + assert flask.Response().max_cookie_size == 100 + + @app.route("/") + def index(): + r = flask.Response("", status=204) + r.set_cookie("foo", "bar" * 100) + return r + + client.get("/") + assert len(recwarn) == 1 + w = recwarn.pop() + assert "cookie is too large" in str(w.message) + + app.config["MAX_COOKIE_SIZE"] = 0 + + client.get("/") + assert len(recwarn) == 0 + + +@require_cpython_gc +def test_app_freed_on_zero_refcount(): + # A Flask instance should not create a reference cycle that prevents CPython + # from freeing it when all external references to it are released (see #3761). + gc.disable() + try: + app = flask.Flask(__name__) + assert app.view_functions["static"] + weak = weakref.ref(app) + assert weak() is not None + del app + assert weak() is None + finally: + gc.enable() diff --git a/src/flask-main/tests/test_blueprints.py b/src/flask-main/tests/test_blueprints.py new file mode 100644 index 0000000..ed1683c --- /dev/null +++ b/src/flask-main/tests/test_blueprints.py @@ -0,0 +1,1127 @@ +import pytest +from jinja2 import TemplateNotFound +from werkzeug.http import parse_cache_control_header + +import flask + + +def test_blueprint_specific_error_handling(app, client): + frontend = flask.Blueprint("frontend", __name__) + backend = flask.Blueprint("backend", __name__) + sideend = flask.Blueprint("sideend", __name__) + + @frontend.errorhandler(403) + def frontend_forbidden(e): + return "frontend says no", 403 + + @frontend.route("/frontend-no") + def frontend_no(): + flask.abort(403) + + @backend.errorhandler(403) + def backend_forbidden(e): + return "backend says no", 403 + + @backend.route("/backend-no") + def backend_no(): + flask.abort(403) + + @sideend.route("/what-is-a-sideend") + def sideend_no(): + flask.abort(403) + + app.register_blueprint(frontend) + app.register_blueprint(backend) + app.register_blueprint(sideend) + + @app.errorhandler(403) + def app_forbidden(e): + return "application itself says no", 403 + + assert client.get("/frontend-no").data == b"frontend says no" + assert client.get("/backend-no").data == b"backend says no" + assert client.get("/what-is-a-sideend").data == b"application itself says no" + + +def test_blueprint_specific_user_error_handling(app, client): + class MyDecoratorException(Exception): + pass + + class MyFunctionException(Exception): + pass + + blue = flask.Blueprint("blue", __name__) + + @blue.errorhandler(MyDecoratorException) + def my_decorator_exception_handler(e): + assert isinstance(e, MyDecoratorException) + return "boom" + + def my_function_exception_handler(e): + assert isinstance(e, MyFunctionException) + return "bam" + + blue.register_error_handler(MyFunctionException, my_function_exception_handler) + + @blue.route("/decorator") + def blue_deco_test(): + raise MyDecoratorException() + + @blue.route("/function") + def blue_func_test(): + raise MyFunctionException() + + app.register_blueprint(blue) + + assert client.get("/decorator").data == b"boom" + assert client.get("/function").data == b"bam" + + +def test_blueprint_app_error_handling(app, client): + errors = flask.Blueprint("errors", __name__) + + @errors.app_errorhandler(403) + def forbidden_handler(e): + return "you shall not pass", 403 + + @app.route("/forbidden") + def app_forbidden(): + flask.abort(403) + + forbidden_bp = flask.Blueprint("forbidden_bp", __name__) + + @forbidden_bp.route("/nope") + def bp_forbidden(): + flask.abort(403) + + app.register_blueprint(errors) + app.register_blueprint(forbidden_bp) + + assert client.get("/forbidden").data == b"you shall not pass" + assert client.get("/nope").data == b"you shall not pass" + + +@pytest.mark.parametrize( + ("prefix", "rule", "url"), + ( + ("", "/", "/"), + ("/", "", "/"), + ("/", "/", "/"), + ("/foo", "", "/foo"), + ("/foo/", "", "/foo/"), + ("", "/bar", "/bar"), + ("/foo/", "/bar", "/foo/bar"), + ("/foo/", "bar", "/foo/bar"), + ("/foo", "/bar", "/foo/bar"), + ("/foo/", "//bar", "/foo/bar"), + ("/foo//", "/bar", "/foo/bar"), + ), +) +def test_blueprint_prefix_slash(app, client, prefix, rule, url): + bp = flask.Blueprint("test", __name__, url_prefix=prefix) + + @bp.route(rule) + def index(): + return "", 204 + + app.register_blueprint(bp) + assert client.get(url).status_code == 204 + + +def test_blueprint_url_defaults(app, client): + bp = flask.Blueprint("test", __name__) + + @bp.route("/foo", defaults={"baz": 42}) + def foo(bar, baz): + return f"{bar}/{baz:d}" + + @bp.route("/bar") + def bar(bar): + return str(bar) + + app.register_blueprint(bp, url_prefix="/1", url_defaults={"bar": 23}) + app.register_blueprint(bp, name="test2", url_prefix="/2", url_defaults={"bar": 19}) + + assert client.get("/1/foo").data == b"23/42" + assert client.get("/2/foo").data == b"19/42" + assert client.get("/1/bar").data == b"23" + assert client.get("/2/bar").data == b"19" + + +def test_blueprint_url_processors(app, client): + bp = flask.Blueprint("frontend", __name__, url_prefix="/") + + @bp.url_defaults + def add_language_code(endpoint, values): + values.setdefault("lang_code", flask.g.lang_code) + + @bp.url_value_preprocessor + def pull_lang_code(endpoint, values): + flask.g.lang_code = values.pop("lang_code") + + @bp.route("/") + def index(): + return flask.url_for(".about") + + @bp.route("/about") + def about(): + return flask.url_for(".index") + + app.register_blueprint(bp) + + assert client.get("/de/").data == b"/de/about" + assert client.get("/de/about").data == b"/de/" + + +def test_templates_and_static(test_apps): + from blueprintapp import app + + client = app.test_client() + + rv = client.get("/") + assert rv.data == b"Hello from the Frontend" + rv = client.get("/admin/") + assert rv.data == b"Hello from the Admin" + rv = client.get("/admin/index2") + assert rv.data == b"Hello from the Admin" + rv = client.get("/admin/static/test.txt") + assert rv.data.strip() == b"Admin File" + rv.close() + rv = client.get("/admin/static/css/test.css") + assert rv.data.strip() == b"/* nested file */" + rv.close() + + # try/finally, in case other tests use this app for Blueprint tests. + max_age_default = app.config["SEND_FILE_MAX_AGE_DEFAULT"] + try: + expected_max_age = 3600 + if app.config["SEND_FILE_MAX_AGE_DEFAULT"] == expected_max_age: + expected_max_age = 7200 + app.config["SEND_FILE_MAX_AGE_DEFAULT"] = expected_max_age + rv = client.get("/admin/static/css/test.css") + cc = parse_cache_control_header(rv.headers["Cache-Control"]) + assert cc.max_age == expected_max_age + rv.close() + finally: + app.config["SEND_FILE_MAX_AGE_DEFAULT"] = max_age_default + + with app.test_request_context(): + assert ( + flask.url_for("admin.static", filename="test.txt") + == "/admin/static/test.txt" + ) + + with app.test_request_context(): + with pytest.raises(TemplateNotFound) as e: + flask.render_template("missing.html") + assert e.value.name == "missing.html" + + with flask.Flask(__name__).test_request_context(): + assert flask.render_template("nested/nested.txt") == "I'm nested" + + +def test_default_static_max_age(app): + class MyBlueprint(flask.Blueprint): + def get_send_file_max_age(self, filename): + return 100 + + blueprint = MyBlueprint("blueprint", __name__, static_folder="static") + app.register_blueprint(blueprint) + + # try/finally, in case other tests use this app for Blueprint tests. + max_age_default = app.config["SEND_FILE_MAX_AGE_DEFAULT"] + try: + with app.test_request_context(): + unexpected_max_age = 3600 + if app.config["SEND_FILE_MAX_AGE_DEFAULT"] == unexpected_max_age: + unexpected_max_age = 7200 + app.config["SEND_FILE_MAX_AGE_DEFAULT"] = unexpected_max_age + rv = blueprint.send_static_file("index.html") + cc = parse_cache_control_header(rv.headers["Cache-Control"]) + assert cc.max_age == 100 + rv.close() + finally: + app.config["SEND_FILE_MAX_AGE_DEFAULT"] = max_age_default + + +def test_templates_list(test_apps): + from blueprintapp import app + + templates = sorted(app.jinja_env.list_templates()) + assert templates == ["admin/index.html", "frontend/index.html"] + + +def test_dotted_name_not_allowed(app, client): + with pytest.raises(ValueError): + flask.Blueprint("app.ui", __name__) + + +def test_empty_name_not_allowed(app, client): + with pytest.raises(ValueError): + flask.Blueprint("", __name__) + + +def test_dotted_names_from_app(app, client): + test = flask.Blueprint("test", __name__) + + @app.route("/") + def app_index(): + return flask.url_for("test.index") + + @test.route("/test/") + def index(): + return flask.url_for("app_index") + + app.register_blueprint(test) + + rv = client.get("/") + assert rv.data == b"/test/" + + +def test_empty_url_defaults(app, client): + bp = flask.Blueprint("bp", __name__) + + @bp.route("/", defaults={"page": 1}) + @bp.route("/page/") + def something(page): + return str(page) + + app.register_blueprint(bp) + + assert client.get("/").data == b"1" + assert client.get("/page/2").data == b"2" + + +def test_route_decorator_custom_endpoint(app, client): + bp = flask.Blueprint("bp", __name__) + + @bp.route("/foo") + def foo(): + return flask.request.endpoint + + @bp.route("/bar", endpoint="bar") + def foo_bar(): + return flask.request.endpoint + + @bp.route("/bar/123", endpoint="123") + def foo_bar_foo(): + return flask.request.endpoint + + @bp.route("/bar/foo") + def bar_foo(): + return flask.request.endpoint + + app.register_blueprint(bp, url_prefix="/py") + + @app.route("/") + def index(): + return flask.request.endpoint + + assert client.get("/").data == b"index" + assert client.get("/py/foo").data == b"bp.foo" + assert client.get("/py/bar").data == b"bp.bar" + assert client.get("/py/bar/123").data == b"bp.123" + assert client.get("/py/bar/foo").data == b"bp.bar_foo" + + +def test_route_decorator_custom_endpoint_with_dots(app, client): + bp = flask.Blueprint("bp", __name__) + + with pytest.raises(ValueError): + bp.route("/", endpoint="a.b")(lambda: "") + + with pytest.raises(ValueError): + bp.add_url_rule("/", endpoint="a.b") + + def view(): + return "" + + view.__name__ = "a.b" + + with pytest.raises(ValueError): + bp.add_url_rule("/", view_func=view) + + +def test_endpoint_decorator(app, client): + from werkzeug.routing import Rule + + app.url_map.add(Rule("/foo", endpoint="bar")) + + bp = flask.Blueprint("bp", __name__) + + @bp.endpoint("bar") + def foobar(): + return flask.request.endpoint + + app.register_blueprint(bp, url_prefix="/bp_prefix") + + assert client.get("/foo").data == b"bar" + assert client.get("/bp_prefix/bar").status_code == 404 + + +def test_template_filter(app): + bp = flask.Blueprint("bp", __name__) + + @bp.app_template_filter() + def my_reverse(s): + return s[::-1] + + @bp.app_template_filter + def my_reverse_2(s): + return s[::-1] + + @bp.app_template_filter("my_reverse_custom_name_3") + def my_reverse_3(s): + return s[::-1] + + @bp.app_template_filter(name="my_reverse_custom_name_4") + def my_reverse_4(s): + return s[::-1] + + app.register_blueprint(bp, url_prefix="/py") + assert "my_reverse" in app.jinja_env.filters.keys() + assert app.jinja_env.filters["my_reverse"] == my_reverse + assert app.jinja_env.filters["my_reverse"]("abcd") == "dcba" + + assert "my_reverse_2" in app.jinja_env.filters.keys() + assert app.jinja_env.filters["my_reverse_2"] == my_reverse_2 + assert app.jinja_env.filters["my_reverse_2"]("abcd") == "dcba" + + assert "my_reverse_custom_name_3" in app.jinja_env.filters.keys() + assert app.jinja_env.filters["my_reverse_custom_name_3"] == my_reverse_3 + assert app.jinja_env.filters["my_reverse_custom_name_3"]("abcd") == "dcba" + + assert "my_reverse_custom_name_4" in app.jinja_env.filters.keys() + assert app.jinja_env.filters["my_reverse_custom_name_4"] == my_reverse_4 + assert app.jinja_env.filters["my_reverse_custom_name_4"]("abcd") == "dcba" + + +def test_add_template_filter(app): + bp = flask.Blueprint("bp", __name__) + + def my_reverse(s): + return s[::-1] + + bp.add_app_template_filter(my_reverse) + app.register_blueprint(bp, url_prefix="/py") + assert "my_reverse" in app.jinja_env.filters.keys() + assert app.jinja_env.filters["my_reverse"] == my_reverse + assert app.jinja_env.filters["my_reverse"]("abcd") == "dcba" + + +def test_template_filter_with_name(app): + bp = flask.Blueprint("bp", __name__) + + @bp.app_template_filter("strrev") + def my_reverse(s): + return s[::-1] + + app.register_blueprint(bp, url_prefix="/py") + assert "strrev" in app.jinja_env.filters.keys() + assert app.jinja_env.filters["strrev"] == my_reverse + assert app.jinja_env.filters["strrev"]("abcd") == "dcba" + + +def test_add_template_filter_with_name(app): + bp = flask.Blueprint("bp", __name__) + + def my_reverse(s): + return s[::-1] + + bp.add_app_template_filter(my_reverse, "strrev") + app.register_blueprint(bp, url_prefix="/py") + assert "strrev" in app.jinja_env.filters.keys() + assert app.jinja_env.filters["strrev"] == my_reverse + assert app.jinja_env.filters["strrev"]("abcd") == "dcba" + + +def test_template_filter_with_template(app, client): + bp = flask.Blueprint("bp", __name__) + + @bp.app_template_filter() + def super_reverse(s): + return s[::-1] + + app.register_blueprint(bp, url_prefix="/py") + + @app.route("/") + def index(): + return flask.render_template("template_filter.html", value="abcd") + + rv = client.get("/") + assert rv.data == b"dcba" + + +def test_template_filter_after_route_with_template(app, client): + @app.route("/") + def index(): + return flask.render_template("template_filter.html", value="abcd") + + bp = flask.Blueprint("bp", __name__) + + @bp.app_template_filter() + def super_reverse(s): + return s[::-1] + + app.register_blueprint(bp, url_prefix="/py") + rv = client.get("/") + assert rv.data == b"dcba" + + +def test_add_template_filter_with_template(app, client): + bp = flask.Blueprint("bp", __name__) + + def super_reverse(s): + return s[::-1] + + bp.add_app_template_filter(super_reverse) + app.register_blueprint(bp, url_prefix="/py") + + @app.route("/") + def index(): + return flask.render_template("template_filter.html", value="abcd") + + rv = client.get("/") + assert rv.data == b"dcba" + + +def test_template_filter_with_name_and_template(app, client): + bp = flask.Blueprint("bp", __name__) + + @bp.app_template_filter("super_reverse") + def my_reverse(s): + return s[::-1] + + app.register_blueprint(bp, url_prefix="/py") + + @app.route("/") + def index(): + return flask.render_template("template_filter.html", value="abcd") + + rv = client.get("/") + assert rv.data == b"dcba" + + +def test_add_template_filter_with_name_and_template(app, client): + bp = flask.Blueprint("bp", __name__) + + def my_reverse(s): + return s[::-1] + + bp.add_app_template_filter(my_reverse, "super_reverse") + app.register_blueprint(bp, url_prefix="/py") + + @app.route("/") + def index(): + return flask.render_template("template_filter.html", value="abcd") + + rv = client.get("/") + assert rv.data == b"dcba" + + +def test_template_test(app): + bp = flask.Blueprint("bp", __name__) + + @bp.app_template_test() + def is_boolean(value): + return isinstance(value, bool) + + @bp.app_template_test + def boolean_2(value): + return isinstance(value, bool) + + @bp.app_template_test("my_boolean_custom_name") + def boolean_3(value): + return isinstance(value, bool) + + @bp.app_template_test(name="my_boolean_custom_name_2") + def boolean_4(value): + return isinstance(value, bool) + + app.register_blueprint(bp, url_prefix="/py") + assert "is_boolean" in app.jinja_env.tests.keys() + assert app.jinja_env.tests["is_boolean"] == is_boolean + assert app.jinja_env.tests["is_boolean"](False) + + assert "boolean_2" in app.jinja_env.tests.keys() + assert app.jinja_env.tests["boolean_2"] == boolean_2 + assert app.jinja_env.tests["boolean_2"](False) + + assert "my_boolean_custom_name" in app.jinja_env.tests.keys() + assert app.jinja_env.tests["my_boolean_custom_name"] == boolean_3 + assert app.jinja_env.tests["my_boolean_custom_name"](False) + + assert "my_boolean_custom_name_2" in app.jinja_env.tests.keys() + assert app.jinja_env.tests["my_boolean_custom_name_2"] == boolean_4 + assert app.jinja_env.tests["my_boolean_custom_name_2"](False) + + +def test_add_template_test(app): + bp = flask.Blueprint("bp", __name__) + + def is_boolean(value): + return isinstance(value, bool) + + bp.add_app_template_test(is_boolean) + app.register_blueprint(bp, url_prefix="/py") + assert "is_boolean" in app.jinja_env.tests.keys() + assert app.jinja_env.tests["is_boolean"] == is_boolean + assert app.jinja_env.tests["is_boolean"](False) + + +def test_template_test_with_name(app): + bp = flask.Blueprint("bp", __name__) + + @bp.app_template_test("boolean") + def is_boolean(value): + return isinstance(value, bool) + + app.register_blueprint(bp, url_prefix="/py") + assert "boolean" in app.jinja_env.tests.keys() + assert app.jinja_env.tests["boolean"] == is_boolean + assert app.jinja_env.tests["boolean"](False) + + +def test_add_template_test_with_name(app): + bp = flask.Blueprint("bp", __name__) + + def is_boolean(value): + return isinstance(value, bool) + + bp.add_app_template_test(is_boolean, "boolean") + app.register_blueprint(bp, url_prefix="/py") + assert "boolean" in app.jinja_env.tests.keys() + assert app.jinja_env.tests["boolean"] == is_boolean + assert app.jinja_env.tests["boolean"](False) + + +def test_template_test_with_template(app, client): + bp = flask.Blueprint("bp", __name__) + + @bp.app_template_test() + def boolean(value): + return isinstance(value, bool) + + app.register_blueprint(bp, url_prefix="/py") + + @app.route("/") + def index(): + return flask.render_template("template_test.html", value=False) + + rv = client.get("/") + assert b"Success!" in rv.data + + +def test_template_test_after_route_with_template(app, client): + @app.route("/") + def index(): + return flask.render_template("template_test.html", value=False) + + bp = flask.Blueprint("bp", __name__) + + @bp.app_template_test() + def boolean(value): + return isinstance(value, bool) + + app.register_blueprint(bp, url_prefix="/py") + rv = client.get("/") + assert b"Success!" in rv.data + + +def test_add_template_test_with_template(app, client): + bp = flask.Blueprint("bp", __name__) + + def boolean(value): + return isinstance(value, bool) + + bp.add_app_template_test(boolean) + app.register_blueprint(bp, url_prefix="/py") + + @app.route("/") + def index(): + return flask.render_template("template_test.html", value=False) + + rv = client.get("/") + assert b"Success!" in rv.data + + +def test_template_test_with_name_and_template(app, client): + bp = flask.Blueprint("bp", __name__) + + @bp.app_template_test("boolean") + def is_boolean(value): + return isinstance(value, bool) + + app.register_blueprint(bp, url_prefix="/py") + + @app.route("/") + def index(): + return flask.render_template("template_test.html", value=False) + + rv = client.get("/") + assert b"Success!" in rv.data + + +def test_add_template_test_with_name_and_template(app, client): + bp = flask.Blueprint("bp", __name__) + + def is_boolean(value): + return isinstance(value, bool) + + bp.add_app_template_test(is_boolean, "boolean") + app.register_blueprint(bp, url_prefix="/py") + + @app.route("/") + def index(): + return flask.render_template("template_test.html", value=False) + + rv = client.get("/") + assert b"Success!" in rv.data + + +def test_context_processing(app, client): + answer_bp = flask.Blueprint("answer_bp", __name__) + + def template_string(): + return flask.render_template_string( + "{% if notanswer %}{{ notanswer }} is not the answer. {% endif %}" + "{% if answer %}{{ answer }} is the answer.{% endif %}" + ) + + # App global context processor + @answer_bp.app_context_processor + def not_answer_context_processor(): + return {"notanswer": 43} + + # Blueprint local context processor + @answer_bp.context_processor + def answer_context_processor(): + return {"answer": 42} + + # Setup endpoints for testing + @answer_bp.route("/bp") + def bp_page(): + return template_string() + + @app.route("/") + def app_page(): + return template_string() + + # Register the blueprint + app.register_blueprint(answer_bp) + + app_page_bytes = client.get("/").data + answer_page_bytes = client.get("/bp").data + + assert b"43" in app_page_bytes + assert b"42" not in app_page_bytes + + assert b"42" in answer_page_bytes + assert b"43" in answer_page_bytes + + +def test_template_global(app): + bp = flask.Blueprint("bp", __name__) + + @bp.app_template_global() + def get_answer(): + return 42 + + @bp.app_template_global + def get_stuff_1(): + return "get_stuff_1" + + @bp.app_template_global("my_get_stuff_custom_name_2") + def get_stuff_2(): + return "get_stuff_2" + + @bp.app_template_global(name="my_get_stuff_custom_name_3") + def get_stuff_3(): + return "get_stuff_3" + + # Make sure the function is not in the jinja_env already + assert "get_answer" not in app.jinja_env.globals.keys() + app.register_blueprint(bp) + + # Tests + assert "get_answer" in app.jinja_env.globals.keys() + assert app.jinja_env.globals["get_answer"] is get_answer + assert app.jinja_env.globals["get_answer"]() == 42 + + assert "get_stuff_1" in app.jinja_env.globals.keys() + assert app.jinja_env.globals["get_stuff_1"] == get_stuff_1 + assert app.jinja_env.globals["get_stuff_1"](), "get_stuff_1" + + assert "my_get_stuff_custom_name_2" in app.jinja_env.globals.keys() + assert app.jinja_env.globals["my_get_stuff_custom_name_2"] == get_stuff_2 + assert app.jinja_env.globals["my_get_stuff_custom_name_2"](), "get_stuff_2" + + assert "my_get_stuff_custom_name_3" in app.jinja_env.globals.keys() + assert app.jinja_env.globals["my_get_stuff_custom_name_3"] == get_stuff_3 + assert app.jinja_env.globals["my_get_stuff_custom_name_3"](), "get_stuff_3" + + with app.app_context(): + rv = flask.render_template_string("{{ get_answer() }}") + assert rv == "42" + + rv = flask.render_template_string("{{ get_stuff_1() }}") + assert rv == "get_stuff_1" + + rv = flask.render_template_string("{{ my_get_stuff_custom_name_2() }}") + assert rv == "get_stuff_2" + + rv = flask.render_template_string("{{ my_get_stuff_custom_name_3() }}") + assert rv == "get_stuff_3" + + +def test_request_processing(app, client): + bp = flask.Blueprint("bp", __name__) + evts = [] + + @bp.before_request + def before_bp(): + evts.append("before") + + @bp.after_request + def after_bp(response): + response.data += b"|after" + evts.append("after") + return response + + @bp.teardown_request + def teardown_bp(exc): + evts.append("teardown") + + # Setup routes for testing + @bp.route("/bp") + def bp_endpoint(): + return "request" + + app.register_blueprint(bp) + + assert evts == [] + rv = client.get("/bp") + assert rv.data == b"request|after" + assert evts == ["before", "after", "teardown"] + + +def test_app_request_processing(app, client): + bp = flask.Blueprint("bp", __name__) + evts = [] + + @bp.before_app_request + def before_app(): + evts.append("before") + + @bp.after_app_request + def after_app(response): + response.data += b"|after" + evts.append("after") + return response + + @bp.teardown_app_request + def teardown_app(exc): + evts.append("teardown") + + app.register_blueprint(bp) + + # Setup routes for testing + @app.route("/") + def bp_endpoint(): + return "request" + + # before first request + assert evts == [] + + # first request + resp = client.get("/").data + assert resp == b"request|after" + assert evts == ["before", "after", "teardown"] + + # second request + resp = client.get("/").data + assert resp == b"request|after" + assert evts == ["before", "after", "teardown"] * 2 + + +def test_app_url_processors(app, client): + bp = flask.Blueprint("bp", __name__) + + # Register app-wide url defaults and preprocessor on blueprint + @bp.app_url_defaults + def add_language_code(endpoint, values): + values.setdefault("lang_code", flask.g.lang_code) + + @bp.app_url_value_preprocessor + def pull_lang_code(endpoint, values): + flask.g.lang_code = values.pop("lang_code") + + # Register route rules at the app level + @app.route("//") + def index(): + return flask.url_for("about") + + @app.route("//about") + def about(): + return flask.url_for("index") + + app.register_blueprint(bp) + + assert client.get("/de/").data == b"/de/about" + assert client.get("/de/about").data == b"/de/" + + +def test_nested_blueprint(app, client): + parent = flask.Blueprint("parent", __name__) + child = flask.Blueprint("child", __name__) + grandchild = flask.Blueprint("grandchild", __name__) + + @parent.errorhandler(403) + def forbidden(e): + return "Parent no", 403 + + @parent.route("/") + def parent_index(): + return "Parent yes" + + @parent.route("/no") + def parent_no(): + flask.abort(403) + + @child.route("/") + def child_index(): + return "Child yes" + + @child.route("/no") + def child_no(): + flask.abort(403) + + @grandchild.errorhandler(403) + def grandchild_forbidden(e): + return "Grandchild no", 403 + + @grandchild.route("/") + def grandchild_index(): + return "Grandchild yes" + + @grandchild.route("/no") + def grandchild_no(): + flask.abort(403) + + child.register_blueprint(grandchild, url_prefix="/grandchild") + parent.register_blueprint(child, url_prefix="/child") + app.register_blueprint(parent, url_prefix="/parent") + + assert client.get("/parent/").data == b"Parent yes" + assert client.get("/parent/child/").data == b"Child yes" + assert client.get("/parent/child/grandchild/").data == b"Grandchild yes" + assert client.get("/parent/no").data == b"Parent no" + assert client.get("/parent/child/no").data == b"Parent no" + assert client.get("/parent/child/grandchild/no").data == b"Grandchild no" + + +def test_nested_callback_order(app, client): + parent = flask.Blueprint("parent", __name__) + child = flask.Blueprint("child", __name__) + + @app.before_request + def app_before1(): + flask.g.setdefault("seen", []).append("app_1") + + @app.teardown_request + def app_teardown1(e=None): + assert flask.g.seen.pop() == "app_1" + + @app.before_request + def app_before2(): + flask.g.setdefault("seen", []).append("app_2") + + @app.teardown_request + def app_teardown2(e=None): + assert flask.g.seen.pop() == "app_2" + + @app.context_processor + def app_ctx(): + return dict(key="app") + + @parent.before_request + def parent_before1(): + flask.g.setdefault("seen", []).append("parent_1") + + @parent.teardown_request + def parent_teardown1(e=None): + assert flask.g.seen.pop() == "parent_1" + + @parent.before_request + def parent_before2(): + flask.g.setdefault("seen", []).append("parent_2") + + @parent.teardown_request + def parent_teardown2(e=None): + assert flask.g.seen.pop() == "parent_2" + + @parent.context_processor + def parent_ctx(): + return dict(key="parent") + + @child.before_request + def child_before1(): + flask.g.setdefault("seen", []).append("child_1") + + @child.teardown_request + def child_teardown1(e=None): + assert flask.g.seen.pop() == "child_1" + + @child.before_request + def child_before2(): + flask.g.setdefault("seen", []).append("child_2") + + @child.teardown_request + def child_teardown2(e=None): + assert flask.g.seen.pop() == "child_2" + + @child.context_processor + def child_ctx(): + return dict(key="child") + + @child.route("/a") + def a(): + return ", ".join(flask.g.seen) + + @child.route("/b") + def b(): + return flask.render_template_string("{{ key }}") + + parent.register_blueprint(child) + app.register_blueprint(parent) + assert ( + client.get("/a").data == b"app_1, app_2, parent_1, parent_2, child_1, child_2" + ) + assert client.get("/b").data == b"child" + + +@pytest.mark.parametrize( + "parent_init, child_init, parent_registration, child_registration", + [ + ("/parent", "/child", None, None), + ("/parent", None, None, "/child"), + (None, None, "/parent", "/child"), + ("/other", "/something", "/parent", "/child"), + ], +) +def test_nesting_url_prefixes( + parent_init, + child_init, + parent_registration, + child_registration, + app, + client, +) -> None: + parent = flask.Blueprint("parent", __name__, url_prefix=parent_init) + child = flask.Blueprint("child", __name__, url_prefix=child_init) + + @child.route("/") + def index(): + return "index" + + parent.register_blueprint(child, url_prefix=child_registration) + app.register_blueprint(parent, url_prefix=parent_registration) + + response = client.get("/parent/child/") + assert response.status_code == 200 + + +def test_nesting_subdomains(app, client) -> None: + app.subdomain_matching = True + app.config["SERVER_NAME"] = "example.test" + client.allow_subdomain_redirects = True + + parent = flask.Blueprint("parent", __name__) + child = flask.Blueprint("child", __name__) + + @child.route("/child/") + def index(): + return "child" + + parent.register_blueprint(child) + app.register_blueprint(parent, subdomain="api") + + response = client.get("/child/", base_url="http://api.example.test") + assert response.status_code == 200 + + +def test_child_and_parent_subdomain(app, client) -> None: + app.subdomain_matching = True + app.config["SERVER_NAME"] = "example.test" + client.allow_subdomain_redirects = True + + parent = flask.Blueprint("parent", __name__) + child = flask.Blueprint("child", __name__, subdomain="api") + + @child.route("/") + def index(): + return "child" + + parent.register_blueprint(child) + app.register_blueprint(parent, subdomain="parent") + + response = client.get("/", base_url="http://api.parent.example.test") + assert response.status_code == 200 + + response = client.get("/", base_url="http://parent.example.test") + assert response.status_code == 404 + + +def test_unique_blueprint_names(app, client) -> None: + bp = flask.Blueprint("bp", __name__) + bp2 = flask.Blueprint("bp", __name__) + + app.register_blueprint(bp) + + with pytest.raises(ValueError): + app.register_blueprint(bp) # same bp, same name, error + + app.register_blueprint(bp, name="again") # same bp, different name, ok + + with pytest.raises(ValueError): + app.register_blueprint(bp2) # different bp, same name, error + + app.register_blueprint(bp2, name="alt") # different bp, different name, ok + + +def test_self_registration(app, client) -> None: + bp = flask.Blueprint("bp", __name__) + with pytest.raises(ValueError): + bp.register_blueprint(bp) + + +def test_blueprint_renaming(app, client) -> None: + bp = flask.Blueprint("bp", __name__) + bp2 = flask.Blueprint("bp2", __name__) + + @bp.get("/") + def index(): + return flask.request.endpoint + + @bp.get("/error") + def error(): + flask.abort(403) + + @bp.errorhandler(403) + def forbidden(_: Exception): + return "Error", 403 + + @bp2.get("/") + def index2(): + return flask.request.endpoint + + bp.register_blueprint(bp2, url_prefix="/a", name="sub") + app.register_blueprint(bp, url_prefix="/a") + app.register_blueprint(bp, url_prefix="/b", name="alt") + + assert client.get("/a/").data == b"bp.index" + assert client.get("/b/").data == b"alt.index" + assert client.get("/a/a/").data == b"bp.sub.index2" + assert client.get("/b/a/").data == b"alt.sub.index2" + assert client.get("/a/error").data == b"Error" + assert client.get("/b/error").data == b"Error" diff --git a/src/flask-main/tests/test_cli.py b/src/flask-main/tests/test_cli.py new file mode 100644 index 0000000..a5aab50 --- /dev/null +++ b/src/flask-main/tests/test_cli.py @@ -0,0 +1,702 @@ +# This file was part of Flask-CLI and was modified under the terms of +# its Revised BSD License. Copyright © 2015 CERN. +import importlib.metadata +import os +import platform +import ssl +import sys +import types +from functools import partial +from pathlib import Path + +import click +import pytest +from _pytest.monkeypatch import notset +from click.testing import CliRunner + +from flask import Blueprint +from flask import current_app +from flask import Flask +from flask.cli import AppGroup +from flask.cli import find_best_app +from flask.cli import FlaskGroup +from flask.cli import get_version +from flask.cli import load_dotenv +from flask.cli import locate_app +from flask.cli import NoAppException +from flask.cli import prepare_import +from flask.cli import run_command +from flask.cli import ScriptInfo +from flask.cli import with_appcontext + +cwd = Path.cwd() +test_path = (Path(__file__) / ".." / "test_apps").resolve() + + +@pytest.fixture +def runner(): + return CliRunner() + + +def test_cli_name(test_apps): + """Make sure the CLI object's name is the app's name and not the app itself""" + from cliapp.app import testapp + + assert testapp.cli.name == testapp.name + + +def test_find_best_app(test_apps): + class Module: + app = Flask("appname") + + assert find_best_app(Module) == Module.app + + class Module: + application = Flask("appname") + + assert find_best_app(Module) == Module.application + + class Module: + myapp = Flask("appname") + + assert find_best_app(Module) == Module.myapp + + class Module: + @staticmethod + def create_app(): + return Flask("appname") + + app = find_best_app(Module) + assert isinstance(app, Flask) + assert app.name == "appname" + + class Module: + @staticmethod + def create_app(**kwargs): + return Flask("appname") + + app = find_best_app(Module) + assert isinstance(app, Flask) + assert app.name == "appname" + + class Module: + @staticmethod + def make_app(): + return Flask("appname") + + app = find_best_app(Module) + assert isinstance(app, Flask) + assert app.name == "appname" + + class Module: + myapp = Flask("appname1") + + @staticmethod + def create_app(): + return Flask("appname2") + + assert find_best_app(Module) == Module.myapp + + class Module: + myapp = Flask("appname1") + + @staticmethod + def create_app(): + return Flask("appname2") + + assert find_best_app(Module) == Module.myapp + + class Module: + pass + + pytest.raises(NoAppException, find_best_app, Module) + + class Module: + myapp1 = Flask("appname1") + myapp2 = Flask("appname2") + + pytest.raises(NoAppException, find_best_app, Module) + + class Module: + @staticmethod + def create_app(foo, bar): + return Flask("appname2") + + pytest.raises(NoAppException, find_best_app, Module) + + class Module: + @staticmethod + def create_app(): + raise TypeError("bad bad factory!") + + pytest.raises(TypeError, find_best_app, Module) + + +@pytest.mark.parametrize( + "value,path,result", + ( + ("test", cwd, "test"), + ("test.py", cwd, "test"), + ("a/test", cwd / "a", "test"), + ("test/__init__.py", cwd, "test"), + ("test/__init__", cwd, "test"), + # nested package + ( + test_path / "cliapp" / "inner1" / "__init__", + test_path, + "cliapp.inner1", + ), + ( + test_path / "cliapp" / "inner1" / "inner2", + test_path, + "cliapp.inner1.inner2", + ), + # dotted name + ("test.a.b", cwd, "test.a.b"), + (test_path / "cliapp.app", test_path, "cliapp.app"), + # not a Python file, will be caught during import + (test_path / "cliapp" / "message.txt", test_path, "cliapp.message.txt"), + ), +) +def test_prepare_import(request, value, path, result): + """Expect the correct path to be set and the correct import and app names + to be returned. + + :func:`prepare_exec_for_file` has a side effect where the parent directory + of the given import is added to :data:`sys.path`. This is reset after the + test runs. + """ + original_path = sys.path[:] + + def reset_path(): + sys.path[:] = original_path + + request.addfinalizer(reset_path) + + assert prepare_import(value) == result + assert sys.path[0] == str(path) + + +@pytest.mark.parametrize( + "iname,aname,result", + ( + ("cliapp.app", None, "testapp"), + ("cliapp.app", "testapp", "testapp"), + ("cliapp.factory", None, "app"), + ("cliapp.factory", "create_app", "app"), + ("cliapp.factory", "create_app()", "app"), + ("cliapp.factory", 'create_app2("foo", "bar")', "app2_foo_bar"), + # trailing comma space + ("cliapp.factory", 'create_app2("foo", "bar", )', "app2_foo_bar"), + # strip whitespace + ("cliapp.factory", " create_app () ", "app"), + ), +) +def test_locate_app(test_apps, iname, aname, result): + assert locate_app(iname, aname).name == result + + +@pytest.mark.parametrize( + "iname,aname", + ( + ("notanapp.py", None), + ("cliapp/app", None), + ("cliapp.app", "notanapp"), + # not enough arguments + ("cliapp.factory", 'create_app2("foo")'), + # invalid identifier + ("cliapp.factory", "create_app("), + # no app returned + ("cliapp.factory", "no_app"), + # nested import error + ("cliapp.importerrorapp", None), + # not a Python file + ("cliapp.message.txt", None), + ), +) +def test_locate_app_raises(test_apps, iname, aname): + with pytest.raises(NoAppException): + locate_app(iname, aname) + + +def test_locate_app_suppress_raise(test_apps): + app = locate_app("notanapp.py", None, raise_if_not_found=False) + assert app is None + + # only direct import error is suppressed + with pytest.raises(NoAppException): + locate_app("cliapp.importerrorapp", None, raise_if_not_found=False) + + +def test_get_version(test_apps, capsys): + class MockCtx: + resilient_parsing = False + color = None + + def exit(self): + return + + ctx = MockCtx() + get_version(ctx, None, "test") + out, err = capsys.readouterr() + assert f"Python {platform.python_version()}" in out + assert f"Flask {importlib.metadata.version('flask')}" in out + assert f"Werkzeug {importlib.metadata.version('werkzeug')}" in out + + +def test_scriptinfo(test_apps, monkeypatch): + obj = ScriptInfo(app_import_path="cliapp.app:testapp") + app = obj.load_app() + assert app.name == "testapp" + assert obj.load_app() is app + + # import app with module's absolute path + cli_app_path = str(test_path / "cliapp" / "app.py") + + obj = ScriptInfo(app_import_path=cli_app_path) + app = obj.load_app() + assert app.name == "testapp" + assert obj.load_app() is app + obj = ScriptInfo(app_import_path=f"{cli_app_path}:testapp") + app = obj.load_app() + assert app.name == "testapp" + assert obj.load_app() is app + + def create_app(): + return Flask("createapp") + + obj = ScriptInfo(create_app=create_app) + app = obj.load_app() + assert app.name == "createapp" + assert obj.load_app() is app + + obj = ScriptInfo() + pytest.raises(NoAppException, obj.load_app) + + # import app from wsgi.py in current directory + monkeypatch.chdir(test_path / "helloworld") + obj = ScriptInfo() + app = obj.load_app() + assert app.name == "hello" + + # import app from app.py in current directory + monkeypatch.chdir(test_path / "cliapp") + obj = ScriptInfo() + app = obj.load_app() + assert app.name == "testapp" + + +def test_app_cli_has_app_context(app, runner): + def _param_cb(ctx, param, value): + # current_app should be available in parameter callbacks + return bool(current_app) + + @app.cli.command() + @click.argument("value", callback=_param_cb) + def check(value): + app = click.get_current_context().obj.load_app() + # the loaded app should be the same as current_app + same_app = current_app._get_current_object() is app + return same_app, value + + cli = FlaskGroup(create_app=lambda: app) + result = runner.invoke(cli, ["check", "x"], standalone_mode=False) + assert result.return_value == (True, True) + + +def test_with_appcontext(runner): + @click.command() + @with_appcontext + def testcmd(): + click.echo(current_app.name) + + obj = ScriptInfo(create_app=lambda: Flask("testapp")) + + result = runner.invoke(testcmd, obj=obj) + assert result.exit_code == 0 + assert result.output == "testapp\n" + + +def test_appgroup_app_context(runner): + @click.group(cls=AppGroup) + def cli(): + pass + + @cli.command() + def test(): + click.echo(current_app.name) + + @cli.group() + def subgroup(): + pass + + @subgroup.command() + def test2(): + click.echo(current_app.name) + + obj = ScriptInfo(create_app=lambda: Flask("testappgroup")) + + result = runner.invoke(cli, ["test"], obj=obj) + assert result.exit_code == 0 + assert result.output == "testappgroup\n" + + result = runner.invoke(cli, ["subgroup", "test2"], obj=obj) + assert result.exit_code == 0 + assert result.output == "testappgroup\n" + + +def test_flaskgroup_app_context(runner): + def create_app(): + return Flask("flaskgroup") + + @click.group(cls=FlaskGroup, create_app=create_app) + def cli(**params): + pass + + @cli.command() + def test(): + click.echo(current_app.name) + + result = runner.invoke(cli, ["test"]) + assert result.exit_code == 0 + assert result.output == "flaskgroup\n" + + +@pytest.mark.parametrize("set_debug_flag", (True, False)) +def test_flaskgroup_debug(runner, set_debug_flag): + def create_app(): + app = Flask("flaskgroup") + app.debug = True + return app + + @click.group(cls=FlaskGroup, create_app=create_app, set_debug_flag=set_debug_flag) + def cli(**params): + pass + + @cli.command() + def test(): + click.echo(str(current_app.debug)) + + result = runner.invoke(cli, ["test"]) + assert result.exit_code == 0 + assert result.output == f"{not set_debug_flag}\n" + + +def test_flaskgroup_nested(app, runner): + cli = click.Group("cli") + flask_group = FlaskGroup(name="flask", create_app=lambda: app) + cli.add_command(flask_group) + + @flask_group.command() + def show(): + click.echo(current_app.name) + + result = runner.invoke(cli, ["flask", "show"]) + assert result.output == "flask_test\n" + + +def test_no_command_echo_loading_error(): + from flask.cli import cli + + try: + runner = CliRunner(mix_stderr=False) + except (DeprecationWarning, TypeError): + # Click >= 8.2 + runner = CliRunner() + + result = runner.invoke(cli, ["missing"]) + assert result.exit_code == 2 + assert "FLASK_APP" in result.stderr + assert "Usage:" in result.stderr + + +def test_help_echo_loading_error(): + from flask.cli import cli + + try: + runner = CliRunner(mix_stderr=False) + except (DeprecationWarning, TypeError): + # Click >= 8.2 + runner = CliRunner() + + result = runner.invoke(cli, ["--help"]) + assert result.exit_code == 0 + assert "FLASK_APP" in result.stderr + assert "Usage:" in result.stdout + + +def test_help_echo_exception(): + def create_app(): + raise Exception("oh no") + + cli = FlaskGroup(create_app=create_app) + + try: + runner = CliRunner(mix_stderr=False) + except (DeprecationWarning, TypeError): + # Click >= 8.2 + runner = CliRunner() + + result = runner.invoke(cli, ["--help"]) + assert result.exit_code == 0 + assert "Exception: oh no" in result.stderr + assert "Usage:" in result.stdout + + +class TestRoutes: + @pytest.fixture + def app(self): + app = Flask(__name__) + app.add_url_rule( + "/get_post//", + methods=["GET", "POST"], + endpoint="yyy_get_post", + ) + app.add_url_rule("/zzz_post", methods=["POST"], endpoint="aaa_post") + return app + + @pytest.fixture + def invoke(self, app, runner): + cli = FlaskGroup(create_app=lambda: app) + return partial(runner.invoke, cli) + + def expect_order(self, order, output): + # skip the header and match the start of each row + for expect, line in zip(order, output.splitlines()[2:], strict=False): + # do this instead of startswith for nicer pytest output + assert line[: len(expect)] == expect + + def test_simple(self, invoke): + result = invoke(["routes"]) + assert result.exit_code == 0 + self.expect_order(["aaa_post", "static", "yyy_get_post"], result.output) + + def test_sort(self, app, invoke): + default_output = invoke(["routes"]).output + endpoint_output = invoke(["routes", "-s", "endpoint"]).output + assert default_output == endpoint_output + self.expect_order( + ["static", "yyy_get_post", "aaa_post"], + invoke(["routes", "-s", "methods"]).output, + ) + self.expect_order( + ["yyy_get_post", "static", "aaa_post"], + invoke(["routes", "-s", "rule"]).output, + ) + match_order = [r.endpoint for r in app.url_map.iter_rules()] + self.expect_order(match_order, invoke(["routes", "-s", "match"]).output) + + def test_all_methods(self, invoke): + output = invoke(["routes"]).output + assert "GET, HEAD, OPTIONS, POST" not in output + output = invoke(["routes", "--all-methods"]).output + assert "GET, HEAD, OPTIONS, POST" in output + + def test_no_routes(self, runner): + app = Flask(__name__, static_folder=None) + cli = FlaskGroup(create_app=lambda: app) + result = runner.invoke(cli, ["routes"]) + assert result.exit_code == 0 + assert "No routes were registered." in result.output + + def test_subdomain(self, runner): + app = Flask(__name__, static_folder=None) + app.add_url_rule("/a", subdomain="a", endpoint="a") + app.add_url_rule("/b", subdomain="b", endpoint="b") + cli = FlaskGroup(create_app=lambda: app) + result = runner.invoke(cli, ["routes"]) + assert result.exit_code == 0 + assert "Subdomain" in result.output + + def test_host(self, runner): + app = Flask(__name__, static_folder=None, host_matching=True) + app.add_url_rule("/a", host="a", endpoint="a") + app.add_url_rule("/b", host="b", endpoint="b") + cli = FlaskGroup(create_app=lambda: app) + result = runner.invoke(cli, ["routes"]) + assert result.exit_code == 0 + assert "Host" in result.output + + +def dotenv_not_available(): + try: + import dotenv # noqa: F401 + except ImportError: + return True + + return False + + +need_dotenv = pytest.mark.skipif( + dotenv_not_available(), reason="dotenv is not installed" +) + + +@need_dotenv +def test_load_dotenv(monkeypatch): + # can't use monkeypatch.delitem since the keys don't exist yet + for item in ("FOO", "BAR", "SPAM", "HAM"): + monkeypatch._setitem.append((os.environ, item, notset)) + + monkeypatch.setenv("EGGS", "3") + monkeypatch.chdir(test_path) + assert load_dotenv() + assert Path.cwd() == test_path + # .flaskenv doesn't overwrite .env + assert os.environ["FOO"] == "env" + # set only in .flaskenv + assert os.environ["BAR"] == "bar" + # set only in .env + assert os.environ["SPAM"] == "1" + # set manually, files don't overwrite + assert os.environ["EGGS"] == "3" + # test env file encoding + assert os.environ["HAM"] == "火腿" + # Non existent file should not load + assert not load_dotenv("non-existent-file", load_defaults=False) + + +@need_dotenv +def test_dotenv_path(monkeypatch): + for item in ("FOO", "BAR", "EGGS"): + monkeypatch._setitem.append((os.environ, item, notset)) + + load_dotenv(test_path / ".flaskenv") + assert Path.cwd() == cwd + assert "FOO" in os.environ + + +def test_dotenv_optional(monkeypatch): + monkeypatch.setitem(sys.modules, "dotenv", None) + monkeypatch.chdir(test_path) + load_dotenv() + assert "FOO" not in os.environ + + +@need_dotenv +def test_disable_dotenv_from_env(monkeypatch, runner): + monkeypatch.chdir(test_path) + monkeypatch.setitem(os.environ, "FLASK_SKIP_DOTENV", "1") + runner.invoke(FlaskGroup()) + assert "FOO" not in os.environ + + +def test_run_cert_path(): + # no key + with pytest.raises(click.BadParameter): + run_command.make_context("run", ["--cert", __file__]) + + # no cert + with pytest.raises(click.BadParameter): + run_command.make_context("run", ["--key", __file__]) + + # cert specified first + ctx = run_command.make_context("run", ["--cert", __file__, "--key", __file__]) + assert ctx.params["cert"] == (__file__, __file__) + + # key specified first + ctx = run_command.make_context("run", ["--key", __file__, "--cert", __file__]) + assert ctx.params["cert"] == (__file__, __file__) + + +def test_run_cert_adhoc(monkeypatch): + monkeypatch.setitem(sys.modules, "cryptography", None) + + # cryptography not installed + with pytest.raises(click.BadParameter): + run_command.make_context("run", ["--cert", "adhoc"]) + + # cryptography installed + monkeypatch.setitem(sys.modules, "cryptography", types.ModuleType("cryptography")) + ctx = run_command.make_context("run", ["--cert", "adhoc"]) + assert ctx.params["cert"] == "adhoc" + + # no key with adhoc + with pytest.raises(click.BadParameter): + run_command.make_context("run", ["--cert", "adhoc", "--key", __file__]) + + +def test_run_cert_import(monkeypatch): + monkeypatch.setitem(sys.modules, "not_here", None) + + # ImportError + with pytest.raises(click.BadParameter): + run_command.make_context("run", ["--cert", "not_here"]) + + with pytest.raises(click.BadParameter): + run_command.make_context("run", ["--cert", "flask"]) + + # SSLContext + ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + + monkeypatch.setitem(sys.modules, "ssl_context", ssl_context) + ctx = run_command.make_context("run", ["--cert", "ssl_context"]) + assert ctx.params["cert"] is ssl_context + + # no --key with SSLContext + with pytest.raises(click.BadParameter): + run_command.make_context("run", ["--cert", "ssl_context", "--key", __file__]) + + +def test_run_cert_no_ssl(monkeypatch): + monkeypatch.setitem(sys.modules, "ssl", None) + + with pytest.raises(click.BadParameter): + run_command.make_context("run", ["--cert", "not_here"]) + + +def test_cli_blueprints(app): + """Test blueprint commands register correctly to the application""" + custom = Blueprint("custom", __name__, cli_group="customized") + nested = Blueprint("nested", __name__) + merged = Blueprint("merged", __name__, cli_group=None) + late = Blueprint("late", __name__) + + @custom.cli.command("custom") + def custom_command(): + click.echo("custom_result") + + @nested.cli.command("nested") + def nested_command(): + click.echo("nested_result") + + @merged.cli.command("merged") + def merged_command(): + click.echo("merged_result") + + @late.cli.command("late") + def late_command(): + click.echo("late_result") + + app.register_blueprint(custom) + app.register_blueprint(nested) + app.register_blueprint(merged) + app.register_blueprint(late, cli_group="late_registration") + + app_runner = app.test_cli_runner() + + result = app_runner.invoke(args=["customized", "custom"]) + assert "custom_result" in result.output + + result = app_runner.invoke(args=["nested", "nested"]) + assert "nested_result" in result.output + + result = app_runner.invoke(args=["merged"]) + assert "merged_result" in result.output + + result = app_runner.invoke(args=["late_registration", "late"]) + assert "late_result" in result.output + + +def test_cli_empty(app): + """If a Blueprint's CLI group is empty, do not register it.""" + bp = Blueprint("blue", __name__, cli_group="blue") + app.register_blueprint(bp) + + result = app.test_cli_runner().invoke(args=["blue", "--help"]) + assert result.exit_code == 2, f"Unexpected success:\n\n{result.output}" + + +def test_run_exclude_patterns(): + ctx = run_command.make_context("run", ["--exclude-patterns", __file__]) + assert ctx.params["exclude_patterns"] == [__file__] diff --git a/src/flask-main/tests/test_config.py b/src/flask-main/tests/test_config.py new file mode 100644 index 0000000..e5b1906 --- /dev/null +++ b/src/flask-main/tests/test_config.py @@ -0,0 +1,250 @@ +import json +import os + +import pytest + +import flask + +# config keys used for the TestConfig +TEST_KEY = "foo" +SECRET_KEY = "config" + + +def common_object_test(app): + assert app.secret_key == "config" + assert app.config["TEST_KEY"] == "foo" + assert "TestConfig" not in app.config + + +def test_config_from_pyfile(): + app = flask.Flask(__name__) + app.config.from_pyfile(f"{__file__.rsplit('.', 1)[0]}.py") + common_object_test(app) + + +def test_config_from_object(): + app = flask.Flask(__name__) + app.config.from_object(__name__) + common_object_test(app) + + +def test_config_from_file_json(): + app = flask.Flask(__name__) + current_dir = os.path.dirname(os.path.abspath(__file__)) + app.config.from_file(os.path.join(current_dir, "static", "config.json"), json.load) + common_object_test(app) + + +def test_config_from_file_toml(): + tomllib = pytest.importorskip("tomllib", reason="tomllib added in 3.11") + app = flask.Flask(__name__) + current_dir = os.path.dirname(os.path.abspath(__file__)) + app.config.from_file( + os.path.join(current_dir, "static", "config.toml"), tomllib.load, text=False + ) + common_object_test(app) + + +def test_from_prefixed_env(monkeypatch): + monkeypatch.setenv("FLASK_STRING", "value") + monkeypatch.setenv("FLASK_BOOL", "true") + monkeypatch.setenv("FLASK_INT", "1") + monkeypatch.setenv("FLASK_FLOAT", "1.2") + monkeypatch.setenv("FLASK_LIST", "[1, 2]") + monkeypatch.setenv("FLASK_DICT", '{"k": "v"}') + monkeypatch.setenv("NOT_FLASK_OTHER", "other") + + app = flask.Flask(__name__) + app.config.from_prefixed_env() + + assert app.config["STRING"] == "value" + assert app.config["BOOL"] is True + assert app.config["INT"] == 1 + assert app.config["FLOAT"] == 1.2 + assert app.config["LIST"] == [1, 2] + assert app.config["DICT"] == {"k": "v"} + assert "OTHER" not in app.config + + +def test_from_prefixed_env_custom_prefix(monkeypatch): + monkeypatch.setenv("FLASK_A", "a") + monkeypatch.setenv("NOT_FLASK_A", "b") + + app = flask.Flask(__name__) + app.config.from_prefixed_env("NOT_FLASK") + + assert app.config["A"] == "b" + + +def test_from_prefixed_env_nested(monkeypatch): + monkeypatch.setenv("FLASK_EXIST__ok", "other") + monkeypatch.setenv("FLASK_EXIST__inner__ik", "2") + monkeypatch.setenv("FLASK_EXIST__new__more", '{"k": false}') + monkeypatch.setenv("FLASK_NEW__K", "v") + + app = flask.Flask(__name__) + app.config["EXIST"] = {"ok": "value", "flag": True, "inner": {"ik": 1}} + app.config.from_prefixed_env() + + if os.name != "nt": + assert app.config["EXIST"] == { + "ok": "other", + "flag": True, + "inner": {"ik": 2}, + "new": {"more": {"k": False}}, + } + else: + # Windows env var keys are always uppercase. + assert app.config["EXIST"] == { + "ok": "value", + "OK": "other", + "flag": True, + "inner": {"ik": 1}, + "INNER": {"IK": 2}, + "NEW": {"MORE": {"k": False}}, + } + + assert app.config["NEW"] == {"K": "v"} + + +def test_config_from_mapping(): + app = flask.Flask(__name__) + app.config.from_mapping({"SECRET_KEY": "config", "TEST_KEY": "foo"}) + common_object_test(app) + + app = flask.Flask(__name__) + app.config.from_mapping([("SECRET_KEY", "config"), ("TEST_KEY", "foo")]) + common_object_test(app) + + app = flask.Flask(__name__) + app.config.from_mapping(SECRET_KEY="config", TEST_KEY="foo") + common_object_test(app) + + app = flask.Flask(__name__) + app.config.from_mapping(SECRET_KEY="config", TEST_KEY="foo", skip_key="skip") + common_object_test(app) + + app = flask.Flask(__name__) + with pytest.raises(TypeError): + app.config.from_mapping({}, {}) + + +def test_config_from_class(): + class Base: + TEST_KEY = "foo" + + class Test(Base): + SECRET_KEY = "config" + + app = flask.Flask(__name__) + app.config.from_object(Test) + common_object_test(app) + + +def test_config_from_envvar(monkeypatch): + monkeypatch.setattr("os.environ", {}) + app = flask.Flask(__name__) + + with pytest.raises(RuntimeError) as e: + app.config.from_envvar("FOO_SETTINGS") + + assert "'FOO_SETTINGS' is not set" in str(e.value) + assert not app.config.from_envvar("FOO_SETTINGS", silent=True) + + monkeypatch.setattr( + "os.environ", {"FOO_SETTINGS": f"{__file__.rsplit('.', 1)[0]}.py"} + ) + assert app.config.from_envvar("FOO_SETTINGS") + common_object_test(app) + + +def test_config_from_envvar_missing(monkeypatch): + monkeypatch.setattr("os.environ", {"FOO_SETTINGS": "missing.cfg"}) + app = flask.Flask(__name__) + with pytest.raises(IOError) as e: + app.config.from_envvar("FOO_SETTINGS") + msg = str(e.value) + assert msg.startswith( + "[Errno 2] Unable to load configuration file (No such file or directory):" + ) + assert msg.endswith("missing.cfg'") + assert not app.config.from_envvar("FOO_SETTINGS", silent=True) + + +def test_config_missing(): + app = flask.Flask(__name__) + with pytest.raises(IOError) as e: + app.config.from_pyfile("missing.cfg") + msg = str(e.value) + assert msg.startswith( + "[Errno 2] Unable to load configuration file (No such file or directory):" + ) + assert msg.endswith("missing.cfg'") + assert not app.config.from_pyfile("missing.cfg", silent=True) + + +def test_config_missing_file(): + app = flask.Flask(__name__) + with pytest.raises(IOError) as e: + app.config.from_file("missing.json", load=json.load) + msg = str(e.value) + assert msg.startswith( + "[Errno 2] Unable to load configuration file (No such file or directory):" + ) + assert msg.endswith("missing.json'") + assert not app.config.from_file("missing.json", load=json.load, silent=True) + + +def test_custom_config_class(): + class Config(flask.Config): + pass + + class Flask(flask.Flask): + config_class = Config + + app = Flask(__name__) + assert isinstance(app.config, Config) + app.config.from_object(__name__) + common_object_test(app) + + +def test_session_lifetime(): + app = flask.Flask(__name__) + app.config["PERMANENT_SESSION_LIFETIME"] = 42 + assert app.permanent_session_lifetime.seconds == 42 + + +def test_get_namespace(): + app = flask.Flask(__name__) + app.config["FOO_OPTION_1"] = "foo option 1" + app.config["FOO_OPTION_2"] = "foo option 2" + app.config["BAR_STUFF_1"] = "bar stuff 1" + app.config["BAR_STUFF_2"] = "bar stuff 2" + foo_options = app.config.get_namespace("FOO_") + assert 2 == len(foo_options) + assert "foo option 1" == foo_options["option_1"] + assert "foo option 2" == foo_options["option_2"] + bar_options = app.config.get_namespace("BAR_", lowercase=False) + assert 2 == len(bar_options) + assert "bar stuff 1" == bar_options["STUFF_1"] + assert "bar stuff 2" == bar_options["STUFF_2"] + foo_options = app.config.get_namespace("FOO_", trim_namespace=False) + assert 2 == len(foo_options) + assert "foo option 1" == foo_options["foo_option_1"] + assert "foo option 2" == foo_options["foo_option_2"] + bar_options = app.config.get_namespace( + "BAR_", lowercase=False, trim_namespace=False + ) + assert 2 == len(bar_options) + assert "bar stuff 1" == bar_options["BAR_STUFF_1"] + assert "bar stuff 2" == bar_options["BAR_STUFF_2"] + + +@pytest.mark.parametrize("encoding", ["utf-8", "iso-8859-15", "latin-1"]) +def test_from_pyfile_weird_encoding(tmp_path, encoding): + f = tmp_path / "my_config.py" + f.write_text(f'# -*- coding: {encoding} -*-\nTEST_VALUE = "föö"\n', encoding) + app = flask.Flask(__name__) + app.config.from_pyfile(os.fspath(f)) + value = app.config["TEST_VALUE"] + assert value == "föö" diff --git a/src/flask-main/tests/test_converters.py b/src/flask-main/tests/test_converters.py new file mode 100644 index 0000000..d94a765 --- /dev/null +++ b/src/flask-main/tests/test_converters.py @@ -0,0 +1,42 @@ +from werkzeug.routing import BaseConverter + +from flask import request +from flask import session +from flask import url_for + + +def test_custom_converters(app, client): + class ListConverter(BaseConverter): + def to_python(self, value): + return value.split(",") + + def to_url(self, value): + base_to_url = super().to_url + return ",".join(base_to_url(x) for x in value) + + app.url_map.converters["list"] = ListConverter + + @app.route("/") + def index(args): + return "|".join(args) + + assert client.get("/1,2,3").data == b"1|2|3" + + with app.test_request_context(): + assert url_for("index", args=[4, 5, 6]) == "/4,5,6" + + +def test_context_available(app, client): + class ContextConverter(BaseConverter): + def to_python(self, value): + assert request is not None + assert session is not None + return value + + app.url_map.converters["ctx"] = ContextConverter + + @app.get("/") + def index(name): + return name + + assert client.get("/admin").data == b"admin" diff --git a/src/flask-main/tests/test_helpers.py b/src/flask-main/tests/test_helpers.py new file mode 100644 index 0000000..efd22aa --- /dev/null +++ b/src/flask-main/tests/test_helpers.py @@ -0,0 +1,383 @@ +import io +import os + +import pytest +import werkzeug.exceptions + +import flask +from flask.helpers import get_debug_flag + + +class FakePath: + """Fake object to represent a ``PathLike object``. + + This represents a ``pathlib.Path`` object in python 3. + See: https://www.python.org/dev/peps/pep-0519/ + """ + + def __init__(self, path): + self.path = path + + def __fspath__(self): + return self.path + + +class PyBytesIO: + def __init__(self, *args, **kwargs): + self._io = io.BytesIO(*args, **kwargs) + + def __getattr__(self, name): + return getattr(self._io, name) + + +class TestSendfile: + def test_send_file(self, app, req_ctx): + rv = flask.send_file("static/index.html") + assert rv.direct_passthrough + assert rv.mimetype == "text/html" + + with app.open_resource("static/index.html") as f: + rv.direct_passthrough = False + assert rv.data == f.read() + + rv.close() + + def test_static_file(self, app, req_ctx): + # Default max_age is None. + + # Test with static file handler. + rv = app.send_static_file("index.html") + assert rv.cache_control.max_age is None + rv.close() + + # Test with direct use of send_file. + rv = flask.send_file("static/index.html") + assert rv.cache_control.max_age is None + rv.close() + + app.config["SEND_FILE_MAX_AGE_DEFAULT"] = 3600 + + # Test with static file handler. + rv = app.send_static_file("index.html") + assert rv.cache_control.max_age == 3600 + rv.close() + + # Test with direct use of send_file. + rv = flask.send_file("static/index.html") + assert rv.cache_control.max_age == 3600 + rv.close() + + # Test with pathlib.Path. + rv = app.send_static_file(FakePath("index.html")) + assert rv.cache_control.max_age == 3600 + rv.close() + + class StaticFileApp(flask.Flask): + def get_send_file_max_age(self, filename): + return 10 + + app = StaticFileApp(__name__) + + with app.test_request_context(): + # Test with static file handler. + rv = app.send_static_file("index.html") + assert rv.cache_control.max_age == 10 + rv.close() + + # Test with direct use of send_file. + rv = flask.send_file("static/index.html") + assert rv.cache_control.max_age == 10 + rv.close() + + def test_send_from_directory(self, app, req_ctx): + app.root_path = os.path.join( + os.path.dirname(__file__), "test_apps", "subdomaintestmodule" + ) + rv = flask.send_from_directory("static", "hello.txt") + rv.direct_passthrough = False + assert rv.data.strip() == b"Hello Subdomain" + rv.close() + + +class TestUrlFor: + def test_url_for_with_anchor(self, app, req_ctx): + @app.route("/") + def index(): + return "42" + + assert flask.url_for("index", _anchor="x y") == "/#x%20y" + + def test_url_for_with_scheme(self, app, req_ctx): + @app.route("/") + def index(): + return "42" + + assert ( + flask.url_for("index", _external=True, _scheme="https") + == "https://localhost/" + ) + + def test_url_for_with_scheme_not_external(self, app, req_ctx): + app.add_url_rule("/", endpoint="index") + + # Implicit external with scheme. + url = flask.url_for("index", _scheme="https") + assert url == "https://localhost/" + + # Error when external=False with scheme + with pytest.raises(ValueError): + flask.url_for("index", _scheme="https", _external=False) + + def test_url_for_with_alternating_schemes(self, app, req_ctx): + @app.route("/") + def index(): + return "42" + + assert flask.url_for("index", _external=True) == "http://localhost/" + assert ( + flask.url_for("index", _external=True, _scheme="https") + == "https://localhost/" + ) + assert flask.url_for("index", _external=True) == "http://localhost/" + + def test_url_with_method(self, app, req_ctx): + from flask.views import MethodView + + class MyView(MethodView): + def get(self, id=None): + if id is None: + return "List" + return f"Get {id:d}" + + def post(self): + return "Create" + + myview = MyView.as_view("myview") + app.add_url_rule("/myview/", methods=["GET"], view_func=myview) + app.add_url_rule("/myview/", methods=["GET"], view_func=myview) + app.add_url_rule("/myview/create", methods=["POST"], view_func=myview) + + assert flask.url_for("myview", _method="GET") == "/myview/" + assert flask.url_for("myview", id=42, _method="GET") == "/myview/42" + assert flask.url_for("myview", _method="POST") == "/myview/create" + + def test_url_for_with_self(self, app, req_ctx): + @app.route("/") + def index(self): + return "42" + + assert flask.url_for("index", self="2") == "/2" + + +def test_redirect_no_app(): + response = flask.redirect("https://localhost", 307) + assert response.location == "https://localhost" + assert response.status_code == 307 + + +def test_redirect_with_app(app): + def redirect(location, code=302): + raise ValueError + + app.redirect = redirect + + with app.app_context(), pytest.raises(ValueError): + flask.redirect("other") + + +def test_abort_no_app(): + with pytest.raises(werkzeug.exceptions.Unauthorized): + flask.abort(401) + + with pytest.raises(LookupError): + flask.abort(900) + + +def test_app_aborter_class(): + class MyAborter(werkzeug.exceptions.Aborter): + pass + + class MyFlask(flask.Flask): + aborter_class = MyAborter + + app = MyFlask(__name__) + assert isinstance(app.aborter, MyAborter) + + +def test_abort_with_app(app): + class My900Error(werkzeug.exceptions.HTTPException): + code = 900 + + app.aborter.mapping[900] = My900Error + + with app.app_context(), pytest.raises(My900Error): + flask.abort(900) + + +class TestNoImports: + """Test Flasks are created without import. + + Avoiding ``__import__`` helps create Flask instances where there are errors + at import time. Those runtime errors will be apparent to the user soon + enough, but tools which build Flask instances meta-programmatically benefit + from a Flask which does not ``__import__``. Instead of importing to + retrieve file paths or metadata on a module or package, use the pkgutil and + imp modules in the Python standard library. + """ + + def test_name_with_import_error(self, modules_tmp_path): + (modules_tmp_path / "importerror.py").write_text("raise NotImplementedError()") + try: + flask.Flask("importerror") + except NotImplementedError: + AssertionError("Flask(import_name) is importing import_name.") + + +class TestStreaming: + def test_streaming_with_context(self, app, client): + @app.route("/") + def index(): + def generate(): + yield "Hello " + yield flask.request.args["name"] + yield "!" + + return flask.Response(flask.stream_with_context(generate())) + + rv = client.get("/?name=World") + assert rv.data == b"Hello World!" + + def test_streaming_with_context_as_decorator(self, app, client): + @app.route("/") + def index(): + @flask.stream_with_context + def generate(hello): + yield hello + yield flask.request.args["name"] + yield "!" + + return flask.Response(generate("Hello ")) + + rv = client.get("/?name=World") + assert rv.data == b"Hello World!" + + def test_streaming_with_context_and_custom_close(self, app, client): + called = [] + + class Wrapper: + def __init__(self, gen): + self._gen = gen + + def __iter__(self): + return self + + def close(self): + called.append(42) + + def __next__(self): + return next(self._gen) + + next = __next__ + + @app.route("/") + def index(): + def generate(): + yield "Hello " + yield flask.request.args["name"] + yield "!" + + return flask.Response(flask.stream_with_context(Wrapper(generate()))) + + rv = client.get("/?name=World") + assert rv.data == b"Hello World!" + assert called == [42] + + def test_stream_keeps_session(self, app, client): + @app.route("/") + def index(): + flask.session["test"] = "flask" + + @flask.stream_with_context + def gen(): + yield flask.session["test"] + + return flask.Response(gen()) + + rv = client.get("/") + assert rv.data == b"flask" + + def test_async_view(self, app, client): + @app.route("/") + async def index(): + flask.session["test"] = "flask" + + @flask.stream_with_context + def gen(): + yield flask.session["test"] + + return flask.Response(gen()) + + # response is closed without reading stream + client.get().close() + # response stream is read + assert client.get().text == "flask" + + # same as above, but with client context preservation + with client: + client.get().close() + + with client: + assert client.get().text == "flask" + + +class TestHelpers: + @pytest.mark.parametrize( + ("debug", "expect"), + [ + ("", False), + ("0", False), + ("False", False), + ("No", False), + ("True", True), + ], + ) + def test_get_debug_flag(self, monkeypatch, debug, expect): + monkeypatch.setenv("FLASK_DEBUG", debug) + assert get_debug_flag() == expect + + def test_make_response(self): + app = flask.Flask(__name__) + with app.test_request_context(): + rv = flask.helpers.make_response() + assert rv.status_code == 200 + assert rv.mimetype == "text/html" + + rv = flask.helpers.make_response("Hello") + assert rv.status_code == 200 + assert rv.data == b"Hello" + assert rv.mimetype == "text/html" + + +@pytest.mark.parametrize("mode", ("r", "rb", "rt")) +def test_open_resource(mode): + app = flask.Flask(__name__) + + with app.open_resource("static/index.html", mode) as f: + assert "

Hello World!

" in str(f.read()) + + +@pytest.mark.parametrize("mode", ("w", "x", "a", "r+")) +def test_open_resource_exceptions(mode): + app = flask.Flask(__name__) + + with pytest.raises(ValueError): + app.open_resource("static/index.html", mode) + + +@pytest.mark.parametrize("encoding", ("utf-8", "utf-16-le")) +def test_open_resource_with_encoding(tmp_path, encoding): + app = flask.Flask(__name__, root_path=os.fspath(tmp_path)) + (tmp_path / "test").write_text("test", encoding=encoding) + + with app.open_resource("test", mode="rt", encoding=encoding) as f: + assert f.read() == "test" diff --git a/src/flask-main/tests/test_instance_config.py b/src/flask-main/tests/test_instance_config.py new file mode 100644 index 0000000..835a878 --- /dev/null +++ b/src/flask-main/tests/test_instance_config.py @@ -0,0 +1,111 @@ +import os + +import pytest + +import flask + + +def test_explicit_instance_paths(modules_tmp_path): + with pytest.raises(ValueError, match=".*must be absolute"): + flask.Flask(__name__, instance_path="instance") + + app = flask.Flask(__name__, instance_path=os.fspath(modules_tmp_path)) + assert app.instance_path == os.fspath(modules_tmp_path) + + +def test_uninstalled_module_paths(modules_tmp_path, purge_module): + (modules_tmp_path / "config_module_app.py").write_text( + "import os\n" + "import flask\n" + "here = os.path.abspath(os.path.dirname(__file__))\n" + "app = flask.Flask(__name__)\n" + ) + purge_module("config_module_app") + + from config_module_app import app + + assert app.instance_path == os.fspath(modules_tmp_path / "instance") + + +def test_uninstalled_package_paths(modules_tmp_path, purge_module): + app = modules_tmp_path / "config_package_app" + app.mkdir() + (app / "__init__.py").write_text( + "import os\n" + "import flask\n" + "here = os.path.abspath(os.path.dirname(__file__))\n" + "app = flask.Flask(__name__)\n" + ) + purge_module("config_package_app") + + from config_package_app import app + + assert app.instance_path == os.fspath(modules_tmp_path / "instance") + + +def test_uninstalled_namespace_paths(tmp_path, monkeypatch, purge_module): + def create_namespace(package): + project = tmp_path / f"project-{package}" + monkeypatch.syspath_prepend(os.fspath(project)) + ns = project / "namespace" / package + ns.mkdir(parents=True) + (ns / "__init__.py").write_text("import flask\napp = flask.Flask(__name__)\n") + return project + + _ = create_namespace("package1") + project2 = create_namespace("package2") + purge_module("namespace.package2") + purge_module("namespace") + + from namespace.package2 import app + + assert app.instance_path == os.fspath(project2 / "instance") + + +def test_installed_module_paths( + modules_tmp_path, modules_tmp_path_prefix, purge_module, site_packages +): + (site_packages / "site_app.py").write_text( + "import flask\napp = flask.Flask(__name__)\n" + ) + purge_module("site_app") + + from site_app import app + + assert app.instance_path == os.fspath( + modules_tmp_path / "var" / "site_app-instance" + ) + + +def test_installed_package_paths( + modules_tmp_path, modules_tmp_path_prefix, purge_module, monkeypatch +): + installed_path = modules_tmp_path / "path" + installed_path.mkdir() + monkeypatch.syspath_prepend(installed_path) + + app = installed_path / "installed_package" + app.mkdir() + (app / "__init__.py").write_text("import flask\napp = flask.Flask(__name__)\n") + purge_module("installed_package") + + from installed_package import app + + assert app.instance_path == os.fspath( + modules_tmp_path / "var" / "installed_package-instance" + ) + + +def test_prefix_package_paths( + modules_tmp_path, modules_tmp_path_prefix, purge_module, site_packages +): + app = site_packages / "site_package" + app.mkdir() + (app / "__init__.py").write_text("import flask\napp = flask.Flask(__name__)\n") + purge_module("site_package") + + import site_package + + assert site_package.app.instance_path == os.fspath( + modules_tmp_path / "var" / "site_package-instance" + ) diff --git a/src/flask-main/tests/test_json.py b/src/flask-main/tests/test_json.py new file mode 100644 index 0000000..1e2b27d --- /dev/null +++ b/src/flask-main/tests/test_json.py @@ -0,0 +1,346 @@ +import datetime +import decimal +import io +import uuid + +import pytest +from werkzeug.http import http_date + +import flask +from flask import json +from flask.json.provider import DefaultJSONProvider + + +@pytest.mark.parametrize("debug", (True, False)) +def test_bad_request_debug_message(app, client, debug): + app.config["DEBUG"] = debug + app.config["TRAP_BAD_REQUEST_ERRORS"] = False + + @app.route("/json", methods=["POST"]) + def post_json(): + flask.request.get_json() + return None + + rv = client.post("/json", data=None, content_type="application/json") + assert rv.status_code == 400 + contains = b"Failed to decode JSON object" in rv.data + assert contains == debug + + +def test_json_bad_requests(app, client): + @app.route("/json", methods=["POST"]) + def return_json(): + return flask.jsonify(foo=str(flask.request.get_json())) + + rv = client.post("/json", data="malformed", content_type="application/json") + assert rv.status_code == 400 + + +def test_json_custom_mimetypes(app, client): + @app.route("/json", methods=["POST"]) + def return_json(): + return flask.request.get_json() + + rv = client.post("/json", data='"foo"', content_type="application/x+json") + assert rv.data == b"foo" + + +@pytest.mark.parametrize( + "test_value,expected", [(True, '"\\u2603"'), (False, '"\u2603"')] +) +def test_json_as_unicode(test_value, expected, app, app_ctx): + app.json.ensure_ascii = test_value + rv = app.json.dumps("\N{SNOWMAN}") + assert rv == expected + + +def test_json_dump_to_file(app, app_ctx): + test_data = {"name": "Flask"} + out = io.StringIO() + + flask.json.dump(test_data, out) + out.seek(0) + rv = flask.json.load(out) + assert rv == test_data + + +@pytest.mark.parametrize( + "test_value", [0, -1, 1, 23, 3.14, "s", "longer string", True, False, None] +) +def test_jsonify_basic_types(test_value, app, client): + url = "/jsonify_basic_types" + app.add_url_rule(url, url, lambda x=test_value: flask.jsonify(x)) + rv = client.get(url) + assert rv.mimetype == "application/json" + assert flask.json.loads(rv.data) == test_value + + +def test_jsonify_dicts(app, client): + d = { + "a": 0, + "b": 23, + "c": 3.14, + "d": "t", + "e": "Hi", + "f": True, + "g": False, + "h": ["test list", 10, False], + "i": {"test": "dict"}, + } + + @app.route("/kw") + def return_kwargs(): + return flask.jsonify(**d) + + @app.route("/dict") + def return_dict(): + return flask.jsonify(d) + + for url in "/kw", "/dict": + rv = client.get(url) + assert rv.mimetype == "application/json" + assert flask.json.loads(rv.data) == d + + +def test_jsonify_arrays(app, client): + """Test jsonify of lists and args unpacking.""" + a_list = [ + 0, + 42, + 3.14, + "t", + "hello", + True, + False, + ["test list", 2, False], + {"test": "dict"}, + ] + + @app.route("/args_unpack") + def return_args_unpack(): + return flask.jsonify(*a_list) + + @app.route("/array") + def return_array(): + return flask.jsonify(a_list) + + for url in "/args_unpack", "/array": + rv = client.get(url) + assert rv.mimetype == "application/json" + assert flask.json.loads(rv.data) == a_list + + +@pytest.mark.parametrize( + "value", [datetime.datetime(1973, 3, 11, 6, 30, 45), datetime.date(1975, 1, 5)] +) +def test_jsonify_datetime(app, client, value): + @app.route("/") + def index(): + return flask.jsonify(value=value) + + r = client.get() + assert r.json["value"] == http_date(value) + + +class FixedOffset(datetime.tzinfo): + """Fixed offset in hours east from UTC. + + This is a slight adaptation of the ``FixedOffset`` example found in + https://docs.python.org/2.7/library/datetime.html. + """ + + def __init__(self, hours, name): + self.__offset = datetime.timedelta(hours=hours) + self.__name = name + + def utcoffset(self, dt): + return self.__offset + + def tzname(self, dt): + return self.__name + + def dst(self, dt): + return datetime.timedelta() + + +@pytest.mark.parametrize("tz", (("UTC", 0), ("PST", -8), ("KST", 9))) +def test_jsonify_aware_datetimes(tz): + """Test if aware datetime.datetime objects are converted into GMT.""" + tzinfo = FixedOffset(hours=tz[1], name=tz[0]) + dt = datetime.datetime(2017, 1, 1, 12, 34, 56, tzinfo=tzinfo) + gmt = FixedOffset(hours=0, name="GMT") + expected = dt.astimezone(gmt).strftime('"%a, %d %b %Y %H:%M:%S %Z"') + assert flask.json.dumps(dt) == expected + + +def test_jsonify_uuid_types(app, client): + """Test jsonify with uuid.UUID types""" + + test_uuid = uuid.UUID(bytes=b"\xde\xad\xbe\xef" * 4) + url = "/uuid_test" + app.add_url_rule(url, url, lambda: flask.jsonify(x=test_uuid)) + + rv = client.get(url) + + rv_x = flask.json.loads(rv.data)["x"] + assert rv_x == str(test_uuid) + rv_uuid = uuid.UUID(rv_x) + assert rv_uuid == test_uuid + + +def test_json_decimal(): + rv = flask.json.dumps(decimal.Decimal("0.003")) + assert rv == '"0.003"' + + +def test_json_attr(app, client): + @app.route("/add", methods=["POST"]) + def add(): + json = flask.request.get_json() + return str(json["a"] + json["b"]) + + rv = client.post( + "/add", + data=flask.json.dumps({"a": 1, "b": 2}), + content_type="application/json", + ) + assert rv.data == b"3" + + +def test_tojson_filter(app, req_ctx): + # The tojson filter is tested in Jinja, this confirms that it's + # using Flask's dumps. + rv = flask.render_template_string( + "const data = {{ data|tojson }};", + data={"name": "", "time": datetime.datetime(2021, 2, 1, 7, 15)}, + ) + assert rv == ( + 'const data = {"name": "\\u003c/script\\u003e",' + ' "time": "Mon, 01 Feb 2021 07:15:00 GMT"};' + ) + + +def test_json_customization(app, client): + class X: # noqa: B903, for Python2 compatibility + def __init__(self, val): + self.val = val + + def default(o): + if isinstance(o, X): + return f"<{o.val}>" + + return DefaultJSONProvider.default(o) + + class CustomProvider(DefaultJSONProvider): + def object_hook(self, obj): + if len(obj) == 1 and "_foo" in obj: + return X(obj["_foo"]) + + return obj + + def loads(self, s, **kwargs): + kwargs.setdefault("object_hook", self.object_hook) + return super().loads(s, **kwargs) + + app.json = CustomProvider(app) + app.json.default = default + + @app.route("/", methods=["POST"]) + def index(): + return flask.json.dumps(flask.request.get_json()["x"]) + + rv = client.post( + "/", + data=flask.json.dumps({"x": {"_foo": 42}}), + content_type="application/json", + ) + assert rv.data == b'"<42>"' + + +def _has_encoding(name): + try: + import codecs + + codecs.lookup(name) + return True + except LookupError: + return False + + +def test_json_key_sorting(app, client): + app.debug = True + assert app.json.sort_keys + d = dict.fromkeys(range(20), "foo") + + @app.route("/") + def index(): + return flask.jsonify(values=d) + + rv = client.get("/") + lines = [x.strip() for x in rv.data.strip().decode("utf-8").splitlines()] + sorted_by_str = [ + "{", + '"values": {', + '"0": "foo",', + '"1": "foo",', + '"10": "foo",', + '"11": "foo",', + '"12": "foo",', + '"13": "foo",', + '"14": "foo",', + '"15": "foo",', + '"16": "foo",', + '"17": "foo",', + '"18": "foo",', + '"19": "foo",', + '"2": "foo",', + '"3": "foo",', + '"4": "foo",', + '"5": "foo",', + '"6": "foo",', + '"7": "foo",', + '"8": "foo",', + '"9": "foo"', + "}", + "}", + ] + sorted_by_int = [ + "{", + '"values": {', + '"0": "foo",', + '"1": "foo",', + '"2": "foo",', + '"3": "foo",', + '"4": "foo",', + '"5": "foo",', + '"6": "foo",', + '"7": "foo",', + '"8": "foo",', + '"9": "foo",', + '"10": "foo",', + '"11": "foo",', + '"12": "foo",', + '"13": "foo",', + '"14": "foo",', + '"15": "foo",', + '"16": "foo",', + '"17": "foo",', + '"18": "foo",', + '"19": "foo"', + "}", + "}", + ] + + try: + assert lines == sorted_by_int + except AssertionError: + assert lines == sorted_by_str + + +def test_html_method(): + class ObjectWithHTML: + def __html__(self): + return "

test

" + + result = json.dumps(ObjectWithHTML()) + assert result == '"

test

"' diff --git a/src/flask-main/tests/test_json_tag.py b/src/flask-main/tests/test_json_tag.py new file mode 100644 index 0000000..677160a --- /dev/null +++ b/src/flask-main/tests/test_json_tag.py @@ -0,0 +1,86 @@ +from datetime import datetime +from datetime import timezone +from uuid import uuid4 + +import pytest +from markupsafe import Markup + +from flask.json.tag import JSONTag +from flask.json.tag import TaggedJSONSerializer + + +@pytest.mark.parametrize( + "data", + ( + {" t": (1, 2, 3)}, + {" t__": b"a"}, + {" di": " di"}, + {"x": (1, 2, 3), "y": 4}, + (1, 2, 3), + [(1, 2, 3)], + b"\xff", + Markup(""), + uuid4(), + datetime.now(tz=timezone.utc).replace(microsecond=0), + ), +) +def test_dump_load_unchanged(data): + s = TaggedJSONSerializer() + assert s.loads(s.dumps(data)) == data + + +def test_duplicate_tag(): + class TagDict(JSONTag): + key = " d" + + s = TaggedJSONSerializer() + pytest.raises(KeyError, s.register, TagDict) + s.register(TagDict, force=True, index=0) + assert isinstance(s.tags[" d"], TagDict) + assert isinstance(s.order[0], TagDict) + + +def test_custom_tag(): + class Foo: # noqa: B903, for Python2 compatibility + def __init__(self, data): + self.data = data + + class TagFoo(JSONTag): + __slots__ = () + key = " f" + + def check(self, value): + return isinstance(value, Foo) + + def to_json(self, value): + return self.serializer.tag(value.data) + + def to_python(self, value): + return Foo(value) + + s = TaggedJSONSerializer() + s.register(TagFoo) + assert s.loads(s.dumps(Foo("bar"))).data == "bar" + + +def test_tag_interface(): + t = JSONTag(None) + pytest.raises(NotImplementedError, t.check, None) + pytest.raises(NotImplementedError, t.to_json, None) + pytest.raises(NotImplementedError, t.to_python, None) + + +def test_tag_order(): + class Tag1(JSONTag): + key = " 1" + + class Tag2(JSONTag): + key = " 2" + + s = TaggedJSONSerializer() + + s.register(Tag1, index=-1) + assert isinstance(s.order[-2], Tag1) + + s.register(Tag2, index=None) + assert isinstance(s.order[-1], Tag2) diff --git a/src/flask-main/tests/test_logging.py b/src/flask-main/tests/test_logging.py new file mode 100644 index 0000000..a5f0463 --- /dev/null +++ b/src/flask-main/tests/test_logging.py @@ -0,0 +1,98 @@ +import logging +import sys +from io import StringIO + +import pytest + +from flask.logging import default_handler +from flask.logging import has_level_handler +from flask.logging import wsgi_errors_stream + + +@pytest.fixture(autouse=True) +def reset_logging(pytestconfig): + root_handlers = logging.root.handlers[:] + logging.root.handlers = [] + root_level = logging.root.level + + logger = logging.getLogger("flask_test") + logger.handlers = [] + logger.setLevel(logging.NOTSET) + + logging_plugin = pytestconfig.pluginmanager.unregister(name="logging-plugin") + + yield + + logging.root.handlers[:] = root_handlers + logging.root.setLevel(root_level) + + logger.handlers = [] + logger.setLevel(logging.NOTSET) + + if logging_plugin: + pytestconfig.pluginmanager.register(logging_plugin, "logging-plugin") + + +def test_logger(app): + assert app.logger.name == "flask_test" + assert app.logger.level == logging.NOTSET + assert app.logger.handlers == [default_handler] + + +def test_logger_debug(app): + app.debug = True + assert app.logger.level == logging.DEBUG + assert app.logger.handlers == [default_handler] + + +def test_existing_handler(app): + logging.root.addHandler(logging.StreamHandler()) + assert app.logger.level == logging.NOTSET + assert not app.logger.handlers + + +def test_wsgi_errors_stream(app, client): + @app.route("/") + def index(): + app.logger.error("test") + return "" + + stream = StringIO() + client.get("/", errors_stream=stream) + assert "ERROR in test_logging: test" in stream.getvalue() + + assert wsgi_errors_stream._get_current_object() is sys.stderr + + with app.test_request_context(errors_stream=stream): + assert wsgi_errors_stream._get_current_object() is stream + + +def test_has_level_handler(): + logger = logging.getLogger("flask.app") + assert not has_level_handler(logger) + + handler = logging.StreamHandler() + logging.root.addHandler(handler) + assert has_level_handler(logger) + + logger.propagate = False + assert not has_level_handler(logger) + logger.propagate = True + + handler.setLevel(logging.ERROR) + assert not has_level_handler(logger) + + +def test_log_view_exception(app, client): + @app.route("/") + def index(): + raise Exception("test") + + app.testing = False + stream = StringIO() + rv = client.get("/", errors_stream=stream) + assert rv.status_code == 500 + assert rv.data + err = stream.getvalue() + assert "Exception on / [GET]" in err + assert "Exception: test" in err diff --git a/src/flask-main/tests/test_regression.py b/src/flask-main/tests/test_regression.py new file mode 100644 index 0000000..0ddcf97 --- /dev/null +++ b/src/flask-main/tests/test_regression.py @@ -0,0 +1,30 @@ +import flask + + +def test_aborting(app): + class Foo(Exception): + whatever = 42 + + @app.errorhandler(Foo) + def handle_foo(e): + return str(e.whatever) + + @app.route("/") + def index(): + raise flask.abort(flask.redirect(flask.url_for("test"))) + + @app.route("/test") + def test(): + raise Foo() + + with app.test_client() as c: + rv = c.get("/") + location_parts = rv.headers["Location"].rpartition("/") + + if location_parts[0]: + # For older Werkzeug that used absolute redirects. + assert location_parts[0] == "http://localhost" + + assert location_parts[2] == "test" + rv = c.get("/test") + assert rv.data == b"42" diff --git a/src/flask-main/tests/test_reqctx.py b/src/flask-main/tests/test_reqctx.py new file mode 100644 index 0000000..78561f5 --- /dev/null +++ b/src/flask-main/tests/test_reqctx.py @@ -0,0 +1,326 @@ +import warnings + +import pytest + +import flask +from flask.globals import app_ctx +from flask.sessions import SecureCookieSessionInterface +from flask.sessions import SessionInterface + +try: + from greenlet import greenlet +except ImportError: + greenlet = None + + +def test_teardown_on_pop(app): + buffer = [] + + @app.teardown_request + def end_of_request(exception): + buffer.append(exception) + + ctx = app.test_request_context() + ctx.push() + assert buffer == [] + ctx.pop() + assert buffer == [None] + + +def test_teardown_with_previous_exception(app): + buffer = [] + + @app.teardown_request + def end_of_request(exception): + buffer.append(exception) + + try: + raise Exception("dummy") + except Exception: + pass + + with app.test_request_context(): + assert buffer == [] + assert buffer == [None] + + +def test_teardown_with_handled_exception(app): + buffer = [] + + @app.teardown_request + def end_of_request(exception): + buffer.append(exception) + + with app.test_request_context(): + assert buffer == [] + try: + raise Exception("dummy") + except Exception: + pass + assert buffer == [None] + + +def test_proper_test_request_context(app): + app.config.update(SERVER_NAME="localhost.localdomain:5000") + + @app.route("/") + def index(): + return None + + @app.route("/", subdomain="foo") + def sub(): + return None + + with app.test_request_context("/"): + assert ( + flask.url_for("index", _external=True) + == "http://localhost.localdomain:5000/" + ) + + with app.test_request_context("/"): + assert ( + flask.url_for("sub", _external=True) + == "http://foo.localhost.localdomain:5000/" + ) + + # suppress Werkzeug 0.15 warning about name mismatch + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", "Current server name", UserWarning, "flask.app" + ) + with app.test_request_context( + "/", environ_overrides={"HTTP_HOST": "localhost"} + ): + pass + + app.config.update(SERVER_NAME="localhost") + with app.test_request_context("/", environ_overrides={"SERVER_NAME": "localhost"}): + pass + + app.config.update(SERVER_NAME="localhost:80") + with app.test_request_context( + "/", environ_overrides={"SERVER_NAME": "localhost:80"} + ): + pass + + +def test_context_binding(app): + @app.route("/") + def index(): + return f"Hello {flask.request.args['name']}!" + + @app.route("/meh") + def meh(): + return flask.request.url + + with app.test_request_context("/?name=World"): + assert index() == "Hello World!" + with app.test_request_context("/meh"): + assert meh() == "http://localhost/meh" + assert not flask.request + + +def test_context_test(app): + assert not flask.request + assert not flask.has_request_context() + ctx = app.test_request_context() + ctx.push() + try: + assert flask.request + assert flask.has_request_context() + finally: + ctx.pop() + + +def test_manual_context_binding(app): + @app.route("/") + def index(): + return f"Hello {flask.request.args['name']}!" + + ctx = app.test_request_context("/?name=World") + ctx.push() + assert index() == "Hello World!" + ctx.pop() + with pytest.raises(RuntimeError): + index() + + +@pytest.mark.skipif(greenlet is None, reason="greenlet not installed") +class TestGreenletContextCopying: + def test_greenlet_context_copying(self, app, client): + greenlets = [] + + @app.route("/") + def index(): + flask.session["fizz"] = "buzz" + ctx = app_ctx.copy() + + def g(): + assert not flask.request + assert not flask.current_app + with ctx: + assert flask.request + assert flask.current_app == app + assert flask.request.path == "/" + assert flask.request.args["foo"] == "bar" + assert flask.session.get("fizz") == "buzz" + assert not flask.request + return 42 + + greenlets.append(greenlet(g)) + return "Hello World!" + + rv = client.get("/?foo=bar") + assert rv.data == b"Hello World!" + + result = greenlets[0].run() + assert result == 42 + + def test_greenlet_context_copying_api(self, app, client): + greenlets = [] + + @app.route("/") + def index(): + flask.session["fizz"] = "buzz" + + @flask.copy_current_request_context + def g(): + assert flask.request + assert flask.current_app == app + assert flask.request.path == "/" + assert flask.request.args["foo"] == "bar" + assert flask.session.get("fizz") == "buzz" + return 42 + + greenlets.append(greenlet(g)) + return "Hello World!" + + rv = client.get("/?foo=bar") + assert rv.data == b"Hello World!" + + result = greenlets[0].run() + assert result == 42 + + +def test_session_error_pops_context(): + class SessionError(Exception): + pass + + class FailingSessionInterface(SessionInterface): + def open_session(self, app, request): + raise SessionError() + + class CustomFlask(flask.Flask): + session_interface = FailingSessionInterface() + + app = CustomFlask(__name__) + + @app.route("/") + def index(): + # shouldn't get here + AssertionError() + + response = app.test_client().get("/") + assert response.status_code == 500 + assert not flask.request + assert not flask.current_app + + +def test_session_dynamic_cookie_name(): + # This session interface will use a cookie with a different name if the + # requested url ends with the string "dynamic_cookie" + class PathAwareSessionInterface(SecureCookieSessionInterface): + def get_cookie_name(self, app): + if flask.request.url.endswith("dynamic_cookie"): + return "dynamic_cookie_name" + else: + return super().get_cookie_name(app) + + class CustomFlask(flask.Flask): + session_interface = PathAwareSessionInterface() + + app = CustomFlask(__name__) + app.secret_key = "secret_key" + + @app.route("/set", methods=["POST"]) + def set(): + flask.session["value"] = flask.request.form["value"] + return "value set" + + @app.route("/get") + def get(): + v = flask.session.get("value", "None") + return v + + @app.route("/set_dynamic_cookie", methods=["POST"]) + def set_dynamic_cookie(): + flask.session["value"] = flask.request.form["value"] + return "value set" + + @app.route("/get_dynamic_cookie") + def get_dynamic_cookie(): + v = flask.session.get("value", "None") + return v + + test_client = app.test_client() + + # first set the cookie in both /set urls but each with a different value + assert test_client.post("/set", data={"value": "42"}).data == b"value set" + assert ( + test_client.post("/set_dynamic_cookie", data={"value": "616"}).data + == b"value set" + ) + + # now check that the relevant values come back - meaning that different + # cookies are being used for the urls that end with "dynamic cookie" + assert test_client.get("/get").data == b"42" + assert test_client.get("/get_dynamic_cookie").data == b"616" + + +def test_bad_environ_raises_bad_request(): + app = flask.Flask(__name__) + + from flask.testing import EnvironBuilder + + builder = EnvironBuilder(app) + environ = builder.get_environ() + + # use a non-printable character in the Host - this is key to this test + environ["HTTP_HOST"] = "\x8a" + + with app.request_context(environ) as ctx: + response = app.full_dispatch_request(ctx) + + assert response.status_code == 400 + + +def test_environ_for_valid_idna_completes(): + app = flask.Flask(__name__) + + @app.route("/") + def index(): + return "Hello World!" + + from flask.testing import EnvironBuilder + + builder = EnvironBuilder(app) + environ = builder.get_environ() + + # these characters are all IDNA-compatible + environ["HTTP_HOST"] = "ąśźäüжŠßя.com" + + with app.request_context(environ) as ctx: + response = app.full_dispatch_request(ctx) + + assert response.status_code == 200 + + +def test_normal_environ_completes(): + app = flask.Flask(__name__) + + @app.route("/") + def index(): + return "Hello World!" + + response = app.test_client().get("/", headers={"host": "xn--on-0ia.com"}) + assert response.status_code == 200 diff --git a/src/flask-main/tests/test_request.py b/src/flask-main/tests/test_request.py new file mode 100644 index 0000000..3e95ab3 --- /dev/null +++ b/src/flask-main/tests/test_request.py @@ -0,0 +1,70 @@ +from __future__ import annotations + +from flask import Flask +from flask import Request +from flask import request +from flask.testing import FlaskClient + + +def test_max_content_length(app: Flask, client: FlaskClient) -> None: + app.config["MAX_CONTENT_LENGTH"] = 50 + + @app.post("/") + def index(): + request.form["myfile"] + AssertionError() + + @app.errorhandler(413) + def catcher(error): + return "42" + + rv = client.post("/", data={"myfile": "foo" * 50}) + assert rv.data == b"42" + + +def test_limit_config(app: Flask): + app.config["MAX_CONTENT_LENGTH"] = 100 + app.config["MAX_FORM_MEMORY_SIZE"] = 50 + app.config["MAX_FORM_PARTS"] = 3 + r = Request({}) + + # no app context, use Werkzeug defaults + assert r.max_content_length is None + assert r.max_form_memory_size == 500_000 + assert r.max_form_parts == 1_000 + + # in app context, use config + with app.app_context(): + assert r.max_content_length == 100 + assert r.max_form_memory_size == 50 + assert r.max_form_parts == 3 + + # regardless of app context, use override + r.max_content_length = 90 + r.max_form_memory_size = 30 + r.max_form_parts = 4 + + assert r.max_content_length == 90 + assert r.max_form_memory_size == 30 + assert r.max_form_parts == 4 + + with app.app_context(): + assert r.max_content_length == 90 + assert r.max_form_memory_size == 30 + assert r.max_form_parts == 4 + + +def test_trusted_hosts_config(app: Flask) -> None: + app.config["TRUSTED_HOSTS"] = ["example.test", ".other.test"] + + @app.get("/") + def index() -> str: + return "" + + client = app.test_client() + r = client.get(base_url="http://example.test") + assert r.status_code == 200 + r = client.get(base_url="http://a.other.test") + assert r.status_code == 200 + r = client.get(base_url="http://bad.test") + assert r.status_code == 400 diff --git a/src/flask-main/tests/test_session_interface.py b/src/flask-main/tests/test_session_interface.py new file mode 100644 index 0000000..5564be7 --- /dev/null +++ b/src/flask-main/tests/test_session_interface.py @@ -0,0 +1,28 @@ +import flask +from flask.globals import app_ctx +from flask.sessions import SessionInterface + + +def test_open_session_with_endpoint(): + """If request.endpoint (or other URL matching behavior) is needed + while loading the session, RequestContext.match_request() can be + called manually. + """ + + class MySessionInterface(SessionInterface): + def save_session(self, app, session, response): + pass + + def open_session(self, app, request): + app_ctx.match_request() + assert request.endpoint is not None + + app = flask.Flask(__name__) + app.session_interface = MySessionInterface() + + @app.get("/") + def index(): + return "Hello, World!" + + response = app.test_client().get("/") + assert response.status_code == 200 diff --git a/src/flask-main/tests/test_signals.py b/src/flask-main/tests/test_signals.py new file mode 100644 index 0000000..32ab333 --- /dev/null +++ b/src/flask-main/tests/test_signals.py @@ -0,0 +1,181 @@ +import flask + + +def test_template_rendered(app, client): + @app.route("/") + def index(): + return flask.render_template("simple_template.html", whiskey=42) + + recorded = [] + + def record(sender, template, context): + recorded.append((template, context)) + + flask.template_rendered.connect(record, app) + try: + client.get("/") + assert len(recorded) == 1 + template, context = recorded[0] + assert template.name == "simple_template.html" + assert context["whiskey"] == 42 + finally: + flask.template_rendered.disconnect(record, app) + + +def test_before_render_template(): + app = flask.Flask(__name__) + + @app.route("/") + def index(): + return flask.render_template("simple_template.html", whiskey=42) + + recorded = [] + + def record(sender, template, context): + context["whiskey"] = 43 + recorded.append((template, context)) + + flask.before_render_template.connect(record, app) + try: + rv = app.test_client().get("/") + assert len(recorded) == 1 + template, context = recorded[0] + assert template.name == "simple_template.html" + assert context["whiskey"] == 43 + assert rv.data == b"

43

" + finally: + flask.before_render_template.disconnect(record, app) + + +def test_request_signals(): + app = flask.Flask(__name__) + calls = [] + + def before_request_signal(sender): + calls.append("before-signal") + + def after_request_signal(sender, response): + assert response.data == b"stuff" + calls.append("after-signal") + + @app.before_request + def before_request_handler(): + calls.append("before-handler") + + @app.after_request + def after_request_handler(response): + calls.append("after-handler") + response.data = "stuff" + return response + + @app.route("/") + def index(): + calls.append("handler") + return "ignored anyway" + + flask.request_started.connect(before_request_signal, app) + flask.request_finished.connect(after_request_signal, app) + + try: + rv = app.test_client().get("/") + assert rv.data == b"stuff" + + assert calls == [ + "before-signal", + "before-handler", + "handler", + "after-handler", + "after-signal", + ] + finally: + flask.request_started.disconnect(before_request_signal, app) + flask.request_finished.disconnect(after_request_signal, app) + + +def test_request_exception_signal(): + app = flask.Flask(__name__) + recorded = [] + + @app.route("/") + def index(): + raise ZeroDivisionError + + def record(sender, exception): + recorded.append(exception) + + flask.got_request_exception.connect(record, app) + try: + assert app.test_client().get("/").status_code == 500 + assert len(recorded) == 1 + assert isinstance(recorded[0], ZeroDivisionError) + finally: + flask.got_request_exception.disconnect(record, app) + + +def test_appcontext_signals(app, client): + recorded = [] + + def record_push(sender, **kwargs): + recorded.append("push") + + def record_pop(sender, **kwargs): + recorded.append("pop") + + @app.route("/") + def index(): + return "Hello" + + flask.appcontext_pushed.connect(record_push, app) + flask.appcontext_popped.connect(record_pop, app) + try: + rv = client.get("/") + assert rv.data == b"Hello" + assert recorded == ["push", "pop"] + finally: + flask.appcontext_pushed.disconnect(record_push, app) + flask.appcontext_popped.disconnect(record_pop, app) + + +def test_flash_signal(app): + @app.route("/") + def index(): + flask.flash("This is a flash message", category="notice") + return flask.redirect("/other") + + recorded = [] + + def record(sender, message, category): + recorded.append((message, category)) + + flask.message_flashed.connect(record, app) + try: + client = app.test_client() + with client.session_transaction(): + client.get("/") + assert len(recorded) == 1 + message, category = recorded[0] + assert message == "This is a flash message" + assert category == "notice" + finally: + flask.message_flashed.disconnect(record, app) + + +def test_appcontext_tearing_down_signal(app, client): + app.testing = False + recorded = [] + + def record_teardown(sender, exc): + recorded.append(exc) + + @app.route("/") + def index(): + raise ZeroDivisionError + + flask.appcontext_tearing_down.connect(record_teardown, app) + try: + rv = client.get("/") + assert rv.status_code == 500 + assert len(recorded) == 1 + assert isinstance(recorded[0], ZeroDivisionError) + finally: + flask.appcontext_tearing_down.disconnect(record_teardown, app) diff --git a/src/flask-main/tests/test_subclassing.py b/src/flask-main/tests/test_subclassing.py new file mode 100644 index 0000000..3b9fe31 --- /dev/null +++ b/src/flask-main/tests/test_subclassing.py @@ -0,0 +1,21 @@ +from io import StringIO + +import flask + + +def test_suppressed_exception_logging(): + class SuppressedFlask(flask.Flask): + def log_exception(self, ctx, exc_info): + pass + + out = StringIO() + app = SuppressedFlask(__name__) + + @app.route("/") + def index(): + raise Exception("test") + + rv = app.test_client().get("/", errors_stream=out) + assert rv.status_code == 500 + assert b"Internal Server Error" in rv.data + assert not out.getvalue() diff --git a/src/flask-main/tests/test_templating.py b/src/flask-main/tests/test_templating.py new file mode 100644 index 0000000..85549df --- /dev/null +++ b/src/flask-main/tests/test_templating.py @@ -0,0 +1,532 @@ +import logging + +import pytest +import werkzeug.serving +from jinja2 import TemplateNotFound +from markupsafe import Markup + +import flask + + +def test_context_processing(app, client): + @app.context_processor + def context_processor(): + return {"injected_value": 42} + + @app.route("/") + def index(): + return flask.render_template("context_template.html", value=23) + + rv = client.get("/") + assert rv.data == b"

23|42" + + +def test_original_win(app, client): + @app.route("/") + def index(): + return flask.render_template_string("{{ config }}", config=42) + + rv = client.get("/") + assert rv.data == b"42" + + +def test_simple_stream(app, client): + @app.route("/") + def index(): + return flask.stream_template_string("{{ config }}", config=42) + + rv = client.get("/") + assert rv.data == b"42" + + +def test_request_less_rendering(app, app_ctx): + app.config["WORLD_NAME"] = "Special World" + + @app.context_processor + def context_processor(): + return dict(foo=42) + + rv = flask.render_template_string("Hello {{ config.WORLD_NAME }} {{ foo }}") + assert rv == "Hello Special World 42" + + +def test_standard_context(app, client): + @app.route("/") + def index(): + flask.g.foo = 23 + flask.session["test"] = "aha" + return flask.render_template_string( + """ + {{ request.args.foo }} + {{ g.foo }} + {{ config.DEBUG }} + {{ session.test }} + """ + ) + + rv = client.get("/?foo=42") + assert rv.data.split() == [b"42", b"23", b"False", b"aha"] + + +def test_escaping(app, client): + text = "

Hello World!" + + @app.route("/") + def index(): + return flask.render_template( + "escaping_template.html", text=text, html=Markup(text) + ) + + lines = client.get("/").data.splitlines() + assert lines == [ + b"<p>Hello World!", + b"

Hello World!", + b"

Hello World!", + b"

Hello World!", + b"<p>Hello World!", + b"

Hello World!", + ] + + +def test_no_escaping(app, client): + text = "

Hello World!" + + @app.route("/") + def index(): + return flask.render_template( + "non_escaping_template.txt", text=text, html=Markup(text) + ) + + lines = client.get("/").data.splitlines() + assert lines == [ + b"

Hello World!", + b"

Hello World!", + b"

Hello World!", + b"

Hello World!", + b"<p>Hello World!", + b"

Hello World!", + b"

Hello World!", + b"

Hello World!", + ] + + +def test_escaping_without_template_filename(app, client, req_ctx): + assert flask.render_template_string("{{ foo }}", foo="") == "<test>" + assert flask.render_template("mail.txt", foo="") == " Mail" + + +def test_macros(app, req_ctx): + macro = flask.get_template_attribute("_macro.html", "hello") + assert macro("World") == "Hello World!" + + +def test_template_filter(app): + @app.template_filter() + def my_reverse(s): + return s[::-1] + + assert "my_reverse" in app.jinja_env.filters.keys() + assert app.jinja_env.filters["my_reverse"] == my_reverse + assert app.jinja_env.filters["my_reverse"]("abcd") == "dcba" + + @app.template_filter + def my_reverse_2(s): + return s[::-1] + + assert "my_reverse_2" in app.jinja_env.filters.keys() + assert app.jinja_env.filters["my_reverse_2"] == my_reverse_2 + assert app.jinja_env.filters["my_reverse_2"]("abcd") == "dcba" + + @app.template_filter("my_reverse_custom_name_3") + def my_reverse_3(s): + return s[::-1] + + assert "my_reverse_custom_name_3" in app.jinja_env.filters.keys() + assert app.jinja_env.filters["my_reverse_custom_name_3"] == my_reverse_3 + assert app.jinja_env.filters["my_reverse_custom_name_3"]("abcd") == "dcba" + + @app.template_filter(name="my_reverse_custom_name_4") + def my_reverse_4(s): + return s[::-1] + + assert "my_reverse_custom_name_4" in app.jinja_env.filters.keys() + assert app.jinja_env.filters["my_reverse_custom_name_4"] == my_reverse_4 + assert app.jinja_env.filters["my_reverse_custom_name_4"]("abcd") == "dcba" + + +def test_add_template_filter(app): + def my_reverse(s): + return s[::-1] + + app.add_template_filter(my_reverse) + assert "my_reverse" in app.jinja_env.filters.keys() + assert app.jinja_env.filters["my_reverse"] == my_reverse + assert app.jinja_env.filters["my_reverse"]("abcd") == "dcba" + + +def test_template_filter_with_name(app): + @app.template_filter("strrev") + def my_reverse(s): + return s[::-1] + + assert "strrev" in app.jinja_env.filters.keys() + assert app.jinja_env.filters["strrev"] == my_reverse + assert app.jinja_env.filters["strrev"]("abcd") == "dcba" + + +def test_add_template_filter_with_name(app): + def my_reverse(s): + return s[::-1] + + app.add_template_filter(my_reverse, "strrev") + assert "strrev" in app.jinja_env.filters.keys() + assert app.jinja_env.filters["strrev"] == my_reverse + assert app.jinja_env.filters["strrev"]("abcd") == "dcba" + + +def test_template_filter_with_template(app, client): + @app.template_filter() + def super_reverse(s): + return s[::-1] + + @app.route("/") + def index(): + return flask.render_template("template_filter.html", value="abcd") + + rv = client.get("/") + assert rv.data == b"dcba" + + +def test_add_template_filter_with_template(app, client): + def super_reverse(s): + return s[::-1] + + app.add_template_filter(super_reverse) + + @app.route("/") + def index(): + return flask.render_template("template_filter.html", value="abcd") + + rv = client.get("/") + assert rv.data == b"dcba" + + +def test_template_filter_with_name_and_template(app, client): + @app.template_filter("super_reverse") + def my_reverse(s): + return s[::-1] + + @app.route("/") + def index(): + return flask.render_template("template_filter.html", value="abcd") + + rv = client.get("/") + assert rv.data == b"dcba" + + +def test_add_template_filter_with_name_and_template(app, client): + def my_reverse(s): + return s[::-1] + + app.add_template_filter(my_reverse, "super_reverse") + + @app.route("/") + def index(): + return flask.render_template("template_filter.html", value="abcd") + + rv = client.get("/") + assert rv.data == b"dcba" + + +def test_template_test(app): + @app.template_test() + def boolean(value): + return isinstance(value, bool) + + assert "boolean" in app.jinja_env.tests.keys() + assert app.jinja_env.tests["boolean"] == boolean + assert app.jinja_env.tests["boolean"](False) + + @app.template_test + def boolean_2(value): + return isinstance(value, bool) + + assert "boolean_2" in app.jinja_env.tests.keys() + assert app.jinja_env.tests["boolean_2"] == boolean_2 + assert app.jinja_env.tests["boolean_2"](False) + + @app.template_test("my_boolean_custom_name") + def boolean_3(value): + return isinstance(value, bool) + + assert "my_boolean_custom_name" in app.jinja_env.tests.keys() + assert app.jinja_env.tests["my_boolean_custom_name"] == boolean_3 + assert app.jinja_env.tests["my_boolean_custom_name"](False) + + @app.template_test(name="my_boolean_custom_name_2") + def boolean_4(value): + return isinstance(value, bool) + + assert "my_boolean_custom_name_2" in app.jinja_env.tests.keys() + assert app.jinja_env.tests["my_boolean_custom_name_2"] == boolean_4 + assert app.jinja_env.tests["my_boolean_custom_name_2"](False) + + +def test_add_template_test(app): + def boolean(value): + return isinstance(value, bool) + + app.add_template_test(boolean) + assert "boolean" in app.jinja_env.tests.keys() + assert app.jinja_env.tests["boolean"] == boolean + assert app.jinja_env.tests["boolean"](False) + + +def test_template_test_with_name(app): + @app.template_test("boolean") + def is_boolean(value): + return isinstance(value, bool) + + assert "boolean" in app.jinja_env.tests.keys() + assert app.jinja_env.tests["boolean"] == is_boolean + assert app.jinja_env.tests["boolean"](False) + + +def test_add_template_test_with_name(app): + def is_boolean(value): + return isinstance(value, bool) + + app.add_template_test(is_boolean, "boolean") + assert "boolean" in app.jinja_env.tests.keys() + assert app.jinja_env.tests["boolean"] == is_boolean + assert app.jinja_env.tests["boolean"](False) + + +def test_template_test_with_template(app, client): + @app.template_test() + def boolean(value): + return isinstance(value, bool) + + @app.route("/") + def index(): + return flask.render_template("template_test.html", value=False) + + rv = client.get("/") + assert b"Success!" in rv.data + + +def test_add_template_test_with_template(app, client): + def boolean(value): + return isinstance(value, bool) + + app.add_template_test(boolean) + + @app.route("/") + def index(): + return flask.render_template("template_test.html", value=False) + + rv = client.get("/") + assert b"Success!" in rv.data + + +def test_template_test_with_name_and_template(app, client): + @app.template_test("boolean") + def is_boolean(value): + return isinstance(value, bool) + + @app.route("/") + def index(): + return flask.render_template("template_test.html", value=False) + + rv = client.get("/") + assert b"Success!" in rv.data + + +def test_add_template_test_with_name_and_template(app, client): + def is_boolean(value): + return isinstance(value, bool) + + app.add_template_test(is_boolean, "boolean") + + @app.route("/") + def index(): + return flask.render_template("template_test.html", value=False) + + rv = client.get("/") + assert b"Success!" in rv.data + + +def test_add_template_global(app, app_ctx): + @app.template_global() + def get_stuff(): + return 42 + + assert "get_stuff" in app.jinja_env.globals.keys() + assert app.jinja_env.globals["get_stuff"] == get_stuff + assert app.jinja_env.globals["get_stuff"](), 42 + + rv = flask.render_template_string("{{ get_stuff() }}") + assert rv == "42" + + @app.template_global + def get_stuff_1(): + return "get_stuff_1" + + assert "get_stuff_1" in app.jinja_env.globals.keys() + assert app.jinja_env.globals["get_stuff_1"] == get_stuff_1 + assert app.jinja_env.globals["get_stuff_1"](), "get_stuff_1" + + rv = flask.render_template_string("{{ get_stuff_1() }}") + assert rv == "get_stuff_1" + + @app.template_global("my_get_stuff_custom_name_2") + def get_stuff_2(): + return "get_stuff_2" + + assert "my_get_stuff_custom_name_2" in app.jinja_env.globals.keys() + assert app.jinja_env.globals["my_get_stuff_custom_name_2"] == get_stuff_2 + assert app.jinja_env.globals["my_get_stuff_custom_name_2"](), "get_stuff_2" + + rv = flask.render_template_string("{{ my_get_stuff_custom_name_2() }}") + assert rv == "get_stuff_2" + + @app.template_global(name="my_get_stuff_custom_name_3") + def get_stuff_3(): + return "get_stuff_3" + + assert "my_get_stuff_custom_name_3" in app.jinja_env.globals.keys() + assert app.jinja_env.globals["my_get_stuff_custom_name_3"] == get_stuff_3 + assert app.jinja_env.globals["my_get_stuff_custom_name_3"](), "get_stuff_3" + + rv = flask.render_template_string("{{ my_get_stuff_custom_name_3() }}") + assert rv == "get_stuff_3" + + +def test_custom_template_loader(client): + class MyFlask(flask.Flask): + def create_global_jinja_loader(self): + from jinja2 import DictLoader + + return DictLoader({"index.html": "Hello Custom World!"}) + + app = MyFlask(__name__) + + @app.route("/") + def index(): + return flask.render_template("index.html") + + c = app.test_client() + rv = c.get("/") + assert rv.data == b"Hello Custom World!" + + +def test_iterable_loader(app, client): + @app.context_processor + def context_processor(): + return {"whiskey": "Jameson"} + + @app.route("/") + def index(): + return flask.render_template( + [ + "no_template.xml", # should skip this one + "simple_template.html", # should render this + "context_template.html", + ], + value=23, + ) + + rv = client.get("/") + assert rv.data == b"

Jameson

" + + +def test_templates_auto_reload(app): + # debug is False, config option is None + assert app.debug is False + assert app.config["TEMPLATES_AUTO_RELOAD"] is None + assert app.jinja_env.auto_reload is False + # debug is False, config option is False + app = flask.Flask(__name__) + app.config["TEMPLATES_AUTO_RELOAD"] = False + assert app.debug is False + assert app.jinja_env.auto_reload is False + # debug is False, config option is True + app = flask.Flask(__name__) + app.config["TEMPLATES_AUTO_RELOAD"] = True + assert app.debug is False + assert app.jinja_env.auto_reload is True + # debug is True, config option is None + app = flask.Flask(__name__) + app.config["DEBUG"] = True + assert app.config["TEMPLATES_AUTO_RELOAD"] is None + assert app.jinja_env.auto_reload is True + # debug is True, config option is False + app = flask.Flask(__name__) + app.config["DEBUG"] = True + app.config["TEMPLATES_AUTO_RELOAD"] = False + assert app.jinja_env.auto_reload is False + # debug is True, config option is True + app = flask.Flask(__name__) + app.config["DEBUG"] = True + app.config["TEMPLATES_AUTO_RELOAD"] = True + assert app.jinja_env.auto_reload is True + + +def test_templates_auto_reload_debug_run(app, monkeypatch): + def run_simple_mock(*args, **kwargs): + pass + + monkeypatch.setattr(werkzeug.serving, "run_simple", run_simple_mock) + + app.run() + assert not app.jinja_env.auto_reload + + app.run(debug=True) + assert app.jinja_env.auto_reload + + +def test_template_loader_debugging(test_apps, monkeypatch): + from blueprintapp import app + + called = [] + + class _TestHandler(logging.Handler): + def handle(self, record): + called.append(True) + text = str(record.msg) + assert "1: trying loader of application 'blueprintapp'" in text + assert ( + "2: trying loader of blueprint 'admin' (blueprintapp.apps.admin)" + ) in text + assert ( + "trying loader of blueprint 'frontend' (blueprintapp.apps.frontend)" + ) in text + assert "Error: the template could not be found" in text + assert ( + "looked up from an endpoint that belongs to the blueprint 'frontend'" + ) in text + assert "See https://flask.palletsprojects.com/blueprints/#templates" in text + + with app.test_client() as c: + monkeypatch.setitem(app.config, "EXPLAIN_TEMPLATE_LOADING", True) + monkeypatch.setattr( + logging.getLogger("blueprintapp"), "handlers", [_TestHandler()] + ) + + with pytest.raises(TemplateNotFound) as excinfo: + c.get("/missing") + + assert "missing_template.html" in str(excinfo.value) + + assert len(called) == 1 + + +def test_custom_jinja_env(): + class CustomEnvironment(flask.templating.Environment): + pass + + class CustomFlask(flask.Flask): + jinja_environment = CustomEnvironment + + app = CustomFlask(__name__) + assert isinstance(app.jinja_env, CustomEnvironment) diff --git a/src/flask-main/tests/test_testing.py b/src/flask-main/tests/test_testing.py new file mode 100644 index 0000000..e14f27c --- /dev/null +++ b/src/flask-main/tests/test_testing.py @@ -0,0 +1,385 @@ +import importlib.metadata + +import click +import pytest + +import flask +from flask import appcontext_popped +from flask.cli import ScriptInfo +from flask.globals import _cv_app +from flask.json import jsonify +from flask.testing import EnvironBuilder +from flask.testing import FlaskCliRunner + + +def test_environ_defaults_from_config(app, client): + app.config["SERVER_NAME"] = "example.com:1234" + app.config["APPLICATION_ROOT"] = "/foo" + + @app.route("/") + def index(): + return flask.request.url + + ctx = app.test_request_context() + assert ctx.request.url == "http://example.com:1234/foo/" + + rv = client.get("/") + assert rv.data == b"http://example.com:1234/foo/" + + +def test_environ_defaults(app, client, app_ctx, req_ctx): + @app.route("/") + def index(): + return flask.request.url + + ctx = app.test_request_context() + assert ctx.request.url == "http://localhost/" + with client: + rv = client.get("/") + assert rv.data == b"http://localhost/" + + +def test_environ_base_default(app, client): + @app.route("/") + def index(): + flask.g.remote_addr = flask.request.remote_addr + flask.g.user_agent = flask.request.user_agent.string + return "" + + with client: + client.get("/") + assert flask.g.remote_addr == "127.0.0.1" + assert flask.g.user_agent == ( + f"Werkzeug/{importlib.metadata.version('werkzeug')}" + ) + + +def test_environ_base_modified(app, client): + @app.route("/") + def index(): + flask.g.remote_addr = flask.request.remote_addr + flask.g.user_agent = flask.request.user_agent.string + return "" + + client.environ_base["REMOTE_ADDR"] = "192.168.0.22" + client.environ_base["HTTP_USER_AGENT"] = "Foo" + + with client: + client.get("/") + assert flask.g.remote_addr == "192.168.0.22" + assert flask.g.user_agent == "Foo" + + +def test_client_open_environ(app, client, request): + @app.route("/index") + def index(): + return flask.request.remote_addr + + builder = EnvironBuilder(app, path="/index", method="GET") + request.addfinalizer(builder.close) + + rv = client.open(builder) + assert rv.data == b"127.0.0.1" + + environ = builder.get_environ() + client.environ_base["REMOTE_ADDR"] = "127.0.0.2" + rv = client.open(environ) + assert rv.data == b"127.0.0.2" + + +def test_specify_url_scheme(app, client): + @app.route("/") + def index(): + return flask.request.url + + ctx = app.test_request_context(url_scheme="https") + assert ctx.request.url == "https://localhost/" + + rv = client.get("/", url_scheme="https") + assert rv.data == b"https://localhost/" + + +def test_path_is_url(app): + eb = EnvironBuilder(app, "https://example.com/") + assert eb.url_scheme == "https" + assert eb.host == "example.com" + assert eb.script_root == "" + assert eb.path == "/" + + +def test_environbuilder_json_dumps(app): + """EnvironBuilder.json_dumps() takes settings from the app.""" + app.json.ensure_ascii = False + eb = EnvironBuilder(app, json="\u20ac") + assert eb.input_stream.read().decode("utf8") == '"\u20ac"' + + +def test_blueprint_with_subdomain(): + app = flask.Flask(__name__, subdomain_matching=True) + app.config["SERVER_NAME"] = "example.com:1234" + app.config["APPLICATION_ROOT"] = "/foo" + client = app.test_client() + + bp = flask.Blueprint("company", __name__, subdomain="xxx") + + @bp.route("/") + def index(): + return flask.request.url + + app.register_blueprint(bp) + + ctx = app.test_request_context("/", subdomain="xxx") + assert ctx.request.url == "http://xxx.example.com:1234/foo/" + + with ctx: + assert ctx.request.blueprint == bp.name + + rv = client.get("/", subdomain="xxx") + assert rv.data == b"http://xxx.example.com:1234/foo/" + + +def test_redirect_session(app, client, app_ctx): + @app.route("/redirect") + def index(): + flask.session["redirect"] = True + return flask.redirect("/target") + + @app.route("/target") + def get_session(): + flask.session["target"] = True + return "" + + with client: + client.get("/redirect", follow_redirects=True) + assert flask.session["redirect"] is True + assert flask.session["target"] is True + + +def test_session_transactions(app, client): + @app.route("/") + def index(): + return str(flask.session["foo"]) + + with client: + with client.session_transaction() as sess: + assert len(sess) == 0 + sess["foo"] = [42] + assert len(sess) == 1 + rv = client.get("/") + assert rv.data == b"[42]" + with client.session_transaction() as sess: + assert len(sess) == 1 + assert sess["foo"] == [42] + + +def test_session_transactions_no_null_sessions(): + app = flask.Flask(__name__) + + with app.test_client() as c: + with pytest.raises(RuntimeError) as e: + with c.session_transaction(): + pass + assert "Session backend did not open a session" in str(e.value) + + +def test_session_transactions_keep_context(app, client, req_ctx): + client.get("/") + req = flask.request._get_current_object() + assert req is not None + with client.session_transaction(): + assert req is flask.request._get_current_object() + + +def test_session_transaction_needs_cookies(app): + c = app.test_client(use_cookies=False) + + with pytest.raises(TypeError, match="Cookies are disabled."): + with c.session_transaction(): + pass + + +def test_test_client_context_binding(app, client): + app.testing = False + + @app.route("/") + def index(): + flask.g.value = 42 + return "Hello World!" + + @app.route("/other") + def other(): + raise ZeroDivisionError + + with client: + resp = client.get("/") + assert flask.g.value == 42 + assert resp.data == b"Hello World!" + assert resp.status_code == 200 + + with client: + resp = client.get("/other") + assert not hasattr(flask.g, "value") + assert b"Internal Server Error" in resp.data + assert resp.status_code == 500 + flask.g.value = 23 + + with pytest.raises(RuntimeError): + flask.g.value # noqa: B018 + + +def test_reuse_client(client): + c = client + + with c: + assert client.get("/").status_code == 404 + + with c: + assert client.get("/").status_code == 404 + + +def test_full_url_request(app, client): + @app.route("/action", methods=["POST"]) + def action(): + return "x" + + with client: + rv = client.post("http://domain.com/action?vodka=42", data={"gin": 43}) + assert rv.status_code == 200 + assert "gin" in flask.request.form + assert "vodka" in flask.request.args + + +def test_json_request_and_response(app, client): + @app.route("/echo", methods=["POST"]) + def echo(): + return jsonify(flask.request.get_json()) + + with client: + json_data = {"drink": {"gin": 1, "tonic": True}, "price": 10} + rv = client.post("/echo", json=json_data) + + # Request should be in JSON + assert flask.request.is_json + assert flask.request.get_json() == json_data + + # Response should be in JSON + assert rv.status_code == 200 + assert rv.is_json + assert rv.get_json() == json_data + + +def test_client_json_no_app_context(app, client): + @app.route("/hello", methods=["POST"]) + def hello(): + return f"Hello, {flask.request.json['name']}!" + + class Namespace: + count = 0 + + def add(self, app): + self.count += 1 + + ns = Namespace() + + with appcontext_popped.connected_to(ns.add, app): + rv = client.post("/hello", json={"name": "Flask"}) + + assert rv.get_data(as_text=True) == "Hello, Flask!" + assert ns.count == 1 + + +def test_subdomain(): + app = flask.Flask(__name__, subdomain_matching=True) + app.config["SERVER_NAME"] = "example.com" + client = app.test_client() + + @app.route("/", subdomain="") + def view(company_id): + return company_id + + with app.test_request_context(): + url = flask.url_for("view", company_id="xxx") + + with client: + response = client.get(url) + + assert 200 == response.status_code + assert b"xxx" == response.data + + +def test_nosubdomain(app, client): + app.config["SERVER_NAME"] = "example.com" + + @app.route("/") + def view(company_id): + return company_id + + with app.test_request_context(): + url = flask.url_for("view", company_id="xxx") + + with client: + response = client.get(url) + + assert 200 == response.status_code + assert b"xxx" == response.data + + +def test_cli_runner_class(app): + runner = app.test_cli_runner() + assert isinstance(runner, FlaskCliRunner) + + class SubRunner(FlaskCliRunner): + pass + + app.test_cli_runner_class = SubRunner + runner = app.test_cli_runner() + assert isinstance(runner, SubRunner) + + +def test_cli_invoke(app): + @app.cli.command("hello") + def hello_command(): + click.echo("Hello, World!") + + runner = app.test_cli_runner() + # invoke with command name + result = runner.invoke(args=["hello"]) + assert "Hello" in result.output + # invoke with command object + result = runner.invoke(hello_command) + assert "Hello" in result.output + + +def test_cli_custom_obj(app): + class NS: + called = False + + def create_app(): + NS.called = True + return app + + @app.cli.command("hello") + def hello_command(): + click.echo("Hello, World!") + + script_info = ScriptInfo(create_app=create_app) + runner = app.test_cli_runner() + runner.invoke(hello_command, obj=script_info) + assert NS.called + + +def test_client_pop_all_preserved(app, req_ctx, client): + @app.route("/") + def index(): + # stream_with_context pushes a third context, preserved by response + return flask.stream_with_context("hello") + + # req_ctx fixture pushed an initial context + with client: + # request pushes a second request context, preserved by client + rv = client.get("/") + + # close the response, releasing the context held by stream_with_context + rv.close() + # only req_ctx fixture should still be pushed + assert _cv_app.get(None) is req_ctx diff --git a/src/flask-main/tests/test_user_error_handler.py b/src/flask-main/tests/test_user_error_handler.py new file mode 100644 index 0000000..79c5a73 --- /dev/null +++ b/src/flask-main/tests/test_user_error_handler.py @@ -0,0 +1,295 @@ +import pytest +from werkzeug.exceptions import Forbidden +from werkzeug.exceptions import HTTPException +from werkzeug.exceptions import InternalServerError +from werkzeug.exceptions import NotFound + +import flask + + +def test_error_handler_no_match(app, client): + class CustomException(Exception): + pass + + @app.errorhandler(CustomException) + def custom_exception_handler(e): + assert isinstance(e, CustomException) + return "custom" + + with pytest.raises(TypeError) as exc_info: + app.register_error_handler(CustomException(), None) + + assert "CustomException() is an instance, not a class." in str(exc_info.value) + + with pytest.raises(ValueError) as exc_info: + app.register_error_handler(list, None) + + assert "'list' is not a subclass of Exception." in str(exc_info.value) + + @app.errorhandler(500) + def handle_500(e): + assert isinstance(e, InternalServerError) + + if e.original_exception is not None: + return f"wrapped {type(e.original_exception).__name__}" + + return "direct" + + with pytest.raises(ValueError) as exc_info: + app.register_error_handler(999, None) + + assert "Use a subclass of HTTPException" in str(exc_info.value) + + @app.route("/custom") + def custom_test(): + raise CustomException() + + @app.route("/keyerror") + def key_error(): + raise KeyError() + + @app.route("/abort") + def do_abort(): + flask.abort(500) + + app.testing = False + assert client.get("/custom").data == b"custom" + assert client.get("/keyerror").data == b"wrapped KeyError" + assert client.get("/abort").data == b"direct" + + +def test_error_handler_subclass(app): + class ParentException(Exception): + pass + + class ChildExceptionUnregistered(ParentException): + pass + + class ChildExceptionRegistered(ParentException): + pass + + @app.errorhandler(ParentException) + def parent_exception_handler(e): + assert isinstance(e, ParentException) + return "parent" + + @app.errorhandler(ChildExceptionRegistered) + def child_exception_handler(e): + assert isinstance(e, ChildExceptionRegistered) + return "child-registered" + + @app.route("/parent") + def parent_test(): + raise ParentException() + + @app.route("/child-unregistered") + def unregistered_test(): + raise ChildExceptionUnregistered() + + @app.route("/child-registered") + def registered_test(): + raise ChildExceptionRegistered() + + c = app.test_client() + + assert c.get("/parent").data == b"parent" + assert c.get("/child-unregistered").data == b"parent" + assert c.get("/child-registered").data == b"child-registered" + + +def test_error_handler_http_subclass(app): + class ForbiddenSubclassRegistered(Forbidden): + pass + + class ForbiddenSubclassUnregistered(Forbidden): + pass + + @app.errorhandler(403) + def code_exception_handler(e): + assert isinstance(e, Forbidden) + return "forbidden" + + @app.errorhandler(ForbiddenSubclassRegistered) + def subclass_exception_handler(e): + assert isinstance(e, ForbiddenSubclassRegistered) + return "forbidden-registered" + + @app.route("/forbidden") + def forbidden_test(): + raise Forbidden() + + @app.route("/forbidden-registered") + def registered_test(): + raise ForbiddenSubclassRegistered() + + @app.route("/forbidden-unregistered") + def unregistered_test(): + raise ForbiddenSubclassUnregistered() + + c = app.test_client() + + assert c.get("/forbidden").data == b"forbidden" + assert c.get("/forbidden-unregistered").data == b"forbidden" + assert c.get("/forbidden-registered").data == b"forbidden-registered" + + +def test_error_handler_blueprint(app): + bp = flask.Blueprint("bp", __name__) + + @bp.errorhandler(500) + def bp_exception_handler(e): + return "bp-error" + + @bp.route("/error") + def bp_test(): + raise InternalServerError() + + @app.errorhandler(500) + def app_exception_handler(e): + return "app-error" + + @app.route("/error") + def app_test(): + raise InternalServerError() + + app.register_blueprint(bp, url_prefix="/bp") + + c = app.test_client() + + assert c.get("/error").data == b"app-error" + assert c.get("/bp/error").data == b"bp-error" + + +def test_default_error_handler(): + bp = flask.Blueprint("bp", __name__) + + @bp.errorhandler(HTTPException) + def bp_exception_handler(e): + assert isinstance(e, HTTPException) + assert isinstance(e, NotFound) + return "bp-default" + + @bp.errorhandler(Forbidden) + def bp_forbidden_handler(e): + assert isinstance(e, Forbidden) + return "bp-forbidden" + + @bp.route("/undefined") + def bp_registered_test(): + raise NotFound() + + @bp.route("/forbidden") + def bp_forbidden_test(): + raise Forbidden() + + app = flask.Flask(__name__) + + @app.errorhandler(HTTPException) + def catchall_exception_handler(e): + assert isinstance(e, HTTPException) + assert isinstance(e, NotFound) + return "default" + + @app.errorhandler(Forbidden) + def catchall_forbidden_handler(e): + assert isinstance(e, Forbidden) + return "forbidden" + + @app.route("/forbidden") + def forbidden(): + raise Forbidden() + + @app.route("/slash/") + def slash(): + return "slash" + + app.register_blueprint(bp, url_prefix="/bp") + + c = app.test_client() + assert c.get("/bp/undefined").data == b"bp-default" + assert c.get("/bp/forbidden").data == b"bp-forbidden" + assert c.get("/undefined").data == b"default" + assert c.get("/forbidden").data == b"forbidden" + # Don't handle RequestRedirect raised when adding slash. + assert c.get("/slash", follow_redirects=True).data == b"slash" + + +class TestGenericHandlers: + """Test how very generic handlers are dispatched to.""" + + class Custom(Exception): + pass + + @pytest.fixture() + def app(self, app): + @app.route("/custom") + def do_custom(): + raise self.Custom() + + @app.route("/error") + def do_error(): + raise KeyError() + + @app.route("/abort") + def do_abort(): + flask.abort(500) + + @app.route("/raise") + def do_raise(): + raise InternalServerError() + + app.config["PROPAGATE_EXCEPTIONS"] = False + return app + + def report_error(self, e): + original = getattr(e, "original_exception", None) + + if original is not None: + return f"wrapped {type(original).__name__}" + + return f"direct {type(e).__name__}" + + @pytest.mark.parametrize("to_handle", (InternalServerError, 500)) + def test_handle_class_or_code(self, app, client, to_handle): + """``InternalServerError`` and ``500`` are aliases, they should + have the same behavior. Both should only receive + ``InternalServerError``, which might wrap another error. + """ + + @app.errorhandler(to_handle) + def handle_500(e): + assert isinstance(e, InternalServerError) + return self.report_error(e) + + assert client.get("/custom").data == b"wrapped Custom" + assert client.get("/error").data == b"wrapped KeyError" + assert client.get("/abort").data == b"direct InternalServerError" + assert client.get("/raise").data == b"direct InternalServerError" + + def test_handle_generic_http(self, app, client): + """``HTTPException`` should only receive ``HTTPException`` + subclasses. It will receive ``404`` routing exceptions. + """ + + @app.errorhandler(HTTPException) + def handle_http(e): + assert isinstance(e, HTTPException) + return str(e.code) + + assert client.get("/error").data == b"500" + assert client.get("/abort").data == b"500" + assert client.get("/not-found").data == b"404" + + def test_handle_generic(self, app, client): + """Generic ``Exception`` will handle all exceptions directly, + including ``HTTPExceptions``. + """ + + @app.errorhandler(Exception) + def handle_exception(e): + return self.report_error(e) + + assert client.get("/custom").data == b"direct Custom" + assert client.get("/error").data == b"direct KeyError" + assert client.get("/abort").data == b"direct InternalServerError" + assert client.get("/not-found").data == b"direct NotFound" diff --git a/src/flask-main/tests/test_views.py b/src/flask-main/tests/test_views.py new file mode 100644 index 0000000..eab5eda --- /dev/null +++ b/src/flask-main/tests/test_views.py @@ -0,0 +1,260 @@ +import pytest +from werkzeug.http import parse_set_header + +import flask.views + + +def common_test(app): + c = app.test_client() + + assert c.get("/").data == b"GET" + assert c.post("/").data == b"POST" + assert c.put("/").status_code == 405 + meths = parse_set_header(c.open("/", method="OPTIONS").headers["Allow"]) + assert sorted(meths) == ["GET", "HEAD", "OPTIONS", "POST"] + + +def test_basic_view(app): + class Index(flask.views.View): + methods = ["GET", "POST"] + + def dispatch_request(self): + return flask.request.method + + app.add_url_rule("/", view_func=Index.as_view("index")) + common_test(app) + + +def test_method_based_view(app): + class Index(flask.views.MethodView): + def get(self): + return "GET" + + def post(self): + return "POST" + + app.add_url_rule("/", view_func=Index.as_view("index")) + + common_test(app) + + +def test_view_patching(app): + class Index(flask.views.MethodView): + def get(self): + raise ZeroDivisionError + + def post(self): + raise ZeroDivisionError + + class Other(Index): + def get(self): + return "GET" + + def post(self): + return "POST" + + view = Index.as_view("index") + view.view_class = Other + app.add_url_rule("/", view_func=view) + common_test(app) + + +def test_view_inheritance(app, client): + class Index(flask.views.MethodView): + def get(self): + return "GET" + + def post(self): + return "POST" + + class BetterIndex(Index): + def delete(self): + return "DELETE" + + app.add_url_rule("/", view_func=BetterIndex.as_view("index")) + + meths = parse_set_header(client.open("/", method="OPTIONS").headers["Allow"]) + assert sorted(meths) == ["DELETE", "GET", "HEAD", "OPTIONS", "POST"] + + +def test_view_decorators(app, client): + def add_x_parachute(f): + def new_function(*args, **kwargs): + resp = flask.make_response(f(*args, **kwargs)) + resp.headers["X-Parachute"] = "awesome" + return resp + + return new_function + + class Index(flask.views.View): + decorators = [add_x_parachute] + + def dispatch_request(self): + return "Awesome" + + app.add_url_rule("/", view_func=Index.as_view("index")) + rv = client.get("/") + assert rv.headers["X-Parachute"] == "awesome" + assert rv.data == b"Awesome" + + +def test_view_provide_automatic_options_attr(): + app = flask.Flask(__name__) + + class Index1(flask.views.View): + provide_automatic_options = False + + def dispatch_request(self): + return "Hello World!" + + app.add_url_rule("/", view_func=Index1.as_view("index")) + c = app.test_client() + rv = c.open("/", method="OPTIONS") + assert rv.status_code == 405 + + app = flask.Flask(__name__) + + class Index2(flask.views.View): + methods = ["OPTIONS"] + provide_automatic_options = True + + def dispatch_request(self): + return "Hello World!" + + app.add_url_rule("/", view_func=Index2.as_view("index")) + c = app.test_client() + rv = c.open("/", method="OPTIONS") + assert sorted(rv.allow) == ["OPTIONS"] + + app = flask.Flask(__name__) + + class Index3(flask.views.View): + def dispatch_request(self): + return "Hello World!" + + app.add_url_rule("/", view_func=Index3.as_view("index")) + c = app.test_client() + rv = c.open("/", method="OPTIONS") + assert "OPTIONS" in rv.allow + + +def test_implicit_head(app, client): + class Index(flask.views.MethodView): + def get(self): + return flask.Response("Blub", headers={"X-Method": flask.request.method}) + + app.add_url_rule("/", view_func=Index.as_view("index")) + rv = client.get("/") + assert rv.data == b"Blub" + assert rv.headers["X-Method"] == "GET" + rv = client.head("/") + assert rv.data == b"" + assert rv.headers["X-Method"] == "HEAD" + + +def test_explicit_head(app, client): + class Index(flask.views.MethodView): + def get(self): + return "GET" + + def head(self): + return flask.Response("", headers={"X-Method": "HEAD"}) + + app.add_url_rule("/", view_func=Index.as_view("index")) + rv = client.get("/") + assert rv.data == b"GET" + rv = client.head("/") + assert rv.data == b"" + assert rv.headers["X-Method"] == "HEAD" + + +def test_endpoint_override(app): + app.debug = True + + class Index(flask.views.View): + methods = ["GET", "POST"] + + def dispatch_request(self): + return flask.request.method + + app.add_url_rule("/", view_func=Index.as_view("index")) + + with pytest.raises(AssertionError): + app.add_url_rule("/", view_func=Index.as_view("index")) + + # But these tests should still pass. We just log a warning. + common_test(app) + + +def test_methods_var_inheritance(app, client): + class BaseView(flask.views.MethodView): + methods = ["GET", "PROPFIND"] + + class ChildView(BaseView): + def get(self): + return "GET" + + def propfind(self): + return "PROPFIND" + + app.add_url_rule("/", view_func=ChildView.as_view("index")) + + assert client.get("/").data == b"GET" + assert client.open("/", method="PROPFIND").data == b"PROPFIND" + assert ChildView.methods == {"PROPFIND", "GET"} + + +def test_multiple_inheritance(app, client): + class GetView(flask.views.MethodView): + def get(self): + return "GET" + + class DeleteView(flask.views.MethodView): + def delete(self): + return "DELETE" + + class GetDeleteView(GetView, DeleteView): + pass + + app.add_url_rule("/", view_func=GetDeleteView.as_view("index")) + + assert client.get("/").data == b"GET" + assert client.delete("/").data == b"DELETE" + assert sorted(GetDeleteView.methods) == ["DELETE", "GET"] + + +def test_remove_method_from_parent(app, client): + class GetView(flask.views.MethodView): + def get(self): + return "GET" + + class OtherView(flask.views.MethodView): + def post(self): + return "POST" + + class View(GetView, OtherView): + methods = ["GET"] + + app.add_url_rule("/", view_func=View.as_view("index")) + + assert client.get("/").data == b"GET" + assert client.post("/").status_code == 405 + assert sorted(View.methods) == ["GET"] + + +def test_init_once(app, client): + n = 0 + + class CountInit(flask.views.View): + init_every_request = False + + def __init__(self): + nonlocal n + n += 1 + + def dispatch_request(self): + return str(n) + + app.add_url_rule("/", view_func=CountInit.as_view("index")) + assert client.get("/").data == b"1" + assert client.get("/").data == b"1" diff --git a/src/flask-main/tests/type_check/typing_app_decorators.py b/src/flask-main/tests/type_check/typing_app_decorators.py new file mode 100644 index 0000000..0e25a30 --- /dev/null +++ b/src/flask-main/tests/type_check/typing_app_decorators.py @@ -0,0 +1,32 @@ +from __future__ import annotations + +from flask import Flask +from flask import Response + +app = Flask(__name__) + + +@app.after_request +def after_sync(response: Response) -> Response: + return Response() + + +@app.after_request +async def after_async(response: Response) -> Response: + return Response() + + +@app.before_request +def before_sync() -> None: ... + + +@app.before_request +async def before_async() -> None: ... + + +@app.teardown_appcontext +def teardown_sync(exc: BaseException | None) -> None: ... + + +@app.teardown_appcontext +async def teardown_async(exc: BaseException | None) -> None: ... diff --git a/src/flask-main/tests/type_check/typing_error_handler.py b/src/flask-main/tests/type_check/typing_error_handler.py new file mode 100644 index 0000000..ec9c886 --- /dev/null +++ b/src/flask-main/tests/type_check/typing_error_handler.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +from http import HTTPStatus + +from werkzeug.exceptions import BadRequest +from werkzeug.exceptions import NotFound + +from flask import Flask + +app = Flask(__name__) + + +@app.errorhandler(400) +@app.errorhandler(HTTPStatus.BAD_REQUEST) +@app.errorhandler(BadRequest) +def handle_400(e: BadRequest) -> str: + return "" + + +@app.errorhandler(ValueError) +def handle_custom(e: ValueError) -> str: + return "" + + +@app.errorhandler(ValueError) +def handle_accept_base(e: Exception) -> str: + return "" + + +@app.errorhandler(BadRequest) +@app.errorhandler(404) +def handle_multiple(e: BadRequest | NotFound) -> str: + return "" diff --git a/src/flask-main/tests/type_check/typing_route.py b/src/flask-main/tests/type_check/typing_route.py new file mode 100644 index 0000000..8bc271b --- /dev/null +++ b/src/flask-main/tests/type_check/typing_route.py @@ -0,0 +1,112 @@ +from __future__ import annotations + +import typing as t +from http import HTTPStatus + +from flask import Flask +from flask import jsonify +from flask import stream_template +from flask.templating import render_template +from flask.views import View +from flask.wrappers import Response + +app = Flask(__name__) + + +@app.route("/str") +def hello_str() -> str: + return "

Hello, World!

" + + +@app.route("/bytes") +def hello_bytes() -> bytes: + return b"

Hello, World!

" + + +@app.route("/json") +def hello_json() -> Response: + return jsonify("Hello, World!") + + +@app.route("/json/dict") +def hello_json_dict() -> dict[str, t.Any]: + return {"response": "Hello, World!"} + + +@app.route("/json/dict") +def hello_json_list() -> list[t.Any]: + return [{"message": "Hello"}, {"message": "World"}] + + +class StatusJSON(t.TypedDict): + status: str + + +@app.route("/typed-dict") +def typed_dict() -> StatusJSON: + return {"status": "ok"} + + +@app.route("/generator") +def hello_generator() -> t.Generator[str, None, None]: + def show() -> t.Generator[str, None, None]: + for x in range(100): + yield f"data:{x}\n\n" + + return show() + + +@app.route("/generator-expression") +def hello_generator_expression() -> t.Iterator[bytes]: + return (f"data:{x}\n\n".encode() for x in range(100)) + + +@app.route("/iterator") +def hello_iterator() -> t.Iterator[str]: + return iter([f"data:{x}\n\n" for x in range(100)]) + + +@app.route("/status") +@app.route("/status/") +def tuple_status(code: int = 200) -> tuple[str, int]: + return "hello", code + + +@app.route("/status-enum") +def tuple_status_enum() -> tuple[str, int]: + return "hello", HTTPStatus.OK + + +@app.route("/headers") +def tuple_headers() -> tuple[str, dict[str, str]]: + return "Hello, World!", {"Content-Type": "text/plain"} + + +@app.route("/template") +@app.route("/template/") +def return_template(name: str | None = None) -> str: + return render_template("index.html", name=name) + + +@app.route("/template") +def return_template_stream() -> t.Iterator[str]: + return stream_template("index.html", name="Hello") + + +@app.route("/async") +async def async_route() -> str: + return "Hello" + + +class RenderTemplateView(View): + def __init__(self: RenderTemplateView, template_name: str) -> None: + self.template_name = template_name + + def dispatch_request(self: RenderTemplateView) -> str: + return render_template(self.template_name) + + +app.add_url_rule( + "/about", + view_func=RenderTemplateView.as_view("about_page", template_name="about.html"), +) diff --git a/src/flask-main/uv.lock b/src/flask-main/uv.lock new file mode 100644 index 0000000..8d6b5f3 --- /dev/null +++ b/src/flask-main/uv.lock @@ -0,0 +1,1666 @@ +version = 1 +revision = 3 +requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] + +[[package]] +name = "alabaster" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, +] + +[[package]] +name = "anyio" +version = "4.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" }, +] + +[[package]] +name = "asgiref" +version = "3.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/08/4dfec9b90758a59acc6be32ac82e98d1fbfc321cb5cfa410436dbacf821c/asgiref-3.10.0.tar.gz", hash = "sha256:d89f2d8cd8b56dada7d52fa7dc8075baa08fb836560710d38c292a7a3f78c04e", size = 37483, upload-time = "2025-10-05T09:15:06.557Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/9c/fc2331f538fbf7eedba64b2052e99ccf9ba9d6888e2f41441ee28847004b/asgiref-3.10.0-py3-none-any.whl", hash = "sha256:aef8a81283a34d0ab31630c9b7dfe70c812c95eba78171367ca8745e88124734", size = 24050, upload-time = "2025-10-05T09:15:05.11Z" }, +] + +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, +] + +[[package]] +name = "blinker" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, +] + +[[package]] +name = "cachetools" +version = "6.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/44/ca1675be2a83aeee1886ab745b28cda92093066590233cc501890eb8417a/cachetools-6.2.2.tar.gz", hash = "sha256:8e6d266b25e539df852251cfd6f990b4bc3a141db73b939058d809ebd2590fc6", size = 31571, upload-time = "2025-11-13T17:42:51.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/46/eb6eca305c77a4489affe1c5d8f4cae82f285d9addd8de4ec084a7184221/cachetools-6.2.2-py3-none-any.whl", hash = "sha256:6c09c98183bf58560c97b2abfcedcbaf6a896a490f534b031b661d3723b45ace", size = 11503, upload-time = "2025-11-13T17:42:50.232Z" }, +] + +[[package]] +name = "certifi" +version = "2025.11.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, + { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, + { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, + { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, + { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, + { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, + { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, + { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, + { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, + { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, + { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" }, +] + +[[package]] +name = "chardet" +version = "5.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618, upload-time = "2023-08-01T19:23:02.662Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385, upload-time = "2023-08-01T19:23:00.661Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", size = 209709, upload-time = "2025-10-14T04:40:11.385Z" }, + { url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", size = 148814, upload-time = "2025-10-14T04:40:13.135Z" }, + { url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", size = 144467, upload-time = "2025-10-14T04:40:14.728Z" }, + { url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", size = 162280, upload-time = "2025-10-14T04:40:16.14Z" }, + { url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", size = 159454, upload-time = "2025-10-14T04:40:17.567Z" }, + { url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", size = 153609, upload-time = "2025-10-14T04:40:19.08Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", size = 151849, upload-time = "2025-10-14T04:40:20.607Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", size = 151586, upload-time = "2025-10-14T04:40:21.719Z" }, + { url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", size = 145290, upload-time = "2025-10-14T04:40:23.069Z" }, + { url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", size = 163663, upload-time = "2025-10-14T04:40:24.17Z" }, + { url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", size = 151964, upload-time = "2025-10-14T04:40:25.368Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", size = 161064, upload-time = "2025-10-14T04:40:26.806Z" }, + { url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", size = 155015, upload-time = "2025-10-14T04:40:28.284Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", size = 99792, upload-time = "2025-10-14T04:40:29.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", size = 107198, upload-time = "2025-10-14T04:40:30.644Z" }, + { url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", size = 100262, upload-time = "2025-10-14T04:40:32.108Z" }, + { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, + { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, + { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, + { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, + { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, + { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, + { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, + { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, + { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "cryptography" +version = "46.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", size = 7225004, upload-time = "2025-10-15T23:16:52.239Z" }, + { url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload-time = "2025-10-15T23:16:54.369Z" }, + { url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload-time = "2025-10-15T23:16:56.414Z" }, + { url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload-time = "2025-10-15T23:16:58.442Z" }, + { url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload-time = "2025-10-15T23:17:00.378Z" }, + { url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload-time = "2025-10-15T23:17:01.98Z" }, + { url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload-time = "2025-10-15T23:17:04.078Z" }, + { url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload-time = "2025-10-15T23:17:05.483Z" }, + { url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload-time = "2025-10-15T23:17:07.425Z" }, + { url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload-time = "2025-10-15T23:17:09.343Z" }, + { url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload-time = "2025-10-15T23:17:11.22Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload-time = "2025-10-15T23:17:12.829Z" }, + { url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload-time = "2025-10-15T23:17:14.65Z" }, + { url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload-time = "2025-10-15T23:17:16.142Z" }, + { url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload-time = "2025-10-15T23:17:18.04Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e2/a510aa736755bffa9d2f75029c229111a1d02f8ecd5de03078f4c18d91a3/cryptography-46.0.3-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217", size = 7158012, upload-time = "2025-10-15T23:17:19.982Z" }, + { url = "https://files.pythonhosted.org/packages/73/dc/9aa866fbdbb95b02e7f9d086f1fccfeebf8953509b87e3f28fff927ff8a0/cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5", size = 4288728, upload-time = "2025-10-15T23:17:21.527Z" }, + { url = "https://files.pythonhosted.org/packages/c5/fd/bc1daf8230eaa075184cbbf5f8cd00ba9db4fd32d63fb83da4671b72ed8a/cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715", size = 4435078, upload-time = "2025-10-15T23:17:23.042Z" }, + { url = "https://files.pythonhosted.org/packages/82/98/d3bd5407ce4c60017f8ff9e63ffee4200ab3e23fe05b765cab805a7db008/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54", size = 4293460, upload-time = "2025-10-15T23:17:24.885Z" }, + { url = "https://files.pythonhosted.org/packages/26/e9/e23e7900983c2b8af7a08098db406cf989d7f09caea7897e347598d4cd5b/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459", size = 3995237, upload-time = "2025-10-15T23:17:26.449Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/af68c509d4a138cfe299d0d7ddb14afba15233223ebd933b4bbdbc7155d3/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422", size = 4967344, upload-time = "2025-10-15T23:17:28.06Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e3/8643d077c53868b681af077edf6b3cb58288b5423610f21c62aadcbe99f4/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7", size = 4466564, upload-time = "2025-10-15T23:17:29.665Z" }, + { url = "https://files.pythonhosted.org/packages/0e/43/c1e8726fa59c236ff477ff2b5dc071e54b21e5a1e51aa2cee1676f1c986f/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044", size = 4292415, upload-time = "2025-10-15T23:17:31.686Z" }, + { url = "https://files.pythonhosted.org/packages/42/f9/2f8fefdb1aee8a8e3256a0568cffc4e6d517b256a2fe97a029b3f1b9fe7e/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665", size = 4931457, upload-time = "2025-10-15T23:17:33.478Z" }, + { url = "https://files.pythonhosted.org/packages/79/30/9b54127a9a778ccd6d27c3da7563e9f2d341826075ceab89ae3b41bf5be2/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3", size = 4466074, upload-time = "2025-10-15T23:17:35.158Z" }, + { url = "https://files.pythonhosted.org/packages/ac/68/b4f4a10928e26c941b1b6a179143af9f4d27d88fe84a6a3c53592d2e76bf/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20", size = 4420569, upload-time = "2025-10-15T23:17:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/a3/49/3746dab4c0d1979888f125226357d3262a6dd40e114ac29e3d2abdf1ec55/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de", size = 4681941, upload-time = "2025-10-15T23:17:39.236Z" }, + { url = "https://files.pythonhosted.org/packages/fd/30/27654c1dbaf7e4a3531fa1fc77986d04aefa4d6d78259a62c9dc13d7ad36/cryptography-46.0.3-cp314-cp314t-win32.whl", hash = "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914", size = 3022339, upload-time = "2025-10-15T23:17:40.888Z" }, + { url = "https://files.pythonhosted.org/packages/f6/30/640f34ccd4d2a1bc88367b54b926b781b5a018d65f404d409aba76a84b1c/cryptography-46.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db", size = 3494315, upload-time = "2025-10-15T23:17:42.769Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8b/88cc7e3bd0a8e7b861f26981f7b820e1f46aa9d26cc482d0feba0ecb4919/cryptography-46.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21", size = 2919331, upload-time = "2025-10-15T23:17:44.468Z" }, + { url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload-time = "2025-10-15T23:17:46.294Z" }, + { url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" }, + { url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload-time = "2025-10-15T23:17:51.357Z" }, + { url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload-time = "2025-10-15T23:17:52.964Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload-time = "2025-10-15T23:17:54.965Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload-time = "2025-10-15T23:17:56.754Z" }, + { url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload-time = "2025-10-15T23:17:58.588Z" }, + { url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload-time = "2025-10-15T23:18:00.897Z" }, + { url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload-time = "2025-10-15T23:18:02.749Z" }, + { url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload-time = "2025-10-15T23:18:04.85Z" }, + { url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" }, + { url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload-time = "2025-10-15T23:18:08.672Z" }, + { url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload-time = "2025-10-15T23:18:10.632Z" }, + { url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" }, + { url = "https://files.pythonhosted.org/packages/d9/cd/1a8633802d766a0fa46f382a77e096d7e209e0817892929655fe0586ae32/cryptography-46.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a23582810fedb8c0bc47524558fb6c56aac3fc252cb306072fd2815da2a47c32", size = 3689163, upload-time = "2025-10-15T23:18:13.821Z" }, + { url = "https://files.pythonhosted.org/packages/4c/59/6b26512964ace6480c3e54681a9859c974172fb141c38df11eadd8416947/cryptography-46.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e7aec276d68421f9574040c26e2a7c3771060bc0cff408bae1dcb19d3ab1e63c", size = 3429474, upload-time = "2025-10-15T23:18:15.477Z" }, + { url = "https://files.pythonhosted.org/packages/06/8a/e60e46adab4362a682cf142c7dcb5bf79b782ab2199b0dcb81f55970807f/cryptography-46.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7ce938a99998ed3c8aa7e7272dca1a610401ede816d36d0693907d863b10d9ea", size = 3698132, upload-time = "2025-10-15T23:18:17.056Z" }, + { url = "https://files.pythonhosted.org/packages/da/38/f59940ec4ee91e93d3311f7532671a5cef5570eb04a144bf203b58552d11/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:191bb60a7be5e6f54e30ba16fdfae78ad3a342a0599eb4193ba88e3f3d6e185b", size = 4243992, upload-time = "2025-10-15T23:18:18.695Z" }, + { url = "https://files.pythonhosted.org/packages/b0/0c/35b3d92ddebfdfda76bb485738306545817253d0a3ded0bfe80ef8e67aa5/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c70cc23f12726be8f8bc72e41d5065d77e4515efae3690326764ea1b07845cfb", size = 4409944, upload-time = "2025-10-15T23:18:20.597Z" }, + { url = "https://files.pythonhosted.org/packages/99/55/181022996c4063fc0e7666a47049a1ca705abb9c8a13830f074edb347495/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:9394673a9f4de09e28b5356e7fff97d778f8abad85c9d5ac4a4b7e25a0de7717", size = 4242957, upload-time = "2025-10-15T23:18:22.18Z" }, + { url = "https://files.pythonhosted.org/packages/ba/af/72cd6ef29f9c5f731251acadaeb821559fe25f10852f44a63374c9ca08c1/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:94cd0549accc38d1494e1f8de71eca837d0509d0d44bf11d158524b0e12cebf9", size = 4409447, upload-time = "2025-10-15T23:18:24.209Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c3/e90f4a4feae6410f914f8ebac129b9ae7a8c92eb60a638012dde42030a9d/cryptography-46.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c", size = 3438528, upload-time = "2025-10-15T23:18:26.227Z" }, +] + +[[package]] +name = "distlib" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, +] + +[[package]] +name = "docutils" +version = "0.21.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, +] + +[[package]] +name = "filelock" +version = "3.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/46/0028a82567109b5ef6e4d2a1f04a583fb513e6cf9527fcdd09afd817deeb/filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4", size = 18922, upload-time = "2025-10-08T18:03:50.056Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2", size = 16054, upload-time = "2025-10-08T18:03:48.35Z" }, +] + +[[package]] +name = "flask" +version = "3.2.0.dev0" +source = { editable = "." } +dependencies = [ + { name = "blinker" }, + { name = "click" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "werkzeug" }, +] + +[package.optional-dependencies] +async = [ + { name = "asgiref" }, +] +dotenv = [ + { name = "python-dotenv" }, +] + +[package.dev-dependencies] +dev = [ + { name = "ruff" }, + { name = "tox" }, + { name = "tox-uv" }, +] +docs = [ + { name = "pallets-sphinx-themes" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "sphinx-tabs" }, + { name = "sphinxcontrib-log-cabinet" }, +] +docs-auto = [ + { name = "sphinx-autobuild", version = "2024.10.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx-autobuild", version = "2025.8.25", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +gha-update = [ + { name = "gha-update", marker = "python_full_version >= '3.12'" }, +] +pre-commit = [ + { name = "pre-commit" }, + { name = "pre-commit-uv" }, +] +tests = [ + { name = "asgiref" }, + { name = "greenlet" }, + { name = "pytest" }, + { name = "python-dotenv" }, +] +typing = [ + { name = "asgiref" }, + { name = "cryptography" }, + { name = "mypy" }, + { name = "pyright" }, + { name = "pytest" }, + { name = "python-dotenv" }, + { name = "types-contextvars" }, + { name = "types-dataclasses" }, +] + +[package.metadata] +requires-dist = [ + { name = "asgiref", marker = "extra == 'async'", specifier = ">=3.2" }, + { name = "blinker", specifier = ">=1.9.0" }, + { name = "click", specifier = ">=8.1.3" }, + { name = "itsdangerous", specifier = ">=2.2.0" }, + { name = "jinja2", specifier = ">=3.1.2" }, + { name = "markupsafe", specifier = ">=2.1.1" }, + { name = "python-dotenv", marker = "extra == 'dotenv'" }, + { name = "werkzeug", specifier = ">=3.1.0" }, +] +provides-extras = ["async", "dotenv"] + +[package.metadata.requires-dev] +dev = [ + { name = "ruff" }, + { name = "tox" }, + { name = "tox-uv" }, +] +docs = [ + { name = "pallets-sphinx-themes" }, + { name = "sphinx" }, + { name = "sphinx-tabs" }, + { name = "sphinxcontrib-log-cabinet" }, +] +docs-auto = [{ name = "sphinx-autobuild" }] +gha-update = [{ name = "gha-update", marker = "python_full_version >= '3.12'" }] +pre-commit = [ + { name = "pre-commit" }, + { name = "pre-commit-uv" }, +] +tests = [ + { name = "asgiref" }, + { name = "greenlet" }, + { name = "pytest" }, + { name = "python-dotenv" }, +] +typing = [ + { name = "asgiref" }, + { name = "cryptography" }, + { name = "mypy" }, + { name = "pyright" }, + { name = "pytest" }, + { name = "python-dotenv" }, + { name = "types-contextvars" }, + { name = "types-dataclasses" }, +] + +[[package]] +name = "gha-update" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click", marker = "python_full_version >= '3.12'" }, + { name = "httpx", marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/e8/eb710e08998a22b314cc068f14805cfdd12e3934a5496c8916c5a164c65a/gha_update-0.2.0.tar.gz", hash = "sha256:328ee0db09346ad13ee90646698cea2ec1f9035964ddd7c2a728a91034c3f4b0", size = 4756, upload-time = "2025-07-14T03:13:33.254Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/29/a0e42b0b80d614aa82929f65cce0d51443eea296802b2094cadc5660321b/gha_update-0.2.0-py3-none-any.whl", hash = "sha256:ec5641bf23f71baa1232fc61b3059fb08456e1b78150d1e9c1bab69b37046e49", size = 5323, upload-time = "2025-07-14T03:13:31.932Z" }, +] + +[[package]] +name = "greenlet" +version = "3.2.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/ed/6bfa4109fcb23a58819600392564fea69cdc6551ffd5e69ccf1d52a40cbc/greenlet-3.2.4-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8c68325b0d0acf8d91dde4e6f930967dd52a5302cd4062932a6b2e7c2969f47c", size = 271061, upload-time = "2025-08-07T13:17:15.373Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fc/102ec1a2fc015b3a7652abab7acf3541d58c04d3d17a8d3d6a44adae1eb1/greenlet-3.2.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:94385f101946790ae13da500603491f04a76b6e4c059dab271b3ce2e283b2590", size = 629475, upload-time = "2025-08-07T13:42:54.009Z" }, + { url = "https://files.pythonhosted.org/packages/c5/26/80383131d55a4ac0fb08d71660fd77e7660b9db6bdb4e8884f46d9f2cc04/greenlet-3.2.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f10fd42b5ee276335863712fa3da6608e93f70629c631bf77145021600abc23c", size = 640802, upload-time = "2025-08-07T13:45:25.52Z" }, + { url = "https://files.pythonhosted.org/packages/9f/7c/e7833dbcd8f376f3326bd728c845d31dcde4c84268d3921afcae77d90d08/greenlet-3.2.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c8c9e331e58180d0d83c5b7999255721b725913ff6bc6cf39fa2a45841a4fd4b", size = 636703, upload-time = "2025-08-07T13:53:12.622Z" }, + { url = "https://files.pythonhosted.org/packages/e9/49/547b93b7c0428ede7b3f309bc965986874759f7d89e4e04aeddbc9699acb/greenlet-3.2.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58b97143c9cc7b86fc458f215bd0932f1757ce649e05b640fea2e79b54cedb31", size = 635417, upload-time = "2025-08-07T13:18:25.189Z" }, + { url = "https://files.pythonhosted.org/packages/7f/91/ae2eb6b7979e2f9b035a9f612cf70f1bf54aad4e1d125129bef1eae96f19/greenlet-3.2.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2ca18a03a8cfb5b25bc1cbe20f3d9a4c80d8c3b13ba3df49ac3961af0b1018d", size = 584358, upload-time = "2025-08-07T13:18:23.708Z" }, + { url = "https://files.pythonhosted.org/packages/f7/85/433de0c9c0252b22b16d413c9407e6cb3b41df7389afc366ca204dbc1393/greenlet-3.2.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fe0a28a7b952a21e2c062cd5756d34354117796c6d9215a87f55e38d15402c5", size = 1113550, upload-time = "2025-08-07T13:42:37.467Z" }, + { url = "https://files.pythonhosted.org/packages/a1/8d/88f3ebd2bc96bf7747093696f4335a0a8a4c5acfcf1b757717c0d2474ba3/greenlet-3.2.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8854167e06950ca75b898b104b63cc646573aa5fef1353d4508ecdd1ee76254f", size = 1137126, upload-time = "2025-08-07T13:18:20.239Z" }, + { url = "https://files.pythonhosted.org/packages/f1/29/74242b7d72385e29bcc5563fba67dad94943d7cd03552bac320d597f29b2/greenlet-3.2.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f47617f698838ba98f4ff4189aef02e7343952df3a615f847bb575c3feb177a7", size = 1544904, upload-time = "2025-11-04T12:42:04.763Z" }, + { url = "https://files.pythonhosted.org/packages/c8/e2/1572b8eeab0f77df5f6729d6ab6b141e4a84ee8eb9bc8c1e7918f94eda6d/greenlet-3.2.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af41be48a4f60429d5cad9d22175217805098a9ef7c40bfef44f7669fb9d74d8", size = 1611228, upload-time = "2025-11-04T12:42:08.423Z" }, + { url = "https://files.pythonhosted.org/packages/d6/6f/b60b0291d9623c496638c582297ead61f43c4b72eef5e9c926ef4565ec13/greenlet-3.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:73f49b5368b5359d04e18d15828eecc1806033db5233397748f4ca813ff1056c", size = 298654, upload-time = "2025-08-07T13:50:00.469Z" }, + { url = "https://files.pythonhosted.org/packages/a4/de/f28ced0a67749cac23fecb02b694f6473f47686dff6afaa211d186e2ef9c/greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2", size = 272305, upload-time = "2025-08-07T13:15:41.288Z" }, + { url = "https://files.pythonhosted.org/packages/09/16/2c3792cba130000bf2a31c5272999113f4764fd9d874fb257ff588ac779a/greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246", size = 632472, upload-time = "2025-08-07T13:42:55.044Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/95d48d7e3d433e6dae5b1682e4292242a53f22df82e6d3dda81b1701a960/greenlet-3.2.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94abf90142c2a18151632371140b3dba4dee031633fe614cb592dbb6c9e17bc3", size = 644646, upload-time = "2025-08-07T13:45:26.523Z" }, + { url = "https://files.pythonhosted.org/packages/d5/5e/405965351aef8c76b8ef7ad370e5da58d57ef6068df197548b015464001a/greenlet-3.2.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633", size = 640519, upload-time = "2025-08-07T13:53:13.928Z" }, + { url = "https://files.pythonhosted.org/packages/25/5d/382753b52006ce0218297ec1b628e048c4e64b155379331f25a7316eb749/greenlet-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079", size = 639707, upload-time = "2025-08-07T13:18:27.146Z" }, + { url = "https://files.pythonhosted.org/packages/1f/8e/abdd3f14d735b2929290a018ecf133c901be4874b858dd1c604b9319f064/greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8", size = 587684, upload-time = "2025-08-07T13:18:25.164Z" }, + { url = "https://files.pythonhosted.org/packages/5d/65/deb2a69c3e5996439b0176f6651e0052542bb6c8f8ec2e3fba97c9768805/greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52", size = 1116647, upload-time = "2025-08-07T13:42:38.655Z" }, + { url = "https://files.pythonhosted.org/packages/3f/cc/b07000438a29ac5cfb2194bfc128151d52f333cee74dd7dfe3fb733fc16c/greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa", size = 1142073, upload-time = "2025-08-07T13:18:21.737Z" }, + { url = "https://files.pythonhosted.org/packages/67/24/28a5b2fa42d12b3d7e5614145f0bd89714c34c08be6aabe39c14dd52db34/greenlet-3.2.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c9c6de1940a7d828635fbd254d69db79e54619f165ee7ce32fda763a9cb6a58c", size = 1548385, upload-time = "2025-11-04T12:42:11.067Z" }, + { url = "https://files.pythonhosted.org/packages/6a/05/03f2f0bdd0b0ff9a4f7b99333d57b53a7709c27723ec8123056b084e69cd/greenlet-3.2.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03c5136e7be905045160b1b9fdca93dd6727b180feeafda6818e6496434ed8c5", size = 1613329, upload-time = "2025-11-04T12:42:12.928Z" }, + { url = "https://files.pythonhosted.org/packages/d8/0f/30aef242fcab550b0b3520b8e3561156857c94288f0332a79928c31a52cf/greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9", size = 299100, upload-time = "2025-08-07T13:44:12.287Z" }, + { url = "https://files.pythonhosted.org/packages/44/69/9b804adb5fd0671f367781560eb5eb586c4d495277c93bde4307b9e28068/greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd", size = 274079, upload-time = "2025-08-07T13:15:45.033Z" }, + { url = "https://files.pythonhosted.org/packages/46/e9/d2a80c99f19a153eff70bc451ab78615583b8dac0754cfb942223d2c1a0d/greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb", size = 640997, upload-time = "2025-08-07T13:42:56.234Z" }, + { url = "https://files.pythonhosted.org/packages/3b/16/035dcfcc48715ccd345f3a93183267167cdd162ad123cd93067d86f27ce4/greenlet-3.2.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968", size = 655185, upload-time = "2025-08-07T13:45:27.624Z" }, + { url = "https://files.pythonhosted.org/packages/31/da/0386695eef69ffae1ad726881571dfe28b41970173947e7c558d9998de0f/greenlet-3.2.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9", size = 649926, upload-time = "2025-08-07T13:53:15.251Z" }, + { url = "https://files.pythonhosted.org/packages/68/88/69bf19fd4dc19981928ceacbc5fd4bb6bc2215d53199e367832e98d1d8fe/greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6", size = 651839, upload-time = "2025-08-07T13:18:30.281Z" }, + { url = "https://files.pythonhosted.org/packages/19/0d/6660d55f7373b2ff8152401a83e02084956da23ae58cddbfb0b330978fe9/greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0", size = 607586, upload-time = "2025-08-07T13:18:28.544Z" }, + { url = "https://files.pythonhosted.org/packages/8e/1a/c953fdedd22d81ee4629afbb38d2f9d71e37d23caace44775a3a969147d4/greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0", size = 1123281, upload-time = "2025-08-07T13:42:39.858Z" }, + { url = "https://files.pythonhosted.org/packages/3f/c7/12381b18e21aef2c6bd3a636da1088b888b97b7a0362fac2e4de92405f97/greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f", size = 1151142, upload-time = "2025-08-07T13:18:22.981Z" }, + { url = "https://files.pythonhosted.org/packages/27/45/80935968b53cfd3f33cf99ea5f08227f2646e044568c9b1555b58ffd61c2/greenlet-3.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee7a6ec486883397d70eec05059353b8e83eca9168b9f3f9a361971e77e0bcd0", size = 1564846, upload-time = "2025-11-04T12:42:15.191Z" }, + { url = "https://files.pythonhosted.org/packages/69/02/b7c30e5e04752cb4db6202a3858b149c0710e5453b71a3b2aec5d78a1aab/greenlet-3.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:326d234cbf337c9c3def0676412eb7040a35a768efc92504b947b3e9cfc7543d", size = 1633814, upload-time = "2025-11-04T12:42:17.175Z" }, + { url = "https://files.pythonhosted.org/packages/e9/08/b0814846b79399e585f974bbeebf5580fbe59e258ea7be64d9dfb253c84f/greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02", size = 299899, upload-time = "2025-08-07T13:38:53.448Z" }, + { url = "https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", size = 272814, upload-time = "2025-08-07T13:15:50.011Z" }, + { url = "https://files.pythonhosted.org/packages/62/dd/b9f59862e9e257a16e4e610480cfffd29e3fae018a68c2332090b53aac3d/greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", size = 641073, upload-time = "2025-08-07T13:42:57.23Z" }, + { url = "https://files.pythonhosted.org/packages/f7/0b/bc13f787394920b23073ca3b6c4a7a21396301ed75a655bcb47196b50e6e/greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc", size = 655191, upload-time = "2025-08-07T13:45:29.752Z" }, + { url = "https://files.pythonhosted.org/packages/f2/d6/6adde57d1345a8d0f14d31e4ab9c23cfe8e2cd39c3baf7674b4b0338d266/greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a", size = 649516, upload-time = "2025-08-07T13:53:16.314Z" }, + { url = "https://files.pythonhosted.org/packages/7f/3b/3a3328a788d4a473889a2d403199932be55b1b0060f4ddd96ee7cdfcad10/greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504", size = 652169, upload-time = "2025-08-07T13:18:32.861Z" }, + { url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" }, + { url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" }, + { url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" }, + { url = "https://files.pythonhosted.org/packages/1c/53/f9c440463b3057485b8594d7a638bed53ba531165ef0ca0e6c364b5cc807/greenlet-3.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e343822feb58ac4d0a1211bd9399de2b3a04963ddeec21530fc426cc121f19b", size = 1564759, upload-time = "2025-11-04T12:42:19.395Z" }, + { url = "https://files.pythonhosted.org/packages/47/e4/3bb4240abdd0a8d23f4f88adec746a3099f0d86bfedb623f063b2e3b4df0/greenlet-3.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca7f6f1f2649b89ce02f6f229d7c19f680a6238af656f61e0115b24857917929", size = 1634288, upload-time = "2025-11-04T12:42:21.174Z" }, + { url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" }, + { url = "https://files.pythonhosted.org/packages/22/5c/85273fd7cc388285632b0498dbbab97596e04b154933dfe0f3e68156c68c/greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", size = 273586, upload-time = "2025-08-07T13:16:08.004Z" }, + { url = "https://files.pythonhosted.org/packages/d1/75/10aeeaa3da9332c2e761e4c50d4c3556c21113ee3f0afa2cf5769946f7a3/greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", size = 686346, upload-time = "2025-08-07T13:42:59.944Z" }, + { url = "https://files.pythonhosted.org/packages/c0/aa/687d6b12ffb505a4447567d1f3abea23bd20e73a5bed63871178e0831b7a/greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5", size = 699218, upload-time = "2025-08-07T13:45:30.969Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8b/29aae55436521f1d6f8ff4e12fb676f3400de7fcf27fccd1d4d17fd8fecd/greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", size = 694659, upload-time = "2025-08-07T13:53:17.759Z" }, + { url = "https://files.pythonhosted.org/packages/92/2e/ea25914b1ebfde93b6fc4ff46d6864564fba59024e928bdc7de475affc25/greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", size = 695355, upload-time = "2025-08-07T13:18:34.517Z" }, + { url = "https://files.pythonhosted.org/packages/72/60/fc56c62046ec17f6b0d3060564562c64c862948c9d4bc8aa807cf5bd74f4/greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", size = 657512, upload-time = "2025-08-07T13:18:33.969Z" }, + { url = "https://files.pythonhosted.org/packages/23/6e/74407aed965a4ab6ddd93a7ded3180b730d281c77b765788419484cdfeef/greenlet-3.2.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2917bdf657f5859fbf3386b12d68ede4cf1f04c90c3a6bc1f013dd68a22e2269", size = 1612508, upload-time = "2025-11-04T12:42:23.427Z" }, + { url = "https://files.pythonhosted.org/packages/0d/da/343cd760ab2f92bac1845ca07ee3faea9fe52bee65f7bcb19f16ad7de08b/greenlet-3.2.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:015d48959d4add5d6c9f6c5210ee3803a830dce46356e3bc326d6776bde54681", size = 1680760, upload-time = "2025-11-04T12:42:25.341Z" }, + { url = "https://files.pythonhosted.org/packages/e3/a5/6ddab2b4c112be95601c13428db1d8b6608a8b6039816f2ba09c346c08fc/greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01", size = 303425, upload-time = "2025-08-07T13:32:27.59Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi", marker = "python_full_version >= '3.12'" }, + { name = "h11", marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio", marker = "python_full_version >= '3.12'" }, + { name = "certifi", marker = "python_full_version >= '3.12'" }, + { name = "httpcore", marker = "python_full_version >= '3.12'" }, + { name = "idna", marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "identify" +version = "2.6.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/e7/685de97986c916a6d93b3876139e00eef26ad5bbbd61925d670ae8013449/identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf", size = 99311, upload-time = "2025-10-02T17:43:40.631Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757", size = 99183, upload-time = "2025-10-02T17:43:39.137Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026, upload-time = "2022-07-01T12:21:05.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, + { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "mypy" +version = "1.18.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/77/8f0d0001ffad290cef2f7f216f96c814866248a0b92a722365ed54648e7e/mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b", size = 3448846, upload-time = "2025-09-19T00:11:10.519Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/6f/657961a0743cff32e6c0611b63ff1c1970a0b482ace35b069203bf705187/mypy-1.18.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c", size = 12807973, upload-time = "2025-09-19T00:10:35.282Z" }, + { url = "https://files.pythonhosted.org/packages/10/e9/420822d4f661f13ca8900f5fa239b40ee3be8b62b32f3357df9a3045a08b/mypy-1.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e", size = 11896527, upload-time = "2025-09-19T00:10:55.791Z" }, + { url = "https://files.pythonhosted.org/packages/aa/73/a05b2bbaa7005f4642fcfe40fb73f2b4fb6bb44229bd585b5878e9a87ef8/mypy-1.18.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448acd386266989ef11662ce3c8011fd2a7b632e0ec7d61a98edd8e27472225b", size = 12507004, upload-time = "2025-09-19T00:11:05.411Z" }, + { url = "https://files.pythonhosted.org/packages/4f/01/f6e4b9f0d031c11ccbd6f17da26564f3a0f3c4155af344006434b0a05a9d/mypy-1.18.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9e171c465ad3901dc652643ee4bffa8e9fef4d7d0eece23b428908c77a76a66", size = 13245947, upload-time = "2025-09-19T00:10:46.923Z" }, + { url = "https://files.pythonhosted.org/packages/d7/97/19727e7499bfa1ae0773d06afd30ac66a58ed7437d940c70548634b24185/mypy-1.18.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:592ec214750bc00741af1f80cbf96b5013d81486b7bb24cb052382c19e40b428", size = 13499217, upload-time = "2025-09-19T00:09:39.472Z" }, + { url = "https://files.pythonhosted.org/packages/9f/4f/90dc8c15c1441bf31cf0f9918bb077e452618708199e530f4cbd5cede6ff/mypy-1.18.2-cp310-cp310-win_amd64.whl", hash = "sha256:7fb95f97199ea11769ebe3638c29b550b5221e997c63b14ef93d2e971606ebed", size = 9766753, upload-time = "2025-09-19T00:10:49.161Z" }, + { url = "https://files.pythonhosted.org/packages/88/87/cafd3ae563f88f94eec33f35ff722d043e09832ea8530ef149ec1efbaf08/mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f", size = 12731198, upload-time = "2025-09-19T00:09:44.857Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e0/1e96c3d4266a06d4b0197ace5356d67d937d8358e2ee3ffac71faa843724/mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341", size = 11817879, upload-time = "2025-09-19T00:09:47.131Z" }, + { url = "https://files.pythonhosted.org/packages/72/ef/0c9ba89eb03453e76bdac5a78b08260a848c7bfc5d6603634774d9cd9525/mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d", size = 12427292, upload-time = "2025-09-19T00:10:22.472Z" }, + { url = "https://files.pythonhosted.org/packages/1a/52/ec4a061dd599eb8179d5411d99775bec2a20542505988f40fc2fee781068/mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86", size = 13163750, upload-time = "2025-09-19T00:09:51.472Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5f/2cf2ceb3b36372d51568f2208c021870fe7834cf3186b653ac6446511839/mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37", size = 13351827, upload-time = "2025-09-19T00:09:58.311Z" }, + { url = "https://files.pythonhosted.org/packages/c8/7d/2697b930179e7277529eaaec1513f8de622818696857f689e4a5432e5e27/mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8", size = 9757983, upload-time = "2025-09-19T00:10:09.071Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/dfdd2bc60c66611dd8335f463818514733bc763e4760dee289dcc33df709/mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34", size = 12908273, upload-time = "2025-09-19T00:10:58.321Z" }, + { url = "https://files.pythonhosted.org/packages/81/14/6a9de6d13a122d5608e1a04130724caf9170333ac5a924e10f670687d3eb/mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764", size = 11920910, upload-time = "2025-09-19T00:10:20.043Z" }, + { url = "https://files.pythonhosted.org/packages/5f/a9/b29de53e42f18e8cc547e38daa9dfa132ffdc64f7250e353f5c8cdd44bee/mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893", size = 12465585, upload-time = "2025-09-19T00:10:33.005Z" }, + { url = "https://files.pythonhosted.org/packages/77/ae/6c3d2c7c61ff21f2bee938c917616c92ebf852f015fb55917fd6e2811db2/mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914", size = 13348562, upload-time = "2025-09-19T00:10:11.51Z" }, + { url = "https://files.pythonhosted.org/packages/4d/31/aec68ab3b4aebdf8f36d191b0685d99faa899ab990753ca0fee60fb99511/mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8", size = 13533296, upload-time = "2025-09-19T00:10:06.568Z" }, + { url = "https://files.pythonhosted.org/packages/9f/83/abcb3ad9478fca3ebeb6a5358bb0b22c95ea42b43b7789c7fb1297ca44f4/mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074", size = 9828828, upload-time = "2025-09-19T00:10:28.203Z" }, + { url = "https://files.pythonhosted.org/packages/5f/04/7f462e6fbba87a72bc8097b93f6842499c428a6ff0c81dd46948d175afe8/mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc", size = 12898728, upload-time = "2025-09-19T00:10:01.33Z" }, + { url = "https://files.pythonhosted.org/packages/99/5b/61ed4efb64f1871b41fd0b82d29a64640f3516078f6c7905b68ab1ad8b13/mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e", size = 11910758, upload-time = "2025-09-19T00:10:42.607Z" }, + { url = "https://files.pythonhosted.org/packages/3c/46/d297d4b683cc89a6e4108c4250a6a6b717f5fa96e1a30a7944a6da44da35/mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986", size = 12475342, upload-time = "2025-09-19T00:11:00.371Z" }, + { url = "https://files.pythonhosted.org/packages/83/45/4798f4d00df13eae3bfdf726c9244bcb495ab5bd588c0eed93a2f2dd67f3/mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d", size = 13338709, upload-time = "2025-09-19T00:11:03.358Z" }, + { url = "https://files.pythonhosted.org/packages/d7/09/479f7358d9625172521a87a9271ddd2441e1dab16a09708f056e97007207/mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba", size = 13529806, upload-time = "2025-09-19T00:10:26.073Z" }, + { url = "https://files.pythonhosted.org/packages/71/cf/ac0f2c7e9d0ea3c75cd99dff7aec1c9df4a1376537cb90e4c882267ee7e9/mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544", size = 9833262, upload-time = "2025-09-19T00:10:40.035Z" }, + { url = "https://files.pythonhosted.org/packages/5a/0c/7d5300883da16f0063ae53996358758b2a2df2a09c72a5061fa79a1f5006/mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce", size = 12893775, upload-time = "2025-09-19T00:10:03.814Z" }, + { url = "https://files.pythonhosted.org/packages/50/df/2cffbf25737bdb236f60c973edf62e3e7b4ee1c25b6878629e88e2cde967/mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d", size = 11936852, upload-time = "2025-09-19T00:10:51.631Z" }, + { url = "https://files.pythonhosted.org/packages/be/50/34059de13dd269227fb4a03be1faee6e2a4b04a2051c82ac0a0b5a773c9a/mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c", size = 12480242, upload-time = "2025-09-19T00:11:07.955Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/040983fad5132d85914c874a2836252bbc57832065548885b5bb5b0d4359/mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb", size = 13326683, upload-time = "2025-09-19T00:09:55.572Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ba/89b2901dd77414dd7a8c8729985832a5735053be15b744c18e4586e506ef/mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075", size = 13514749, upload-time = "2025-09-19T00:10:44.827Z" }, + { url = "https://files.pythonhosted.org/packages/25/bc/cc98767cffd6b2928ba680f3e5bc969c4152bf7c2d83f92f5a504b92b0eb/mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf", size = 9982959, upload-time = "2025-09-19T00:10:37.344Z" }, + { url = "https://files.pythonhosted.org/packages/87/e3/be76d87158ebafa0309946c4a73831974d4d6ab4f4ef40c3b53a385a66fd/mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e", size = 2352367, upload-time = "2025-09-19T00:10:15.489Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pallets-sphinx-themes" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "sphinx-notfound-page" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3b/08/c57dd89e45dbc976930200a2cb7826ed76f3c9791454a9fcd1cde3f17177/pallets_sphinx_themes-2.3.0.tar.gz", hash = "sha256:6293ced11a1d5d3de7268af1acd60428732b5a9e6051a47a596c6d9a083e60d9", size = 21029, upload-time = "2024-10-24T18:52:38.574Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/7d/a4aa06e452e031559dcfb066e035d2615ebfa6148e93514d7c36030004c1/pallets_sphinx_themes-2.3.0-py3-none-any.whl", hash = "sha256:7ed13de3743c462c2804e2aa63d96cc9ffa82cb76d0251cea03de9bcd9f8dbec", size = 24745, upload-time = "2024-10-24T18:52:37.265Z" }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pre-commit" +version = "4.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/49/7845c2d7bf6474efd8e27905b51b11e6ce411708c91e829b93f324de9929/pre_commit-4.4.0.tar.gz", hash = "sha256:f0233ebab440e9f17cabbb558706eb173d19ace965c68cdce2c081042b4fab15", size = 197501, upload-time = "2025-11-08T21:12:11.607Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/11/574fe7d13acf30bfd0a8dd7fa1647040f2b8064f13f43e8c963b1e65093b/pre_commit-4.4.0-py2.py3-none-any.whl", hash = "sha256:b35ea52957cbf83dcc5d8ee636cbead8624e3a15fbfa61a370e42158ac8a5813", size = 226049, upload-time = "2025-11-08T21:12:10.228Z" }, +] + +[[package]] +name = "pre-commit-uv" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pre-commit" }, + { name = "uv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/42/84372bc99a841bfdd8b182a50186471a7f5e873d8e8bcec0d0cb6dabcbb0/pre_commit_uv-4.2.0.tar.gz", hash = "sha256:c32bb1d90235507726eee2aeef2be5fdab431a6f1906e3f1addb0a4e99b369d1", size = 6912, upload-time = "2025-10-09T19:30:48.354Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/9f/ec8491f6b3022489a4d36ce372214c10a34f90b425aa61ff2e0a8dc5b9d5/pre_commit_uv-4.2.0-py3-none-any.whl", hash = "sha256:cc1b56641e6c62d90a4d8b4f0af6f2610f1c397ce81af024e768c0f33715cb81", size = 5650, upload-time = "2025-10-09T19:30:47.257Z" }, +] + +[[package]] +name = "pycparser" +version = "2.23" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyproject-api" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/7b/c0e1333b61d41c69e59e5366e727b18c4992688caf0de1be10b3e5265f6b/pyproject_api-1.10.0.tar.gz", hash = "sha256:40c6f2d82eebdc4afee61c773ed208c04c19db4c4a60d97f8d7be3ebc0bbb330", size = 22785, upload-time = "2025-10-09T19:12:27.21Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/cc/cecf97be298bee2b2a37dd360618c819a2a7fd95251d8e480c1f0eb88f3b/pyproject_api-1.10.0-py3-none-any.whl", hash = "sha256:8757c41a79c0f4ab71b99abed52b97ecf66bd20b04fa59da43b5840bac105a09", size = 13218, upload-time = "2025-10-09T19:12:24.428Z" }, +] + +[[package]] +name = "pyright" +version = "1.1.407" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nodeenv" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/1b/0aa08ee42948b61745ac5b5b5ccaec4669e8884b53d31c8ec20b2fcd6b6f/pyright-1.1.407.tar.gz", hash = "sha256:099674dba5c10489832d4a4b2d302636152a9a42d317986c38474c76fe562262", size = 4122872, upload-time = "2025-10-24T23:17:15.145Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/93/b69052907d032b00c40cb656d21438ec00b3a471733de137a3f65a49a0a0/pyright-1.1.407-py3-none-any.whl", hash = "sha256:6dd419f54fcc13f03b52285796d65e639786373f433e243f8b94cf93a7444d21", size = 5997008, upload-time = "2025-10-24T23:17:13.159Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/56/f013048ac4bc4c1d9be45afd4ab209ea62822fb1598f40687e6bf45dcea4/pytest-9.0.1.tar.gz", hash = "sha256:3e9c069ea73583e255c3b21cf46b8d3c56f6e3a1a8f6da94ccb0fcf57b9d73c8", size = 1564125, upload-time = "2025-11-12T13:05:09.333Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/8b/6300fb80f858cda1c51ffa17075df5d846757081d11ab4aa35cef9e6258b/pytest-9.0.1-py3-none-any.whl", hash = "sha256:67be0030d194df2dfa7b556f2e56fb3c3315bd5c8822c6951162b92b32ce7dad", size = 373668, upload-time = "2025-11-12T13:05:07.379Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "roman-numerals-py" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/76/48fd56d17c5bdbdf65609abbc67288728a98ed4c02919428d4f52d23b24b/roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d", size = 9017, upload-time = "2025-02-22T07:34:54.333Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c", size = 7742, upload-time = "2025-02-22T07:34:52.422Z" }, +] + +[[package]] +name = "ruff" +version = "0.14.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/fa/fbb67a5780ae0f704876cb8ac92d6d76da41da4dc72b7ed3565ab18f2f52/ruff-0.14.5.tar.gz", hash = "sha256:8d3b48d7d8aad423d3137af7ab6c8b1e38e4de104800f0d596990f6ada1a9fc1", size = 5615944, upload-time = "2025-11-13T19:58:51.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/31/c07e9c535248d10836a94e4f4e8c5a31a1beed6f169b31405b227872d4f4/ruff-0.14.5-py3-none-linux_armv6l.whl", hash = "sha256:f3b8248123b586de44a8018bcc9fefe31d23dda57a34e6f0e1e53bd51fd63594", size = 13171630, upload-time = "2025-11-13T19:57:54.894Z" }, + { url = "https://files.pythonhosted.org/packages/8e/5c/283c62516dca697cd604c2796d1487396b7a436b2f0ecc3fd412aca470e0/ruff-0.14.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f7a75236570318c7a30edd7f5491945f0169de738d945ca8784500b517163a72", size = 13413925, upload-time = "2025-11-13T19:57:59.181Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f3/aa319f4afc22cb6fcba2b9cdfc0f03bbf747e59ab7a8c5e90173857a1361/ruff-0.14.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6d146132d1ee115f8802356a2dc9a634dbf58184c51bff21f313e8cd1c74899a", size = 12574040, upload-time = "2025-11-13T19:58:02.056Z" }, + { url = "https://files.pythonhosted.org/packages/f9/7f/cb5845fcc7c7e88ed57f58670189fc2ff517fe2134c3821e77e29fd3b0c8/ruff-0.14.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2380596653dcd20b057794d55681571a257a42327da8894b93bbd6111aa801f", size = 13009755, upload-time = "2025-11-13T19:58:05.172Z" }, + { url = "https://files.pythonhosted.org/packages/21/d2/bcbedbb6bcb9253085981730687ddc0cc7b2e18e8dc13cf4453de905d7a0/ruff-0.14.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2d1fa985a42b1f075a098fa1ab9d472b712bdb17ad87a8ec86e45e7fa6273e68", size = 12937641, upload-time = "2025-11-13T19:58:08.345Z" }, + { url = "https://files.pythonhosted.org/packages/a4/58/e25de28a572bdd60ffc6bb71fc7fd25a94ec6a076942e372437649cbb02a/ruff-0.14.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88f0770d42b7fa02bbefddde15d235ca3aa24e2f0137388cc15b2dcbb1f7c7a7", size = 13610854, upload-time = "2025-11-13T19:58:11.419Z" }, + { url = "https://files.pythonhosted.org/packages/7d/24/43bb3fd23ecee9861970978ea1a7a63e12a204d319248a7e8af539984280/ruff-0.14.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3676cb02b9061fee7294661071c4709fa21419ea9176087cb77e64410926eb78", size = 15061088, upload-time = "2025-11-13T19:58:14.551Z" }, + { url = "https://files.pythonhosted.org/packages/23/44/a022f288d61c2f8c8645b24c364b719aee293ffc7d633a2ca4d116b9c716/ruff-0.14.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b595bedf6bc9cab647c4a173a61acf4f1ac5f2b545203ba82f30fcb10b0318fb", size = 14734717, upload-time = "2025-11-13T19:58:17.518Z" }, + { url = "https://files.pythonhosted.org/packages/58/81/5c6ba44de7e44c91f68073e0658109d8373b0590940efe5bd7753a2585a3/ruff-0.14.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f55382725ad0bdb2e8ee2babcbbfb16f124f5a59496a2f6a46f1d9d99d93e6e2", size = 14028812, upload-time = "2025-11-13T19:58:20.533Z" }, + { url = "https://files.pythonhosted.org/packages/ad/ef/41a8b60f8462cb320f68615b00299ebb12660097c952c600c762078420f8/ruff-0.14.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7497d19dce23976bdaca24345ae131a1d38dcfe1b0850ad8e9e6e4fa321a6e19", size = 13825656, upload-time = "2025-11-13T19:58:23.345Z" }, + { url = "https://files.pythonhosted.org/packages/7c/00/207e5de737fdb59b39eb1fac806904fe05681981b46d6a6db9468501062e/ruff-0.14.5-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:410e781f1122d6be4f446981dd479470af86537fb0b8857f27a6e872f65a38e4", size = 13959922, upload-time = "2025-11-13T19:58:26.537Z" }, + { url = "https://files.pythonhosted.org/packages/bc/7e/fa1f5c2776db4be405040293618846a2dece5c70b050874c2d1f10f24776/ruff-0.14.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c01be527ef4c91a6d55e53b337bfe2c0f82af024cc1a33c44792d6844e2331e1", size = 12932501, upload-time = "2025-11-13T19:58:29.822Z" }, + { url = "https://files.pythonhosted.org/packages/67/d8/d86bf784d693a764b59479a6bbdc9515ae42c340a5dc5ab1dabef847bfaa/ruff-0.14.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f66e9bb762e68d66e48550b59c74314168ebb46199886c5c5aa0b0fbcc81b151", size = 12927319, upload-time = "2025-11-13T19:58:32.923Z" }, + { url = "https://files.pythonhosted.org/packages/ac/de/ee0b304d450ae007ce0cb3e455fe24fbcaaedae4ebaad6c23831c6663651/ruff-0.14.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d93be8f1fa01022337f1f8f3bcaa7ffee2d0b03f00922c45c2207954f351f465", size = 13206209, upload-time = "2025-11-13T19:58:35.952Z" }, + { url = "https://files.pythonhosted.org/packages/33/aa/193ca7e3a92d74f17d9d5771a765965d2cf42c86e6f0fd95b13969115723/ruff-0.14.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c135d4b681f7401fe0e7312017e41aba9b3160861105726b76cfa14bc25aa367", size = 13953709, upload-time = "2025-11-13T19:58:39.002Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f1/7119e42aa1d3bf036ffc9478885c2e248812b7de9abea4eae89163d2929d/ruff-0.14.5-py3-none-win32.whl", hash = "sha256:c83642e6fccfb6dea8b785eb9f456800dcd6a63f362238af5fc0c83d027dd08b", size = 12925808, upload-time = "2025-11-13T19:58:42.779Z" }, + { url = "https://files.pythonhosted.org/packages/3b/9d/7c0a255d21e0912114784e4a96bf62af0618e2190cae468cd82b13625ad2/ruff-0.14.5-py3-none-win_amd64.whl", hash = "sha256:9d55d7af7166f143c94eae1db3312f9ea8f95a4defef1979ed516dbb38c27621", size = 14331546, upload-time = "2025-11-13T19:58:45.691Z" }, + { url = "https://files.pythonhosted.org/packages/e5/80/69756670caedcf3b9be597a6e12276a6cf6197076eb62aad0c608f8efce0/ruff-0.14.5-py3-none-win_arm64.whl", hash = "sha256:4b700459d4649e2594b31f20a9de33bc7c19976d4746d8d0798ad959621d64a4", size = 13433331, upload-time = "2025-11-13T19:58:48.434Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "snowballstemmer" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" }, +] + +[[package]] +name = "sphinx" +version = "8.1.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "alabaster", marker = "python_full_version < '3.11'" }, + { name = "babel", marker = "python_full_version < '3.11'" }, + { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, + { name = "docutils", marker = "python_full_version < '3.11'" }, + { name = "imagesize", marker = "python_full_version < '3.11'" }, + { name = "jinja2", marker = "python_full_version < '3.11'" }, + { name = "packaging", marker = "python_full_version < '3.11'" }, + { name = "pygments", marker = "python_full_version < '3.11'" }, + { name = "requests", marker = "python_full_version < '3.11'" }, + { name = "snowballstemmer", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version < '3.11'" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/be0b61178fe2cdcb67e2a92fc9ebb488e3c51c4f74a36a7824c0adf23425/sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927", size = 8184611, upload-time = "2024-10-13T20:27:13.93Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/60/1ddff83a56d33aaf6f10ec8ce84b4c007d9368b21008876fceda7e7381ef/sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2", size = 3487125, upload-time = "2024-10-13T20:27:10.448Z" }, +] + +[[package]] +name = "sphinx" +version = "8.2.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "alabaster", marker = "python_full_version >= '3.11'" }, + { name = "babel", marker = "python_full_version >= '3.11'" }, + { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, + { name = "docutils", marker = "python_full_version >= '3.11'" }, + { name = "imagesize", marker = "python_full_version >= '3.11'" }, + { name = "jinja2", marker = "python_full_version >= '3.11'" }, + { name = "packaging", marker = "python_full_version >= '3.11'" }, + { name = "pygments", marker = "python_full_version >= '3.11'" }, + { name = "requests", marker = "python_full_version >= '3.11'" }, + { name = "roman-numerals-py", marker = "python_full_version >= '3.11'" }, + { name = "snowballstemmer", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/ad/4360e50ed56cb483667b8e6dadf2d3fda62359593faabbe749a27c4eaca6/sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348", size = 8321876, upload-time = "2025-03-02T22:31:59.658Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3", size = 3589741, upload-time = "2025-03-02T22:31:56.836Z" }, +] + +[[package]] +name = "sphinx-autobuild" +version = "2024.10.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "starlette", marker = "python_full_version < '3.11'" }, + { name = "uvicorn", marker = "python_full_version < '3.11'" }, + { name = "watchfiles", marker = "python_full_version < '3.11'" }, + { name = "websockets", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a5/2c/155e1de2c1ba96a72e5dba152c509a8b41e047ee5c2def9e9f0d812f8be7/sphinx_autobuild-2024.10.3.tar.gz", hash = "sha256:248150f8f333e825107b6d4b86113ab28fa51750e5f9ae63b59dc339be951fb1", size = 14023, upload-time = "2024-10-02T23:15:30.172Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/c0/eba125db38c84d3c74717008fd3cb5000b68cd7e2cbafd1349c6a38c3d3b/sphinx_autobuild-2024.10.3-py3-none-any.whl", hash = "sha256:158e16c36f9d633e613c9aaf81c19b0fc458ca78b112533b20dafcda430d60fa", size = 11908, upload-time = "2024-10-02T23:15:28.739Z" }, +] + +[[package]] +name = "sphinx-autobuild" +version = "2025.8.25" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "starlette", marker = "python_full_version >= '3.11'" }, + { name = "uvicorn", marker = "python_full_version >= '3.11'" }, + { name = "watchfiles", marker = "python_full_version >= '3.11'" }, + { name = "websockets", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e0/3c/a59a3a453d4133777f7ed2e83c80b7dc817d43c74b74298ca0af869662ad/sphinx_autobuild-2025.8.25.tar.gz", hash = "sha256:9cf5aab32853c8c31af572e4fecdc09c997e2b8be5a07daf2a389e270e85b213", size = 15200, upload-time = "2025-08-25T18:44:55.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/20/56411b52f917696995f5ad27d2ea7e9492c84a043c5b49a3a3173573cd93/sphinx_autobuild-2025.8.25-py3-none-any.whl", hash = "sha256:b750ac7d5a18603e4665294323fd20f6dcc0a984117026d1986704fa68f0379a", size = 12535, upload-time = "2025-08-25T18:44:54.164Z" }, +] + +[[package]] +name = "sphinx-notfound-page" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/b2/67603444a8ee97b4a8ea71b0a9d6bab1727ed65e362c87e02f818ee57b8a/sphinx_notfound_page-1.1.0.tar.gz", hash = "sha256:913e1754370bb3db201d9300d458a8b8b5fb22e9246a816643a819a9ea2b8067", size = 7392, upload-time = "2025-01-28T18:45:02.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/d4/019fe439c840a7966012bbb95ccbdd81c5c10271749706793b43beb05145/sphinx_notfound_page-1.1.0-py3-none-any.whl", hash = "sha256:835dc76ff7914577a1f58d80a2c8418fb6138c0932c8da8adce4d9096fbcd389", size = 8167, upload-time = "2025-01-28T18:45:00.465Z" }, +] + +[[package]] +name = "sphinx-tabs" +version = "3.4.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "pygments" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/53/a9a91995cb365e589f413b77fc75f1c0e9b4ac61bfa8da52a779ad855cc0/sphinx-tabs-3.4.7.tar.gz", hash = "sha256:991ad4a424ff54119799ba1491701aa8130dd43509474aef45a81c42d889784d", size = 15891, upload-time = "2024-10-08T13:37:27.887Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/c6/f47505b564b918a3ba60c1e99232d4942c4a7e44ecaae603e829e3d05dae/sphinx_tabs-3.4.7-py3-none-any.whl", hash = "sha256:c12d7a36fd413b369e9e9967a0a4015781b71a9c393575419834f19204bd1915", size = 9727, upload-time = "2024-10-08T13:37:26.192Z" }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, +] + +[[package]] +name = "sphinxcontrib-log-cabinet" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/75/26/0687391e10c605a4d0c7ebe118c57c51ecc687128bcdae5803d9b96def81/sphinxcontrib-log-cabinet-1.0.1.tar.gz", hash = "sha256:103b2e62df4e57abb943bea05ee9c2beb7da922222c8b77314ffd6ab9901c558", size = 4072, upload-time = "2019-07-05T23:22:34.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/e7/dbfc155c1b4c429a9a8149032a56bfb7bab4efabc656abb24ab4619c715d/sphinxcontrib_log_cabinet-1.0.1-py2.py3-none-any.whl", hash = "sha256:3decc888e8e453d1912cd95d50efb0794a4670a214efa65e71a7de277dcfe2cd", size = 4887, upload-time = "2019-07-05T23:22:32.969Z" }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, +] + +[[package]] +name = "starlette" +version = "0.50.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/b8/73a0e6a6e079a9d9cfa64113d771e421640b6f679a52eeb9b32f72d871a1/starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca", size = 2646985, upload-time = "2025-11-01T15:25:27.516Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload-time = "2025-11-01T15:25:25.461Z" }, +] + +[[package]] +name = "tomli" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" }, + { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" }, + { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" }, + { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" }, + { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" }, + { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" }, + { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" }, + { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" }, + { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" }, + { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" }, + { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" }, + { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" }, + { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" }, + { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" }, + { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" }, + { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" }, + { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" }, + { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" }, + { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" }, + { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" }, + { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" }, + { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" }, + { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" }, + { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" }, + { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" }, + { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" }, + { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" }, + { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" }, + { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" }, + { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" }, + { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" }, + { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" }, + { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" }, + { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" }, + { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, +] + +[[package]] +name = "tox" +version = "4.32.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "chardet" }, + { name = "colorama" }, + { name = "filelock" }, + { name = "packaging" }, + { name = "platformdirs" }, + { name = "pluggy" }, + { name = "pyproject-api" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/59/bf/0e4dbd42724cbae25959f0e34c95d0c730df03ab03f54d52accd9abfc614/tox-4.32.0.tar.gz", hash = "sha256:1ad476b5f4d3679455b89a992849ffc3367560bbc7e9495ee8a3963542e7c8ff", size = 203330, upload-time = "2025-10-24T18:03:38.132Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/cc/e09c0d663a004945f82beecd4f147053567910479314e8d01ba71e5d5dea/tox-4.32.0-py3-none-any.whl", hash = "sha256:451e81dc02ba8d1ed20efd52ee409641ae4b5d5830e008af10fe8823ef1bd551", size = 175905, upload-time = "2025-10-24T18:03:36.337Z" }, +] + +[[package]] +name = "tox-uv" +version = "1.29.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "tox" }, + { name = "uv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4f/90/06752775b8cfadba8856190f5beae9f552547e0f287e0246677972107375/tox_uv-1.29.0.tar.gz", hash = "sha256:30fa9e6ad507df49d3c6a2f88894256bcf90f18e240a00764da6ecab1db24895", size = 23427, upload-time = "2025-10-09T20:40:27.384Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/17/221d62937c4130b044bb437caac4181e7e13d5536bbede65264db1f0ac9f/tox_uv-1.29.0-py3-none-any.whl", hash = "sha256:b1d251286edeeb4bc4af1e24c8acfdd9404700143c2199ccdbb4ea195f7de6cc", size = 17254, upload-time = "2025-10-09T20:40:25.885Z" }, +] + +[[package]] +name = "types-contextvars" +version = "2.4.7.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/20/6a0271fe78050f15eaad21f94a4efebbddcfd0cadac0d35b056e8d32b40f/types-contextvars-2.4.7.3.tar.gz", hash = "sha256:a15a1624c709d04974900ea4f8c4fc2676941bf7d4771a9c9c4ac3daa0e0060d", size = 3166, upload-time = "2023-07-07T09:16:39.067Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/5e/770b3dd271a925b4428ee17664536d037695698eb764b4c5ed6fe815169b/types_contextvars-2.4.7.3-py3-none-any.whl", hash = "sha256:bcd8e97a5b58e76d20f5cc161ba39b29b60ac46dcc6edf3e23c1d33f99b34351", size = 2752, upload-time = "2023-07-07T09:16:37.855Z" }, +] + +[[package]] +name = "types-dataclasses" +version = "0.6.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/6a/dec8fbc818b1e716cb2d9424f1ea0f6f3b1443460eb6a70d00d9d8527360/types-dataclasses-0.6.6.tar.gz", hash = "sha256:4b5a2fcf8e568d5a1974cd69010e320e1af8251177ec968de7b9bb49aa49f7b9", size = 2884, upload-time = "2022-06-30T09:49:21.449Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/85/23ab2bbc280266af5bf22ded4e070946d1694d1721ced90666b649eaa795/types_dataclasses-0.6.6-py3-none-any.whl", hash = "sha256:a0a1ab5324ba30363a15c9daa0f053ae4fff914812a1ebd8ad84a08e5349574d", size = 2868, upload-time = "2022-06-30T09:49:19.977Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + +[[package]] +name = "uv" +version = "0.9.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/42/18d158032c594110e7dab012931a3cd6a09f4dec801854dc877a3c6e865a/uv-0.9.10.tar.gz", hash = "sha256:84128ca76a665fe0584bff6ef913741d5615a4187c1aaed81739d519e669cfbd", size = 3738676, upload-time = "2025-11-17T17:02:36.069Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/d2/68f07f3b96539b986d36a6f2b03f9f8484373f7d8f05949221be1a1f9993/uv-0.9.10-py3-none-linux_armv6l.whl", hash = "sha256:a02b27e00afe0d7908fbb9ec61ad5338508e02356138a8a718af239fe773dd2e", size = 20522030, upload-time = "2025-11-17T17:01:48.503Z" }, + { url = "https://files.pythonhosted.org/packages/27/67/763da49b0dfafb5465376e29e176f56c3f1ca79a38ef59983fe5d62dd371/uv-0.9.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3c2ddc0bca2e4b7e31623a17d60ea5f8d1ee4ff3ee27ddbf884e089e57cd0c93", size = 19627118, upload-time = "2025-11-17T17:01:51.736Z" }, + { url = "https://files.pythonhosted.org/packages/b7/e8/5b5b5a719830ee5505bddf35082f6dc41569dab8fb6761626e5034c27748/uv-0.9.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8c8969d54c9f603107445a23f36fba03a0dfa4c75b3ada2faf94ed371f26f3a4", size = 18174169, upload-time = "2025-11-17T17:01:54.034Z" }, + { url = "https://files.pythonhosted.org/packages/35/6b/4c074cab3bf483a172bec905e1696269b0222d73fd8dc890388da8b110c9/uv-0.9.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:c9a326f04b670f1af2d9fa90e50af57a9a872a8dc90bb377d0c27b2030438ffc", size = 19950337, upload-time = "2025-11-17T17:01:56.35Z" }, + { url = "https://files.pythonhosted.org/packages/45/9d/a8930ce9f5247629335de32dd97f19b955f86fb7524ea268f17181c3bccc/uv-0.9.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:55ecddf19314f7da5b8dea6bcfcc86f5769dd7d22106c731e8e6cfbf3fa6b98d", size = 20123165, upload-time = "2025-11-17T17:01:58.668Z" }, + { url = "https://files.pythonhosted.org/packages/c5/4b/3b136c1effa53ab84bc72f66cbc3aca0013fad6581d9c03c2989635b3be1/uv-0.9.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:763cf33c7c5ab490323fab667b24f66412c3c3ca58e86b56e13fc0109d031a1b", size = 21024525, upload-time = "2025-11-17T17:02:01.022Z" }, + { url = "https://files.pythonhosted.org/packages/64/f8/7aab6deb6362431edd670c2885d802ced2cb045fd4f73df462c9c8b05f32/uv-0.9.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5e7a86eb5edbb9064b1401522eb10c5bd25ff56c04cd0ed253d0cd088a448bef", size = 22578222, upload-time = "2025-11-17T17:02:03.333Z" }, + { url = "https://files.pythonhosted.org/packages/7d/5c/658647132d2c2396bd484b9ee42ab9a0a3f439a14b59e91c48b66b6b7403/uv-0.9.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c133e34feca0823d952a1d323d1d47f2b35e13154de5ca293d188f5b8792a62", size = 22205447, upload-time = "2025-11-17T17:02:05.706Z" }, + { url = "https://files.pythonhosted.org/packages/80/22/b4c766d855aaefd3797c6e296af4b006d560b78cec5f3041902cd6d7abce/uv-0.9.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:92d4b58ef810eaf5edf95c607d613ff240ab903ee599af1d687f891ab64b4129", size = 21247210, upload-time = "2025-11-17T17:02:08.312Z" }, + { url = "https://files.pythonhosted.org/packages/a2/0c/4b705b613237519e8640baaa1fe4809ddef2482ad7b82ea807c71e5ddb5d/uv-0.9.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21981bc859802c94d4b8f026b8734d39e8146baac703f1e3eab2e2d87d65ca8c", size = 21389310, upload-time = "2025-11-17T17:02:11.117Z" }, + { url = "https://files.pythonhosted.org/packages/35/44/81df7494107d8473a754fea74e51c8727192a71c11fc4c92aa901e36a255/uv-0.9.10-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:110fd2062dccc4825f96cef0007f6bbb6f5da117af50415bed6f6dcd5e1e3603", size = 20062308, upload-time = "2025-11-17T17:02:14.437Z" }, + { url = "https://files.pythonhosted.org/packages/d5/b5/0919100138d2cdb2f4f058a0e7c6fc059aad1914b1f6637c7fcfe2e670d9/uv-0.9.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:b0a0c4594871de9e8b12c9e10982dc83e2414b712a34e84860fac9fbc8510c5a", size = 21182524, upload-time = "2025-11-17T17:02:16.844Z" }, + { url = "https://files.pythonhosted.org/packages/43/f5/deb8be34797f71ae72f690da26b388682cc478bda630822fff7bc90e80f3/uv-0.9.10-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:516d1c5549912696ba68376d2652661ed327199a9ec15763f7baa29bc75b35ec", size = 20084575, upload-time = "2025-11-17T17:02:19.174Z" }, + { url = "https://files.pythonhosted.org/packages/f1/da/4e51d384669103a9ba8ede1ecf1c99c932148bdb365794ac586f90e5296f/uv-0.9.10-py3-none-musllinux_1_1_i686.whl", hash = "sha256:780a7af4a7dfb0894a8d367b5c3a48f926480f465a34aa4a8633d6e4f521e517", size = 20495061, upload-time = "2025-11-17T17:02:21.978Z" }, + { url = "https://files.pythonhosted.org/packages/78/9f/c16fb7e0290744a22987ffb9700c69fe1e459e199e04f3e881ce05d032e4/uv-0.9.10-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:4e256d3cc542f73435b27a3d0bf2a6b0f9d2dd6dd5c8df1a8028834deb896819", size = 21601257, upload-time = "2025-11-17T17:02:24.646Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f9/ea01841fe2660add4b8fbc2b3a34e8d4f812d9bdaeeeead5470e1142b4be/uv-0.9.10-py3-none-win32.whl", hash = "sha256:dfcc3cd5765158e82a0f52066462e378887aac01b347b588907fe3290af92356", size = 19338075, upload-time = "2025-11-17T17:02:28.007Z" }, + { url = "https://files.pythonhosted.org/packages/43/c3/72dbdf10ea0e70ca47248aaa25122df9dfa7a7e85e96ba6031cafc67cb02/uv-0.9.10-py3-none-win_amd64.whl", hash = "sha256:9ee9027eb98cf5a99d9a3d5ddab7048745e2e49d572869044f66772e17f57792", size = 21367277, upload-time = "2025-11-17T17:02:30.672Z" }, + { url = "https://files.pythonhosted.org/packages/6c/9d/b38be12b524dbd28dcdc99cfd88f6f3f792823258b96efb6d691025872cb/uv-0.9.10-py3-none-win_arm64.whl", hash = "sha256:d4903e73f5e281ce5c3def60014bef93d2d1a148901653e222470ce54987f4a1", size = 19770409, upload-time = "2025-11-17T17:02:34.057Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.38.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/ce/f06b84e2697fef4688ca63bdb2fdf113ca0a3be33f94488f2cadb690b0cf/uvicorn-0.38.0.tar.gz", hash = "sha256:fd97093bdd120a2609fc0d3afe931d4d4ad688b6e75f0f929fde1bc36fe0e91d", size = 80605, upload-time = "2025-10-18T13:46:44.63Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/d9/d88e73ca598f4f6ff671fb5fde8a32925c2e08a637303a1d12883c7305fa/uvicorn-0.38.0-py3-none-any.whl", hash = "sha256:48c0afd214ceb59340075b4a052ea1ee91c16fbc2a9b1469cca0e54566977b02", size = 68109, upload-time = "2025-10-18T13:46:42.958Z" }, +] + +[[package]] +name = "virtualenv" +version = "20.35.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/20/28/e6f1a6f655d620846bd9df527390ecc26b3805a0c5989048c210e22c5ca9/virtualenv-20.35.4.tar.gz", hash = "sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c", size = 6028799, upload-time = "2025-10-29T06:57:40.511Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl", hash = "sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b", size = 6005095, upload-time = "2025-10-29T06:57:37.598Z" }, +] + +[[package]] +name = "watchfiles" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440, upload-time = "2025-10-14T15:06:21.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/1a/206e8cf2dd86fddf939165a57b4df61607a1e0add2785f170a3f616b7d9f/watchfiles-1.1.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:eef58232d32daf2ac67f42dea51a2c80f0d03379075d44a587051e63cc2e368c", size = 407318, upload-time = "2025-10-14T15:04:18.753Z" }, + { url = "https://files.pythonhosted.org/packages/b3/0f/abaf5262b9c496b5dad4ed3c0e799cbecb1f8ea512ecb6ddd46646a9fca3/watchfiles-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:03fa0f5237118a0c5e496185cafa92878568b652a2e9a9382a5151b1a0380a43", size = 394478, upload-time = "2025-10-14T15:04:20.297Z" }, + { url = "https://files.pythonhosted.org/packages/b1/04/9cc0ba88697b34b755371f5ace8d3a4d9a15719c07bdc7bd13d7d8c6a341/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca65483439f9c791897f7db49202301deb6e15fe9f8fe2fed555bf986d10c31", size = 449894, upload-time = "2025-10-14T15:04:21.527Z" }, + { url = "https://files.pythonhosted.org/packages/d2/9c/eda4615863cd8621e89aed4df680d8c3ec3da6a4cf1da113c17decd87c7f/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f0ab1c1af0cb38e3f598244c17919fb1a84d1629cc08355b0074b6d7f53138ac", size = 459065, upload-time = "2025-10-14T15:04:22.795Z" }, + { url = "https://files.pythonhosted.org/packages/84/13/f28b3f340157d03cbc8197629bc109d1098764abe1e60874622a0be5c112/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bc570d6c01c206c46deb6e935a260be44f186a2f05179f52f7fcd2be086a94d", size = 488377, upload-time = "2025-10-14T15:04:24.138Z" }, + { url = "https://files.pythonhosted.org/packages/86/93/cfa597fa9389e122488f7ffdbd6db505b3b915ca7435ecd7542e855898c2/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e84087b432b6ac94778de547e08611266f1f8ffad28c0ee4c82e028b0fc5966d", size = 595837, upload-time = "2025-10-14T15:04:25.057Z" }, + { url = "https://files.pythonhosted.org/packages/57/1e/68c1ed5652b48d89fc24d6af905d88ee4f82fa8bc491e2666004e307ded1/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:620bae625f4cb18427b1bb1a2d9426dc0dd5a5ba74c7c2cdb9de405f7b129863", size = 473456, upload-time = "2025-10-14T15:04:26.497Z" }, + { url = "https://files.pythonhosted.org/packages/d5/dc/1a680b7458ffa3b14bb64878112aefc8f2e4f73c5af763cbf0bd43100658/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:544364b2b51a9b0c7000a4b4b02f90e9423d97fbbf7e06689236443ebcad81ab", size = 455614, upload-time = "2025-10-14T15:04:27.539Z" }, + { url = "https://files.pythonhosted.org/packages/61/a5/3d782a666512e01eaa6541a72ebac1d3aae191ff4a31274a66b8dd85760c/watchfiles-1.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bbe1ef33d45bc71cf21364df962af171f96ecaeca06bd9e3d0b583efb12aec82", size = 630690, upload-time = "2025-10-14T15:04:28.495Z" }, + { url = "https://files.pythonhosted.org/packages/9b/73/bb5f38590e34687b2a9c47a244aa4dd50c56a825969c92c9c5fc7387cea1/watchfiles-1.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1a0bb430adb19ef49389e1ad368450193a90038b5b752f4ac089ec6942c4dff4", size = 622459, upload-time = "2025-10-14T15:04:29.491Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ac/c9bb0ec696e07a20bd58af5399aeadaef195fb2c73d26baf55180fe4a942/watchfiles-1.1.1-cp310-cp310-win32.whl", hash = "sha256:3f6d37644155fb5beca5378feb8c1708d5783145f2a0f1c4d5a061a210254844", size = 272663, upload-time = "2025-10-14T15:04:30.435Z" }, + { url = "https://files.pythonhosted.org/packages/11/a0/a60c5a7c2ec59fa062d9a9c61d02e3b6abd94d32aac2d8344c4bdd033326/watchfiles-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:a36d8efe0f290835fd0f33da35042a1bb5dc0e83cbc092dcf69bce442579e88e", size = 287453, upload-time = "2025-10-14T15:04:31.53Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/2c5f479fb531ce2f0564eda479faecf253d886b1ab3630a39b7bf7362d46/watchfiles-1.1.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f57b396167a2565a4e8b5e56a5a1c537571733992b226f4f1197d79e94cf0ae5", size = 406529, upload-time = "2025-10-14T15:04:32.899Z" }, + { url = "https://files.pythonhosted.org/packages/fe/cd/f515660b1f32f65df671ddf6f85bfaca621aee177712874dc30a97397977/watchfiles-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:421e29339983e1bebc281fab40d812742268ad057db4aee8c4d2bce0af43b741", size = 394384, upload-time = "2025-10-14T15:04:33.761Z" }, + { url = "https://files.pythonhosted.org/packages/7b/c3/28b7dc99733eab43fca2d10f55c86e03bd6ab11ca31b802abac26b23d161/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e43d39a741e972bab5d8100b5cdacf69db64e34eb19b6e9af162bccf63c5cc6", size = 448789, upload-time = "2025-10-14T15:04:34.679Z" }, + { url = "https://files.pythonhosted.org/packages/4a/24/33e71113b320030011c8e4316ccca04194bf0cbbaeee207f00cbc7d6b9f5/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f537afb3276d12814082a2e9b242bdcf416c2e8fd9f799a737990a1dbe906e5b", size = 460521, upload-time = "2025-10-14T15:04:35.963Z" }, + { url = "https://files.pythonhosted.org/packages/f4/c3/3c9a55f255aa57b91579ae9e98c88704955fa9dac3e5614fb378291155df/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2cd9e04277e756a2e2d2543d65d1e2166d6fd4c9b183f8808634fda23f17b14", size = 488722, upload-time = "2025-10-14T15:04:37.091Z" }, + { url = "https://files.pythonhosted.org/packages/49/36/506447b73eb46c120169dc1717fe2eff07c234bb3232a7200b5f5bd816e9/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f3f58818dc0b07f7d9aa7fe9eb1037aecb9700e63e1f6acfed13e9fef648f5d", size = 596088, upload-time = "2025-10-14T15:04:38.39Z" }, + { url = "https://files.pythonhosted.org/packages/82/ab/5f39e752a9838ec4d52e9b87c1e80f1ee3ccdbe92e183c15b6577ab9de16/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb9f66367023ae783551042d31b1d7fd422e8289eedd91f26754a66f44d5cff", size = 472923, upload-time = "2025-10-14T15:04:39.666Z" }, + { url = "https://files.pythonhosted.org/packages/af/b9/a419292f05e302dea372fa7e6fda5178a92998411f8581b9830d28fb9edb/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aebfd0861a83e6c3d1110b78ad54704486555246e542be3e2bb94195eabb2606", size = 456080, upload-time = "2025-10-14T15:04:40.643Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c3/d5932fd62bde1a30c36e10c409dc5d54506726f08cb3e1d8d0ba5e2bc8db/watchfiles-1.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5fac835b4ab3c6487b5dbad78c4b3724e26bcc468e886f8ba8cc4306f68f6701", size = 629432, upload-time = "2025-10-14T15:04:41.789Z" }, + { url = "https://files.pythonhosted.org/packages/f7/77/16bddd9779fafb795f1a94319dc965209c5641db5bf1edbbccace6d1b3c0/watchfiles-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:399600947b170270e80134ac854e21b3ccdefa11a9529a3decc1327088180f10", size = 623046, upload-time = "2025-10-14T15:04:42.718Z" }, + { url = "https://files.pythonhosted.org/packages/46/ef/f2ecb9a0f342b4bfad13a2787155c6ee7ce792140eac63a34676a2feeef2/watchfiles-1.1.1-cp311-cp311-win32.whl", hash = "sha256:de6da501c883f58ad50db3a32ad397b09ad29865b5f26f64c24d3e3281685849", size = 271473, upload-time = "2025-10-14T15:04:43.624Z" }, + { url = "https://files.pythonhosted.org/packages/94/bc/f42d71125f19731ea435c3948cad148d31a64fccde3867e5ba4edee901f9/watchfiles-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:35c53bd62a0b885bf653ebf6b700d1bf05debb78ad9292cf2a942b23513dc4c4", size = 287598, upload-time = "2025-10-14T15:04:44.516Z" }, + { url = "https://files.pythonhosted.org/packages/57/c9/a30f897351f95bbbfb6abcadafbaca711ce1162f4db95fc908c98a9165f3/watchfiles-1.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:57ca5281a8b5e27593cb7d82c2ac927ad88a96ed406aa446f6344e4328208e9e", size = 277210, upload-time = "2025-10-14T15:04:45.883Z" }, + { url = "https://files.pythonhosted.org/packages/74/d5/f039e7e3c639d9b1d09b07ea412a6806d38123f0508e5f9b48a87b0a76cc/watchfiles-1.1.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8c89f9f2f740a6b7dcc753140dd5e1ab9215966f7a3530d0c0705c83b401bd7d", size = 404745, upload-time = "2025-10-14T15:04:46.731Z" }, + { url = "https://files.pythonhosted.org/packages/a5/96/a881a13aa1349827490dab2d363c8039527060cfcc2c92cc6d13d1b1049e/watchfiles-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd404be08018c37350f0d6e34676bd1e2889990117a2b90070b3007f172d0610", size = 391769, upload-time = "2025-10-14T15:04:48.003Z" }, + { url = "https://files.pythonhosted.org/packages/4b/5b/d3b460364aeb8da471c1989238ea0e56bec24b6042a68046adf3d9ddb01c/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8526e8f916bb5b9a0a777c8317c23ce65de259422bba5b31325a6fa6029d33af", size = 449374, upload-time = "2025-10-14T15:04:49.179Z" }, + { url = "https://files.pythonhosted.org/packages/b9/44/5769cb62d4ed055cb17417c0a109a92f007114a4e07f30812a73a4efdb11/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2edc3553362b1c38d9f06242416a5d8e9fe235c204a4072e988ce2e5bb1f69f6", size = 459485, upload-time = "2025-10-14T15:04:50.155Z" }, + { url = "https://files.pythonhosted.org/packages/19/0c/286b6301ded2eccd4ffd0041a1b726afda999926cf720aab63adb68a1e36/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30f7da3fb3f2844259cba4720c3fc7138eb0f7b659c38f3bfa65084c7fc7abce", size = 488813, upload-time = "2025-10-14T15:04:51.059Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2b/8530ed41112dd4a22f4dcfdb5ccf6a1baad1ff6eed8dc5a5f09e7e8c41c7/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8979280bdafff686ba5e4d8f97840f929a87ed9cdf133cbbd42f7766774d2aa", size = 594816, upload-time = "2025-10-14T15:04:52.031Z" }, + { url = "https://files.pythonhosted.org/packages/ce/d2/f5f9fb49489f184f18470d4f99f4e862a4b3e9ac2865688eb2099e3d837a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dcc5c24523771db3a294c77d94771abcfcb82a0e0ee8efd910c37c59ec1b31bb", size = 475186, upload-time = "2025-10-14T15:04:53.064Z" }, + { url = "https://files.pythonhosted.org/packages/cf/68/5707da262a119fb06fbe214d82dd1fe4a6f4af32d2d14de368d0349eb52a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db5d7ae38ff20153d542460752ff397fcf5c96090c1230803713cf3147a6803", size = 456812, upload-time = "2025-10-14T15:04:55.174Z" }, + { url = "https://files.pythonhosted.org/packages/66/ab/3cbb8756323e8f9b6f9acb9ef4ec26d42b2109bce830cc1f3468df20511d/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:28475ddbde92df1874b6c5c8aaeb24ad5be47a11f87cde5a28ef3835932e3e94", size = 630196, upload-time = "2025-10-14T15:04:56.22Z" }, + { url = "https://files.pythonhosted.org/packages/78/46/7152ec29b8335f80167928944a94955015a345440f524d2dfe63fc2f437b/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:36193ed342f5b9842edd3532729a2ad55c4160ffcfa3700e0d54be496b70dd43", size = 622657, upload-time = "2025-10-14T15:04:57.521Z" }, + { url = "https://files.pythonhosted.org/packages/0a/bf/95895e78dd75efe9a7f31733607f384b42eb5feb54bd2eb6ed57cc2e94f4/watchfiles-1.1.1-cp312-cp312-win32.whl", hash = "sha256:859e43a1951717cc8de7f4c77674a6d389b106361585951d9e69572823f311d9", size = 272042, upload-time = "2025-10-14T15:04:59.046Z" }, + { url = "https://files.pythonhosted.org/packages/87/0a/90eb755f568de2688cb220171c4191df932232c20946966c27a59c400850/watchfiles-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:91d4c9a823a8c987cce8fa2690923b069966dabb196dd8d137ea2cede885fde9", size = 288410, upload-time = "2025-10-14T15:05:00.081Z" }, + { url = "https://files.pythonhosted.org/packages/36/76/f322701530586922fbd6723c4f91ace21364924822a8772c549483abed13/watchfiles-1.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:a625815d4a2bdca61953dbba5a39d60164451ef34c88d751f6c368c3ea73d404", size = 278209, upload-time = "2025-10-14T15:05:01.168Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f4/f750b29225fe77139f7ae5de89d4949f5a99f934c65a1f1c0b248f26f747/watchfiles-1.1.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:130e4876309e8686a5e37dba7d5e9bc77e6ed908266996ca26572437a5271e18", size = 404321, upload-time = "2025-10-14T15:05:02.063Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/f07a295cde762644aa4c4bb0f88921d2d141af45e735b965fb2e87858328/watchfiles-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f3bde70f157f84ece3765b42b4a52c6ac1a50334903c6eaf765362f6ccca88a", size = 391783, upload-time = "2025-10-14T15:05:03.052Z" }, + { url = "https://files.pythonhosted.org/packages/bc/11/fc2502457e0bea39a5c958d86d2cb69e407a4d00b85735ca724bfa6e0d1a/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e0b1fe858430fc0251737ef3824c54027bedb8c37c38114488b8e131cf8219", size = 449279, upload-time = "2025-10-14T15:05:04.004Z" }, + { url = "https://files.pythonhosted.org/packages/e3/1f/d66bc15ea0b728df3ed96a539c777acfcad0eb78555ad9efcaa1274688f0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f27db948078f3823a6bb3b465180db8ebecf26dd5dae6f6180bd87383b6b4428", size = 459405, upload-time = "2025-10-14T15:05:04.942Z" }, + { url = "https://files.pythonhosted.org/packages/be/90/9f4a65c0aec3ccf032703e6db02d89a157462fbb2cf20dd415128251cac0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059098c3a429f62fc98e8ec62b982230ef2c8df68c79e826e37b895bc359a9c0", size = 488976, upload-time = "2025-10-14T15:05:05.905Z" }, + { url = "https://files.pythonhosted.org/packages/37/57/ee347af605d867f712be7029bb94c8c071732a4b44792e3176fa3c612d39/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfb5862016acc9b869bb57284e6cb35fdf8e22fe59f7548858e2f971d045f150", size = 595506, upload-time = "2025-10-14T15:05:06.906Z" }, + { url = "https://files.pythonhosted.org/packages/a8/78/cc5ab0b86c122047f75e8fc471c67a04dee395daf847d3e59381996c8707/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:319b27255aacd9923b8a276bb14d21a5f7ff82564c744235fc5eae58d95422ae", size = 474936, upload-time = "2025-10-14T15:05:07.906Z" }, + { url = "https://files.pythonhosted.org/packages/62/da/def65b170a3815af7bd40a3e7010bf6ab53089ef1b75d05dd5385b87cf08/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c755367e51db90e75b19454b680903631d41f9e3607fbd941d296a020c2d752d", size = 456147, upload-time = "2025-10-14T15:05:09.138Z" }, + { url = "https://files.pythonhosted.org/packages/57/99/da6573ba71166e82d288d4df0839128004c67d2778d3b566c138695f5c0b/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c22c776292a23bfc7237a98f791b9ad3144b02116ff10d820829ce62dff46d0b", size = 630007, upload-time = "2025-10-14T15:05:10.117Z" }, + { url = "https://files.pythonhosted.org/packages/a8/51/7439c4dd39511368849eb1e53279cd3454b4a4dbace80bab88feeb83c6b5/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3a476189be23c3686bc2f4321dd501cb329c0a0469e77b7b534ee10129ae6374", size = 622280, upload-time = "2025-10-14T15:05:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/95/9c/8ed97d4bba5db6fdcdb2b298d3898f2dd5c20f6b73aee04eabe56c59677e/watchfiles-1.1.1-cp313-cp313-win32.whl", hash = "sha256:bf0a91bfb5574a2f7fc223cf95eeea79abfefa404bf1ea5e339c0c1560ae99a0", size = 272056, upload-time = "2025-10-14T15:05:12.156Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/c14e28429f744a260d8ceae18bf58c1d5fa56b50d006a7a9f80e1882cb0d/watchfiles-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:52e06553899e11e8074503c8e716d574adeeb7e68913115c4b3653c53f9bae42", size = 288162, upload-time = "2025-10-14T15:05:13.208Z" }, + { url = "https://files.pythonhosted.org/packages/dc/61/fe0e56c40d5cd29523e398d31153218718c5786b5e636d9ae8ae79453d27/watchfiles-1.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac3cc5759570cd02662b15fbcd9d917f7ecd47efe0d6b40474eafd246f91ea18", size = 277909, upload-time = "2025-10-14T15:05:14.49Z" }, + { url = "https://files.pythonhosted.org/packages/79/42/e0a7d749626f1e28c7108a99fb9bf524b501bbbeb9b261ceecde644d5a07/watchfiles-1.1.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:563b116874a9a7ce6f96f87cd0b94f7faf92d08d0021e837796f0a14318ef8da", size = 403389, upload-time = "2025-10-14T15:05:15.777Z" }, + { url = "https://files.pythonhosted.org/packages/15/49/08732f90ce0fbbc13913f9f215c689cfc9ced345fb1bcd8829a50007cc8d/watchfiles-1.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ad9fe1dae4ab4212d8c91e80b832425e24f421703b5a42ef2e4a1e215aff051", size = 389964, upload-time = "2025-10-14T15:05:16.85Z" }, + { url = "https://files.pythonhosted.org/packages/27/0d/7c315d4bd5f2538910491a0393c56bf70d333d51bc5b34bee8e68e8cea19/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce70f96a46b894b36eba678f153f052967a0d06d5b5a19b336ab0dbbd029f73e", size = 448114, upload-time = "2025-10-14T15:05:17.876Z" }, + { url = "https://files.pythonhosted.org/packages/c3/24/9e096de47a4d11bc4df41e9d1e61776393eac4cb6eb11b3e23315b78b2cc/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb467c999c2eff23a6417e58d75e5828716f42ed8289fe6b77a7e5a91036ca70", size = 460264, upload-time = "2025-10-14T15:05:18.962Z" }, + { url = "https://files.pythonhosted.org/packages/cc/0f/e8dea6375f1d3ba5fcb0b3583e2b493e77379834c74fd5a22d66d85d6540/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:836398932192dae4146c8f6f737d74baeac8b70ce14831a239bdb1ca882fc261", size = 487877, upload-time = "2025-10-14T15:05:20.094Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/df24cfc6424a12deb41503b64d42fbea6b8cb357ec62ca84a5a3476f654a/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:743185e7372b7bc7c389e1badcc606931a827112fbbd37f14c537320fca08620", size = 595176, upload-time = "2025-10-14T15:05:21.134Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b5/853b6757f7347de4e9b37e8cc3289283fb983cba1ab4d2d7144694871d9c/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afaeff7696e0ad9f02cbb8f56365ff4686ab205fcf9c4c5b6fdfaaa16549dd04", size = 473577, upload-time = "2025-10-14T15:05:22.306Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f7/0a4467be0a56e80447c8529c9fce5b38eab4f513cb3d9bf82e7392a5696b/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7eb7da0eb23aa2ba036d4f616d46906013a68caf61b7fdbe42fc8b25132e77", size = 455425, upload-time = "2025-10-14T15:05:23.348Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e0/82583485ea00137ddf69bc84a2db88bd92ab4a6e3c405e5fb878ead8d0e7/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:831a62658609f0e5c64178211c942ace999517f5770fe9436be4c2faeba0c0ef", size = 628826, upload-time = "2025-10-14T15:05:24.398Z" }, + { url = "https://files.pythonhosted.org/packages/28/9a/a785356fccf9fae84c0cc90570f11702ae9571036fb25932f1242c82191c/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f9a2ae5c91cecc9edd47e041a930490c31c3afb1f5e6d71de3dc671bfaca02bf", size = 622208, upload-time = "2025-10-14T15:05:25.45Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f4/0872229324ef69b2c3edec35e84bd57a1289e7d3fe74588048ed8947a323/watchfiles-1.1.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:d1715143123baeeaeadec0528bb7441103979a1d5f6fd0e1f915383fea7ea6d5", size = 404315, upload-time = "2025-10-14T15:05:26.501Z" }, + { url = "https://files.pythonhosted.org/packages/7b/22/16d5331eaed1cb107b873f6ae1b69e9ced582fcf0c59a50cd84f403b1c32/watchfiles-1.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:39574d6370c4579d7f5d0ad940ce5b20db0e4117444e39b6d8f99db5676c52fd", size = 390869, upload-time = "2025-10-14T15:05:27.649Z" }, + { url = "https://files.pythonhosted.org/packages/b2/7e/5643bfff5acb6539b18483128fdc0ef2cccc94a5b8fbda130c823e8ed636/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7365b92c2e69ee952902e8f70f3ba6360d0d596d9299d55d7d386df84b6941fb", size = 449919, upload-time = "2025-10-14T15:05:28.701Z" }, + { url = "https://files.pythonhosted.org/packages/51/2e/c410993ba5025a9f9357c376f48976ef0e1b1aefb73b97a5ae01a5972755/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bfff9740c69c0e4ed32416f013f3c45e2ae42ccedd1167ef2d805c000b6c71a5", size = 460845, upload-time = "2025-10-14T15:05:30.064Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a4/2df3b404469122e8680f0fcd06079317e48db58a2da2950fb45020947734/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b27cf2eb1dda37b2089e3907d8ea92922b673c0c427886d4edc6b94d8dfe5db3", size = 489027, upload-time = "2025-10-14T15:05:31.064Z" }, + { url = "https://files.pythonhosted.org/packages/ea/84/4587ba5b1f267167ee715b7f66e6382cca6938e0a4b870adad93e44747e6/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526e86aced14a65a5b0ec50827c745597c782ff46b571dbfe46192ab9e0b3c33", size = 595615, upload-time = "2025-10-14T15:05:32.074Z" }, + { url = "https://files.pythonhosted.org/packages/6a/0f/c6988c91d06e93cd0bb3d4a808bcf32375ca1904609835c3031799e3ecae/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04e78dd0b6352db95507fd8cb46f39d185cf8c74e4cf1e4fbad1d3df96faf510", size = 474836, upload-time = "2025-10-14T15:05:33.209Z" }, + { url = "https://files.pythonhosted.org/packages/b4/36/ded8aebea91919485b7bbabbd14f5f359326cb5ec218cd67074d1e426d74/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c85794a4cfa094714fb9c08d4a218375b2b95b8ed1666e8677c349906246c05", size = 455099, upload-time = "2025-10-14T15:05:34.189Z" }, + { url = "https://files.pythonhosted.org/packages/98/e0/8c9bdba88af756a2fce230dd365fab2baf927ba42cd47521ee7498fd5211/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:74d5012b7630714b66be7b7b7a78855ef7ad58e8650c73afc4c076a1f480a8d6", size = 630626, upload-time = "2025-10-14T15:05:35.216Z" }, + { url = "https://files.pythonhosted.org/packages/2a/84/a95db05354bf2d19e438520d92a8ca475e578c647f78f53197f5a2f17aaf/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:8fbe85cb3201c7d380d3d0b90e63d520f15d6afe217165d7f98c9c649654db81", size = 622519, upload-time = "2025-10-14T15:05:36.259Z" }, + { url = "https://files.pythonhosted.org/packages/1d/ce/d8acdc8de545de995c339be67711e474c77d643555a9bb74a9334252bd55/watchfiles-1.1.1-cp314-cp314-win32.whl", hash = "sha256:3fa0b59c92278b5a7800d3ee7733da9d096d4aabcfabb9a928918bd276ef9b9b", size = 272078, upload-time = "2025-10-14T15:05:37.63Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c9/a74487f72d0451524be827e8edec251da0cc1fcf111646a511ae752e1a3d/watchfiles-1.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:c2047d0b6cea13b3316bdbafbfa0c4228ae593d995030fda39089d36e64fc03a", size = 287664, upload-time = "2025-10-14T15:05:38.95Z" }, + { url = "https://files.pythonhosted.org/packages/df/b8/8ac000702cdd496cdce998c6f4ee0ca1f15977bba51bdf07d872ebdfc34c/watchfiles-1.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:842178b126593addc05acf6fce960d28bc5fae7afbaa2c6c1b3a7b9460e5be02", size = 277154, upload-time = "2025-10-14T15:05:39.954Z" }, + { url = "https://files.pythonhosted.org/packages/47/a8/e3af2184707c29f0f14b1963c0aace6529f9d1b8582d5b99f31bbf42f59e/watchfiles-1.1.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:88863fbbc1a7312972f1c511f202eb30866370ebb8493aef2812b9ff28156a21", size = 403820, upload-time = "2025-10-14T15:05:40.932Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/e47e307c2f4bd75f9f9e8afbe3876679b18e1bcec449beca132a1c5ffb2d/watchfiles-1.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:55c7475190662e202c08c6c0f4d9e345a29367438cf8e8037f3155e10a88d5a5", size = 390510, upload-time = "2025-10-14T15:05:41.945Z" }, + { url = "https://files.pythonhosted.org/packages/d5/a0/ad235642118090f66e7b2f18fd5c42082418404a79205cdfca50b6309c13/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f53fa183d53a1d7a8852277c92b967ae99c2d4dcee2bfacff8868e6e30b15f7", size = 448408, upload-time = "2025-10-14T15:05:43.385Z" }, + { url = "https://files.pythonhosted.org/packages/df/85/97fa10fd5ff3332ae17e7e40e20784e419e28521549780869f1413742e9d/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6aae418a8b323732fa89721d86f39ec8f092fc2af67f4217a2b07fd3e93c6101", size = 458968, upload-time = "2025-10-14T15:05:44.404Z" }, + { url = "https://files.pythonhosted.org/packages/47/c2/9059c2e8966ea5ce678166617a7f75ecba6164375f3b288e50a40dc6d489/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f096076119da54a6080e8920cbdaac3dbee667eb91dcc5e5b78840b87415bd44", size = 488096, upload-time = "2025-10-14T15:05:45.398Z" }, + { url = "https://files.pythonhosted.org/packages/94/44/d90a9ec8ac309bc26db808a13e7bfc0e4e78b6fc051078a554e132e80160/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00485f441d183717038ed2e887a7c868154f216877653121068107b227a2f64c", size = 596040, upload-time = "2025-10-14T15:05:46.502Z" }, + { url = "https://files.pythonhosted.org/packages/95/68/4e3479b20ca305cfc561db3ed207a8a1c745ee32bf24f2026a129d0ddb6e/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a55f3e9e493158d7bfdb60a1165035f1cf7d320914e7b7ea83fe22c6023b58fc", size = 473847, upload-time = "2025-10-14T15:05:47.484Z" }, + { url = "https://files.pythonhosted.org/packages/4f/55/2af26693fd15165c4ff7857e38330e1b61ab8c37d15dc79118cdba115b7a/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c91ed27800188c2ae96d16e3149f199d62f86c7af5f5f4d2c61a3ed8cd3666c", size = 455072, upload-time = "2025-10-14T15:05:48.928Z" }, + { url = "https://files.pythonhosted.org/packages/66/1d/d0d200b10c9311ec25d2273f8aad8c3ef7cc7ea11808022501811208a750/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:311ff15a0bae3714ffb603e6ba6dbfba4065ab60865d15a6ec544133bdb21099", size = 629104, upload-time = "2025-10-14T15:05:49.908Z" }, + { url = "https://files.pythonhosted.org/packages/e3/bd/fa9bb053192491b3867ba07d2343d9f2252e00811567d30ae8d0f78136fe/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01", size = 622112, upload-time = "2025-10-14T15:05:50.941Z" }, + { url = "https://files.pythonhosted.org/packages/ba/4c/a888c91e2e326872fa4705095d64acd8aa2fb9c1f7b9bd0588f33850516c/watchfiles-1.1.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:17ef139237dfced9da49fb7f2232c86ca9421f666d78c264c7ffca6601d154c3", size = 409611, upload-time = "2025-10-14T15:06:05.809Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c7/5420d1943c8e3ce1a21c0a9330bcf7edafb6aa65d26b21dbb3267c9e8112/watchfiles-1.1.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:672b8adf25b1a0d35c96b5888b7b18699d27d4194bac8beeae75be4b7a3fc9b2", size = 396889, upload-time = "2025-10-14T15:06:07.035Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e5/0072cef3804ce8d3aaddbfe7788aadff6b3d3f98a286fdbee9fd74ca59a7/watchfiles-1.1.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77a13aea58bc2b90173bc69f2a90de8e282648939a00a602e1dc4ee23e26b66d", size = 451616, upload-time = "2025-10-14T15:06:08.072Z" }, + { url = "https://files.pythonhosted.org/packages/83/4e/b87b71cbdfad81ad7e83358b3e447fedd281b880a03d64a760fe0a11fc2e/watchfiles-1.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b495de0bb386df6a12b18335a0285dda90260f51bdb505503c02bcd1ce27a8b", size = 458413, upload-time = "2025-10-14T15:06:09.209Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8e/e500f8b0b77be4ff753ac94dc06b33d8f0d839377fee1b78e8c8d8f031bf/watchfiles-1.1.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:db476ab59b6765134de1d4fe96a1a9c96ddf091683599be0f26147ea1b2e4b88", size = 408250, upload-time = "2025-10-14T15:06:10.264Z" }, + { url = "https://files.pythonhosted.org/packages/bd/95/615e72cd27b85b61eec764a5ca51bd94d40b5adea5ff47567d9ebc4d275a/watchfiles-1.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:89eef07eee5e9d1fda06e38822ad167a044153457e6fd997f8a858ab7564a336", size = 396117, upload-time = "2025-10-14T15:06:11.28Z" }, + { url = "https://files.pythonhosted.org/packages/c9/81/e7fe958ce8a7fb5c73cc9fb07f5aeaf755e6aa72498c57d760af760c91f8/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce19e06cbda693e9e7686358af9cd6f5d61312ab8b00488bc36f5aabbaf77e24", size = 450493, upload-time = "2025-10-14T15:06:12.321Z" }, + { url = "https://files.pythonhosted.org/packages/6e/d4/ed38dd3b1767193de971e694aa544356e63353c33a85d948166b5ff58b9e/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e6f39af2eab0118338902798b5aa6664f46ff66bc0280de76fca67a7f262a49", size = 457546, upload-time = "2025-10-14T15:06:13.372Z" }, +] + +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/da/6462a9f510c0c49837bbc9345aca92d767a56c1fb2939e1579df1e1cdcf7/websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b", size = 175423, upload-time = "2025-03-05T20:01:35.363Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9f/9d11c1a4eb046a9e106483b9ff69bce7ac880443f00e5ce64261b47b07e7/websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205", size = 173080, upload-time = "2025-03-05T20:01:37.304Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4f/b462242432d93ea45f297b6179c7333dd0402b855a912a04e7fc61c0d71f/websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a", size = 173329, upload-time = "2025-03-05T20:01:39.668Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0c/6afa1f4644d7ed50284ac59cc70ef8abd44ccf7d45850d989ea7310538d0/websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e", size = 182312, upload-time = "2025-03-05T20:01:41.815Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d4/ffc8bd1350b229ca7a4db2a3e1c482cf87cea1baccd0ef3e72bc720caeec/websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf", size = 181319, upload-time = "2025-03-05T20:01:43.967Z" }, + { url = "https://files.pythonhosted.org/packages/97/3a/5323a6bb94917af13bbb34009fac01e55c51dfde354f63692bf2533ffbc2/websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb", size = 181631, upload-time = "2025-03-05T20:01:46.104Z" }, + { url = "https://files.pythonhosted.org/packages/a6/cc/1aeb0f7cee59ef065724041bb7ed667b6ab1eeffe5141696cccec2687b66/websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d", size = 182016, upload-time = "2025-03-05T20:01:47.603Z" }, + { url = "https://files.pythonhosted.org/packages/79/f9/c86f8f7af208e4161a7f7e02774e9d0a81c632ae76db2ff22549e1718a51/websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9", size = 181426, upload-time = "2025-03-05T20:01:48.949Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b9/828b0bc6753db905b91df6ae477c0b14a141090df64fb17f8a9d7e3516cf/websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c", size = 181360, upload-time = "2025-03-05T20:01:50.938Z" }, + { url = "https://files.pythonhosted.org/packages/89/fb/250f5533ec468ba6327055b7d98b9df056fb1ce623b8b6aaafb30b55d02e/websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256", size = 176388, upload-time = "2025-03-05T20:01:52.213Z" }, + { url = "https://files.pythonhosted.org/packages/1c/46/aca7082012768bb98e5608f01658ff3ac8437e563eca41cf068bd5849a5e/websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41", size = 176830, upload-time = "2025-03-05T20:01:53.922Z" }, + { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423, upload-time = "2025-03-05T20:01:56.276Z" }, + { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082, upload-time = "2025-03-05T20:01:57.563Z" }, + { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330, upload-time = "2025-03-05T20:01:59.063Z" }, + { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878, upload-time = "2025-03-05T20:02:00.305Z" }, + { url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883, upload-time = "2025-03-05T20:02:03.148Z" }, + { url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252, upload-time = "2025-03-05T20:02:05.29Z" }, + { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521, upload-time = "2025-03-05T20:02:07.458Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958, upload-time = "2025-03-05T20:02:09.842Z" }, + { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918, upload-time = "2025-03-05T20:02:11.968Z" }, + { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388, upload-time = "2025-03-05T20:02:13.32Z" }, + { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828, upload-time = "2025-03-05T20:02:14.585Z" }, + { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, + { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" }, + { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" }, + { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" }, + { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" }, + { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" }, + { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" }, + { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" }, + { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" }, + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/d40f779fa16f74d3468357197af8d6ad07e7c5a27ea1ca74ceb38986f77a/websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3", size = 173109, upload-time = "2025-03-05T20:03:17.769Z" }, + { url = "https://files.pythonhosted.org/packages/bc/cd/5b887b8585a593073fd92f7c23ecd3985cd2c3175025a91b0d69b0551372/websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1", size = 173343, upload-time = "2025-03-05T20:03:19.094Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ae/d34f7556890341e900a95acf4886833646306269f899d58ad62f588bf410/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475", size = 174599, upload-time = "2025-03-05T20:03:21.1Z" }, + { url = "https://files.pythonhosted.org/packages/71/e6/5fd43993a87db364ec60fc1d608273a1a465c0caba69176dd160e197ce42/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9", size = 174207, upload-time = "2025-03-05T20:03:23.221Z" }, + { url = "https://files.pythonhosted.org/packages/2b/fb/c492d6daa5ec067c2988ac80c61359ace5c4c674c532985ac5a123436cec/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04", size = 174155, upload-time = "2025-03-05T20:03:25.321Z" }, + { url = "https://files.pythonhosted.org/packages/68/a1/dcb68430b1d00b698ae7a7e0194433bce4f07ded185f0ee5fb21e2a2e91e/websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122", size = 176884, upload-time = "2025-03-05T20:03:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, +] + +[[package]] +name = "werkzeug" +version = "3.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925, upload-time = "2024-11-08T15:52:18.093Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498, upload-time = "2024-11-08T15:52:16.132Z" }, +] diff --git a/src/flask.zip b/src/flask.zip new file mode 100644 index 0000000000000000000000000000000000000000..6ab639e071a29e99e063c64a5a9bde077a004ee0 GIT binary patch literal 820084 zcma%hW0Wk+vSr)0ZFirxPTRI^+qP}nwr%6IZQGtR^JebMci*Vi%3Ad&5R>b zNnj8tfdBewm$TRWZ$JL~7c>AQfQgm9qb04izPSy(vJxl&;Ey4;x!nKJ0`s31bVkN5 zhPF0Nf31xj{w~%? zEWQMI!@3OUj1>%wWJs_0nx$i?28j)#;@frMxol#TGo1DE0xeM=sV<0cc`)@+sx)-kWQ5}KiY?4IV(qsv^8qE6 zjs49pB>ABQQoOn-n?*j36ZL-SO@vL5M&0To~hzvHCnu-Ydq&PcEw&A2?C#AF&&jbUqhd zWJ7;Irbd}BdE`1j#qp7=)2SM%s2atcE|nS4fBz3h{0ESQ(&<(Xe}OFiJ81uJK-${S z8af#3I~mg&+FD!d+ZfR~n*9q)_Hz$b5?D^Dh=uY2_H|#ZJzt$U*WE7V6TM*%~ z+OC=8IQu&={_PaTM&?eo z4u2VCVs84c4Qt2rLk%#%gxq*XWEXoNz<0iqFbS+JG%u7a7+EQLxo+%nL;Uvl!iV&g z5znwBb{8nN36*ijiUUzw_7lDxe0&Z3R$nW=j8gCOU;>0V*@b+ePw)mAA3Xpsm)R63g_QxCy~dDJeDXnnXjaD zh~C@!Qs%9M{~ss&&(cEvx09KgJDE8f{AY>$XG^00y`{LKqO!2AlCZR#6u*-2f1c<+ zDAMDvRbus5XQn^^0QCP~6B#(0(mEL1**ZAUSsVQeb8C4?2NcG?fS+_hoISco#v(L8=|9_C^KRjqEgQRouR~kQmy-4%_ z<-z}u3!S^Q)xX@A98+&I$N(es;x+umSG}bf4{)XN2n-})V$~mU8leRA<84T8t%!o# z5YN56^$>pF7F&_*Je%&dUo-5Va8D-23e_UZ7`txcM{dOF5a|lnERX~CE# zz0SEJid|tFfU_k;SzpdYvM_%(eOksPoQ$Ndef*HYr&H0q3fX`GC944QUMbF~oNFNF z7-+4}1g1}Q*Cewt*4_^6-RVXnp&~XS*|c(AABk0Q?%?g$^DP13h(-2+fzj z=;i%IkNN)#J(It2(%HfIuLkU$jUE3Xg5WrQg}*2YzwidHCgSBS`fHnZ1S3<#B)FZc zjU*tq@ib6ct+^Sk{akp%4(=k^IE7H>FAK<>7ABMp##iR-b<;&^0}{>yJ{&Agfz}`% zU4#9yOC{&pp%8rp=>P+q-4GGqgXqX`A-W?qg3a2P7U3e(qD&gW9hQla5b}@BWlc@#D3hjK@+To(LWl z%OnKL+(3mFb9EXK7_cuz-aI^cQ=MNoQ+93H^4DctyT)?dS#q4+rS+k z4n7IdYyf3}TFq^pZe2Qtw2h5sH(ndg^NS8xK(7X9%#Kg9Lhopv+jHlcWffJxWoizjP*E)GkoEcC4FG zGpoRPUGo(;Isy&U7-6k(TfE&5PWl1na__UxR!%)(B)3E1@V4t_XBzhUP50o4duIF?0vL)7awsUk^Fjqa;91BUKACy4$ zRN$iI84iUwo8+~a5x1(FU6{^d>pV_WLaTw6om_;Ykh*TPKcfHBH-Re{3jnW7B)tN* z{z$gjtE!V3$t%0d;U`kggw8)4ym~qE{2?J*S|`_eSGBE(mw}+>1QK%#Fatae1FR@%O((_n)&D z?*E=5uC@-ACRVntj{m2`^(pU)@A{W!6@L>K+5c+}D_cWLI(Pm5CE$4{^v7f|APC)f z35$EGQ9?Dw83hbct|U85!DDo)X9cs`Z%Dp=G^M8-5eXxrcXWJqtoNDZCv%Ama=bR^!G>MPEoEjigWIoi5Do-)g-V>h98Ix*?CX}aZ1NWCDLCpxETG;%5>yXFwHcv ztGPODJiC0IcOGPSnsL#N1nFlu4-tj41PuDhW$-Tz0{Iva;_5K`u zhW=-$z;E?mM*Qo)mA|3l|B$-=ClBoY#@zo}otiuS)057M8#bE+2;Q@*a?J_~lx}yR z*qRkhvR~m96<`(Eia9`{$>r+Ej2I#F`bA6)Vg<`}+po7GRkpz8R@!h{>~`PIW6K2~(Gt z2hqF1%m4+TI&7$_NJ3Uxnhym_!Scq`5-B7q>h1BbxkzD*Z{3qKL!Hx5J^DGfP1-ZM zxjH&x@HIbJ#+{uTzRo+_i5r)&II;6E@N19;=fB*&yI&(3!@1n0$n*cmoI^TyanmTK zu`RW%pq{^WN3f~!a|RjvaFfE0X<{N(1qw-wbx#26SppeWaDx#G`D)zSQcYQ zHWFoD@$TDA*j^a3*Bj^AGEWFBkN1X?_9!lVk4TXMz-e z`IP|<06_WwiW7Fu23F>dX8$DEmeOB>u_AcCs3O!vHUX9;4=tgJ)me8$2Ljp0v3pYn zfYJ}58mgnXsSiTj-{jMEYGbcqoEqv1nX#uj&P>%6w#pT{<}^>Rns!vqBAjaNCpvUyM*+Y2RdLeQz?O8hz9 zpF}spePq4&t}O5S*f5KUh}oQ9sWpsY)XRkBrt>BXm&{PH0|4XN+NWwsT+^--kAuLM>5Z9Pi zPX>4pi{oGq!(i^m?nT?IksG=0dz+<0lV2XSfh4Mt1ad)c%rQ(6DUy5KLH^e77j6D7 za2vN6N^XjoRG4d^!d_AZEc^S%_USA_#@VB_Cjd&`GK5><{99;c?2T?UoA0Mf_jc8e zc6W!D{oSMr$DecB0my+iw%yII$lUgiC!B7ta5|md_MXqz50kIwo6xsUn~V|UCVc6Y zh`6*7NL>}iBj)i0Zk>jN$t)$MTO#yHg=22y$uGOlNw2hZ(KVThg=lQjBZ8p!v$<(CO&zymI2u7`x>10zQv13O<6Zd$0%-e@?xG9DJVttqjC~ zlMinF8{5zS>W=*Xr8}p;g|y>8Rd=PVZJWt};(b$F*Cbx0ECmocTPkJoqEenwa;0BE zWtb@d%{oX%EAg;ndO$|3j#W(J)tE;2i4(n_)s-(W2)HRruBz1lZrs@S&G~hmM zvlIA2;9x$NL};r2Huikr0gF}DalXa^c6;Bz(-KGp3Z6NQvGt@Sp#6uLUkv&B(S24K z-qNZs+d4ogn{^ZZ*$@DwtwP`58UN9ecCB(vz&a41jJ@1*;u{0NPtUsDsE@y<*N&Au z>wL~~@>AZPD(4X9l6cy0&f2?SW(6wdSl7^jyR~1aE1m;Zv{bCHS4_6MpR&|<;tcm@ zc-rolRD^k`iy!eH7>c_rKtqO;gXl81rAOG;8BTRbpQ1wIk+NT_N}m*Cq?+{_O|S>& zumraFVGTE;+V5NS_?msYp(>SdH&9(;+1#ZoW8Y zrwJoI03FVc!4qR4PyZ5AlY(g%Te;{Kp8*<3?oR49-`N2Vi9gQp_c6`^7|@6l6`8J9 z2hJqJuG~fqK5g1dT;5#YIHleWJx)jQ@2BhaKmK^J9Pj=;xBXlgkl82(wlQM6`N*aoEge{ezKTji4$> z(7~&JCK8lK+#uz(HXUj`_mHLhw6Nol%D0}&h`I;<&*d3OyE+;E*2qqOb@=aHqW@V= z{jIA0Yy0e<@{5k|w;3e(+e7n&D#{jzBm}u<1(OQM2nvg<*;tHyUJ%hNwXdVu=>3r; z+rw)8jz*AB5cgy{?y~thV-DiJE@`mm?N33La>AB1f61wVG^d;;M8~9TkdzHl&|oOV zT^4DX2t3j9so>4_0n2TmML49C^S!c&31?A!n<_2R%2; zApA*n?fsOnETWNe-Xgf+%hY%q5jRoaFyA9-%}cxLYnpAUT1fU8*6=DZoAov15zA5V z*m>r&l=DAV8!QI61{I@i0|Fz+VzHj)nuckez=NZtR;gFDauM<@`Bk!a-2O_r>V;uv z_Uvzi$u!e;cs2L9Coyd*f|WQZWGZGV6&mhv!4Pbmhi^F6R;_n8H#$78#!DxqHqc!5 z?|!!~&Z{o_>+}1+1LxoT{E!?Dq7LXz}kO z0U%i(HIRL7^#bkDx7dc!&}mSYc+^a&D4(@h zyOXr1rl%1QA{EII0kQ%}m<(gz*ye7^;@R16sgrrwvlp zq!bA!)Tl5}zC(T!Xe+66Zjn?vf2B#sCUouTXB15=x8bm_Pa3L_EOzM>PaiVUA2WCj z!{3m-#NK{!z%2AduDm%kkD0owkWU+?9Nwl?h@m$@HAP7$%jD>RV4?LU$YkK4$(n#b zC$2nk4mM-z+^%~`M8bAm(MLA<1f*)2)FCcnjrh=^&)&MY=O@-v?lX}{zbDNTlU-Cy zM1m z>F6$0_|3=YJ={{zpf#zv*N3!HC#E)dNR(Viee<;(U;n2&wh}59B06|K+BAo zrO@v-j~)>JjDo44`~<~1`{vM3ggOCQvQ3j2C$kzWFA9>|wcs^?%);QVI!IGPLC03g zbz)}0>Y3_xzh`cZ`nb;Z#I`zMC-U3cEu3Y$zN6O~Ejt5;T5}ATMv=87E;1b$a!Edb zUhBe<(^az#leo_`rk=i;j&=e0jv7vXaN*5P3>SD|Dz7ou+6CV6qBfbfA&eWv4_4xg z+xP#{W~;+_Jhzk3(@>AD9b7?B0Rz8-=lQK{UN6>5I&G3nYL~3mR<--NOM}OHTXpyj z#)c+JQ_{wQ2^q!kvG{!c)Z6Gmp8XxCdXxHzjr-ecRT1n3wTC_5Q9)&~YCkdtU&(mz z1YC!VdLa}P<~gg>A@sUtdq-z0_S@rWcAh>J|M$1o+jp%i^Z1)#_BT6kj_SkASjow>Qf!LrU$mBcz@YaMT%+t;T`h7gl8hU^R-_XwKyHiQsC3L3C_T5us5e z_%xuryNDx!Kf+<&iE1=}s z2UL53dcY4=1$(!aKi1hQ(Pz<#)!0qN^PiaP*N-1AV9%8;j@Z5glXuQ6!|rqsM7lMp z6l%LtCc{RFVXT$*v~qNP4YN!s76szMLf<@_k>~-E=s=R&>pIQw8MRI zoWMoLXCu4Z0)QcYuE9|~IoWxEgb5Pg6q0A1D&+xY?m21*ako&v(4|0z`IYGsoVJoA zU+su;=Y=Y&boztDQRYua(Mvu0%#b@syKo`=I;TmnprUFsMfa zh0SF4EvjV2^X>Q?J^h+K`KX+IDU4Ok_3i$F1uw2XB}-j2FOdw;{X9B;UOZh~D$U&R zjk&J*`9Q5r|M}aCyZX}h1c^IHd_pS8UhgS}FOzW6P89B$a;LRZaRyVRwC(c|KS}SM z14hJSuy9&^Q|{?Ap16ZtOEQt;J*rcdRy?wUZ#=yE*s1B=?B+6+T+ycA!d$@)p1ak`>RXLivARBkgZew&dPF$w13o> zUzkAEf?EWVJtGCycj#VjG>?9tQlfNG%T`2UR(2WGOGPwrZ14Rn)k)s8s#^}%c+rey zSf)EMIZ7#rZb4_5KoYnFD-2O$*Y@}@5j^i_xa#y zYi8%E1!rNGvDahA;9x{4_z#ESup|5|1UT+z=BV)ZTs8h8Z^qMEh!gP?tv48Q%p(oX zzV0qjfWs8JV%!G)#6IlCU7o-mL18P0N9ZR3Bf9|;ZwG$t>vUzAxI3&sEg;9Nu2`&Qp>BgSu&2OE^oNCQUj;_;I-P62k zD0&c=iMMTZB+LvI#|6aDsl&Zn<}US!z$6l6X!mJS9DF%U8S=V#$Y%FxUBTtD-qeP_+UmW%&dk|1SF;bLW+*VuoZSTDE`;0B=k?qdXDZ867B9f##I@z z|Lhv!WA!+JG-#$I8v{XM)U_yVR@6j2KC@gsUbvoaMl2n`%@Fi)*5O=0^7!k43^ANP z9mcl;{0OaZxI^oVadi-wKsSie)b$xcS=xp2g1WiDmpAf#1pZnh-n4rhF*!9SkT{5W z?a+wYOTpP)gn8;^5{*ug9_c&O^q(X{%X(2fzqSBFwkZ zu>7Q5$)o_fsf(i$x`4fa)z?zu2_gXfYIBKA?@D)5XmLQLFTTFh^(lxZQnYRkhztND z@_Gc&%|6=sbB_$}$?>{U_3(Q2axydS^;G*@iHA}z_-8CIQPpKgQbVXSxz1O)T}llj z!a%sxtvh1K^5(PcYxO2P_;&RR?=&6nK4*P~7XA-y<{QAiPlf1vAuSWV#SO(9yi!nL zFT%nQA9fppqO-uAZka5c;62V1IV{vjnO-XPzd7EWzRu3Sb9HNsL~OR-qv>}Y``K)2`x$$?-=8i0vTHkCwN^OR{E(aQT~ zIniK+hh0dmZ4d!NuN+l85qxfdd&ZO<7YafP3i7Z{3TMykGE}El>Vb>;ikx_9#{o8& z7ViY|hdvO)Vg&IaZo~MnTaHm@4LVoDc(QmdnJ@g=))4^ahw$g%ceuy^VWSrZvl*v? zevO292s-Ra>%L?Iv;UKfXjh$zqe!5H1CsY?B|f-|1;T=VOxWVeuK&=m#>wfjr5Wo& zj|`@sbh;2jM#BtTV@cDkXPV0zZL&8?YQZq^uUhA07@4>U?Sqj zBb*@{^!BmSUBdpBf-5TN4e&vl?_2o}&lQ}Ty5f?CB6wawac!N#bYP5C(dwvGbxiqg z;-Z^Og}nYSqPGZaolhA#0}KugSAL0pEPcW45tj5)LMnfKnnO3=5P-e}yd3vjfk_?e zeu3Zl774s^&NN!)Dl8;*=!Socu(ID1tGmR>p>vpZjn0V$Fjpj1^+wXtzH&BiMBour4a_;u zpIPOMgPvle`hnRRIknaMRr#}*$7v0OF*h!S~R(| zA%PCQQ^~`PCv*_N`JL&+5|ls+y{&9u|7omy1f0Md#jkZ zMe;`kP+0qT2Tu0yV=BE^R|8PsDughZ2Gdo3NOQfi9t1xYt+hrUD<}94@*P)8u-Y!d z=wp||wYz?%-k{C6>mOoKiky$TTo;UQWJg<`1J9b9yv0;gtpNMciiJ^bd{#i!(y!~~ zS&R|+LODHmdLf)usd+;oEitcPJI{8#Um)GxkQ!M5)`VJj!3=c2_lF6Bm=FRoO?FYS zxQlh-fa?Y@T?VtVvzy^(`E%X;+W2lhmt=01K!L{HkM@`+)DeW#8V@4OFrXNck=a*> zJOMcr6Z->OYd9AigA*3(EWi)xlKTJ^K$@blAWg|f?<_7%_LLR=%;CT;XtycU@=r>w z6v69KpNPl%-@why$>FGd0gD3tBgsSup9<OVA7OMtCv>e%FIPmm3+yfJ{jufBKlNpmEWywV6_`y84Y(4@!(#TKB zhu+f|n8-$aS_Ny3=d*Dc`7Bqg-tD#>{;z?{B?lzZj{-7iu1*f z>huLyet^F-zguk%1xqfWl`>sOfIMf+>GHWCZp4mZJuYayW%O5Fe60I{}d@1ik zS4qgwf%yE+IA42KN=9fiY}qR`i;KMCmy3tSkPD&!QxAraf@};&yGEj$wMxM>OnwRd z9b51sg)cFeaG#hZp2T$pZyX>;A;b2hd6pg4r#PK_#;PX}c-) zXfEQ<%PV}562mmjYvOVQA@du#w-6MTQ}agI(@eAGIu}IKIEcsZ!qtD-8WOuZtM%;_ z;#-_S=^*s8!zyB>oWjC`J+?IndA$dxLC501{4fmQj>pw+EaFw^!LBbIvS|n?roaiX2cwzH#9g)EOjt zwGa0Lg;rf8wX?IHoLgUlurvrxt~9`l%DD1jL4t(Kfv?Xt;?~XS82}o~v!;8G)+Eu@ zc?w||CD{P;-@pO@3GKyhj9V4ezS%1?Rzi0f{Q=X_V|LIKG}a*Ozynd3MB+Z`GCuA6FI&mSps%5$~{6Ec4GF`#60E4AHENq z-PY~wIHU4G>(e5%`2@EFu+rWPQJ{@PI23lN=p%s$98&bU)Xqo8!AeZo?N@ zR14eyC@ieJ=wnK3HztX?+@8P(#r<)eaQC~>SRag!BT2I*iowk0o4#)osRqUeG%I*#(8Kwj{@Ps$XE&D)8#A^v=?tY z%9?JvTT!3(E482hn;x|*v-4h(ck=XCWxWABw$|$bkvNfs<3_&Cmv>MU`Mz=J8Dwgc z0ApmuavpD&79_l1qUO=sP#O32n^7z_%jB)r7A}HAnEu^PaK1QtG!T);aoqeak!NXX zsiAFH<|d~>Zele6+{joUCrI>4*Or6wejTM7AdV;hS%f==QGQ+*szzag*4^VRO1AQt z#C-p{m!Wgai@FA3%pz2@#1xFW3ljBsMEL^YVYPr6UJ*&~@oHCn7K^%o63Ppe4#vr- zK4r|g?s5-aw0w<sfP*S~bHNMCov*`!J?xGJn5;Rx} z0t2@^bLj^HP1)RGPQ2_$NV?RO%89A`OG<&(B`i*ZdT7PJ9Rk_khlecnphJ#=ouDU1 z9?iHZARCt?P?A(masDjI&T=|~fj*~BXlMA?U!{uj%pL@nH$g+F&qOTzqw!$m#sn!q z>EvFB{A2Mh^hj<-aG7+viChCV-0xT~*SeexqZsA{4Y_?jnsoh^hX;W;*waUqkZUE> zH``js6i3dyK`2v_3x6aTdeJfcwT6;?SH((*J|1Wpn?Q-OhA`}Z?Anax&a&0f=AG-u zpc&oW?-qk^he1;M#SxRY)IO2f9WbVVaVel8pYpD}ZMj70g8EUWM?bD$*n9&5u1u+> z6I?->hxs9jbc%u5?X>EB{xm4kaimn!r= z{=VEps&$Q)$!l<)CF}g0Hs+;TSx!0+azqOoy;|&7MTX|;z-)ZHcZ<#1o7&+qfw zu#?)@G0-CQ^4;(-SKMa@)oKb2mUD`f6-Fs?Gi?JwwCcY!Z5U<`Gv0K;^@iX>S$*rq zDQ`UF=-STZ+`R}e3vQHrp-d=tOAqde3*{Tt>QiIcZC{N{jtLVc{;6}!$~90C6GVDX1Q>VSF^=xM;=Oi^r{9^70PAYQSkZ4z zQSvlnFHb1lD4gh`xwG~AlBc+gh6j%!|6)2Koe{V=jrrYJO9ZHNOXFb2N1;P7h&Acl zG}sRu7D0NzSV2*XqFQwKV6>k9W#Xzdld#CA@il$}rs<%EX^+BS7bq4)|2?nh6||Gc zIcctT1^|Xn!q@NRXgl6jFr}L`F$oP)n5v&m#EKa}#{WK-lne99K?_CW9hYzmORAVt z)FGYi$5n*iea|iDaHM6RZ2l_(xIpuFvsuMYJamlBm!Dwy_XarGNCi8T9WGSB_3o2> zp0u_|2wnv);{q!cGk0%Y1a_iBGp4w87{bf1L44;}jvFnpJkkmwkmnHo;rvHpMFUkJ ziOiW6j||NYJL!P<5K<|Ddh9gpF|n8vH|tW5Co-kXIK45?4xOZ^s#orO`Jw$^IJH9; zlNacfmwUGZdvqk=u4LZ0M7d3uT=Y0jAgqW>v=b-7>YxuxR_Ci}rU z_shTdq|(Yy-edSV|Gq`)6z~q(`1Yp}$ZHgIU>IDp#VSYf;^jUw=bOi~M2u3YOCZHe zp9%>uCiJ0EIO)MXEN9~9<~G3?M%2&6mwrX+O)#rl%=MI;qCA zkNX5Jq}C<9S@gdFT3^(-+B+oSz#snP2nXX0fQS}eYHrJYr!eK-0f#@?KC}-J^#tH! z8WU6nEnmNavPp}Bi82@9oyMKfM@BvdhFCg6OrKs{iL$QV0^EcBl7*y^9#J?Pdr0uB z({hN?UW@ieKQ`{a6An`EaZP~~{rMG_H~g#pHiB28E;a7gv+kPqCTRS9HDG9o1q!l} z34kD|c|Qcg??`eHGm1NTOK%YkNv+Xse_izkI2JCAfm!B=zR57Kv{o0d<%YK2L}+e; zmM#0Zci&~K+vKPwkeR@9;~4nkU{k>7PO$MF28P3szAOW}cK$vgPYjteY`uJ_x?^++ z5JR)5SgOx>v>!kfO>~Hz@67X(-?pVNu^$IFZQ9iz*8YU9lwg3r2$jg2g-las%92Ui zbF{=oyz%BUabnX?>AUn>xkk^ z{&`r;xlpIqQLgb7p6qzw6|(|$B9Rn+BbHgkobKv)-?eR9}Bv6s<@Q9_veKbw%MA zfjPdC_&9&6zSIq-q^O($Y7g6&DLs{;M$jCY4>gKLpwh9Yd@MF&KGDrnS8_Pxb?qvc zY*yr)IRYT?P^N8{0kY;*m5>6?n6jZ*yg0>f!!v)1V-p*504Xs8y2e3Dn=#?&6cxMh zSRBd;1%BtwSmjFbuTh5_VQmBEj(N7OJ)tr{u!w}f#N6*bFnkTZT;uySPhzc06`pth zcpsCHBFEV9JGlR~=JiJ#iH}Y%myR#JSnD-T)ni&DV0b5?)k}Q4IY%y(fGQN718Bl7 zCDT_ocq7f zkWl-@P3x_OG{E3mpaKvuWQ#YUTJ-vcUI$GV%Q6?&j%V1J1BfNxB&&f%b&*tqW{#>; z@GFUV@iZ3C5adq@O~42m3TNz1(ff7o#&|%&RwroMz^^@;1J}2tW=abGg0qoz5lvF1 z3ay-yVG!*wZ0y-50hV^g)k{o6TDBA@C+t1ed<>-MZ?u-iLEAeS6J*9Qh@r@BZaS61 zgC;+d=GV5N~`QId5u6IBMk&I>+ z2Z8oD3!`7(d+alQPBV3$14ebZ{iF?ZNy$1xoEAnYUi^~<<kwl7)~%Gv!r@ zt?I7Z(%XXx%9*ZE?hJB+N2YEDrX!z+5*?suM^#=iEIQ8L*X|ADSacTs0(>C^4E$L- zOh>yFUJw*rMVSja7z}*?Aisxs^0CFHI&uligv9VsMQ12Tk~g6}JMCC<*sHt_+naM_ zKX855{Wrtk_&&;&qrhB z=v)rsl1?lMV3V?pTPc2~3^wv>E96%;mBbx7%qM4%Ga~Zw^l88Mf^PcTM67BI#Wtdl=<3r_vSmp}R0d)= zGaQf2s$fhxx@^{S;$fAqjUvAo9X|9UmPHOwacmQ~peJqYp7;3FuX3&R(r3_ye)^_X+lj3U7 zPW3({A7aR1y>@2RZc*h=_>_$yROHlrDmVrkFws-Dm7``N5leAIHmwta05CP4m>glc zAKYnzhOlh4m+%1?Z0A(C8m;K|6c2?`^d}5XXl+B{=k3rYr;=eX!v=Y0I7VHdC*fsatO}rwPhPwmzA<9ME=$o##rU4!d%Q5YG4GIiXbh zwoA;LXctX}nyw?Wp}mWO4nguV(M`z)r}P6J2zKXOc;u-!xa0Ds<;i}2|M9`V-i|e zAn~AWoFW(8{wbX`?w3u0-Cd>Pc&+oxe#z3Df~Gi&-Caz<)zC%buwit5np>`#zbF6o zL0a2Rw*!okm=X?W;VmBu#lmpwI5Dm>fi#yMN+hQF#m|ln6iM3e)Dm;#7L^SxC)UDl zNUIZRHQ{rvM(u-NN5gX4UqI&XUvbD=#1;Xt$^kF>fF)tQ z5Oj*?)md)GPc;v7lf8|Ka}i^qo5MX6YQHu!n~`k)Dw7=$|n#V=Iq_D=|5|801cd zxXa!J@yyZ*%A!>e9D!oj-4|G796&5-f=vV?*5Z%b3Ny?u>L$Dk1?MkVKgG8XC;3Zd z)p;-+)rM&F>zO5{EHxv({)6de&+q1}{jTLemM*0rTLdk3+eP(AefY0-+JK)Decq$iahmc4OgwxPU1 zGbyO!2Du8RqK#RIF&cCZYW;0n$^_y;pK^mbZG2&F);`Fa%hY4&)oqw>r0?32+gK-28nT)GS(L;s>{I#BVi)k;SM9 z&^Ry5rUt(DS4d~#$)7O;i@!I}I5Y?@6G;oFce#?0Qgy7Pnh)IRhDx5**Kxg%DRqCl zg9?fiVa>iB@91g~<|N9hxIAI@dtK@A$0tD_jj0|qT>C#;QOi6Q5`5Fal+#3(pn zf}Cz`M)u#$--@*FTndZg9}=86XCiimVPCKPBG>+;l@&tJx zoJUoHEFg3?lvy@SMN=)swGRVbFGC3wvGNog*&XUMM*O-Ps_Me0u2m@+f!{+4>Lc( z!H-;2xKD$^VsISaTc9uy!!tllv2OM>hUj)b7sJSe;zd9gwb#6|80sp(eA%v(tq{mU zV(s^Z)}(z?3OJ)GM$|j57{pX?Mtx8!O-vVnzhX$sku`EI9!0%+0=_pk2m7%6uGij;U8(yWRuE*} zxnP`wa9ohBjR{#w(oU&DEqSsGvWOQe>uHF{DjD&;-Gu77!T7{*wVii#kbx<2iES?} z_v}H8@_Y&!IA|5sf+ldmdl9B(;J(oX`{ms<6h3ew4`8wqq;Lt>skR!9l@pw#&FY$5q`)^*H*E|sFBq6s3!Zy z6P2@ia?WR-`Gx|1((=|p!(!Xq&q_Y@eZ z*+-4CdVj_t8{L3A!N2R;gk3VU)nr=+PQFU;Cuoa8b+Yy9T_z(%avthW=Nef)KT1oz z6z!4ND-P`=115~s%1wKi2lks7#)K<~Qswjv6soq`7%2`;!z$iz%S-Rcp<52J>ix8s zZQk1*unM#w^^Sn`vt<66yg0Nj5d9{fTMqtgy@HP(f z)B|cJNtqF;Z-=}z!QZNbQF0@>tANhkX~fN{ps(dZH_A0B@EX3q?X=}!KsqaVkhqd# zd|0pY&|2yEaP;mqyKSHeTf@TGlt-Pe1Bo4NL2OrZ`wzEB20a>*w)r) zUT&|5vSzVKU>K6BxJ*C^_0*s0dpTU$WiAcAb|S<^)Wt@L%U~_8XN!$U{u^PB#l{ml z1*9QdQ*y!+(bYCaVtevJv5d#-YHE@NQ-AqzwP`+{N-&b>C^$h{>Vc$9XIa2=UYn%Y+NtBoItd(ylm-=4Bigc(ymLTDy#Uw zLcMz0gQZ3x=!XQS<^ryv?@nd}0v1WFvmS}<`6;HxyiWJ$cAYDz0rRa^hzpvP6x)TR z3wj$O&lS7r;dw7jQfS|AEsl%#ET*X-Z8o#j^)*;%8|wHbD20Q|{$2{S;n|)&(DT5a{p@i@=Sofd_-CmZ`>@FYXlQDeq@6K~*U_Wq#)k9c zgaRY$o2w0H(spM~cyEQJ{XEI}I##iljwlXO{00Eu0Ul`49opIZXVUJ0n|+9_vnYHV zP}@>aAKV=VnWZ`GxOQsts|sqCM80iL!SGCa6(NEjpB5kn)VFq~V(4z)DmPx0fu@zM zy^Z+oSiPLXvh{^l}^wikHQ75 z>LBplNnHF=#N|eK9@C#U(kPR03uaMxAPrN+pv!laP7^zOAM6BWb#L)ob{njSW(;#{ znx$eg%T-@s?}uNj)SJ2Bboxq2npjx@5jr*AEeJ!N?M%YK8k8dKLyEx^jg4l96O+SBHYL!SZF!Gr zRZpc^j3sWyJEKJ$i+*X}Sg{~xfWhecE#5ip1-ej0vd&za`BlmejelHzAQAMNB%urp zsvHLRs#k;cYj^Ga2MWZW5TJY~PtREHmvwP;vzf zUn9XFI`*azZU6Uk>xw4^MB*B~u}iVhSDaaBG5(&a6lvGT#Z$^$NXEynDr5sfL+)$K zb*|E!5?sI+_9E&&8)<9e-Y)crh}Mk&O)6HaILlXuk*#T7AcBsQDW_VQu+0$pFI?2#5Krmg}D|=q)ZS4$H$_ zrxT@m@_dOYt*?c8J>h-&5ANTzp66|oB_6XR&VPY*0^Ns<>E3|usk?S8ZUNL+c3`eF zJcN`;_tW3L`E3wZ{P*r@k?j&~K_HnZx*`LD{irZQiEPVduT7`LrCQgzu!cfj%=zbw z1?a;w-oqm29PU9PD&pkA#L35eOR(DDlj`1`Kp1XYM-xlfpnC6emBeNzfK!&4I^8DKhC98S>vc*U8d zy(y>=7;p3%(t5VLja6D3OO~KXZpJg@etd>4JOn2lrdp;f@IS}L6JJkLCtQDEjj_KD z95q`IoAZJeAYo~-C>>TQ{jmRx5~vam+7Z)W$rM{KCO;~01xDmw?jl(5>_E((PO9p| z>!PL$A(hm5Q#}vTbsNqorMTr@b>Y?ri`OJNTKI0n;Vm5pxW2rk1wAzz(PYH@m49@m ziDPc+HuP2%nRF(EqGM);38dLe@7Oj`6ow*JLZx!fBN+!ZJr3&MPK{UOSJPwowI^6MrycstI@ zJ~wiQW2YZlFra)bk_1iPLM!7(s3(mjGtRC6~TeJ%rPhgViicrLBejkumA6%v+3iFKaDs|PZY@_MHK-Q?Tw82G$FF1+nONA+bnm#P z;k<5&y&S0sTb_3@d^=I!PWT0+=0N|GWGNwAo=b=<%;iF3N{20N?JE-C<0kYd#&eFh z*o!!*vDKLOg?a4Fem%b6ERPS9be>G&d{04P&CkhbG(^)4O(mI%kYH$zLL^){Ul&p6F9(g7 zY;z&qf(e`H0h5?vxQ#Ow3>vfu41Ku`)Lr!Zz4i0lPJ}K$y@*TMyBEe7-%*UMo%}CY zeb9@NbQ(?hOV_0DLwtjqr8=~#gujKMr08FH7u4svn>;CRr5Z0)@utOUk@OkeFv!J4 z!;q+J)j?&&Qky@lr|O#lbiX3%R7uejb}4Yiu@`dZfO)Ig*kvHTdU*+A_9}fleSE`2 zW=@}vk98Jewm)x!nUrWn13$s?y(MN~a{eUB-jgo|9{vUPUi;*m<~|$e+}zRe!TR!g z1MUp?d44*-_HlCd^mOzb;c6;O^JXWh6%2&e9A2%J{fiv1O19qlDjOt?S zbu+>7b7d9Mc>PN?S-`LYRIL{a#T0@1(k6TIU=?mNhv(%787K&b5urJ|6Yn#ney){H z9)t5EvSt*p7Y2biD-{V7BNT;iCaxA{7@C#Ot*8F#AhzxZ3?pCw?~B8?y4Bd%goae0 z1A-g;d>UA3+>%(-mqHe3rmbm<;8td6OAA#5KjWHtL?&Rj5W~8ds$OFuVU&4#WqHazaeW|U;8p(r7;j_2u*Lop{bYXko z8Dtp_(o=ipl8C4LrBSumDb;Uh&%-+MD1Q(cr(mwO1vf0)I4~uLWe`OZ)gGR{c?=S; zR;-uonOR+Id93*fU{VH@4h_|ND+A~)ieNp?@f{sIFO{89@y}9&8jx@3q+O8yUBLtB zmQ{ROBQC^(n7x`2{5|p9o^#XDyMq8{`$*x?43|%#W`s1g@b}D68g7c$;i?~ULpp4G zF0lEk5pL&43x&?;C|>gH=DXi{B5QCrhKk-hI@%9q>7t@g>L7aCFc4gKq1-;Jk7&+D zY-&B2lh@&HqPe(veTKvmG&{5FkNpT<`qKz}YSeT+@@%xY9_z}|uK__&$AV?OxAFbF z?TUsfY37z?w!OjJg*Ol?cj@4a&KB%*i)frOag$|VxdX$4R8{HD@9DZs#>pCCCTii5 zfzvOJtynZPZtN@%pn}?FLGj7>vk$`G-BnK5PKi?*8p>tzm@1c}#yt@Wn|dh$y4lwk z@#d-@T~eJY==QOej*_6{#~w9-pQ~hrt{8MtG)F=TI4iI9ZR>gmv+ArsAbDLsz5cRQ z-G<=9e>Q+wbHI<>L`kiK#PRKXds2Nk%)(CW`EhxB8xVcH`OT)c`JJ$w0bMUtf0KPK zg}5~Bfm-KE#alzhE+#)5Xi0j^vLY^!0USoMIw%IpXz+0-sKb`7xNq+0xs{)hg+_ z@}f#4sFnl+=Ap-QQ;H6r@s?a5hkvgi!$P8&bK0qkNJ28%+IU>EKAbS1_bE?&)YwFW zWKt(yNn>#<1`t>rx3%MJ^uXDdfDpt535@&_Sw83dRAcVhcA{P?i^@625T%zDB6wi#@tV8JX!LT#4wX=}$ z?NuwHxFXv2YI+6YC|oi_y#epkTo>myWVDe4ZBsB9$HPji7;?2 z0}aHG^fCz+yAk{vOes&a!I;8Ef}czs&TGBW&#YMIng_dmrf^V_+7eQ=y4 z3g>4uY;d3utxByGG6&^wjRdnl+rNUd@)m`@`p3%YCj6bi@28i5-?enARgk3PC@F{( zXP=tn(mALRtZgdb@OU_cIk~{gGyQ1*H)>{p?yF4;600D}8Q$$-pC4|eE~Epp`F(?7 z^-?)WECmZ{9FWc4Avx-i7JG=gosQXXKtOkY-3`YQnAQmZ|8%ZgOI#3#f(M(^*nDVO z*dXvzOg!cB*E&%&E7GpEsSl0d(`b)LMhIh7u@Y>qMonWFD7ABBoTTUdUr5V*cPB5M zD?ZMWtnXZ=Q%9%Z-0a^MAfG+E{sF=#^Taxh;rVJc(Vr6^-%y(b;psCgY3!M|0~H@m z)vfjQ_x1~1iPHt>RrJ8ht6ckiB-;toZ?o(fIsssqipRDt zbc4GUE=rBOI`N{W0s|@=ktQ_w{0^jpy|``#Ue-^Bw_1M1%%DlsB^=^^&*K@_5v4({ zDq}>&$8gY=D)x++ty&Al?!Fu&|B9%3R=iFuKVA3vPD?%I_L3hBbX}?|*CLn-Jgcko z7A_%-TU{tHfL&CQRVrl6ujTR-1pnhqaDqVmpi2M>=VFxPMqMa}A zrv-1%5@B#^V;goVd6cKU2_4*~hOiFZuTPb;8>`pdEU0o9UzAOy+56odw|B!?GAm?2 zkt!Qk;Yqlie{bJNuEPauB9C`M2|m2A%!Jw(RZxWD=_;a+ ze(~rYD3M8k_8>v^Y;_b_Mmn14cz&?TtJXG^emZgwR%gxUSTX0;@0Xr1Z6Xr4OI)Lo zHL4%F)`GSymjW@4gsdyRJ`;*BWIq{Piw`Wmw`XybU!rDrY~hST;4`AU3+_$b5~TKf z7?efj?#P~Fjx=#}D~${nFZBv!@$LkfHqZ8o*MSj)6A%4+?#8UO(#F2gu)MWUC?r~s zkP!-~XvKbr12hV1)tG*8);n;){psvqsN|!fWA13>1Bd;Uz!tz4x`#U5o#QKEAUz%; zZ$XSZVGPh|_^rq|h?9I&^NGP%t-omR_~e$wG8>D&931)* zj9jz^L{axg8y@GPfn%=TM5S>f=%$pDd&OnvZ@Y%YA9kVW*+&>Sw}}(l$I~U#fZ_Z= zZKBa)mXyuv>|MJO)Si67t|H(S7#XtJcx8B)5yp`R!FQ_VqGX%uuFng49%)(C3dEFE zK{a3L2p_Xk?dTntf)~g_qQRSY!rmcSmO)N=&mm6gF5(ReTD3hs%PH1}9P=?7${3jiTdeDsU*5@s`4`IbUomrCy#bsm-FG0L$%Q@Vq zb<$*iL%j~@v31Q6FgAOq1MAS%StR2Y@5^^PJ8>ALv{AYIK$zZ%F2%ic2eR{8=FiLu z-@j_@-`oms%z|xBUvDe9@f^kpTtAel<)uubM1z#EPle+jc7OD8;;1v(wDh)|As;ib z1RNpF3}d5KHjqDbd+W18a^~et^JrD!9?B`Y9V4siGnudYL5snj+{3B-JD)9-)F(Iv z=`f{tFEgCCV~y0TN0*UCdR?W;_e*OZMb%(><(dfTsXJ}H`z$B_ShB{8MV(Qi*#mSe zZ7btdOhmE}3f-EO?M#hHsX$w>aMB9rrpN>nR}A+c;!VBA%xKu7iI8DAi8&!m1u!Km zQ3vV54sa4b368Hd$^uooso!@i^vu+5=&E!6hW#PR`V<@Er4=O2NVWh%jjHSJ_UZnH zaMju!Radc!67$3OH9e|Rq@!gkHyyxB%q9mGCjZV#l) zOfx>I^^)tC$M}KpKoFC0M3p6Bc8n))RhC7Xuuwsc5HA%Xp6F_(OhTf>kcTE}xfgl= z14k^Bc9MB{>7ghqOieYX$pm`y)qw`O@iBgkoy zIBueOLa`dAqVwA*hnOvAN_`mTZw%5K)9RRWv;C2Zm~vmrXFOhbDxryGP}N+Q6enjL z?;VYH;Z6L&&4M{-0_(to-)3U4-GHLa+lBp>;%ttWw-$p4uP*n>@9sSu>7O}LC3&uI zYi4p(nAey+)U2k8=xJXhbRLC~5HvTBU0;Yx{l`Wu9@kE*!Wbq*?;1;E!GTL;ET&#d zQAo*So9)NARMYR3a(rz17c46*dm5X@eYUj;3X|__wvu#jTwMI4B!a!|fm>DS1?WN@ zZUmp@vU#iySxC1NKwsI?2Q;lV!v5xng3fd|oPTh1&%-}=AW~mxD))ZRT8BG?i5d9; zoIHKX%llpTJvy3C>88R4|tijHtWksW}wOqa|&nEEl@wI|7bVyCtC=o#z z;Ziy0nh?vUVpD%E4ls8K7oljyCj4s*lg76wcq|S%gU+##Jhyqk#e8sk;rNDX!3Fuq zA5|4AM>B|SFQ78|)gS1)WSQ(9xm=s-k&8#LyPRMhGYYZ%9-6H5HB(#YCvFr=a8OVs zB_sQ{k}=MPHZoMH1T4bP&aR?Y0EQ5kSZ9sTr|Dr+fd_17f&U{<-w!umg5Fv1{3sT0 z>N#ETU=!5O=a0BD@6A=qhb%z>pdn{0AH8SOi@EGs=7VrR-)ok~9Xo2D);4WI(d4PW zA5$SDGuN>eIkB({Lb;cbOktw1Su9Ajx07H-13u`wx}POoD0&`eK?1TAK*d>S5_TJl zvEBu}M>V`H9vEP-53V-9OPoGnv-WV>1v23${6ryw8{?>M_m7R zb15=r3s@>WW^-15p1@Hp3JSC2sNyuZqK|I2)L;R9PH-o2kX*&?us~3ybvHGT()+8~;@X(HIt9ZN#yiAek>r%wP+=gq;ocFt}<(50s{v z#3^z`tRrrlmQI!Gmk_82njnx3Gf_LIu39A@?D>^cjsZvXJhtrk`a3d&RfNew!5&v! zoCFJfoM`e4#(M88>IA);TC!Pa|m z1b3CUXWXf0G*k=>9T9XwS-%av6{>8imbG8O6zL|zFFxebEZYoL)@WpY!O+VZ_J?|9 z%@}zG9+7_q!{A`|QllG>&Rj~SH;nj5xhPy6N*7h{b~ZCaS{b7)mVd)z5iHvDJS!dB z1PLfT^%LoZOhG1nWeu8(3a?IMVM*(*O`~-N#W)g@3c8uU5{L9KS+#HjF^o)psBudG z^mHun(#y4C*Y@k~E_0dlj5!tv&oLZ}a%mG-)c>aUjt-9yziKL$h)BPx11iT*4={mp zkl8W2xUlA>nGD3hyh5Zp%3rFMK6JR+;9lU2f($>2gwUH;iImX8&l+SXsw|OeY9im( zRdpZ51Dcq9TB4R6eoNVonltP~0gZ)58;$V8IsFAw3M^JxU_@H}**;gS4whWKkTRpM z`N9wD4<3>+{u1z}N+aWW*vo`XQ+D;M-w*4=`4BEDP!Q-aY74HNC4>85Ten*Tyhffo zXJC!Xu^ctG>VjC^nLTcVdQ=@w^(r#@cfntLDlvKi3apJo#)caxs9#7_%meR4MIs41 zp*b2Bq-0u#Jh0y?#869Hl9jqdD-8W1mQwknkSGRxFB}DoaA<9J^{!-T`=qekW^Lpc zJ7CBuXNA<3Z6FSg><#uI$SRqCS^>aVwrD_8b)U3j_w4U~9+~XN)fMm}S!7Df!Nrxx z-}5_r|Kg!P(CY@3F-h3&Hm5d1_WPIg?g3&F*BSi;B=(HEr`ETlCe=)apakSliEIOv zvSR}VjR6zLU$|7D$Z269m60Y&jc^~ajM)SIBg#I7zv*WO7r%LA;8}NLCnu{MMjK|a zw%GEDIHoITX=?L4tteklxgZ9udbnfV>E4PiOK4d+8*59>{-6>oo6w?g%u%-98rD)` zFg?m;J6Erk+13e$VWj*=z%u&Wl_F2HSo8kh+5+p>iz9m<2Wa+dHe$;Mm4J|+S zw#$UeYoCTgKhEnz{I1{D=VKP)D9GS1!wdZ}@wf|sdJEktFGGk))`un7-?C&Vg``yw zN{K*Dg}=*@qjN+-cBV%yt9Oy%W>@}nvZ)eR$atH$-2=UjA?>%Tu|sm_1yoit;3@OPZ99R!(pLmGi5YKnA3y}g+Yp_}(o!`1T`;&GeIgRHk5U>Ld4_q)!HtuG zO1ZO@?X6Ud>=wr}E^z`&Zw_>t$p`0>~qTe=Zn_@p- z4J6@YJ0G%o9!>}MA*(6Q{gG_xNozc8G!U1{Aa&>^CFu~+r=0I2`Bii&3v;1lk(|{- zSx6mMQv%OU$gUWjYkI#5K(K^xaO)3dw=G^*0)mT{Xn!OKWLJ%QIPcInL5Owj_}T`x zfTgl)+jd4wrXyfdSAF(Q0;9ZMMOx}3T6uyf}gt8{~ZQuNdnIiMNo)Hiu$!f1Ve;);hMu{<~K2n zVO)*%qqAz9=;yKPcCeO1)2*n6TpisC*pDG<#QdK(i2kg-R~kW=hk9gR(N&uHg+g8j zLz!V(o$temDLSuvnTCi{koqMg z2O+&?F&imRZgKB^E((dd!@G-BVyR1=DdsT;Y^(yV@IXlAEqJOF7($UMa@LQS_QdL8 zJjj^V@9IIOH>$(mH{W~GdjwvjcRZ-!@4{IQdt>JsVp1MpQJ?G}7+s zjDz0)^}VZC|44rY$iIJX;?8f;57o7DvPE=r*d7l@j_0$sg4+PBXLi3baf2M|@jU zqzVX#S=MpQ)PG)H9}1Fyrxn;-J!kDsP*nntkd@tMK_3o#y)vLE6v3V6Ce>wD^nA@l znIT;btAG+6!`J&4L8rnb11(#PYXEGe>fO-&@a^T~3Fy$=9gp-1Di$>EFiY5R6# z>2G|S(5(?6sP<|5e82OlG2E~S-wUJUT`8$3{}W3kI_6foE`hx%J4 z0Q}*RO2Gi!ILT8~jtQ1X)S7-80B7bw%Zr&uH)ZHW{+9oa@9V!91b}a~#KG{Wy9{>` ziE{_O^uuM4W~&c}JWOVjia=+5>55lemSVPMxY16uqSH2MHKhyzUMtCwyp=YNn!yY= z%_F}xQ*~5M73vTcPD8ZB@?7G21|*NgsaYIFi^4+0hVcoySme@Ny>DN0hNl?a)lLYD8O~85P>VaWz zvS*lx2M*wlglu3ms>DDSfL1YBkW+QW#ZiS{f(mB5rw9N_jHvtw#StlF1dsDB zb2>4D>dMA08D?<>anvlI2^NkCorQSvX|rHgVP#6!JxE&&lPhuUq=>;eILgF&apYua zWLY47LGQN-HK1QoR^sy==(<^^dDUqmWs@-vLZkJt%U)7_`dfu$SC3!xS0fr$i-DiD-g;9#NTJ#byGIA}PKtha_rIp@%6{TZ#J6 z`x|ObK#7Z3JzgzU1;(n6d!n9-W5d8g%;qUm@%NBGv8ck*$m5)`3yOPZ5Czwsp_mH4 zXrzpq!w9y4a$IQgWiyX8)cWt@E+b{pOA`-j948|CU;X4^iY5~_gVm*SwqDQ|tJXgCCGz73-yw&Wu zRn=zdlAx%JnWZM@(mEMTC<4SX`aK$bXMCi50Xe z@cY~rJ>z?SBesQxtt-`83R)q6TjH8FqA{(>U%$)c9%>llns<1z8{BZv=?30}o?@K| zo!jfBD{Gvj%hB?zE8lB!01QMvz)!8W4$2ntIy6-?udiI?0=!2&Um2mV{)Sq>E}=^J zwiFXpkvCzZMbqJwli8*!;h8otsa7fwN`nqq)Md}uyF3oI6s~hFs^8*;Rq}W_b%4*k zPil}xt-SW=M4Z7=WB|D|Qn06@dC-{d)Q_!Soboif`N%W7({Lq;44FGuLkhXe|I=y^ z2&zK?;HDsHW5ZyvU`I+5vC#5oD$N1r{n4XO9b4_K~BD9MvhpGL^- zuHdeRrZwSF)4|l-BvlVR&vp8X=D*UwROv(0(5*M-ow{!RS$XbjR#j}PqOh-pkOnY$ z1B>Qo(x9V}`IC-9s$rZ-wyzn0mj<)~Vl|c7=Hdo`o99zYm4TYunOZBJ-Bb@i$Sv5- z!OX#1T6ukY({+nvH-Teesq>{gUKnwdL;d$ad`xwHJ z&9(5O%FMf<%H^eMaX@@8<7xIf{6}Su%#DutisX}0yVS3QFp%RaG9^$;NV0FyZT}Z= zywGhK&w;An#@%Z&+MUZzfn0V)7N~RSGxG6d>TWY{P2iW>9$9*r8vJm{mcNe|np>jm zg&-B4cDdVl|Iquz`XCKMR2W`DF6l8hzD2N!7=90*Cf_?T!lsQ7h>!JcWOQDKO{BfV zjxn*40uAZi;^~S%)bHX=jT65NVnr5TjCZoOR%D~~rCJgp%Bn0iL=l+ou*LX<@3>oO zcsD;U;0(EbZEbcS>F!>Q0HgrI+$S?gOPmq@zIj1E-h0}iJAyz+Pl|*r5RlY243yNcyU@zN-yzzglBCPAv83gwvLj{_f&KgH}AYU03C&)fKy;< z77dRv$cdOCvwaL_gvZ^p-U0kggZArjXNJD>06&w z1w-K+H1nA_p`KPZyCr`YpY_C{gn&h~^k_QsFS$Vst?wA~EhpZx1ODCij@0z{lfq>P z*mW^EGPr9+N~&V-FnRlz_L4e0?4;8Z0~)Ic5j|*Mz1Dn#BR|YzX$n{>y3o;pCv>&R zD{8UT=>UDEd1Ju(x!nZy4h9Sz&IX$2aGkD?xZPHy)rYgeZtg3>f%a9lna{W@}FWy+GcZE zxV6vE2iAft1Qd?+)?`K>QG#ScGk~PL39E7k$X|R2bo-yJXBjMg-Yvb&MEp(Z0$|Vt zdIX!+%=kO+V*VejcRvd{|MoJhkV$q3TlC);wZlC&c)Zdq&BjYej5Lju3Vrc6`zsyz zh8UXT_KCl0r>`X~S~3xPkjZ+r37bU?gZ- zU+0eFUfE!hDKKHE6YHK86R1fJrJE$%NuP?%=54cK@7q5HEU6bjFw*~?G9N?9nZlNP zpJ4(=vJbhSu{lu$$m*qVd6Kao(Abw5=oJ5kVQQ+YD9KIHi7b+S z$y#RZb^!+o+?ZG_M;>Z$m=8W+pvK!7RdrP@a~>7lwj;l+FF=C<@(%6#oiH|;@Zd;g zd=F`b#9c9NUHW_~+)5fh)|Bl0PEoI2J3LOa;HNV;o?KU}N}^h|vvFINARdG_tchnZ z#%`OE;p?%Bu6i?Z^#X{AorZwPhyr4kc7H2HV|83VHmx8Zbz9=oDJ zrbS`>X()JNd{cfiuU||O+Aa*3lc#z1UMuXw^wF)Df1+ZBz(Fx{`i={k>551KX;lyo z4J9x!xhQex&D$*&SCPRRIumfM5o+{qiloJYd~TU5laV4vgurc;6W&O}X>QvG+YaWF z1U5J*E?-!bb%v-S90R=tpOc9PpQg&{W+x2WMsa@;Min5x8hh$ha?8LEWEAqhQ`A8X zR59WamXZs{k{MN$2~IGrxDF0fJF%`myhV#I`D1T>s)1t zh!aMfIB6_zC})~$G{0*gFtTKb@AJ4Lff7#mGO8?55e)>0C%xPecnJ~E+>0d5J}d2x zaE5u6m;ScI<3|b#FQh7o$v4jgYhb>RVecI^MR(6v?sm9-Jn+;&#EPHhZKi~OD_C*( zENex9L9vKJ^8p$^ty2fEAEEt}eAd{$vR(B)NA(CEn6oqCiq=C23O`$8hi+cU6~pdD zICYaLB}OCe`>#aFA&EgCa`T5d)lLa-o@~&ZxKw?>qUu~Bs!Co%O4v&z?KM(1sp)BO z$|2Xgy|%pWlNBfbX1$>-?6fGd{_c|wH^9P8hke+kDptAolk1c9!{;EJyvUt=uJXXz z2+P;Qccncx1_O=ETP4yO*Ir=%y%^JoU|s*}!S0+L1^Kd;@(DDhJ~vCud<0eZZQNd> z*j|*nXOPw4s%7PWhA$a=5P*ExxWH#?pO^18%4;IVm6DrvZ^e=)G&%&XkMnnZm`D!R zPUER@X)4wGNsFfwGHgFOT+=4sm{C<2H2*V zhFjV;HyPr(1vDFMKNPw+jyXHozo;^w!a>qxXE#zG9IAF#HAp_>c!{~HSYckjU@d*~ zIhWT0f)jLW;X5QQ{}@znDswquCp?K-%Y@QHVIW!WR3pz9IoECMF(t8|=F^zT-x&V$ zpfcLtd!qMA*|%uX+G{psXK9@xtIxd>=dDmF%|tD&I?@u=bm+m+VvY#~Qpu_+7cV=p z-1jg|LyK|{xs=dyvD`Lp3Y!@DR@!GHOY&uOcLE6p4uxN=wYlBNPbe>tA~IUq6t+UN zNvWeE>b81kNYYu3JabTBzGKWVuKHX%3WGgh=-v@>-K21SmJ1@6GmdCIoNAZF9m@^+ z*6pVl&gM@YEZqg}(x_$1COUdCE;(ausp^i)!RuEtE#xdL%XfTxH=-(+T(?EXlVJ9a zZVbNyj{_Cz=!&L=wD_ic0#;P~3+1rkZe{GaMj_6LUP170jA=hBm|{AFjV6Qe5Yw|v zUVFSmGc1#f@JdT!q8nsic7|9*cSk)a|Ah?@T8JGEuP;9?ub(?zeYO-VDm-34-(Ly- z6&TZ|W}Zwo<5meGZT^fYVp2rWHdK}(pcI*m^R;x!Xdip>x6?!8E``PH5mTkq!GwXl zZ0Q9O5Y_?!ZoIDV-N^UJCvS=FQDZBF6FnK1#%2j?Gjw4094@Pf)!emnmj^0F3HHWy zAB2vuq|QpzVWT0UK*%%8{LGrw{Rgaek?7=aB8ABK4LiGL))wTmQnuSz|GoaTuRL%m@jt>cioyUVxBrI+t{@q+PLJ6Ap&IF7QS{H%&_t1dS&JIK=#uO( ziM-Gxw6AR7()81v1GiBWW6AONF0yAu>Y5;%;^lyMwgiEIPi_G(hIKNMYNLZg>iQZr zlb{U8lXEJ0Q>=kc+B3uEdUI8J#_4u}-t_UXpXZJ0>s?VV;AX4ussl1so=x4Oc41er zmWM~`d+v!n@B^?r0dnZ}C~Na6;K}z4;7vau`A3lms*7P0>ak28BhVSV^tLr;pYkKe z;S7xo-OBQfEQ$~id5z?4?V6HvWy14A7pR6B`9@&L-6Y^*TRjV|ll_qcZmz)9QR>z6 z^{@XS3Y-Q5IMh`$n+4-)yJqhx&Jsb@uc7N*F?}tsl$EI*I2~1PYfJ>K zaXDHEcI4ck=+MO4SUn*~DcwnJr-?;|QB{^?V%s`A!B*B=ffsfYg{+}iC1#VZ00Mg& zt||hZ6<2v*PiNFL4hf{R6rAKh0=F)eMK<4)Bi~gRCHIEy`zDw?e~G-KkWTGDCorr& zkQC>x=^!j!k*)y~-V9y#5I+$`Y10cYg4~kNSh&(j>+3o;4JdfFjA=wvlIW%!N`nHx zlnL47XRRnkALiCfrrHPr?D;W4Oj_5CF{J3ao6H2`pu6{I+&n*ANu9z1hs5X;I+YDj zSV$nY0F80QEdDCa+J(7IaxIJ)?=(qGGUiPhMtbzz$V~i%)QNK)W=&FgW8;uT>%*h% z9wBcwNHEL}+;j@<&PkQTc^X$h;|AbVpOMX$ex7K*q{Z(r{X`7f@+w$wiTE9l;*<@P z$CFAOkSd&o)+Fe*^;M9^Rgf6xZ8+FBj5wwt%cn)l)+UBc%;QN@1|u}FjCIo)Jn4RW z#uHDhq_oBb ztVqjqd+D6W<50*;yDib{d;6&YZojg!J4bR=wDx+*-)@&fQ$zKRZtOw^%s)$*Snx?B zP>c;$FqLfSJjd>U(pSkcx3?g!5D{2}dEr)T$D>G7&ca}Y$+y9m$d z#5?>Dv3jevliRiKtv?S3=pvf|xZnkrEyX&zD0h}ANi zz5G=`LGeh~C5h_$@b~)`OtAwk)8?V2S=OOryp4;qMKnBWmsVREkmpL&zZ+~+7_vd- z5*UjZ!G;uazeu~neEKVJu^H3Bk7l(5a$LW5N0@(uT^B`=OWqf}5!$+n;qQaN?KRW| zq=*b)Nb%nv8m;(#66}%EQ90LXu*v7sOek1k%&wPtNA`MIdI_Ahltjh;5_q1YqKcEl zS1CbgEke2N0+qoLpMA&8W8SsRADQ7a&N5UcQ~N;jwe+|#DI_M*(|-}fKJjKlGv_n$ zXw=vsH4D`+40Ih1K(RyI!+c;Y82~+BXEW#Z%Uajo1-z&KYx3u2CjLf{o0k_pjVInY zR`ePZhO;(wTpu~gm4~@u_M`934Vlk7pd0Zo9|^wh|YCv;$c4sz3bU*(9@UYbvAw7sveV`~ELe`E!51 z<n;okL)gDx03+#vn>M*@J`@7mM6y$Hf-BDD*3@XOdCdDS=FI92e6# zHO?5t^0dMsFuD>Wn?_Q9v9fu&gW;pgEZ{h>C2ob(_T*%@GhZ;9S|Es4)T7M0Fyh2t zP@V;F?-hq!aD7*Jjgm&QifGT6D~E;Yt?IB|KWxJFIr2>$)gqLaye*euBQvDX5Y#G~ z{&=Wg5Np9i`u)D-Dj{Ezjc#qT>(b!00Rnf!&0CP!-x;5**a~88nQ))#c8LFw08d1u zfB(vHCrGFm5Rcj~LQKdcYdH>%4O(sGGT|A(B{47pXmRZ{=w812$<-w(8|?XyuKoXI zhKT>UYyY?7H@;{uit9|Jf$3Z)t2|ZuNidFiCORmI3sr!8Iwm z_O*CYYG1&D>grr(`c(?5tJNaqb;0BuWMR&HzT%7Cy*9^D*)Lu^HKrh%)vbjfq&yAj zFyJf}lmIwY0y+IPiDKF&T(mSqMn5*Iyh3Q$20a`TOs%rxN6J(`jehu>-& zVsUuS6D3O~Hx>HuP*6z)yNNH|v;P=%U^XGf;9DjZMEBPur>)krM`gTVJH1g4b|&8V z*yI3t!A}}8wurMWgmO%i4LP8=1<`Ew`#s2m{JIg%2<^n7K+-qPjjkf^7Z>&bQEdfH zRwimtHPJyU==C{xt%`k>AsSo5eeNzF0YcZ|e_~|GA+5U1f9~o%Ef|4gaUYej;oAsUZCYghcxb zi1fcX5C5^<$oQX0+0@tpU}y7x)zy_=#!7gS7-Ya8eOuc8V6Lt&yU3*cfa`0-&43O~ z{|-qz*MColdG||q9Ls~FS_Ovlhl{GR>e8C&jbe#yXnrRs2rYmX6BE-vv9J^w6Vr{; zj@{cIWE!#5-aWwY?XP$kbXhAcehF7c=_B_u6lTW+VO;D7iKA?L!54^r7<4$bOHI2g z+Dy>-c3?B7r(qvQSW|E_vf@}<)?32QyD_RIk(~=VSqc!<+2EcdWBbYZyzCWk`m^bj z^KU!~|2n`@`;{MLlmf5yPO#!d=@>K?7#=G>yJj3T&!TZAT3-!`q_*&y#)i zR}$R*=Hv;fj`qsZm)$$U(V8HOlag+=-{c2euXl6&1BFb2qGB~0)48YlbAgswz2nZM zs7NBr(VJEY^x?H`r`k~>&BJ#aXGTKi@dSniIQYR!`?gI=CC6=O=QZ2&{87}DmNt(3 z!uLi}bq-kZcR%UgzR}p&7zw1zl$WLm;z*jtvmttD81@2PM3}P6( zmk^<_@_YK%4o#9j*~fzeEA?O*rGfq@CurGh(&?U_XZONI*h&8BWFP+Pq{h>TIW82n2lm&S} z$7~QQfo2oHP|;DDlD*73EUZJOg8qp?DuBXZ*ciGo&KaY2;0H>lyQWERF9Q&U6<3)Y z`>$a*aAYq}Q`3X2RePHCOebacaUf1+7EwrVd<<;mw4YAeaRLS9eA$Gn)VWVLK z_P9*Bgq#Z*RKNm7>x%Ez?4N#XOc|Hb$}gH1dD;%N3?zbw-bMw(?rCIOJw3vjTDiB2 z6{`+Vea%tX*j@Lxw>bueenCM3Y#f{$o>zv{?U6Qoo-U|0BeX9I)~qIbNfeg4PSsy% ziVIbgm+{$u+ou9nT;18m{$0-a=rGY|)fkeI>0}u@VSB^eV;Rsb-;oc;b1dfgs*niw zai$=32M`UA^p3-z4J(#Zqb=1b>CWF2>#3Cc`ti$KU_t&~zbV7RTi?0*v9bLVhS22q z3&DA>U(=A2>|0MV0N}UPU2NCHmA`g87y9w!?c7ZA`#3ZRDyFcuZjg-p8Of2;Kc;qd z)(55S%geNOK2mi^YBNBPvZoks0L4RR^t;Q};(R}yz=gX0Ar3$SQDq_Odk1Apq?~t? z%LjR1gOo4T!^WJu_kR|G1zgB;S-cimxE2L1M!0!>A6;L&0Q1qQ70t8nc;6ukAEoe#gp77V$_*E}3WYb^X`y>3qF0dK7r=y@0VaR|)AHS_eQ zJO|2cHlyAWF;(p)dXyO3xp+4f@kQ5XW+ZyLUYEham+7q3pI(A^dLS%oC-+-_-f=cI zd@n=V4uLm7BafwqBVJkNSsTyd>cLKcMEkxt-=rGicZOC9m}k=xG+WC+cWy#L0+L(f zdPI8D?#;x%PpB}hc*C^8hb9E6_x?n80X7!Ljx300O_o)(bO-WQr6((3a3Ptgvor2# zn6o=lI z6*)6*Q&Iv4f8z*KIj;ICT%no#;$X087e~tU<4e8g&R4b_lU^cR zt$ytc4c%81cfV}JavCngS8z0PY|;}1?Kn%8Vk%=qp2fklH$_IwRF#Eg&@D}~3|Uhr zAi|;h&l+A9*QCT4tQ_LPBDz$&EvDZLrkQY|W3cz#)KMk?W+TJKsg&Vlk0BM*v_34q z(tg%3u&76yx>Q>vdt`FLa)Y4u4raWozQPfS`0@Y+8s62FFfNOhr?USGOhB{00~=@! z-Zle5SP)WD^nz4hzVs;$v2`0US2`bQIUV0l7U*aXg9vm4Jbuc|InqXwAA`l@Xt!>81(K(3gUqLr-Sb#lrxy4xP2lJ8ymOi(19yECe6~(Euf0_f+<>)HV^p6UK=V86*zf5 z-V#J&A!NgQS4_rzyoqNq{UL)lXj~>`6skPYFCUdjA%$mh4`;^0AuF_|Q&6cVZk14| zC}t+pARw?mZ^}|gOG{U1&~9&Un774eoIHNcz%PBfI=txKut)Tkfkfi@= zOaAf6-yxxfKxARkF)uIAfI0OR%|ZMdJ#@A4x4xOEkxqU4$s)Ng&!2hbzp6m>it0X} zao{_vuq<0Si*il}Z>9`q36tNrj}n`R4R0n zwYYmw2-ORsjN=vgYAo1-b4*FY*sLV$NIsZ|9B#pa#MoX11=*&@Xn`q#+1!M;Z))De z!Ps*1awJ}_Ziv3!?AK>0L2G;Eb-w1D_=s&DJD}d>>NpIs)Pp$M==>@(NUv@h3uaVU z$WPNO$4zJa5FLU(`jUMHwO=dEx%B9kgvxV+Vk`pmC8M~k%r-yzp~&2{cBF8YJ?K{B zpzs;Q1E2x0Ew9G|%FD}3NqPA)!wCmYd=G$_JwG0mf3xku0GjpSC!&ZAVQ7Djp^!@% z&y|SswECQ)R=9^3<{~R8`B$G&@#|NVKMn9u5cG*~I&-$oD~{dXBknD_3|w4X7He(k zF)^qyv9SPa+HZ=SG3&9aH>}iPOqP|9g{S!ZxzhCt^o|dMgGww17_dEWAGo-lGkIbj8RP`ErNlE&fY1u!y_}atA~Wgu$V5vzu($ zeuDm*e1bFZoA4=vF_A}&YR4Hd`Fu=N!LZJs8Pzh{%5~&{*E{HO>VA!(#GU!KN7;S- zCSZRyTQfaSfQ00Ok6k@p;glw2xY`%wb0xK%E+fbFnv?1xEz9*gmkGv--D>B1t5sO~ zR4=v=5`i}2q!fp4gKJ+<*G`qn8%;0+HhIU+BG#rLn75*&?EIZo*ym@Rot^l3RMd)T z*w{5JVkV~Nr_d^9hY{pk^BG6R6e%0-Ll3_ZP`SHB5MZ?5U|>uH#ay|#^=--ZS%g(x zxte->hd@PFrESC-vNn5h6UThJYqTyeTIWya{++X{hvU9hIj&lqT}u{D7VJFnJ3k;l z(+Zn;k~?LUl)#TDwo3o})Fh~+WI(Px*ZmQDq1p+tFwKrl+szFD9BXVL~n- zkcp+|5e)!Qfm|wPC9~CH4UEnlQ?e|Ixk*-27YQkVRoq=yO=U#HuW9m>YhF)uJv}|L zNsJ_Og(}4`D2PY_L9yDJqC@`La{0rva)Jz&iOPWazH{9 zkMW~saH!{?LP_J7!a&AK+zDN_g*DUZq`{z!j7?&^qOMlBu{Dag9Fn#Pl4LisZh7LW zA%*Q*IL`7@fAzJLkMd}THDAy+$x+x7{lfvpJX73FwC1OFXWfefk;#8!ngio+s<|l%=x#r>~Y=mXBTJ z6r7rDdmo`3&m@OhX5o3ZaAy;7r5w05Zbz%Xd9J9Y@$5iq)me?@tXRP&y#1xL$I4nd zNebU+U|S&I{Mo~MIab_wFq(>Oqkw`s?He?7sI zHdh2$*tR#<&4Lmsmx zIu1*1VfUytwI&cEqVK`PN91APeFU2-f1=z=XAD*=wZrkeu(T+z*|g!bS(I(@SR28g zsiQmLao8p^SDBQ!=%mn{rgk#Oenyts;XiDNZ=(L`+#NzFac0?GBlCLFU@KY zzzZvvPKHz1!hjRV_7P`xe0=6neIkIOW5kA!OGprZ=J4wv26PsHmq|gMxFS^OqyW0< z%#o21y(_DBlY{V{MV#t=*83NKC1t9UIS8lI4>GmCiwP?c!xUh`6pB$U?-Xa-DYK{1 z;h{B0MW(frfoEz$V?8G%hrut|8OIp+L zi6Frqs|wss;c6%Bh?&iKGoMhta2N!Vu^93AZVqw~d+I{9F_K&gYsY~DJEyN;C)ut> z2_k*#voxWL4F#QcEy-^`5UU}h=HV-g7GqM02^0(Jd?!n=LhSn zwYiT0UTQDTgj3cTLjC%kM*dF)dP7TLm$vy7SErsx7h_-O`&_4)(i|_5b=}U1(rjk7 zlISpXo6KqURv*DA(>*b;ii4X^UJbh%<4eV` z7U<0KDnw;WIo})Gn@Enj81O(cmgP@Hl-npV479ndG*HZZ#Nz9eT^i2kL|i|n_4go_qWY;%f%75{&@FN$Zw8p<0vAMii>UR+D;~P8j{@`G zDB9Z2P|KQMa46PT)8P~j2O;X^HX0khm`+?^Gsl#&nl2`su4sQ}{s{B&E(BBE40A{9 zi_3K>64yozi^+-ZP-8CC)qxt3_?y!s5@Bb~Bz#%(2JX+LUB>PGXbT1m{KyK$YA%8# zr}CY=j=t*2iDd8BEph3@_94?kJ@Nj9@DF@T27krxG9aX^4Gf>GGq3iC`|huekqR@p zl6R#rpM760Q$YX$zUuPUTi0VbD`*f?D3Ie$j>>EevbS)=}fcLjg3yL<}awa(r!l@;KP0^R((e|N@{9qmJ8+DJ1QzFj4tQ;gkJaGGu%(P z_@9nh-jDb4`#C5i!sT@kT!%h#;gnP?h1`6ryR%WR8(=z2OenDfqJXz*?YyzMIn|+8 z-^RmuXVt1M;$&qh^9{}6tytkC2TNj{UOLdN+9Hyjud%rL%z*(b3`c(sSCeDldi9re zyu~bF(KoHUVBF_C1*IRXe;E~LD5|XVtl+I&D!)!;%1s@uUR*lKm^7v{{@YgvJ zV7#0XE%yA7`1OHd0vxRW-Ksm1AIY(yF(WpB578MvqtCYy`11Nr!!T_rD{S6k@0g6Q z9op6)x=O+xK+wxK3w|cOb~NCOE|(C)ZnsciRk5&KXBiUn_Dh;A%vWsOnQl1FYfc2W zf*4n8FGwtBDIFqnX|mPF_}~G0%s10a8p685BJWf)wf3TgYuc-wiA;RLj zCa3&3B{kjZ!uvh?GdD6UvLqwp?jncFePryXG!|@`pxB19ojb02Jxnw$_3@+UF$6wq z>Tw$vW+;^^nrebn`Ht=ZZ?lWB-*j5!h_a8>YUx*6+u_D=0rK;QRuX;D)}1s$cEP2@pJN-8cc?pI2RTQ|+3KBJ_P5~@{w z>g1#nDjM31tP|i~1VrQrJY9n3K8UDd%%2>)xN)DJN_6~Wt7t?n;#E9=d3zbcrD-DQ zi7YH6b8^4uxS5@e$1VlIF15&n?3v{Kh4YnWRP)IOf5uSU&u%z0fz@gcng9jr|7cAq zmMteAu-Nj;>pQ4)M@D~xs`^N4#h4iMM*kW?*c`PBd?CraL^bur5HpQUCJ!A0<2|rw zXJ_Z2$E5QqK-kF0$YwX$_;SU0?P4cU&wv#d@T5s2yMC`M$?*F*eKq{XC)yq)oQL=J z{VW~OnY4yodqkToKkQF!>HgNEdDq1Xp(%tddv}K9N>#!Y&z1BY*eNyax<{Ta#piP0 ze9ujgC}-*dqZ(cNNoNiQH(P4BGWdg5vng)Rx)I!GauAYx_Xd{o57;Ym9Xu9+dwpr8 zNVO?tIH#5HcIzL4pxB_02%crgR^6)0#n|s3JTT&#TEhkQl*;(u9E3kGyy}-STtio| z+D_KlV}dOW^65i0GaX+U%UVe*RfpSrgA~?h{mQbp`n}$O@#Df*MCDA6L^z4N9}e}H z*r+Uxmm3^XgAN}EX&`5Gn%?z7*XyVR$&2Xqsxh>WD=3& zbaW8_tOHaZ1;y!P(NWPnufP<=d<&SK(ZiYI2jIV?j7*n4<9gXG#%O;mZ)CDwPX0BYu{|>6PvbdaVSb|;YYO=}MCdgXo&9%7O-oS^lyTa;MQys56> zJN%(PO%V@EHu0x>kM9>}S8fyL3CokGh;NXl>tR7R>EHAO(|E$K?3I`D(4S) z3U<%iG7Bw~$&FERr2D0v8NakvzD%Jk>=hf-a>KZCRQ~~2(g71nc&Q8QCbX2Ye+@pZ zy}ayeA151-5Jal99H9_~kvpFqHcKGx5yODzHRLv@R4{%@=~=ezaT(p8i{)=Kh)LVH z9__bN3v&?ydX2IQCd9&Hvk*?~H8^;r#fyLo>G4Fdtn%=pKVHEdYwP-4a__|yZUOTb zpdr-s^t}fTsGlG|LHV(oPUgv%JHEA)A8~VWaRJiSw|>C?m^&|aMDOpTI-Ce`3wFHn)?3^$zB~Le{wG>6EFA|FKpCfb-Y)nHXG6U(n}e$A zO(uSRmPuH@dLTVx!;NV8M!#&u0RXRrMU_GXlBt&$KL!TIyahX3rQ~~@HF#7*;>VV= z12Zn;>1x=#MVzi1_YY+S(Io=i6p0jaQhCbWm3)(YcRb8Aln#UL^`!_<&DKD(kMx@n ztM|>Xmd`LFn}5;gBX+M?sW|Uo6v%i)+$C8TA0!q7(uk761uC${E0SVO&773bX+Xxm zS?N1qHWvOa)mf5JQ-=U@9m;dW%;Fd6&mMf;y3g3~;uP}J!~3PCGwo&e&%#G!8R~5pw6#Q zyv0Dt!H{@Sc>Ckg59?yE;+tDj5$D}7%K+yCJHKZW0VvithE3pd-g}io$65L7PFKQw zWc3)nZ+lfZUqPtn_ZZ|JcN$xFa1_7C+PU;TcjYtVLr1^H>l%w!CRh0RW;?e;NX)jz zh@8#VeIHQeR+WlPS3`$~=5*|v*8~1R0!)kG%v&vDO zCkSbLKJ?udUgifi+=kp?Pfd1FtG5aI=sL&-1<~USm!3V{A73pv952H_=vVq*6;liC zRY&RV###q5mMEAH?}D-A=7UnvVB(lvrg1zT``Ly)Z`F2BZm$-T$qwRIaI1NH1F-bE zhMd3sjba#HPf%|+zKxPGF|OVy`~7!E`fuxvxz=9B;J_STUwD{&j%qAq7IyUqS*Zh; zg`O^?_P^@ zSFUt<{)cU}46jF1&}f=ep?o@+%XtTMD;lYjpFRYOA( zU@Yek9``R^larHF3=C1h9b` zLZOM9b9xBTEa20IEu)pkAVK-L@Dgg(TMYFMOLp!jNT^=WE5oKD?{ z!$Tp!Nck0W9gq;{*pHVgNZ2hFsy$WuQbI=Fl5svzVO57d+6gC`d9&+%c2k4udPFk1 z^-E+ibf->_N*;!`URGel-VRKB9kP<&n`G(zrH`t0%j?Ty&Cg@8acB$QC}6QwR!one zfIan5;)qF>%Mg3T{idU6$A6A!Mu>&$08r_rlLY%sk$qVD;|q6}w^j^^&s%2vPXcg} z*Q*{kQxbV3`v;{5OJ4|L30LV=(B_*gSDs^;O2BvL zL1VjL(DAz7aXRSi%_b{Ud_Q#jW^)0LIh-kGAEfxT6+McC#LrM3S598|7fWw=b`9%a zy0Mlq%@t*2rTc>^b*`u9FWBtc0{-N_GRC}}zYq`Zi#l^!Cb!6{yA(mIc7$&YZ`}Oe zw`ysobgo;)XZ=hE_sw$alOfiOM;mzilQ*1^Cj$PjL+XZ;j*Ln3wHp!{E<0?eyccG} zAbSl5X_u2Zr*!w2^}&$fqJq-M9QF7R8lAl=ECItWXbl&g_BI;u;RHMhn$@6l6Lk-lf7@EUhR$bS=CQ9 zIhr%Guv|R7ptSG!smk(aMwOJ%5_&(|y4{@;9P}e#Gy3!qdE@6!U|%gJdbMY!rQxfU zXb1tm{JPDO9Z$Y67N9|>sHl`0S4gE{g*E`?k^iQk}Hv-1_mxGQ#n6% zuuEa`@*B?88ZN@b4St6GDolIk`$1j+irHq}`eU;-*VPOcQfA1wV8d0aa~8xS>J zu)s;T(ZHyI&si!yD07F(j9r;Rhh1y4{El-IrG_{xTgGY$m^LaoM9w|@AAYa~U^OAB z!HMQh_)mbX3L=}|;O4tKVfl85x@pmnqFy*UG=5W2Q9*`>LaLB9O9`AX3D*!1gDjrG zt@HqoHdMGFrv%DV8QZm3L2K=Z(z3E8&bIfDHr(mMnF2&Uppg_YCz(mdbUN+2U(wMh z3sMJ?GiW?c$A33$ex;|EVuk_B1(wvkJWNVV?09!8G!%BR-kQkX4@0*|_guTWl3l7p zzv?8_NVhdPR?QPxcZRxU)Km~jafc%sG(KLl4>FH9H&f0&6n9=~(S*n03b^*7yc(9< z`t;EQ5&C)tG5g*P4&{QCs)OlOzX zTH+5;qz$-_c{k2?U5Zl*hyN@ z;xT>nP1=88-+DXM{@@g4B$bXZ_-o?xYfLWQiX`8ZVqd7Jh!-0HiZ&2AerG3%PK`N* zO+>U$SyPHQFV}Rv?02_WZHx)i*41OtniAddFvvrfK}d(PRthR*Uq!8npxFda$2F?odIc+k=b1gnGIXVoT*<}bckYci5p z;S>-XJ!SzT+I;Yw2at=Og|Y_Q*TDP(DHhvWA>jl2i@y5~+6hyzQ2#Lv-dAzu^)Gq} z6Z0P&Jm;9hOnl04U+*SuUn9C$n0Dl=;u0QFl72n>F`2`aG*_~vX>V<57C4Py8=kV? zB(J813E)n|ed27LQ$j+5c=vvEl@kTv47Ieh`WB1c6PpwhMzI-_Wzn&)wr<>T81=#U zGa1Z84A^l$`8)9IwWMawCFDYPfve0FQ;PXt_n-gx4U!qo%F0E9heRQOy6e|}4rbRF zrU37lf9?vWV8Ljvul$o0gSg`M%K}F6v`l!alV|h(MTiln5*wk2>De4Z6&}h)UDcp^ zu`)UQ-tTeEaO|lIMl$p4Ejkxwo=F&^f?m;^`6EB3W+V?_wW~Y?RhODyd*d4GM?x*gpG^B5uP! z(TMl&HU|51z2R80eG)J2yj>KmP#i`Cg_TI%{)RCihq{)l^kjY@K}S`JnPSXQ-nl5c z=K0h_=)^QIJRA;RI0t{lgO8XdPYIX@F$V`nRn25HayqEGan!==JzI%}cJENn~b>OCI~JtzophoPaN>8=4sE}iUM+Q|>7v8=6UXaQI6=DF3Z;rN) z?Wu?8k0aOnAWSf_ry?}3ME>dRlX~E$2Au4dA}?jY*$T7~s#VW<8;e5bPPmNom0|po zTZs5EcEG>vNW$nUOau`R!8$aTbNJ_!1v}a&$oWQl_oz$%xUmF1I`mL?FtuM#%<5^0hmPK8d&#-=J3_)qF2 zneYg`g?C-F%4hC3u#G>UNt%BXA@`eJ3msOJ|7|%y*tVOCJX0P}gq8Z$3xJ?iQBxzl zu-r9Ez-PvW2ec?z)qHiZJ|i~Zd!Vn;MFA)qlw{~UaM-+YdyA7n`zOEh{%F&w`GcgS z^jIDGzxFb{Gvw2`C+e;BcnQa%RR<{0 z01*q&YhXYavEhRp8?;1iIPsHYDS!zdj`=JzU-bhH%+=NPdVj(XKnqlOD5hlDl5dSM z+uKG;%F0MjO{qIOJJ07`NWe+ds6LUa-I0T(dIp^)hp(Uilm8jo&PC*PPXeY~Ap5Yg zW?8Fs@!-*N(TfKbj>mymJR?nvY;w(l_gUQ>vamyc{i2U(FJ4_Y#DdK^@6SR;RYOD4 zO0#pW>Eie5vcsmxp&jt`{6yW&$D{JSm%BA%K=!=vRy(hGCcZ~u)%83vVh6e>^JQEg z4+_azeY*yhtlLNCD-B)OeW5klJh)QXEe%G~xW2h;1blPdiGMflK>y2XgN|~GMJ2cH zsn7e=J97c;O2<OlkxI5mxmmi;}vJ3!MLj@OXQJ#SpXY)1-kiw<;R$SZJt=VI98|fl%w3PQ!#PXM)=xb2oXh|23R&lEZGT<*luUa*a-?Pf*K=XO!QV z8HfI=4O;bNr*%&EN5`$OEJ;MmD_OF~q0Mpas;X{-@0_l?U;5{G6LCrr1>7!Mh^C@N zrT0}50ooaiw^*v9yL1NRsMj5%k&)4_gapioC&iEI%mUeDVR?B(VEGFY z3tCyx8)tg57p6Pm3cNkDV6^pab?EwF3P64YV2*$;MRa$I zM)KWifW|T+x;EB#Q?0G$hf95X5XG0yD)G%ZPe*3(z9rM?S1d&i7t7{fA1wv(tZAyB z(Z##hT^x;lku-^sC~m>r-exWQp-U(i6MK6@EZ`nbxtbgDMJ`;&4Kr!8t%lhI{w7t-ZU zjAc=;3IGFQxvpoecl&eYL@$pwTfkF1t*q&3YD&GmzPRpZ2e|;p{#v^LAXS29J^IKv z)8#3_fTg+(K*H{-`&qtTyBFB}$!&18sUH#cta-003uVOTgg z)5RLoT)GUW=5*jQ|I4*~WqOTa*pP3bSFCb+S>3gi|V`E}EbEEhN8*I1kucs7nSU|F^MN>xe z4ADb(=eN++noVs!qkK?I7d2alx4eUQj8C}>wA$LDiK~7|j3AZtBboMxxVP5^u#uT? zv|fcQb9t?sVt9STH)wA0pos7|Q78I`z&ZZc1ki~)lhdY2%wdZ;hqd=PyUZNc_@6!E zQEY7v+9h?5As(Vv!F;-a4Z~iR3OR~-F%X?e)=|LN-Zosm^i%7;+(Ft2ua%!oots z>V-|rpKM2(k1wxKXO&AQfK^mFpXsv7kxaHug3(SsU1pY4 zC7ZEySt8Z|>$V30yAiK493FoOehtCu5|GR7>E>>#)y_=gn%(XA=u`5rWzA^q#Xwi= z{qd-@Fgw{p^Ql4a@E_Xy*B?ryxF3NQmEKTj>0zc}hU)d&L<$?fYKGnws;T zJIhWl7|XBAcgsxqFwV;lpTKF;p71#=G<)vC0>4&V6JcSd&EWHyVP9o<2c6{Wa_23oM!c(yP&b$WVUMSi zdXI}9EC3wG+dSO?s$o=vp?|qOP@-KQxw&ZofFy*|+3HATA0eJ?I}a)#2>|PRdl>Tu z!xr%3Wluf%MS+^U>G}XbU@v8+hw#(wiDrunQ>N!FPPXEH)@(mOJzZV7=JdSz`Uu@E zj269lNykr|lr=Qwvx;U}5diW9u;Q8;bii=~f4QXjSy^DIBjO;??g!z;y{qV?r2g7D zT|ATtw$0hIyREASL@wXCa=oabogBxbS;x`6y}c{IB~P?@K5^Tw=O_K zucCFI!EF}qw=}-R3@^{LGgXP8c|D#}NAlk=k7n4i>aDudb7XkY!rgJ+8*EUB-bng5kJahf_cA{@nOnxE~`;7t_NqnK;^fhDk0$>~&X zGq-1mbLV$~$K(EwG>02y-n*5RWe*F)#0Y^lZrUO2g>0BBevcHPLfyfIJA|z;p0Q0L ztQCbR5{}B9X77M5FFU=}=X-+FhV`#%<a#I{D!aA7+Bfcod(|bvs&;voDJ1?I~EkeNAC3gYN)v z69E4HSZ=Tdd|gmXOpITd2LmY}n*f0Kn>(_MyR2V20T{_FkAn^w=NB3pDmuCzfOXn% z;#xYgCd&r*3$1)Gn&MLiBv_E8ODo0lQ`qslADC8Fumw2SoF>U%BW5wZ zVwJzqzrfJXU#nnyM_eMJ2|7tl$CG3lpOc^qg;f8RWmzKg0F)}#WT%Hk1 zG&#e4igCu{^w_OG*BC?VjlF;~X-f?ci+@QxWHe_hhj_N2^N&tk?V^?l$aLI{=h$d@ zq@J~;fnvJqVCL8Ywcx@G{bfqDSzuyuxeS=w7_Htyx30DyY-Ci z`H8@onij1rBA+I@hJseX(@8pJ&ao~%x<0K;Ink_4sQNM0VCG(cu1r>4lfiD;O@|B}JoTDro ze19U>IsNFwJs=7~m8bD21+HPQaZJ)8Mr_LHOS2Ptw7V?Gae4Yx)khGV)ik5=OhuE6 zow&nd1uG%9Y0g!>o^WM-=_U?b!K?n$+rpmB(~PlgF47_bZ-TeL1$p7M2?Re8H*(Pr zo!M~Eair($YOaLb`uafN;=G{psICcq>SawjC!)YQ5!=dgjPPv>?dWnqcE zTk|F&u3}X_IVYzG3@XZe&x(53Mw6wOBH@~uEpWd(1z1_XK}9WETSY~Myau4o9{~A- z!|P%*y$5xTY(mEfUW`dsZf|+fxid~0&u+=H4wgW#^=(zv>M9JbjrmufdlB>ct8Dz)f&D?YqT3$ zm+~x^tZ9f!n`zM2OAh?4HpLLDeMEcx34Y0E#he<*BVwBTr{r66!KgcIy+u~Zq;Xe>|UY_;W4hv~p{qlSRd1}p@maoaJ?lfMi4z8qfJ%^wXNUkA0uf%?Kh+p+Jt=eU9n-0 z*C#R)G35{XUtL1Ty$|Fwyp8EK`JkhB7=JJRC#+#xX*xymjUEe0;F*!3?Vj}gcrVi@ zhDG3UyjpEkXq5td^gZeC{oo~3NQW7Tn6$ol&8q9UDr-L7jO0082Nt<;Z#d5N;mlDm z1F|(M?w^tpBS7$T;v-g7RUvBH%qmt>%*?3kjl?SO4*-b9b=t6HGFL`G5gUSl$Ib|N zK6b0+{&;oWfxo^Ge~OB@OV&7CFO3oy^hQ5oeftYIJ{&$bRywVkEU_?5K^Nb7@3*J5 zX8>Y(-=42fhRi+DRfO!mqkxUIT#=*(g&Wxf!E4nI07Nbw-|@2XdG9s^jQ4s}qJ)q; zd?P(|qR!EN)_Peeb&JyeYQa*9KE0EPE@Q7DL2a>iB$qrdHd#ME&qiHCG4L4~tXvNo z9;iLESuKhdI}4qPih7lSR_@S$=go=;0@7rSIhyi}E9wr!7rxRSAs5g&GB!r>;cwhf ze4q2~8iSt5U}0uOtv-EED%R!YkltkF+NUbC7*5+~F7#c2L5RB*k*M6oD)%idrw6o# z{X-c4lPC~{EKepK5^qmy{fdNqlW%}bJ@fZTF^3`8r_dB7ZM%-RDc9$oh2eg^pjw(t zgyw*&a_+Ehv#7u{!Y(fRYtX9KrB~g}sc=U^{5@(ji{&xZw1L!n>?t^?)&1^$cRGPy zM>+_RpyBQHsmpSgWvl`;f}6r&J-XWJE+r+!wfVhr!NTMDqVL^u!1D6)7{7P=i=;^A z?dgvjiiG9Su!ATm~?W)^r4Ay5) z1q2GMW|hD5s%ucGcKv%QNPw^gE#~Fr6WfMrx#VnASwU-Gc;5Lu1GZ`8BjOVXiMAmnT93IP5i}0pS|g#J(Z_6TZsQo z&tb(jJ~?f>*Bw))H%n9&b-)&-e75&eiB^c}XtT9f|7|BS);CYY$Uz-iJw4&nT<(qE zFcqg;bD4XwdMZD;m?8*YpeHTb7Y=?fnZ?mx7%S(xdyS+`^bv3}Zo1{%*2ikNf- zAoX~$w)i5aT0C#z#Az7BqFavDzV_A(UpYsM%Rn0BiA9dmq0hK(*Z2v_&tkPDIVXoy zI2#$5>ly$-m?D?GRDwtKiO9<%0w)Z+gHeJkHr4(U0=?MV z1SoOOhRt;I&dy-`mp{LM2U`r=lZ(BFgA6Jw48&Ys#Q^zy@$?cgsS_mTFpHQJj6eI* z_Xdv*4~35?LGWEuQ**&28?Ss{<)5HxaAFq;x4GK+pM{J56DJP0kbc|=R}J=^*Vfx_ zZ>WcOg;LaFz{w@h5erVvC~uL=*vW2%1nv^Zf{pOpRJ0dz!dz`dS&~*tfV9{8(Zzf z($6mw4;(}29s0lG;?Mzlsp|jloB0<0!r3B=Y?2|P?Ia!g=L*KRYX5y_|96P!e}4&p zEb-^6N*dC&=vZEw`G4AZ_sHm#`!T$R1^Wm$p%5`mBWC6Z6WN1f;<@djV zd-Nazl*`kHQ|9A2K)%gFve*CY-|G)L)e>ML9?uQxUO9q2> zlLPgd_ZuI@RCX&nHvD9s6dHwG%C$hEeos%&`>Qd*pHd2Xo_SD&L^-d9S)}CTRJrv2 znUAwjHCbfpVhKFUHP8M%~8s6WR@qT-aczr2SE#9B6%pH9A zr)4jWWve?qXBVBho3{Z`mediNy?^?5KXlQISWe_P{T1JN*%lfe&O9Y>z{mgq6N{B5 zHqx>{M1s&2D@x=~(~Wwf0LJ7?^JM`%czb)ho3=AHHdZx{`k#7M9(01{a&3p`>TT9M z?>>wJj};mkssek`x7ibRFjK6)eZLhYu)7_rpoWtBkD)0HWm>tcY+Z=H&N>p*FO@1$ z!va;SMrZx|b=1KaIyTB5dwk1&m(%4&ltP4$ozsk_^H5V!Wwo^61BwC!qHuL}-QL}0cGw?>n%Yj(^V$HgFu$S#x%ht&_m@F& z1>OHIiX;$1@F0Nz!QCaeJHg!v?(P~wu;3n?AR&Z726uNG++l(a4ui`uoXPKbe&=6x z>QvoZ_sxB=tLDYb?(WsA*ZO{Xuii7S^mucEFa+^G(u3-6kv(Gl_Z=Oah=qG$yZNW0 z3SR1bD_7yY8Fi z@H_7yblUf$$A2zik4Ui{pnAhfj{!nG-n%%2oSMI%sHpA?7>2_ChT|xK@|gMrPVcrxPq)wA0=Kj;?PnOU0;8zCWK^7gEnc*o@R{UxL>;QH{#M#twqH26Iz zl-p^EQ%Omw$dG0zkrGtdw6XG+TMYpi2xH_TG-H-J<5kA64k6Comy zP{NQBMJ`@-J>3}1L2yVo|9X z(J)Gcpu`b5m)N-*e^N5+5S8gU}$K#_$17J{R12AgBeg$%&Y$H>U|kB!H(co;l_5!1fp#Bb`i8PRw1U%v{GhmZChg-J*t+?J;6 z0NfQCep}$ZV2~W>>FG&F@@AnWWG~BBuIhGwx_AT94u3dGNlVkVUAl=Ra}n6@5JxDD zyhpwHwj$H%9>Z82tdZ(G-hTBP*f!DC`#N`}E;0ng6hP7_2FqHC?9 z?=9)k8MF__7XJiFrfeuwkKsM}pT8|4ug14E@^?EoMmll+7D+ z*7Zyg)rTi|J4}irmZup)cxMpegGL{B@Pn6- z@9C#Zb`w(A#}L@vkIu@Um3(C1Iqc1)v`)YXmyiBeiR>X_-i>4$`s9sIK!9_%j=_-I z9Yqvjon28TD@4pg0tcThKhZarbo~<;nG=W0`cLZ=> zorgZ(hI*eaH#(D}ZBrpa*w)roz;k*= zMlHgiKW-dc0UY*#uE5=Ny~Fq7S5wkh8{!^23JHh_8iwkb1rH#UNy6~Q|IKA}rja<5 zsx~$@JI#$}R?ZHw;^95r2zSB0Une$plgzVD4M8Y>p+yL&zzlNNemNQYFn+q;CJ*1J zx7V6`)Y8)W7cyH~TCU-a8|{J!0WSa;W8uqKf!F-o>zYDu>hxO$f7JU=N-Ch7HG4uu z$VmeFPye16qxUZKATS^$C&#E-{hg2(qe>3H=#Fn0f-&r-3Zo0xftK=lM=W|(0MDSO z>&4W(JQG_K6)d^pWOL5pcK=Iih{FPbbM)hBTVfqz5(+^8s<*E{bHbocu@@H}3soj$ zf(f#2DBeEiLi+9hO|eCnzMIE&poD1EFaHY8$z0pHJdUA$htZRLEzVZDy1L{Vgs)z3 z=1d@=vzBVFt^_nIsHhCs)P1g7hF=|M07F2$ziVnr+1uOi2++{b`~oAKJ6Dwfp_r2W z{;Osed(VCEq7mdYTW1Rzh9c(p(D->Xit_MP_jIv5Wux!n4JF{uA8L{}atW5upH9({ zfXoK)y&l~HdEOf|RQlOY_;f*!ip2leOE%~SoSo7GF_j*Zf)Jz{r)BOvP=VSr{0aFG zaJ$sxYKCwJPZ=3oLqk%ZHc<~_ODJG797n9s|8kE@&8z$g(1q&LMxHYfxp?%l@b_=V zUtk>_oq3?kpMiD4{~80wa;rb)-(jb>#KfrQ<2)e5?M_OgEKGzLbrE4gOWteYm-A|) zcS4)biIS=5#

n*zj#D=gPxW(m^&6Gz~GNDzIts9XLQ{b+^| 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%0AMLkzAwj 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 z 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

    E%4~%z6Gh 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
    k9DBpeu-SqKQ}cVU^yrmRxZw`#4R%h3 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 z zT{IrVDwmtH0c3lgE-Kd+u0(0_XRpUT5@kH)R38y#Ib2niC8d?ka>1|eCvYQ<9(a{K zJs}E~xv_qM5}s`1aiNr;%Qo&c^k2gFN3wh_sP6&Tb)m1_R^y;M#Mx)`E)RHu55TMx zN;BvCjiqZxkZ=V3=qeO|c5UW4-XY9<3hoiF2`z=eTOw*enA7cQnox$T%8X3N(gqE+ zyd2cVqbzW9Q^A(q^%iZ5?MY$&{r#H{aJFN)q$&WOs)0lxk9r0jEi5hpJS@yHUMu7ic>psXNfN^C#~fQ7`Y0-42r-u|Kv2h63F!gH8dyI!XkT&9Os6=Yq=N z(p+)L_$pMe04`556y)?x`+h#9Qu?s-N$`Ye;^om&}QDSG2Z>brpH=eE(sWyeTx z5)Zgv9AV1-ikY}vpSZ-kl2th6=pK-jbw_B2Cs?BTC7RK3&E(< z5I-SCO}8(lrhZFmH#o4Ht4bD9?hGOw(*S+awjGe+oW%yE<>ERK!WsDdfGL23U~|)t zYYzPRo1c$lR!+7)f-t((Ev0p6l#f^bDVs|bU<|lwI`xX8)}mxHlfuD)10whfNE=0z_VA4V)rxf!`o<5-?T+_F*A@x*D)3))_v)Or#Wpg3EZkMb>4+4SR%#z{h!Zf4RjIZCOB_a#&^+`bayf|ONfrTZv zZXe?Ez9skalyhVz)~-17DH7g@lfHMcjP8!+U}hqItve zkeuVQ@k1W0oU^K_a55_FtFTD?yVLb%aBHjVunm_k%{<1t^fI2pL-;y`BL&?e0{4~Y z>UbSf{fu)v2eJlf5o%3J18!9(;!{un_)Zs~#kPM!OMVmP{T#TNIE@nJu*2U;{%P3i2kk8Wa zm7Ty{qi*zZnFPxY%Og}GOcUHrx+;+QH-qWlC%30VbPcH%sAAmJ?mDe65#$xhUM`_z z9GWf@BYz0x8$`Gna`w^AkvkOmssI<1b4Om)mtPBEUYb|!su7x#6UM)R9lM7DSm;UL zM8C;lE)KqJ!i`A=r9>-c;H`L7*h-%F3GF!>C7?~8_Rz%>@jv=)XMpGy3*a}62ZFUY zC|_%(Kn+R*>vkVfO1|W96^gUTH!trHTRr9ch}ay+*&N0SUMsa>{miW!RjKA2_w2g! z-$NPm7)$MO;BA* zZbJN?$wR$x(9ekJISQN-cn$l5M2byMhu-L-rB75CY8lY?vR3XX;nm~0a;5?PQT3Wy zk5?2J9CL@L+$lVS?rJ>ZIBdtidR0i)T^m92TsLPk_%1eRUK?-{-E1w8H2-IfikG0w zzC9U3&$4=�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

    C@vFRWoEF>~U> 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

    D%}bUE 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 WKBj3%lSZM98|ifVCe1iQ^Zy6$L9_b+ literal 0 HcmV?d00001 -- 2.34.1

    oglh;V4}tqG;ck-Snr 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�)IC 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