From 8000d11872bc5a44a174bd54322f35f406b9dfa6 Mon Sep 17 00:00:00 2001 From: grgr Date: Tue, 27 Sep 2022 22:18:08 +0200 Subject: [PATCH] first tutorial on one-to-many db: create a set of lists with a set of items each, plus post handle to add ne items --- .../Flask-2.2.2.dist-info/INSTALLER | 1 + .../Flask-2.2.2.dist-info/LICENSE.rst | 28 + .../Flask-2.2.2.dist-info/METADATA | 123 + .../Flask-2.2.2.dist-info/RECORD | 54 + .../Flask-2.2.2.dist-info/REQUESTED | 0 .../site-packages/Flask-2.2.2.dist-info/WHEEL | 5 + .../Flask-2.2.2.dist-info/entry_points.txt | 2 + .../Flask-2.2.2.dist-info/top_level.txt | 1 + .../Jinja2-3.1.2.dist-info/INSTALLER | 1 + .../Jinja2-3.1.2.dist-info/LICENSE.rst | 28 + .../Jinja2-3.1.2.dist-info/METADATA | 113 + .../Jinja2-3.1.2.dist-info/RECORD | 58 + .../Jinja2-3.1.2.dist-info/WHEEL | 5 + .../Jinja2-3.1.2.dist-info/entry_points.txt | 2 + .../Jinja2-3.1.2.dist-info/top_level.txt | 1 + .../MarkupSafe-2.1.1.dist-info/INSTALLER | 1 + .../MarkupSafe-2.1.1.dist-info/LICENSE.rst | 28 + .../MarkupSafe-2.1.1.dist-info/METADATA | 101 + .../MarkupSafe-2.1.1.dist-info/RECORD | 14 + .../MarkupSafe-2.1.1.dist-info/WHEEL | 5 + .../MarkupSafe-2.1.1.dist-info/top_level.txt | 1 + .../Werkzeug-2.2.2.dist-info/INSTALLER | 1 + .../Werkzeug-2.2.2.dist-info/LICENSE.rst | 28 + .../Werkzeug-2.2.2.dist-info/METADATA | 126 + .../Werkzeug-2.2.2.dist-info/RECORD | 98 + .../Werkzeug-2.2.2.dist-info/WHEEL | 5 + .../Werkzeug-2.2.2.dist-info/top_level.txt | 1 + .../click-8.1.3.dist-info/INSTALLER | 1 + .../click-8.1.3.dist-info/LICENSE.rst | 28 + .../click-8.1.3.dist-info/METADATA | 111 + .../click-8.1.3.dist-info/RECORD | 39 + .../site-packages/click-8.1.3.dist-info/WHEEL | 5 + .../click-8.1.3.dist-info/top_level.txt | 1 + .vtodo/Lib/site-packages/click/__init__.py | 73 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 2614 bytes .../click/__pycache__/_compat.cpython-310.pyc | Bin 0 -> 15752 bytes .../__pycache__/_termui_impl.cpython-310.pyc | Bin 0 -> 16073 bytes .../__pycache__/_textwrap.cpython-310.pyc | Bin 0 -> 1548 bytes .../__pycache__/_winconsole.cpython-310.pyc | Bin 0 -> 7666 bytes .../click/__pycache__/core.cpython-310.pyc | Bin 0 -> 90243 bytes .../__pycache__/decorators.cpython-310.pyc | Bin 0 -> 15614 bytes .../__pycache__/exceptions.cpython-310.pyc | Bin 0 -> 10196 bytes .../__pycache__/formatting.cpython-310.pyc | Bin 0 -> 9459 bytes .../click/__pycache__/globals.cpython-310.pyc | Bin 0 -> 2433 bytes .../click/__pycache__/parser.cpython-310.pyc | Bin 0 -> 13676 bytes .../shell_completion.cpython-310.pyc | Bin 0 -> 16587 bytes .../click/__pycache__/termui.cpython-310.pyc | Bin 0 -> 26217 bytes .../click/__pycache__/testing.cpython-310.pyc | Bin 0 -> 15195 bytes .../click/__pycache__/types.cpython-310.pyc | Bin 0 -> 33244 bytes .../click/__pycache__/utils.cpython-310.pyc | Bin 0 -> 17626 bytes .vtodo/Lib/site-packages/click/_compat.py | 626 ++++ .../Lib/site-packages/click/_termui_impl.py | 717 ++++ .vtodo/Lib/site-packages/click/_textwrap.py | 49 + .vtodo/Lib/site-packages/click/_winconsole.py | 279 ++ .vtodo/Lib/site-packages/click/core.py | 2998 ++++++++++++++++ .vtodo/Lib/site-packages/click/decorators.py | 497 +++ .vtodo/Lib/site-packages/click/exceptions.py | 287 ++ .vtodo/Lib/site-packages/click/formatting.py | 301 ++ .vtodo/Lib/site-packages/click/globals.py | 68 + .vtodo/Lib/site-packages/click/parser.py | 529 +++ .vtodo/Lib/site-packages/click/py.typed | 0 .../site-packages/click/shell_completion.py | 580 ++++ .vtodo/Lib/site-packages/click/termui.py | 787 +++++ .vtodo/Lib/site-packages/click/testing.py | 479 +++ .vtodo/Lib/site-packages/click/types.py | 1073 ++++++ .vtodo/Lib/site-packages/click/utils.py | 580 ++++ .../colorama-0.4.5.dist-info/INSTALLER | 1 + .../colorama-0.4.5.dist-info/LICENSE.txt | 27 + .../colorama-0.4.5.dist-info/METADATA | 411 +++ .../colorama-0.4.5.dist-info/RECORD | 18 + .../colorama-0.4.5.dist-info/WHEEL | 6 + .../colorama-0.4.5.dist-info/top_level.txt | 1 + .vtodo/Lib/site-packages/colorama/__init__.py | 6 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 424 bytes .../colorama/__pycache__/ansi.cpython-310.pyc | Bin 0 -> 2985 bytes .../__pycache__/ansitowin32.cpython-310.pyc | Bin 0 -> 8188 bytes .../__pycache__/initialise.cpython-310.pyc | Bin 0 -> 1671 bytes .../__pycache__/win32.cpython-310.pyc | Bin 0 -> 3932 bytes .../__pycache__/winterm.cpython-310.pyc | Bin 0 -> 4548 bytes .vtodo/Lib/site-packages/colorama/ansi.py | 102 + .../Lib/site-packages/colorama/ansitowin32.py | 266 ++ .../Lib/site-packages/colorama/initialise.py | 80 + .vtodo/Lib/site-packages/colorama/win32.py | 152 + .vtodo/Lib/site-packages/colorama/winterm.py | 169 + .vtodo/Lib/site-packages/flask/__init__.py | 71 + .vtodo/Lib/site-packages/flask/__main__.py | 3 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 2382 bytes .../__pycache__/__main__.cpython-310.pyc | Bin 0 -> 210 bytes .../flask/__pycache__/app.cpython-310.pyc | Bin 0 -> 74896 bytes .../__pycache__/blueprints.cpython-310.pyc | Bin 0 -> 24187 bytes .../flask/__pycache__/cli.cpython-310.pyc | Bin 0 -> 26875 bytes .../flask/__pycache__/config.cpython-310.pyc | Bin 0 -> 12427 bytes .../flask/__pycache__/ctx.cpython-310.pyc | Bin 0 -> 14461 bytes .../__pycache__/debughelpers.cpython-310.pyc | Bin 0 -> 5924 bytes .../flask/__pycache__/globals.cpython-310.pyc | Bin 0 -> 3300 bytes .../flask/__pycache__/helpers.cpython-310.pyc | Bin 0 -> 24017 bytes .../flask/__pycache__/logging.cpython-310.pyc | Bin 0 -> 2453 bytes .../__pycache__/scaffold.cpython-310.pyc | Bin 0 -> 24712 bytes .../__pycache__/sessions.cpython-310.pyc | Bin 0 -> 13613 bytes .../flask/__pycache__/signals.cpython-310.pyc | Bin 0 -> 2389 bytes .../__pycache__/templating.cpython-310.pyc | Bin 0 -> 6957 bytes .../flask/__pycache__/testing.cpython-310.pyc | Bin 0 -> 9399 bytes .../flask/__pycache__/typing.cpython-310.pyc | Bin 0 -> 1673 bytes .../flask/__pycache__/views.cpython-310.pyc | Bin 0 -> 5389 bytes .../__pycache__/wrappers.cpython-310.pyc | Bin 0 -> 5076 bytes .vtodo/Lib/site-packages/flask/app.py | 2548 ++++++++++++++ .vtodo/Lib/site-packages/flask/blueprints.py | 706 ++++ .vtodo/Lib/site-packages/flask/cli.py | 1051 ++++++ .vtodo/Lib/site-packages/flask/config.py | 337 ++ .vtodo/Lib/site-packages/flask/ctx.py | 438 +++ .../Lib/site-packages/flask/debughelpers.py | 158 + .vtodo/Lib/site-packages/flask/globals.py | 107 + .vtodo/Lib/site-packages/flask/helpers.py | 705 ++++ .../Lib/site-packages/flask/json/__init__.py | 342 ++ .../json/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 11498 bytes .../json/__pycache__/provider.cpython-310.pyc | Bin 0 -> 9344 bytes .../json/__pycache__/tag.cpython-310.pyc | Bin 0 -> 10996 bytes .../Lib/site-packages/flask/json/provider.py | 310 ++ .vtodo/Lib/site-packages/flask/json/tag.py | 312 ++ .vtodo/Lib/site-packages/flask/logging.py | 74 + .vtodo/Lib/site-packages/flask/py.typed | 0 .vtodo/Lib/site-packages/flask/scaffold.py | 898 +++++ .vtodo/Lib/site-packages/flask/sessions.py | 419 +++ .vtodo/Lib/site-packages/flask/signals.py | 56 + .vtodo/Lib/site-packages/flask/templating.py | 212 ++ .vtodo/Lib/site-packages/flask/testing.py | 286 ++ .vtodo/Lib/site-packages/flask/typing.py | 80 + .vtodo/Lib/site-packages/flask/views.py | 188 + .vtodo/Lib/site-packages/flask/wrappers.py | 171 + .../itsdangerous-2.1.2.dist-info/INSTALLER | 1 + .../itsdangerous-2.1.2.dist-info/LICENSE.rst | 28 + .../itsdangerous-2.1.2.dist-info/METADATA | 97 + .../itsdangerous-2.1.2.dist-info/RECORD | 23 + .../itsdangerous-2.1.2.dist-info/WHEEL | 5 + .../top_level.txt | 1 + .../site-packages/itsdangerous/__init__.py | 19 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 863 bytes .../__pycache__/_json.cpython-310.pyc | Bin 0 -> 918 bytes .../__pycache__/encoding.cpython-310.pyc | Bin 0 -> 1868 bytes .../__pycache__/exc.cpython-310.pyc | Bin 0 -> 3406 bytes .../__pycache__/serializer.cpython-310.pyc | Bin 0 -> 9697 bytes .../__pycache__/signer.cpython-310.pyc | Bin 0 -> 8466 bytes .../__pycache__/timed.cpython-310.pyc | Bin 0 -> 6476 bytes .../__pycache__/url_safe.cpython-310.pyc | Bin 0 -> 2691 bytes .../Lib/site-packages/itsdangerous/_json.py | 16 + .../site-packages/itsdangerous/encoding.py | 54 + .vtodo/Lib/site-packages/itsdangerous/exc.py | 107 + .../Lib/site-packages/itsdangerous/py.typed | 0 .../site-packages/itsdangerous/serializer.py | 295 ++ .../Lib/site-packages/itsdangerous/signer.py | 257 ++ .../Lib/site-packages/itsdangerous/timed.py | 234 ++ .../site-packages/itsdangerous/url_safe.py | 80 + .vtodo/Lib/site-packages/jinja2/__init__.py | 37 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 1599 bytes .../__pycache__/_identifier.cpython-310.pyc | Bin 0 -> 2074 bytes .../__pycache__/async_utils.cpython-310.pyc | Bin 0 -> 2711 bytes .../__pycache__/bccache.cpython-310.pyc | Bin 0 -> 13958 bytes .../__pycache__/compiler.cpython-310.pyc | Bin 0 -> 54566 bytes .../__pycache__/constants.cpython-310.pyc | Bin 0 -> 1535 bytes .../jinja2/__pycache__/debug.cpython-310.pyc | Bin 0 -> 3991 bytes .../__pycache__/defaults.cpython-310.pyc | Bin 0 -> 1335 bytes .../__pycache__/environment.cpython-310.pyc | Bin 0 -> 53411 bytes .../__pycache__/exceptions.cpython-310.pyc | Bin 0 -> 5534 bytes .../jinja2/__pycache__/ext.cpython-310.pyc | Bin 0 -> 25700 bytes .../__pycache__/filters.cpython-310.pyc | Bin 0 -> 51224 bytes .../__pycache__/idtracking.cpython-310.pyc | Bin 0 -> 11086 bytes .../jinja2/__pycache__/lexer.cpython-310.pyc | Bin 0 -> 20441 bytes .../__pycache__/loaders.cpython-310.pyc | Bin 0 -> 20524 bytes .../jinja2/__pycache__/meta.cpython-310.pyc | Bin 0 -> 3810 bytes .../__pycache__/nativetypes.cpython-310.pyc | Bin 0 -> 5011 bytes .../jinja2/__pycache__/nodes.cpython-310.pyc | Bin 0 -> 40326 bytes .../__pycache__/optimizer.cpython-310.pyc | Bin 0 -> 1956 bytes .../jinja2/__pycache__/parser.cpython-310.pyc | Bin 0 -> 27693 bytes .../__pycache__/runtime.cpython-310.pyc | Bin 0 -> 32170 bytes .../__pycache__/sandbox.cpython-310.pyc | Bin 0 -> 11980 bytes .../jinja2/__pycache__/tests.cpython-310.pyc | Bin 0 -> 6711 bytes .../jinja2/__pycache__/utils.cpython-310.pyc | Bin 0 -> 24531 bytes .../__pycache__/visitor.cpython-310.pyc | Bin 0 -> 3975 bytes .../Lib/site-packages/jinja2/_identifier.py | 6 + .../Lib/site-packages/jinja2/async_utils.py | 84 + .vtodo/Lib/site-packages/jinja2/bccache.py | 406 +++ .vtodo/Lib/site-packages/jinja2/compiler.py | 1957 +++++++++++ .vtodo/Lib/site-packages/jinja2/constants.py | 20 + .vtodo/Lib/site-packages/jinja2/debug.py | 191 ++ .vtodo/Lib/site-packages/jinja2/defaults.py | 48 + .../Lib/site-packages/jinja2/environment.py | 1667 +++++++++ .vtodo/Lib/site-packages/jinja2/exceptions.py | 166 + .vtodo/Lib/site-packages/jinja2/ext.py | 859 +++++ .vtodo/Lib/site-packages/jinja2/filters.py | 1840 ++++++++++ .vtodo/Lib/site-packages/jinja2/idtracking.py | 318 ++ .vtodo/Lib/site-packages/jinja2/lexer.py | 866 +++++ .vtodo/Lib/site-packages/jinja2/loaders.py | 661 ++++ .vtodo/Lib/site-packages/jinja2/meta.py | 111 + .../Lib/site-packages/jinja2/nativetypes.py | 130 + .vtodo/Lib/site-packages/jinja2/nodes.py | 1204 +++++++ .vtodo/Lib/site-packages/jinja2/optimizer.py | 47 + .vtodo/Lib/site-packages/jinja2/parser.py | 1032 ++++++ .vtodo/Lib/site-packages/jinja2/py.typed | 0 .vtodo/Lib/site-packages/jinja2/runtime.py | 1053 ++++++ .vtodo/Lib/site-packages/jinja2/sandbox.py | 428 +++ .vtodo/Lib/site-packages/jinja2/tests.py | 255 ++ .vtodo/Lib/site-packages/jinja2/utils.py | 755 ++++ .vtodo/Lib/site-packages/jinja2/visitor.py | 92 + .../Lib/site-packages/markupsafe/__init__.py | 295 ++ .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 10633 bytes .../__pycache__/_native.cpython-310.pyc | Bin 0 -> 2001 bytes .../Lib/site-packages/markupsafe/_native.py | 63 + .../Lib/site-packages/markupsafe/_speedups.c | 320 ++ .../markupsafe/_speedups.cp310-win_amd64.pyd | Bin 0 -> 15872 bytes .../site-packages/markupsafe/_speedups.pyi | 9 + .vtodo/Lib/site-packages/markupsafe/py.typed | 0 .../nano-0.10.0.dist-info/DESCRIPTION.rst | 93 + .../nano-0.10.0.dist-info/INSTALLER | 1 + .../nano-0.10.0.dist-info/METADATA | 118 + .../nano-0.10.0.dist-info/RECORD | 250 ++ .../nano-0.10.0.dist-info/REQUESTED | 0 .../site-packages/nano-0.10.0.dist-info/WHEEL | 6 + .../nano-0.10.0.dist-info/metadata.json | 1 + .../nano-0.10.0.dist-info/top_level.txt | 1 + .vtodo/Lib/site-packages/nano/__init__.py | 0 .../nano/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 169 bytes .../site-packages/nano/activation/__init__.py | 51 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 1597 bytes .../__pycache__/admin.cpython-310.pyc | Bin 0 -> 617 bytes .../__pycache__/apps.cpython-310.pyc | Bin 0 -> 451 bytes .../__pycache__/forms.cpython-310.pyc | Bin 0 -> 415 bytes .../__pycache__/models.cpython-310.pyc | Bin 0 -> 3519 bytes .../__pycache__/signals.cpython-310.pyc | Bin 0 -> 298 bytes .../__pycache__/tests.cpython-310.pyc | Bin 0 -> 5829 bytes .../__pycache__/urls.cpython-310.pyc | Bin 0 -> 508 bytes .../__pycache__/views.cpython-310.pyc | Bin 0 -> 1398 bytes .../site-packages/nano/activation/admin.py | 11 + .../Lib/site-packages/nano/activation/apps.py | 5 + .../site-packages/nano/activation/forms.py | 5 + .../activation/migrations/0001_initial.py | 36 + .../nano/activation/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-310.pyc | Bin 0 -> 1310 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 191 bytes .../site-packages/nano/activation/models.py | 85 + .../site-packages/nano/activation/signals.py | 2 + .../templates/nano/activation/activated.html | 4 + .../nano/activation/activation_form.html | 8 + .../templates/nano/activation/base.html | 1 + .../site-packages/nano/activation/tests.py | 148 + .../Lib/site-packages/nano/activation/urls.py | 12 + .../site-packages/nano/activation/views.py | 37 + .../Lib/site-packages/nano/badge/__init__.py | 11 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 627 bytes .../badge/__pycache__/admin.cpython-310.pyc | Bin 0 -> 570 bytes .../badge/__pycache__/apps.cpython-310.pyc | Bin 0 -> 431 bytes .../badge/__pycache__/models.cpython-310.pyc | Bin 0 -> 2090 bytes .../badge/__pycache__/urls.cpython-310.pyc | Bin 0 -> 388 bytes .../badge/__pycache__/views.cpython-310.pyc | Bin 0 -> 1068 bytes .vtodo/Lib/site-packages/nano/badge/admin.py | 10 + .vtodo/Lib/site-packages/nano/badge/apps.py | 5 + .../nano/badge/migrations/0001_initial.py | 31 + .../nano/badge/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-310.pyc | Bin 0 -> 1068 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 186 bytes .vtodo/Lib/site-packages/nano/badge/models.py | 43 + .../templates/badge/badge_list_frag.html | 8 + .../nano/badge/templates/show_badge_sum.html | 0 .../nano/badge/templatetags/__init__.py | 0 .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 188 bytes .../__pycache__/badge_tags.cpython-310.pyc | Bin 0 -> 3005 bytes .../nano/badge/templatetags/badge_tags.py | 102 + .vtodo/Lib/site-packages/nano/badge/urls.py | 8 + .vtodo/Lib/site-packages/nano/badge/views.py | 22 + .../Lib/site-packages/nano/blog/__init__.py | 1 + .../blog/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 226 bytes .../blog/__pycache__/admin.cpython-310.pyc | Bin 0 -> 1096 bytes .../blog/__pycache__/apps.cpython-310.pyc | Bin 0 -> 427 bytes .../blog/__pycache__/models.cpython-310.pyc | Bin 0 -> 1171 bytes .../blog/__pycache__/settings.cpython-310.pyc | Bin 0 -> 437 bytes .../blog/__pycache__/tests.cpython-310.pyc | Bin 0 -> 1278 bytes .../blog/__pycache__/tools.cpython-310.pyc | Bin 0 -> 1932 bytes .../blog/__pycache__/urls.cpython-310.pyc | Bin 0 -> 618 bytes .../blog/__pycache__/views.cpython-310.pyc | Bin 0 -> 2351 bytes .vtodo/Lib/site-packages/nano/blog/admin.py | 30 + .vtodo/Lib/site-packages/nano/blog/apps.py | 5 + .../nano/blog/migrations/0001_initial.py | 31 + .../nano/blog/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-310.pyc | Bin 0 -> 930 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 185 bytes .vtodo/Lib/site-packages/nano/blog/models.py | 31 + .../Lib/site-packages/nano/blog/settings.py | 13 + .../blog/templates/blog/entry_archive.html | 23 + .../templates/blog/entry_archive_day.html | 12 + .../templates/blog/entry_archive_month.html | 30 + .../templates/blog/entry_archive_year.html | 21 + .../nano/blog/templates/blog/entry_list.html | 18 + .../blog/templates/blog/entry_with_day.html | 2 + .../templates/blog/entry_with_fulldate.html | 2 + .../blog/entry_with_fulldatetime.html | 2 + .../templates/blog/entry_with_monthday.html | 2 + .../blog/templates/blog/entry_with_time.html | 2 + .../nano/blog/templates/blog/new_user.html | 1 + .vtodo/Lib/site-packages/nano/blog/tests.py | 25 + .vtodo/Lib/site-packages/nano/blog/tools.py | 50 + .vtodo/Lib/site-packages/nano/blog/urls.py | 12 + .vtodo/Lib/site-packages/nano/blog/views.py | 53 + .../Lib/site-packages/nano/chunk/__init__.py | 1 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 229 bytes .../chunk/__pycache__/admin.cpython-310.pyc | Bin 0 -> 556 bytes .../chunk/__pycache__/apps.cpython-310.pyc | Bin 0 -> 431 bytes .../chunk/__pycache__/loader.cpython-310.pyc | Bin 0 -> 279 bytes .../chunk/__pycache__/loaders.cpython-310.pyc | Bin 0 -> 1076 bytes .../chunk/__pycache__/models.cpython-310.pyc | Bin 0 -> 858 bytes .../chunk/__pycache__/tests.cpython-310.pyc | Bin 0 -> 1586 bytes .vtodo/Lib/site-packages/nano/chunk/admin.py | 9 + .vtodo/Lib/site-packages/nano/chunk/apps.py | 5 + .vtodo/Lib/site-packages/nano/chunk/loader.py | 4 + .../Lib/site-packages/nano/chunk/loaders.py | 22 + .../nano/chunk/migrations/0001_initial.py | 27 + .../nano/chunk/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-310.pyc | Bin 0 -> 786 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 186 bytes .vtodo/Lib/site-packages/nano/chunk/models.py | 16 + .vtodo/Lib/site-packages/nano/chunk/tests.py | 30 + .../site-packages/nano/comments/__init__.py | 5 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 340 bytes .../__pycache__/admin.cpython-310.pyc | Bin 0 -> 642 bytes .../comments/__pycache__/apps.cpython-310.pyc | Bin 0 -> 443 bytes .../__pycache__/forms.cpython-310.pyc | Bin 0 -> 1991 bytes .../__pycache__/models.cpython-310.pyc | Bin 0 -> 2934 bytes .../comments/__pycache__/urls.cpython-310.pyc | Bin 0 -> 603 bytes .../__pycache__/views.cpython-310.pyc | Bin 0 -> 3601 bytes .../Lib/site-packages/nano/comments/admin.py | 12 + .../Lib/site-packages/nano/comments/apps.py | 5 + .../Lib/site-packages/nano/comments/forms.py | 39 + .../nano/comments/migrations/0001_initial.py | 42 + .../nano/comments/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-310.pyc | Bin 0 -> 1752 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 189 bytes .../Lib/site-packages/nano/comments/models.py | 62 + .../templates/nano/comments/comment_form.html | 18 + .../nano/comments/comment_form_frag.html | 14 + .../templates/nano/comments/comment_list.html | 10 + .../nano/comments/templatetags/__init__.py | 0 .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 191 bytes .../__pycache__/comments_tags.cpython-310.pyc | Bin 0 -> 1685 bytes .../comments/templatetags/comments_tags.py | 40 + .../comments/tests/migrations/0001_initial.py | 23 + .../comments/tests/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-310.pyc | Bin 0 -> 738 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 195 bytes .../Lib/site-packages/nano/comments/urls.py | 16 + .../Lib/site-packages/nano/comments/views.py | 124 + .../site-packages/nano/countries/__init__.py | 1 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 241 bytes .../__pycache__/admin.cpython-310.pyc | Bin 0 -> 513 bytes .../__pycache__/apps.cpython-310.pyc | Bin 0 -> 447 bytes .../__pycache__/models.cpython-310.pyc | Bin 0 -> 1151 bytes .../__pycache__/tests.cpython-310.pyc | Bin 0 -> 846 bytes .../Lib/site-packages/nano/countries/admin.py | 9 + .../Lib/site-packages/nano/countries/apps.py | 5 + .../nano/countries/migrations/0001_initial.py | 28 + .../nano/countries/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-310.pyc | Bin 0 -> 830 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 190 bytes .../site-packages/nano/countries/models.py | 23 + .../Lib/site-packages/nano/countries/tests.py | 13 + .vtodo/Lib/site-packages/nano/faq/__init__.py | 1 + .../faq/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 223 bytes .../faq/__pycache__/admin.cpython-310.pyc | Bin 0 -> 525 bytes .../nano/faq/__pycache__/apps.cpython-310.pyc | Bin 0 -> 423 bytes .../faq/__pycache__/models.cpython-310.pyc | Bin 0 -> 1204 bytes .../faq/__pycache__/tests.cpython-310.pyc | Bin 0 -> 1331 bytes .../nano/faq/__pycache__/urls.cpython-310.pyc | Bin 0 -> 297 bytes .../faq/__pycache__/views.cpython-310.pyc | Bin 0 -> 770 bytes .vtodo/Lib/site-packages/nano/faq/admin.py | 10 + .vtodo/Lib/site-packages/nano/faq/apps.py | 5 + .../nano/faq/migrations/0001_initial.py | 29 + .../nano/faq/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-310.pyc | Bin 0 -> 945 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 184 bytes .vtodo/Lib/site-packages/nano/faq/models.py | 21 + .vtodo/Lib/site-packages/nano/faq/tests.py | 25 + .vtodo/Lib/site-packages/nano/faq/urls.py | 8 + .vtodo/Lib/site-packages/nano/faq/views.py | 13 + .../Lib/site-packages/nano/mark/__init__.py | 1 + .../mark/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 226 bytes .../mark/__pycache__/admin.cpython-310.pyc | Bin 0 -> 832 bytes .../mark/__pycache__/apps.cpython-310.pyc | Bin 0 -> 427 bytes .../mark/__pycache__/managers.cpython-310.pyc | Bin 0 -> 2523 bytes .../mark/__pycache__/models.cpython-310.pyc | Bin 0 -> 4118 bytes .../mark/__pycache__/urls.cpython-310.pyc | Bin 0 -> 324 bytes .../mark/__pycache__/views.cpython-310.pyc | Bin 0 -> 2803 bytes .vtodo/Lib/site-packages/nano/mark/admin.py | 18 + .vtodo/Lib/site-packages/nano/mark/apps.py | 5 + .../Lib/site-packages/nano/mark/managers.py | 50 + .../nano/mark/migrations/0001_initial.py | 56 + .../nano/mark/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-310.pyc | Bin 0 -> 1842 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 185 bytes .vtodo/Lib/site-packages/nano/mark/models.py | 94 + .../nano/mark/templates/nano/mark/mark.html | 15 + .../mark/templates/nano/mark/mark_faved.html | 15 + .../nano/mark/templatetags/__init__.py | 0 .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 187 bytes .../nano_mark_tags.cpython-310.pyc | Bin 0 -> 1221 bytes .../nano/mark/templatetags/nano_mark_tags.py | 33 + .vtodo/Lib/site-packages/nano/mark/urls.py | 8 + .vtodo/Lib/site-packages/nano/mark/views.py | 85 + .../site-packages/nano/privmsg/__init__.py | 1 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 235 bytes .../privmsg/__pycache__/admin.cpython-310.pyc | Bin 0 -> 699 bytes .../privmsg/__pycache__/apps.cpython-310.pyc | Bin 0 -> 439 bytes .../privmsg/__pycache__/forms.cpython-310.pyc | Bin 0 -> 876 bytes .../__pycache__/models.cpython-310.pyc | Bin 0 -> 2817 bytes .../privmsg/__pycache__/tests.cpython-310.pyc | Bin 0 -> 2224 bytes .../privmsg/__pycache__/urls.cpython-310.pyc | Bin 0 -> 687 bytes .../privmsg/__pycache__/views.cpython-310.pyc | Bin 0 -> 4272 bytes .../Lib/site-packages/nano/privmsg/admin.py | 12 + .vtodo/Lib/site-packages/nano/privmsg/apps.py | 5 + .../Lib/site-packages/nano/privmsg/forms.py | 13 + .../nano/privmsg/migrations/0001_initial.py | 39 + .../nano/privmsg/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-310.pyc | Bin 0 -> 1480 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 188 bytes .../Lib/site-packages/nano/privmsg/models.py | 92 + .../nano/privmsg/templates/privmsg/add.html | 14 + .../privmsg/templates/privmsg/archive.html | 22 + .../templates/privmsg/batch_archive.html | 25 + .../templates/privmsg/message.frag.html | 2 + .../Lib/site-packages/nano/privmsg/tests.py | 51 + .vtodo/Lib/site-packages/nano/privmsg/urls.py | 17 + .../Lib/site-packages/nano/privmsg/views.py | 157 + .../Lib/site-packages/nano/tools/__init__.py | 75 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 2546 bytes .../tools/__pycache__/apps.cpython-310.pyc | Bin 0 -> 431 bytes .../__pycache__/middleware.cpython-310.pyc | Bin 0 -> 689 bytes .../tools/__pycache__/models.cpython-310.pyc | Bin 0 -> 6409 bytes .vtodo/Lib/site-packages/nano/tools/apps.py | 5 + .../site-packages/nano/tools/middleware.py | 11 + .vtodo/Lib/site-packages/nano/tools/models.py | 144 + .../nano/tools/templates/come_back.html | 1 + .../nano/tools/templatetags/__init__.py | 0 .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 188 bytes .../__pycache__/nano_tags.cpython-310.pyc | Bin 0 -> 2530 bytes .../nano/tools/templatetags/nano_tags.py | 89 + .../tools/tests/migrations/0001_initial.py | 32 + .../nano/tools/tests/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-310.pyc | Bin 0 -> 1107 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 192 bytes .../Lib/site-packages/nano/user/__init__.py | 5 + .../user/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 344 bytes .../user/__pycache__/apps.cpython-310.pyc | Bin 0 -> 427 bytes .../user/__pycache__/forms.cpython-310.pyc | Bin 0 -> 2459 bytes .../user/__pycache__/tests.cpython-310.pyc | Bin 0 -> 877 bytes .../user/__pycache__/urls.cpython-310.pyc | Bin 0 -> 716 bytes .../user/__pycache__/views.cpython-310.pyc | Bin 0 -> 5538 bytes .vtodo/Lib/site-packages/nano/user/apps.py | 5 + .vtodo/Lib/site-packages/nano/user/forms.py | 53 + .../user/templates/nano/user/signup_done.html | 8 + .../user/templates/password_change_form.html | 16 + .../nano/user/templates/password_reset.txt | 9 + .../user/templates/password_reset_form.html | 14 + .vtodo/Lib/site-packages/nano/user/tests.py | 15 + .vtodo/Lib/site-packages/nano/user/urls.py | 17 + .vtodo/Lib/site-packages/nano/user/views.py | 192 ++ .vtodo/Lib/site-packages/werkzeug/__init__.py | 6 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 347 bytes .../__pycache__/_internal.cpython-310.pyc | Bin 0 -> 17242 bytes .../__pycache__/_reloader.cpython-310.pyc | Bin 0 -> 12370 bytes .../datastructures.cpython-310.pyc | Bin 0 -> 104654 bytes .../__pycache__/exceptions.cpython-310.pyc | Bin 0 -> 27720 bytes .../__pycache__/formparser.cpython-310.pyc | Bin 0 -> 13222 bytes .../werkzeug/__pycache__/http.cpython-310.pyc | Bin 0 -> 36916 bytes .../__pycache__/local.cpython-310.pyc | Bin 0 -> 20711 bytes .../__pycache__/security.cpython-310.pyc | Bin 0 -> 4875 bytes .../__pycache__/serving.cpython-310.pyc | Bin 0 -> 30103 bytes .../werkzeug/__pycache__/test.cpython-310.pyc | Bin 0 -> 39294 bytes .../__pycache__/testapp.cpython-310.pyc | Bin 0 -> 9554 bytes .../werkzeug/__pycache__/urls.cpython-310.pyc | Bin 0 -> 32563 bytes .../__pycache__/user_agent.cpython-310.pyc | Bin 0 -> 1834 bytes .../__pycache__/utils.cpython-310.pyc | Bin 0 -> 21705 bytes .../werkzeug/__pycache__/wsgi.cpython-310.pyc | Bin 0 -> 31380 bytes .../Lib/site-packages/werkzeug/_internal.py | 548 +++ .../Lib/site-packages/werkzeug/_reloader.py | 446 +++ .../site-packages/werkzeug/datastructures.py | 3040 +++++++++++++++++ .../site-packages/werkzeug/datastructures.pyi | 921 +++++ .../site-packages/werkzeug/debug/__init__.py | 533 +++ .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 14245 bytes .../debug/__pycache__/console.cpython-310.pyc | Bin 0 -> 8142 bytes .../debug/__pycache__/repr.cpython-310.pyc | Bin 0 -> 8884 bytes .../debug/__pycache__/tbtools.cpython-310.pyc | Bin 0 -> 11535 bytes .../site-packages/werkzeug/debug/console.py | 222 ++ .../Lib/site-packages/werkzeug/debug/repr.py | 285 ++ .../werkzeug/debug/shared/ICON_LICENSE.md | 6 + .../werkzeug/debug/shared/console.png | Bin 0 -> 507 bytes .../werkzeug/debug/shared/debugger.js | 359 ++ .../werkzeug/debug/shared/less.png | Bin 0 -> 191 bytes .../werkzeug/debug/shared/more.png | Bin 0 -> 200 bytes .../werkzeug/debug/shared/style.css | 150 + .../site-packages/werkzeug/debug/tbtools.py | 435 +++ .../Lib/site-packages/werkzeug/exceptions.py | 884 +++++ .../Lib/site-packages/werkzeug/formparser.py | 455 +++ .vtodo/Lib/site-packages/werkzeug/http.py | 1311 +++++++ .vtodo/Lib/site-packages/werkzeug/local.py | 648 ++++ .../werkzeug/middleware/__init__.py | 22 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 692 bytes .../__pycache__/dispatcher.cpython-310.pyc | Bin 0 -> 2776 bytes .../__pycache__/http_proxy.cpython-310.pyc | Bin 0 -> 6867 bytes .../__pycache__/lint.cpython-310.pyc | Bin 0 -> 12740 bytes .../__pycache__/profiler.cpython-310.pyc | Bin 0 -> 4983 bytes .../__pycache__/proxy_fix.cpython-310.pyc | Bin 0 -> 6195 bytes .../__pycache__/shared_data.cpython-310.pyc | Bin 0 -> 9150 bytes .../werkzeug/middleware/dispatcher.py | 78 + .../werkzeug/middleware/http_proxy.py | 230 ++ .../site-packages/werkzeug/middleware/lint.py | 420 +++ .../werkzeug/middleware/profiler.py | 139 + .../werkzeug/middleware/proxy_fix.py | 187 + .../werkzeug/middleware/shared_data.py | 280 ++ .vtodo/Lib/site-packages/werkzeug/py.typed | 0 .../werkzeug/routing/__init__.py | 133 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 4622 bytes .../__pycache__/converters.cpython-310.pyc | Bin 0 -> 8739 bytes .../__pycache__/exceptions.cpython-310.pyc | Bin 0 -> 5489 bytes .../routing/__pycache__/map.cpython-310.pyc | Bin 0 -> 30978 bytes .../__pycache__/matcher.cpython-310.pyc | Bin 0 -> 4533 bytes .../routing/__pycache__/rules.cpython-310.pyc | Bin 0 -> 27060 bytes .../werkzeug/routing/converters.py | 257 ++ .../werkzeug/routing/exceptions.py | 146 + .../Lib/site-packages/werkzeug/routing/map.py | 944 +++++ .../site-packages/werkzeug/routing/matcher.py | 185 + .../site-packages/werkzeug/routing/rules.py | 879 +++++ .../site-packages/werkzeug/sansio/__init__.py | 0 .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 180 bytes .../sansio/__pycache__/http.cpython-310.pyc | Bin 0 -> 3867 bytes .../__pycache__/multipart.cpython-310.pyc | Bin 0 -> 6634 bytes .../__pycache__/request.cpython-310.pyc | Bin 0 -> 17058 bytes .../__pycache__/response.cpython-310.pyc | Bin 0 -> 22414 bytes .../sansio/__pycache__/utils.cpython-310.pyc | Bin 0 -> 4571 bytes .../Lib/site-packages/werkzeug/sansio/http.py | 140 + .../werkzeug/sansio/multipart.py | 279 ++ .../site-packages/werkzeug/sansio/request.py | 547 +++ .../site-packages/werkzeug/sansio/response.py | 704 ++++ .../site-packages/werkzeug/sansio/utils.py | 165 + .vtodo/Lib/site-packages/werkzeug/security.py | 140 + .vtodo/Lib/site-packages/werkzeug/serving.py | 1098 ++++++ .vtodo/Lib/site-packages/werkzeug/test.py | 1337 ++++++++ .vtodo/Lib/site-packages/werkzeug/testapp.py | 241 ++ .vtodo/Lib/site-packages/werkzeug/urls.py | 1067 ++++++ .../Lib/site-packages/werkzeug/user_agent.py | 47 + .vtodo/Lib/site-packages/werkzeug/utils.py | 705 ++++ .../werkzeug/wrappers/__init__.py | 3 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 299 bytes .../__pycache__/request.cpython-310.pyc | Bin 0 -> 20228 bytes .../__pycache__/response.cpython-310.pyc | Bin 0 -> 29157 bytes .../werkzeug/wrappers/request.py | 614 ++++ .../werkzeug/wrappers/response.py | 877 +++++ .vtodo/Lib/site-packages/werkzeug/wsgi.py | 1020 ++++++ .vtodo/Scripts/flask.exe | Bin 0 -> 106358 bytes __pycache__/app.cpython-310.pyc | Bin 0 -> 1824 bytes app.py | 63 + database.db | Bin 0 -> 16384 bytes init_db.py | 35 + list_example.py | 16 + schema.sql | 16 + templates/base.html | 41 + templates/create.html | 34 + templates/index.html | 17 + 563 files changed, 64859 insertions(+) create mode 100644 .vtodo/Lib/site-packages/Flask-2.2.2.dist-info/INSTALLER create mode 100644 .vtodo/Lib/site-packages/Flask-2.2.2.dist-info/LICENSE.rst create mode 100644 .vtodo/Lib/site-packages/Flask-2.2.2.dist-info/METADATA create mode 100644 .vtodo/Lib/site-packages/Flask-2.2.2.dist-info/RECORD create mode 100644 .vtodo/Lib/site-packages/Flask-2.2.2.dist-info/REQUESTED create mode 100644 .vtodo/Lib/site-packages/Flask-2.2.2.dist-info/WHEEL create mode 100644 .vtodo/Lib/site-packages/Flask-2.2.2.dist-info/entry_points.txt create mode 100644 .vtodo/Lib/site-packages/Flask-2.2.2.dist-info/top_level.txt create mode 100644 .vtodo/Lib/site-packages/Jinja2-3.1.2.dist-info/INSTALLER create mode 100644 .vtodo/Lib/site-packages/Jinja2-3.1.2.dist-info/LICENSE.rst create mode 100644 .vtodo/Lib/site-packages/Jinja2-3.1.2.dist-info/METADATA create mode 100644 .vtodo/Lib/site-packages/Jinja2-3.1.2.dist-info/RECORD create mode 100644 .vtodo/Lib/site-packages/Jinja2-3.1.2.dist-info/WHEEL create mode 100644 .vtodo/Lib/site-packages/Jinja2-3.1.2.dist-info/entry_points.txt create mode 100644 .vtodo/Lib/site-packages/Jinja2-3.1.2.dist-info/top_level.txt create mode 100644 .vtodo/Lib/site-packages/MarkupSafe-2.1.1.dist-info/INSTALLER create mode 100644 .vtodo/Lib/site-packages/MarkupSafe-2.1.1.dist-info/LICENSE.rst create mode 100644 .vtodo/Lib/site-packages/MarkupSafe-2.1.1.dist-info/METADATA create mode 100644 .vtodo/Lib/site-packages/MarkupSafe-2.1.1.dist-info/RECORD create mode 100644 .vtodo/Lib/site-packages/MarkupSafe-2.1.1.dist-info/WHEEL create mode 100644 .vtodo/Lib/site-packages/MarkupSafe-2.1.1.dist-info/top_level.txt create mode 100644 .vtodo/Lib/site-packages/Werkzeug-2.2.2.dist-info/INSTALLER create mode 100644 .vtodo/Lib/site-packages/Werkzeug-2.2.2.dist-info/LICENSE.rst create mode 100644 .vtodo/Lib/site-packages/Werkzeug-2.2.2.dist-info/METADATA create mode 100644 .vtodo/Lib/site-packages/Werkzeug-2.2.2.dist-info/RECORD create mode 100644 .vtodo/Lib/site-packages/Werkzeug-2.2.2.dist-info/WHEEL create mode 100644 .vtodo/Lib/site-packages/Werkzeug-2.2.2.dist-info/top_level.txt create mode 100644 .vtodo/Lib/site-packages/click-8.1.3.dist-info/INSTALLER create mode 100644 .vtodo/Lib/site-packages/click-8.1.3.dist-info/LICENSE.rst create mode 100644 .vtodo/Lib/site-packages/click-8.1.3.dist-info/METADATA create mode 100644 .vtodo/Lib/site-packages/click-8.1.3.dist-info/RECORD create mode 100644 .vtodo/Lib/site-packages/click-8.1.3.dist-info/WHEEL create mode 100644 .vtodo/Lib/site-packages/click-8.1.3.dist-info/top_level.txt create mode 100644 .vtodo/Lib/site-packages/click/__init__.py create mode 100644 .vtodo/Lib/site-packages/click/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/click/__pycache__/_compat.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/click/__pycache__/_termui_impl.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/click/__pycache__/_textwrap.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/click/__pycache__/_winconsole.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/click/__pycache__/core.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/click/__pycache__/decorators.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/click/__pycache__/exceptions.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/click/__pycache__/formatting.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/click/__pycache__/globals.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/click/__pycache__/parser.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/click/__pycache__/shell_completion.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/click/__pycache__/termui.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/click/__pycache__/testing.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/click/__pycache__/types.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/click/__pycache__/utils.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/click/_compat.py create mode 100644 .vtodo/Lib/site-packages/click/_termui_impl.py create mode 100644 .vtodo/Lib/site-packages/click/_textwrap.py create mode 100644 .vtodo/Lib/site-packages/click/_winconsole.py create mode 100644 .vtodo/Lib/site-packages/click/core.py create mode 100644 .vtodo/Lib/site-packages/click/decorators.py create mode 100644 .vtodo/Lib/site-packages/click/exceptions.py create mode 100644 .vtodo/Lib/site-packages/click/formatting.py create mode 100644 .vtodo/Lib/site-packages/click/globals.py create mode 100644 .vtodo/Lib/site-packages/click/parser.py create mode 100644 .vtodo/Lib/site-packages/click/py.typed create mode 100644 .vtodo/Lib/site-packages/click/shell_completion.py create mode 100644 .vtodo/Lib/site-packages/click/termui.py create mode 100644 .vtodo/Lib/site-packages/click/testing.py create mode 100644 .vtodo/Lib/site-packages/click/types.py create mode 100644 .vtodo/Lib/site-packages/click/utils.py create mode 100644 .vtodo/Lib/site-packages/colorama-0.4.5.dist-info/INSTALLER create mode 100644 .vtodo/Lib/site-packages/colorama-0.4.5.dist-info/LICENSE.txt create mode 100644 .vtodo/Lib/site-packages/colorama-0.4.5.dist-info/METADATA create mode 100644 .vtodo/Lib/site-packages/colorama-0.4.5.dist-info/RECORD create mode 100644 .vtodo/Lib/site-packages/colorama-0.4.5.dist-info/WHEEL create mode 100644 .vtodo/Lib/site-packages/colorama-0.4.5.dist-info/top_level.txt create mode 100644 .vtodo/Lib/site-packages/colorama/__init__.py create mode 100644 .vtodo/Lib/site-packages/colorama/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/colorama/__pycache__/ansi.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/colorama/__pycache__/ansitowin32.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/colorama/__pycache__/initialise.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/colorama/__pycache__/win32.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/colorama/__pycache__/winterm.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/colorama/ansi.py create mode 100644 .vtodo/Lib/site-packages/colorama/ansitowin32.py create mode 100644 .vtodo/Lib/site-packages/colorama/initialise.py create mode 100644 .vtodo/Lib/site-packages/colorama/win32.py create mode 100644 .vtodo/Lib/site-packages/colorama/winterm.py create mode 100644 .vtodo/Lib/site-packages/flask/__init__.py create mode 100644 .vtodo/Lib/site-packages/flask/__main__.py create mode 100644 .vtodo/Lib/site-packages/flask/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/flask/__pycache__/__main__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/flask/__pycache__/app.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/flask/__pycache__/blueprints.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/flask/__pycache__/cli.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/flask/__pycache__/config.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/flask/__pycache__/ctx.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/flask/__pycache__/debughelpers.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/flask/__pycache__/globals.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/flask/__pycache__/helpers.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/flask/__pycache__/logging.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/flask/__pycache__/scaffold.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/flask/__pycache__/sessions.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/flask/__pycache__/signals.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/flask/__pycache__/templating.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/flask/__pycache__/testing.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/flask/__pycache__/typing.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/flask/__pycache__/views.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/flask/__pycache__/wrappers.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/flask/app.py create mode 100644 .vtodo/Lib/site-packages/flask/blueprints.py create mode 100644 .vtodo/Lib/site-packages/flask/cli.py create mode 100644 .vtodo/Lib/site-packages/flask/config.py create mode 100644 .vtodo/Lib/site-packages/flask/ctx.py create mode 100644 .vtodo/Lib/site-packages/flask/debughelpers.py create mode 100644 .vtodo/Lib/site-packages/flask/globals.py create mode 100644 .vtodo/Lib/site-packages/flask/helpers.py create mode 100644 .vtodo/Lib/site-packages/flask/json/__init__.py create mode 100644 .vtodo/Lib/site-packages/flask/json/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/flask/json/__pycache__/provider.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/flask/json/__pycache__/tag.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/flask/json/provider.py create mode 100644 .vtodo/Lib/site-packages/flask/json/tag.py create mode 100644 .vtodo/Lib/site-packages/flask/logging.py create mode 100644 .vtodo/Lib/site-packages/flask/py.typed create mode 100644 .vtodo/Lib/site-packages/flask/scaffold.py create mode 100644 .vtodo/Lib/site-packages/flask/sessions.py create mode 100644 .vtodo/Lib/site-packages/flask/signals.py create mode 100644 .vtodo/Lib/site-packages/flask/templating.py create mode 100644 .vtodo/Lib/site-packages/flask/testing.py create mode 100644 .vtodo/Lib/site-packages/flask/typing.py create mode 100644 .vtodo/Lib/site-packages/flask/views.py create mode 100644 .vtodo/Lib/site-packages/flask/wrappers.py create mode 100644 .vtodo/Lib/site-packages/itsdangerous-2.1.2.dist-info/INSTALLER create mode 100644 .vtodo/Lib/site-packages/itsdangerous-2.1.2.dist-info/LICENSE.rst create mode 100644 .vtodo/Lib/site-packages/itsdangerous-2.1.2.dist-info/METADATA create mode 100644 .vtodo/Lib/site-packages/itsdangerous-2.1.2.dist-info/RECORD create mode 100644 .vtodo/Lib/site-packages/itsdangerous-2.1.2.dist-info/WHEEL create mode 100644 .vtodo/Lib/site-packages/itsdangerous-2.1.2.dist-info/top_level.txt create mode 100644 .vtodo/Lib/site-packages/itsdangerous/__init__.py create mode 100644 .vtodo/Lib/site-packages/itsdangerous/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/itsdangerous/__pycache__/_json.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/itsdangerous/__pycache__/encoding.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/itsdangerous/__pycache__/exc.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/itsdangerous/__pycache__/serializer.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/itsdangerous/__pycache__/signer.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/itsdangerous/__pycache__/timed.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/itsdangerous/__pycache__/url_safe.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/itsdangerous/_json.py create mode 100644 .vtodo/Lib/site-packages/itsdangerous/encoding.py create mode 100644 .vtodo/Lib/site-packages/itsdangerous/exc.py create mode 100644 .vtodo/Lib/site-packages/itsdangerous/py.typed create mode 100644 .vtodo/Lib/site-packages/itsdangerous/serializer.py create mode 100644 .vtodo/Lib/site-packages/itsdangerous/signer.py create mode 100644 .vtodo/Lib/site-packages/itsdangerous/timed.py create mode 100644 .vtodo/Lib/site-packages/itsdangerous/url_safe.py create mode 100644 .vtodo/Lib/site-packages/jinja2/__init__.py create mode 100644 .vtodo/Lib/site-packages/jinja2/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/jinja2/__pycache__/_identifier.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/jinja2/__pycache__/async_utils.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/jinja2/__pycache__/bccache.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/jinja2/__pycache__/compiler.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/jinja2/__pycache__/constants.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/jinja2/__pycache__/debug.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/jinja2/__pycache__/defaults.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/jinja2/__pycache__/environment.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/jinja2/__pycache__/exceptions.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/jinja2/__pycache__/ext.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/jinja2/__pycache__/filters.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/jinja2/__pycache__/idtracking.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/jinja2/__pycache__/lexer.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/jinja2/__pycache__/loaders.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/jinja2/__pycache__/meta.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/jinja2/__pycache__/nativetypes.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/jinja2/__pycache__/nodes.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/jinja2/__pycache__/optimizer.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/jinja2/__pycache__/parser.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/jinja2/__pycache__/runtime.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/jinja2/__pycache__/sandbox.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/jinja2/__pycache__/tests.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/jinja2/__pycache__/utils.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/jinja2/__pycache__/visitor.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/jinja2/_identifier.py create mode 100644 .vtodo/Lib/site-packages/jinja2/async_utils.py create mode 100644 .vtodo/Lib/site-packages/jinja2/bccache.py create mode 100644 .vtodo/Lib/site-packages/jinja2/compiler.py create mode 100644 .vtodo/Lib/site-packages/jinja2/constants.py create mode 100644 .vtodo/Lib/site-packages/jinja2/debug.py create mode 100644 .vtodo/Lib/site-packages/jinja2/defaults.py create mode 100644 .vtodo/Lib/site-packages/jinja2/environment.py create mode 100644 .vtodo/Lib/site-packages/jinja2/exceptions.py create mode 100644 .vtodo/Lib/site-packages/jinja2/ext.py create mode 100644 .vtodo/Lib/site-packages/jinja2/filters.py create mode 100644 .vtodo/Lib/site-packages/jinja2/idtracking.py create mode 100644 .vtodo/Lib/site-packages/jinja2/lexer.py create mode 100644 .vtodo/Lib/site-packages/jinja2/loaders.py create mode 100644 .vtodo/Lib/site-packages/jinja2/meta.py create mode 100644 .vtodo/Lib/site-packages/jinja2/nativetypes.py create mode 100644 .vtodo/Lib/site-packages/jinja2/nodes.py create mode 100644 .vtodo/Lib/site-packages/jinja2/optimizer.py create mode 100644 .vtodo/Lib/site-packages/jinja2/parser.py create mode 100644 .vtodo/Lib/site-packages/jinja2/py.typed create mode 100644 .vtodo/Lib/site-packages/jinja2/runtime.py create mode 100644 .vtodo/Lib/site-packages/jinja2/sandbox.py create mode 100644 .vtodo/Lib/site-packages/jinja2/tests.py create mode 100644 .vtodo/Lib/site-packages/jinja2/utils.py create mode 100644 .vtodo/Lib/site-packages/jinja2/visitor.py create mode 100644 .vtodo/Lib/site-packages/markupsafe/__init__.py create mode 100644 .vtodo/Lib/site-packages/markupsafe/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/markupsafe/__pycache__/_native.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/markupsafe/_native.py create mode 100644 .vtodo/Lib/site-packages/markupsafe/_speedups.c create mode 100644 .vtodo/Lib/site-packages/markupsafe/_speedups.cp310-win_amd64.pyd create mode 100644 .vtodo/Lib/site-packages/markupsafe/_speedups.pyi create mode 100644 .vtodo/Lib/site-packages/markupsafe/py.typed create mode 100644 .vtodo/Lib/site-packages/nano-0.10.0.dist-info/DESCRIPTION.rst create mode 100644 .vtodo/Lib/site-packages/nano-0.10.0.dist-info/INSTALLER create mode 100644 .vtodo/Lib/site-packages/nano-0.10.0.dist-info/METADATA create mode 100644 .vtodo/Lib/site-packages/nano-0.10.0.dist-info/RECORD create mode 100644 .vtodo/Lib/site-packages/nano-0.10.0.dist-info/REQUESTED create mode 100644 .vtodo/Lib/site-packages/nano-0.10.0.dist-info/WHEEL create mode 100644 .vtodo/Lib/site-packages/nano-0.10.0.dist-info/metadata.json create mode 100644 .vtodo/Lib/site-packages/nano-0.10.0.dist-info/top_level.txt create mode 100644 .vtodo/Lib/site-packages/nano/__init__.py create mode 100644 .vtodo/Lib/site-packages/nano/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/activation/__init__.py create mode 100644 .vtodo/Lib/site-packages/nano/activation/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/activation/__pycache__/admin.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/activation/__pycache__/apps.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/activation/__pycache__/forms.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/activation/__pycache__/models.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/activation/__pycache__/signals.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/activation/__pycache__/tests.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/activation/__pycache__/urls.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/activation/__pycache__/views.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/activation/admin.py create mode 100644 .vtodo/Lib/site-packages/nano/activation/apps.py create mode 100644 .vtodo/Lib/site-packages/nano/activation/forms.py create mode 100644 .vtodo/Lib/site-packages/nano/activation/migrations/0001_initial.py create mode 100644 .vtodo/Lib/site-packages/nano/activation/migrations/__init__.py create mode 100644 .vtodo/Lib/site-packages/nano/activation/migrations/__pycache__/0001_initial.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/activation/migrations/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/activation/models.py create mode 100644 .vtodo/Lib/site-packages/nano/activation/signals.py create mode 100644 .vtodo/Lib/site-packages/nano/activation/templates/nano/activation/activated.html create mode 100644 .vtodo/Lib/site-packages/nano/activation/templates/nano/activation/activation_form.html create mode 100644 .vtodo/Lib/site-packages/nano/activation/templates/nano/activation/base.html create mode 100644 .vtodo/Lib/site-packages/nano/activation/tests.py create mode 100644 .vtodo/Lib/site-packages/nano/activation/urls.py create mode 100644 .vtodo/Lib/site-packages/nano/activation/views.py create mode 100644 .vtodo/Lib/site-packages/nano/badge/__init__.py create mode 100644 .vtodo/Lib/site-packages/nano/badge/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/badge/__pycache__/admin.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/badge/__pycache__/apps.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/badge/__pycache__/models.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/badge/__pycache__/urls.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/badge/__pycache__/views.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/badge/admin.py create mode 100644 .vtodo/Lib/site-packages/nano/badge/apps.py create mode 100644 .vtodo/Lib/site-packages/nano/badge/migrations/0001_initial.py create mode 100644 .vtodo/Lib/site-packages/nano/badge/migrations/__init__.py create mode 100644 .vtodo/Lib/site-packages/nano/badge/migrations/__pycache__/0001_initial.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/badge/migrations/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/badge/models.py create mode 100644 .vtodo/Lib/site-packages/nano/badge/templates/badge/badge_list_frag.html create mode 100644 .vtodo/Lib/site-packages/nano/badge/templates/show_badge_sum.html create mode 100644 .vtodo/Lib/site-packages/nano/badge/templatetags/__init__.py create mode 100644 .vtodo/Lib/site-packages/nano/badge/templatetags/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/badge/templatetags/__pycache__/badge_tags.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/badge/templatetags/badge_tags.py create mode 100644 .vtodo/Lib/site-packages/nano/badge/urls.py create mode 100644 .vtodo/Lib/site-packages/nano/badge/views.py create mode 100644 .vtodo/Lib/site-packages/nano/blog/__init__.py create mode 100644 .vtodo/Lib/site-packages/nano/blog/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/blog/__pycache__/admin.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/blog/__pycache__/apps.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/blog/__pycache__/models.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/blog/__pycache__/settings.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/blog/__pycache__/tests.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/blog/__pycache__/tools.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/blog/__pycache__/urls.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/blog/__pycache__/views.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/blog/admin.py create mode 100644 .vtodo/Lib/site-packages/nano/blog/apps.py create mode 100644 .vtodo/Lib/site-packages/nano/blog/migrations/0001_initial.py create mode 100644 .vtodo/Lib/site-packages/nano/blog/migrations/__init__.py create mode 100644 .vtodo/Lib/site-packages/nano/blog/migrations/__pycache__/0001_initial.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/blog/migrations/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/blog/models.py create mode 100644 .vtodo/Lib/site-packages/nano/blog/settings.py create mode 100644 .vtodo/Lib/site-packages/nano/blog/templates/blog/entry_archive.html create mode 100644 .vtodo/Lib/site-packages/nano/blog/templates/blog/entry_archive_day.html create mode 100644 .vtodo/Lib/site-packages/nano/blog/templates/blog/entry_archive_month.html create mode 100644 .vtodo/Lib/site-packages/nano/blog/templates/blog/entry_archive_year.html create mode 100644 .vtodo/Lib/site-packages/nano/blog/templates/blog/entry_list.html create mode 100644 .vtodo/Lib/site-packages/nano/blog/templates/blog/entry_with_day.html create mode 100644 .vtodo/Lib/site-packages/nano/blog/templates/blog/entry_with_fulldate.html create mode 100644 .vtodo/Lib/site-packages/nano/blog/templates/blog/entry_with_fulldatetime.html create mode 100644 .vtodo/Lib/site-packages/nano/blog/templates/blog/entry_with_monthday.html create mode 100644 .vtodo/Lib/site-packages/nano/blog/templates/blog/entry_with_time.html create mode 100644 .vtodo/Lib/site-packages/nano/blog/templates/blog/new_user.html create mode 100644 .vtodo/Lib/site-packages/nano/blog/tests.py create mode 100644 .vtodo/Lib/site-packages/nano/blog/tools.py create mode 100644 .vtodo/Lib/site-packages/nano/blog/urls.py create mode 100644 .vtodo/Lib/site-packages/nano/blog/views.py create mode 100644 .vtodo/Lib/site-packages/nano/chunk/__init__.py create mode 100644 .vtodo/Lib/site-packages/nano/chunk/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/chunk/__pycache__/admin.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/chunk/__pycache__/apps.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/chunk/__pycache__/loader.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/chunk/__pycache__/loaders.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/chunk/__pycache__/models.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/chunk/__pycache__/tests.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/chunk/admin.py create mode 100644 .vtodo/Lib/site-packages/nano/chunk/apps.py create mode 100644 .vtodo/Lib/site-packages/nano/chunk/loader.py create mode 100644 .vtodo/Lib/site-packages/nano/chunk/loaders.py create mode 100644 .vtodo/Lib/site-packages/nano/chunk/migrations/0001_initial.py create mode 100644 .vtodo/Lib/site-packages/nano/chunk/migrations/__init__.py create mode 100644 .vtodo/Lib/site-packages/nano/chunk/migrations/__pycache__/0001_initial.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/chunk/migrations/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/chunk/models.py create mode 100644 .vtodo/Lib/site-packages/nano/chunk/tests.py create mode 100644 .vtodo/Lib/site-packages/nano/comments/__init__.py create mode 100644 .vtodo/Lib/site-packages/nano/comments/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/comments/__pycache__/admin.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/comments/__pycache__/apps.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/comments/__pycache__/forms.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/comments/__pycache__/models.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/comments/__pycache__/urls.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/comments/__pycache__/views.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/comments/admin.py create mode 100644 .vtodo/Lib/site-packages/nano/comments/apps.py create mode 100644 .vtodo/Lib/site-packages/nano/comments/forms.py create mode 100644 .vtodo/Lib/site-packages/nano/comments/migrations/0001_initial.py create mode 100644 .vtodo/Lib/site-packages/nano/comments/migrations/__init__.py create mode 100644 .vtodo/Lib/site-packages/nano/comments/migrations/__pycache__/0001_initial.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/comments/migrations/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/comments/models.py create mode 100644 .vtodo/Lib/site-packages/nano/comments/templates/nano/comments/comment_form.html create mode 100644 .vtodo/Lib/site-packages/nano/comments/templates/nano/comments/comment_form_frag.html create mode 100644 .vtodo/Lib/site-packages/nano/comments/templates/nano/comments/comment_list.html create mode 100644 .vtodo/Lib/site-packages/nano/comments/templatetags/__init__.py create mode 100644 .vtodo/Lib/site-packages/nano/comments/templatetags/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/comments/templatetags/__pycache__/comments_tags.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/comments/templatetags/comments_tags.py create mode 100644 .vtodo/Lib/site-packages/nano/comments/tests/migrations/0001_initial.py create mode 100644 .vtodo/Lib/site-packages/nano/comments/tests/migrations/__init__.py create mode 100644 .vtodo/Lib/site-packages/nano/comments/tests/migrations/__pycache__/0001_initial.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/comments/tests/migrations/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/comments/urls.py create mode 100644 .vtodo/Lib/site-packages/nano/comments/views.py create mode 100644 .vtodo/Lib/site-packages/nano/countries/__init__.py create mode 100644 .vtodo/Lib/site-packages/nano/countries/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/countries/__pycache__/admin.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/countries/__pycache__/apps.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/countries/__pycache__/models.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/countries/__pycache__/tests.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/countries/admin.py create mode 100644 .vtodo/Lib/site-packages/nano/countries/apps.py create mode 100644 .vtodo/Lib/site-packages/nano/countries/migrations/0001_initial.py create mode 100644 .vtodo/Lib/site-packages/nano/countries/migrations/__init__.py create mode 100644 .vtodo/Lib/site-packages/nano/countries/migrations/__pycache__/0001_initial.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/countries/migrations/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/countries/models.py create mode 100644 .vtodo/Lib/site-packages/nano/countries/tests.py create mode 100644 .vtodo/Lib/site-packages/nano/faq/__init__.py create mode 100644 .vtodo/Lib/site-packages/nano/faq/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/faq/__pycache__/admin.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/faq/__pycache__/apps.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/faq/__pycache__/models.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/faq/__pycache__/tests.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/faq/__pycache__/urls.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/faq/__pycache__/views.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/faq/admin.py create mode 100644 .vtodo/Lib/site-packages/nano/faq/apps.py create mode 100644 .vtodo/Lib/site-packages/nano/faq/migrations/0001_initial.py create mode 100644 .vtodo/Lib/site-packages/nano/faq/migrations/__init__.py create mode 100644 .vtodo/Lib/site-packages/nano/faq/migrations/__pycache__/0001_initial.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/faq/migrations/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/faq/models.py create mode 100644 .vtodo/Lib/site-packages/nano/faq/tests.py create mode 100644 .vtodo/Lib/site-packages/nano/faq/urls.py create mode 100644 .vtodo/Lib/site-packages/nano/faq/views.py create mode 100644 .vtodo/Lib/site-packages/nano/mark/__init__.py create mode 100644 .vtodo/Lib/site-packages/nano/mark/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/mark/__pycache__/admin.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/mark/__pycache__/apps.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/mark/__pycache__/managers.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/mark/__pycache__/models.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/mark/__pycache__/urls.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/mark/__pycache__/views.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/mark/admin.py create mode 100644 .vtodo/Lib/site-packages/nano/mark/apps.py create mode 100644 .vtodo/Lib/site-packages/nano/mark/managers.py create mode 100644 .vtodo/Lib/site-packages/nano/mark/migrations/0001_initial.py create mode 100644 .vtodo/Lib/site-packages/nano/mark/migrations/__init__.py create mode 100644 .vtodo/Lib/site-packages/nano/mark/migrations/__pycache__/0001_initial.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/mark/migrations/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/mark/models.py create mode 100644 .vtodo/Lib/site-packages/nano/mark/templates/nano/mark/mark.html create mode 100644 .vtodo/Lib/site-packages/nano/mark/templates/nano/mark/mark_faved.html create mode 100644 .vtodo/Lib/site-packages/nano/mark/templatetags/__init__.py create mode 100644 .vtodo/Lib/site-packages/nano/mark/templatetags/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/mark/templatetags/__pycache__/nano_mark_tags.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/mark/templatetags/nano_mark_tags.py create mode 100644 .vtodo/Lib/site-packages/nano/mark/urls.py create mode 100644 .vtodo/Lib/site-packages/nano/mark/views.py create mode 100644 .vtodo/Lib/site-packages/nano/privmsg/__init__.py create mode 100644 .vtodo/Lib/site-packages/nano/privmsg/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/privmsg/__pycache__/admin.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/privmsg/__pycache__/apps.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/privmsg/__pycache__/forms.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/privmsg/__pycache__/models.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/privmsg/__pycache__/tests.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/privmsg/__pycache__/urls.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/privmsg/__pycache__/views.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/privmsg/admin.py create mode 100644 .vtodo/Lib/site-packages/nano/privmsg/apps.py create mode 100644 .vtodo/Lib/site-packages/nano/privmsg/forms.py create mode 100644 .vtodo/Lib/site-packages/nano/privmsg/migrations/0001_initial.py create mode 100644 .vtodo/Lib/site-packages/nano/privmsg/migrations/__init__.py create mode 100644 .vtodo/Lib/site-packages/nano/privmsg/migrations/__pycache__/0001_initial.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/privmsg/migrations/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/privmsg/models.py create mode 100644 .vtodo/Lib/site-packages/nano/privmsg/templates/privmsg/add.html create mode 100644 .vtodo/Lib/site-packages/nano/privmsg/templates/privmsg/archive.html create mode 100644 .vtodo/Lib/site-packages/nano/privmsg/templates/privmsg/batch_archive.html create mode 100644 .vtodo/Lib/site-packages/nano/privmsg/templates/privmsg/message.frag.html create mode 100644 .vtodo/Lib/site-packages/nano/privmsg/tests.py create mode 100644 .vtodo/Lib/site-packages/nano/privmsg/urls.py create mode 100644 .vtodo/Lib/site-packages/nano/privmsg/views.py create mode 100644 .vtodo/Lib/site-packages/nano/tools/__init__.py create mode 100644 .vtodo/Lib/site-packages/nano/tools/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/tools/__pycache__/apps.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/tools/__pycache__/middleware.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/tools/__pycache__/models.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/tools/apps.py create mode 100644 .vtodo/Lib/site-packages/nano/tools/middleware.py create mode 100644 .vtodo/Lib/site-packages/nano/tools/models.py create mode 100644 .vtodo/Lib/site-packages/nano/tools/templates/come_back.html create mode 100644 .vtodo/Lib/site-packages/nano/tools/templatetags/__init__.py create mode 100644 .vtodo/Lib/site-packages/nano/tools/templatetags/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/tools/templatetags/__pycache__/nano_tags.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/tools/templatetags/nano_tags.py create mode 100644 .vtodo/Lib/site-packages/nano/tools/tests/migrations/0001_initial.py create mode 100644 .vtodo/Lib/site-packages/nano/tools/tests/migrations/__init__.py create mode 100644 .vtodo/Lib/site-packages/nano/tools/tests/migrations/__pycache__/0001_initial.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/tools/tests/migrations/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/user/__init__.py create mode 100644 .vtodo/Lib/site-packages/nano/user/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/user/__pycache__/apps.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/user/__pycache__/forms.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/user/__pycache__/tests.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/user/__pycache__/urls.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/user/__pycache__/views.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/nano/user/apps.py create mode 100644 .vtodo/Lib/site-packages/nano/user/forms.py create mode 100644 .vtodo/Lib/site-packages/nano/user/templates/nano/user/signup_done.html create mode 100644 .vtodo/Lib/site-packages/nano/user/templates/password_change_form.html create mode 100644 .vtodo/Lib/site-packages/nano/user/templates/password_reset.txt create mode 100644 .vtodo/Lib/site-packages/nano/user/templates/password_reset_form.html create mode 100644 .vtodo/Lib/site-packages/nano/user/tests.py create mode 100644 .vtodo/Lib/site-packages/nano/user/urls.py create mode 100644 .vtodo/Lib/site-packages/nano/user/views.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/__init__.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/__pycache__/_internal.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/__pycache__/_reloader.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/__pycache__/datastructures.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/__pycache__/exceptions.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/__pycache__/formparser.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/__pycache__/http.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/__pycache__/local.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/__pycache__/security.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/__pycache__/serving.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/__pycache__/test.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/__pycache__/testapp.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/__pycache__/urls.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/__pycache__/user_agent.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/__pycache__/utils.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/__pycache__/wsgi.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/_internal.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/_reloader.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/datastructures.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/datastructures.pyi create mode 100644 .vtodo/Lib/site-packages/werkzeug/debug/__init__.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/debug/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/debug/__pycache__/console.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/debug/__pycache__/repr.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/debug/__pycache__/tbtools.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/debug/console.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/debug/repr.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/debug/shared/ICON_LICENSE.md create mode 100644 .vtodo/Lib/site-packages/werkzeug/debug/shared/console.png create mode 100644 .vtodo/Lib/site-packages/werkzeug/debug/shared/debugger.js create mode 100644 .vtodo/Lib/site-packages/werkzeug/debug/shared/less.png create mode 100644 .vtodo/Lib/site-packages/werkzeug/debug/shared/more.png create mode 100644 .vtodo/Lib/site-packages/werkzeug/debug/shared/style.css create mode 100644 .vtodo/Lib/site-packages/werkzeug/debug/tbtools.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/exceptions.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/formparser.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/http.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/local.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/middleware/__init__.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/middleware/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/middleware/__pycache__/dispatcher.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/middleware/__pycache__/http_proxy.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/middleware/__pycache__/lint.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/middleware/__pycache__/profiler.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/middleware/__pycache__/proxy_fix.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/middleware/__pycache__/shared_data.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/middleware/dispatcher.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/middleware/http_proxy.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/middleware/lint.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/middleware/profiler.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/middleware/proxy_fix.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/middleware/shared_data.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/py.typed create mode 100644 .vtodo/Lib/site-packages/werkzeug/routing/__init__.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/routing/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/routing/__pycache__/converters.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/routing/__pycache__/exceptions.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/routing/__pycache__/map.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/routing/__pycache__/matcher.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/routing/__pycache__/rules.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/routing/converters.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/routing/exceptions.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/routing/map.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/routing/matcher.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/routing/rules.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/sansio/__init__.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/sansio/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/sansio/__pycache__/http.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/sansio/__pycache__/multipart.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/sansio/__pycache__/request.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/sansio/__pycache__/response.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/sansio/__pycache__/utils.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/sansio/http.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/sansio/multipart.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/sansio/request.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/sansio/response.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/sansio/utils.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/security.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/serving.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/test.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/testapp.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/urls.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/user_agent.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/utils.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/wrappers/__init__.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/wrappers/__pycache__/__init__.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/wrappers/__pycache__/request.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/wrappers/__pycache__/response.cpython-310.pyc create mode 100644 .vtodo/Lib/site-packages/werkzeug/wrappers/request.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/wrappers/response.py create mode 100644 .vtodo/Lib/site-packages/werkzeug/wsgi.py create mode 100644 .vtodo/Scripts/flask.exe create mode 100644 __pycache__/app.cpython-310.pyc create mode 100644 app.py create mode 100644 database.db create mode 100644 init_db.py create mode 100644 list_example.py create mode 100644 schema.sql create mode 100644 templates/base.html create mode 100644 templates/create.html create mode 100644 templates/index.html diff --git a/.vtodo/Lib/site-packages/Flask-2.2.2.dist-info/INSTALLER b/.vtodo/Lib/site-packages/Flask-2.2.2.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.vtodo/Lib/site-packages/Flask-2.2.2.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.vtodo/Lib/site-packages/Flask-2.2.2.dist-info/LICENSE.rst b/.vtodo/Lib/site-packages/Flask-2.2.2.dist-info/LICENSE.rst new file mode 100644 index 0000000..9d227a0 --- /dev/null +++ b/.vtodo/Lib/site-packages/Flask-2.2.2.dist-info/LICENSE.rst @@ -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/.vtodo/Lib/site-packages/Flask-2.2.2.dist-info/METADATA b/.vtodo/Lib/site-packages/Flask-2.2.2.dist-info/METADATA new file mode 100644 index 0000000..f644287 --- /dev/null +++ b/.vtodo/Lib/site-packages/Flask-2.2.2.dist-info/METADATA @@ -0,0 +1,123 @@ +Metadata-Version: 2.1 +Name: Flask +Version: 2.2.2 +Summary: A simple framework for building complex web applications. +Home-page: https://palletsprojects.com/p/flask +Author: Armin Ronacher +Author-email: armin.ronacher@active-4.com +Maintainer: Pallets +Maintainer-email: contact@palletsprojects.com +License: BSD-3-Clause +Project-URL: Donate, https://palletsprojects.com/donate +Project-URL: Documentation, https://flask.palletsprojects.com/ +Project-URL: Changes, https://flask.palletsprojects.com/changes/ +Project-URL: Source Code, https://github.com/pallets/flask/ +Project-URL: Issue Tracker, https://github.com/pallets/flask/issues/ +Project-URL: Twitter, https://twitter.com/PalletsTeam +Project-URL: Chat, https://discord.gg/pallets +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Web Environment +Classifier: Framework :: Flask +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content +Classifier: Topic :: Internet :: WWW/HTTP :: WSGI +Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application +Classifier: Topic :: Software Development :: Libraries :: Application Frameworks +Requires-Python: >=3.7 +Description-Content-Type: text/x-rst +License-File: LICENSE.rst +Requires-Dist: Werkzeug (>=2.2.2) +Requires-Dist: Jinja2 (>=3.0) +Requires-Dist: itsdangerous (>=2.0) +Requires-Dist: click (>=8.0) +Requires-Dist: importlib-metadata (>=3.6.0) ; python_version < "3.10" +Provides-Extra: async +Requires-Dist: asgiref (>=3.2) ; extra == 'async' +Provides-Extra: dotenv +Requires-Dist: python-dotenv ; extra == 'dotenv' + +Flask +===== + +Flask is a lightweight `WSGI`_ web application framework. It is designed +to make getting started quick and easy, with the ability to scale up to +complex applications. It began as a simple wrapper around `Werkzeug`_ +and `Jinja`_ and has become one of the most popular Python web +application frameworks. + +Flask offers suggestions, but doesn't enforce any dependencies or +project layout. It is up to the developer to choose the tools and +libraries they want to use. There are many extensions provided by the +community that make adding new functionality easy. + +.. _WSGI: https://wsgi.readthedocs.io/ +.. _Werkzeug: https://werkzeug.palletsprojects.com/ +.. _Jinja: https://jinja.palletsprojects.com/ + + +Installing +---------- + +Install and update using `pip`_: + +.. code-block:: text + + $ pip install -U Flask + +.. _pip: https://pip.pypa.io/en/stable/getting-started/ + + +A Simple Example +---------------- + +.. code-block:: python + + # save this as app.py + from flask import Flask + + app = Flask(__name__) + + @app.route("/") + def hello(): + return "Hello, World!" + +.. code-block:: text + + $ flask run + * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) + + +Contributing +------------ + +For guidance on setting up a development environment and how to make a +contribution to Flask, see the `contributing guidelines`_. + +.. _contributing guidelines: https://github.com/pallets/flask/blob/main/CONTRIBUTING.rst + + +Donate +------ + +The Pallets organization develops and supports Flask and the libraries +it uses. In order to grow the community of contributors and users, and +allow the maintainers to devote more time to the projects, `please +donate today`_. + +.. _please donate today: https://palletsprojects.com/donate + + +Links +----- + +- Documentation: https://flask.palletsprojects.com/ +- Changes: https://flask.palletsprojects.com/changes/ +- PyPI Releases: https://pypi.org/project/Flask/ +- Source Code: https://github.com/pallets/flask/ +- Issue Tracker: https://github.com/pallets/flask/issues/ +- Website: https://palletsprojects.com/p/flask/ +- Twitter: https://twitter.com/PalletsTeam +- Chat: https://discord.gg/pallets diff --git a/.vtodo/Lib/site-packages/Flask-2.2.2.dist-info/RECORD b/.vtodo/Lib/site-packages/Flask-2.2.2.dist-info/RECORD new file mode 100644 index 0000000..08845ef --- /dev/null +++ b/.vtodo/Lib/site-packages/Flask-2.2.2.dist-info/RECORD @@ -0,0 +1,54 @@ +../../Scripts/flask.exe,sha256=755m837a5Vg_Zn-iHKQPyZ_pBCXC31h2owjm29C3BMw,106358 +Flask-2.2.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +Flask-2.2.2.dist-info/LICENSE.rst,sha256=SJqOEQhQntmKN7uYPhHg9-HTHwvY-Zp5yESOf_N9B-o,1475 +Flask-2.2.2.dist-info/METADATA,sha256=UXiwRLD1johd_tGlYOlOKXkJFIG82ehR3bxqh4XWFwA,3889 +Flask-2.2.2.dist-info/RECORD,, +Flask-2.2.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +Flask-2.2.2.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92 +Flask-2.2.2.dist-info/entry_points.txt,sha256=s3MqQpduU25y4dq3ftBYD6bMVdVnbMpZP-sUNw0zw0k,41 +Flask-2.2.2.dist-info/top_level.txt,sha256=dvi65F6AeGWVU0TBpYiC04yM60-FX1gJFkK31IKQr5c,6 +flask/__init__.py,sha256=Y4mEWqAMxj_Oxq9eYv3tWyN-0nU9yVKBGK_t6BxqvvM,2890 +flask/__main__.py,sha256=bYt9eEaoRQWdejEHFD8REx9jxVEdZptECFsV7F49Ink,30 +flask/__pycache__/__init__.cpython-310.pyc,, +flask/__pycache__/__main__.cpython-310.pyc,, +flask/__pycache__/app.cpython-310.pyc,, +flask/__pycache__/blueprints.cpython-310.pyc,, +flask/__pycache__/cli.cpython-310.pyc,, +flask/__pycache__/config.cpython-310.pyc,, +flask/__pycache__/ctx.cpython-310.pyc,, +flask/__pycache__/debughelpers.cpython-310.pyc,, +flask/__pycache__/globals.cpython-310.pyc,, +flask/__pycache__/helpers.cpython-310.pyc,, +flask/__pycache__/logging.cpython-310.pyc,, +flask/__pycache__/scaffold.cpython-310.pyc,, +flask/__pycache__/sessions.cpython-310.pyc,, +flask/__pycache__/signals.cpython-310.pyc,, +flask/__pycache__/templating.cpython-310.pyc,, +flask/__pycache__/testing.cpython-310.pyc,, +flask/__pycache__/typing.cpython-310.pyc,, +flask/__pycache__/views.cpython-310.pyc,, +flask/__pycache__/wrappers.cpython-310.pyc,, +flask/app.py,sha256=VfBcGmEVveMcSajkUmDXCEOvAd-2mIBJ355KicvQ4gE,99025 +flask/blueprints.py,sha256=Jbrt-2jlLiFklC3De9EWBioPtDjHYYbXlTDK9Z7L2nk,26936 +flask/cli.py,sha256=foLlD8NiIXcxpxMmRQvvlZPbVM8pxOaJG3sa58c9dAA,33486 +flask/config.py,sha256=IWqHecH4poDxNEUg4U_ZA1CTlL5BKZDX3ofG4UGYyi0,12584 +flask/ctx.py,sha256=ZOGEWuFjsCIk3vm-C9pLME0e4saeBkeGpr2tTSvemSM,14851 +flask/debughelpers.py,sha256=_RvAL3TW5lqMJeCVWtTU6rSDJC7jnRaBL6OEkVmooyU,5511 +flask/globals.py,sha256=1DLZMi8Su-S1gf8zEiR3JPi6VXYIrYqm8C9__Ly66ss,3187 +flask/helpers.py,sha256=ELq27745jihrdyAP9qY8KENlCVDdnWRWTIn35L9a-UU,25334 +flask/json/__init__.py,sha256=TOwldHT3_kFaXHlORKi9yCWt7dbPNB0ovdHHQWlSRzY,11175 +flask/json/__pycache__/__init__.cpython-310.pyc,, +flask/json/__pycache__/provider.cpython-310.pyc,, +flask/json/__pycache__/tag.cpython-310.pyc,, +flask/json/provider.py,sha256=jXCNypf11PN4ngQjEt6LnSdCWQ1yHIAkNLHlXQlCB-A,10674 +flask/json/tag.py,sha256=fys3HBLssWHuMAIJuTcf2K0bCtosePBKXIWASZEEjnU,8857 +flask/logging.py,sha256=WYng0bLTRS_CJrocGcCLJpibHf1lygHE_pg-KoUIQ4w,2293 +flask/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +flask/scaffold.py,sha256=tiQRK-vMY5nucoN6pewXF87GaxrltsCGOgTVsT6wm7s,33443 +flask/sessions.py,sha256=66oGlE-v9iac-eb54tFN3ILAjJ1FeeuHHWw98UVaoxc,15847 +flask/signals.py,sha256=H7QwDciK-dtBxinjKpexpglP0E6k0MJILiFWTItfmqU,2136 +flask/templating.py,sha256=1P4OzvSnA2fsJTYgQT3G4owVKsuOz8XddCiR6jMHGJ0,7419 +flask/testing.py,sha256=p51f9P7jDc_IDSiZug7jypnfVcxsQrMg3B2tnjlpEFw,10596 +flask/typing.py,sha256=KgxegTF9v9WvuongeF8LooIvpZPauzGrq9ZXf3gBlYc,2969 +flask/views.py,sha256=bveWilivkPP-4HB9w_fOusBz6sHNIl0QTqKUFMCltzE,6738 +flask/wrappers.py,sha256=Wa-bhjNdPPveSHS1dpzD_r-ayZxIYFF1DoWncKOafrk,5695 diff --git a/.vtodo/Lib/site-packages/Flask-2.2.2.dist-info/REQUESTED b/.vtodo/Lib/site-packages/Flask-2.2.2.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/.vtodo/Lib/site-packages/Flask-2.2.2.dist-info/WHEEL b/.vtodo/Lib/site-packages/Flask-2.2.2.dist-info/WHEEL new file mode 100644 index 0000000..becc9a6 --- /dev/null +++ b/.vtodo/Lib/site-packages/Flask-2.2.2.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.37.1) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/.vtodo/Lib/site-packages/Flask-2.2.2.dist-info/entry_points.txt b/.vtodo/Lib/site-packages/Flask-2.2.2.dist-info/entry_points.txt new file mode 100644 index 0000000..137232d --- /dev/null +++ b/.vtodo/Lib/site-packages/Flask-2.2.2.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +flask = flask.cli:main diff --git a/.vtodo/Lib/site-packages/Flask-2.2.2.dist-info/top_level.txt b/.vtodo/Lib/site-packages/Flask-2.2.2.dist-info/top_level.txt new file mode 100644 index 0000000..7e10602 --- /dev/null +++ b/.vtodo/Lib/site-packages/Flask-2.2.2.dist-info/top_level.txt @@ -0,0 +1 @@ +flask diff --git a/.vtodo/Lib/site-packages/Jinja2-3.1.2.dist-info/INSTALLER b/.vtodo/Lib/site-packages/Jinja2-3.1.2.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.vtodo/Lib/site-packages/Jinja2-3.1.2.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.vtodo/Lib/site-packages/Jinja2-3.1.2.dist-info/LICENSE.rst b/.vtodo/Lib/site-packages/Jinja2-3.1.2.dist-info/LICENSE.rst new file mode 100644 index 0000000..c37cae4 --- /dev/null +++ b/.vtodo/Lib/site-packages/Jinja2-3.1.2.dist-info/LICENSE.rst @@ -0,0 +1,28 @@ +Copyright 2007 Pallets + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/.vtodo/Lib/site-packages/Jinja2-3.1.2.dist-info/METADATA b/.vtodo/Lib/site-packages/Jinja2-3.1.2.dist-info/METADATA new file mode 100644 index 0000000..f54bb5c --- /dev/null +++ b/.vtodo/Lib/site-packages/Jinja2-3.1.2.dist-info/METADATA @@ -0,0 +1,113 @@ +Metadata-Version: 2.1 +Name: Jinja2 +Version: 3.1.2 +Summary: A very fast and expressive template engine. +Home-page: https://palletsprojects.com/p/jinja/ +Author: Armin Ronacher +Author-email: armin.ronacher@active-4.com +Maintainer: Pallets +Maintainer-email: contact@palletsprojects.com +License: BSD-3-Clause +Project-URL: Donate, https://palletsprojects.com/donate +Project-URL: Documentation, https://jinja.palletsprojects.com/ +Project-URL: Changes, https://jinja.palletsprojects.com/changes/ +Project-URL: Source Code, https://github.com/pallets/jinja/ +Project-URL: Issue Tracker, https://github.com/pallets/jinja/issues/ +Project-URL: Twitter, https://twitter.com/PalletsTeam +Project-URL: Chat, https://discord.gg/pallets +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Web Environment +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content +Classifier: Topic :: Text Processing :: Markup :: HTML +Requires-Python: >=3.7 +Description-Content-Type: text/x-rst +License-File: LICENSE.rst +Requires-Dist: MarkupSafe (>=2.0) +Provides-Extra: i18n +Requires-Dist: Babel (>=2.7) ; extra == 'i18n' + +Jinja +===== + +Jinja is a fast, expressive, extensible templating engine. Special +placeholders in the template allow writing code similar to Python +syntax. Then the template is passed data to render the final document. + +It includes: + +- Template inheritance and inclusion. +- Define and import macros within templates. +- HTML templates can use autoescaping to prevent XSS from untrusted + user input. +- A sandboxed environment can safely render untrusted templates. +- AsyncIO support for generating templates and calling async + functions. +- I18N support with Babel. +- Templates are compiled to optimized Python code just-in-time and + cached, or can be compiled ahead-of-time. +- Exceptions point to the correct line in templates to make debugging + easier. +- Extensible filters, tests, functions, and even syntax. + +Jinja's philosophy is that while application logic belongs in Python if +possible, it shouldn't make the template designer's job difficult by +restricting functionality too much. + + +Installing +---------- + +Install and update using `pip`_: + +.. code-block:: text + + $ pip install -U Jinja2 + +.. _pip: https://pip.pypa.io/en/stable/getting-started/ + + +In A Nutshell +------------- + +.. code-block:: jinja + + {% extends "base.html" %} + {% block title %}Members{% endblock %} + {% block content %} + + {% endblock %} + + +Donate +------ + +The Pallets organization develops and supports Jinja and other popular +packages. In order to grow the community of contributors and users, and +allow the maintainers to devote more time to the projects, `please +donate today`_. + +.. _please donate today: https://palletsprojects.com/donate + + +Links +----- + +- Documentation: https://jinja.palletsprojects.com/ +- Changes: https://jinja.palletsprojects.com/changes/ +- PyPI Releases: https://pypi.org/project/Jinja2/ +- Source Code: https://github.com/pallets/jinja/ +- Issue Tracker: https://github.com/pallets/jinja/issues/ +- Website: https://palletsprojects.com/p/jinja/ +- Twitter: https://twitter.com/PalletsTeam +- Chat: https://discord.gg/pallets + + diff --git a/.vtodo/Lib/site-packages/Jinja2-3.1.2.dist-info/RECORD b/.vtodo/Lib/site-packages/Jinja2-3.1.2.dist-info/RECORD new file mode 100644 index 0000000..af42ee3 --- /dev/null +++ b/.vtodo/Lib/site-packages/Jinja2-3.1.2.dist-info/RECORD @@ -0,0 +1,58 @@ +Jinja2-3.1.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +Jinja2-3.1.2.dist-info/LICENSE.rst,sha256=O0nc7kEF6ze6wQ-vG-JgQI_oXSUrjp3y4JefweCUQ3s,1475 +Jinja2-3.1.2.dist-info/METADATA,sha256=PZ6v2SIidMNixR7MRUX9f7ZWsPwtXanknqiZUmRbh4U,3539 +Jinja2-3.1.2.dist-info/RECORD,, +Jinja2-3.1.2.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92 +Jinja2-3.1.2.dist-info/entry_points.txt,sha256=zRd62fbqIyfUpsRtU7EVIFyiu1tPwfgO7EvPErnxgTE,59 +Jinja2-3.1.2.dist-info/top_level.txt,sha256=PkeVWtLb3-CqjWi1fO29OCbj55EhX_chhKrCdrVe_zs,7 +jinja2/__init__.py,sha256=8vGduD8ytwgD6GDSqpYc2m3aU-T7PKOAddvVXgGr_Fs,1927 +jinja2/__pycache__/__init__.cpython-310.pyc,, +jinja2/__pycache__/_identifier.cpython-310.pyc,, +jinja2/__pycache__/async_utils.cpython-310.pyc,, +jinja2/__pycache__/bccache.cpython-310.pyc,, +jinja2/__pycache__/compiler.cpython-310.pyc,, +jinja2/__pycache__/constants.cpython-310.pyc,, +jinja2/__pycache__/debug.cpython-310.pyc,, +jinja2/__pycache__/defaults.cpython-310.pyc,, +jinja2/__pycache__/environment.cpython-310.pyc,, +jinja2/__pycache__/exceptions.cpython-310.pyc,, +jinja2/__pycache__/ext.cpython-310.pyc,, +jinja2/__pycache__/filters.cpython-310.pyc,, +jinja2/__pycache__/idtracking.cpython-310.pyc,, +jinja2/__pycache__/lexer.cpython-310.pyc,, +jinja2/__pycache__/loaders.cpython-310.pyc,, +jinja2/__pycache__/meta.cpython-310.pyc,, +jinja2/__pycache__/nativetypes.cpython-310.pyc,, +jinja2/__pycache__/nodes.cpython-310.pyc,, +jinja2/__pycache__/optimizer.cpython-310.pyc,, +jinja2/__pycache__/parser.cpython-310.pyc,, +jinja2/__pycache__/runtime.cpython-310.pyc,, +jinja2/__pycache__/sandbox.cpython-310.pyc,, +jinja2/__pycache__/tests.cpython-310.pyc,, +jinja2/__pycache__/utils.cpython-310.pyc,, +jinja2/__pycache__/visitor.cpython-310.pyc,, +jinja2/_identifier.py,sha256=_zYctNKzRqlk_murTNlzrju1FFJL7Va_Ijqqd7ii2lU,1958 +jinja2/async_utils.py,sha256=dHlbTeaxFPtAOQEYOGYh_PHcDT0rsDaUJAFDl_0XtTg,2472 +jinja2/bccache.py,sha256=mhz5xtLxCcHRAa56azOhphIAe19u1we0ojifNMClDio,14061 +jinja2/compiler.py,sha256=Gs-N8ThJ7OWK4-reKoO8Wh1ZXz95MVphBKNVf75qBr8,72172 +jinja2/constants.py,sha256=GMoFydBF_kdpaRKPoM5cl5MviquVRLVyZtfp5-16jg0,1433 +jinja2/debug.py,sha256=iWJ432RadxJNnaMOPrjIDInz50UEgni3_HKuFXi2vuQ,6299 +jinja2/defaults.py,sha256=boBcSw78h-lp20YbaXSJsqkAI2uN_mD_TtCydpeq5wU,1267 +jinja2/environment.py,sha256=6uHIcc7ZblqOMdx_uYNKqRnnwAF0_nzbyeMP9FFtuh4,61349 +jinja2/exceptions.py,sha256=ioHeHrWwCWNaXX1inHmHVblvc4haO7AXsjCp3GfWvx0,5071 +jinja2/ext.py,sha256=ivr3P7LKbddiXDVez20EflcO3q2aHQwz9P_PgWGHVqE,31502 +jinja2/filters.py,sha256=9js1V-h2RlyW90IhLiBGLM2U-k6SCy2F4BUUMgB3K9Q,53509 +jinja2/idtracking.py,sha256=GfNmadir4oDALVxzn3DL9YInhJDr69ebXeA2ygfuCGA,10704 +jinja2/lexer.py,sha256=DW2nX9zk-6MWp65YR2bqqj0xqCvLtD-u9NWT8AnFRxQ,29726 +jinja2/loaders.py,sha256=BfptfvTVpClUd-leMkHczdyPNYFzp_n7PKOJ98iyHOg,23207 +jinja2/meta.py,sha256=GNPEvifmSaU3CMxlbheBOZjeZ277HThOPUTf1RkppKQ,4396 +jinja2/nativetypes.py,sha256=DXgORDPRmVWgy034H0xL8eF7qYoK3DrMxs-935d0Fzk,4226 +jinja2/nodes.py,sha256=i34GPRAZexXMT6bwuf5SEyvdmS-bRCy9KMjwN5O6pjk,34550 +jinja2/optimizer.py,sha256=tHkMwXxfZkbfA1KmLcqmBMSaz7RLIvvItrJcPoXTyD8,1650 +jinja2/parser.py,sha256=nHd-DFHbiygvfaPtm9rcQXJChZG7DPsWfiEsqfwKerY,39595 +jinja2/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +jinja2/runtime.py,sha256=5CmD5BjbEJxSiDNTFBeKCaq8qU4aYD2v6q2EluyExms,33476 +jinja2/sandbox.py,sha256=Y0xZeXQnH6EX5VjaV2YixESxoepnRbW_3UeQosaBU3M,14584 +jinja2/tests.py,sha256=Am5Z6Lmfr2XaH_npIfJJ8MdXtWsbLjMULZJulTAj30E,5905 +jinja2/utils.py,sha256=u9jXESxGn8ATZNVolwmkjUVu4SA-tLgV0W7PcSfPfdQ,23965 +jinja2/visitor.py,sha256=MH14C6yq24G_KVtWzjwaI7Wg14PCJIYlWW1kpkxYak0,3568 diff --git a/.vtodo/Lib/site-packages/Jinja2-3.1.2.dist-info/WHEEL b/.vtodo/Lib/site-packages/Jinja2-3.1.2.dist-info/WHEEL new file mode 100644 index 0000000..becc9a6 --- /dev/null +++ b/.vtodo/Lib/site-packages/Jinja2-3.1.2.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.37.1) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/.vtodo/Lib/site-packages/Jinja2-3.1.2.dist-info/entry_points.txt b/.vtodo/Lib/site-packages/Jinja2-3.1.2.dist-info/entry_points.txt new file mode 100644 index 0000000..7b9666c --- /dev/null +++ b/.vtodo/Lib/site-packages/Jinja2-3.1.2.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[babel.extractors] +jinja2 = jinja2.ext:babel_extract[i18n] diff --git a/.vtodo/Lib/site-packages/Jinja2-3.1.2.dist-info/top_level.txt b/.vtodo/Lib/site-packages/Jinja2-3.1.2.dist-info/top_level.txt new file mode 100644 index 0000000..7f7afbf --- /dev/null +++ b/.vtodo/Lib/site-packages/Jinja2-3.1.2.dist-info/top_level.txt @@ -0,0 +1 @@ +jinja2 diff --git a/.vtodo/Lib/site-packages/MarkupSafe-2.1.1.dist-info/INSTALLER b/.vtodo/Lib/site-packages/MarkupSafe-2.1.1.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.vtodo/Lib/site-packages/MarkupSafe-2.1.1.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.vtodo/Lib/site-packages/MarkupSafe-2.1.1.dist-info/LICENSE.rst b/.vtodo/Lib/site-packages/MarkupSafe-2.1.1.dist-info/LICENSE.rst new file mode 100644 index 0000000..9d227a0 --- /dev/null +++ b/.vtodo/Lib/site-packages/MarkupSafe-2.1.1.dist-info/LICENSE.rst @@ -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/.vtodo/Lib/site-packages/MarkupSafe-2.1.1.dist-info/METADATA b/.vtodo/Lib/site-packages/MarkupSafe-2.1.1.dist-info/METADATA new file mode 100644 index 0000000..485a5e0 --- /dev/null +++ b/.vtodo/Lib/site-packages/MarkupSafe-2.1.1.dist-info/METADATA @@ -0,0 +1,101 @@ +Metadata-Version: 2.1 +Name: MarkupSafe +Version: 2.1.1 +Summary: Safely add untrusted strings to HTML/XML markup. +Home-page: https://palletsprojects.com/p/markupsafe/ +Author: Armin Ronacher +Author-email: armin.ronacher@active-4.com +Maintainer: Pallets +Maintainer-email: contact@palletsprojects.com +License: BSD-3-Clause +Project-URL: Donate, https://palletsprojects.com/donate +Project-URL: Documentation, https://markupsafe.palletsprojects.com/ +Project-URL: Changes, https://markupsafe.palletsprojects.com/changes/ +Project-URL: Source Code, https://github.com/pallets/markupsafe/ +Project-URL: Issue Tracker, https://github.com/pallets/markupsafe/issues/ +Project-URL: Twitter, https://twitter.com/PalletsTeam +Project-URL: Chat, https://discord.gg/pallets +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Web Environment +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content +Classifier: Topic :: Text Processing :: Markup :: HTML +Requires-Python: >=3.7 +Description-Content-Type: text/x-rst +License-File: LICENSE.rst + +MarkupSafe +========== + +MarkupSafe implements a text object that escapes characters so it is +safe to use in HTML and XML. Characters that have special meanings are +replaced so that they display as the actual characters. This mitigates +injection attacks, meaning untrusted user input can safely be displayed +on a page. + + +Installing +---------- + +Install and update using `pip`_: + +.. code-block:: text + + pip install -U MarkupSafe + +.. _pip: https://pip.pypa.io/en/stable/getting-started/ + + +Examples +-------- + +.. code-block:: pycon + + >>> from markupsafe import Markup, escape + + >>> # escape replaces special characters and wraps in Markup + >>> escape("") + Markup('<script>alert(document.cookie);</script>') + + >>> # wrap in Markup to mark text "safe" and prevent escaping + >>> Markup("Hello") + Markup('hello') + + >>> escape(Markup("Hello")) + Markup('hello') + + >>> # Markup is a str subclass + >>> # methods and operators escape their arguments + >>> template = Markup("Hello {name}") + >>> template.format(name='"World"') + Markup('Hello "World"') + + +Donate +------ + +The Pallets organization develops and supports MarkupSafe and other +popular packages. In order to grow the community of contributors and +users, and allow the maintainers to devote more time to the projects, +`please donate today`_. + +.. _please donate today: https://palletsprojects.com/donate + + +Links +----- + +- Documentation: https://markupsafe.palletsprojects.com/ +- Changes: https://markupsafe.palletsprojects.com/changes/ +- PyPI Releases: https://pypi.org/project/MarkupSafe/ +- Source Code: https://github.com/pallets/markupsafe/ +- Issue Tracker: https://github.com/pallets/markupsafe/issues/ +- Website: https://palletsprojects.com/p/markupsafe/ +- Twitter: https://twitter.com/PalletsTeam +- Chat: https://discord.gg/pallets + + diff --git a/.vtodo/Lib/site-packages/MarkupSafe-2.1.1.dist-info/RECORD b/.vtodo/Lib/site-packages/MarkupSafe-2.1.1.dist-info/RECORD new file mode 100644 index 0000000..4801a06 --- /dev/null +++ b/.vtodo/Lib/site-packages/MarkupSafe-2.1.1.dist-info/RECORD @@ -0,0 +1,14 @@ +MarkupSafe-2.1.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +MarkupSafe-2.1.1.dist-info/LICENSE.rst,sha256=RjHsDbX9kKVH4zaBcmTGeYIUM4FG-KyUtKV_lu6MnsQ,1503 +MarkupSafe-2.1.1.dist-info/METADATA,sha256=DC93VszmzjLQcrVChRUjtW4XbUwjTdbaplpgdlbFdbs,3242 +MarkupSafe-2.1.1.dist-info/RECORD,, +MarkupSafe-2.1.1.dist-info/WHEEL,sha256=C6CHup2HLC2Rld8AL5u9w89MYULjdaP5k0k7SG83CcI,102 +MarkupSafe-2.1.1.dist-info/top_level.txt,sha256=qy0Plje5IJuvsCBjejJyhDCjEAdcDLK_2agVcex8Z6U,11 +markupsafe/__init__.py,sha256=XGdbhy_OLrsQ5ZoFV3V6HQ3GeJ_ojcabRl_8yqehISk,9579 +markupsafe/__pycache__/__init__.cpython-310.pyc,, +markupsafe/__pycache__/_native.cpython-310.pyc,, +markupsafe/_native.py,sha256=_Q7UsXCOvgdonCgqG3l5asANI6eo50EKnDM-mlwEC5M,1776 +markupsafe/_speedups.c,sha256=n3jzzaJwXcoN8nTFyA53f3vSqsWK2vujI-v6QYifjhQ,7403 +markupsafe/_speedups.cp310-win_amd64.pyd,sha256=Mh72D6F52No2JwGW5GRZfeQKwRrES4u8uZFnw_nN4vk,15872 +markupsafe/_speedups.pyi,sha256=f5QtwIOP0eLrxh2v5p6SmaYmlcHIGIfmz0DovaqL0OU,238 +markupsafe/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/.vtodo/Lib/site-packages/MarkupSafe-2.1.1.dist-info/WHEEL b/.vtodo/Lib/site-packages/MarkupSafe-2.1.1.dist-info/WHEEL new file mode 100644 index 0000000..ab9f74a --- /dev/null +++ b/.vtodo/Lib/site-packages/MarkupSafe-2.1.1.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.37.0) +Root-Is-Purelib: false +Tag: cp310-cp310-win_amd64 + diff --git a/.vtodo/Lib/site-packages/MarkupSafe-2.1.1.dist-info/top_level.txt b/.vtodo/Lib/site-packages/MarkupSafe-2.1.1.dist-info/top_level.txt new file mode 100644 index 0000000..75bf729 --- /dev/null +++ b/.vtodo/Lib/site-packages/MarkupSafe-2.1.1.dist-info/top_level.txt @@ -0,0 +1 @@ +markupsafe diff --git a/.vtodo/Lib/site-packages/Werkzeug-2.2.2.dist-info/INSTALLER b/.vtodo/Lib/site-packages/Werkzeug-2.2.2.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.vtodo/Lib/site-packages/Werkzeug-2.2.2.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.vtodo/Lib/site-packages/Werkzeug-2.2.2.dist-info/LICENSE.rst b/.vtodo/Lib/site-packages/Werkzeug-2.2.2.dist-info/LICENSE.rst new file mode 100644 index 0000000..c37cae4 --- /dev/null +++ b/.vtodo/Lib/site-packages/Werkzeug-2.2.2.dist-info/LICENSE.rst @@ -0,0 +1,28 @@ +Copyright 2007 Pallets + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/.vtodo/Lib/site-packages/Werkzeug-2.2.2.dist-info/METADATA b/.vtodo/Lib/site-packages/Werkzeug-2.2.2.dist-info/METADATA new file mode 100644 index 0000000..a40cd1b --- /dev/null +++ b/.vtodo/Lib/site-packages/Werkzeug-2.2.2.dist-info/METADATA @@ -0,0 +1,126 @@ +Metadata-Version: 2.1 +Name: Werkzeug +Version: 2.2.2 +Summary: The comprehensive WSGI web application library. +Home-page: https://palletsprojects.com/p/werkzeug/ +Author: Armin Ronacher +Author-email: armin.ronacher@active-4.com +Maintainer: Pallets +Maintainer-email: contact@palletsprojects.com +License: BSD-3-Clause +Project-URL: Donate, https://palletsprojects.com/donate +Project-URL: Documentation, https://werkzeug.palletsprojects.com/ +Project-URL: Changes, https://werkzeug.palletsprojects.com/changes/ +Project-URL: Source Code, https://github.com/pallets/werkzeug/ +Project-URL: Issue Tracker, https://github.com/pallets/werkzeug/issues/ +Project-URL: Twitter, https://twitter.com/PalletsTeam +Project-URL: Chat, https://discord.gg/pallets +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Web Environment +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content +Classifier: Topic :: Internet :: WWW/HTTP :: WSGI +Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application +Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware +Classifier: Topic :: Software Development :: Libraries :: Application Frameworks +Requires-Python: >=3.7 +Description-Content-Type: text/x-rst +License-File: LICENSE.rst +Requires-Dist: MarkupSafe (>=2.1.1) +Provides-Extra: watchdog +Requires-Dist: watchdog ; extra == 'watchdog' + +Werkzeug +======== + +*werkzeug* German noun: "tool". Etymology: *werk* ("work"), *zeug* ("stuff") + +Werkzeug is a comprehensive `WSGI`_ web application library. It began as +a simple collection of various utilities for WSGI applications and has +become one of the most advanced WSGI utility libraries. + +It includes: + +- An interactive debugger that allows inspecting stack traces and + source code in the browser with an interactive interpreter for any + frame in the stack. +- A full-featured request object with objects to interact with + headers, query args, form data, files, and cookies. +- A response object that can wrap other WSGI applications and handle + streaming data. +- A routing system for matching URLs to endpoints and generating URLs + for endpoints, with an extensible system for capturing variables + from URLs. +- HTTP utilities to handle entity tags, cache control, dates, user + agents, cookies, files, and more. +- A threaded WSGI server for use while developing applications + locally. +- A test client for simulating HTTP requests during testing without + requiring running a server. + +Werkzeug doesn't enforce any dependencies. It is up to the developer to +choose a template engine, database adapter, and even how to handle +requests. It can be used to build all sorts of end user applications +such as blogs, wikis, or bulletin boards. + +`Flask`_ wraps Werkzeug, using it to handle the details of WSGI while +providing more structure and patterns for defining powerful +applications. + +.. _WSGI: https://wsgi.readthedocs.io/en/latest/ +.. _Flask: https://www.palletsprojects.com/p/flask/ + + +Installing +---------- + +Install and update using `pip`_: + +.. code-block:: text + + pip install -U Werkzeug + +.. _pip: https://pip.pypa.io/en/stable/getting-started/ + + +A Simple Example +---------------- + +.. code-block:: python + + from werkzeug.wrappers import Request, Response + + @Request.application + def application(request): + return Response('Hello, World!') + + if __name__ == '__main__': + from werkzeug.serving import run_simple + run_simple('localhost', 4000, application) + + +Donate +------ + +The Pallets organization develops and supports Werkzeug and other +popular packages. In order to grow the community of contributors and +users, and allow the maintainers to devote more time to the projects, +`please donate today`_. + +.. _please donate today: https://palletsprojects.com/donate + + +Links +----- + +- Documentation: https://werkzeug.palletsprojects.com/ +- Changes: https://werkzeug.palletsprojects.com/changes/ +- PyPI Releases: https://pypi.org/project/Werkzeug/ +- Source Code: https://github.com/pallets/werkzeug/ +- Issue Tracker: https://github.com/pallets/werkzeug/issues/ +- Website: https://palletsprojects.com/p/werkzeug/ +- Twitter: https://twitter.com/PalletsTeam +- Chat: https://discord.gg/pallets diff --git a/.vtodo/Lib/site-packages/Werkzeug-2.2.2.dist-info/RECORD b/.vtodo/Lib/site-packages/Werkzeug-2.2.2.dist-info/RECORD new file mode 100644 index 0000000..af07154 --- /dev/null +++ b/.vtodo/Lib/site-packages/Werkzeug-2.2.2.dist-info/RECORD @@ -0,0 +1,98 @@ +Werkzeug-2.2.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +Werkzeug-2.2.2.dist-info/LICENSE.rst,sha256=O0nc7kEF6ze6wQ-vG-JgQI_oXSUrjp3y4JefweCUQ3s,1475 +Werkzeug-2.2.2.dist-info/METADATA,sha256=hz42ndovEQQy3rwXKZDwR7LA4UNthKegxf_7xIQrjsM,4416 +Werkzeug-2.2.2.dist-info/RECORD,, +Werkzeug-2.2.2.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92 +Werkzeug-2.2.2.dist-info/top_level.txt,sha256=QRyj2VjwJoQkrwjwFIOlB8Xg3r9un0NtqVHQF-15xaw,9 +werkzeug/__init__.py,sha256=UP218Ddd2NYm1dUhTlhvGRQytzAx1Ms1A716UKiPOYk,188 +werkzeug/__pycache__/__init__.cpython-310.pyc,, +werkzeug/__pycache__/_internal.cpython-310.pyc,, +werkzeug/__pycache__/_reloader.cpython-310.pyc,, +werkzeug/__pycache__/datastructures.cpython-310.pyc,, +werkzeug/__pycache__/exceptions.cpython-310.pyc,, +werkzeug/__pycache__/formparser.cpython-310.pyc,, +werkzeug/__pycache__/http.cpython-310.pyc,, +werkzeug/__pycache__/local.cpython-310.pyc,, +werkzeug/__pycache__/security.cpython-310.pyc,, +werkzeug/__pycache__/serving.cpython-310.pyc,, +werkzeug/__pycache__/test.cpython-310.pyc,, +werkzeug/__pycache__/testapp.cpython-310.pyc,, +werkzeug/__pycache__/urls.cpython-310.pyc,, +werkzeug/__pycache__/user_agent.cpython-310.pyc,, +werkzeug/__pycache__/utils.cpython-310.pyc,, +werkzeug/__pycache__/wsgi.cpython-310.pyc,, +werkzeug/_internal.py,sha256=g8PHJz2z39I3x0vwTvTKbXIg0eUQqGF9UtUzDMWT0Qw,16222 +werkzeug/_reloader.py,sha256=lYStlIDduTxBOB8BSozy_44HQ7YT5fup-x3uuac1-2o,14331 +werkzeug/datastructures.py,sha256=T1SRE_KRuNz9Q7P-Ck4PyKPyil1NOx9zDuNMLgrN1Z0,97083 +werkzeug/datastructures.pyi,sha256=HRzDLc7A6qnwluhNqn6AT76CsLZIkAbVVqxn0AbfV-s,34506 +werkzeug/debug/__init__.py,sha256=Gpq6OpS6mHwHk0mJkHc2fWvvjo6ccJVS9QJwJgoeb9I,18893 +werkzeug/debug/__pycache__/__init__.cpython-310.pyc,, +werkzeug/debug/__pycache__/console.cpython-310.pyc,, +werkzeug/debug/__pycache__/repr.cpython-310.pyc,, +werkzeug/debug/__pycache__/tbtools.cpython-310.pyc,, +werkzeug/debug/console.py,sha256=dechqiCtHfs0AQZWZofUC1S97tCuvwDgT0gdha5KwWM,6208 +werkzeug/debug/repr.py,sha256=FFczy4yhVfEQjW99HuZtUce-ebtJWMjp9GnfasXa0KA,9488 +werkzeug/debug/shared/ICON_LICENSE.md,sha256=DhA6Y1gUl5Jwfg0NFN9Rj4VWITt8tUx0IvdGf0ux9-s,222 +werkzeug/debug/shared/console.png,sha256=bxax6RXXlvOij_KeqvSNX0ojJf83YbnZ7my-3Gx9w2A,507 +werkzeug/debug/shared/debugger.js,sha256=tg42SZs1SVmYWZ-_Fj5ELK5-FLHnGNQrei0K2By8Bw8,10521 +werkzeug/debug/shared/less.png,sha256=-4-kNRaXJSONVLahrQKUxMwXGm9R4OnZ9SxDGpHlIR4,191 +werkzeug/debug/shared/more.png,sha256=GngN7CioHQoV58rH6ojnkYi8c_qED2Aka5FO5UXrReY,200 +werkzeug/debug/shared/style.css,sha256=-xSxzUEZGw_IqlDR5iZxitNl8LQUjBM-_Y4UAvXVH8g,6078 +werkzeug/debug/tbtools.py,sha256=Fsmlk6Ao3CcXm9iX7i_8MhCp2SQZ8uHm8Cf5wacnlW4,13293 +werkzeug/exceptions.py,sha256=5MFy6RyaU4nokoYzdDafloY51QUDIGVNKeK_FORUFS0,26543 +werkzeug/formparser.py,sha256=rLEu_ZwVpvqshZg6E4Qiv36QsmzmCytTijBeGX3dDGk,16056 +werkzeug/http.py,sha256=i_LrIU9KsOz27zfkwKIK6eifFuFMKgSuW15k57HbMiE,42162 +werkzeug/local.py,sha256=1IRMV9MFrauLaZeliF0Md1n7ZOcOKLbS03bnQ8Gz5WY,22326 +werkzeug/middleware/__init__.py,sha256=qfqgdT5npwG9ses3-FXQJf3aB95JYP1zchetH_T3PUw,500 +werkzeug/middleware/__pycache__/__init__.cpython-310.pyc,, +werkzeug/middleware/__pycache__/dispatcher.cpython-310.pyc,, +werkzeug/middleware/__pycache__/http_proxy.cpython-310.pyc,, +werkzeug/middleware/__pycache__/lint.cpython-310.pyc,, +werkzeug/middleware/__pycache__/profiler.cpython-310.pyc,, +werkzeug/middleware/__pycache__/proxy_fix.cpython-310.pyc,, +werkzeug/middleware/__pycache__/shared_data.cpython-310.pyc,, +werkzeug/middleware/dispatcher.py,sha256=Fh_w-KyWnTSYF-Lfv5dimQ7THSS7afPAZMmvc4zF1gg,2580 +werkzeug/middleware/http_proxy.py,sha256=HE8VyhS7CR-E1O6_9b68huv8FLgGGR1DLYqkS3Xcp3Q,7558 +werkzeug/middleware/lint.py,sha256=Sr6gV4royDs6ezkqv5trRAyKMDQ60KaEq3-tQ3opUvw,13968 +werkzeug/middleware/profiler.py,sha256=QkXk7cqnaPnF8wQu-5SyPCIOT3_kdABUBorQOghVNOA,4899 +werkzeug/middleware/proxy_fix.py,sha256=l7LC_LDu0Yd4SvUxS5SFigAJMzcIOGm6LNKl9IXJBSU,6974 +werkzeug/middleware/shared_data.py,sha256=fXjrEkuqxUVLG1DLrOdQLc96QQdjftCBZ1oM5oK89h4,9528 +werkzeug/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +werkzeug/routing/__init__.py,sha256=HpvahY7WwkLdV4Cq3Bsc3GrqNon4u6t8-vhbb9E5o00,4819 +werkzeug/routing/__pycache__/__init__.cpython-310.pyc,, +werkzeug/routing/__pycache__/converters.cpython-310.pyc,, +werkzeug/routing/__pycache__/exceptions.cpython-310.pyc,, +werkzeug/routing/__pycache__/map.cpython-310.pyc,, +werkzeug/routing/__pycache__/matcher.cpython-310.pyc,, +werkzeug/routing/__pycache__/rules.cpython-310.pyc,, +werkzeug/routing/converters.py,sha256=05bkekg64vLC6mqqK4ddBh589WH9yBsjtW8IJhdUBvw,6968 +werkzeug/routing/exceptions.py,sha256=RklUDL9ajOv2fTcRNj4pb18Bs4Y-GKk4rIeTSfsqkks,4737 +werkzeug/routing/map.py,sha256=XN4ZjzEF1SfMxtdov89SDE-1_U78KVnnobTfnHzqbmE,36757 +werkzeug/routing/matcher.py,sha256=U8xZTB3e5f3TgbkxdDyVuyxK4w72l1lo_b3tdG2zNrc,7122 +werkzeug/routing/rules.py,sha256=v27RaR5H3sIPRdJ_pdEfOBMN6EivFVpmFzJk7aizdyw,31072 +werkzeug/sansio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +werkzeug/sansio/__pycache__/__init__.cpython-310.pyc,, +werkzeug/sansio/__pycache__/http.cpython-310.pyc,, +werkzeug/sansio/__pycache__/multipart.cpython-310.pyc,, +werkzeug/sansio/__pycache__/request.cpython-310.pyc,, +werkzeug/sansio/__pycache__/response.cpython-310.pyc,, +werkzeug/sansio/__pycache__/utils.cpython-310.pyc,, +werkzeug/sansio/http.py,sha256=9eORg44CDxpmV9i_U_pZ_NR8gdc9UXFCdE7EAP1v-c0,5162 +werkzeug/sansio/multipart.py,sha256=Uyrg2U6s2oft8LXOyuTvFCWTLOEr7INVW8zFTXNwZ7A,9756 +werkzeug/sansio/request.py,sha256=SiGcx2cz-l81TlCCrKrT2fePqC64hN8fSg5Ig6J6vRs,20175 +werkzeug/sansio/response.py,sha256=UTl-teQDDjovrZMkjj3ZQsHw-JtiFak5JfKEk1_vBYU,26026 +werkzeug/sansio/utils.py,sha256=EjbqdHdT-JZWgjUQaaWSgBUIRprXUkrsMQQqJlJHpVU,4847 +werkzeug/security.py,sha256=vrBofh4WZZoUo1eAdJ6F1DrzVRlYauGS2CUDYpbQKj8,4658 +werkzeug/serving.py,sha256=18pfjrHw8b5UCgPPo1ZEoxlYZZ5UREl4jQ9f8LGWMAo,38458 +werkzeug/test.py,sha256=t7T5G-HciIlv1ZXtlydFVpow0VrXnJ_Y3yyEB7T0_Ww,48125 +werkzeug/testapp.py,sha256=RJhT_2JweNiMKe304N3bF1zaIeMpRx-CIMERdeydfTY,9404 +werkzeug/urls.py,sha256=Q9Si-eVh7yxk3rwkzrwGRm146FXVXgg9lBP3k0HUfVM,36600 +werkzeug/user_agent.py,sha256=WclZhpvgLurMF45hsioSbS75H1Zb4iMQGKN3_yZ2oKo,1420 +werkzeug/utils.py,sha256=OYdB2cZPYYgq3C0EVKMIv05BrYzzr9xdefW0H00_IVo,24936 +werkzeug/wrappers/__init__.py,sha256=kGyK7rOud3qCxll_jFyW15YarJhj1xtdf3ocx9ZheB8,120 +werkzeug/wrappers/__pycache__/__init__.cpython-310.pyc,, +werkzeug/wrappers/__pycache__/request.cpython-310.pyc,, +werkzeug/wrappers/__pycache__/response.cpython-310.pyc,, +werkzeug/wrappers/request.py,sha256=UQ559KkGS0Po6HTBgvKMlk1_AsNw5zstzm8o_dRrfdQ,23415 +werkzeug/wrappers/response.py,sha256=c2HUXrrt5Sf8-XEB1fUXxm6jp7Lu80KR0A_tbQFvw1Q,34750 +werkzeug/wsgi.py,sha256=sgkFCzhl23hlSmbvjxbI-hVEjSlPuEBGTDAHmXFcAts,34732 diff --git a/.vtodo/Lib/site-packages/Werkzeug-2.2.2.dist-info/WHEEL b/.vtodo/Lib/site-packages/Werkzeug-2.2.2.dist-info/WHEEL new file mode 100644 index 0000000..becc9a6 --- /dev/null +++ b/.vtodo/Lib/site-packages/Werkzeug-2.2.2.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.37.1) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/.vtodo/Lib/site-packages/Werkzeug-2.2.2.dist-info/top_level.txt b/.vtodo/Lib/site-packages/Werkzeug-2.2.2.dist-info/top_level.txt new file mode 100644 index 0000000..6fe8da8 --- /dev/null +++ b/.vtodo/Lib/site-packages/Werkzeug-2.2.2.dist-info/top_level.txt @@ -0,0 +1 @@ +werkzeug diff --git a/.vtodo/Lib/site-packages/click-8.1.3.dist-info/INSTALLER b/.vtodo/Lib/site-packages/click-8.1.3.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.vtodo/Lib/site-packages/click-8.1.3.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.vtodo/Lib/site-packages/click-8.1.3.dist-info/LICENSE.rst b/.vtodo/Lib/site-packages/click-8.1.3.dist-info/LICENSE.rst new file mode 100644 index 0000000..d12a849 --- /dev/null +++ b/.vtodo/Lib/site-packages/click-8.1.3.dist-info/LICENSE.rst @@ -0,0 +1,28 @@ +Copyright 2014 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/.vtodo/Lib/site-packages/click-8.1.3.dist-info/METADATA b/.vtodo/Lib/site-packages/click-8.1.3.dist-info/METADATA new file mode 100644 index 0000000..8e5dc1e --- /dev/null +++ b/.vtodo/Lib/site-packages/click-8.1.3.dist-info/METADATA @@ -0,0 +1,111 @@ +Metadata-Version: 2.1 +Name: click +Version: 8.1.3 +Summary: Composable command line interface toolkit +Home-page: https://palletsprojects.com/p/click/ +Author: Armin Ronacher +Author-email: armin.ronacher@active-4.com +Maintainer: Pallets +Maintainer-email: contact@palletsprojects.com +License: BSD-3-Clause +Project-URL: Donate, https://palletsprojects.com/donate +Project-URL: Documentation, https://click.palletsprojects.com/ +Project-URL: Changes, https://click.palletsprojects.com/changes/ +Project-URL: Source Code, https://github.com/pallets/click/ +Project-URL: Issue Tracker, https://github.com/pallets/click/issues/ +Project-URL: Twitter, https://twitter.com/PalletsTeam +Project-URL: Chat, https://discord.gg/pallets +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Requires-Python: >=3.7 +Description-Content-Type: text/x-rst +License-File: LICENSE.rst +Requires-Dist: colorama ; platform_system == "Windows" +Requires-Dist: importlib-metadata ; python_version < "3.8" + +\$ click\_ +========== + +Click is a Python package for creating beautiful command line interfaces +in a composable way with as little code as necessary. It's the "Command +Line Interface Creation Kit". It's highly configurable but comes with +sensible defaults out of the box. + +It aims to make the process of writing command line tools quick and fun +while also preventing any frustration caused by the inability to +implement an intended CLI API. + +Click in three points: + +- Arbitrary nesting of commands +- Automatic help page generation +- Supports lazy loading of subcommands at runtime + + +Installing +---------- + +Install and update using `pip`_: + +.. code-block:: text + + $ pip install -U click + +.. _pip: https://pip.pypa.io/en/stable/getting-started/ + + +A Simple Example +---------------- + +.. code-block:: python + + import click + + @click.command() + @click.option("--count", default=1, help="Number of greetings.") + @click.option("--name", prompt="Your name", help="The person to greet.") + def hello(count, name): + """Simple program that greets NAME for a total of COUNT times.""" + for _ in range(count): + click.echo(f"Hello, {name}!") + + if __name__ == '__main__': + hello() + +.. code-block:: text + + $ python hello.py --count=3 + Your name: Click + Hello, Click! + Hello, Click! + Hello, Click! + + +Donate +------ + +The Pallets organization develops and supports Click and other popular +packages. In order to grow the community of contributors and users, and +allow the maintainers to devote more time to the projects, `please +donate today`_. + +.. _please donate today: https://palletsprojects.com/donate + + +Links +----- + +- Documentation: https://click.palletsprojects.com/ +- Changes: https://click.palletsprojects.com/changes/ +- PyPI Releases: https://pypi.org/project/click/ +- Source Code: https://github.com/pallets/click +- Issue Tracker: https://github.com/pallets/click/issues +- Website: https://palletsprojects.com/p/click +- Twitter: https://twitter.com/PalletsTeam +- Chat: https://discord.gg/pallets + + diff --git a/.vtodo/Lib/site-packages/click-8.1.3.dist-info/RECORD b/.vtodo/Lib/site-packages/click-8.1.3.dist-info/RECORD new file mode 100644 index 0000000..0fd3496 --- /dev/null +++ b/.vtodo/Lib/site-packages/click-8.1.3.dist-info/RECORD @@ -0,0 +1,39 @@ +click-8.1.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +click-8.1.3.dist-info/LICENSE.rst,sha256=morRBqOU6FO_4h9C9OctWSgZoigF2ZG18ydQKSkrZY0,1475 +click-8.1.3.dist-info/METADATA,sha256=tFJIX5lOjx7c5LjZbdTPFVDJSgyv9F74XY0XCPp_gnc,3247 +click-8.1.3.dist-info/RECORD,, +click-8.1.3.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92 +click-8.1.3.dist-info/top_level.txt,sha256=J1ZQogalYS4pphY_lPECoNMfw0HzTSrZglC4Yfwo4xA,6 +click/__init__.py,sha256=rQBLutqg-z6m8nOzivIfigDn_emijB_dKv9BZ2FNi5s,3138 +click/__pycache__/__init__.cpython-310.pyc,, +click/__pycache__/_compat.cpython-310.pyc,, +click/__pycache__/_termui_impl.cpython-310.pyc,, +click/__pycache__/_textwrap.cpython-310.pyc,, +click/__pycache__/_winconsole.cpython-310.pyc,, +click/__pycache__/core.cpython-310.pyc,, +click/__pycache__/decorators.cpython-310.pyc,, +click/__pycache__/exceptions.cpython-310.pyc,, +click/__pycache__/formatting.cpython-310.pyc,, +click/__pycache__/globals.cpython-310.pyc,, +click/__pycache__/parser.cpython-310.pyc,, +click/__pycache__/shell_completion.cpython-310.pyc,, +click/__pycache__/termui.cpython-310.pyc,, +click/__pycache__/testing.cpython-310.pyc,, +click/__pycache__/types.cpython-310.pyc,, +click/__pycache__/utils.cpython-310.pyc,, +click/_compat.py,sha256=JIHLYs7Jzz4KT9t-ds4o4jBzLjnwCiJQKqur-5iwCKI,18810 +click/_termui_impl.py,sha256=qK6Cfy4mRFxvxE8dya8RBhLpSC8HjF-lvBc6aNrPdwg,23451 +click/_textwrap.py,sha256=10fQ64OcBUMuK7mFvh8363_uoOxPlRItZBmKzRJDgoY,1353 +click/_winconsole.py,sha256=5ju3jQkcZD0W27WEMGqmEP4y_crUVzPCqsX_FYb7BO0,7860 +click/core.py,sha256=mz87bYEKzIoNYEa56BFAiOJnvt1Y0L-i7wD4_ZecieE,112782 +click/decorators.py,sha256=yo3zvzgUm5q7h5CXjyV6q3h_PJAiUaem178zXwdWUFI,16350 +click/exceptions.py,sha256=7gDaLGuFZBeCNwY9ERMsF2-Z3R9Fvq09Zc6IZSKjseo,9167 +click/formatting.py,sha256=Frf0-5W33-loyY_i9qrwXR8-STnW3m5gvyxLVUdyxyk,9706 +click/globals.py,sha256=TP-qM88STzc7f127h35TD_v920FgfOD2EwzqA0oE8XU,1961 +click/parser.py,sha256=cAEt1uQR8gq3-S9ysqbVU-fdAZNvilxw4ReJ_T1OQMk,19044 +click/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +click/shell_completion.py,sha256=qOp_BeC9esEOSZKyu5G7RIxEUaLsXUX-mTb7hB1r4QY,18018 +click/termui.py,sha256=ACBQVOvFCTSqtD5VREeCAdRtlHd-Imla-Lte4wSfMjA,28355 +click/testing.py,sha256=ptpMYgRY7dVfE3UDgkgwayu9ePw98sQI3D7zZXiCpj4,16063 +click/types.py,sha256=rEb1aZSQKq3ciCMmjpG2Uva9vk498XRL7ThrcK2GRss,35805 +click/utils.py,sha256=33D6E7poH_nrKB-xr-UyDEXnxOcCiQqxuRLtrqeVv6o,18682 diff --git a/.vtodo/Lib/site-packages/click-8.1.3.dist-info/WHEEL b/.vtodo/Lib/site-packages/click-8.1.3.dist-info/WHEEL new file mode 100644 index 0000000..becc9a6 --- /dev/null +++ b/.vtodo/Lib/site-packages/click-8.1.3.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.37.1) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/.vtodo/Lib/site-packages/click-8.1.3.dist-info/top_level.txt b/.vtodo/Lib/site-packages/click-8.1.3.dist-info/top_level.txt new file mode 100644 index 0000000..dca9a90 --- /dev/null +++ b/.vtodo/Lib/site-packages/click-8.1.3.dist-info/top_level.txt @@ -0,0 +1 @@ +click diff --git a/.vtodo/Lib/site-packages/click/__init__.py b/.vtodo/Lib/site-packages/click/__init__.py new file mode 100644 index 0000000..e3ef423 --- /dev/null +++ b/.vtodo/Lib/site-packages/click/__init__.py @@ -0,0 +1,73 @@ +""" +Click is a simple Python module inspired by the stdlib optparse to make +writing command line scripts fun. Unlike other modules, it's based +around a simple API that does not come with too much magic and is +composable. +""" +from .core import Argument as Argument +from .core import BaseCommand as BaseCommand +from .core import Command as Command +from .core import CommandCollection as CommandCollection +from .core import Context as Context +from .core import Group as Group +from .core import MultiCommand as MultiCommand +from .core import Option as Option +from .core import Parameter as Parameter +from .decorators import argument as argument +from .decorators import command as command +from .decorators import confirmation_option as confirmation_option +from .decorators import group as group +from .decorators import help_option as help_option +from .decorators import make_pass_decorator as make_pass_decorator +from .decorators import option as option +from .decorators import pass_context as pass_context +from .decorators import pass_obj as pass_obj +from .decorators import password_option as password_option +from .decorators import version_option as version_option +from .exceptions import Abort as Abort +from .exceptions import BadArgumentUsage as BadArgumentUsage +from .exceptions import BadOptionUsage as BadOptionUsage +from .exceptions import BadParameter as BadParameter +from .exceptions import ClickException as ClickException +from .exceptions import FileError as FileError +from .exceptions import MissingParameter as MissingParameter +from .exceptions import NoSuchOption as NoSuchOption +from .exceptions import UsageError as UsageError +from .formatting import HelpFormatter as HelpFormatter +from .formatting import wrap_text as wrap_text +from .globals import get_current_context as get_current_context +from .parser import OptionParser as OptionParser +from .termui import clear as clear +from .termui import confirm as confirm +from .termui import echo_via_pager as echo_via_pager +from .termui import edit as edit +from .termui import getchar as getchar +from .termui import launch as launch +from .termui import pause as pause +from .termui import progressbar as progressbar +from .termui import prompt as prompt +from .termui import secho as secho +from .termui import style as style +from .termui import unstyle as unstyle +from .types import BOOL as BOOL +from .types import Choice as Choice +from .types import DateTime as DateTime +from .types import File as File +from .types import FLOAT as FLOAT +from .types import FloatRange as FloatRange +from .types import INT as INT +from .types import IntRange as IntRange +from .types import ParamType as ParamType +from .types import Path as Path +from .types import STRING as STRING +from .types import Tuple as Tuple +from .types import UNPROCESSED as UNPROCESSED +from .types import UUID as UUID +from .utils import echo as echo +from .utils import format_filename as format_filename +from .utils import get_app_dir as get_app_dir +from .utils import get_binary_stream as get_binary_stream +from .utils import get_text_stream as get_text_stream +from .utils import open_file as open_file + +__version__ = "8.1.3" diff --git a/.vtodo/Lib/site-packages/click/__pycache__/__init__.cpython-310.pyc b/.vtodo/Lib/site-packages/click/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..48bc0237196e8c4cd29241fbab21f037bc231161 GIT binary patch literal 2614 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 literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/click/__pycache__/_termui_impl.cpython-310.pyc b/.vtodo/Lib/site-packages/click/__pycache__/_termui_impl.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..44954215f8f61f0d5e9012efe9903d6260ebb239 GIT binary patch literal 16073 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 YgOY-OxH|KUJ4#<+8hyPpDRl7v0xTb;UjP6A literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/click/__pycache__/_textwrap.cpython-310.pyc b/.vtodo/Lib/site-packages/click/__pycache__/_textwrap.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..534aff6947746e2117336b6bb52e5b42bcac7643 GIT binary patch literal 1548 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 AasU7T literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/click/__pycache__/decorators.cpython-310.pyc b/.vtodo/Lib/site-packages/click/__pycache__/decorators.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1e4bf4cca7de18138ef6071699ea8a59070d6769 GIT binary patch literal 15614 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 literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/click/__pycache__/exceptions.cpython-310.pyc b/.vtodo/Lib/site-packages/click/__pycache__/exceptions.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b50349387ccd5b6a1d722421a86c357afff388ff GIT binary patch literal 10196 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 literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/click/__pycache__/formatting.cpython-310.pyc b/.vtodo/Lib/site-packages/click/__pycache__/formatting.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4298d103ce0fa9177b42306759769338c6135277 GIT binary patch literal 9459 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 GYULk5{c`pI literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/click/__pycache__/parser.cpython-310.pyc b/.vtodo/Lib/site-packages/click/__pycache__/parser.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1a815bff9e4889d51ed4551f94a49707f0eff684 GIT binary patch literal 13676 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 Y2aE&U6RyyIeAPIe9O3M^G5qQO0g-PuJpcdz literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/click/__pycache__/termui.cpython-310.pyc b/.vtodo/Lib/site-packages/click/__pycache__/termui.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bce1e36c6d72936b8461b091d5b77188a7ddf301 GIT binary patch literal 26217 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 literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/click/__pycache__/types.cpython-310.pyc b/.vtodo/Lib/site-packages/click/__pycache__/types.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a0ccdd26034f4682de5f5a2a111de942946c4f7e GIT binary patch literal 33244 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
+ +
+
+

Console Locked

+

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

+

PIN: + + +

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

%(exception_type)s

+
+

%(exception)s

+
+

Traceback (most recent call last)

+%(summary)s +
+

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

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

Interactive Console

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

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

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

    {title}

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

    {description}

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

    {escape(self.name)}

    \n" + f"{self.get_description(environ)}\n" + ) + + def get_headers( + self, + environ: t.Optional["WSGIEnvironment"] = None, + scope: t.Optional[dict] = None, + ) -> t.List[t.Tuple[str, str]]: + """Get a list of headers.""" + return [("Content-Type", "text/html; charset=utf-8")] + + def get_response( + self, + environ: t.Optional[t.Union["WSGIEnvironment", "WSGIRequest"]] = None, + scope: t.Optional[dict] = None, + ) -> "Response": + """Get a response object. If one was passed to the exception + it's returned directly. + + :param environ: the optional environ for the request. This + can be used to modify the response depending + on how the request looked like. + :return: a :class:`Response` object or a subclass thereof. + """ + from .wrappers.response import Response as WSGIResponse # noqa: F811 + + if self.response is not None: + return self.response + if environ is not None: + environ = _get_environ(environ) + headers = self.get_headers(environ, scope) + return WSGIResponse(self.get_body(environ, scope), self.code, headers) + + def __call__( + self, environ: "WSGIEnvironment", start_response: "StartResponse" + ) -> t.Iterable[bytes]: + """Call the exception as WSGI application. + + :param environ: the WSGI environment. + :param start_response: the response callable provided by the WSGI + server. + """ + response = t.cast("WSGIResponse", self.get_response(environ)) + return response(environ, start_response) + + def __str__(self) -> str: + code = self.code if self.code is not None else "???" + return f"{code} {self.name}: {self.description}" + + def __repr__(self) -> str: + code = self.code if self.code is not None else "???" + return f"<{type(self).__name__} '{code}: {self.name}'>" + + +class BadRequest(HTTPException): + """*400* `Bad Request` + + Raise if the browser sends something to the application the application + or server cannot handle. + """ + + code = 400 + description = ( + "The browser (or proxy) sent a request that this server could " + "not understand." + ) + + +class BadRequestKeyError(BadRequest, KeyError): + """An exception that is used to signal both a :exc:`KeyError` and a + :exc:`BadRequest`. Used by many of the datastructures. + """ + + _description = BadRequest.description + #: Show the KeyError along with the HTTP error message in the + #: response. This should be disabled in production, but can be + #: useful in a debug mode. + show_exception = False + + def __init__(self, arg: t.Optional[str] = None, *args: t.Any, **kwargs: t.Any): + super().__init__(*args, **kwargs) + + if arg is None: + KeyError.__init__(self) + else: + KeyError.__init__(self, arg) + + @property # type: ignore + def description(self) -> str: # type: ignore + if self.show_exception: + return ( + f"{self._description}\n" + f"{KeyError.__name__}: {KeyError.__str__(self)}" + ) + + return self._description + + @description.setter + def description(self, value: str) -> None: + self._description = value + + +class ClientDisconnected(BadRequest): + """Internal exception that is raised if Werkzeug detects a disconnected + client. Since the client is already gone at that point attempting to + send the error message to the client might not work and might ultimately + result in another exception in the server. Mainly this is here so that + it is silenced by default as far as Werkzeug is concerned. + + Since disconnections cannot be reliably detected and are unspecified + by WSGI to a large extent this might or might not be raised if a client + is gone. + + .. versionadded:: 0.8 + """ + + +class SecurityError(BadRequest): + """Raised if something triggers a security error. This is otherwise + exactly like a bad request error. + + .. versionadded:: 0.9 + """ + + +class BadHost(BadRequest): + """Raised if the submitted host is badly formatted. + + .. versionadded:: 0.11.2 + """ + + +class Unauthorized(HTTPException): + """*401* ``Unauthorized`` + + Raise if the user is not authorized to access a resource. + + The ``www_authenticate`` argument should be used to set the + ``WWW-Authenticate`` header. This is used for HTTP basic auth and + other schemes. Use :class:`~werkzeug.datastructures.WWWAuthenticate` + to create correctly formatted values. Strictly speaking a 401 + response is invalid if it doesn't provide at least one value for + this header, although real clients typically don't care. + + :param description: Override the default message used for the body + of the response. + :param www-authenticate: A single value, or list of values, for the + WWW-Authenticate header(s). + + .. versionchanged:: 2.0 + Serialize multiple ``www_authenticate`` items into multiple + ``WWW-Authenticate`` headers, rather than joining them + into a single value, for better interoperability. + + .. versionchanged:: 0.15.3 + If the ``www_authenticate`` argument is not set, the + ``WWW-Authenticate`` header is not set. + + .. versionchanged:: 0.15.3 + The ``response`` argument was restored. + + .. versionchanged:: 0.15.1 + ``description`` was moved back as the first argument, restoring + its previous position. + + .. versionchanged:: 0.15.0 + ``www_authenticate`` was added as the first argument, ahead of + ``description``. + """ + + code = 401 + description = ( + "The server could not verify that you are authorized to access" + " the URL requested. You either supplied the wrong credentials" + " (e.g. a bad password), or your browser doesn't understand" + " how to supply the credentials required." + ) + + def __init__( + self, + description: t.Optional[str] = None, + response: t.Optional["Response"] = None, + www_authenticate: t.Optional[ + t.Union["WWWAuthenticate", t.Iterable["WWWAuthenticate"]] + ] = None, + ) -> None: + super().__init__(description, response) + + from .datastructures import WWWAuthenticate + + if isinstance(www_authenticate, WWWAuthenticate): + www_authenticate = (www_authenticate,) + + self.www_authenticate = www_authenticate + + def get_headers( + self, + environ: t.Optional["WSGIEnvironment"] = None, + scope: t.Optional[dict] = None, + ) -> t.List[t.Tuple[str, str]]: + headers = super().get_headers(environ, scope) + if self.www_authenticate: + headers.extend(("WWW-Authenticate", str(x)) for x in self.www_authenticate) + return headers + + +class Forbidden(HTTPException): + """*403* `Forbidden` + + Raise if the user doesn't have the permission for the requested resource + but was authenticated. + """ + + code = 403 + description = ( + "You don't have the permission to access the requested" + " resource. It is either read-protected or not readable by the" + " server." + ) + + +class NotFound(HTTPException): + """*404* `Not Found` + + Raise if a resource does not exist and never existed. + """ + + code = 404 + description = ( + "The requested URL was not found on the server. If you entered" + " the URL manually please check your spelling and try again." + ) + + +class MethodNotAllowed(HTTPException): + """*405* `Method Not Allowed` + + Raise if the server used a method the resource does not handle. For + example `POST` if the resource is view only. Especially useful for REST. + + The first argument for this exception should be a list of allowed methods. + Strictly speaking the response would be invalid if you don't provide valid + methods in the header which you can do with that list. + """ + + code = 405 + description = "The method is not allowed for the requested URL." + + def __init__( + self, + valid_methods: t.Optional[t.Iterable[str]] = None, + description: t.Optional[str] = None, + response: t.Optional["Response"] = None, + ) -> None: + """Takes an optional list of valid http methods + starting with werkzeug 0.3 the list will be mandatory.""" + super().__init__(description=description, response=response) + self.valid_methods = valid_methods + + def get_headers( + self, + environ: t.Optional["WSGIEnvironment"] = None, + scope: t.Optional[dict] = None, + ) -> t.List[t.Tuple[str, str]]: + headers = super().get_headers(environ, scope) + if self.valid_methods: + headers.append(("Allow", ", ".join(self.valid_methods))) + return headers + + +class NotAcceptable(HTTPException): + """*406* `Not Acceptable` + + Raise if the server can't return any content conforming to the + `Accept` headers of the client. + """ + + code = 406 + description = ( + "The resource identified by the request is only capable of" + " generating response entities which have content" + " characteristics not acceptable according to the accept" + " headers sent in the request." + ) + + +class RequestTimeout(HTTPException): + """*408* `Request Timeout` + + Raise to signalize a timeout. + """ + + code = 408 + description = ( + "The server closed the network connection because the browser" + " didn't finish the request within the specified time." + ) + + +class Conflict(HTTPException): + """*409* `Conflict` + + Raise to signal that a request cannot be completed because it conflicts + with the current state on the server. + + .. versionadded:: 0.7 + """ + + code = 409 + description = ( + "A conflict happened while processing the request. The" + " resource might have been modified while the request was being" + " processed." + ) + + +class Gone(HTTPException): + """*410* `Gone` + + Raise if a resource existed previously and went away without new location. + """ + + code = 410 + description = ( + "The requested URL is no longer available on this server and" + " there is no forwarding address. If you followed a link from a" + " foreign page, please contact the author of this page." + ) + + +class LengthRequired(HTTPException): + """*411* `Length Required` + + Raise if the browser submitted data but no ``Content-Length`` header which + is required for the kind of processing the server does. + """ + + code = 411 + description = ( + "A request with this method requires a valid Content-" + "Length header." + ) + + +class PreconditionFailed(HTTPException): + """*412* `Precondition Failed` + + Status code used in combination with ``If-Match``, ``If-None-Match``, or + ``If-Unmodified-Since``. + """ + + code = 412 + description = ( + "The precondition on the request for the URL failed positive evaluation." + ) + + +class RequestEntityTooLarge(HTTPException): + """*413* `Request Entity Too Large` + + The status code one should return if the data submitted exceeded a given + limit. + """ + + code = 413 + description = "The data value transmitted exceeds the capacity limit." + + +class RequestURITooLarge(HTTPException): + """*414* `Request URI Too Large` + + Like *413* but for too long URLs. + """ + + code = 414 + description = ( + "The length of the requested URL exceeds the capacity limit for" + " this server. The request cannot be processed." + ) + + +class UnsupportedMediaType(HTTPException): + """*415* `Unsupported Media Type` + + The status code returned if the server is unable to handle the media type + the client transmitted. + """ + + code = 415 + description = ( + "The server does not support the media type transmitted in the request." + ) + + +class RequestedRangeNotSatisfiable(HTTPException): + """*416* `Requested Range Not Satisfiable` + + The client asked for an invalid part of the file. + + .. versionadded:: 0.7 + """ + + code = 416 + description = "The server cannot provide the requested range." + + def __init__( + self, + length: t.Optional[int] = None, + units: str = "bytes", + description: t.Optional[str] = None, + response: t.Optional["Response"] = None, + ) -> None: + """Takes an optional `Content-Range` header value based on ``length`` + parameter. + """ + super().__init__(description=description, response=response) + self.length = length + self.units = units + + def get_headers( + self, + environ: t.Optional["WSGIEnvironment"] = None, + scope: t.Optional[dict] = None, + ) -> t.List[t.Tuple[str, str]]: + headers = super().get_headers(environ, scope) + if self.length is not None: + headers.append(("Content-Range", f"{self.units} */{self.length}")) + return headers + + +class ExpectationFailed(HTTPException): + """*417* `Expectation Failed` + + The server cannot meet the requirements of the Expect request-header. + + .. versionadded:: 0.7 + """ + + code = 417 + description = "The server could not meet the requirements of the Expect header" + + +class ImATeapot(HTTPException): + """*418* `I'm a teapot` + + The server should return this if it is a teapot and someone attempted + to brew coffee with it. + + .. versionadded:: 0.7 + """ + + code = 418 + description = "This server is a teapot, not a coffee machine" + + +class UnprocessableEntity(HTTPException): + """*422* `Unprocessable Entity` + + Used if the request is well formed, but the instructions are otherwise + incorrect. + """ + + code = 422 + description = ( + "The request was well-formed but was unable to be followed due" + " to semantic errors." + ) + + +class Locked(HTTPException): + """*423* `Locked` + + Used if the resource that is being accessed is locked. + """ + + code = 423 + description = "The resource that is being accessed is locked." + + +class FailedDependency(HTTPException): + """*424* `Failed Dependency` + + Used if the method could not be performed on the resource + because the requested action depended on another action and that action failed. + """ + + code = 424 + description = ( + "The method could not be performed on the resource because the" + " requested action depended on another action and that action" + " failed." + ) + + +class PreconditionRequired(HTTPException): + """*428* `Precondition Required` + + The server requires this request to be conditional, typically to prevent + the lost update problem, which is a race condition between two or more + clients attempting to update a resource through PUT or DELETE. By requiring + each client to include a conditional header ("If-Match" or "If-Unmodified- + Since") with the proper value retained from a recent GET request, the + server ensures that each client has at least seen the previous revision of + the resource. + """ + + code = 428 + description = ( + "This request is required to be conditional; try using" + ' "If-Match" or "If-Unmodified-Since".' + ) + + +class _RetryAfter(HTTPException): + """Adds an optional ``retry_after`` parameter which will set the + ``Retry-After`` header. May be an :class:`int` number of seconds or + a :class:`~datetime.datetime`. + """ + + def __init__( + self, + description: t.Optional[str] = None, + response: t.Optional["Response"] = None, + retry_after: t.Optional[t.Union[datetime, int]] = None, + ) -> None: + super().__init__(description, response) + self.retry_after = retry_after + + def get_headers( + self, + environ: t.Optional["WSGIEnvironment"] = None, + scope: t.Optional[dict] = None, + ) -> t.List[t.Tuple[str, str]]: + headers = super().get_headers(environ, scope) + + if self.retry_after: + if isinstance(self.retry_after, datetime): + from .http import http_date + + value = http_date(self.retry_after) + else: + value = str(self.retry_after) + + headers.append(("Retry-After", value)) + + return headers + + +class TooManyRequests(_RetryAfter): + """*429* `Too Many Requests` + + The server is limiting the rate at which this user receives + responses, and this request exceeds that rate. (The server may use + any convenient method to identify users and their request rates). + The server may include a "Retry-After" header to indicate how long + the user should wait before retrying. + + :param retry_after: If given, set the ``Retry-After`` header to this + value. May be an :class:`int` number of seconds or a + :class:`~datetime.datetime`. + + .. versionchanged:: 1.0 + Added ``retry_after`` parameter. + """ + + code = 429 + description = "This user has exceeded an allotted request count. Try again later." + + +class RequestHeaderFieldsTooLarge(HTTPException): + """*431* `Request Header Fields Too Large` + + The server refuses to process the request because the header fields are too + large. One or more individual fields may be too large, or the set of all + headers is too large. + """ + + code = 431 + description = "One or more header fields exceeds the maximum size." + + +class UnavailableForLegalReasons(HTTPException): + """*451* `Unavailable For Legal Reasons` + + This status code indicates that the server is denying access to the + resource as a consequence of a legal demand. + """ + + code = 451 + description = "Unavailable for legal reasons." + + +class InternalServerError(HTTPException): + """*500* `Internal Server Error` + + Raise if an internal server error occurred. This is a good fallback if an + unknown error occurred in the dispatcher. + + .. versionchanged:: 1.0.0 + Added the :attr:`original_exception` attribute. + """ + + code = 500 + description = ( + "The server encountered an internal error and was unable to" + " complete your request. Either the server is overloaded or" + " there is an error in the application." + ) + + def __init__( + self, + description: t.Optional[str] = None, + response: t.Optional["Response"] = None, + original_exception: t.Optional[BaseException] = None, + ) -> None: + #: The original exception that caused this 500 error. Can be + #: used by frameworks to provide context when handling + #: unexpected errors. + self.original_exception = original_exception + super().__init__(description=description, response=response) + + +class NotImplemented(HTTPException): + """*501* `Not Implemented` + + Raise if the application does not support the action requested by the + browser. + """ + + code = 501 + description = "The server does not support the action requested by the browser." + + +class BadGateway(HTTPException): + """*502* `Bad Gateway` + + If you do proxying in your application you should return this status code + if you received an invalid response from the upstream server it accessed + in attempting to fulfill the request. + """ + + code = 502 + description = ( + "The proxy server received an invalid response from an upstream server." + ) + + +class ServiceUnavailable(_RetryAfter): + """*503* `Service Unavailable` + + Status code you should return if a service is temporarily + unavailable. + + :param retry_after: If given, set the ``Retry-After`` header to this + value. May be an :class:`int` number of seconds or a + :class:`~datetime.datetime`. + + .. versionchanged:: 1.0 + Added ``retry_after`` parameter. + """ + + code = 503 + description = ( + "The server is temporarily unable to service your request due" + " to maintenance downtime or capacity problems. Please try" + " again later." + ) + + +class GatewayTimeout(HTTPException): + """*504* `Gateway Timeout` + + Status code you should return if a connection to an upstream server + times out. + """ + + code = 504 + description = "The connection to an upstream server timed out." + + +class HTTPVersionNotSupported(HTTPException): + """*505* `HTTP Version Not Supported` + + The server does not support the HTTP protocol version used in the request. + """ + + code = 505 + description = ( + "The server does not support the HTTP protocol version used in the request." + ) + + +default_exceptions: t.Dict[int, t.Type[HTTPException]] = {} + + +def _find_exceptions() -> None: + for obj in globals().values(): + try: + is_http_exception = issubclass(obj, HTTPException) + except TypeError: + is_http_exception = False + if not is_http_exception or obj.code is None: + continue + old_obj = default_exceptions.get(obj.code, None) + if old_obj is not None and issubclass(obj, old_obj): + continue + default_exceptions[obj.code] = obj + + +_find_exceptions() +del _find_exceptions + + +class Aborter: + """When passed a dict of code -> exception items it can be used as + callable that raises exceptions. If the first argument to the + callable is an integer it will be looked up in the mapping, if it's + a WSGI application it will be raised in a proxy exception. + + The rest of the arguments are forwarded to the exception constructor. + """ + + def __init__( + self, + mapping: t.Optional[t.Dict[int, t.Type[HTTPException]]] = None, + extra: t.Optional[t.Dict[int, t.Type[HTTPException]]] = None, + ) -> None: + if mapping is None: + mapping = default_exceptions + self.mapping = dict(mapping) + if extra is not None: + self.mapping.update(extra) + + def __call__( + self, code: t.Union[int, "Response"], *args: t.Any, **kwargs: t.Any + ) -> "te.NoReturn": + from .sansio.response import Response + + if isinstance(code, Response): + raise HTTPException(response=code) + + if code not in self.mapping: + raise LookupError(f"no exception for {code!r}") + + raise self.mapping[code](*args, **kwargs) + + +def abort( + status: t.Union[int, "Response"], *args: t.Any, **kwargs: t.Any +) -> "te.NoReturn": + """Raises an :py:exc:`HTTPException` for the given status code or WSGI + application. + + If a status code is given, it will be looked up in the list of + exceptions and will raise that exception. If passed a WSGI application, + it will wrap it in a proxy WSGI exception and raise that:: + + abort(404) # 404 Not Found + abort(Response('Hello World')) + + """ + _aborter(status, *args, **kwargs) + + +_aborter: Aborter = Aborter() diff --git a/.vtodo/Lib/site-packages/werkzeug/formparser.py b/.vtodo/Lib/site-packages/werkzeug/formparser.py new file mode 100644 index 0000000..10d58ca --- /dev/null +++ b/.vtodo/Lib/site-packages/werkzeug/formparser.py @@ -0,0 +1,455 @@ +import typing as t +from functools import update_wrapper +from io import BytesIO +from itertools import chain +from typing import Union + +from . import exceptions +from .datastructures import FileStorage +from .datastructures import Headers +from .datastructures import MultiDict +from .http import parse_options_header +from .sansio.multipart import Data +from .sansio.multipart import Epilogue +from .sansio.multipart import Field +from .sansio.multipart import File +from .sansio.multipart import MultipartDecoder +from .sansio.multipart import NeedData +from .urls import url_decode_stream +from .wsgi import _make_chunk_iter +from .wsgi import get_content_length +from .wsgi import get_input_stream + +# there are some platforms where SpooledTemporaryFile is not available. +# In that case we need to provide a fallback. +try: + from tempfile import SpooledTemporaryFile +except ImportError: + from tempfile import TemporaryFile + + SpooledTemporaryFile = None # type: ignore + +if t.TYPE_CHECKING: + import typing as te + from _typeshed.wsgi import WSGIEnvironment + + t_parse_result = t.Tuple[t.IO[bytes], MultiDict, MultiDict] + + class TStreamFactory(te.Protocol): + def __call__( + self, + total_content_length: t.Optional[int], + content_type: t.Optional[str], + filename: t.Optional[str], + content_length: t.Optional[int] = None, + ) -> t.IO[bytes]: + ... + + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) + + +def _exhaust(stream: t.IO[bytes]) -> None: + bts = stream.read(64 * 1024) + while bts: + bts = stream.read(64 * 1024) + + +def default_stream_factory( + total_content_length: t.Optional[int], + content_type: t.Optional[str], + filename: t.Optional[str], + content_length: t.Optional[int] = None, +) -> t.IO[bytes]: + max_size = 1024 * 500 + + if SpooledTemporaryFile is not None: + return t.cast(t.IO[bytes], SpooledTemporaryFile(max_size=max_size, mode="rb+")) + elif total_content_length is None or total_content_length > max_size: + return t.cast(t.IO[bytes], TemporaryFile("rb+")) + + return BytesIO() + + +def parse_form_data( + environ: "WSGIEnvironment", + stream_factory: t.Optional["TStreamFactory"] = None, + charset: str = "utf-8", + errors: str = "replace", + max_form_memory_size: t.Optional[int] = None, + max_content_length: t.Optional[int] = None, + cls: t.Optional[t.Type[MultiDict]] = None, + silent: bool = True, +) -> "t_parse_result": + """Parse the form data in the environ and return it as tuple in the form + ``(stream, form, files)``. You should only call this method if the + transport method is `POST`, `PUT`, or `PATCH`. + + If the mimetype of the data transmitted is `multipart/form-data` the + files multidict will be filled with `FileStorage` objects. If the + mimetype is unknown the input stream is wrapped and returned as first + argument, else the stream is empty. + + This is a shortcut for the common usage of :class:`FormDataParser`. + + Have a look at :doc:`/request_data` for more details. + + .. versionadded:: 0.5 + The `max_form_memory_size`, `max_content_length` and + `cls` parameters were added. + + .. versionadded:: 0.5.1 + The optional `silent` flag was added. + + :param environ: the WSGI environment to be used for parsing. + :param stream_factory: An optional callable that returns a new read and + writeable file descriptor. This callable works + the same as :meth:`Response._get_file_stream`. + :param charset: The character set for URL and url encoded form data. + :param errors: The encoding error behavior. + :param max_form_memory_size: the maximum number of bytes to be accepted for + in-memory stored form data. If the data + exceeds the value specified an + :exc:`~exceptions.RequestEntityTooLarge` + exception is raised. + :param max_content_length: If this is provided and the transmitted data + is longer than this value an + :exc:`~exceptions.RequestEntityTooLarge` + exception is raised. + :param cls: an optional dict class to use. If this is not specified + or `None` the default :class:`MultiDict` is used. + :param silent: If set to False parsing errors will not be caught. + :return: A tuple in the form ``(stream, form, files)``. + """ + return FormDataParser( + stream_factory, + charset, + errors, + max_form_memory_size, + max_content_length, + cls, + silent, + ).parse_from_environ(environ) + + +def exhaust_stream(f: F) -> F: + """Helper decorator for methods that exhausts the stream on return.""" + + def wrapper(self, stream, *args, **kwargs): # type: ignore + try: + return f(self, stream, *args, **kwargs) + finally: + exhaust = getattr(stream, "exhaust", None) + + if exhaust is not None: + exhaust() + else: + while True: + chunk = stream.read(1024 * 64) + + if not chunk: + break + + return update_wrapper(t.cast(F, wrapper), f) + + +class FormDataParser: + """This class implements parsing of form data for Werkzeug. By itself + it can parse multipart and url encoded form data. It can be subclassed + and extended but for most mimetypes it is a better idea to use the + untouched stream and expose it as separate attributes on a request + object. + + .. versionadded:: 0.8 + + :param stream_factory: An optional callable that returns a new read and + writeable file descriptor. This callable works + the same as :meth:`Response._get_file_stream`. + :param charset: The character set for URL and url encoded form data. + :param errors: The encoding error behavior. + :param max_form_memory_size: the maximum number of bytes to be accepted for + in-memory stored form data. If the data + exceeds the value specified an + :exc:`~exceptions.RequestEntityTooLarge` + exception is raised. + :param max_content_length: If this is provided and the transmitted data + is longer than this value an + :exc:`~exceptions.RequestEntityTooLarge` + exception is raised. + :param cls: an optional dict class to use. If this is not specified + or `None` the default :class:`MultiDict` is used. + :param silent: If set to False parsing errors will not be caught. + """ + + def __init__( + self, + stream_factory: t.Optional["TStreamFactory"] = None, + charset: str = "utf-8", + errors: str = "replace", + max_form_memory_size: t.Optional[int] = None, + max_content_length: t.Optional[int] = None, + cls: t.Optional[t.Type[MultiDict]] = None, + silent: bool = True, + ) -> None: + if stream_factory is None: + stream_factory = default_stream_factory + + self.stream_factory = stream_factory + self.charset = charset + self.errors = errors + self.max_form_memory_size = max_form_memory_size + self.max_content_length = max_content_length + + if cls is None: + cls = MultiDict + + self.cls = cls + self.silent = silent + + def get_parse_func( + self, mimetype: str, options: t.Dict[str, str] + ) -> t.Optional[ + t.Callable[ + ["FormDataParser", t.IO[bytes], str, t.Optional[int], t.Dict[str, str]], + "t_parse_result", + ] + ]: + return self.parse_functions.get(mimetype) + + def parse_from_environ(self, environ: "WSGIEnvironment") -> "t_parse_result": + """Parses the information from the environment as form data. + + :param environ: the WSGI environment to be used for parsing. + :return: A tuple in the form ``(stream, form, files)``. + """ + content_type = environ.get("CONTENT_TYPE", "") + content_length = get_content_length(environ) + mimetype, options = parse_options_header(content_type) + return self.parse(get_input_stream(environ), mimetype, content_length, options) + + def parse( + self, + stream: t.IO[bytes], + mimetype: str, + content_length: t.Optional[int], + options: t.Optional[t.Dict[str, str]] = None, + ) -> "t_parse_result": + """Parses the information from the given stream, mimetype, + content length and mimetype parameters. + + :param stream: an input stream + :param mimetype: the mimetype of the data + :param content_length: the content length of the incoming data + :param options: optional mimetype parameters (used for + the multipart boundary for instance) + :return: A tuple in the form ``(stream, form, files)``. + """ + if ( + self.max_content_length is not None + and content_length is not None + and content_length > self.max_content_length + ): + # if the input stream is not exhausted, firefox reports Connection Reset + _exhaust(stream) + raise exceptions.RequestEntityTooLarge() + + if options is None: + options = {} + + parse_func = self.get_parse_func(mimetype, options) + + if parse_func is not None: + try: + return parse_func(self, stream, mimetype, content_length, options) + except ValueError: + if not self.silent: + raise + + return stream, self.cls(), self.cls() + + @exhaust_stream + def _parse_multipart( + self, + stream: t.IO[bytes], + mimetype: str, + content_length: t.Optional[int], + options: t.Dict[str, str], + ) -> "t_parse_result": + parser = MultiPartParser( + self.stream_factory, + self.charset, + self.errors, + max_form_memory_size=self.max_form_memory_size, + cls=self.cls, + ) + boundary = options.get("boundary", "").encode("ascii") + + if not boundary: + raise ValueError("Missing boundary") + + form, files = parser.parse(stream, boundary, content_length) + return stream, form, files + + @exhaust_stream + def _parse_urlencoded( + self, + stream: t.IO[bytes], + mimetype: str, + content_length: t.Optional[int], + options: t.Dict[str, str], + ) -> "t_parse_result": + if ( + self.max_form_memory_size is not None + and content_length is not None + and content_length > self.max_form_memory_size + ): + # if the input stream is not exhausted, firefox reports Connection Reset + _exhaust(stream) + raise exceptions.RequestEntityTooLarge() + + form = url_decode_stream(stream, self.charset, errors=self.errors, cls=self.cls) + return stream, form, self.cls() + + #: mapping of mimetypes to parsing functions + parse_functions: t.Dict[ + str, + t.Callable[ + ["FormDataParser", t.IO[bytes], str, t.Optional[int], t.Dict[str, str]], + "t_parse_result", + ], + ] = { + "multipart/form-data": _parse_multipart, + "application/x-www-form-urlencoded": _parse_urlencoded, + "application/x-url-encoded": _parse_urlencoded, + } + + +def _line_parse(line: str) -> t.Tuple[str, bool]: + """Removes line ending characters and returns a tuple (`stripped_line`, + `is_terminated`). + """ + if line[-2:] == "\r\n": + return line[:-2], True + + elif line[-1:] in {"\r", "\n"}: + return line[:-1], True + + return line, False + + +class MultiPartParser: + def __init__( + self, + stream_factory: t.Optional["TStreamFactory"] = None, + charset: str = "utf-8", + errors: str = "replace", + max_form_memory_size: t.Optional[int] = None, + cls: t.Optional[t.Type[MultiDict]] = None, + buffer_size: int = 64 * 1024, + ) -> None: + self.charset = charset + self.errors = errors + self.max_form_memory_size = max_form_memory_size + + if stream_factory is None: + stream_factory = default_stream_factory + + self.stream_factory = stream_factory + + if cls is None: + cls = MultiDict + + self.cls = cls + + self.buffer_size = buffer_size + + def fail(self, message: str) -> "te.NoReturn": + raise ValueError(message) + + def get_part_charset(self, headers: Headers) -> str: + # Figure out input charset for current part + content_type = headers.get("content-type") + + if content_type: + mimetype, ct_params = parse_options_header(content_type) + return ct_params.get("charset", self.charset) + + return self.charset + + def start_file_streaming( + self, event: File, total_content_length: t.Optional[int] + ) -> t.IO[bytes]: + content_type = event.headers.get("content-type") + + try: + content_length = int(event.headers["content-length"]) + except (KeyError, ValueError): + content_length = 0 + + container = self.stream_factory( + total_content_length=total_content_length, + filename=event.filename, + content_type=content_type, + content_length=content_length, + ) + return container + + def parse( + self, stream: t.IO[bytes], boundary: bytes, content_length: t.Optional[int] + ) -> t.Tuple[MultiDict, MultiDict]: + container: t.Union[t.IO[bytes], t.List[bytes]] + _write: t.Callable[[bytes], t.Any] + + iterator = chain( + _make_chunk_iter( + stream, + limit=content_length, + buffer_size=self.buffer_size, + ), + [None], + ) + + parser = MultipartDecoder(boundary, self.max_form_memory_size) + + fields = [] + files = [] + + current_part: Union[Field, File] + for data in iterator: + parser.receive_data(data) + event = parser.next_event() + while not isinstance(event, (Epilogue, NeedData)): + if isinstance(event, Field): + current_part = event + container = [] + _write = container.append + elif isinstance(event, File): + current_part = event + container = self.start_file_streaming(event, content_length) + _write = container.write + elif isinstance(event, Data): + _write(event.data) + if not event.more_data: + if isinstance(current_part, Field): + value = b"".join(container).decode( + self.get_part_charset(current_part.headers), self.errors + ) + fields.append((current_part.name, value)) + else: + container = t.cast(t.IO[bytes], container) + container.seek(0) + files.append( + ( + current_part.name, + FileStorage( + container, + current_part.filename, + current_part.name, + headers=current_part.headers, + ), + ) + ) + + event = parser.next_event() + + return self.cls(fields), self.cls(files) diff --git a/.vtodo/Lib/site-packages/werkzeug/http.py b/.vtodo/Lib/site-packages/werkzeug/http.py new file mode 100644 index 0000000..9777685 --- /dev/null +++ b/.vtodo/Lib/site-packages/werkzeug/http.py @@ -0,0 +1,1311 @@ +import base64 +import email.utils +import re +import typing +import typing as t +import warnings +from datetime import date +from datetime import datetime +from datetime import time +from datetime import timedelta +from datetime import timezone +from enum import Enum +from hashlib import sha1 +from time import mktime +from time import struct_time +from urllib.parse import unquote_to_bytes as _unquote +from urllib.request import parse_http_list as _parse_list_header + +from ._internal import _cookie_quote +from ._internal import _dt_as_utc +from ._internal import _make_cookie_domain +from ._internal import _to_bytes +from ._internal import _to_str +from ._internal import _wsgi_decoding_dance + +if t.TYPE_CHECKING: + from _typeshed.wsgi import WSGIEnvironment + +# for explanation of "media-range", etc. see Sections 5.3.{1,2} of RFC 7231 +_accept_re = re.compile( + r""" + ( # media-range capturing-parenthesis + [^\s;,]+ # type/subtype + (?:[ \t]*;[ \t]* # ";" + (?: # parameter non-capturing-parenthesis + [^\s;,q][^\s;,]* # token that doesn't start with "q" + | # or + q[^\s;,=][^\s;,]* # token that is more than just "q" + ) + )* # zero or more parameters + ) # end of media-range + (?:[ \t]*;[ \t]*q= # weight is a "q" parameter + (\d*(?:\.\d+)?) # qvalue capturing-parentheses + [^,]* # "extension" accept params: who cares? + )? # accept params are optional + """, + re.VERBOSE, +) +_token_chars = frozenset( + "!#$%&'*+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz|~" +) +_etag_re = re.compile(r'([Ww]/)?(?:"(.*?)"|(.*?))(?:\s*,\s*|$)') +_option_header_piece_re = re.compile( + r""" + ;\s*,?\s* # newlines were replaced with commas + (?P + "[^"\\]*(?:\\.[^"\\]*)*" # quoted string + | + [^\s;,=*]+ # token + ) + (?:\*(?P\d+))? # *1, optional continuation index + \s* + (?: # optionally followed by =value + (?: # equals sign, possibly with encoding + \*\s*=\s* # * indicates extended notation + (?: # optional encoding + (?P[^\s]+?) + '(?P[^\s]*?)' + )? + | + =\s* # basic notation + ) + (?P + "[^"\\]*(?:\\.[^"\\]*)*" # quoted string + | + [^;,]+ # token + )? + )? + \s* + """, + flags=re.VERBOSE, +) +_option_header_start_mime_type = re.compile(r",\s*([^;,\s]+)([;,]\s*.+)?") +_entity_headers = frozenset( + [ + "allow", + "content-encoding", + "content-language", + "content-length", + "content-location", + "content-md5", + "content-range", + "content-type", + "expires", + "last-modified", + ] +) +_hop_by_hop_headers = frozenset( + [ + "connection", + "keep-alive", + "proxy-authenticate", + "proxy-authorization", + "te", + "trailer", + "transfer-encoding", + "upgrade", + ] +) +HTTP_STATUS_CODES = { + 100: "Continue", + 101: "Switching Protocols", + 102: "Processing", + 103: "Early Hints", # see RFC 8297 + 200: "OK", + 201: "Created", + 202: "Accepted", + 203: "Non Authoritative Information", + 204: "No Content", + 205: "Reset Content", + 206: "Partial Content", + 207: "Multi Status", + 208: "Already Reported", # see RFC 5842 + 226: "IM Used", # see RFC 3229 + 300: "Multiple Choices", + 301: "Moved Permanently", + 302: "Found", + 303: "See Other", + 304: "Not Modified", + 305: "Use Proxy", + 306: "Switch Proxy", # unused + 307: "Temporary Redirect", + 308: "Permanent Redirect", + 400: "Bad Request", + 401: "Unauthorized", + 402: "Payment Required", # unused + 403: "Forbidden", + 404: "Not Found", + 405: "Method Not Allowed", + 406: "Not Acceptable", + 407: "Proxy Authentication Required", + 408: "Request Timeout", + 409: "Conflict", + 410: "Gone", + 411: "Length Required", + 412: "Precondition Failed", + 413: "Request Entity Too Large", + 414: "Request URI Too Long", + 415: "Unsupported Media Type", + 416: "Requested Range Not Satisfiable", + 417: "Expectation Failed", + 418: "I'm a teapot", # see RFC 2324 + 421: "Misdirected Request", # see RFC 7540 + 422: "Unprocessable Entity", + 423: "Locked", + 424: "Failed Dependency", + 425: "Too Early", # see RFC 8470 + 426: "Upgrade Required", + 428: "Precondition Required", # see RFC 6585 + 429: "Too Many Requests", + 431: "Request Header Fields Too Large", + 449: "Retry With", # proprietary MS extension + 451: "Unavailable For Legal Reasons", + 500: "Internal Server Error", + 501: "Not Implemented", + 502: "Bad Gateway", + 503: "Service Unavailable", + 504: "Gateway Timeout", + 505: "HTTP Version Not Supported", + 506: "Variant Also Negotiates", # see RFC 2295 + 507: "Insufficient Storage", + 508: "Loop Detected", # see RFC 5842 + 510: "Not Extended", + 511: "Network Authentication Failed", +} + + +class COEP(Enum): + """Cross Origin Embedder Policies""" + + UNSAFE_NONE = "unsafe-none" + REQUIRE_CORP = "require-corp" + + +class COOP(Enum): + """Cross Origin Opener Policies""" + + UNSAFE_NONE = "unsafe-none" + SAME_ORIGIN_ALLOW_POPUPS = "same-origin-allow-popups" + SAME_ORIGIN = "same-origin" + + +def quote_header_value( + value: t.Union[str, int], extra_chars: str = "", allow_token: bool = True +) -> str: + """Quote a header value if necessary. + + .. versionadded:: 0.5 + + :param value: the value to quote. + :param extra_chars: a list of extra characters to skip quoting. + :param allow_token: if this is enabled token values are returned + unchanged. + """ + if isinstance(value, bytes): + value = value.decode("latin1") + value = str(value) + if allow_token: + token_chars = _token_chars | set(extra_chars) + if set(value).issubset(token_chars): + return value + value = value.replace("\\", "\\\\").replace('"', '\\"') + return f'"{value}"' + + +def unquote_header_value(value: str, is_filename: bool = False) -> str: + r"""Unquotes a header value. (Reversal of :func:`quote_header_value`). + This does not use the real unquoting but what browsers are actually + using for quoting. + + .. versionadded:: 0.5 + + :param value: the header value to unquote. + :param is_filename: The value represents a filename or path. + """ + if value and value[0] == value[-1] == '"': + # this is not the real unquoting, but fixing this so that the + # RFC is met will result in bugs with internet explorer and + # probably some other browsers as well. IE for example is + # uploading files with "C:\foo\bar.txt" as filename + value = value[1:-1] + + # if this is a filename and the starting characters look like + # a UNC path, then just return the value without quotes. Using the + # replace sequence below on a UNC path has the effect of turning + # the leading double slash into a single slash and then + # _fix_ie_filename() doesn't work correctly. See #458. + if not is_filename or value[:2] != "\\\\": + return value.replace("\\\\", "\\").replace('\\"', '"') + return value + + +def dump_options_header( + header: t.Optional[str], options: t.Mapping[str, t.Optional[t.Union[str, int]]] +) -> str: + """The reverse function to :func:`parse_options_header`. + + :param header: the header to dump + :param options: a dict of options to append. + """ + segments = [] + if header is not None: + segments.append(header) + for key, value in options.items(): + if value is None: + segments.append(key) + else: + segments.append(f"{key}={quote_header_value(value)}") + return "; ".join(segments) + + +def dump_header( + iterable: t.Union[t.Dict[str, t.Union[str, int]], t.Iterable[str]], + allow_token: bool = True, +) -> str: + """Dump an HTTP header again. This is the reversal of + :func:`parse_list_header`, :func:`parse_set_header` and + :func:`parse_dict_header`. This also quotes strings that include an + equals sign unless you pass it as dict of key, value pairs. + + >>> dump_header({'foo': 'bar baz'}) + 'foo="bar baz"' + >>> dump_header(('foo', 'bar baz')) + 'foo, "bar baz"' + + :param iterable: the iterable or dict of values to quote. + :param allow_token: if set to `False` tokens as values are disallowed. + See :func:`quote_header_value` for more details. + """ + if isinstance(iterable, dict): + items = [] + for key, value in iterable.items(): + if value is None: + items.append(key) + else: + items.append( + f"{key}={quote_header_value(value, allow_token=allow_token)}" + ) + else: + items = [quote_header_value(x, allow_token=allow_token) for x in iterable] + return ", ".join(items) + + +def dump_csp_header(header: "ds.ContentSecurityPolicy") -> str: + """Dump a Content Security Policy header. + + These are structured into policies such as "default-src 'self'; + script-src 'self'". + + .. versionadded:: 1.0.0 + Support for Content Security Policy headers was added. + + """ + return "; ".join(f"{key} {value}" for key, value in header.items()) + + +def parse_list_header(value: str) -> t.List[str]: + """Parse lists as described by RFC 2068 Section 2. + + In particular, parse comma-separated lists where the elements of + the list may include quoted-strings. A quoted-string could + contain a comma. A non-quoted string could have quotes in the + middle. Quotes are removed automatically after parsing. + + It basically works like :func:`parse_set_header` just that items + may appear multiple times and case sensitivity is preserved. + + The return value is a standard :class:`list`: + + >>> parse_list_header('token, "quoted value"') + ['token', 'quoted value'] + + To create a header from the :class:`list` again, use the + :func:`dump_header` function. + + :param value: a string with a list header. + :return: :class:`list` + """ + result = [] + for item in _parse_list_header(value): + if item[:1] == item[-1:] == '"': + item = unquote_header_value(item[1:-1]) + result.append(item) + return result + + +def parse_dict_header(value: str, cls: t.Type[dict] = dict) -> t.Dict[str, str]: + """Parse lists of key, value pairs as described by RFC 2068 Section 2 and + convert them into a python dict (or any other mapping object created from + the type with a dict like interface provided by the `cls` argument): + + >>> d = parse_dict_header('foo="is a fish", bar="as well"') + >>> type(d) is dict + True + >>> sorted(d.items()) + [('bar', 'as well'), ('foo', 'is a fish')] + + If there is no value for a key it will be `None`: + + >>> parse_dict_header('key_without_value') + {'key_without_value': None} + + To create a header from the :class:`dict` again, use the + :func:`dump_header` function. + + .. versionchanged:: 0.9 + Added support for `cls` argument. + + :param value: a string with a dict header. + :param cls: callable to use for storage of parsed results. + :return: an instance of `cls` + """ + result = cls() + if isinstance(value, bytes): + value = value.decode("latin1") + for item in _parse_list_header(value): + if "=" not in item: + result[item] = None + continue + name, value = item.split("=", 1) + if value[:1] == value[-1:] == '"': + value = unquote_header_value(value[1:-1]) + result[name] = value + return result + + +def parse_options_header(value: t.Optional[str]) -> t.Tuple[str, t.Dict[str, str]]: + """Parse a ``Content-Type``-like header into a tuple with the + value and any options: + + >>> parse_options_header('text/html; charset=utf8') + ('text/html', {'charset': 'utf8'}) + + This should is not for ``Cache-Control``-like headers, which use a + different format. For those, use :func:`parse_dict_header`. + + :param value: The header value to parse. + + .. versionchanged:: 2.2 + Option names are always converted to lowercase. + + .. versionchanged:: 2.1 + The ``multiple`` parameter is deprecated and will be removed in + Werkzeug 2.2. + + .. versionchanged:: 0.15 + :rfc:`2231` parameter continuations are handled. + + .. versionadded:: 0.5 + """ + if not value: + return "", {} + + result: t.List[t.Any] = [] + + value = "," + value.replace("\n", ",") + while value: + match = _option_header_start_mime_type.match(value) + if not match: + break + result.append(match.group(1)) # mimetype + options: t.Dict[str, str] = {} + # Parse options + rest = match.group(2) + encoding: t.Optional[str] + continued_encoding: t.Optional[str] = None + while rest: + optmatch = _option_header_piece_re.match(rest) + if not optmatch: + break + option, count, encoding, language, option_value = optmatch.groups() + # Continuations don't have to supply the encoding after the + # first line. If we're in a continuation, track the current + # encoding to use for subsequent lines. Reset it when the + # continuation ends. + if not count: + continued_encoding = None + else: + if not encoding: + encoding = continued_encoding + continued_encoding = encoding + option = unquote_header_value(option).lower() + + if option_value is not None: + option_value = unquote_header_value(option_value, option == "filename") + + if encoding is not None: + option_value = _unquote(option_value).decode(encoding) + + if count: + # Continuations append to the existing value. For + # simplicity, this ignores the possibility of + # out-of-order indices, which shouldn't happen anyway. + if option_value is not None: + options[option] = options.get(option, "") + option_value + else: + options[option] = option_value # type: ignore[assignment] + + rest = rest[optmatch.end() :] + result.append(options) + return tuple(result) # type: ignore[return-value] + + return tuple(result) if result else ("", {}) # type: ignore[return-value] + + +_TAnyAccept = t.TypeVar("_TAnyAccept", bound="ds.Accept") + + +@typing.overload +def parse_accept_header(value: t.Optional[str]) -> "ds.Accept": + ... + + +@typing.overload +def parse_accept_header( + value: t.Optional[str], cls: t.Type[_TAnyAccept] +) -> _TAnyAccept: + ... + + +def parse_accept_header( + value: t.Optional[str], cls: t.Optional[t.Type[_TAnyAccept]] = None +) -> _TAnyAccept: + """Parses an HTTP Accept-* header. This does not implement a complete + valid algorithm but one that supports at least value and quality + extraction. + + Returns a new :class:`Accept` object (basically a list of ``(value, quality)`` + tuples sorted by the quality with some additional accessor methods). + + The second parameter can be a subclass of :class:`Accept` that is created + with the parsed values and returned. + + :param value: the accept header string to be parsed. + :param cls: the wrapper class for the return value (can be + :class:`Accept` or a subclass thereof) + :return: an instance of `cls`. + """ + if cls is None: + cls = t.cast(t.Type[_TAnyAccept], ds.Accept) + + if not value: + return cls(None) + + result = [] + for match in _accept_re.finditer(value): + quality_match = match.group(2) + if not quality_match: + quality: float = 1 + else: + quality = max(min(float(quality_match), 1), 0) + result.append((match.group(1), quality)) + return cls(result) + + +_TAnyCC = t.TypeVar("_TAnyCC", bound="ds._CacheControl") +_t_cc_update = t.Optional[t.Callable[[_TAnyCC], None]] + + +@typing.overload +def parse_cache_control_header( + value: t.Optional[str], on_update: _t_cc_update, cls: None = None +) -> "ds.RequestCacheControl": + ... + + +@typing.overload +def parse_cache_control_header( + value: t.Optional[str], on_update: _t_cc_update, cls: t.Type[_TAnyCC] +) -> _TAnyCC: + ... + + +def parse_cache_control_header( + value: t.Optional[str], + on_update: _t_cc_update = None, + cls: t.Optional[t.Type[_TAnyCC]] = None, +) -> _TAnyCC: + """Parse a cache control header. The RFC differs between response and + request cache control, this method does not. It's your responsibility + to not use the wrong control statements. + + .. versionadded:: 0.5 + The `cls` was added. If not specified an immutable + :class:`~werkzeug.datastructures.RequestCacheControl` is returned. + + :param value: a cache control header to be parsed. + :param on_update: an optional callable that is called every time a value + on the :class:`~werkzeug.datastructures.CacheControl` + object is changed. + :param cls: the class for the returned object. By default + :class:`~werkzeug.datastructures.RequestCacheControl` is used. + :return: a `cls` object. + """ + if cls is None: + cls = t.cast(t.Type[_TAnyCC], ds.RequestCacheControl) + + if not value: + return cls((), on_update) + + return cls(parse_dict_header(value), on_update) + + +_TAnyCSP = t.TypeVar("_TAnyCSP", bound="ds.ContentSecurityPolicy") +_t_csp_update = t.Optional[t.Callable[[_TAnyCSP], None]] + + +@typing.overload +def parse_csp_header( + value: t.Optional[str], on_update: _t_csp_update, cls: None = None +) -> "ds.ContentSecurityPolicy": + ... + + +@typing.overload +def parse_csp_header( + value: t.Optional[str], on_update: _t_csp_update, cls: t.Type[_TAnyCSP] +) -> _TAnyCSP: + ... + + +def parse_csp_header( + value: t.Optional[str], + on_update: _t_csp_update = None, + cls: t.Optional[t.Type[_TAnyCSP]] = None, +) -> _TAnyCSP: + """Parse a Content Security Policy header. + + .. versionadded:: 1.0.0 + Support for Content Security Policy headers was added. + + :param value: a csp header to be parsed. + :param on_update: an optional callable that is called every time a value + on the object is changed. + :param cls: the class for the returned object. By default + :class:`~werkzeug.datastructures.ContentSecurityPolicy` is used. + :return: a `cls` object. + """ + if cls is None: + cls = t.cast(t.Type[_TAnyCSP], ds.ContentSecurityPolicy) + + if value is None: + return cls((), on_update) + + items = [] + + for policy in value.split(";"): + policy = policy.strip() + + # Ignore badly formatted policies (no space) + if " " in policy: + directive, value = policy.strip().split(" ", 1) + items.append((directive.strip(), value.strip())) + + return cls(items, on_update) + + +def parse_set_header( + value: t.Optional[str], + on_update: t.Optional[t.Callable[["ds.HeaderSet"], None]] = None, +) -> "ds.HeaderSet": + """Parse a set-like header and return a + :class:`~werkzeug.datastructures.HeaderSet` object: + + >>> hs = parse_set_header('token, "quoted value"') + + The return value is an object that treats the items case-insensitively + and keeps the order of the items: + + >>> 'TOKEN' in hs + True + >>> hs.index('quoted value') + 1 + >>> hs + HeaderSet(['token', 'quoted value']) + + To create a header from the :class:`HeaderSet` again, use the + :func:`dump_header` function. + + :param value: a set header to be parsed. + :param on_update: an optional callable that is called every time a + value on the :class:`~werkzeug.datastructures.HeaderSet` + object is changed. + :return: a :class:`~werkzeug.datastructures.HeaderSet` + """ + if not value: + return ds.HeaderSet(None, on_update) + return ds.HeaderSet(parse_list_header(value), on_update) + + +def parse_authorization_header( + value: t.Optional[str], +) -> t.Optional["ds.Authorization"]: + """Parse an HTTP basic/digest authorization header transmitted by the web + browser. The return value is either `None` if the header was invalid or + not given, otherwise an :class:`~werkzeug.datastructures.Authorization` + object. + + :param value: the authorization header to parse. + :return: a :class:`~werkzeug.datastructures.Authorization` object or `None`. + """ + if not value: + return None + value = _wsgi_decoding_dance(value) + try: + auth_type, auth_info = value.split(None, 1) + auth_type = auth_type.lower() + except ValueError: + return None + if auth_type == "basic": + try: + username, password = base64.b64decode(auth_info).split(b":", 1) + except Exception: + return None + try: + return ds.Authorization( + "basic", + { + "username": _to_str(username, "utf-8"), + "password": _to_str(password, "utf-8"), + }, + ) + except UnicodeDecodeError: + return None + elif auth_type == "digest": + auth_map = parse_dict_header(auth_info) + for key in "username", "realm", "nonce", "uri", "response": + if key not in auth_map: + return None + if "qop" in auth_map: + if not auth_map.get("nc") or not auth_map.get("cnonce"): + return None + return ds.Authorization("digest", auth_map) + return None + + +def parse_www_authenticate_header( + value: t.Optional[str], + on_update: t.Optional[t.Callable[["ds.WWWAuthenticate"], None]] = None, +) -> "ds.WWWAuthenticate": + """Parse an HTTP WWW-Authenticate header into a + :class:`~werkzeug.datastructures.WWWAuthenticate` object. + + :param value: a WWW-Authenticate header to parse. + :param on_update: an optional callable that is called every time a value + on the :class:`~werkzeug.datastructures.WWWAuthenticate` + object is changed. + :return: a :class:`~werkzeug.datastructures.WWWAuthenticate` object. + """ + if not value: + return ds.WWWAuthenticate(on_update=on_update) + try: + auth_type, auth_info = value.split(None, 1) + auth_type = auth_type.lower() + except (ValueError, AttributeError): + return ds.WWWAuthenticate(value.strip().lower(), on_update=on_update) + return ds.WWWAuthenticate(auth_type, parse_dict_header(auth_info), on_update) + + +def parse_if_range_header(value: t.Optional[str]) -> "ds.IfRange": + """Parses an if-range header which can be an etag or a date. Returns + a :class:`~werkzeug.datastructures.IfRange` object. + + .. versionchanged:: 2.0 + If the value represents a datetime, it is timezone-aware. + + .. versionadded:: 0.7 + """ + if not value: + return ds.IfRange() + date = parse_date(value) + if date is not None: + return ds.IfRange(date=date) + # drop weakness information + return ds.IfRange(unquote_etag(value)[0]) + + +def parse_range_header( + value: t.Optional[str], make_inclusive: bool = True +) -> t.Optional["ds.Range"]: + """Parses a range header into a :class:`~werkzeug.datastructures.Range` + object. If the header is missing or malformed `None` is returned. + `ranges` is a list of ``(start, stop)`` tuples where the ranges are + non-inclusive. + + .. versionadded:: 0.7 + """ + if not value or "=" not in value: + return None + + ranges = [] + last_end = 0 + units, rng = value.split("=", 1) + units = units.strip().lower() + + for item in rng.split(","): + item = item.strip() + if "-" not in item: + return None + if item.startswith("-"): + if last_end < 0: + return None + try: + begin = int(item) + except ValueError: + return None + end = None + last_end = -1 + elif "-" in item: + begin_str, end_str = item.split("-", 1) + begin_str = begin_str.strip() + end_str = end_str.strip() + + try: + begin = int(begin_str) + except ValueError: + return None + + if begin < last_end or last_end < 0: + return None + if end_str: + try: + end = int(end_str) + 1 + except ValueError: + return None + + if begin >= end: + return None + else: + end = None + last_end = end if end is not None else -1 + ranges.append((begin, end)) + + return ds.Range(units, ranges) + + +def parse_content_range_header( + value: t.Optional[str], + on_update: t.Optional[t.Callable[["ds.ContentRange"], None]] = None, +) -> t.Optional["ds.ContentRange"]: + """Parses a range header into a + :class:`~werkzeug.datastructures.ContentRange` object or `None` if + parsing is not possible. + + .. versionadded:: 0.7 + + :param value: a content range header to be parsed. + :param on_update: an optional callable that is called every time a value + on the :class:`~werkzeug.datastructures.ContentRange` + object is changed. + """ + if value is None: + return None + try: + units, rangedef = (value or "").strip().split(None, 1) + except ValueError: + return None + + if "/" not in rangedef: + return None + rng, length_str = rangedef.split("/", 1) + if length_str == "*": + length = None + else: + try: + length = int(length_str) + except ValueError: + return None + + if rng == "*": + return ds.ContentRange(units, None, None, length, on_update=on_update) + elif "-" not in rng: + return None + + start_str, stop_str = rng.split("-", 1) + try: + start = int(start_str) + stop = int(stop_str) + 1 + except ValueError: + return None + + if is_byte_range_valid(start, stop, length): + return ds.ContentRange(units, start, stop, length, on_update=on_update) + + return None + + +def quote_etag(etag: str, weak: bool = False) -> str: + """Quote an etag. + + :param etag: the etag to quote. + :param weak: set to `True` to tag it "weak". + """ + if '"' in etag: + raise ValueError("invalid etag") + etag = f'"{etag}"' + if weak: + etag = f"W/{etag}" + return etag + + +def unquote_etag( + etag: t.Optional[str], +) -> t.Union[t.Tuple[str, bool], t.Tuple[None, None]]: + """Unquote a single etag: + + >>> unquote_etag('W/"bar"') + ('bar', True) + >>> unquote_etag('"bar"') + ('bar', False) + + :param etag: the etag identifier to unquote. + :return: a ``(etag, weak)`` tuple. + """ + if not etag: + return None, None + etag = etag.strip() + weak = False + if etag.startswith(("W/", "w/")): + weak = True + etag = etag[2:] + if etag[:1] == etag[-1:] == '"': + etag = etag[1:-1] + return etag, weak + + +def parse_etags(value: t.Optional[str]) -> "ds.ETags": + """Parse an etag header. + + :param value: the tag header to parse + :return: an :class:`~werkzeug.datastructures.ETags` object. + """ + if not value: + return ds.ETags() + strong = [] + weak = [] + end = len(value) + pos = 0 + while pos < end: + match = _etag_re.match(value, pos) + if match is None: + break + is_weak, quoted, raw = match.groups() + if raw == "*": + return ds.ETags(star_tag=True) + elif quoted: + raw = quoted + if is_weak: + weak.append(raw) + else: + strong.append(raw) + pos = match.end() + return ds.ETags(strong, weak) + + +def generate_etag(data: bytes) -> str: + """Generate an etag for some data. + + .. versionchanged:: 2.0 + Use SHA-1. MD5 may not be available in some environments. + """ + return sha1(data).hexdigest() + + +def parse_date(value: t.Optional[str]) -> t.Optional[datetime]: + """Parse an :rfc:`2822` date into a timezone-aware + :class:`datetime.datetime` object, or ``None`` if parsing fails. + + This is a wrapper for :func:`email.utils.parsedate_to_datetime`. It + returns ``None`` if parsing fails instead of raising an exception, + and always returns a timezone-aware datetime object. If the string + doesn't have timezone information, it is assumed to be UTC. + + :param value: A string with a supported date format. + + .. versionchanged:: 2.0 + Return a timezone-aware datetime object. Use + ``email.utils.parsedate_to_datetime``. + """ + if value is None: + return None + + try: + dt = email.utils.parsedate_to_datetime(value) + except (TypeError, ValueError): + return None + + if dt.tzinfo is None: + return dt.replace(tzinfo=timezone.utc) + + return dt + + +def http_date( + timestamp: t.Optional[t.Union[datetime, date, int, float, struct_time]] = None +) -> str: + """Format a datetime object or timestamp into an :rfc:`2822` date + string. + + This is a wrapper for :func:`email.utils.format_datetime`. It + assumes naive datetime objects are in UTC instead of raising an + exception. + + :param timestamp: The datetime or timestamp to format. Defaults to + the current time. + + .. versionchanged:: 2.0 + Use ``email.utils.format_datetime``. Accept ``date`` objects. + """ + if isinstance(timestamp, date): + if not isinstance(timestamp, datetime): + # Assume plain date is midnight UTC. + timestamp = datetime.combine(timestamp, time(), tzinfo=timezone.utc) + else: + # Ensure datetime is timezone-aware. + timestamp = _dt_as_utc(timestamp) + + return email.utils.format_datetime(timestamp, usegmt=True) + + if isinstance(timestamp, struct_time): + timestamp = mktime(timestamp) + + return email.utils.formatdate(timestamp, usegmt=True) + + +def parse_age(value: t.Optional[str] = None) -> t.Optional[timedelta]: + """Parses a base-10 integer count of seconds into a timedelta. + + If parsing fails, the return value is `None`. + + :param value: a string consisting of an integer represented in base-10 + :return: a :class:`datetime.timedelta` object or `None`. + """ + if not value: + return None + try: + seconds = int(value) + except ValueError: + return None + if seconds < 0: + return None + try: + return timedelta(seconds=seconds) + except OverflowError: + return None + + +def dump_age(age: t.Optional[t.Union[timedelta, int]] = None) -> t.Optional[str]: + """Formats the duration as a base-10 integer. + + :param age: should be an integer number of seconds, + a :class:`datetime.timedelta` object, or, + if the age is unknown, `None` (default). + """ + if age is None: + return None + if isinstance(age, timedelta): + age = int(age.total_seconds()) + else: + age = int(age) + + if age < 0: + raise ValueError("age cannot be negative") + + return str(age) + + +def is_resource_modified( + environ: "WSGIEnvironment", + etag: t.Optional[str] = None, + data: t.Optional[bytes] = None, + last_modified: t.Optional[t.Union[datetime, str]] = None, + ignore_if_range: bool = True, +) -> bool: + """Convenience method for conditional requests. + + :param environ: the WSGI environment of the request to be checked. + :param etag: the etag for the response for comparison. + :param data: or alternatively the data of the response to automatically + generate an etag using :func:`generate_etag`. + :param last_modified: an optional date of the last modification. + :param ignore_if_range: If `False`, `If-Range` header will be taken into + account. + :return: `True` if the resource was modified, otherwise `False`. + + .. versionchanged:: 2.0 + SHA-1 is used to generate an etag value for the data. MD5 may + not be available in some environments. + + .. versionchanged:: 1.0.0 + The check is run for methods other than ``GET`` and ``HEAD``. + """ + return _sansio_http.is_resource_modified( + http_range=environ.get("HTTP_RANGE"), + http_if_range=environ.get("HTTP_IF_RANGE"), + http_if_modified_since=environ.get("HTTP_IF_MODIFIED_SINCE"), + http_if_none_match=environ.get("HTTP_IF_NONE_MATCH"), + http_if_match=environ.get("HTTP_IF_MATCH"), + etag=etag, + data=data, + last_modified=last_modified, + ignore_if_range=ignore_if_range, + ) + + +def remove_entity_headers( + headers: t.Union["ds.Headers", t.List[t.Tuple[str, str]]], + allowed: t.Iterable[str] = ("expires", "content-location"), +) -> None: + """Remove all entity headers from a list or :class:`Headers` object. This + operation works in-place. `Expires` and `Content-Location` headers are + by default not removed. The reason for this is :rfc:`2616` section + 10.3.5 which specifies some entity headers that should be sent. + + .. versionchanged:: 0.5 + added `allowed` parameter. + + :param headers: a list or :class:`Headers` object. + :param allowed: a list of headers that should still be allowed even though + they are entity headers. + """ + allowed = {x.lower() for x in allowed} + headers[:] = [ + (key, value) + for key, value in headers + if not is_entity_header(key) or key.lower() in allowed + ] + + +def remove_hop_by_hop_headers( + headers: t.Union["ds.Headers", t.List[t.Tuple[str, str]]] +) -> None: + """Remove all HTTP/1.1 "Hop-by-Hop" headers from a list or + :class:`Headers` object. This operation works in-place. + + .. versionadded:: 0.5 + + :param headers: a list or :class:`Headers` object. + """ + headers[:] = [ + (key, value) for key, value in headers if not is_hop_by_hop_header(key) + ] + + +def is_entity_header(header: str) -> bool: + """Check if a header is an entity header. + + .. versionadded:: 0.5 + + :param header: the header to test. + :return: `True` if it's an entity header, `False` otherwise. + """ + return header.lower() in _entity_headers + + +def is_hop_by_hop_header(header: str) -> bool: + """Check if a header is an HTTP/1.1 "Hop-by-Hop" header. + + .. versionadded:: 0.5 + + :param header: the header to test. + :return: `True` if it's an HTTP/1.1 "Hop-by-Hop" header, `False` otherwise. + """ + return header.lower() in _hop_by_hop_headers + + +def parse_cookie( + header: t.Union["WSGIEnvironment", str, bytes, None], + charset: str = "utf-8", + errors: str = "replace", + cls: t.Optional[t.Type["ds.MultiDict"]] = None, +) -> "ds.MultiDict[str, str]": + """Parse a cookie from a string or WSGI environ. + + The same key can be provided multiple times, the values are stored + in-order. The default :class:`MultiDict` will have the first value + first, and all values can be retrieved with + :meth:`MultiDict.getlist`. + + :param header: The cookie header as a string, or a WSGI environ dict + with a ``HTTP_COOKIE`` key. + :param charset: The charset for the cookie values. + :param errors: The error behavior for the charset decoding. + :param cls: A dict-like class to store the parsed cookies in. + Defaults to :class:`MultiDict`. + + .. versionchanged:: 1.0.0 + Returns a :class:`MultiDict` instead of a + ``TypeConversionDict``. + + .. versionchanged:: 0.5 + Returns a :class:`TypeConversionDict` instead of a regular dict. + The ``cls`` parameter was added. + """ + if isinstance(header, dict): + cookie = header.get("HTTP_COOKIE", "") + elif header is None: + cookie = "" + else: + cookie = header + + return _sansio_http.parse_cookie( + cookie=cookie, charset=charset, errors=errors, cls=cls + ) + + +def dump_cookie( + key: str, + value: t.Union[bytes, str] = "", + max_age: t.Optional[t.Union[timedelta, int]] = None, + expires: t.Optional[t.Union[str, datetime, int, float]] = None, + path: t.Optional[str] = "/", + domain: t.Optional[str] = None, + secure: bool = False, + httponly: bool = False, + charset: str = "utf-8", + sync_expires: bool = True, + max_size: int = 4093, + samesite: t.Optional[str] = None, +) -> str: + """Create a Set-Cookie header without the ``Set-Cookie`` prefix. + + The return value is usually restricted to ascii as the vast majority + of values are properly escaped, but that is no guarantee. It's + tunneled through latin1 as required by :pep:`3333`. + + The return value is not ASCII safe if the key contains unicode + characters. This is technically against the specification but + happens in the wild. It's strongly recommended to not use + non-ASCII values for the keys. + + :param max_age: should be a number of seconds, or `None` (default) if + the cookie should last only as long as the client's + browser session. Additionally `timedelta` objects + are accepted, too. + :param expires: should be a `datetime` object or unix timestamp. + :param path: limits the cookie to a given path, per default it will + span the whole domain. + :param domain: Use this if you want to set a cross-domain cookie. For + example, ``domain=".example.com"`` will set a cookie + that is readable by the domain ``www.example.com``, + ``foo.example.com`` etc. Otherwise, a cookie will only + be readable by the domain that set it. + :param secure: The cookie will only be available via HTTPS + :param httponly: disallow JavaScript to access the cookie. This is an + extension to the cookie standard and probably not + supported by all browsers. + :param charset: the encoding for string values. + :param sync_expires: automatically set expires if max_age is defined + but expires not. + :param max_size: Warn if the final header value exceeds this size. The + default, 4093, should be safely `supported by most browsers + `_. Set to 0 to disable this check. + :param samesite: Limits the scope of the cookie such that it will + only be attached to requests if those requests are same-site. + + .. _`cookie`: http://browsercookielimits.squawky.net/ + + .. versionchanged:: 1.0.0 + The string ``'None'`` is accepted for ``samesite``. + """ + key = _to_bytes(key, charset) + value = _to_bytes(value, charset) + + if path is not None: + from .urls import iri_to_uri + + path = iri_to_uri(path, charset) + + domain = _make_cookie_domain(domain) + + if isinstance(max_age, timedelta): + max_age = int(max_age.total_seconds()) + + if expires is not None: + if not isinstance(expires, str): + expires = http_date(expires) + elif max_age is not None and sync_expires: + expires = http_date(datetime.now(tz=timezone.utc).timestamp() + max_age) + + if samesite is not None: + samesite = samesite.title() + + if samesite not in {"Strict", "Lax", "None"}: + raise ValueError("SameSite must be 'Strict', 'Lax', or 'None'.") + + buf = [key + b"=" + _cookie_quote(value)] + + # XXX: In theory all of these parameters that are not marked with `None` + # should be quoted. Because stdlib did not quote it before I did not + # want to introduce quoting there now. + for k, v, q in ( + (b"Domain", domain, True), + (b"Expires", expires, False), + (b"Max-Age", max_age, False), + (b"Secure", secure, None), + (b"HttpOnly", httponly, None), + (b"Path", path, False), + (b"SameSite", samesite, False), + ): + if q is None: + if v: + buf.append(k) + continue + + if v is None: + continue + + tmp = bytearray(k) + if not isinstance(v, (bytes, bytearray)): + v = _to_bytes(str(v), charset) + if q: + v = _cookie_quote(v) + tmp += b"=" + v + buf.append(bytes(tmp)) + + # The return value will be an incorrectly encoded latin1 header for + # consistency with the headers object. + rv = b"; ".join(buf) + rv = rv.decode("latin1") + + # Warn if the final value of the cookie is larger than the limit. If the + # cookie is too large, then it may be silently ignored by the browser, + # which can be quite hard to debug. + cookie_size = len(rv) + + if max_size and cookie_size > max_size: + value_size = len(value) + warnings.warn( + f"The {key.decode(charset)!r} cookie is too large: the value was" + f" {value_size} bytes but the" + f" header required {cookie_size - value_size} extra bytes. The final size" + f" was {cookie_size} bytes but the limit is {max_size} bytes. Browsers may" + f" silently ignore cookies larger than this.", + stacklevel=2, + ) + + return rv + + +def is_byte_range_valid( + start: t.Optional[int], stop: t.Optional[int], length: t.Optional[int] +) -> bool: + """Checks if a given byte content range is valid for the given length. + + .. versionadded:: 0.7 + """ + if (start is None) != (stop is None): + return False + elif start is None: + return length is None or length >= 0 + elif length is None: + return 0 <= start < stop # type: ignore + elif start >= stop: # type: ignore + return False + return 0 <= start < length + + +# circular dependencies +from . import datastructures as ds +from .sansio import http as _sansio_http diff --git a/.vtodo/Lib/site-packages/werkzeug/local.py b/.vtodo/Lib/site-packages/werkzeug/local.py new file mode 100644 index 0000000..70e9bf7 --- /dev/null +++ b/.vtodo/Lib/site-packages/werkzeug/local.py @@ -0,0 +1,648 @@ +import copy +import math +import operator +import typing as t +from contextvars import ContextVar +from functools import partial +from functools import update_wrapper +from operator import attrgetter + +from .wsgi import ClosingIterator + +if t.TYPE_CHECKING: + from _typeshed.wsgi import StartResponse + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + +T = t.TypeVar("T") +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) + + +def release_local(local: t.Union["Local", "LocalStack"]) -> None: + """Release the data for the current context in a :class:`Local` or + :class:`LocalStack` without using a :class:`LocalManager`. + + This should not be needed for modern use cases, and may be removed + in the future. + + .. versionadded:: 0.6.1 + """ + local.__release_local__() + + +class Local: + """Create a namespace of context-local data. This wraps a + :class:`ContextVar` containing a :class:`dict` value. + + This may incur a performance penalty compared to using individual + context vars, as it has to copy data to avoid mutating the dict + between nested contexts. + + :param context_var: The :class:`~contextvars.ContextVar` to use as + storage for this local. If not given, one will be created. + Context vars not created at the global scope may interfere with + garbage collection. + + .. versionchanged:: 2.0 + Uses ``ContextVar`` instead of a custom storage implementation. + """ + + __slots__ = ("__storage",) + + def __init__( + self, context_var: t.Optional[ContextVar[t.Dict[str, t.Any]]] = None + ) -> None: + if context_var is None: + # A ContextVar not created at global scope interferes with + # Python's garbage collection. However, a local only makes + # sense defined at the global scope as well, in which case + # the GC issue doesn't seem relevant. + context_var = ContextVar(f"werkzeug.Local<{id(self)}>.storage") + + object.__setattr__(self, "_Local__storage", context_var) + + def __iter__(self) -> t.Iterator[t.Tuple[str, t.Any]]: + return iter(self.__storage.get({}).items()) + + def __call__( + self, name: str, *, unbound_message: t.Optional[str] = None + ) -> "LocalProxy": + """Create a :class:`LocalProxy` that access an attribute on this + local namespace. + + :param name: Proxy this attribute. + :param unbound_message: The error message that the proxy will + show if the attribute isn't set. + """ + return LocalProxy(self, name, unbound_message=unbound_message) + + def __release_local__(self) -> None: + self.__storage.set({}) + + def __getattr__(self, name: str) -> t.Any: + values = self.__storage.get({}) + + if name in values: + return values[name] + + raise AttributeError(name) + + def __setattr__(self, name: str, value: t.Any) -> None: + values = self.__storage.get({}).copy() + values[name] = value + self.__storage.set(values) + + def __delattr__(self, name: str) -> None: + values = self.__storage.get({}) + + if name in values: + values = values.copy() + del values[name] + self.__storage.set(values) + else: + raise AttributeError(name) + + +class LocalStack(t.Generic[T]): + """Create a stack of context-local data. This wraps a + :class:`ContextVar` containing a :class:`list` value. + + This may incur a performance penalty compared to using individual + context vars, as it has to copy data to avoid mutating the list + between nested contexts. + + :param context_var: The :class:`~contextvars.ContextVar` to use as + storage for this local. If not given, one will be created. + Context vars not created at the global scope may interfere with + garbage collection. + + .. versionchanged:: 2.0 + Uses ``ContextVar`` instead of a custom storage implementation. + + .. versionadded:: 0.6.1 + """ + + __slots__ = ("_storage",) + + def __init__(self, context_var: t.Optional[ContextVar[t.List[T]]] = None) -> None: + if context_var is None: + # A ContextVar not created at global scope interferes with + # Python's garbage collection. However, a local only makes + # sense defined at the global scope as well, in which case + # the GC issue doesn't seem relevant. + context_var = ContextVar(f"werkzeug.LocalStack<{id(self)}>.storage") + + self._storage = context_var + + def __release_local__(self) -> None: + self._storage.set([]) + + def push(self, obj: T) -> t.List[T]: + """Add a new item to the top of the stack.""" + stack = self._storage.get([]).copy() + stack.append(obj) + self._storage.set(stack) + return stack + + def pop(self) -> t.Optional[T]: + """Remove the top item from the stack and return it. If the + stack is empty, return ``None``. + """ + stack = self._storage.get([]) + + if len(stack) == 0: + return None + + rv = stack[-1] + self._storage.set(stack[:-1]) + return rv + + @property + def top(self) -> t.Optional[T]: + """The topmost item on the stack. If the stack is empty, + `None` is returned. + """ + stack = self._storage.get([]) + + if len(stack) == 0: + return None + + return stack[-1] + + def __call__( + self, name: t.Optional[str] = None, *, unbound_message: t.Optional[str] = None + ) -> "LocalProxy": + """Create a :class:`LocalProxy` that accesses the top of this + local stack. + + :param name: If given, the proxy access this attribute of the + top item, rather than the item itself. + :param unbound_message: The error message that the proxy will + show if the stack is empty. + """ + return LocalProxy(self, name, unbound_message=unbound_message) + + +class LocalManager: + """Manage releasing the data for the current context in one or more + :class:`Local` and :class:`LocalStack` objects. + + This should not be needed for modern use cases, and may be removed + in the future. + + :param locals: A local or list of locals to manage. + + .. versionchanged:: 2.0 + ``ident_func`` is deprecated and will be removed in Werkzeug + 2.1. + + .. versionchanged:: 0.7 + The ``ident_func`` parameter was added. + + .. versionchanged:: 0.6.1 + The :func:`release_local` function can be used instead of a + manager. + """ + + __slots__ = ("locals",) + + def __init__( + self, + locals: t.Optional[ + t.Union[Local, LocalStack, t.Iterable[t.Union[Local, LocalStack]]] + ] = None, + ) -> None: + if locals is None: + self.locals = [] + elif isinstance(locals, Local): + self.locals = [locals] + else: + self.locals = list(locals) # type: ignore[arg-type] + + def cleanup(self) -> None: + """Release the data in the locals for this context. Call this at + the end of each request or use :meth:`make_middleware`. + """ + for local in self.locals: + release_local(local) + + def make_middleware(self, app: "WSGIApplication") -> "WSGIApplication": + """Wrap a WSGI application so that local data is released + automatically after the response has been sent for a request. + """ + + def application( + environ: "WSGIEnvironment", start_response: "StartResponse" + ) -> t.Iterable[bytes]: + return ClosingIterator(app(environ, start_response), self.cleanup) + + return application + + def middleware(self, func: "WSGIApplication") -> "WSGIApplication": + """Like :meth:`make_middleware` but used as a decorator on the + WSGI application function. + + .. code-block:: python + + @manager.middleware + def application(environ, start_response): + ... + """ + return update_wrapper(self.make_middleware(func), func) + + def __repr__(self) -> str: + return f"<{type(self).__name__} storages: {len(self.locals)}>" + + +class _ProxyLookup: + """Descriptor that handles proxied attribute lookup for + :class:`LocalProxy`. + + :param f: The built-in function this attribute is accessed through. + Instead of looking up the special method, the function call + is redone on the object. + :param fallback: Return this function if the proxy is unbound + instead of raising a :exc:`RuntimeError`. + :param is_attr: This proxied name is an attribute, not a function. + Call the fallback immediately to get the value. + :param class_value: Value to return when accessed from the + ``LocalProxy`` class directly. Used for ``__doc__`` so building + docs still works. + """ + + __slots__ = ("bind_f", "fallback", "is_attr", "class_value", "name") + + def __init__( + self, + f: t.Optional[t.Callable] = None, + fallback: t.Optional[t.Callable] = None, + class_value: t.Optional[t.Any] = None, + is_attr: bool = False, + ) -> None: + bind_f: t.Optional[t.Callable[["LocalProxy", t.Any], t.Callable]] + + if hasattr(f, "__get__"): + # A Python function, can be turned into a bound method. + + def bind_f(instance: "LocalProxy", obj: t.Any) -> t.Callable: + return f.__get__(obj, type(obj)) # type: ignore + + elif f is not None: + # A C function, use partial to bind the first argument. + + def bind_f(instance: "LocalProxy", obj: t.Any) -> t.Callable: + return partial(f, obj) # type: ignore + + else: + # Use getattr, which will produce a bound method. + bind_f = None + + self.bind_f = bind_f + self.fallback = fallback + self.class_value = class_value + self.is_attr = is_attr + + def __set_name__(self, owner: "LocalProxy", name: str) -> None: + self.name = name + + def __get__(self, instance: "LocalProxy", owner: t.Optional[type] = None) -> t.Any: + if instance is None: + if self.class_value is not None: + return self.class_value + + return self + + try: + obj = instance._get_current_object() # type: ignore[misc] + except RuntimeError: + if self.fallback is None: + raise + + fallback = self.fallback.__get__(instance, owner) + + if self.is_attr: + # __class__ and __doc__ are attributes, not methods. + # Call the fallback to get the value. + return fallback() + + return fallback + + if self.bind_f is not None: + return self.bind_f(instance, obj) + + return getattr(obj, self.name) + + def __repr__(self) -> str: + return f"proxy {self.name}" + + def __call__(self, instance: "LocalProxy", *args: t.Any, **kwargs: t.Any) -> t.Any: + """Support calling unbound methods from the class. For example, + this happens with ``copy.copy``, which does + ``type(x).__copy__(x)``. ``type(x)`` can't be proxied, so it + returns the proxy type and descriptor. + """ + return self.__get__(instance, type(instance))(*args, **kwargs) + + +class _ProxyIOp(_ProxyLookup): + """Look up an augmented assignment method on a proxied object. The + method is wrapped to return the proxy instead of the object. + """ + + __slots__ = () + + def __init__( + self, f: t.Optional[t.Callable] = None, fallback: t.Optional[t.Callable] = None + ) -> None: + super().__init__(f, fallback) + + def bind_f(instance: "LocalProxy", obj: t.Any) -> t.Callable: + def i_op(self: t.Any, other: t.Any) -> "LocalProxy": + f(self, other) # type: ignore + return instance + + return i_op.__get__(obj, type(obj)) # type: ignore + + self.bind_f = bind_f + + +def _l_to_r_op(op: F) -> F: + """Swap the argument order to turn an l-op into an r-op.""" + + def r_op(obj: t.Any, other: t.Any) -> t.Any: + return op(other, obj) + + return t.cast(F, r_op) + + +def _identity(o: T) -> T: + return o + + +class LocalProxy(t.Generic[T]): + """A proxy to the object bound to a context-local object. All + operations on the proxy are forwarded to the bound object. If no + object is bound, a ``RuntimeError`` is raised. + + :param local: The context-local object that provides the proxied + object. + :param name: Proxy this attribute from the proxied object. + :param unbound_message: The error message to show if the + context-local object is unbound. + + Proxy a :class:`~contextvars.ContextVar` to make it easier to + access. Pass a name to proxy that attribute. + + .. code-block:: python + + _request_var = ContextVar("request") + request = LocalProxy(_request_var) + session = LocalProxy(_request_var, "session") + + Proxy an attribute on a :class:`Local` namespace by calling the + local with the attribute name: + + .. code-block:: python + + data = Local() + user = data("user") + + Proxy the top item on a :class:`LocalStack` by calling the local. + Pass a name to proxy that attribute. + + .. code-block:: + + app_stack = LocalStack() + current_app = app_stack() + g = app_stack("g") + + Pass a function to proxy the return value from that function. This + was previously used to access attributes of local objects before + that was supported directly. + + .. code-block:: python + + session = LocalProxy(lambda: request.session) + + ``__repr__`` and ``__class__`` are proxied, so ``repr(x)`` and + ``isinstance(x, cls)`` will look like the proxied object. Use + ``issubclass(type(x), LocalProxy)`` to check if an object is a + proxy. + + .. code-block:: python + + repr(user) # + isinstance(user, User) # True + issubclass(type(user), LocalProxy) # True + + .. versionchanged:: 2.2.2 + ``__wrapped__`` is set when wrapping an object, not only when + wrapping a function, to prevent doctest from failing. + + .. versionchanged:: 2.2 + Can proxy a ``ContextVar`` or ``LocalStack`` directly. + + .. versionchanged:: 2.2 + The ``name`` parameter can be used with any proxied object, not + only ``Local``. + + .. versionchanged:: 2.2 + Added the ``unbound_message`` parameter. + + .. versionchanged:: 2.0 + Updated proxied attributes and methods to reflect the current + data model. + + .. versionchanged:: 0.6.1 + The class can be instantiated with a callable. + """ + + __slots__ = ("__wrapped", "_get_current_object") + + _get_current_object: t.Callable[[], T] + """Return the current object this proxy is bound to. If the proxy is + unbound, this raises a ``RuntimeError``. + + This should be used if you need to pass the object to something that + doesn't understand the proxy. It can also be useful for performance + if you are accessing the object multiple times in a function, rather + than going through the proxy multiple times. + """ + + def __init__( + self, + local: t.Union[ContextVar[T], Local, LocalStack[T], t.Callable[[], T]], + name: t.Optional[str] = None, + *, + unbound_message: t.Optional[str] = None, + ) -> None: + if name is None: + get_name = _identity + else: + get_name = attrgetter(name) # type: ignore[assignment] + + if unbound_message is None: + unbound_message = "object is not bound" + + if isinstance(local, Local): + if name is None: + raise TypeError("'name' is required when proxying a 'Local' object.") + + def _get_current_object() -> T: + try: + return get_name(local) # type: ignore[return-value] + except AttributeError: + raise RuntimeError(unbound_message) from None + + elif isinstance(local, LocalStack): + + def _get_current_object() -> T: + obj = local.top # type: ignore[union-attr] + + if obj is None: + raise RuntimeError(unbound_message) + + return get_name(obj) + + elif isinstance(local, ContextVar): + + def _get_current_object() -> T: + try: + obj = local.get() # type: ignore[union-attr] + except LookupError: + raise RuntimeError(unbound_message) from None + + return get_name(obj) + + elif callable(local): + + def _get_current_object() -> T: + return get_name(local()) # type: ignore + + else: + raise TypeError(f"Don't know how to proxy '{type(local)}'.") + + object.__setattr__(self, "_LocalProxy__wrapped", local) + object.__setattr__(self, "_get_current_object", _get_current_object) + + __doc__ = _ProxyLookup( # type: ignore + class_value=__doc__, fallback=lambda self: type(self).__doc__, is_attr=True + ) + __wrapped__ = _ProxyLookup( + fallback=lambda self: self._LocalProxy__wrapped, is_attr=True + ) + # __del__ should only delete the proxy + __repr__ = _ProxyLookup( # type: ignore + repr, fallback=lambda self: f"<{type(self).__name__} unbound>" + ) + __str__ = _ProxyLookup(str) # type: ignore + __bytes__ = _ProxyLookup(bytes) + __format__ = _ProxyLookup() # type: ignore + __lt__ = _ProxyLookup(operator.lt) + __le__ = _ProxyLookup(operator.le) + __eq__ = _ProxyLookup(operator.eq) # type: ignore + __ne__ = _ProxyLookup(operator.ne) # type: ignore + __gt__ = _ProxyLookup(operator.gt) + __ge__ = _ProxyLookup(operator.ge) + __hash__ = _ProxyLookup(hash) # type: ignore + __bool__ = _ProxyLookup(bool, fallback=lambda self: False) + __getattr__ = _ProxyLookup(getattr) + # __getattribute__ triggered through __getattr__ + __setattr__ = _ProxyLookup(setattr) # type: ignore + __delattr__ = _ProxyLookup(delattr) # type: ignore + __dir__ = _ProxyLookup(dir, fallback=lambda self: []) # type: ignore + # __get__ (proxying descriptor not supported) + # __set__ (descriptor) + # __delete__ (descriptor) + # __set_name__ (descriptor) + # __objclass__ (descriptor) + # __slots__ used by proxy itself + # __dict__ (__getattr__) + # __weakref__ (__getattr__) + # __init_subclass__ (proxying metaclass not supported) + # __prepare__ (metaclass) + __class__ = _ProxyLookup( + fallback=lambda self: type(self), is_attr=True + ) # type: ignore + __instancecheck__ = _ProxyLookup(lambda self, other: isinstance(other, self)) + __subclasscheck__ = _ProxyLookup(lambda self, other: issubclass(other, self)) + # __class_getitem__ triggered through __getitem__ + __call__ = _ProxyLookup(lambda self, *args, **kwargs: self(*args, **kwargs)) + __len__ = _ProxyLookup(len) + __length_hint__ = _ProxyLookup(operator.length_hint) + __getitem__ = _ProxyLookup(operator.getitem) + __setitem__ = _ProxyLookup(operator.setitem) + __delitem__ = _ProxyLookup(operator.delitem) + # __missing__ triggered through __getitem__ + __iter__ = _ProxyLookup(iter) + __next__ = _ProxyLookup(next) + __reversed__ = _ProxyLookup(reversed) + __contains__ = _ProxyLookup(operator.contains) + __add__ = _ProxyLookup(operator.add) + __sub__ = _ProxyLookup(operator.sub) + __mul__ = _ProxyLookup(operator.mul) + __matmul__ = _ProxyLookup(operator.matmul) + __truediv__ = _ProxyLookup(operator.truediv) + __floordiv__ = _ProxyLookup(operator.floordiv) + __mod__ = _ProxyLookup(operator.mod) + __divmod__ = _ProxyLookup(divmod) + __pow__ = _ProxyLookup(pow) + __lshift__ = _ProxyLookup(operator.lshift) + __rshift__ = _ProxyLookup(operator.rshift) + __and__ = _ProxyLookup(operator.and_) + __xor__ = _ProxyLookup(operator.xor) + __or__ = _ProxyLookup(operator.or_) + __radd__ = _ProxyLookup(_l_to_r_op(operator.add)) + __rsub__ = _ProxyLookup(_l_to_r_op(operator.sub)) + __rmul__ = _ProxyLookup(_l_to_r_op(operator.mul)) + __rmatmul__ = _ProxyLookup(_l_to_r_op(operator.matmul)) + __rtruediv__ = _ProxyLookup(_l_to_r_op(operator.truediv)) + __rfloordiv__ = _ProxyLookup(_l_to_r_op(operator.floordiv)) + __rmod__ = _ProxyLookup(_l_to_r_op(operator.mod)) + __rdivmod__ = _ProxyLookup(_l_to_r_op(divmod)) + __rpow__ = _ProxyLookup(_l_to_r_op(pow)) + __rlshift__ = _ProxyLookup(_l_to_r_op(operator.lshift)) + __rrshift__ = _ProxyLookup(_l_to_r_op(operator.rshift)) + __rand__ = _ProxyLookup(_l_to_r_op(operator.and_)) + __rxor__ = _ProxyLookup(_l_to_r_op(operator.xor)) + __ror__ = _ProxyLookup(_l_to_r_op(operator.or_)) + __iadd__ = _ProxyIOp(operator.iadd) + __isub__ = _ProxyIOp(operator.isub) + __imul__ = _ProxyIOp(operator.imul) + __imatmul__ = _ProxyIOp(operator.imatmul) + __itruediv__ = _ProxyIOp(operator.itruediv) + __ifloordiv__ = _ProxyIOp(operator.ifloordiv) + __imod__ = _ProxyIOp(operator.imod) + __ipow__ = _ProxyIOp(operator.ipow) + __ilshift__ = _ProxyIOp(operator.ilshift) + __irshift__ = _ProxyIOp(operator.irshift) + __iand__ = _ProxyIOp(operator.iand) + __ixor__ = _ProxyIOp(operator.ixor) + __ior__ = _ProxyIOp(operator.ior) + __neg__ = _ProxyLookup(operator.neg) + __pos__ = _ProxyLookup(operator.pos) + __abs__ = _ProxyLookup(abs) + __invert__ = _ProxyLookup(operator.invert) + __complex__ = _ProxyLookup(complex) + __int__ = _ProxyLookup(int) + __float__ = _ProxyLookup(float) + __index__ = _ProxyLookup(operator.index) + __round__ = _ProxyLookup(round) + __trunc__ = _ProxyLookup(math.trunc) + __floor__ = _ProxyLookup(math.floor) + __ceil__ = _ProxyLookup(math.ceil) + __enter__ = _ProxyLookup() + __exit__ = _ProxyLookup() + __await__ = _ProxyLookup() + __aiter__ = _ProxyLookup() + __anext__ = _ProxyLookup() + __aenter__ = _ProxyLookup() + __aexit__ = _ProxyLookup() + __copy__ = _ProxyLookup(copy.copy) + __deepcopy__ = _ProxyLookup(copy.deepcopy) + # __getnewargs_ex__ (pickle through proxy not supported) + # __getnewargs__ (pickle) + # __getstate__ (pickle) + # __setstate__ (pickle) + # __reduce__ (pickle) + # __reduce_ex__ (pickle) diff --git a/.vtodo/Lib/site-packages/werkzeug/middleware/__init__.py b/.vtodo/Lib/site-packages/werkzeug/middleware/__init__.py new file mode 100644 index 0000000..6ddcf7f --- /dev/null +++ b/.vtodo/Lib/site-packages/werkzeug/middleware/__init__.py @@ -0,0 +1,22 @@ +""" +Middleware +========== + +A WSGI middleware is a WSGI application that wraps another application +in order to observe or change its behavior. Werkzeug provides some +middleware for common use cases. + +.. toctree:: + :maxdepth: 1 + + proxy_fix + shared_data + dispatcher + http_proxy + lint + profiler + +The :doc:`interactive debugger ` is also a middleware that can +be applied manually, although it is typically used automatically with +the :doc:`development server `. +""" diff --git a/.vtodo/Lib/site-packages/werkzeug/middleware/__pycache__/__init__.cpython-310.pyc b/.vtodo/Lib/site-packages/werkzeug/middleware/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a7274f591d204c35cc73b574de6aaf10532b3cb GIT binary patch literal 692 zcmYjPO>fjN5Y6_&%2NNs9Jo}XSs`vwS%EkpB;Zm|C5Yrs;@Q}05?l6UKln$S_&>e& z%#9N#Cf=&-NU=QcdB*cz?9b0tA@S?^tM<>LC?4kIGz2T&$d{I~nBrSu7T3ka8U7YR zeR9V1w8Kd1^;0PI7H+QJe}LhM1(yJiAcoMp7TNg`*doI&Vi0!pY$?JCt=tGc8UfjZ zZxY2V2>`9du_JJtpdpJ}=VJ*s6z_h})Io@T>kK7Gejs%+!e%b@!yxxgiJ-+qsZ^>g zrG3khsHzl5R0Hk}1-2EuR2h+$`-9%Nea?~Dg`fPgiTm6@~(%OxVwJyWqet9)`GxfBd-(i|BT^9cUNeSP2 literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/werkzeug/middleware/__pycache__/dispatcher.cpython-310.pyc b/.vtodo/Lib/site-packages/werkzeug/middleware/__pycache__/dispatcher.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..364f6529127743135231d4cc7da326da23f70e37 GIT binary patch literal 2776 zcmZ`*TW=f17vGyN**a;3Dp0vdqY9}Qr7ofmNai9!TAK9VhNuo8ETh$UXPj(V?`~(t zNe#{uedY@wr1HoU-)CQW;ww~tF26IIja?{f?d)96{rt`u=j-dCgXh;bZ^oA~`;8W> zkB`O2c=Zkj;S3$&3NLksp7b92>NTwVG?+Gq4VO*=%$vieoi{LV4O^~r#}Q4@dg6%I z_ujBAyn)kg^REKO3Ac(OO=516JY%;KU2qd0Nfmzh!Y2$5juOqLi4dtg;YzYtNp7TO zoarPxOeOnraA%kC6`|&c8zySWnwdPD&Qg;UAkGbR{fh+(@>yo2V3u<-kxFKUeg5Dc zgLy}~7lvCb&ZkqDEFbeJNrF`;DlxDDrVJ`m_W7Y?EBqIqT!H&mH2=* zV@f`nNo@@iN$pAj%QUH)T0Za`OVvYgS%I87ovSW-(!X)1qLS93B~DSR2U&Fp&Y)@P$H8 zsg`W#;9$Rsu41V?mg#0lW)aLP@8BGSKAzK#$)5v$l~v8gTm#C$YMNJd)?-L?cn-Nb zzCf_er{pnZj|o|%`5de#3DOBH$bth|XC+oi-wY`UWT`f&;rPs|O3)G*B2h9n>70${ z|8xt$==B(%nLJLpMqXZ+j4{*GSd>~-C8ePFmU52P+~P+{XDwTP9ntha7w#|esWidv6}dG%>y@iAWg1qSI1 zU9>?@xI=%<6BmUq0@0AcLwcLCC4z}}#koQ)&zqp_iT0BQn%aYlcm!{{w8bDzcz*;Y^V|(0z8F7rL^qP;!zyMx{TG|Elt9)u@k3%wFlkR^=m) zWwR1iHkC9pmBqAKKQ#C7ZsXOLF)ZAL6FCd7XeJJ3K)}HH#og+SyNbCir8Nrl^Pr1M!Z&bb+xT;$j1l4~>S9n)fTFBy+?V&wG#cLVk5 z(TP;wd@E;%H1;V=ASN4>r97tTW$vsdW5 z@UJ+hjYsb;{HGpO%Fo^i^ruZwH-B^%!H>>Qe$A^p#PQUz5`4&QSRD=FVcuG_7XB;H zDjFYv4__BwP%OOf=?_J7Z@9LW+Y1VMxs_Xs6=xP~@mA|z-!yqu#UB^ghn ziZ;slRN91BLxol}f4h5kh8qzMr9P6PccKpy3p`t&7ME7c bi|i@eA5@NiL|N99xWH+%CIU&nm;Ap09&$1- literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/werkzeug/middleware/__pycache__/http_proxy.cpython-310.pyc b/.vtodo/Lib/site-packages/werkzeug/middleware/__pycache__/http_proxy.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bc3088f05b3b8478dccb8b651e973c1b16c53a32 GIT binary patch literal 6867 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 literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/werkzeug/middleware/__pycache__/profiler.cpython-310.pyc b/.vtodo/Lib/site-packages/werkzeug/middleware/__pycache__/profiler.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b0f909b45ce641d6b5415a9a83c07279bad14cab GIT binary patch literal 4983 zcmb_g&2!tv6$d~N1R+vimXkQ?Ot(#D5>74JZKl(PaWZxt#n!Zz)kqmnSam=UyObb< z1hZI5rc9me87qghe?Zb1>6lahA6$FtxtAtQlm6awt>1Ux?&`-N*>u0g);8}=EUAO6) z#(|4BJ1%#QyL-D2!ubmd{n-HPCDtU7>awWWM^CjSg7N zhWyq^;&F&|A`x?N-3y{PXvdZAu1}p+NTDA$7L@K{MY_MBQ4f>c0+l5Iy==L?gKZ!~cW0)%#sARv+eK#K!CQCk%7?#DqdKbwPg zvM0u{z;Q-L;Bq@o!iPA|`a?urFAqfWKu0EBwlCzv12NdHpPkS3vF?&Z-99<+#cX~m zA27$>yI{gw7^vl2L5w{iQ}{0233}UtLzL_FU;g@(ht9RO|yh0q*rx&#?+_!P3Z-I==k>ML73)CDHK&_DtS{M~U?U4go z{amAO`PGTVn}SqU*s2X5y26S=DXIl(Iav}E)CbE4HsnVpKbBdMyEGejyxszhdq zP!%N06Y|ZUZa20*PJy~DfxHoHrOBY*P6D;15}qs%MUbkk`X0S({UK^^rAUFx{UCf8 zY>RYj5)#+WLgLz3JX!A#p@kpyBIWz{uwP1oIAzDof1Y{p+WNRYJ}Z9elNDKMoZ+Q| zW!Wqe6*3-7p({XXqcbdkOfFKDW>R1JJ__+|<1>pdJ+q$K9sRBFwEUUDFF!M$ndmEY zY%2DiRt^iwcw!vd$~-ntjKku{KDI`M6BE_Gb6hyGPRv6m9~%{4F%C;dg^@l zZXdeJ+nvE~;5~9rtV0)4RM3JH=&S1ezEra#TffbIZXK3K<=r{F&Hn}8F=~NRyUOQ3 zx3I!IUc|S_0{D0sSsZy^Sbww5##V(Gtxzn zBcK}yx1JQRrLhYf%LR0!4tD@#(c`rO#aN&QX;#`10SCk~S3C-Vh)#l7u1lq0 z(+j9ED2%m|zeNL{g`)5j5OidmSg@&aWE~@z$u|n&%&jMue05|T7f&yS= z3-Gcj<7LKyLjpEfW_mgBEbY&=+GR;t)nYVD(2&rO*}F;9lP_T)bE$)BHTg2VI(d1X z6{*%h0+s?LLMSpTN#!+6XnK6m?dPSIBw$EN@=foo0Fz6^r>N_rtgtOq6NkrXtt2mF zv^JCbPrFkioeqesG|rW@IY6G-IufqakUPoidQ7FwGFe&2jz&SPcp*SEZw(Nn-aHQe z40b7j*)GgmOSV&XO%K0{=~+Y_%Q0WcQ}Va-4o0k5^wPbgZ^!z9G@Wy=El`rDQq1== z&-e3xGtjR1{-=W=9*>lKpC=)@3+XVWyug!zl5dcv*6MxqDw(;f06sY;D}LNV4U`pN z#H`qfaT;ar{XW&SK`dXTMaA||iB#S~`|K<`3lB+A{8QX%1H?si*w-yH|Jh$#mi3)m zu>R}X*0)PV>p!kzePflZuPxX6w>6Jh-_z|{v)^exwM%jZT(VM<*2xL-9lXg8i4fK^ zM-BT(R9c&+lUM0ENZiSvG8&TE%&IODSENj&yg^H=o1d(&__yz_-2VOQ+MVXC4^|@G z5xl;iZbv$W=xRlufm)c0w9VWbx!LL1Gg1a(8*bG#=~po=YaVnSDmjG;S`~A#@IOd6 Bn_mC` literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/werkzeug/middleware/__pycache__/proxy_fix.cpython-310.pyc b/.vtodo/Lib/site-packages/werkzeug/middleware/__pycache__/proxy_fix.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..141421ed0816e1590f2ce69b5154fbfff00c811a GIT binary patch literal 6195 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 literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/werkzeug/middleware/__pycache__/shared_data.cpython-310.pyc b/.vtodo/Lib/site-packages/werkzeug/middleware/__pycache__/shared_data.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..834d159ecdd96a8151aa0e466a14ce423b16c0f7 GIT binary patch literal 9150 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 X0ns0y^rR*5Irwvoi+VnLo-+RjH1MCc literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/werkzeug/middleware/dispatcher.py b/.vtodo/Lib/site-packages/werkzeug/middleware/dispatcher.py new file mode 100644 index 0000000..ace1c75 --- /dev/null +++ b/.vtodo/Lib/site-packages/werkzeug/middleware/dispatcher.py @@ -0,0 +1,78 @@ +""" +Application Dispatcher +====================== + +This middleware creates a single WSGI application that dispatches to +multiple other WSGI applications mounted at different URL paths. + +A common example is writing a Single Page Application, where you have a +backend API and a frontend written in JavaScript that does the routing +in the browser rather than requesting different pages from the server. +The frontend is a single HTML and JS file that should be served for any +path besides "/api". + +This example dispatches to an API app under "/api", an admin app +under "/admin", and an app that serves frontend files for all other +requests:: + + app = DispatcherMiddleware(serve_frontend, { + '/api': api_app, + '/admin': admin_app, + }) + +In production, you might instead handle this at the HTTP server level, +serving files or proxying to application servers based on location. The +API and admin apps would each be deployed with a separate WSGI server, +and the static files would be served directly by the HTTP server. + +.. autoclass:: DispatcherMiddleware + +:copyright: 2007 Pallets +:license: BSD-3-Clause +""" +import typing as t + +if t.TYPE_CHECKING: + from _typeshed.wsgi import StartResponse + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + + +class DispatcherMiddleware: + """Combine multiple applications as a single WSGI application. + Requests are dispatched to an application based on the path it is + mounted under. + + :param app: The WSGI application to dispatch to if the request + doesn't match a mounted path. + :param mounts: Maps path prefixes to applications for dispatching. + """ + + def __init__( + self, + app: "WSGIApplication", + mounts: t.Optional[t.Dict[str, "WSGIApplication"]] = None, + ) -> None: + self.app = app + self.mounts = mounts or {} + + def __call__( + self, environ: "WSGIEnvironment", start_response: "StartResponse" + ) -> t.Iterable[bytes]: + script = environ.get("PATH_INFO", "") + path_info = "" + + while "/" in script: + if script in self.mounts: + app = self.mounts[script] + break + + script, last_item = script.rsplit("/", 1) + path_info = f"/{last_item}{path_info}" + else: + app = self.mounts.get(script, self.app) + + original_script_name = environ.get("SCRIPT_NAME", "") + environ["SCRIPT_NAME"] = original_script_name + script + environ["PATH_INFO"] = path_info + return app(environ, start_response) diff --git a/.vtodo/Lib/site-packages/werkzeug/middleware/http_proxy.py b/.vtodo/Lib/site-packages/werkzeug/middleware/http_proxy.py new file mode 100644 index 0000000..1cde458 --- /dev/null +++ b/.vtodo/Lib/site-packages/werkzeug/middleware/http_proxy.py @@ -0,0 +1,230 @@ +""" +Basic HTTP Proxy +================ + +.. autoclass:: ProxyMiddleware + +:copyright: 2007 Pallets +:license: BSD-3-Clause +""" +import typing as t +from http import client + +from ..datastructures import EnvironHeaders +from ..http import is_hop_by_hop_header +from ..urls import url_parse +from ..urls import url_quote +from ..wsgi import get_input_stream + +if t.TYPE_CHECKING: + from _typeshed.wsgi import StartResponse + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + + +class ProxyMiddleware: + """Proxy requests under a path to an external server, routing other + requests to the app. + + This middleware can only proxy HTTP requests, as HTTP is the only + protocol handled by the WSGI server. Other protocols, such as + WebSocket requests, cannot be proxied at this layer. This should + only be used for development, in production a real proxy server + should be used. + + The middleware takes a dict mapping a path prefix to a dict + describing the host to be proxied to:: + + app = ProxyMiddleware(app, { + "/static/": { + "target": "http://127.0.0.1:5001/", + } + }) + + Each host has the following options: + + ``target``: + The target URL to dispatch to. This is required. + ``remove_prefix``: + Whether to remove the prefix from the URL before dispatching it + to the target. The default is ``False``. + ``host``: + ``""`` (default): + The host header is automatically rewritten to the URL of the + target. + ``None``: + The host header is unmodified from the client request. + Any other value: + The host header is overwritten with the value. + ``headers``: + A dictionary of headers to be sent with the request to the + target. The default is ``{}``. + ``ssl_context``: + A :class:`ssl.SSLContext` defining how to verify requests if the + target is HTTPS. The default is ``None``. + + In the example above, everything under ``"/static/"`` is proxied to + the server on port 5001. The host header is rewritten to the target, + and the ``"/static/"`` prefix is removed from the URLs. + + :param app: The WSGI application to wrap. + :param targets: Proxy target configurations. See description above. + :param chunk_size: Size of chunks to read from input stream and + write to target. + :param timeout: Seconds before an operation to a target fails. + + .. versionadded:: 0.14 + """ + + def __init__( + self, + app: "WSGIApplication", + targets: t.Mapping[str, t.Dict[str, t.Any]], + chunk_size: int = 2 << 13, + timeout: int = 10, + ) -> None: + def _set_defaults(opts: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]: + opts.setdefault("remove_prefix", False) + opts.setdefault("host", "") + opts.setdefault("headers", {}) + opts.setdefault("ssl_context", None) + return opts + + self.app = app + self.targets = { + f"/{k.strip('/')}/": _set_defaults(v) for k, v in targets.items() + } + self.chunk_size = chunk_size + self.timeout = timeout + + def proxy_to( + self, opts: t.Dict[str, t.Any], path: str, prefix: str + ) -> "WSGIApplication": + target = url_parse(opts["target"]) + host = t.cast(str, target.ascii_host) + + def application( + environ: "WSGIEnvironment", start_response: "StartResponse" + ) -> t.Iterable[bytes]: + headers = list(EnvironHeaders(environ).items()) + headers[:] = [ + (k, v) + for k, v in headers + if not is_hop_by_hop_header(k) + and k.lower() not in ("content-length", "host") + ] + headers.append(("Connection", "close")) + + if opts["host"] == "": + headers.append(("Host", host)) + elif opts["host"] is None: + headers.append(("Host", environ["HTTP_HOST"])) + else: + headers.append(("Host", opts["host"])) + + headers.extend(opts["headers"].items()) + remote_path = path + + if opts["remove_prefix"]: + remote_path = remote_path[len(prefix) :].lstrip("/") + remote_path = f"{target.path.rstrip('/')}/{remote_path}" + + content_length = environ.get("CONTENT_LENGTH") + chunked = False + + if content_length not in ("", None): + headers.append(("Content-Length", content_length)) # type: ignore + elif content_length is not None: + headers.append(("Transfer-Encoding", "chunked")) + chunked = True + + try: + if target.scheme == "http": + con = client.HTTPConnection( + host, target.port or 80, timeout=self.timeout + ) + elif target.scheme == "https": + con = client.HTTPSConnection( + host, + target.port or 443, + timeout=self.timeout, + context=opts["ssl_context"], + ) + else: + raise RuntimeError( + "Target scheme must be 'http' or 'https', got" + f" {target.scheme!r}." + ) + + con.connect() + remote_url = url_quote(remote_path) + querystring = environ["QUERY_STRING"] + + if querystring: + remote_url = f"{remote_url}?{querystring}" + + con.putrequest(environ["REQUEST_METHOD"], remote_url, skip_host=True) + + for k, v in headers: + if k.lower() == "connection": + v = "close" + + con.putheader(k, v) + + con.endheaders() + stream = get_input_stream(environ) + + while True: + data = stream.read(self.chunk_size) + + if not data: + break + + if chunked: + con.send(b"%x\r\n%s\r\n" % (len(data), data)) + else: + con.send(data) + + resp = con.getresponse() + except OSError: + from ..exceptions import BadGateway + + return BadGateway()(environ, start_response) + + start_response( + f"{resp.status} {resp.reason}", + [ + (k.title(), v) + for k, v in resp.getheaders() + if not is_hop_by_hop_header(k) + ], + ) + + def read() -> t.Iterator[bytes]: + while True: + try: + data = resp.read(self.chunk_size) + except OSError: + break + + if not data: + break + + yield data + + return read() + + return application + + def __call__( + self, environ: "WSGIEnvironment", start_response: "StartResponse" + ) -> t.Iterable[bytes]: + path = environ["PATH_INFO"] + app = self.app + + for prefix, opts in self.targets.items(): + if path.startswith(prefix): + app = self.proxy_to(opts, path, prefix) + break + + return app(environ, start_response) diff --git a/.vtodo/Lib/site-packages/werkzeug/middleware/lint.py b/.vtodo/Lib/site-packages/werkzeug/middleware/lint.py new file mode 100644 index 0000000..6b54630 --- /dev/null +++ b/.vtodo/Lib/site-packages/werkzeug/middleware/lint.py @@ -0,0 +1,420 @@ +""" +WSGI Protocol Linter +==================== + +This module provides a middleware that performs sanity checks on the +behavior of the WSGI server and application. It checks that the +:pep:`3333` WSGI spec is properly implemented. It also warns on some +common HTTP errors such as non-empty responses for 304 status codes. + +.. autoclass:: LintMiddleware + +:copyright: 2007 Pallets +:license: BSD-3-Clause +""" +import typing as t +from types import TracebackType +from urllib.parse import urlparse +from warnings import warn + +from ..datastructures import Headers +from ..http import is_entity_header +from ..wsgi import FileWrapper + +if t.TYPE_CHECKING: + from _typeshed.wsgi import StartResponse + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + + +class WSGIWarning(Warning): + """Warning class for WSGI warnings.""" + + +class HTTPWarning(Warning): + """Warning class for HTTP warnings.""" + + +def check_type(context: str, obj: object, need: t.Type = str) -> None: + if type(obj) is not need: + warn( + f"{context!r} requires {need.__name__!r}, got {type(obj).__name__!r}.", + WSGIWarning, + stacklevel=3, + ) + + +class InputStream: + def __init__(self, stream: t.IO[bytes]) -> None: + self._stream = stream + + def read(self, *args: t.Any) -> bytes: + if len(args) == 0: + warn( + "WSGI does not guarantee an EOF marker on the input stream, thus making" + " calls to 'wsgi.input.read()' unsafe. Conforming servers may never" + " return from this call.", + WSGIWarning, + stacklevel=2, + ) + elif len(args) != 1: + warn( + "Too many parameters passed to 'wsgi.input.read()'.", + WSGIWarning, + stacklevel=2, + ) + return self._stream.read(*args) + + def readline(self, *args: t.Any) -> bytes: + if len(args) == 0: + warn( + "Calls to 'wsgi.input.readline()' without arguments are unsafe. Use" + " 'wsgi.input.read()' instead.", + WSGIWarning, + stacklevel=2, + ) + elif len(args) == 1: + warn( + "'wsgi.input.readline()' was called with a size hint. WSGI does not" + " support this, although it's available on all major servers.", + WSGIWarning, + stacklevel=2, + ) + else: + raise TypeError("Too many arguments passed to 'wsgi.input.readline()'.") + return self._stream.readline(*args) + + def __iter__(self) -> t.Iterator[bytes]: + try: + return iter(self._stream) + except TypeError: + warn("'wsgi.input' is not iterable.", WSGIWarning, stacklevel=2) + return iter(()) + + def close(self) -> None: + warn("The application closed the input stream!", WSGIWarning, stacklevel=2) + self._stream.close() + + +class ErrorStream: + def __init__(self, stream: t.IO[str]) -> None: + self._stream = stream + + def write(self, s: str) -> None: + check_type("wsgi.error.write()", s, str) + self._stream.write(s) + + def flush(self) -> None: + self._stream.flush() + + def writelines(self, seq: t.Iterable[str]) -> None: + for line in seq: + self.write(line) + + def close(self) -> None: + warn("The application closed the error stream!", WSGIWarning, stacklevel=2) + self._stream.close() + + +class GuardedWrite: + def __init__(self, write: t.Callable[[bytes], object], chunks: t.List[int]) -> None: + self._write = write + self._chunks = chunks + + def __call__(self, s: bytes) -> None: + check_type("write()", s, bytes) + self._write(s) + self._chunks.append(len(s)) + + +class GuardedIterator: + def __init__( + self, + iterator: t.Iterable[bytes], + headers_set: t.Tuple[int, Headers], + chunks: t.List[int], + ) -> None: + self._iterator = iterator + self._next = iter(iterator).__next__ + self.closed = False + self.headers_set = headers_set + self.chunks = chunks + + def __iter__(self) -> "GuardedIterator": + return self + + def __next__(self) -> bytes: + if self.closed: + warn("Iterated over closed 'app_iter'.", WSGIWarning, stacklevel=2) + + rv = self._next() + + if not self.headers_set: + warn( + "The application returned before it started the response.", + WSGIWarning, + stacklevel=2, + ) + + check_type("application iterator items", rv, bytes) + self.chunks.append(len(rv)) + return rv + + def close(self) -> None: + self.closed = True + + if hasattr(self._iterator, "close"): + self._iterator.close() # type: ignore + + if self.headers_set: + status_code, headers = self.headers_set + bytes_sent = sum(self.chunks) + content_length = headers.get("content-length", type=int) + + if status_code == 304: + for key, _value in headers: + key = key.lower() + if key not in ("expires", "content-location") and is_entity_header( + key + ): + warn( + f"Entity header {key!r} found in 304 response.", HTTPWarning + ) + if bytes_sent: + warn("304 responses must not have a body.", HTTPWarning) + elif 100 <= status_code < 200 or status_code == 204: + if content_length != 0: + warn( + f"{status_code} responses must have an empty content length.", + HTTPWarning, + ) + if bytes_sent: + warn(f"{status_code} responses must not have a body.", HTTPWarning) + elif content_length is not None and content_length != bytes_sent: + warn( + "Content-Length and the number of bytes sent to the" + " client do not match.", + WSGIWarning, + ) + + def __del__(self) -> None: + if not self.closed: + try: + warn( + "Iterator was garbage collected before it was closed.", WSGIWarning + ) + except Exception: + pass + + +class LintMiddleware: + """Warns about common errors in the WSGI and HTTP behavior of the + server and wrapped application. Some of the issues it checks are: + + - invalid status codes + - non-bytes sent to the WSGI server + - strings returned from the WSGI application + - non-empty conditional responses + - unquoted etags + - relative URLs in the Location header + - unsafe calls to wsgi.input + - unclosed iterators + + Error information is emitted using the :mod:`warnings` module. + + :param app: The WSGI application to wrap. + + .. code-block:: python + + from werkzeug.middleware.lint import LintMiddleware + app = LintMiddleware(app) + """ + + def __init__(self, app: "WSGIApplication") -> None: + self.app = app + + def check_environ(self, environ: "WSGIEnvironment") -> None: + if type(environ) is not dict: + warn( + "WSGI environment is not a standard Python dict.", + WSGIWarning, + stacklevel=4, + ) + for key in ( + "REQUEST_METHOD", + "SERVER_NAME", + "SERVER_PORT", + "wsgi.version", + "wsgi.input", + "wsgi.errors", + "wsgi.multithread", + "wsgi.multiprocess", + "wsgi.run_once", + ): + if key not in environ: + warn( + f"Required environment key {key!r} not found", + WSGIWarning, + stacklevel=3, + ) + if environ["wsgi.version"] != (1, 0): + warn("Environ is not a WSGI 1.0 environ.", WSGIWarning, stacklevel=3) + + script_name = environ.get("SCRIPT_NAME", "") + path_info = environ.get("PATH_INFO", "") + + if script_name and script_name[0] != "/": + warn( + f"'SCRIPT_NAME' does not start with a slash: {script_name!r}", + WSGIWarning, + stacklevel=3, + ) + + if path_info and path_info[0] != "/": + warn( + f"'PATH_INFO' does not start with a slash: {path_info!r}", + WSGIWarning, + stacklevel=3, + ) + + def check_start_response( + self, + status: str, + headers: t.List[t.Tuple[str, str]], + exc_info: t.Optional[ + t.Tuple[t.Type[BaseException], BaseException, TracebackType] + ], + ) -> t.Tuple[int, Headers]: + check_type("status", status, str) + status_code_str = status.split(None, 1)[0] + + if len(status_code_str) != 3 or not status_code_str.isdecimal(): + warn("Status code must be three digits.", WSGIWarning, stacklevel=3) + + if len(status) < 4 or status[3] != " ": + warn( + f"Invalid value for status {status!r}. Valid status strings are three" + " digits, a space and a status explanation.", + WSGIWarning, + stacklevel=3, + ) + + status_code = int(status_code_str) + + if status_code < 100: + warn("Status code < 100 detected.", WSGIWarning, stacklevel=3) + + if type(headers) is not list: + warn("Header list is not a list.", WSGIWarning, stacklevel=3) + + for item in headers: + if type(item) is not tuple or len(item) != 2: + warn("Header items must be 2-item tuples.", WSGIWarning, stacklevel=3) + name, value = item + if type(name) is not str or type(value) is not str: + warn( + "Header keys and values must be strings.", WSGIWarning, stacklevel=3 + ) + if name.lower() == "status": + warn( + "The status header is not supported due to" + " conflicts with the CGI spec.", + WSGIWarning, + stacklevel=3, + ) + + if exc_info is not None and not isinstance(exc_info, tuple): + warn("Invalid value for exc_info.", WSGIWarning, stacklevel=3) + + headers = Headers(headers) + self.check_headers(headers) + + return status_code, headers + + def check_headers(self, headers: Headers) -> None: + etag = headers.get("etag") + + if etag is not None: + if etag.startswith(("W/", "w/")): + if etag.startswith("w/"): + warn( + "Weak etag indicator should be upper case.", + HTTPWarning, + stacklevel=4, + ) + + etag = etag[2:] + + if not (etag[:1] == etag[-1:] == '"'): + warn("Unquoted etag emitted.", HTTPWarning, stacklevel=4) + + location = headers.get("location") + + if location is not None: + if not urlparse(location).netloc: + warn( + "Absolute URLs required for location header.", + HTTPWarning, + stacklevel=4, + ) + + def check_iterator(self, app_iter: t.Iterable[bytes]) -> None: + if isinstance(app_iter, bytes): + warn( + "The application returned a bytestring. The response will send one" + " character at a time to the client, which will kill performance." + " Return a list or iterable instead.", + WSGIWarning, + stacklevel=3, + ) + + def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Iterable[bytes]: + if len(args) != 2: + warn("A WSGI app takes two arguments.", WSGIWarning, stacklevel=2) + + if kwargs: + warn( + "A WSGI app does not take keyword arguments.", WSGIWarning, stacklevel=2 + ) + + environ: "WSGIEnvironment" = args[0] + start_response: "StartResponse" = args[1] + + self.check_environ(environ) + environ["wsgi.input"] = InputStream(environ["wsgi.input"]) + environ["wsgi.errors"] = ErrorStream(environ["wsgi.errors"]) + + # Hook our own file wrapper in so that applications will always + # iterate to the end and we can check the content length. + environ["wsgi.file_wrapper"] = FileWrapper + + headers_set: t.List[t.Any] = [] + chunks: t.List[int] = [] + + def checking_start_response( + *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[bytes], None]: + if len(args) not in {2, 3}: + warn( + f"Invalid number of arguments: {len(args)}, expected 2 or 3.", + WSGIWarning, + stacklevel=2, + ) + + if kwargs: + warn("'start_response' does not take keyword arguments.", WSGIWarning) + + status: str = args[0] + headers: t.List[t.Tuple[str, str]] = args[1] + exc_info: t.Optional[ + t.Tuple[t.Type[BaseException], BaseException, TracebackType] + ] = (args[2] if len(args) == 3 else None) + + headers_set[:] = self.check_start_response(status, headers, exc_info) + return GuardedWrite(start_response(status, headers, exc_info), chunks) + + app_iter = self.app(environ, t.cast("StartResponse", checking_start_response)) + self.check_iterator(app_iter) + return GuardedIterator( + app_iter, t.cast(t.Tuple[int, Headers], headers_set), chunks + ) diff --git a/.vtodo/Lib/site-packages/werkzeug/middleware/profiler.py b/.vtodo/Lib/site-packages/werkzeug/middleware/profiler.py new file mode 100644 index 0000000..200dae0 --- /dev/null +++ b/.vtodo/Lib/site-packages/werkzeug/middleware/profiler.py @@ -0,0 +1,139 @@ +""" +Application Profiler +==================== + +This module provides a middleware that profiles each request with the +:mod:`cProfile` module. This can help identify bottlenecks in your code +that may be slowing down your application. + +.. autoclass:: ProfilerMiddleware + +:copyright: 2007 Pallets +:license: BSD-3-Clause +""" +import os.path +import sys +import time +import typing as t +from pstats import Stats + +try: + from cProfile import Profile +except ImportError: + from profile import Profile # type: ignore + +if t.TYPE_CHECKING: + from _typeshed.wsgi import StartResponse + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + + +class ProfilerMiddleware: + """Wrap a WSGI application and profile the execution of each + request. Responses are buffered so that timings are more exact. + + If ``stream`` is given, :class:`pstats.Stats` are written to it + after each request. If ``profile_dir`` is given, :mod:`cProfile` + data files are saved to that directory, one file per request. + + The filename can be customized by passing ``filename_format``. If + it is a string, it will be formatted using :meth:`str.format` with + the following fields available: + + - ``{method}`` - The request method; GET, POST, etc. + - ``{path}`` - The request path or 'root' should one not exist. + - ``{elapsed}`` - The elapsed time of the request. + - ``{time}`` - The time of the request. + + If it is a callable, it will be called with the WSGI ``environ`` + dict and should return a filename. + + :param app: The WSGI application to wrap. + :param stream: Write stats to this stream. Disable with ``None``. + :param sort_by: A tuple of columns to sort stats by. See + :meth:`pstats.Stats.sort_stats`. + :param restrictions: A tuple of restrictions to filter stats by. See + :meth:`pstats.Stats.print_stats`. + :param profile_dir: Save profile data files to this directory. + :param filename_format: Format string for profile data file names, + or a callable returning a name. See explanation above. + + .. code-block:: python + + from werkzeug.middleware.profiler import ProfilerMiddleware + app = ProfilerMiddleware(app) + + .. versionchanged:: 0.15 + Stats are written even if ``profile_dir`` is given, and can be + disable by passing ``stream=None``. + + .. versionadded:: 0.15 + Added ``filename_format``. + + .. versionadded:: 0.9 + Added ``restrictions`` and ``profile_dir``. + """ + + def __init__( + self, + app: "WSGIApplication", + stream: t.IO[str] = sys.stdout, + sort_by: t.Iterable[str] = ("time", "calls"), + restrictions: t.Iterable[t.Union[str, int, float]] = (), + profile_dir: t.Optional[str] = None, + filename_format: str = "{method}.{path}.{elapsed:.0f}ms.{time:.0f}.prof", + ) -> None: + self._app = app + self._stream = stream + self._sort_by = sort_by + self._restrictions = restrictions + self._profile_dir = profile_dir + self._filename_format = filename_format + + def __call__( + self, environ: "WSGIEnvironment", start_response: "StartResponse" + ) -> t.Iterable[bytes]: + response_body: t.List[bytes] = [] + + def catching_start_response(status, headers, exc_info=None): # type: ignore + start_response(status, headers, exc_info) + return response_body.append + + def runapp() -> None: + app_iter = self._app( + environ, t.cast("StartResponse", catching_start_response) + ) + response_body.extend(app_iter) + + if hasattr(app_iter, "close"): + app_iter.close() # type: ignore + + profile = Profile() + start = time.time() + profile.runcall(runapp) + body = b"".join(response_body) + elapsed = time.time() - start + + if self._profile_dir is not None: + if callable(self._filename_format): + filename = self._filename_format(environ) + else: + filename = self._filename_format.format( + method=environ["REQUEST_METHOD"], + path=environ["PATH_INFO"].strip("/").replace("/", ".") or "root", + elapsed=elapsed * 1000.0, + time=time.time(), + ) + filename = os.path.join(self._profile_dir, filename) + profile.dump_stats(filename) + + if self._stream is not None: + stats = Stats(profile, stream=self._stream) + stats.sort_stats(*self._sort_by) + print("-" * 80, file=self._stream) + path_info = environ.get("PATH_INFO", "") + print(f"PATH: {path_info!r}", file=self._stream) + stats.print_stats(*self._restrictions) + print(f"{'-' * 80}\n", file=self._stream) + + return [body] diff --git a/.vtodo/Lib/site-packages/werkzeug/middleware/proxy_fix.py b/.vtodo/Lib/site-packages/werkzeug/middleware/proxy_fix.py new file mode 100644 index 0000000..4cef7cc --- /dev/null +++ b/.vtodo/Lib/site-packages/werkzeug/middleware/proxy_fix.py @@ -0,0 +1,187 @@ +""" +X-Forwarded-For Proxy Fix +========================= + +This module provides a middleware that adjusts the WSGI environ based on +``X-Forwarded-`` headers that proxies in front of an application may +set. + +When an application is running behind a proxy server, WSGI may see the +request as coming from that server rather than the real client. Proxies +set various headers to track where the request actually came from. + +This middleware should only be used if the application is actually +behind such a proxy, and should be configured with the number of proxies +that are chained in front of it. Not all proxies set all the headers. +Since incoming headers can be faked, you must set how many proxies are +setting each header so the middleware knows what to trust. + +.. autoclass:: ProxyFix + +:copyright: 2007 Pallets +:license: BSD-3-Clause +""" +import typing as t + +from ..http import parse_list_header + +if t.TYPE_CHECKING: + from _typeshed.wsgi import StartResponse + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + + +class ProxyFix: + """Adjust the WSGI environ based on ``X-Forwarded-`` that proxies in + front of the application may set. + + - ``X-Forwarded-For`` sets ``REMOTE_ADDR``. + - ``X-Forwarded-Proto`` sets ``wsgi.url_scheme``. + - ``X-Forwarded-Host`` sets ``HTTP_HOST``, ``SERVER_NAME``, and + ``SERVER_PORT``. + - ``X-Forwarded-Port`` sets ``HTTP_HOST`` and ``SERVER_PORT``. + - ``X-Forwarded-Prefix`` sets ``SCRIPT_NAME``. + + You must tell the middleware how many proxies set each header so it + knows what values to trust. It is a security issue to trust values + that came from the client rather than a proxy. + + The original values of the headers are stored in the WSGI + environ as ``werkzeug.proxy_fix.orig``, a dict. + + :param app: The WSGI application to wrap. + :param x_for: Number of values to trust for ``X-Forwarded-For``. + :param x_proto: Number of values to trust for ``X-Forwarded-Proto``. + :param x_host: Number of values to trust for ``X-Forwarded-Host``. + :param x_port: Number of values to trust for ``X-Forwarded-Port``. + :param x_prefix: Number of values to trust for + ``X-Forwarded-Prefix``. + + .. code-block:: python + + from werkzeug.middleware.proxy_fix import ProxyFix + # App is behind one proxy that sets the -For and -Host headers. + app = ProxyFix(app, x_for=1, x_host=1) + + .. versionchanged:: 1.0 + Deprecated code has been removed: + + * The ``num_proxies`` argument and attribute. + * The ``get_remote_addr`` method. + * The environ keys ``orig_remote_addr``, + ``orig_wsgi_url_scheme``, and ``orig_http_host``. + + .. versionchanged:: 0.15 + All headers support multiple values. The ``num_proxies`` + argument is deprecated. Each header is configured with a + separate number of trusted proxies. + + .. versionchanged:: 0.15 + Original WSGI environ values are stored in the + ``werkzeug.proxy_fix.orig`` dict. ``orig_remote_addr``, + ``orig_wsgi_url_scheme``, and ``orig_http_host`` are deprecated + and will be removed in 1.0. + + .. versionchanged:: 0.15 + Support ``X-Forwarded-Port`` and ``X-Forwarded-Prefix``. + + .. versionchanged:: 0.15 + ``X-Forwarded-Host`` and ``X-Forwarded-Port`` modify + ``SERVER_NAME`` and ``SERVER_PORT``. + """ + + def __init__( + self, + app: "WSGIApplication", + x_for: int = 1, + x_proto: int = 1, + x_host: int = 0, + x_port: int = 0, + x_prefix: int = 0, + ) -> None: + self.app = app + self.x_for = x_for + self.x_proto = x_proto + self.x_host = x_host + self.x_port = x_port + self.x_prefix = x_prefix + + def _get_real_value(self, trusted: int, value: t.Optional[str]) -> t.Optional[str]: + """Get the real value from a list header based on the configured + number of trusted proxies. + + :param trusted: Number of values to trust in the header. + :param value: Comma separated list header value to parse. + :return: The real value, or ``None`` if there are fewer values + than the number of trusted proxies. + + .. versionchanged:: 1.0 + Renamed from ``_get_trusted_comma``. + + .. versionadded:: 0.15 + """ + if not (trusted and value): + return None + values = parse_list_header(value) + if len(values) >= trusted: + return values[-trusted] + return None + + def __call__( + self, environ: "WSGIEnvironment", start_response: "StartResponse" + ) -> t.Iterable[bytes]: + """Modify the WSGI environ based on the various ``Forwarded`` + headers before calling the wrapped application. Store the + original environ values in ``werkzeug.proxy_fix.orig_{key}``. + """ + environ_get = environ.get + orig_remote_addr = environ_get("REMOTE_ADDR") + orig_wsgi_url_scheme = environ_get("wsgi.url_scheme") + orig_http_host = environ_get("HTTP_HOST") + environ.update( + { + "werkzeug.proxy_fix.orig": { + "REMOTE_ADDR": orig_remote_addr, + "wsgi.url_scheme": orig_wsgi_url_scheme, + "HTTP_HOST": orig_http_host, + "SERVER_NAME": environ_get("SERVER_NAME"), + "SERVER_PORT": environ_get("SERVER_PORT"), + "SCRIPT_NAME": environ_get("SCRIPT_NAME"), + } + } + ) + + x_for = self._get_real_value(self.x_for, environ_get("HTTP_X_FORWARDED_FOR")) + if x_for: + environ["REMOTE_ADDR"] = x_for + + x_proto = self._get_real_value( + self.x_proto, environ_get("HTTP_X_FORWARDED_PROTO") + ) + if x_proto: + environ["wsgi.url_scheme"] = x_proto + + x_host = self._get_real_value(self.x_host, environ_get("HTTP_X_FORWARDED_HOST")) + if x_host: + environ["HTTP_HOST"] = environ["SERVER_NAME"] = x_host + # "]" to check for IPv6 address without port + if ":" in x_host and not x_host.endswith("]"): + environ["SERVER_NAME"], environ["SERVER_PORT"] = x_host.rsplit(":", 1) + + x_port = self._get_real_value(self.x_port, environ_get("HTTP_X_FORWARDED_PORT")) + if x_port: + host = environ.get("HTTP_HOST") + if host: + # "]" to check for IPv6 address without port + if ":" in host and not host.endswith("]"): + host = host.rsplit(":", 1)[0] + environ["HTTP_HOST"] = f"{host}:{x_port}" + environ["SERVER_PORT"] = x_port + + x_prefix = self._get_real_value( + self.x_prefix, environ_get("HTTP_X_FORWARDED_PREFIX") + ) + if x_prefix: + environ["SCRIPT_NAME"] = x_prefix + + return self.app(environ, start_response) diff --git a/.vtodo/Lib/site-packages/werkzeug/middleware/shared_data.py b/.vtodo/Lib/site-packages/werkzeug/middleware/shared_data.py new file mode 100644 index 0000000..2ec396c --- /dev/null +++ b/.vtodo/Lib/site-packages/werkzeug/middleware/shared_data.py @@ -0,0 +1,280 @@ +""" +Serve Shared Static Files +========================= + +.. autoclass:: SharedDataMiddleware + :members: is_allowed + +:copyright: 2007 Pallets +:license: BSD-3-Clause +""" +import mimetypes +import os +import pkgutil +import posixpath +import typing as t +from datetime import datetime +from datetime import timezone +from io import BytesIO +from time import time +from zlib import adler32 + +from ..http import http_date +from ..http import is_resource_modified +from ..security import safe_join +from ..utils import get_content_type +from ..wsgi import get_path_info +from ..wsgi import wrap_file + +_TOpener = t.Callable[[], t.Tuple[t.IO[bytes], datetime, int]] +_TLoader = t.Callable[[t.Optional[str]], t.Tuple[t.Optional[str], t.Optional[_TOpener]]] + +if t.TYPE_CHECKING: + from _typeshed.wsgi import StartResponse + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + + +class SharedDataMiddleware: + + """A WSGI middleware which provides static content for development + environments or simple server setups. Its usage is quite simple:: + + import os + from werkzeug.middleware.shared_data import SharedDataMiddleware + + app = SharedDataMiddleware(app, { + '/shared': os.path.join(os.path.dirname(__file__), 'shared') + }) + + The contents of the folder ``./shared`` will now be available on + ``http://example.com/shared/``. This is pretty useful during development + because a standalone media server is not required. Files can also be + mounted on the root folder and still continue to use the application because + the shared data middleware forwards all unhandled requests to the + application, even if the requests are below one of the shared folders. + + If `pkg_resources` is available you can also tell the middleware to serve + files from package data:: + + app = SharedDataMiddleware(app, { + '/static': ('myapplication', 'static') + }) + + This will then serve the ``static`` folder in the `myapplication` + Python package. + + The optional `disallow` parameter can be a list of :func:`~fnmatch.fnmatch` + rules for files that are not accessible from the web. If `cache` is set to + `False` no caching headers are sent. + + Currently the middleware does not support non-ASCII filenames. If the + encoding on the file system happens to match the encoding of the URI it may + work but this could also be by accident. We strongly suggest using ASCII + only file names for static files. + + The middleware will guess the mimetype using the Python `mimetype` + module. If it's unable to figure out the charset it will fall back + to `fallback_mimetype`. + + :param app: the application to wrap. If you don't want to wrap an + application you can pass it :exc:`NotFound`. + :param exports: a list or dict of exported files and folders. + :param disallow: a list of :func:`~fnmatch.fnmatch` rules. + :param cache: enable or disable caching headers. + :param cache_timeout: the cache timeout in seconds for the headers. + :param fallback_mimetype: The fallback mimetype for unknown files. + + .. versionchanged:: 1.0 + The default ``fallback_mimetype`` is + ``application/octet-stream``. If a filename looks like a text + mimetype, the ``utf-8`` charset is added to it. + + .. versionadded:: 0.6 + Added ``fallback_mimetype``. + + .. versionchanged:: 0.5 + Added ``cache_timeout``. + """ + + def __init__( + self, + app: "WSGIApplication", + exports: t.Union[ + t.Dict[str, t.Union[str, t.Tuple[str, str]]], + t.Iterable[t.Tuple[str, t.Union[str, t.Tuple[str, str]]]], + ], + disallow: None = None, + cache: bool = True, + cache_timeout: int = 60 * 60 * 12, + fallback_mimetype: str = "application/octet-stream", + ) -> None: + self.app = app + self.exports: t.List[t.Tuple[str, _TLoader]] = [] + self.cache = cache + self.cache_timeout = cache_timeout + + if isinstance(exports, dict): + exports = exports.items() + + for key, value in exports: + if isinstance(value, tuple): + loader = self.get_package_loader(*value) + elif isinstance(value, str): + if os.path.isfile(value): + loader = self.get_file_loader(value) + else: + loader = self.get_directory_loader(value) + else: + raise TypeError(f"unknown def {value!r}") + + self.exports.append((key, loader)) + + if disallow is not None: + from fnmatch import fnmatch + + self.is_allowed = lambda x: not fnmatch(x, disallow) + + self.fallback_mimetype = fallback_mimetype + + def is_allowed(self, filename: str) -> bool: + """Subclasses can override this method to disallow the access to + certain files. However by providing `disallow` in the constructor + this method is overwritten. + """ + return True + + def _opener(self, filename: str) -> _TOpener: + return lambda: ( + open(filename, "rb"), + datetime.fromtimestamp(os.path.getmtime(filename), tz=timezone.utc), + int(os.path.getsize(filename)), + ) + + def get_file_loader(self, filename: str) -> _TLoader: + return lambda x: (os.path.basename(filename), self._opener(filename)) + + def get_package_loader(self, package: str, package_path: str) -> _TLoader: + load_time = datetime.now(timezone.utc) + provider = pkgutil.get_loader(package) + reader = provider.get_resource_reader(package) # type: ignore + + def loader( + path: t.Optional[str], + ) -> t.Tuple[t.Optional[str], t.Optional[_TOpener]]: + if path is None: + return None, None + + path = safe_join(package_path, path) + + if path is None: + return None, None + + basename = posixpath.basename(path) + + try: + resource = reader.open_resource(path) + except OSError: + return None, None + + if isinstance(resource, BytesIO): + return ( + basename, + lambda: (resource, load_time, len(resource.getvalue())), + ) + + return ( + basename, + lambda: ( + resource, + datetime.fromtimestamp( + os.path.getmtime(resource.name), tz=timezone.utc + ), + os.path.getsize(resource.name), + ), + ) + + return loader + + def get_directory_loader(self, directory: str) -> _TLoader: + def loader( + path: t.Optional[str], + ) -> t.Tuple[t.Optional[str], t.Optional[_TOpener]]: + if path is not None: + path = safe_join(directory, path) + + if path is None: + return None, None + else: + path = directory + + if os.path.isfile(path): + return os.path.basename(path), self._opener(path) + + return None, None + + return loader + + def generate_etag(self, mtime: datetime, file_size: int, real_filename: str) -> str: + real_filename = os.fsencode(real_filename) + timestamp = mtime.timestamp() + checksum = adler32(real_filename) & 0xFFFFFFFF + return f"wzsdm-{timestamp}-{file_size}-{checksum}" + + def __call__( + self, environ: "WSGIEnvironment", start_response: "StartResponse" + ) -> t.Iterable[bytes]: + path = get_path_info(environ) + file_loader = None + + for search_path, loader in self.exports: + if search_path == path: + real_filename, file_loader = loader(None) + + if file_loader is not None: + break + + if not search_path.endswith("/"): + search_path += "/" + + if path.startswith(search_path): + real_filename, file_loader = loader(path[len(search_path) :]) + + if file_loader is not None: + break + + if file_loader is None or not self.is_allowed(real_filename): # type: ignore + return self.app(environ, start_response) + + guessed_type = mimetypes.guess_type(real_filename) # type: ignore + mime_type = get_content_type(guessed_type[0] or self.fallback_mimetype, "utf-8") + f, mtime, file_size = file_loader() + + headers = [("Date", http_date())] + + if self.cache: + timeout = self.cache_timeout + etag = self.generate_etag(mtime, file_size, real_filename) # type: ignore + headers += [ + ("Etag", f'"{etag}"'), + ("Cache-Control", f"max-age={timeout}, public"), + ] + + if not is_resource_modified(environ, etag, last_modified=mtime): + f.close() + start_response("304 Not Modified", headers) + return [] + + headers.append(("Expires", http_date(time() + timeout))) + else: + headers.append(("Cache-Control", "public")) + + headers.extend( + ( + ("Content-Type", mime_type), + ("Content-Length", str(file_size)), + ("Last-Modified", http_date(mtime)), + ) + ) + start_response("200 OK", headers) + return wrap_file(environ, f) diff --git a/.vtodo/Lib/site-packages/werkzeug/py.typed b/.vtodo/Lib/site-packages/werkzeug/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/.vtodo/Lib/site-packages/werkzeug/routing/__init__.py b/.vtodo/Lib/site-packages/werkzeug/routing/__init__.py new file mode 100644 index 0000000..84b043f --- /dev/null +++ b/.vtodo/Lib/site-packages/werkzeug/routing/__init__.py @@ -0,0 +1,133 @@ +"""When it comes to combining multiple controller or view functions +(however you want to call them) you need a dispatcher. A simple way +would be applying regular expression tests on the ``PATH_INFO`` and +calling registered callback functions that return the value then. + +This module implements a much more powerful system than simple regular +expression matching because it can also convert values in the URLs and +build URLs. + +Here a simple example that creates a URL map for an application with +two subdomains (www and kb) and some URL rules: + +.. code-block:: python + + m = Map([ + # Static URLs + Rule('/', endpoint='static/index'), + Rule('/about', endpoint='static/about'), + Rule('/help', endpoint='static/help'), + # Knowledge Base + Subdomain('kb', [ + Rule('/', endpoint='kb/index'), + Rule('/browse/', endpoint='kb/browse'), + Rule('/browse//', endpoint='kb/browse'), + Rule('/browse//', endpoint='kb/browse') + ]) + ], default_subdomain='www') + +If the application doesn't use subdomains it's perfectly fine to not set +the default subdomain and not use the `Subdomain` rule factory. The +endpoint in the rules can be anything, for example import paths or +unique identifiers. The WSGI application can use those endpoints to get the +handler for that URL. It doesn't have to be a string at all but it's +recommended. + +Now it's possible to create a URL adapter for one of the subdomains and +build URLs: + +.. code-block:: python + + c = m.bind('example.com') + + c.build("kb/browse", dict(id=42)) + 'http://kb.example.com/browse/42/' + + c.build("kb/browse", dict()) + 'http://kb.example.com/browse/' + + c.build("kb/browse", dict(id=42, page=3)) + 'http://kb.example.com/browse/42/3' + + c.build("static/about") + '/about' + + c.build("static/index", force_external=True) + 'http://www.example.com/' + + c = m.bind('example.com', subdomain='kb') + + c.build("static/about") + 'http://www.example.com/about' + +The first argument to bind is the server name *without* the subdomain. +Per default it will assume that the script is mounted on the root, but +often that's not the case so you can provide the real mount point as +second argument: + +.. code-block:: python + + c = m.bind('example.com', '/applications/example') + +The third argument can be the subdomain, if not given the default +subdomain is used. For more details about binding have a look at the +documentation of the `MapAdapter`. + +And here is how you can match URLs: + +.. code-block:: python + + c = m.bind('example.com') + + c.match("/") + ('static/index', {}) + + c.match("/about") + ('static/about', {}) + + c = m.bind('example.com', '/', 'kb') + + c.match("/") + ('kb/index', {}) + + c.match("/browse/42/23") + ('kb/browse', {'id': 42, 'page': 23}) + +If matching fails you get a ``NotFound`` exception, if the rule thinks +it's a good idea to redirect (for example because the URL was defined +to have a slash at the end but the request was missing that slash) it +will raise a ``RequestRedirect`` exception. Both are subclasses of +``HTTPException`` so you can use those errors as responses in the +application. + +If matching succeeded but the URL rule was incompatible to the given +method (for example there were only rules for ``GET`` and ``HEAD`` but +routing tried to match a ``POST`` request) a ``MethodNotAllowed`` +exception is raised. +""" +from .converters import AnyConverter as AnyConverter +from .converters import BaseConverter as BaseConverter +from .converters import FloatConverter as FloatConverter +from .converters import IntegerConverter as IntegerConverter +from .converters import PathConverter as PathConverter +from .converters import UnicodeConverter as UnicodeConverter +from .converters import UUIDConverter as UUIDConverter +from .converters import ValidationError as ValidationError +from .exceptions import BuildError as BuildError +from .exceptions import NoMatch as NoMatch +from .exceptions import RequestAliasRedirect as RequestAliasRedirect +from .exceptions import RequestPath as RequestPath +from .exceptions import RequestRedirect as RequestRedirect +from .exceptions import RoutingException as RoutingException +from .exceptions import WebsocketMismatch as WebsocketMismatch +from .map import Map as Map +from .map import MapAdapter as MapAdapter +from .matcher import StateMachineMatcher as StateMachineMatcher +from .rules import EndpointPrefix as EndpointPrefix +from .rules import parse_converter_args as parse_converter_args +from .rules import Rule as Rule +from .rules import RuleFactory as RuleFactory +from .rules import RuleTemplate as RuleTemplate +from .rules import RuleTemplateFactory as RuleTemplateFactory +from .rules import Subdomain as Subdomain +from .rules import Submount as Submount diff --git a/.vtodo/Lib/site-packages/werkzeug/routing/__pycache__/__init__.cpython-310.pyc b/.vtodo/Lib/site-packages/werkzeug/routing/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f82e75b0bc3e2ef55a1029f1450326e1fa15a3b5 GIT binary patch literal 4622 zcmbtYT~i##6ph@&h+k1F*`l( z?qMOW{GdGL$K;LIJmoLsDd*mqSq3DR90!$}(>?uh`}RHOcB2;-yc&M~@%v}NKX2A* z|DsCu=Nc+sDzior#f598V(SSh#^2 zu5)vuR4AkfBb!PW43ue#ZDFG!O+J!m-biP0D7s3>G>y+_ZKL{GER9g7sZrKqCE=8H zRuC8{(dq1MAHHet?7se?(-ATWy#PxVBO~jS!HU$>mBI0awHPZMCU=?1=Q)varf9~b z>3N5P$cmv3GnmE34ON0AB|H%f@WCjNVzZ`~#loIpks-~OlshSG_9`|HX}e@qR|PV& zm=>PK2pL=QbOOIQ!8TYCY5l-e5y8r0|(KmFzp-O<Yuf?JK)Fa~~U!fB0~~K*j0D`g41w`xoMmi5|r&>?`q7S~Xtyp!7xqN=`ro9Ar)$6le!ZW*5d&PV^_B#1xZxk;xo~U z67WllL_1-X^N75~G8dyb`ltuZ#q=H@NJmgGik=LdHfK$7I8a_0NF`-BT$xuSAqj#M zl-pn$luQAgHK+#p89+mfmnG3pAbu2r;ZZMA#`4tS{lRxTmxx1b&8^a~tz4Ovvag_Z zFb5J4k~R|Un33RNQ-~cWN*4{}2|Izz6V@401n3ZnWH)o{GS4U|F9au4A^5(lM};Rf z)UOKp;9b5kBbeqatXS!jjEo(F9?J{wbdz(Cz0{ip~=)`|A17N98|9S3umx# z!IUl1r>kf3F++=!xVlnlK4=7L8jBz_{fzP)1sEN_Kn^4dT4gA!ClWc~OG+QmyE^@fh+HDX4Jl%7+g%Ee39i#&^ zE`UG_q)ED05DQbIPrz6H1|r1ybjZliODyVH5D4a%`}vLWz9H(Ziptqm5y<3pavHS7 zTr5(G4)3#c%Z7-0yo-Kx!rK}vo;PMExdXBiLML8BHu!E4Dkq~DiJa0Op9SdzYljrE z*2kn7Sjh`@z({g^@u>I+?vE5@yL#F(> z9)#kSf8ltx9Wt#;7-VM+T%8{iT z*qOAcC_#EcJ};9abzs7ILj|!4FMAVTVLa~cNR$b#CHa+s{N3q%_v$czLjb>dwf!wB zBuWM>WUrYBHo)b4{_Lu~9}Z}U$vuY4Ge*AURS^i=ag0X-LIDqfA{2;(>}aI+f8*_1 zTc0?e-%ifH$zK4sU)yZCT!deD;;8wk_=8N>goEewRdaX|J{|ynDCv zZM9+PPcn`|){j?oHdx<#N$-uaHodFg(%S_e){4N`jw5Ny=s^2i(M|@Sx^yLe(6qdt zyKo#IXj^%&x)!eq<=#e?J&MLjXcvBY7ZQfr>H%H}>aC;)6n^o$5zULQif6>00UJ)y zvX)9?RXZr1*+vSmXrJ20%@M!n==baV0*UH8RSy*&P?#R|)oT5s+wAxXias-_aQ50= z7w6OMHWawsKKI5;T0?)EFq2eFQE{D$8&phFF+;^Hit~%0u!jEHVdkiqrvk6>npvb` ziHcxnmwkBS*4HH4R&J98hyS+#ce9?P;r-vdsN(~;sJ`k*Uaw- zQ}Lfaf3tb?4k_LqDT6oSk=0q+MYtU~9qK1%N?Lc+JfX_b_fhxAMov9R@sx%n+9S~D t_^&GKw+f21+HL&Bz_r`W^z3}*tNaE1g5uo%0mW2p{&;@kfBUyQ@n60Io{Rth literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/werkzeug/routing/__pycache__/converters.cpython-310.pyc b/.vtodo/Lib/site-packages/werkzeug/routing/__pycache__/converters.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..837b17d867905ac09c67eb0f310957e1679b14d2 GIT binary patch literal 8739 zcmdT}-ESP%b)V1u;x4(8D2h}SW2_{WwxO1?;-C(}$Z}{YQ7zN1Ou5aP&T_bOmmG3- zW_@Q?A~#DQ2?eAteaK_s8nF9Nzz;?Lg+BH_C@^nDpY#|c2w-QD_6_u+K)6Z<7J;}URLB!%vu5YoR>#Fe`3^TkSlmI z$jyiu)E1F1dL`sboG&3i>&+oQ$N5?0%icNU&vAYZ`SacdsNn?-e8XYM`m^hCfK5*R7SM}mJ@9w7Vm}c#+HT{(-4*fH6Cn?}qO-dqqfX+7yDP(0$JO?c%-}8b zrr1J~({ehOoRR$SO)B!4vf>MbeSsMuQID;G%_7{$1QL>2FW(X>};& z``Q=!Mn#u%D8u$yu?U)+k#zjzIZB3`B#X$>7W<)})a#4L{gy^8=-q{hl~e~!Qv0zR zbi~A5s0SVLb=T+3q#J+^QA$7dOyE9pG5WcQL-0>Kprioku1G?Y4Y8} zHfP>SQsB2P;~{|z5=E?SoHLn-P86n``ORAlxJ7Tr3F9s!#Hun zhDhz5qeR4Mo_<^@-6LsY)YOacdB(KxL%wV>_g_lqj(=naZ zbNYhbojum(91SEH?B)7=(eyS>VCSY;4d z-G+hLR^U8n;fHD*)vg6iZgpCYDs!SH_f{{26yE8?iL)b|T`63ckm1Z|cB2bOB?#V~ zS0`WP9!|W)oZ(N;ib`im7D2Q7KG=Bl%65*9s-yLXzg*WuD;% zYE`BFR2m`(2Ge2exk)`3fL_59b&angVE{U3*oJQC)*lSR_#eYG{yS$G|CO_iKNy*X z=aSl=fh59Gy5f6CPD7Gs64w-L$(tF59b%V7SoZ?k>Wr{qtNS*cp@{5tDJ=p>M=`-?Ok@RHlt{H$CXzr(4}3$RP0a32wni;0}!p* z2^-842N+5y+m7Pc-vPN)OLei@2!srD(ha6y1ZYWzU^VU|VYTXjlE!Eb%NXtoZ*|qV zRlPOr2|nAw*s;@xP~kaG_CyHG_5+W=+a)%tux4{i^=+OG>-Z%5A>}3|CuNW6%#SNM zR2S+B#WX$LGd=^+^Q@;@&v;8a0SG{W?N3eA+I_X{n%Cc0eXjQ!L}3OO5fOU9`?%u; zB)eKq`zU*?Cw(mWwed07J8NIXNDV1K5#om17L=>rO!Y&hnL{(27S;kkP8w0Gefy_0 zD!pPs>sH23;7}*4YeCd-gZMTc2ECr^l}bid(XEdsL_xlXY-;UCekd7L%rR7~A{ zoCWv%5arBGih4o6s&_9=odWZ<3C1;$`a1Q$gM>KezV;a0B>}(^+w%0!3~~gYf_G8f ziLRu6Y$TQk9!Apd830az?=N+LX4k-3KP`DO0WsZIko`DLy9%EZbQa~l)=pp$$99lGUf zbcD&7>SII2X7MXbzfCXC#1T9*FD>wiXKxPZI%L1KA}fG`@%YpvnZRvhl$hvX0krY5 zF|T(oOqpV3=EXqGgpjErL9YBZB@QLurGyW{DR_Pj6>sBaH-h20blJFKODb>Rp15eM z?%?LFISa!Bmx>HxT6zx@Y>2Mi-#qXS#004ukebt`X?vOI9KZ+;QyeMZRm2P@OwvTm zgburb|4uBLoq*k|C;kBnh#okbZ1Vyqg4StxICAO_lB zyTE369w%ifo!jWNc7zT@ACG&00D(zFA!tizg1XJAz`{! zIs5Du#E{N}ofarI$4)jS9pjCAW2UCq^bdts_xxQyiQ$U-A^GEQ!eW0n6rSRl0}g`D z8a)#r(jAkk^lX_S$b)GCy9DQi1InC}Ibx4ZPR~MZrUzHFo+a}$a6^mgEsF4|^DxAD zc^`wzzodk;R6algJHVjAKXonqrZ4c_Cp(V)&P*nqV=xR zP#++n^~#b0e|kQEKS^u@khEdCPE8Kr&s$mf(rzs2rl$3 zKYRjOlOfAJ9c#qX=TtJBx25a>&z80(wYyC}2&M%K7??ky`R0(!V!lN<=G}!Ub40XY z!c5WurtRRZAc*j2QX07=WENppZ@hsh(95X74P5&awKWKFE(Q26+FSss#u^Phdm2fDnA3B#`CkDhVk6U&VD&xo-Y`gMP1Se z4crqKiTMuhm`>XON(VryB8U*Tot94nl>5)vol_wV4owVbH(?YIjVN(PoV86?If$^b zhGxwCZ(r>P27Y)njETTLP7K)z)s`*y+52PBAHC{qg5+;+tLp7-hD8oA2#O;=!SQrN zNIM0?NpQF9gkl$)H8#Q-_hoE`*Hy0{Y|4&Mj>>SlgD3^XF=!G4lYb_TF^<2gX~;H# zoG`9)6gE4)3UQOeKvD6ds53O$AjHbaNg}NFwe4+Y1qeA%oMxG+2Zzvk!7Mrz7WX+y zG8~2?<3SW1#7;nrWqW(&=FOY0u552BISxWnpc;=|>9g3B!#)`;@k6x^4#^;x!aF_L z#69dcp-{@yCQ7oa&YM9HJ?Za`bA7NomB9up!#DE7To0Zqys(?qSHCx0*W@8UfD92P zIsj@;U31I(&gKS8%L_RQYwZX|M)YJ8+VM}MCWK>5PcDL-Q0Wqhc!#+%X55Yl%67|2cbpGN3dkZ)36h$*e zt~wk=w!lo+CYw!TYX45^LB@SC+r2b-kcZ@~Vs5||PyK$tMnARrbSl!sdP^pmO|E=M zPwXPT&?kI^CjW{%egz3D^m+PbFdnwc^}}{3Q(-&mb4J}Q6yCwj@q*Lpt_-NaCph>E z^nr~&5z|LEi}EPVpo{`}@KN{!+e}(|^yvOuL&o3z`>#@JYWfw?w`gZyG@u!sj_<9G zVs^vu+Eg^X`E|qd-M2q{^Ts{*M)T&4?|<~O*N<Pd=)$MI`G_$7lb#6%PQi?F^<2X>`y2evc<9R#>h7PHJbbfZuWiX zC>sUikCruW;Nut7RkCTeZ_?rI@yDdpbi&osZI}>_lB>oRRuikQh^5V+ymz<0{^PsrZ{Oee(a7Lt@xW7$wHYU%q ofN$pT0*#DO)XU~t<8 literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/werkzeug/routing/__pycache__/exceptions.cpython-310.pyc b/.vtodo/Lib/site-packages/werkzeug/routing/__pycache__/exceptions.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f2e42a7f0dc7f95e3446b68b5d60fdac3a169647 GIT binary patch literal 5489 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 P&7YaCER^TpSakmfyw>!^ literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/werkzeug/routing/__pycache__/map.cpython-310.pyc b/.vtodo/Lib/site-packages/werkzeug/routing/__pycache__/map.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..09507e3501ab421d9d46afd195333b73285ddf90 GIT binary patch literal 30978 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 ac`Dj$VH>aj3dbK*KL1;C1WMbL%6|dWOe2o~ literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/werkzeug/routing/__pycache__/rules.cpython-310.pyc b/.vtodo/Lib/site-packages/werkzeug/routing/__pycache__/rules.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e594218bc3bfb290091b5b093f98eb0d082cd449 GIT binary patch literal 27060 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 None: + self.map = map + + def to_python(self, value: str) -> t.Any: + return value + + def to_url(self, value: t.Any) -> str: + if isinstance(value, (bytes, bytearray)): + return _fast_url_quote(value) + return _fast_url_quote(str(value).encode(self.map.charset)) + + +class UnicodeConverter(BaseConverter): + """This converter is the default converter and accepts any string but + only one path segment. Thus the string can not include a slash. + + This is the default validator. + + Example:: + + Rule('/pages/'), + Rule('/') + + :param map: the :class:`Map`. + :param minlength: the minimum length of the string. Must be greater + or equal 1. + :param maxlength: the maximum length of the string. + :param length: the exact length of the string. + """ + + part_isolating = True + + def __init__( + self, + map: "Map", + minlength: int = 1, + maxlength: t.Optional[int] = None, + length: t.Optional[int] = None, + ) -> None: + super().__init__(map) + if length is not None: + length_regex = f"{{{int(length)}}}" + else: + if maxlength is None: + maxlength_value = "" + else: + maxlength_value = str(int(maxlength)) + length_regex = f"{{{int(minlength)},{maxlength_value}}}" + self.regex = f"[^/]{length_regex}" + + +class AnyConverter(BaseConverter): + """Matches one of the items provided. Items can either be Python + identifiers or strings:: + + Rule('/') + + :param map: the :class:`Map`. + :param items: this function accepts the possible items as positional + arguments. + + .. versionchanged:: 2.2 + Value is validated when building a URL. + """ + + part_isolating = True + + def __init__(self, map: "Map", *items: str) -> None: + super().__init__(map) + self.items = set(items) + self.regex = f"(?:{'|'.join([re.escape(x) for x in items])})" + + def to_url(self, value: t.Any) -> str: + if value in self.items: + return str(value) + + valid_values = ", ".join(f"'{item}'" for item in sorted(self.items)) + raise ValueError(f"'{value}' is not one of {valid_values}") + + +class PathConverter(BaseConverter): + """Like the default :class:`UnicodeConverter`, but it also matches + slashes. This is useful for wikis and similar applications:: + + Rule('/') + Rule('//edit') + + :param map: the :class:`Map`. + """ + + regex = "[^/].*?" + weight = 200 + part_isolating = False + + +class NumberConverter(BaseConverter): + """Baseclass for `IntegerConverter` and `FloatConverter`. + + :internal: + """ + + weight = 50 + num_convert: t.Callable = int + part_isolating = True + + def __init__( + self, + map: "Map", + fixed_digits: int = 0, + min: t.Optional[int] = None, + max: t.Optional[int] = None, + signed: bool = False, + ) -> None: + if signed: + self.regex = self.signed_regex + super().__init__(map) + self.fixed_digits = fixed_digits + self.min = min + self.max = max + self.signed = signed + + def to_python(self, value: str) -> t.Any: + if self.fixed_digits and len(value) != self.fixed_digits: + raise ValidationError() + value = self.num_convert(value) + if (self.min is not None and value < self.min) or ( + self.max is not None and value > self.max + ): + raise ValidationError() + return value + + def to_url(self, value: t.Any) -> str: + value = str(self.num_convert(value)) + if self.fixed_digits: + value = value.zfill(self.fixed_digits) + return value + + @property + def signed_regex(self) -> str: + return f"-?{self.regex}" + + +class IntegerConverter(NumberConverter): + """This converter only accepts integer values:: + + Rule("/page/") + + By default it only accepts unsigned, positive values. The ``signed`` + parameter will enable signed, negative values. :: + + Rule("/page/") + + :param map: The :class:`Map`. + :param fixed_digits: The number of fixed digits in the URL. If you + set this to ``4`` for example, the rule will only match if the + URL looks like ``/0001/``. The default is variable length. + :param min: The minimal value. + :param max: The maximal value. + :param signed: Allow signed (negative) values. + + .. versionadded:: 0.15 + The ``signed`` parameter. + """ + + regex = r"\d+" + part_isolating = True + + +class FloatConverter(NumberConverter): + """This converter only accepts floating point values:: + + Rule("/probability/") + + By default it only accepts unsigned, positive values. The ``signed`` + parameter will enable signed, negative values. :: + + Rule("/offset/") + + :param map: The :class:`Map`. + :param min: The minimal value. + :param max: The maximal value. + :param signed: Allow signed (negative) values. + + .. versionadded:: 0.15 + The ``signed`` parameter. + """ + + regex = r"\d+\.\d+" + num_convert = float + part_isolating = True + + def __init__( + self, + map: "Map", + min: t.Optional[float] = None, + max: t.Optional[float] = None, + signed: bool = False, + ) -> None: + super().__init__(map, min=min, max=max, signed=signed) # type: ignore + + +class UUIDConverter(BaseConverter): + """This converter only accepts UUID strings:: + + Rule('/object/') + + .. versionadded:: 0.10 + + :param map: the :class:`Map`. + """ + + regex = ( + r"[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-" + r"[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}" + ) + part_isolating = True + + def to_python(self, value: str) -> uuid.UUID: + return uuid.UUID(value) + + def to_url(self, value: uuid.UUID) -> str: + return str(value) + + +#: the default converter mapping for the map. +DEFAULT_CONVERTERS: t.Mapping[str, t.Type[BaseConverter]] = { + "default": UnicodeConverter, + "string": UnicodeConverter, + "any": AnyConverter, + "path": PathConverter, + "int": IntegerConverter, + "float": FloatConverter, + "uuid": UUIDConverter, +} diff --git a/.vtodo/Lib/site-packages/werkzeug/routing/exceptions.py b/.vtodo/Lib/site-packages/werkzeug/routing/exceptions.py new file mode 100644 index 0000000..7cbe6e9 --- /dev/null +++ b/.vtodo/Lib/site-packages/werkzeug/routing/exceptions.py @@ -0,0 +1,146 @@ +import difflib +import typing as t + +from ..exceptions import BadRequest +from ..exceptions import HTTPException +from ..utils import cached_property +from ..utils import redirect + +if t.TYPE_CHECKING: + from _typeshed.wsgi import WSGIEnvironment + from .map import MapAdapter + from .rules import Rule # noqa: F401 + from ..wrappers.request import Request + from ..wrappers.response import Response + + +class RoutingException(Exception): + """Special exceptions that require the application to redirect, notifying + about missing urls, etc. + + :internal: + """ + + +class RequestRedirect(HTTPException, RoutingException): + """Raise if the map requests a redirect. This is for example the case if + `strict_slashes` are activated and an url that requires a trailing slash. + + The attribute `new_url` contains the absolute destination url. + """ + + code = 308 + + def __init__(self, new_url: str) -> None: + super().__init__(new_url) + self.new_url = new_url + + def get_response( + self, + environ: t.Optional[t.Union["WSGIEnvironment", "Request"]] = None, + scope: t.Optional[dict] = None, + ) -> "Response": + return redirect(self.new_url, self.code) + + +class RequestPath(RoutingException): + """Internal exception.""" + + __slots__ = ("path_info",) + + def __init__(self, path_info: str) -> None: + super().__init__() + self.path_info = path_info + + +class RequestAliasRedirect(RoutingException): # noqa: B903 + """This rule is an alias and wants to redirect to the canonical URL.""" + + def __init__(self, matched_values: t.Mapping[str, t.Any], endpoint: str) -> None: + super().__init__() + self.matched_values = matched_values + self.endpoint = endpoint + + +class BuildError(RoutingException, LookupError): + """Raised if the build system cannot find a URL for an endpoint with the + values provided. + """ + + def __init__( + self, + endpoint: str, + values: t.Mapping[str, t.Any], + method: t.Optional[str], + adapter: t.Optional["MapAdapter"] = None, + ) -> None: + super().__init__(endpoint, values, method) + self.endpoint = endpoint + self.values = values + self.method = method + self.adapter = adapter + + @cached_property + def suggested(self) -> t.Optional["Rule"]: + return self.closest_rule(self.adapter) + + def closest_rule(self, adapter: t.Optional["MapAdapter"]) -> t.Optional["Rule"]: + def _score_rule(rule: "Rule") -> float: + return sum( + [ + 0.98 + * difflib.SequenceMatcher( + None, rule.endpoint, self.endpoint + ).ratio(), + 0.01 * bool(set(self.values or ()).issubset(rule.arguments)), + 0.01 * bool(rule.methods and self.method in rule.methods), + ] + ) + + if adapter and adapter.map._rules: + return max(adapter.map._rules, key=_score_rule) + + return None + + def __str__(self) -> str: + message = [f"Could not build url for endpoint {self.endpoint!r}"] + if self.method: + message.append(f" ({self.method!r})") + if self.values: + message.append(f" with values {sorted(self.values)!r}") + message.append(".") + if self.suggested: + if self.endpoint == self.suggested.endpoint: + if ( + self.method + and self.suggested.methods is not None + and self.method not in self.suggested.methods + ): + message.append( + " Did you mean to use methods" + f" {sorted(self.suggested.methods)!r}?" + ) + missing_values = self.suggested.arguments.union( + set(self.suggested.defaults or ()) + ) - set(self.values.keys()) + if missing_values: + message.append( + f" Did you forget to specify values {sorted(missing_values)!r}?" + ) + else: + message.append(f" Did you mean {self.suggested.endpoint!r} instead?") + return "".join(message) + + +class WebsocketMismatch(BadRequest): + """The only matched rule is either a WebSocket and the request is + HTTP, or the rule is HTTP and the request is a WebSocket. + """ + + +class NoMatch(Exception): + __slots__ = ("have_match_for", "websocket_mismatch") + + def __init__(self, have_match_for: t.Set[str], websocket_mismatch: bool) -> None: + self.have_match_for = have_match_for + self.websocket_mismatch = websocket_mismatch diff --git a/.vtodo/Lib/site-packages/werkzeug/routing/map.py b/.vtodo/Lib/site-packages/werkzeug/routing/map.py new file mode 100644 index 0000000..daf94b6 --- /dev/null +++ b/.vtodo/Lib/site-packages/werkzeug/routing/map.py @@ -0,0 +1,944 @@ +import posixpath +import typing as t +import warnings +from pprint import pformat +from threading import Lock + +from .._internal import _encode_idna +from .._internal import _get_environ +from .._internal import _to_str +from .._internal import _wsgi_decoding_dance +from ..datastructures import ImmutableDict +from ..datastructures import MultiDict +from ..exceptions import BadHost +from ..exceptions import HTTPException +from ..exceptions import MethodNotAllowed +from ..exceptions import NotFound +from ..urls import url_encode +from ..urls import url_join +from ..urls import url_quote +from ..wsgi import get_host +from .converters import DEFAULT_CONVERTERS +from .exceptions import BuildError +from .exceptions import NoMatch +from .exceptions import RequestAliasRedirect +from .exceptions import RequestPath +from .exceptions import RequestRedirect +from .exceptions import WebsocketMismatch +from .matcher import StateMachineMatcher +from .rules import _simple_rule_re +from .rules import Rule + +if t.TYPE_CHECKING: + import typing_extensions as te + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + from .converters import BaseConverter + from .rules import RuleFactory + from ..wrappers.request import Request + + +class Map: + """The map class stores all the URL rules and some configuration + parameters. Some of the configuration values are only stored on the + `Map` instance since those affect all rules, others are just defaults + and can be overridden for each rule. Note that you have to specify all + arguments besides the `rules` as keyword arguments! + + :param rules: sequence of url rules for this map. + :param default_subdomain: The default subdomain for rules without a + subdomain defined. + :param charset: charset of the url. defaults to ``"utf-8"`` + :param strict_slashes: If a rule ends with a slash but the matched + URL does not, redirect to the URL with a trailing slash. + :param merge_slashes: Merge consecutive slashes when matching or + building URLs. Matches will redirect to the normalized URL. + Slashes in variable parts are not merged. + :param redirect_defaults: This will redirect to the default rule if it + wasn't visited that way. This helps creating + unique URLs. + :param converters: A dict of converters that adds additional converters + to the list of converters. If you redefine one + converter this will override the original one. + :param sort_parameters: If set to `True` the url parameters are sorted. + See `url_encode` for more details. + :param sort_key: The sort key function for `url_encode`. + :param encoding_errors: the error method to use for decoding + :param host_matching: if set to `True` it enables the host matching + feature and disables the subdomain one. If + enabled the `host` parameter to rules is used + instead of the `subdomain` one. + + .. versionchanged:: 1.0 + If ``url_scheme`` is ``ws`` or ``wss``, only WebSocket rules + will match. + + .. versionchanged:: 1.0 + Added ``merge_slashes``. + + .. versionchanged:: 0.7 + Added ``encoding_errors`` and ``host_matching``. + + .. versionchanged:: 0.5 + Added ``sort_parameters`` and ``sort_key``. + """ + + #: A dict of default converters to be used. + default_converters = ImmutableDict(DEFAULT_CONVERTERS) + + #: The type of lock to use when updating. + #: + #: .. versionadded:: 1.0 + lock_class = Lock + + def __init__( + self, + rules: t.Optional[t.Iterable["RuleFactory"]] = None, + default_subdomain: str = "", + charset: str = "utf-8", + strict_slashes: bool = True, + merge_slashes: bool = True, + redirect_defaults: bool = True, + converters: t.Optional[t.Mapping[str, t.Type["BaseConverter"]]] = None, + sort_parameters: bool = False, + sort_key: t.Optional[t.Callable[[t.Any], t.Any]] = None, + encoding_errors: str = "replace", + host_matching: bool = False, + ) -> None: + self._matcher = StateMachineMatcher(merge_slashes) + self._rules_by_endpoint: t.Dict[str, t.List[Rule]] = {} + self._remap = True + self._remap_lock = self.lock_class() + + self.default_subdomain = default_subdomain + self.charset = charset + self.encoding_errors = encoding_errors + self.strict_slashes = strict_slashes + self.merge_slashes = merge_slashes + self.redirect_defaults = redirect_defaults + self.host_matching = host_matching + + self.converters = self.default_converters.copy() + if converters: + self.converters.update(converters) + + self.sort_parameters = sort_parameters + self.sort_key = sort_key + + for rulefactory in rules or (): + self.add(rulefactory) + + def is_endpoint_expecting(self, endpoint: str, *arguments: str) -> bool: + """Iterate over all rules and check if the endpoint expects + the arguments provided. This is for example useful if you have + some URLs that expect a language code and others that do not and + you want to wrap the builder a bit so that the current language + code is automatically added if not provided but endpoints expect + it. + + :param endpoint: the endpoint to check. + :param arguments: this function accepts one or more arguments + as positional arguments. Each one of them is + checked. + """ + self.update() + arguments = set(arguments) + for rule in self._rules_by_endpoint[endpoint]: + if arguments.issubset(rule.arguments): + return True + return False + + @property + def _rules(self) -> t.List[Rule]: + return [rule for rules in self._rules_by_endpoint.values() for rule in rules] + + def iter_rules(self, endpoint: t.Optional[str] = None) -> t.Iterator[Rule]: + """Iterate over all rules or the rules of an endpoint. + + :param endpoint: if provided only the rules for that endpoint + are returned. + :return: an iterator + """ + self.update() + if endpoint is not None: + return iter(self._rules_by_endpoint[endpoint]) + return iter(self._rules) + + def add(self, rulefactory: "RuleFactory") -> None: + """Add a new rule or factory to the map and bind it. Requires that the + rule is not bound to another map. + + :param rulefactory: a :class:`Rule` or :class:`RuleFactory` + """ + for rule in rulefactory.get_rules(self): + rule.bind(self) + if not rule.build_only: + self._matcher.add(rule) + self._rules_by_endpoint.setdefault(rule.endpoint, []).append(rule) + self._remap = True + + def bind( + self, + server_name: str, + script_name: t.Optional[str] = None, + subdomain: t.Optional[str] = None, + url_scheme: str = "http", + default_method: str = "GET", + path_info: t.Optional[str] = None, + query_args: t.Optional[t.Union[t.Mapping[str, t.Any], str]] = None, + ) -> "MapAdapter": + """Return a new :class:`MapAdapter` with the details specified to the + call. Note that `script_name` will default to ``'/'`` if not further + specified or `None`. The `server_name` at least is a requirement + because the HTTP RFC requires absolute URLs for redirects and so all + redirect exceptions raised by Werkzeug will contain the full canonical + URL. + + If no path_info is passed to :meth:`match` it will use the default path + info passed to bind. While this doesn't really make sense for + manual bind calls, it's useful if you bind a map to a WSGI + environment which already contains the path info. + + `subdomain` will default to the `default_subdomain` for this map if + no defined. If there is no `default_subdomain` you cannot use the + subdomain feature. + + .. versionchanged:: 1.0 + If ``url_scheme`` is ``ws`` or ``wss``, only WebSocket rules + will match. + + .. versionchanged:: 0.15 + ``path_info`` defaults to ``'/'`` if ``None``. + + .. versionchanged:: 0.8 + ``query_args`` can be a string. + + .. versionchanged:: 0.7 + Added ``query_args``. + """ + server_name = server_name.lower() + if self.host_matching: + if subdomain is not None: + raise RuntimeError("host matching enabled and a subdomain was provided") + elif subdomain is None: + subdomain = self.default_subdomain + if script_name is None: + script_name = "/" + if path_info is None: + path_info = "/" + + try: + server_name = _encode_idna(server_name) # type: ignore + except UnicodeError as e: + raise BadHost() from e + + return MapAdapter( + self, + server_name, + script_name, + subdomain, + url_scheme, + path_info, + default_method, + query_args, + ) + + def bind_to_environ( + self, + environ: t.Union["WSGIEnvironment", "Request"], + server_name: t.Optional[str] = None, + subdomain: t.Optional[str] = None, + ) -> "MapAdapter": + """Like :meth:`bind` but you can pass it an WSGI environment and it + will fetch the information from that dictionary. Note that because of + limitations in the protocol there is no way to get the current + subdomain and real `server_name` from the environment. If you don't + provide it, Werkzeug will use `SERVER_NAME` and `SERVER_PORT` (or + `HTTP_HOST` if provided) as used `server_name` with disabled subdomain + feature. + + If `subdomain` is `None` but an environment and a server name is + provided it will calculate the current subdomain automatically. + Example: `server_name` is ``'example.com'`` and the `SERVER_NAME` + in the wsgi `environ` is ``'staging.dev.example.com'`` the calculated + subdomain will be ``'staging.dev'``. + + If the object passed as environ has an environ attribute, the value of + this attribute is used instead. This allows you to pass request + objects. Additionally `PATH_INFO` added as a default of the + :class:`MapAdapter` so that you don't have to pass the path info to + the match method. + + .. versionchanged:: 1.0.0 + If the passed server name specifies port 443, it will match + if the incoming scheme is ``https`` without a port. + + .. versionchanged:: 1.0.0 + A warning is shown when the passed server name does not + match the incoming WSGI server name. + + .. versionchanged:: 0.8 + This will no longer raise a ValueError when an unexpected server + name was passed. + + .. versionchanged:: 0.5 + previously this method accepted a bogus `calculate_subdomain` + parameter that did not have any effect. It was removed because + of that. + + :param environ: a WSGI environment. + :param server_name: an optional server name hint (see above). + :param subdomain: optionally the current subdomain (see above). + """ + env = _get_environ(environ) + wsgi_server_name = get_host(env).lower() + scheme = env["wsgi.url_scheme"] + upgrade = any( + v.strip() == "upgrade" + for v in env.get("HTTP_CONNECTION", "").lower().split(",") + ) + + if upgrade and env.get("HTTP_UPGRADE", "").lower() == "websocket": + scheme = "wss" if scheme == "https" else "ws" + + if server_name is None: + server_name = wsgi_server_name + else: + server_name = server_name.lower() + + # strip standard port to match get_host() + if scheme in {"http", "ws"} and server_name.endswith(":80"): + server_name = server_name[:-3] + elif scheme in {"https", "wss"} and server_name.endswith(":443"): + server_name = server_name[:-4] + + if subdomain is None and not self.host_matching: + cur_server_name = wsgi_server_name.split(".") + real_server_name = server_name.split(".") + offset = -len(real_server_name) + + if cur_server_name[offset:] != real_server_name: + # This can happen even with valid configs if the server was + # accessed directly by IP address under some situations. + # Instead of raising an exception like in Werkzeug 0.7 or + # earlier we go by an invalid subdomain which will result + # in a 404 error on matching. + warnings.warn( + f"Current server name {wsgi_server_name!r} doesn't match configured" + f" server name {server_name!r}", + stacklevel=2, + ) + subdomain = "" + else: + subdomain = ".".join(filter(None, cur_server_name[:offset])) + + def _get_wsgi_string(name: str) -> t.Optional[str]: + val = env.get(name) + if val is not None: + return _wsgi_decoding_dance(val, self.charset) + return None + + script_name = _get_wsgi_string("SCRIPT_NAME") + path_info = _get_wsgi_string("PATH_INFO") + query_args = _get_wsgi_string("QUERY_STRING") + return Map.bind( + self, + server_name, + script_name, + subdomain, + scheme, + env["REQUEST_METHOD"], + path_info, + query_args=query_args, + ) + + def update(self) -> None: + """Called before matching and building to keep the compiled rules + in the correct order after things changed. + """ + if not self._remap: + return + + with self._remap_lock: + if not self._remap: + return + + self._matcher.update() + for rules in self._rules_by_endpoint.values(): + rules.sort(key=lambda x: x.build_compare_key()) + self._remap = False + + def __repr__(self) -> str: + rules = self.iter_rules() + return f"{type(self).__name__}({pformat(list(rules))})" + + +class MapAdapter: + + """Returned by :meth:`Map.bind` or :meth:`Map.bind_to_environ` and does + the URL matching and building based on runtime information. + """ + + def __init__( + self, + map: Map, + server_name: str, + script_name: str, + subdomain: t.Optional[str], + url_scheme: str, + path_info: str, + default_method: str, + query_args: t.Optional[t.Union[t.Mapping[str, t.Any], str]] = None, + ): + self.map = map + self.server_name = _to_str(server_name) + script_name = _to_str(script_name) + if not script_name.endswith("/"): + script_name += "/" + self.script_name = script_name + self.subdomain = _to_str(subdomain) + self.url_scheme = _to_str(url_scheme) + self.path_info = _to_str(path_info) + self.default_method = _to_str(default_method) + self.query_args = query_args + self.websocket = self.url_scheme in {"ws", "wss"} + + def dispatch( + self, + view_func: t.Callable[[str, t.Mapping[str, t.Any]], "WSGIApplication"], + path_info: t.Optional[str] = None, + method: t.Optional[str] = None, + catch_http_exceptions: bool = False, + ) -> "WSGIApplication": + """Does the complete dispatching process. `view_func` is called with + the endpoint and a dict with the values for the view. It should + look up the view function, call it, and return a response object + or WSGI application. http exceptions are not caught by default + so that applications can display nicer error messages by just + catching them by hand. If you want to stick with the default + error messages you can pass it ``catch_http_exceptions=True`` and + it will catch the http exceptions. + + Here a small example for the dispatch usage:: + + from werkzeug.wrappers import Request, Response + from werkzeug.wsgi import responder + from werkzeug.routing import Map, Rule + + def on_index(request): + return Response('Hello from the index') + + url_map = Map([Rule('/', endpoint='index')]) + views = {'index': on_index} + + @responder + def application(environ, start_response): + request = Request(environ) + urls = url_map.bind_to_environ(environ) + return urls.dispatch(lambda e, v: views[e](request, **v), + catch_http_exceptions=True) + + Keep in mind that this method might return exception objects, too, so + use :class:`Response.force_type` to get a response object. + + :param view_func: a function that is called with the endpoint as + first argument and the value dict as second. Has + to dispatch to the actual view function with this + information. (see above) + :param path_info: the path info to use for matching. Overrides the + path info specified on binding. + :param method: the HTTP method used for matching. Overrides the + method specified on binding. + :param catch_http_exceptions: set to `True` to catch any of the + werkzeug :class:`HTTPException`\\s. + """ + try: + try: + endpoint, args = self.match(path_info, method) + except RequestRedirect as e: + return e + return view_func(endpoint, args) + except HTTPException as e: + if catch_http_exceptions: + return e + raise + + @t.overload + def match( # type: ignore + self, + path_info: t.Optional[str] = None, + method: t.Optional[str] = None, + return_rule: "te.Literal[False]" = False, + query_args: t.Optional[t.Union[t.Mapping[str, t.Any], str]] = None, + websocket: t.Optional[bool] = None, + ) -> t.Tuple[str, t.Mapping[str, t.Any]]: + ... + + @t.overload + def match( + self, + path_info: t.Optional[str] = None, + method: t.Optional[str] = None, + return_rule: "te.Literal[True]" = True, + query_args: t.Optional[t.Union[t.Mapping[str, t.Any], str]] = None, + websocket: t.Optional[bool] = None, + ) -> t.Tuple[Rule, t.Mapping[str, t.Any]]: + ... + + def match( + self, + path_info: t.Optional[str] = None, + method: t.Optional[str] = None, + return_rule: bool = False, + query_args: t.Optional[t.Union[t.Mapping[str, t.Any], str]] = None, + websocket: t.Optional[bool] = None, + ) -> t.Tuple[t.Union[str, Rule], t.Mapping[str, t.Any]]: + """The usage is simple: you just pass the match method the current + path info as well as the method (which defaults to `GET`). The + following things can then happen: + + - you receive a `NotFound` exception that indicates that no URL is + matching. A `NotFound` exception is also a WSGI application you + can call to get a default page not found page (happens to be the + same object as `werkzeug.exceptions.NotFound`) + + - you receive a `MethodNotAllowed` exception that indicates that there + is a match for this URL but not for the current request method. + This is useful for RESTful applications. + + - you receive a `RequestRedirect` exception with a `new_url` + attribute. This exception is used to notify you about a request + Werkzeug requests from your WSGI application. This is for example the + case if you request ``/foo`` although the correct URL is ``/foo/`` + You can use the `RequestRedirect` instance as response-like object + similar to all other subclasses of `HTTPException`. + + - you receive a ``WebsocketMismatch`` exception if the only + match is a WebSocket rule but the bind is an HTTP request, or + if the match is an HTTP rule but the bind is a WebSocket + request. + + - you get a tuple in the form ``(endpoint, arguments)`` if there is + a match (unless `return_rule` is True, in which case you get a tuple + in the form ``(rule, arguments)``) + + If the path info is not passed to the match method the default path + info of the map is used (defaults to the root URL if not defined + explicitly). + + All of the exceptions raised are subclasses of `HTTPException` so they + can be used as WSGI responses. They will all render generic error or + redirect pages. + + Here is a small example for matching: + + >>> m = Map([ + ... Rule('/', endpoint='index'), + ... Rule('/downloads/', endpoint='downloads/index'), + ... Rule('/downloads/', endpoint='downloads/show') + ... ]) + >>> urls = m.bind("example.com", "/") + >>> urls.match("/", "GET") + ('index', {}) + >>> urls.match("/downloads/42") + ('downloads/show', {'id': 42}) + + And here is what happens on redirect and missing URLs: + + >>> urls.match("/downloads") + Traceback (most recent call last): + ... + RequestRedirect: http://example.com/downloads/ + >>> urls.match("/missing") + Traceback (most recent call last): + ... + NotFound: 404 Not Found + + :param path_info: the path info to use for matching. Overrides the + path info specified on binding. + :param method: the HTTP method used for matching. Overrides the + method specified on binding. + :param return_rule: return the rule that matched instead of just the + endpoint (defaults to `False`). + :param query_args: optional query arguments that are used for + automatic redirects as string or dictionary. It's + currently not possible to use the query arguments + for URL matching. + :param websocket: Match WebSocket instead of HTTP requests. A + websocket request has a ``ws`` or ``wss`` + :attr:`url_scheme`. This overrides that detection. + + .. versionadded:: 1.0 + Added ``websocket``. + + .. versionchanged:: 0.8 + ``query_args`` can be a string. + + .. versionadded:: 0.7 + Added ``query_args``. + + .. versionadded:: 0.6 + Added ``return_rule``. + """ + self.map.update() + if path_info is None: + path_info = self.path_info + else: + path_info = _to_str(path_info, self.map.charset) + if query_args is None: + query_args = self.query_args or {} + method = (method or self.default_method).upper() + + if websocket is None: + websocket = self.websocket + + domain_part = self.server_name if self.map.host_matching else self.subdomain + path_part = f"/{path_info.lstrip('/')}" if path_info else "" + + try: + result = self.map._matcher.match(domain_part, path_part, method, websocket) + except RequestPath as e: + raise RequestRedirect( + self.make_redirect_url( + url_quote(e.path_info, self.map.charset, safe="/:|+"), + query_args, + ) + ) from None + except RequestAliasRedirect as e: + raise RequestRedirect( + self.make_alias_redirect_url( + f"{domain_part}|{path_part}", + e.endpoint, + e.matched_values, + method, + query_args, + ) + ) from None + except NoMatch as e: + if e.have_match_for: + raise MethodNotAllowed(valid_methods=list(e.have_match_for)) from None + + if e.websocket_mismatch: + raise WebsocketMismatch() from None + + raise NotFound() from None + else: + rule, rv = result + + if self.map.redirect_defaults: + redirect_url = self.get_default_redirect(rule, method, rv, query_args) + if redirect_url is not None: + raise RequestRedirect(redirect_url) + + if rule.redirect_to is not None: + if isinstance(rule.redirect_to, str): + + def _handle_match(match: t.Match[str]) -> str: + value = rv[match.group(1)] + return rule._converters[match.group(1)].to_url(value) + + redirect_url = _simple_rule_re.sub(_handle_match, rule.redirect_to) + else: + redirect_url = rule.redirect_to(self, **rv) + + if self.subdomain: + netloc = f"{self.subdomain}.{self.server_name}" + else: + netloc = self.server_name + + raise RequestRedirect( + url_join( + f"{self.url_scheme or 'http'}://{netloc}{self.script_name}", + redirect_url, + ) + ) + + if return_rule: + return rule, rv + else: + return rule.endpoint, rv + + def test( + self, path_info: t.Optional[str] = None, method: t.Optional[str] = None + ) -> bool: + """Test if a rule would match. Works like `match` but returns `True` + if the URL matches, or `False` if it does not exist. + + :param path_info: the path info to use for matching. Overrides the + path info specified on binding. + :param method: the HTTP method used for matching. Overrides the + method specified on binding. + """ + try: + self.match(path_info, method) + except RequestRedirect: + pass + except HTTPException: + return False + return True + + def allowed_methods(self, path_info: t.Optional[str] = None) -> t.Iterable[str]: + """Returns the valid methods that match for a given path. + + .. versionadded:: 0.7 + """ + try: + self.match(path_info, method="--") + except MethodNotAllowed as e: + return e.valid_methods # type: ignore + except HTTPException: + pass + return [] + + def get_host(self, domain_part: t.Optional[str]) -> str: + """Figures out the full host name for the given domain part. The + domain part is a subdomain in case host matching is disabled or + a full host name. + """ + if self.map.host_matching: + if domain_part is None: + return self.server_name + return _to_str(domain_part, "ascii") + subdomain = domain_part + if subdomain is None: + subdomain = self.subdomain + else: + subdomain = _to_str(subdomain, "ascii") + + if subdomain: + return f"{subdomain}.{self.server_name}" + else: + return self.server_name + + def get_default_redirect( + self, + rule: Rule, + method: str, + values: t.MutableMapping[str, t.Any], + query_args: t.Union[t.Mapping[str, t.Any], str], + ) -> t.Optional[str]: + """A helper that returns the URL to redirect to if it finds one. + This is used for default redirecting only. + + :internal: + """ + assert self.map.redirect_defaults + for r in self.map._rules_by_endpoint[rule.endpoint]: + # every rule that comes after this one, including ourself + # has a lower priority for the defaults. We order the ones + # with the highest priority up for building. + if r is rule: + break + if r.provides_defaults_for(rule) and r.suitable_for(values, method): + values.update(r.defaults) # type: ignore + domain_part, path = r.build(values) # type: ignore + return self.make_redirect_url(path, query_args, domain_part=domain_part) + return None + + def encode_query_args(self, query_args: t.Union[t.Mapping[str, t.Any], str]) -> str: + if not isinstance(query_args, str): + return url_encode(query_args, self.map.charset) + return query_args + + def make_redirect_url( + self, + path_info: str, + query_args: t.Optional[t.Union[t.Mapping[str, t.Any], str]] = None, + domain_part: t.Optional[str] = None, + ) -> str: + """Creates a redirect URL. + + :internal: + """ + if query_args: + suffix = f"?{self.encode_query_args(query_args)}" + else: + suffix = "" + + scheme = self.url_scheme or "http" + host = self.get_host(domain_part) + path = posixpath.join(self.script_name.strip("/"), path_info.lstrip("/")) + return f"{scheme}://{host}/{path}{suffix}" + + def make_alias_redirect_url( + self, + path: str, + endpoint: str, + values: t.Mapping[str, t.Any], + method: str, + query_args: t.Union[t.Mapping[str, t.Any], str], + ) -> str: + """Internally called to make an alias redirect URL.""" + url = self.build( + endpoint, values, method, append_unknown=False, force_external=True + ) + if query_args: + url += f"?{self.encode_query_args(query_args)}" + assert url != path, "detected invalid alias setting. No canonical URL found" + return url + + def _partial_build( + self, + endpoint: str, + values: t.Mapping[str, t.Any], + method: t.Optional[str], + append_unknown: bool, + ) -> t.Optional[t.Tuple[str, str, bool]]: + """Helper for :meth:`build`. Returns subdomain and path for the + rule that accepts this endpoint, values and method. + + :internal: + """ + # in case the method is none, try with the default method first + if method is None: + rv = self._partial_build( + endpoint, values, self.default_method, append_unknown + ) + if rv is not None: + return rv + + # Default method did not match or a specific method is passed. + # Check all for first match with matching host. If no matching + # host is found, go with first result. + first_match = None + + for rule in self.map._rules_by_endpoint.get(endpoint, ()): + if rule.suitable_for(values, method): + build_rv = rule.build(values, append_unknown) + + if build_rv is not None: + rv = (build_rv[0], build_rv[1], rule.websocket) + if self.map.host_matching: + if rv[0] == self.server_name: + return rv + elif first_match is None: + first_match = rv + else: + return rv + + return first_match + + def build( + self, + endpoint: str, + values: t.Optional[t.Mapping[str, t.Any]] = None, + method: t.Optional[str] = None, + force_external: bool = False, + append_unknown: bool = True, + url_scheme: t.Optional[str] = None, + ) -> str: + """Building URLs works pretty much the other way round. Instead of + `match` you call `build` and pass it the endpoint and a dict of + arguments for the placeholders. + + The `build` function also accepts an argument called `force_external` + which, if you set it to `True` will force external URLs. Per default + external URLs (include the server name) will only be used if the + target URL is on a different subdomain. + + >>> m = Map([ + ... Rule('/', endpoint='index'), + ... Rule('/downloads/', endpoint='downloads/index'), + ... Rule('/downloads/', endpoint='downloads/show') + ... ]) + >>> urls = m.bind("example.com", "/") + >>> urls.build("index", {}) + '/' + >>> urls.build("downloads/show", {'id': 42}) + '/downloads/42' + >>> urls.build("downloads/show", {'id': 42}, force_external=True) + 'http://example.com/downloads/42' + + Because URLs cannot contain non ASCII data you will always get + bytes back. Non ASCII characters are urlencoded with the + charset defined on the map instance. + + Additional values are converted to strings and appended to the URL as + URL querystring parameters: + + >>> urls.build("index", {'q': 'My Searchstring'}) + '/?q=My+Searchstring' + + When processing those additional values, lists are furthermore + interpreted as multiple values (as per + :py:class:`werkzeug.datastructures.MultiDict`): + + >>> urls.build("index", {'q': ['a', 'b', 'c']}) + '/?q=a&q=b&q=c' + + Passing a ``MultiDict`` will also add multiple values: + + >>> urls.build("index", MultiDict((('p', 'z'), ('q', 'a'), ('q', 'b')))) + '/?p=z&q=a&q=b' + + If a rule does not exist when building a `BuildError` exception is + raised. + + The build method accepts an argument called `method` which allows you + to specify the method you want to have an URL built for if you have + different methods for the same endpoint specified. + + :param endpoint: the endpoint of the URL to build. + :param values: the values for the URL to build. Unhandled values are + appended to the URL as query parameters. + :param method: the HTTP method for the rule if there are different + URLs for different methods on the same endpoint. + :param force_external: enforce full canonical external URLs. If the URL + scheme is not provided, this will generate + a protocol-relative URL. + :param append_unknown: unknown parameters are appended to the generated + URL as query string argument. Disable this + if you want the builder to ignore those. + :param url_scheme: Scheme to use in place of the bound + :attr:`url_scheme`. + + .. versionchanged:: 2.0 + Added the ``url_scheme`` parameter. + + .. versionadded:: 0.6 + Added the ``append_unknown`` parameter. + """ + self.map.update() + + if values: + if isinstance(values, MultiDict): + values = { + k: (v[0] if len(v) == 1 else v) + for k, v in dict.items(values) + if len(v) != 0 + } + else: # plain dict + values = {k: v for k, v in values.items() if v is not None} + else: + values = {} + + rv = self._partial_build(endpoint, values, method, append_unknown) + if rv is None: + raise BuildError(endpoint, values, method, self) + + domain_part, path, websocket = rv + host = self.get_host(domain_part) + + if url_scheme is None: + url_scheme = self.url_scheme + + # Always build WebSocket routes with the scheme (browsers + # require full URLs). If bound to a WebSocket, ensure that HTTP + # routes are built with an HTTP scheme. + secure = url_scheme in {"https", "wss"} + + if websocket: + force_external = True + url_scheme = "wss" if secure else "ws" + elif url_scheme: + url_scheme = "https" if secure else "http" + + # shortcut this. + if not force_external and ( + (self.map.host_matching and host == self.server_name) + or (not self.map.host_matching and domain_part == self.subdomain) + ): + return f"{self.script_name.rstrip('/')}/{path.lstrip('/')}" + + scheme = f"{url_scheme}:" if url_scheme else "" + return f"{scheme}//{host}{self.script_name[:-1]}/{path.lstrip('/')}" diff --git a/.vtodo/Lib/site-packages/werkzeug/routing/matcher.py b/.vtodo/Lib/site-packages/werkzeug/routing/matcher.py new file mode 100644 index 0000000..d22b05a --- /dev/null +++ b/.vtodo/Lib/site-packages/werkzeug/routing/matcher.py @@ -0,0 +1,185 @@ +import re +import typing as t +from dataclasses import dataclass +from dataclasses import field + +from .converters import ValidationError +from .exceptions import NoMatch +from .exceptions import RequestAliasRedirect +from .exceptions import RequestPath +from .rules import Rule +from .rules import RulePart + + +class SlashRequired(Exception): + pass + + +@dataclass +class State: + """A representation of a rule state. + + This includes the *rules* that correspond to the state and the + possible *static* and *dynamic* transitions to the next state. + """ + + dynamic: t.List[t.Tuple[RulePart, "State"]] = field(default_factory=list) + rules: t.List[Rule] = field(default_factory=list) + static: t.Dict[str, "State"] = field(default_factory=dict) + + +class StateMachineMatcher: + def __init__(self, merge_slashes: bool) -> None: + self._root = State() + self.merge_slashes = merge_slashes + + def add(self, rule: Rule) -> None: + state = self._root + for part in rule._parts: + if part.static: + state.static.setdefault(part.content, State()) + state = state.static[part.content] + else: + for test_part, new_state in state.dynamic: + if test_part == part: + state = new_state + break + else: + new_state = State() + state.dynamic.append((part, new_state)) + state = new_state + state.rules.append(rule) + + def update(self) -> None: + # For every state the dynamic transitions should be sorted by + # the weight of the transition + state = self._root + + def _update_state(state: State) -> None: + state.dynamic.sort(key=lambda entry: entry[0].weight) + for new_state in state.static.values(): + _update_state(new_state) + for _, new_state in state.dynamic: + _update_state(new_state) + + _update_state(state) + + def match( + self, domain: str, path: str, method: str, websocket: bool + ) -> t.Tuple[Rule, t.MutableMapping[str, t.Any]]: + # To match to a rule we need to start at the root state and + # try to follow the transitions until we find a match, or find + # there is no transition to follow. + + have_match_for = set() + websocket_mismatch = False + + def _match( + state: State, parts: t.List[str], values: t.List[str] + ) -> t.Optional[t.Tuple[Rule, t.List[str]]]: + # This function is meant to be called recursively, and will attempt + # to match the head part to the state's transitions. + nonlocal have_match_for, websocket_mismatch + + # The base case is when all parts have been matched via + # transitions. Hence if there is a rule with methods & + # websocket that work return it and the dynamic values + # extracted. + if parts == []: + for rule in state.rules: + if rule.methods is not None and method not in rule.methods: + have_match_for.update(rule.methods) + elif rule.websocket != websocket: + websocket_mismatch = True + else: + return rule, values + + # Test if there is a match with this path with a + # trailing slash, if so raise an exception to report + # that matching is possible with an additional slash + if "" in state.static: + for rule in state.static[""].rules: + if websocket == rule.websocket and ( + rule.methods is None or method in rule.methods + ): + if rule.strict_slashes: + raise SlashRequired() + else: + return rule, values + return None + + part = parts[0] + # To match this part try the static transitions first + if part in state.static: + rv = _match(state.static[part], parts[1:], values) + if rv is not None: + return rv + # No match via the static transitions, so try the dynamic + # ones. + for test_part, new_state in state.dynamic: + target = part + remaining = parts[1:] + # A final part indicates a transition that always + # consumes the remaining parts i.e. transitions to a + # final state. + if test_part.final: + target = "/".join(parts) + remaining = [] + match = re.compile(test_part.content).match(target) + if match is not None: + rv = _match(new_state, remaining, values + list(match.groups())) + if rv is not None: + return rv + + # If there is no match and the only part left is a + # trailing slash ("") consider rules that aren't + # strict-slashes as these should match if there is a final + # slash part. + if parts == [""]: + for rule in state.rules: + if rule.strict_slashes: + continue + if rule.methods is not None and method not in rule.methods: + have_match_for.update(rule.methods) + elif rule.websocket != websocket: + websocket_mismatch = True + else: + return rule, values + + return None + + try: + rv = _match(self._root, [domain, *path.split("/")], []) + except SlashRequired: + raise RequestPath(f"{path}/") from None + + if self.merge_slashes and rv is None: + # Try to match again, but with slashes merged + path = re.sub("/{2,}?", "/", path) + try: + rv = _match(self._root, [domain, *path.split("/")], []) + except SlashRequired: + raise RequestPath(f"{path}/") from None + if rv is None: + raise NoMatch(have_match_for, websocket_mismatch) + else: + raise RequestPath(f"{path}") + elif rv is not None: + rule, values = rv + + result = {} + for name, value in zip(rule._converters.keys(), values): + try: + value = rule._converters[name].to_python(value) + except ValidationError: + raise NoMatch(have_match_for, websocket_mismatch) from None + result[str(name)] = value + if rule.defaults: + result.update(rule.defaults) + + if rule.alias and rule.map.redirect_defaults: + raise RequestAliasRedirect(result, rule.endpoint) + + return rule, result + + raise NoMatch(have_match_for, websocket_mismatch) diff --git a/.vtodo/Lib/site-packages/werkzeug/routing/rules.py b/.vtodo/Lib/site-packages/werkzeug/routing/rules.py new file mode 100644 index 0000000..a61717a --- /dev/null +++ b/.vtodo/Lib/site-packages/werkzeug/routing/rules.py @@ -0,0 +1,879 @@ +import ast +import re +import typing as t +from dataclasses import dataclass +from string import Template +from types import CodeType + +from .._internal import _to_bytes +from ..urls import url_encode +from ..urls import url_quote +from .converters import ValidationError + +if t.TYPE_CHECKING: + from .converters import BaseConverter + from .map import Map + + +class Weighting(t.NamedTuple): + number_static_weights: int + static_weights: t.List[t.Tuple[int, int]] + number_argument_weights: int + argument_weights: t.List[int] + + +@dataclass +class RulePart: + """A part of a rule. + + Rules can be represented by parts as delimited by `/` with + instances of this class representing those parts. The *content* is + either the raw content if *static* or a regex string to match + against. The *weight* can be used to order parts when matching. + + """ + + content: str + final: bool + static: bool + weight: Weighting + + +_part_re = re.compile( + r""" + (?: + (?P\/) # a slash + | + (?P[^<\/]+) # static rule data + | + (?: + < + (?: + (?P[a-zA-Z_][a-zA-Z0-9_]*) # converter name + (?:\((?P.*?)\))? # converter arguments + \: # variable delimiter + )? + (?P[a-zA-Z_][a-zA-Z0-9_]*) # variable name + > + ) + ) + """, + re.VERBOSE, +) + +_simple_rule_re = re.compile(r"<([^>]+)>") +_converter_args_re = re.compile( + r""" + ((?P\w+)\s*=\s*)? + (?P + True|False| + \d+.\d+| + \d+.| + \d+| + [\w\d_.]+| + [urUR]?(?P"[^"]*?"|'[^']*') + )\s*, + """, + re.VERBOSE, +) + + +_PYTHON_CONSTANTS = {"None": None, "True": True, "False": False} + + +def _find(value: str, target: str, pos: int) -> int: + """Find the *target* in *value* after *pos*. + + Returns the *value* length if *target* isn't found. + """ + try: + return value.index(target, pos) + except ValueError: + return len(value) + + +def _pythonize(value: str) -> t.Union[None, bool, int, float, str]: + if value in _PYTHON_CONSTANTS: + return _PYTHON_CONSTANTS[value] + for convert in int, float: + try: + return convert(value) # type: ignore + except ValueError: + pass + if value[:1] == value[-1:] and value[0] in "\"'": + value = value[1:-1] + return str(value) + + +def parse_converter_args(argstr: str) -> t.Tuple[t.Tuple, t.Dict[str, t.Any]]: + argstr += "," + args = [] + kwargs = {} + + for item in _converter_args_re.finditer(argstr): + value = item.group("stringval") + if value is None: + value = item.group("value") + value = _pythonize(value) + if not item.group("name"): + args.append(value) + else: + name = item.group("name") + kwargs[name] = value + + return tuple(args), kwargs + + +class RuleFactory: + """As soon as you have more complex URL setups it's a good idea to use rule + factories to avoid repetitive tasks. Some of them are builtin, others can + be added by subclassing `RuleFactory` and overriding `get_rules`. + """ + + def get_rules(self, map: "Map") -> t.Iterable["Rule"]: + """Subclasses of `RuleFactory` have to override this method and return + an iterable of rules.""" + raise NotImplementedError() + + +class Subdomain(RuleFactory): + """All URLs provided by this factory have the subdomain set to a + specific domain. For example if you want to use the subdomain for + the current language this can be a good setup:: + + url_map = Map([ + Rule('/', endpoint='#select_language'), + Subdomain('', [ + Rule('/', endpoint='index'), + Rule('/about', endpoint='about'), + Rule('/help', endpoint='help') + ]) + ]) + + All the rules except for the ``'#select_language'`` endpoint will now + listen on a two letter long subdomain that holds the language code + for the current request. + """ + + def __init__(self, subdomain: str, rules: t.Iterable[RuleFactory]) -> None: + self.subdomain = subdomain + self.rules = rules + + def get_rules(self, map: "Map") -> t.Iterator["Rule"]: + for rulefactory in self.rules: + for rule in rulefactory.get_rules(map): + rule = rule.empty() + rule.subdomain = self.subdomain + yield rule + + +class Submount(RuleFactory): + """Like `Subdomain` but prefixes the URL rule with a given string:: + + url_map = Map([ + Rule('/', endpoint='index'), + Submount('/blog', [ + Rule('/', endpoint='blog/index'), + Rule('/entry/', endpoint='blog/show') + ]) + ]) + + Now the rule ``'blog/show'`` matches ``/blog/entry/``. + """ + + def __init__(self, path: str, rules: t.Iterable[RuleFactory]) -> None: + self.path = path.rstrip("/") + self.rules = rules + + def get_rules(self, map: "Map") -> t.Iterator["Rule"]: + for rulefactory in self.rules: + for rule in rulefactory.get_rules(map): + rule = rule.empty() + rule.rule = self.path + rule.rule + yield rule + + +class EndpointPrefix(RuleFactory): + """Prefixes all endpoints (which must be strings for this factory) with + another string. This can be useful for sub applications:: + + url_map = Map([ + Rule('/', endpoint='index'), + EndpointPrefix('blog/', [Submount('/blog', [ + Rule('/', endpoint='index'), + Rule('/entry/', endpoint='show') + ])]) + ]) + """ + + def __init__(self, prefix: str, rules: t.Iterable[RuleFactory]) -> None: + self.prefix = prefix + self.rules = rules + + def get_rules(self, map: "Map") -> t.Iterator["Rule"]: + for rulefactory in self.rules: + for rule in rulefactory.get_rules(map): + rule = rule.empty() + rule.endpoint = self.prefix + rule.endpoint + yield rule + + +class RuleTemplate: + """Returns copies of the rules wrapped and expands string templates in + the endpoint, rule, defaults or subdomain sections. + + Here a small example for such a rule template:: + + from werkzeug.routing import Map, Rule, RuleTemplate + + resource = RuleTemplate([ + Rule('/$name/', endpoint='$name.list'), + Rule('/$name/', endpoint='$name.show') + ]) + + url_map = Map([resource(name='user'), resource(name='page')]) + + When a rule template is called the keyword arguments are used to + replace the placeholders in all the string parameters. + """ + + def __init__(self, rules: t.Iterable["Rule"]) -> None: + self.rules = list(rules) + + def __call__(self, *args: t.Any, **kwargs: t.Any) -> "RuleTemplateFactory": + return RuleTemplateFactory(self.rules, dict(*args, **kwargs)) + + +class RuleTemplateFactory(RuleFactory): + """A factory that fills in template variables into rules. Used by + `RuleTemplate` internally. + + :internal: + """ + + def __init__( + self, rules: t.Iterable[RuleFactory], context: t.Dict[str, t.Any] + ) -> None: + self.rules = rules + self.context = context + + def get_rules(self, map: "Map") -> t.Iterator["Rule"]: + for rulefactory in self.rules: + for rule in rulefactory.get_rules(map): + new_defaults = subdomain = None + if rule.defaults: + new_defaults = {} + for key, value in rule.defaults.items(): + if isinstance(value, str): + value = Template(value).substitute(self.context) + new_defaults[key] = value + if rule.subdomain is not None: + subdomain = Template(rule.subdomain).substitute(self.context) + new_endpoint = rule.endpoint + if isinstance(new_endpoint, str): + new_endpoint = Template(new_endpoint).substitute(self.context) + yield Rule( + Template(rule.rule).substitute(self.context), + new_defaults, + subdomain, + rule.methods, + rule.build_only, + new_endpoint, + rule.strict_slashes, + ) + + +def _prefix_names(src: str) -> ast.stmt: + """ast parse and prefix names with `.` to avoid collision with user vars""" + tree = ast.parse(src).body[0] + if isinstance(tree, ast.Expr): + tree = tree.value # type: ignore + for node in ast.walk(tree): + if isinstance(node, ast.Name): + node.id = f".{node.id}" + return tree + + +_CALL_CONVERTER_CODE_FMT = "self._converters[{elem!r}].to_url()" +_IF_KWARGS_URL_ENCODE_CODE = """\ +if kwargs: + params = self._encode_query_vars(kwargs) + q = "?" if params else "" +else: + q = params = "" +""" +_IF_KWARGS_URL_ENCODE_AST = _prefix_names(_IF_KWARGS_URL_ENCODE_CODE) +_URL_ENCODE_AST_NAMES = (_prefix_names("q"), _prefix_names("params")) + + +class Rule(RuleFactory): + """A Rule represents one URL pattern. There are some options for `Rule` + that change the way it behaves and are passed to the `Rule` constructor. + Note that besides the rule-string all arguments *must* be keyword arguments + in order to not break the application on Werkzeug upgrades. + + `string` + Rule strings basically are just normal URL paths with placeholders in + the format ```` where the converter and the + arguments are optional. If no converter is defined the `default` + converter is used which means `string` in the normal configuration. + + URL rules that end with a slash are branch URLs, others are leaves. + If you have `strict_slashes` enabled (which is the default), all + branch URLs that are matched without a trailing slash will trigger a + redirect to the same URL with the missing slash appended. + + The converters are defined on the `Map`. + + `endpoint` + The endpoint for this rule. This can be anything. A reference to a + function, a string, a number etc. The preferred way is using a string + because the endpoint is used for URL generation. + + `defaults` + An optional dict with defaults for other rules with the same endpoint. + This is a bit tricky but useful if you want to have unique URLs:: + + url_map = Map([ + Rule('/all/', defaults={'page': 1}, endpoint='all_entries'), + Rule('/all/page/', endpoint='all_entries') + ]) + + If a user now visits ``http://example.com/all/page/1`` they will be + redirected to ``http://example.com/all/``. If `redirect_defaults` is + disabled on the `Map` instance this will only affect the URL + generation. + + `subdomain` + The subdomain rule string for this rule. If not specified the rule + only matches for the `default_subdomain` of the map. If the map is + not bound to a subdomain this feature is disabled. + + Can be useful if you want to have user profiles on different subdomains + and all subdomains are forwarded to your application:: + + url_map = Map([ + Rule('/', subdomain='', endpoint='user/homepage'), + Rule('/stats', subdomain='', endpoint='user/stats') + ]) + + `methods` + A sequence of http methods this rule applies to. If not specified, all + methods are allowed. For example this can be useful if you want different + endpoints for `POST` and `GET`. If methods are defined and the path + matches but the method matched against is not in this list or in the + list of another rule for that path the error raised is of the type + `MethodNotAllowed` rather than `NotFound`. If `GET` is present in the + list of methods and `HEAD` is not, `HEAD` is added automatically. + + `strict_slashes` + Override the `Map` setting for `strict_slashes` only for this rule. If + not specified the `Map` setting is used. + + `merge_slashes` + Override :attr:`Map.merge_slashes` for this rule. + + `build_only` + Set this to True and the rule will never match but will create a URL + that can be build. This is useful if you have resources on a subdomain + or folder that are not handled by the WSGI application (like static data) + + `redirect_to` + If given this must be either a string or callable. In case of a + callable it's called with the url adapter that triggered the match and + the values of the URL as keyword arguments and has to return the target + for the redirect, otherwise it has to be a string with placeholders in + rule syntax:: + + def foo_with_slug(adapter, id): + # ask the database for the slug for the old id. this of + # course has nothing to do with werkzeug. + return f'foo/{Foo.get_slug_for_id(id)}' + + url_map = Map([ + Rule('/foo/', endpoint='foo'), + Rule('/some/old/url/', redirect_to='foo/'), + Rule('/other/old/url/', redirect_to=foo_with_slug) + ]) + + When the rule is matched the routing system will raise a + `RequestRedirect` exception with the target for the redirect. + + Keep in mind that the URL will be joined against the URL root of the + script so don't use a leading slash on the target URL unless you + really mean root of that domain. + + `alias` + If enabled this rule serves as an alias for another rule with the same + endpoint and arguments. + + `host` + If provided and the URL map has host matching enabled this can be + used to provide a match rule for the whole host. This also means + that the subdomain feature is disabled. + + `websocket` + If ``True``, this rule is only matches for WebSocket (``ws://``, + ``wss://``) requests. By default, rules will only match for HTTP + requests. + + .. versionchanged:: 2.1 + Percent-encoded newlines (``%0a``), which are decoded by WSGI + servers, are considered when routing instead of terminating the + match early. + + .. versionadded:: 1.0 + Added ``websocket``. + + .. versionadded:: 1.0 + Added ``merge_slashes``. + + .. versionadded:: 0.7 + Added ``alias`` and ``host``. + + .. versionchanged:: 0.6.1 + ``HEAD`` is added to ``methods`` if ``GET`` is present. + """ + + def __init__( + self, + string: str, + defaults: t.Optional[t.Mapping[str, t.Any]] = None, + subdomain: t.Optional[str] = None, + methods: t.Optional[t.Iterable[str]] = None, + build_only: bool = False, + endpoint: t.Optional[str] = None, + strict_slashes: t.Optional[bool] = None, + merge_slashes: t.Optional[bool] = None, + redirect_to: t.Optional[t.Union[str, t.Callable[..., str]]] = None, + alias: bool = False, + host: t.Optional[str] = None, + websocket: bool = False, + ) -> None: + if not string.startswith("/"): + raise ValueError("urls must start with a leading slash") + self.rule = string + self.is_leaf = not string.endswith("/") + self.is_branch = string.endswith("/") + + self.map: "Map" = None # type: ignore + self.strict_slashes = strict_slashes + self.merge_slashes = merge_slashes + self.subdomain = subdomain + self.host = host + self.defaults = defaults + self.build_only = build_only + self.alias = alias + self.websocket = websocket + + if methods is not None: + if isinstance(methods, str): + raise TypeError("'methods' should be a list of strings.") + + methods = {x.upper() for x in methods} + + if "HEAD" not in methods and "GET" in methods: + methods.add("HEAD") + + if websocket and methods - {"GET", "HEAD", "OPTIONS"}: + raise ValueError( + "WebSocket rules can only use 'GET', 'HEAD', and 'OPTIONS' methods." + ) + + self.methods = methods + self.endpoint: str = endpoint # type: ignore + self.redirect_to = redirect_to + + if defaults: + self.arguments = set(map(str, defaults)) + else: + self.arguments = set() + + self._converters: t.Dict[str, "BaseConverter"] = {} + self._trace: t.List[t.Tuple[bool, str]] = [] + self._parts: t.List[RulePart] = [] + + def empty(self) -> "Rule": + """ + Return an unbound copy of this rule. + + This can be useful if want to reuse an already bound URL for another + map. See ``get_empty_kwargs`` to override what keyword arguments are + provided to the new copy. + """ + return type(self)(self.rule, **self.get_empty_kwargs()) + + def get_empty_kwargs(self) -> t.Mapping[str, t.Any]: + """ + Provides kwargs for instantiating empty copy with empty() + + Use this method to provide custom keyword arguments to the subclass of + ``Rule`` when calling ``some_rule.empty()``. Helpful when the subclass + has custom keyword arguments that are needed at instantiation. + + Must return a ``dict`` that will be provided as kwargs to the new + instance of ``Rule``, following the initial ``self.rule`` value which + is always provided as the first, required positional argument. + """ + defaults = None + if self.defaults: + defaults = dict(self.defaults) + return dict( + defaults=defaults, + subdomain=self.subdomain, + methods=self.methods, + build_only=self.build_only, + endpoint=self.endpoint, + strict_slashes=self.strict_slashes, + redirect_to=self.redirect_to, + alias=self.alias, + host=self.host, + ) + + def get_rules(self, map: "Map") -> t.Iterator["Rule"]: + yield self + + def refresh(self) -> None: + """Rebinds and refreshes the URL. Call this if you modified the + rule in place. + + :internal: + """ + self.bind(self.map, rebind=True) + + def bind(self, map: "Map", rebind: bool = False) -> None: + """Bind the url to a map and create a regular expression based on + the information from the rule itself and the defaults from the map. + + :internal: + """ + if self.map is not None and not rebind: + raise RuntimeError(f"url rule {self!r} already bound to map {self.map!r}") + self.map = map + if self.strict_slashes is None: + self.strict_slashes = map.strict_slashes + if self.merge_slashes is None: + self.merge_slashes = map.merge_slashes + if self.subdomain is None: + self.subdomain = map.default_subdomain + self.compile() + + def get_converter( + self, + variable_name: str, + converter_name: str, + args: t.Tuple, + kwargs: t.Mapping[str, t.Any], + ) -> "BaseConverter": + """Looks up the converter for the given parameter. + + .. versionadded:: 0.9 + """ + if converter_name not in self.map.converters: + raise LookupError(f"the converter {converter_name!r} does not exist") + return self.map.converters[converter_name](self.map, *args, **kwargs) + + def _encode_query_vars(self, query_vars: t.Mapping[str, t.Any]) -> str: + return url_encode( + query_vars, + charset=self.map.charset, + sort=self.map.sort_parameters, + key=self.map.sort_key, + ) + + def _parse_rule(self, rule: str) -> t.Iterable[RulePart]: + content = "" + static = True + argument_weights = [] + static_weights: t.List[t.Tuple[int, int]] = [] + final = False + + pos = 0 + while pos < len(rule): + match = _part_re.match(rule, pos) + if match is None: + raise ValueError(f"malformed url rule: {rule!r}") + + data = match.groupdict() + if data["static"] is not None: + static_weights.append((len(static_weights), -len(data["static"]))) + self._trace.append((False, data["static"])) + content += data["static"] if static else re.escape(data["static"]) + + if data["variable"] is not None: + if static: + # Switching content to represent regex, hence the need to escape + content = re.escape(content) + static = False + c_args, c_kwargs = parse_converter_args(data["arguments"] or "") + convobj = self.get_converter( + data["variable"], data["converter"] or "default", c_args, c_kwargs + ) + self._converters[data["variable"]] = convobj + self.arguments.add(data["variable"]) + if not convobj.part_isolating: + final = True + content += f"({convobj.regex})" + argument_weights.append(convobj.weight) + self._trace.append((True, data["variable"])) + + if data["slash"] is not None: + self._trace.append((False, "/")) + if final: + content += "/" + else: + if not static: + content += r"\Z" + weight = Weighting( + -len(static_weights), + static_weights, + -len(argument_weights), + argument_weights, + ) + yield RulePart( + content=content, final=final, static=static, weight=weight + ) + content = "" + static = True + argument_weights = [] + static_weights = [] + final = False + + pos = match.end() + + if not static: + content += r"\Z" + weight = Weighting( + -len(static_weights), + static_weights, + -len(argument_weights), + argument_weights, + ) + yield RulePart(content=content, final=final, static=static, weight=weight) + + def compile(self) -> None: + """Compiles the regular expression and stores it.""" + assert self.map is not None, "rule not bound" + + if self.map.host_matching: + domain_rule = self.host or "" + else: + domain_rule = self.subdomain or "" + self._parts = [] + self._trace = [] + self._converters = {} + if domain_rule == "": + self._parts = [ + RulePart( + content="", final=False, static=True, weight=Weighting(0, [], 0, []) + ) + ] + else: + self._parts.extend(self._parse_rule(domain_rule)) + self._trace.append((False, "|")) + rule = self.rule + if self.merge_slashes: + rule = re.sub("/{2,}?", "/", self.rule) + self._parts.extend(self._parse_rule(rule)) + + self._build: t.Callable[..., t.Tuple[str, str]] + self._build = self._compile_builder(False).__get__(self, None) + self._build_unknown: t.Callable[..., t.Tuple[str, str]] + self._build_unknown = self._compile_builder(True).__get__(self, None) + + @staticmethod + def _get_func_code(code: CodeType, name: str) -> t.Callable[..., t.Tuple[str, str]]: + globs: t.Dict[str, t.Any] = {} + locs: t.Dict[str, t.Any] = {} + exec(code, globs, locs) + return locs[name] # type: ignore + + def _compile_builder( + self, append_unknown: bool = True + ) -> t.Callable[..., t.Tuple[str, str]]: + defaults = self.defaults or {} + dom_ops: t.List[t.Tuple[bool, str]] = [] + url_ops: t.List[t.Tuple[bool, str]] = [] + + opl = dom_ops + for is_dynamic, data in self._trace: + if data == "|" and opl is dom_ops: + opl = url_ops + continue + # this seems like a silly case to ever come up but: + # if a default is given for a value that appears in the rule, + # resolve it to a constant ahead of time + if is_dynamic and data in defaults: + data = self._converters[data].to_url(defaults[data]) + opl.append((False, data)) + elif not is_dynamic: + opl.append( + (False, url_quote(_to_bytes(data, self.map.charset), safe="/:|+")) + ) + else: + opl.append((True, data)) + + def _convert(elem: str) -> ast.stmt: + ret = _prefix_names(_CALL_CONVERTER_CODE_FMT.format(elem=elem)) + ret.args = [ast.Name(str(elem), ast.Load())] # type: ignore # str for py2 + return ret + + def _parts(ops: t.List[t.Tuple[bool, str]]) -> t.List[ast.AST]: + parts = [ + _convert(elem) if is_dynamic else ast.Str(s=elem) + for is_dynamic, elem in ops + ] + parts = parts or [ast.Str("")] + # constant fold + ret = [parts[0]] + for p in parts[1:]: + if isinstance(p, ast.Str) and isinstance(ret[-1], ast.Str): + ret[-1] = ast.Str(ret[-1].s + p.s) + else: + ret.append(p) + return ret + + dom_parts = _parts(dom_ops) + url_parts = _parts(url_ops) + if not append_unknown: + body = [] + else: + body = [_IF_KWARGS_URL_ENCODE_AST] + url_parts.extend(_URL_ENCODE_AST_NAMES) + + def _join(parts: t.List[ast.AST]) -> ast.AST: + if len(parts) == 1: # shortcut + return parts[0] + return ast.JoinedStr(parts) + + body.append( + ast.Return(ast.Tuple([_join(dom_parts), _join(url_parts)], ast.Load())) + ) + + pargs = [ + elem + for is_dynamic, elem in dom_ops + url_ops + if is_dynamic and elem not in defaults + ] + kargs = [str(k) for k in defaults] + + func_ast: ast.FunctionDef = _prefix_names("def _(): pass") # type: ignore + func_ast.name = f"" + func_ast.args.args.append(ast.arg(".self", None)) + for arg in pargs + kargs: + func_ast.args.args.append(ast.arg(arg, None)) + func_ast.args.kwarg = ast.arg(".kwargs", None) + for _ in kargs: + func_ast.args.defaults.append(ast.Str("")) + func_ast.body = body + + # use `ast.parse` instead of `ast.Module` for better portability + # Python 3.8 changes the signature of `ast.Module` + module = ast.parse("") + module.body = [func_ast] + + # mark everything as on line 1, offset 0 + # less error-prone than `ast.fix_missing_locations` + # bad line numbers cause an assert to fail in debug builds + for node in ast.walk(module): + if "lineno" in node._attributes: + node.lineno = 1 + if "end_lineno" in node._attributes: + node.end_lineno = node.lineno # type: ignore[attr-defined] + if "col_offset" in node._attributes: + node.col_offset = 0 + if "end_col_offset" in node._attributes: + node.end_col_offset = node.col_offset # type: ignore[attr-defined] + + code = compile(module, "", "exec") + return self._get_func_code(code, func_ast.name) + + def build( + self, values: t.Mapping[str, t.Any], append_unknown: bool = True + ) -> t.Optional[t.Tuple[str, str]]: + """Assembles the relative url for that rule and the subdomain. + If building doesn't work for some reasons `None` is returned. + + :internal: + """ + try: + if append_unknown: + return self._build_unknown(**values) + else: + return self._build(**values) + except ValidationError: + return None + + def provides_defaults_for(self, rule: "Rule") -> bool: + """Check if this rule has defaults for a given rule. + + :internal: + """ + return bool( + not self.build_only + and self.defaults + and self.endpoint == rule.endpoint + and self != rule + and self.arguments == rule.arguments + ) + + def suitable_for( + self, values: t.Mapping[str, t.Any], method: t.Optional[str] = None + ) -> bool: + """Check if the dict of values has enough data for url generation. + + :internal: + """ + # if a method was given explicitly and that method is not supported + # by this rule, this rule is not suitable. + if ( + method is not None + and self.methods is not None + and method not in self.methods + ): + return False + + defaults = self.defaults or () + + # all arguments required must be either in the defaults dict or + # the value dictionary otherwise it's not suitable + for key in self.arguments: + if key not in defaults and key not in values: + return False + + # in case defaults are given we ensure that either the value was + # skipped or the value is the same as the default value. + if defaults: + for key, value in defaults.items(): + if key in values and value != values[key]: + return False + + return True + + def build_compare_key(self) -> t.Tuple[int, int, int]: + """The build compare key for sorting. + + :internal: + """ + return (1 if self.alias else 0, -len(self.arguments), -len(self.defaults or ())) + + def __eq__(self, other: object) -> bool: + return isinstance(other, type(self)) and self._trace == other._trace + + __hash__ = None # type: ignore + + def __str__(self) -> str: + return self.rule + + def __repr__(self) -> str: + if self.map is None: + return f"<{type(self).__name__} (unbound)>" + parts = [] + for is_dynamic, data in self._trace: + if is_dynamic: + parts.append(f"<{data}>") + else: + parts.append(data) + parts = "".join(parts).lstrip("|") + methods = f" ({', '.join(self.methods)})" if self.methods is not None else "" + return f"<{type(self).__name__} {parts!r}{methods} -> {self.endpoint}>" diff --git a/.vtodo/Lib/site-packages/werkzeug/sansio/__init__.py b/.vtodo/Lib/site-packages/werkzeug/sansio/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/.vtodo/Lib/site-packages/werkzeug/sansio/__pycache__/__init__.cpython-310.pyc b/.vtodo/Lib/site-packages/werkzeug/sansio/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1bf048ee505259aece6fecce0347ddf90f064dfd GIT binary patch literal 180 zcmd1j<>g`kg7qrK$sqbMh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o11}*(xTqIJKxa zCbcLtIWeX0S%*!l^kJl@xyv1RYo1apelWGUDu$T!*urL4sUS2Mj literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/werkzeug/sansio/__pycache__/http.cpython-310.pyc b/.vtodo/Lib/site-packages/werkzeug/sansio/__pycache__/http.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..787af4cd3c9e54e962b26a0f3459ff1f9594f19b GIT binary patch literal 3867 zcma)9-EZ8+5$7(+JKmRclI3qV-8hcpbEPxtych=~ZV@L)fLcu}B?TB6CyKi#QR0#3 z-8~5sctu(TunQ#rgZ-GN{y%)}Q(pVfG*0?4v*ewm7^uqwhqJ?-;q1)pZ}!qwt8T&d z*BkGKx0fvIZ#X!8orl4%;8vdz%i&};!_yY*dE%xO)PG6Cw7Le?;73(zT#I5 zUje@AR}Ei{ytV+>Bc?+#=|nHyqtPCv34WpqDL>T@XwBqF5R%@k{*j zm#%-lcBOW;cCAMEJN#!b|D0G93!DsWnEM8BKZl!@b}sNYjg5=^`k>C=;x|D5B1G!# z_|ovQe+5=Z)~wF0Z!zMl9g=%I&_YK;@ht@j2p0Qcn(jrSKMJH0{b)FfL0a!~-49fM ztV0-8`#S9_ErD%qi$qAU(HA<{#+ij4nuSrL7~wkw!}F!lL0<;Rw&?GOfD1I!94Al4 zX=&x?=B>~FaPas}=fSN9z3aESw;yz_XXeoX!G^lM40m>;v!2&?v>p|<@|NMF(sX`d zIvl1v8bpHkRg{DxU!2O5G!gw_pu?RL)ne$NySW2P2=Z1ODBYjh$mgT&B$W`B>1QfZ z=&?+~8V0ny=HcnU{XX344XEPGz#0u zM&f`)_|Bw}HRM`W#XD>45xK@IlUnA=uQbqOvOy>Aq^axA8`{p?7ceR&p0zIX>x2c&b^oct=Ad)`KA)0*mZs=>Ss(Mpqj zkwgNL#D+req?`>>$-*?@k&eY2{T{1|eYC#*2`ftL zm5I~D_1MRQmH%t{H1|dQ7`;bjNv*75NxklSCxvE|SE24!9%0?RPSI3j@xlK!9jmp+ z^p0T2nV{i73N=a-CCVrcAzG12lQXfy67MmHau91FlR!uNB0e;>@D)o3hM$?zDGSCr z1;?T=h~q;OUd+Ypn!*7KlER%aY`bmN8;p~%w>f=lz+&@^D`)mu57pCAnRdjIsVg{* z6)6e9i=V9LC%Y44$2LC{ILI|>4z_|!(xd--j0G9mLl%q!Bc?Cz6qT%M%a0gqi z&MN}5r*b=t)lrkH?xS(6qlZzbk1m`ZeGai-WPm(7Wj*#P9$ zF&-hR!5ug;e1aac08kpw3~V6NA&kH!*z{$IuLBmTU?|w0IAkI0YuIojneInW?`()G z8^y2%;RvFZ4chDnF`zsPBCeWh$9~N?6w^u^?ZIpeAOm1Gu{H)^NeW_6IH3R+3kzuFVG(zu@M`Q| z3u<{K2GCA^X$pT;;!!_@dv;+NBZ$2VxB3XkpG@%{lZV#hYj7&DCQzK2^~`!^%J^$% zLNltCp12?-r=)g9GkZWkvxxP?(BSD;_W6BGd>@Cf>P&h-=zb(VDQ` zn2uERqH359M^P+%&m0&4lXLI4<*A?ukh^uLh05Je6KJ8ivz4ZC0kIBtL9>d&O{^$+ z2MIe%OhCN*(0RtOc#ptKrGa>s2Y}s#w4MIdDRn40`*XhY2>p*o>AyXj{>yXdkDg0^ r@GA7BSEb)Cdi0;PM!$oY{)7Im&~$#B0}Toq6g1{QgSzz}+Q0ZOZXY$` literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/werkzeug/sansio/__pycache__/multipart.cpython-310.pyc b/.vtodo/Lib/site-packages/werkzeug/sansio/__pycache__/multipart.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..38ed1b2f125d764bf7fc945ec4c6320d04664fd9 GIT binary patch literal 6634 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 literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/werkzeug/sansio/__pycache__/response.cpython-310.pyc b/.vtodo/Lib/site-packages/werkzeug/sansio/__pycache__/response.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..510b087ed9c029676e3845c99d537c6fdf49dc1a GIT binary patch literal 22414 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 literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/werkzeug/sansio/__pycache__/utils.cpython-310.pyc b/.vtodo/Lib/site-packages/werkzeug/sansio/__pycache__/utils.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..adc77ae1cb42dacc5f48dc3de7a006aa4a1fa2ab GIT binary patch literal 4571 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 HXTSR|CIR@| literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/werkzeug/sansio/http.py b/.vtodo/Lib/site-packages/werkzeug/sansio/http.py new file mode 100644 index 0000000..8288882 --- /dev/null +++ b/.vtodo/Lib/site-packages/werkzeug/sansio/http.py @@ -0,0 +1,140 @@ +import re +import typing as t +from datetime import datetime + +from .._internal import _cookie_parse_impl +from .._internal import _dt_as_utc +from .._internal import _to_str +from ..http import generate_etag +from ..http import parse_date +from ..http import parse_etags +from ..http import parse_if_range_header +from ..http import unquote_etag + +_etag_re = re.compile(r'([Ww]/)?(?:"(.*?)"|(.*?))(?:\s*,\s*|$)') + + +def is_resource_modified( + http_range: t.Optional[str] = None, + http_if_range: t.Optional[str] = None, + http_if_modified_since: t.Optional[str] = None, + http_if_none_match: t.Optional[str] = None, + http_if_match: t.Optional[str] = None, + etag: t.Optional[str] = None, + data: t.Optional[bytes] = None, + last_modified: t.Optional[t.Union[datetime, str]] = None, + ignore_if_range: bool = True, +) -> bool: + """Convenience method for conditional requests. + :param http_range: Range HTTP header + :param http_if_range: If-Range HTTP header + :param http_if_modified_since: If-Modified-Since HTTP header + :param http_if_none_match: If-None-Match HTTP header + :param http_if_match: If-Match HTTP header + :param etag: the etag for the response for comparison. + :param data: or alternatively the data of the response to automatically + generate an etag using :func:`generate_etag`. + :param last_modified: an optional date of the last modification. + :param ignore_if_range: If `False`, `If-Range` header will be taken into + account. + :return: `True` if the resource was modified, otherwise `False`. + + .. versionadded:: 2.2 + """ + if etag is None and data is not None: + etag = generate_etag(data) + elif data is not None: + raise TypeError("both data and etag given") + + unmodified = False + if isinstance(last_modified, str): + last_modified = parse_date(last_modified) + + # HTTP doesn't use microsecond, remove it to avoid false positive + # comparisons. Mark naive datetimes as UTC. + if last_modified is not None: + last_modified = _dt_as_utc(last_modified.replace(microsecond=0)) + + if_range = None + if not ignore_if_range and http_range is not None: + # https://tools.ietf.org/html/rfc7233#section-3.2 + # A server MUST ignore an If-Range header field received in a request + # that does not contain a Range header field. + if_range = parse_if_range_header(http_if_range) + + if if_range is not None and if_range.date is not None: + modified_since: t.Optional[datetime] = if_range.date + else: + modified_since = parse_date(http_if_modified_since) + + if modified_since and last_modified and last_modified <= modified_since: + unmodified = True + + if etag: + etag, _ = unquote_etag(etag) + etag = t.cast(str, etag) + + if if_range is not None and if_range.etag is not None: + unmodified = parse_etags(if_range.etag).contains(etag) + else: + if_none_match = parse_etags(http_if_none_match) + if if_none_match: + # https://tools.ietf.org/html/rfc7232#section-3.2 + # "A recipient MUST use the weak comparison function when comparing + # entity-tags for If-None-Match" + unmodified = if_none_match.contains_weak(etag) + + # https://tools.ietf.org/html/rfc7232#section-3.1 + # "Origin server MUST use the strong comparison function when + # comparing entity-tags for If-Match" + if_match = parse_etags(http_if_match) + if if_match: + unmodified = not if_match.is_strong(etag) + + return not unmodified + + +def parse_cookie( + cookie: t.Union[bytes, str, None] = "", + charset: str = "utf-8", + errors: str = "replace", + cls: t.Optional[t.Type["ds.MultiDict"]] = None, +) -> "ds.MultiDict[str, str]": + """Parse a cookie from a string. + + The same key can be provided multiple times, the values are stored + in-order. The default :class:`MultiDict` will have the first value + first, and all values can be retrieved with + :meth:`MultiDict.getlist`. + + :param cookie: The cookie header as a string. + :param charset: The charset for the cookie values. + :param errors: The error behavior for the charset decoding. + :param cls: A dict-like class to store the parsed cookies in. + Defaults to :class:`MultiDict`. + + .. versionadded:: 2.2 + """ + # PEP 3333 sends headers through the environ as latin1 decoded + # strings. Encode strings back to bytes for parsing. + if isinstance(cookie, str): + cookie = cookie.encode("latin1", "replace") + + if cls is None: + cls = ds.MultiDict + + def _parse_pairs() -> t.Iterator[t.Tuple[str, str]]: + for key, val in _cookie_parse_impl(cookie): # type: ignore + key_str = _to_str(key, charset, errors, allow_none_charset=True) + + if not key_str: + continue + + val_str = _to_str(val, charset, errors, allow_none_charset=True) + yield key_str, val_str + + return cls(_parse_pairs()) + + +# circular dependencies +from .. import datastructures as ds diff --git a/.vtodo/Lib/site-packages/werkzeug/sansio/multipart.py b/.vtodo/Lib/site-packages/werkzeug/sansio/multipart.py new file mode 100644 index 0000000..d8abeb3 --- /dev/null +++ b/.vtodo/Lib/site-packages/werkzeug/sansio/multipart.py @@ -0,0 +1,279 @@ +import re +from dataclasses import dataclass +from enum import auto +from enum import Enum +from typing import cast +from typing import List +from typing import Optional +from typing import Tuple + +from .._internal import _to_bytes +from .._internal import _to_str +from ..datastructures import Headers +from ..exceptions import RequestEntityTooLarge +from ..http import parse_options_header + + +class Event: + pass + + +@dataclass(frozen=True) +class Preamble(Event): + data: bytes + + +@dataclass(frozen=True) +class Field(Event): + name: str + headers: Headers + + +@dataclass(frozen=True) +class File(Event): + name: str + filename: str + headers: Headers + + +@dataclass(frozen=True) +class Data(Event): + data: bytes + more_data: bool + + +@dataclass(frozen=True) +class Epilogue(Event): + data: bytes + + +class NeedData(Event): + pass + + +NEED_DATA = NeedData() + + +class State(Enum): + PREAMBLE = auto() + PART = auto() + DATA = auto() + EPILOGUE = auto() + COMPLETE = auto() + + +# Multipart line breaks MUST be CRLF (\r\n) by RFC-7578, except that +# many implementations break this and either use CR or LF alone. +LINE_BREAK = b"(?:\r\n|\n|\r)" +BLANK_LINE_RE = re.compile(b"(?:\r\n\r\n|\r\r|\n\n)", re.MULTILINE) +LINE_BREAK_RE = re.compile(LINE_BREAK, re.MULTILINE) +# Header values can be continued via a space or tab after the linebreak, as +# per RFC2231 +HEADER_CONTINUATION_RE = re.compile(b"%s[ \t]" % LINE_BREAK, re.MULTILINE) +# This must be long enough to contain any line breaks plus any +# additional boundary markers (--) such that they will be found in a +# subsequent search +SEARCH_EXTRA_LENGTH = 8 + + +class MultipartDecoder: + """Decodes a multipart message as bytes into Python events. + + The part data is returned as available to allow the caller to save + the data from memory to disk, if desired. + """ + + def __init__( + self, + boundary: bytes, + max_form_memory_size: Optional[int] = None, + ) -> None: + self.buffer = bytearray() + self.complete = False + self.max_form_memory_size = max_form_memory_size + self.state = State.PREAMBLE + self.boundary = boundary + + # Note in the below \h i.e. horizontal whitespace is used + # as [^\S\n\r] as \h isn't supported in python. + + # The preamble must end with a boundary where the boundary is + # prefixed by a line break, RFC2046. Except that many + # implementations including Werkzeug's tests omit the line + # break prefix. In addition the first boundary could be the + # epilogue boundary (for empty form-data) hence the matching + # group to understand if it is an epilogue boundary. + self.preamble_re = re.compile( + rb"%s?--%s(--[^\S\n\r]*%s?|[^\S\n\r]*%s)" + % (LINE_BREAK, re.escape(boundary), LINE_BREAK, LINE_BREAK), + re.MULTILINE, + ) + # A boundary must include a line break prefix and suffix, and + # may include trailing whitespace. In addition the boundary + # could be the epilogue boundary hence the matching group to + # understand if it is an epilogue boundary. + self.boundary_re = re.compile( + rb"%s--%s(--[^\S\n\r]*%s?|[^\S\n\r]*%s)" + % (LINE_BREAK, re.escape(boundary), LINE_BREAK, LINE_BREAK), + re.MULTILINE, + ) + self._search_position = 0 + + def last_newline(self) -> int: + try: + last_nl = self.buffer.rindex(b"\n") + except ValueError: + last_nl = len(self.buffer) + try: + last_cr = self.buffer.rindex(b"\r") + except ValueError: + last_cr = len(self.buffer) + + return min(last_nl, last_cr) + + def receive_data(self, data: Optional[bytes]) -> None: + if data is None: + self.complete = True + elif ( + self.max_form_memory_size is not None + and len(self.buffer) + len(data) > self.max_form_memory_size + ): + raise RequestEntityTooLarge() + else: + self.buffer.extend(data) + + def next_event(self) -> Event: + event: Event = NEED_DATA + + if self.state == State.PREAMBLE: + match = self.preamble_re.search(self.buffer, self._search_position) + if match is not None: + if match.group(1).startswith(b"--"): + self.state = State.EPILOGUE + else: + self.state = State.PART + data = bytes(self.buffer[: match.start()]) + del self.buffer[: match.end()] + event = Preamble(data=data) + self._search_position = 0 + else: + # Update the search start position to be equal to the + # current buffer length (already searched) minus a + # safe buffer for part of the search target. + self._search_position = max( + 0, len(self.buffer) - len(self.boundary) - SEARCH_EXTRA_LENGTH + ) + + elif self.state == State.PART: + match = BLANK_LINE_RE.search(self.buffer, self._search_position) + if match is not None: + headers = self._parse_headers(self.buffer[: match.start()]) + del self.buffer[: match.end()] + + if "content-disposition" not in headers: + raise ValueError("Missing Content-Disposition header") + + disposition, extra = parse_options_header( + headers["content-disposition"] + ) + name = cast(str, extra.get("name")) + filename = extra.get("filename") + if filename is not None: + event = File( + filename=filename, + headers=headers, + name=name, + ) + else: + event = Field( + headers=headers, + name=name, + ) + self.state = State.DATA + self._search_position = 0 + else: + # Update the search start position to be equal to the + # current buffer length (already searched) minus a + # safe buffer for part of the search target. + self._search_position = max(0, len(self.buffer) - SEARCH_EXTRA_LENGTH) + + elif self.state == State.DATA: + if self.buffer.find(b"--" + self.boundary) == -1: + # No complete boundary in the buffer, but there may be + # a partial boundary at the end. As the boundary + # starts with either a nl or cr find the earliest and + # return up to that as data. + data_length = del_index = self.last_newline() + more_data = True + else: + match = self.boundary_re.search(self.buffer) + if match is not None: + if match.group(1).startswith(b"--"): + self.state = State.EPILOGUE + else: + self.state = State.PART + data_length = match.start() + del_index = match.end() + else: + data_length = del_index = self.last_newline() + more_data = match is None + + data = bytes(self.buffer[:data_length]) + del self.buffer[:del_index] + if data or not more_data: + event = Data(data=data, more_data=more_data) + + elif self.state == State.EPILOGUE and self.complete: + event = Epilogue(data=bytes(self.buffer)) + del self.buffer[:] + self.state = State.COMPLETE + + if self.complete and isinstance(event, NeedData): + raise ValueError(f"Invalid form-data cannot parse beyond {self.state}") + + return event + + def _parse_headers(self, data: bytes) -> Headers: + headers: List[Tuple[str, str]] = [] + # Merge the continued headers into one line + data = HEADER_CONTINUATION_RE.sub(b" ", data) + # Now there is one header per line + for line in data.splitlines(): + if line.strip() != b"": + name, value = _to_str(line).strip().split(":", 1) + headers.append((name.strip(), value.strip())) + return Headers(headers) + + +class MultipartEncoder: + def __init__(self, boundary: bytes) -> None: + self.boundary = boundary + self.state = State.PREAMBLE + + def send_event(self, event: Event) -> bytes: + if isinstance(event, Preamble) and self.state == State.PREAMBLE: + self.state = State.PART + return event.data + elif isinstance(event, (Field, File)) and self.state in { + State.PREAMBLE, + State.PART, + State.DATA, + }: + self.state = State.DATA + data = b"\r\n--" + self.boundary + b"\r\n" + data += b'Content-Disposition: form-data; name="%s"' % _to_bytes(event.name) + if isinstance(event, File): + data += b'; filename="%s"' % _to_bytes(event.filename) + data += b"\r\n" + for name, value in cast(Field, event).headers: + if name.lower() != "content-disposition": + data += _to_bytes(f"{name}: {value}\r\n") + data += b"\r\n" + return data + elif isinstance(event, Data) and self.state == State.DATA: + return event.data + elif isinstance(event, Epilogue): + self.state = State.COMPLETE + return b"\r\n--" + self.boundary + b"--\r\n" + event.data + else: + raise ValueError(f"Cannot generate {event} in state: {self.state}") diff --git a/.vtodo/Lib/site-packages/werkzeug/sansio/request.py b/.vtodo/Lib/site-packages/werkzeug/sansio/request.py new file mode 100644 index 0000000..8832baa --- /dev/null +++ b/.vtodo/Lib/site-packages/werkzeug/sansio/request.py @@ -0,0 +1,547 @@ +import typing as t +from datetime import datetime + +from .._internal import _to_str +from ..datastructures import Accept +from ..datastructures import Authorization +from ..datastructures import CharsetAccept +from ..datastructures import ETags +from ..datastructures import Headers +from ..datastructures import HeaderSet +from ..datastructures import IfRange +from ..datastructures import ImmutableList +from ..datastructures import ImmutableMultiDict +from ..datastructures import LanguageAccept +from ..datastructures import MIMEAccept +from ..datastructures import MultiDict +from ..datastructures import Range +from ..datastructures import RequestCacheControl +from ..http import parse_accept_header +from ..http import parse_authorization_header +from ..http import parse_cache_control_header +from ..http import parse_date +from ..http import parse_etags +from ..http import parse_if_range_header +from ..http import parse_list_header +from ..http import parse_options_header +from ..http import parse_range_header +from ..http import parse_set_header +from ..urls import url_decode +from ..user_agent import UserAgent +from ..utils import cached_property +from ..utils import header_property +from .http import parse_cookie +from .utils import get_current_url +from .utils import get_host + + +class Request: + """Represents the non-IO parts of a HTTP request, including the + method, URL info, and headers. + + This class is not meant for general use. It should only be used when + implementing WSGI, ASGI, or another HTTP application spec. Werkzeug + provides a WSGI implementation at :cls:`werkzeug.wrappers.Request`. + + :param method: The method the request was made with, such as + ``GET``. + :param scheme: The URL scheme of the protocol the request used, such + as ``https`` or ``wss``. + :param server: The address of the server. ``(host, port)``, + ``(path, None)`` for unix sockets, or ``None`` if not known. + :param root_path: The prefix that the application is mounted under. + This is prepended to generated URLs, but is not part of route + matching. + :param path: The path part of the URL after ``root_path``. + :param query_string: The part of the URL after the "?". + :param headers: The headers received with the request. + :param remote_addr: The address of the client sending the request. + + .. versionadded:: 2.0 + """ + + #: The charset used to decode most data in the request. + charset = "utf-8" + + #: the error handling procedure for errors, defaults to 'replace' + encoding_errors = "replace" + + #: the class to use for `args` and `form`. The default is an + #: :class:`~werkzeug.datastructures.ImmutableMultiDict` which supports + #: multiple values per key. alternatively it makes sense to use an + #: :class:`~werkzeug.datastructures.ImmutableOrderedMultiDict` which + #: preserves order or a :class:`~werkzeug.datastructures.ImmutableDict` + #: which is the fastest but only remembers the last key. It is also + #: possible to use mutable structures, but this is not recommended. + #: + #: .. versionadded:: 0.6 + parameter_storage_class: t.Type[MultiDict] = ImmutableMultiDict + + #: The type to be used for dict values from the incoming WSGI + #: environment. (For example for :attr:`cookies`.) By default an + #: :class:`~werkzeug.datastructures.ImmutableMultiDict` is used. + #: + #: .. versionchanged:: 1.0.0 + #: Changed to ``ImmutableMultiDict`` to support multiple values. + #: + #: .. versionadded:: 0.6 + dict_storage_class: t.Type[MultiDict] = ImmutableMultiDict + + #: the type to be used for list values from the incoming WSGI environment. + #: By default an :class:`~werkzeug.datastructures.ImmutableList` is used + #: (for example for :attr:`access_list`). + #: + #: .. versionadded:: 0.6 + list_storage_class: t.Type[t.List] = ImmutableList + + user_agent_class: t.Type[UserAgent] = UserAgent + """The class used and returned by the :attr:`user_agent` property to + parse the header. Defaults to + :class:`~werkzeug.user_agent.UserAgent`, which does no parsing. An + extension can provide a subclass that uses a parser to provide other + data. + + .. versionadded:: 2.0 + """ + + #: Valid host names when handling requests. By default all hosts are + #: trusted, which means that whatever the client says the host is + #: will be accepted. + #: + #: Because ``Host`` and ``X-Forwarded-Host`` headers can be set to + #: any value by a malicious client, it is recommended to either set + #: this property or implement similar validation in the proxy (if + #: the application is being run behind one). + #: + #: .. versionadded:: 0.9 + trusted_hosts: t.Optional[t.List[str]] = None + + def __init__( + self, + method: str, + scheme: str, + server: t.Optional[t.Tuple[str, t.Optional[int]]], + root_path: str, + path: str, + query_string: bytes, + headers: Headers, + remote_addr: t.Optional[str], + ) -> None: + #: The method the request was made with, such as ``GET``. + self.method = method.upper() + #: The URL scheme of the protocol the request used, such as + #: ``https`` or ``wss``. + self.scheme = scheme + #: The address of the server. ``(host, port)``, ``(path, None)`` + #: for unix sockets, or ``None`` if not known. + self.server = server + #: The prefix that the application is mounted under, without a + #: trailing slash. :attr:`path` comes after this. + self.root_path = root_path.rstrip("/") + #: The path part of the URL after :attr:`root_path`. This is the + #: path used for routing within the application. + self.path = "/" + path.lstrip("/") + #: The part of the URL after the "?". This is the raw value, use + #: :attr:`args` for the parsed values. + self.query_string = query_string + #: The headers received with the request. + self.headers = headers + #: The address of the client sending the request. + self.remote_addr = remote_addr + + def __repr__(self) -> str: + try: + url = self.url + except Exception as e: + url = f"(invalid URL: {e})" + + return f"<{type(self).__name__} {url!r} [{self.method}]>" + + @property + def url_charset(self) -> str: + """The charset that is assumed for URLs. Defaults to the value + of :attr:`charset`. + + .. versionadded:: 0.6 + """ + return self.charset + + @cached_property + def args(self) -> "MultiDict[str, str]": + """The parsed URL parameters (the part in the URL after the question + mark). + + By default an + :class:`~werkzeug.datastructures.ImmutableMultiDict` + is returned from this function. This can be changed by setting + :attr:`parameter_storage_class` to a different type. This might + be necessary if the order of the form data is important. + """ + return url_decode( + self.query_string, + self.url_charset, + errors=self.encoding_errors, + cls=self.parameter_storage_class, + ) + + @cached_property + def access_route(self) -> t.List[str]: + """If a forwarded header exists this is a list of all ip addresses + from the client ip to the last proxy server. + """ + if "X-Forwarded-For" in self.headers: + return self.list_storage_class( + parse_list_header(self.headers["X-Forwarded-For"]) + ) + elif self.remote_addr is not None: + return self.list_storage_class([self.remote_addr]) + return self.list_storage_class() + + @cached_property + def full_path(self) -> str: + """Requested path, including the query string.""" + return f"{self.path}?{_to_str(self.query_string, self.url_charset)}" + + @property + def is_secure(self) -> bool: + """``True`` if the request was made with a secure protocol + (HTTPS or WSS). + """ + return self.scheme in {"https", "wss"} + + @cached_property + def url(self) -> str: + """The full request URL with the scheme, host, root path, path, + and query string.""" + return get_current_url( + self.scheme, self.host, self.root_path, self.path, self.query_string + ) + + @cached_property + def base_url(self) -> str: + """Like :attr:`url` but without the query string.""" + return get_current_url(self.scheme, self.host, self.root_path, self.path) + + @cached_property + def root_url(self) -> str: + """The request URL scheme, host, and root path. This is the root + that the application is accessed from. + """ + return get_current_url(self.scheme, self.host, self.root_path) + + @cached_property + def host_url(self) -> str: + """The request URL scheme and host only.""" + return get_current_url(self.scheme, self.host) + + @cached_property + def host(self) -> str: + """The host name the request was made to, including the port if + it's non-standard. Validated with :attr:`trusted_hosts`. + """ + return get_host( + self.scheme, self.headers.get("host"), self.server, self.trusted_hosts + ) + + @cached_property + def cookies(self) -> "ImmutableMultiDict[str, str]": + """A :class:`dict` with the contents of all cookies transmitted with + the request.""" + wsgi_combined_cookie = ";".join(self.headers.getlist("Cookie")) + return parse_cookie( # type: ignore + wsgi_combined_cookie, + self.charset, + self.encoding_errors, + cls=self.dict_storage_class, + ) + + # Common Descriptors + + content_type = header_property[str]( + "Content-Type", + doc="""The Content-Type entity-header field indicates the media + type of the entity-body sent to the recipient or, in the case of + the HEAD method, the media type that would have been sent had + the request been a GET.""", + read_only=True, + ) + + @cached_property + def content_length(self) -> t.Optional[int]: + """The Content-Length entity-header field indicates the size of the + entity-body in bytes or, in the case of the HEAD method, the size of + the entity-body that would have been sent had the request been a + GET. + """ + if self.headers.get("Transfer-Encoding", "") == "chunked": + return None + + content_length = self.headers.get("Content-Length") + if content_length is not None: + try: + return max(0, int(content_length)) + except (ValueError, TypeError): + pass + + return None + + content_encoding = header_property[str]( + "Content-Encoding", + doc="""The Content-Encoding entity-header field is used as a + modifier to the media-type. When present, its value indicates + what additional content codings have been applied to the + entity-body, and thus what decoding mechanisms must be applied + in order to obtain the media-type referenced by the Content-Type + header field. + + .. versionadded:: 0.9""", + read_only=True, + ) + content_md5 = header_property[str]( + "Content-MD5", + doc="""The Content-MD5 entity-header field, as defined in + RFC 1864, is an MD5 digest of the entity-body for the purpose of + providing an end-to-end message integrity check (MIC) of the + entity-body. (Note: a MIC is good for detecting accidental + modification of the entity-body in transit, but is not proof + against malicious attacks.) + + .. versionadded:: 0.9""", + read_only=True, + ) + referrer = header_property[str]( + "Referer", + doc="""The Referer[sic] request-header field allows the client + to specify, for the server's benefit, the address (URI) of the + resource from which the Request-URI was obtained (the + "referrer", although the header field is misspelled).""", + read_only=True, + ) + date = header_property( + "Date", + None, + parse_date, + doc="""The Date general-header field represents the date and + time at which the message was originated, having the same + semantics as orig-date in RFC 822. + + .. versionchanged:: 2.0 + The datetime object is timezone-aware. + """, + read_only=True, + ) + max_forwards = header_property( + "Max-Forwards", + None, + int, + doc="""The Max-Forwards request-header field provides a + mechanism with the TRACE and OPTIONS methods to limit the number + of proxies or gateways that can forward the request to the next + inbound server.""", + read_only=True, + ) + + def _parse_content_type(self) -> None: + if not hasattr(self, "_parsed_content_type"): + self._parsed_content_type = parse_options_header( + self.headers.get("Content-Type", "") + ) + + @property + def mimetype(self) -> str: + """Like :attr:`content_type`, but without parameters (eg, without + charset, type etc.) and always lowercase. For example if the content + type is ``text/HTML; charset=utf-8`` the mimetype would be + ``'text/html'``. + """ + self._parse_content_type() + return self._parsed_content_type[0].lower() + + @property + def mimetype_params(self) -> t.Dict[str, str]: + """The mimetype parameters as dict. For example if the content + type is ``text/html; charset=utf-8`` the params would be + ``{'charset': 'utf-8'}``. + """ + self._parse_content_type() + return self._parsed_content_type[1] + + @cached_property + def pragma(self) -> HeaderSet: + """The Pragma general-header field is used to include + implementation-specific directives that might apply to any recipient + along the request/response chain. All pragma directives specify + optional behavior from the viewpoint of the protocol; however, some + systems MAY require that behavior be consistent with the directives. + """ + return parse_set_header(self.headers.get("Pragma", "")) + + # Accept + + @cached_property + def accept_mimetypes(self) -> MIMEAccept: + """List of mimetypes this client supports as + :class:`~werkzeug.datastructures.MIMEAccept` object. + """ + return parse_accept_header(self.headers.get("Accept"), MIMEAccept) + + @cached_property + def accept_charsets(self) -> CharsetAccept: + """List of charsets this client supports as + :class:`~werkzeug.datastructures.CharsetAccept` object. + """ + return parse_accept_header(self.headers.get("Accept-Charset"), CharsetAccept) + + @cached_property + def accept_encodings(self) -> Accept: + """List of encodings this client accepts. Encodings in a HTTP term + are compression encodings such as gzip. For charsets have a look at + :attr:`accept_charset`. + """ + return parse_accept_header(self.headers.get("Accept-Encoding")) + + @cached_property + def accept_languages(self) -> LanguageAccept: + """List of languages this client accepts as + :class:`~werkzeug.datastructures.LanguageAccept` object. + + .. versionchanged 0.5 + In previous versions this was a regular + :class:`~werkzeug.datastructures.Accept` object. + """ + return parse_accept_header(self.headers.get("Accept-Language"), LanguageAccept) + + # ETag + + @cached_property + def cache_control(self) -> RequestCacheControl: + """A :class:`~werkzeug.datastructures.RequestCacheControl` object + for the incoming cache control headers. + """ + cache_control = self.headers.get("Cache-Control") + return parse_cache_control_header(cache_control, None, RequestCacheControl) + + @cached_property + def if_match(self) -> ETags: + """An object containing all the etags in the `If-Match` header. + + :rtype: :class:`~werkzeug.datastructures.ETags` + """ + return parse_etags(self.headers.get("If-Match")) + + @cached_property + def if_none_match(self) -> ETags: + """An object containing all the etags in the `If-None-Match` header. + + :rtype: :class:`~werkzeug.datastructures.ETags` + """ + return parse_etags(self.headers.get("If-None-Match")) + + @cached_property + def if_modified_since(self) -> t.Optional[datetime]: + """The parsed `If-Modified-Since` header as a datetime object. + + .. versionchanged:: 2.0 + The datetime object is timezone-aware. + """ + return parse_date(self.headers.get("If-Modified-Since")) + + @cached_property + def if_unmodified_since(self) -> t.Optional[datetime]: + """The parsed `If-Unmodified-Since` header as a datetime object. + + .. versionchanged:: 2.0 + The datetime object is timezone-aware. + """ + return parse_date(self.headers.get("If-Unmodified-Since")) + + @cached_property + def if_range(self) -> IfRange: + """The parsed ``If-Range`` header. + + .. versionchanged:: 2.0 + ``IfRange.date`` is timezone-aware. + + .. versionadded:: 0.7 + """ + return parse_if_range_header(self.headers.get("If-Range")) + + @cached_property + def range(self) -> t.Optional[Range]: + """The parsed `Range` header. + + .. versionadded:: 0.7 + + :rtype: :class:`~werkzeug.datastructures.Range` + """ + return parse_range_header(self.headers.get("Range")) + + # User Agent + + @cached_property + def user_agent(self) -> UserAgent: + """The user agent. Use ``user_agent.string`` to get the header + value. Set :attr:`user_agent_class` to a subclass of + :class:`~werkzeug.user_agent.UserAgent` to provide parsing for + the other properties or other extended data. + + .. versionchanged:: 2.0 + The built in parser is deprecated and will be removed in + Werkzeug 2.1. A ``UserAgent`` subclass must be set to parse + data from the string. + """ + return self.user_agent_class(self.headers.get("User-Agent", "")) + + # Authorization + + @cached_property + def authorization(self) -> t.Optional[Authorization]: + """The `Authorization` object in parsed form.""" + return parse_authorization_header(self.headers.get("Authorization")) + + # CORS + + origin = header_property[str]( + "Origin", + doc=( + "The host that the request originated from. Set" + " :attr:`~CORSResponseMixin.access_control_allow_origin` on" + " the response to indicate which origins are allowed." + ), + read_only=True, + ) + + access_control_request_headers = header_property( + "Access-Control-Request-Headers", + load_func=parse_set_header, + doc=( + "Sent with a preflight request to indicate which headers" + " will be sent with the cross origin request. Set" + " :attr:`~CORSResponseMixin.access_control_allow_headers`" + " on the response to indicate which headers are allowed." + ), + read_only=True, + ) + + access_control_request_method = header_property[str]( + "Access-Control-Request-Method", + doc=( + "Sent with a preflight request to indicate which method" + " will be used for the cross origin request. Set" + " :attr:`~CORSResponseMixin.access_control_allow_methods`" + " on the response to indicate which methods are allowed." + ), + read_only=True, + ) + + @property + def is_json(self) -> bool: + """Check if the mimetype indicates JSON data, either + :mimetype:`application/json` or :mimetype:`application/*+json`. + """ + mt = self.mimetype + return ( + mt == "application/json" + or mt.startswith("application/") + and mt.endswith("+json") + ) diff --git a/.vtodo/Lib/site-packages/werkzeug/sansio/response.py b/.vtodo/Lib/site-packages/werkzeug/sansio/response.py new file mode 100644 index 0000000..de0bec2 --- /dev/null +++ b/.vtodo/Lib/site-packages/werkzeug/sansio/response.py @@ -0,0 +1,704 @@ +import typing as t +from datetime import datetime +from datetime import timedelta +from datetime import timezone +from http import HTTPStatus + +from .._internal import _to_str +from ..datastructures import Headers +from ..datastructures import HeaderSet +from ..http import dump_cookie +from ..http import HTTP_STATUS_CODES +from ..utils import get_content_type +from werkzeug.datastructures import CallbackDict +from werkzeug.datastructures import ContentRange +from werkzeug.datastructures import ContentSecurityPolicy +from werkzeug.datastructures import ResponseCacheControl +from werkzeug.datastructures import WWWAuthenticate +from werkzeug.http import COEP +from werkzeug.http import COOP +from werkzeug.http import dump_age +from werkzeug.http import dump_header +from werkzeug.http import dump_options_header +from werkzeug.http import http_date +from werkzeug.http import parse_age +from werkzeug.http import parse_cache_control_header +from werkzeug.http import parse_content_range_header +from werkzeug.http import parse_csp_header +from werkzeug.http import parse_date +from werkzeug.http import parse_options_header +from werkzeug.http import parse_set_header +from werkzeug.http import parse_www_authenticate_header +from werkzeug.http import quote_etag +from werkzeug.http import unquote_etag +from werkzeug.utils import header_property + + +def _set_property(name: str, doc: t.Optional[str] = None) -> property: + def fget(self: "Response") -> HeaderSet: + def on_update(header_set: HeaderSet) -> None: + if not header_set and name in self.headers: + del self.headers[name] + elif header_set: + self.headers[name] = header_set.to_header() + + return parse_set_header(self.headers.get(name), on_update) + + def fset( + self: "Response", + value: t.Optional[ + t.Union[str, t.Dict[str, t.Union[str, int]], t.Iterable[str]] + ], + ) -> None: + if not value: + del self.headers[name] + elif isinstance(value, str): + self.headers[name] = value + else: + self.headers[name] = dump_header(value) + + return property(fget, fset, doc=doc) + + +class Response: + """Represents the non-IO parts of an HTTP response, specifically the + status and headers but not the body. + + This class is not meant for general use. It should only be used when + implementing WSGI, ASGI, or another HTTP application spec. Werkzeug + provides a WSGI implementation at :cls:`werkzeug.wrappers.Response`. + + :param status: The status code for the response. Either an int, in + which case the default status message is added, or a string in + the form ``{code} {message}``, like ``404 Not Found``. Defaults + to 200. + :param headers: A :class:`~werkzeug.datastructures.Headers` object, + or a list of ``(key, value)`` tuples that will be converted to a + ``Headers`` object. + :param mimetype: The mime type (content type without charset or + other parameters) of the response. If the value starts with + ``text/`` (or matches some other special cases), the charset + will be added to create the ``content_type``. + :param content_type: The full content type of the response. + Overrides building the value from ``mimetype``. + + .. versionadded:: 2.0 + """ + + #: the charset of the response. + charset = "utf-8" + + #: the default status if none is provided. + default_status = 200 + + #: the default mimetype if none is provided. + default_mimetype: t.Optional[str] = "text/plain" + + #: Warn if a cookie header exceeds this size. The default, 4093, should be + #: safely `supported by most browsers `_. A cookie larger than + #: this size will still be sent, but it may be ignored or handled + #: incorrectly by some browsers. Set to 0 to disable this check. + #: + #: .. versionadded:: 0.13 + #: + #: .. _`cookie`: http://browsercookielimits.squawky.net/ + max_cookie_size = 4093 + + # A :class:`Headers` object representing the response headers. + headers: Headers + + def __init__( + self, + status: t.Optional[t.Union[int, str, HTTPStatus]] = None, + headers: t.Optional[ + t.Union[ + t.Mapping[str, t.Union[str, int, t.Iterable[t.Union[str, int]]]], + t.Iterable[t.Tuple[str, t.Union[str, int]]], + ] + ] = None, + mimetype: t.Optional[str] = None, + content_type: t.Optional[str] = None, + ) -> None: + if isinstance(headers, Headers): + self.headers = headers + elif not headers: + self.headers = Headers() + else: + self.headers = Headers(headers) + + if content_type is None: + if mimetype is None and "content-type" not in self.headers: + mimetype = self.default_mimetype + if mimetype is not None: + mimetype = get_content_type(mimetype, self.charset) + content_type = mimetype + if content_type is not None: + self.headers["Content-Type"] = content_type + if status is None: + status = self.default_status + self.status = status # type: ignore + + def __repr__(self) -> str: + return f"<{type(self).__name__} [{self.status}]>" + + @property + def status_code(self) -> int: + """The HTTP status code as a number.""" + return self._status_code + + @status_code.setter + def status_code(self, code: int) -> None: + self.status = code # type: ignore + + @property + def status(self) -> str: + """The HTTP status code as a string.""" + return self._status + + @status.setter + def status(self, value: t.Union[str, int, HTTPStatus]) -> None: + if not isinstance(value, (str, bytes, int, HTTPStatus)): + raise TypeError("Invalid status argument") + + self._status, self._status_code = self._clean_status(value) + + def _clean_status(self, value: t.Union[str, int, HTTPStatus]) -> t.Tuple[str, int]: + if isinstance(value, HTTPStatus): + value = int(value) + status = _to_str(value, self.charset) + split_status = status.split(None, 1) + + if len(split_status) == 0: + raise ValueError("Empty status argument") + + try: + status_code = int(split_status[0]) + except ValueError: + # only message + return f"0 {status}", 0 + + if len(split_status) > 1: + # code and message + return status, status_code + + # only code, look up message + try: + status = f"{status_code} {HTTP_STATUS_CODES[status_code].upper()}" + except KeyError: + status = f"{status_code} UNKNOWN" + + return status, status_code + + def set_cookie( + self, + key: str, + value: str = "", + max_age: t.Optional[t.Union[timedelta, int]] = None, + expires: t.Optional[t.Union[str, datetime, int, float]] = None, + path: t.Optional[str] = "/", + domain: t.Optional[str] = None, + secure: bool = False, + httponly: bool = False, + samesite: t.Optional[str] = None, + ) -> None: + """Sets a cookie. + + A warning is raised if the size of the cookie header exceeds + :attr:`max_cookie_size`, but the header will still be set. + + :param key: the key (name) of the cookie to be set. + :param value: the value of the cookie. + :param max_age: should be a number of seconds, or `None` (default) if + the cookie should last only as long as the client's + browser session. + :param expires: should be a `datetime` object or UNIX timestamp. + :param path: limits the cookie to a given path, per default it will + span the whole domain. + :param domain: if you want to set a cross-domain cookie. For example, + ``domain=".example.com"`` will set a cookie that is + readable by the domain ``www.example.com``, + ``foo.example.com`` etc. Otherwise, a cookie will only + be readable by the domain that set it. + :param secure: If ``True``, the cookie will only be available + via HTTPS. + :param httponly: Disallow JavaScript access to the cookie. + :param samesite: Limit the scope of the cookie to only be + attached to requests that are "same-site". + """ + self.headers.add( + "Set-Cookie", + dump_cookie( + key, + value=value, + max_age=max_age, + expires=expires, + path=path, + domain=domain, + secure=secure, + httponly=httponly, + charset=self.charset, + max_size=self.max_cookie_size, + samesite=samesite, + ), + ) + + def delete_cookie( + self, + key: str, + path: str = "/", + domain: t.Optional[str] = None, + secure: bool = False, + httponly: bool = False, + samesite: t.Optional[str] = None, + ) -> None: + """Delete a cookie. Fails silently if key doesn't exist. + + :param key: the key (name) of the cookie to be deleted. + :param path: if the cookie that should be deleted was limited to a + path, the path has to be defined here. + :param domain: if the cookie that should be deleted was limited to a + domain, that domain has to be defined here. + :param secure: If ``True``, the cookie will only be available + via HTTPS. + :param httponly: Disallow JavaScript access to the cookie. + :param samesite: Limit the scope of the cookie to only be + attached to requests that are "same-site". + """ + self.set_cookie( + key, + expires=0, + max_age=0, + path=path, + domain=domain, + secure=secure, + httponly=httponly, + samesite=samesite, + ) + + @property + def is_json(self) -> bool: + """Check if the mimetype indicates JSON data, either + :mimetype:`application/json` or :mimetype:`application/*+json`. + """ + mt = self.mimetype + return mt is not None and ( + mt == "application/json" + or mt.startswith("application/") + and mt.endswith("+json") + ) + + # Common Descriptors + + @property + def mimetype(self) -> t.Optional[str]: + """The mimetype (content type without charset etc.)""" + ct = self.headers.get("content-type") + + if ct: + return ct.split(";")[0].strip() + else: + return None + + @mimetype.setter + def mimetype(self, value: str) -> None: + self.headers["Content-Type"] = get_content_type(value, self.charset) + + @property + def mimetype_params(self) -> t.Dict[str, str]: + """The mimetype parameters as dict. For example if the + content type is ``text/html; charset=utf-8`` the params would be + ``{'charset': 'utf-8'}``. + + .. versionadded:: 0.5 + """ + + def on_update(d: CallbackDict) -> None: + self.headers["Content-Type"] = dump_options_header(self.mimetype, d) + + d = parse_options_header(self.headers.get("content-type", ""))[1] + return CallbackDict(d, on_update) + + location = header_property[str]( + "Location", + doc="""The Location response-header field is used to redirect + the recipient to a location other than the Request-URI for + completion of the request or identification of a new + resource.""", + ) + age = header_property( + "Age", + None, + parse_age, + dump_age, # type: ignore + doc="""The Age response-header field conveys the sender's + estimate of the amount of time since the response (or its + revalidation) was generated at the origin server. + + Age values are non-negative decimal integers, representing time + in seconds.""", + ) + content_type = header_property[str]( + "Content-Type", + doc="""The Content-Type entity-header field indicates the media + type of the entity-body sent to the recipient or, in the case of + the HEAD method, the media type that would have been sent had + the request been a GET.""", + ) + content_length = header_property( + "Content-Length", + None, + int, + str, + doc="""The Content-Length entity-header field indicates the size + of the entity-body, in decimal number of OCTETs, sent to the + recipient or, in the case of the HEAD method, the size of the + entity-body that would have been sent had the request been a + GET.""", + ) + content_location = header_property[str]( + "Content-Location", + doc="""The Content-Location entity-header field MAY be used to + supply the resource location for the entity enclosed in the + message when that entity is accessible from a location separate + from the requested resource's URI.""", + ) + content_encoding = header_property[str]( + "Content-Encoding", + doc="""The Content-Encoding entity-header field is used as a + modifier to the media-type. When present, its value indicates + what additional content codings have been applied to the + entity-body, and thus what decoding mechanisms must be applied + in order to obtain the media-type referenced by the Content-Type + header field.""", + ) + content_md5 = header_property[str]( + "Content-MD5", + doc="""The Content-MD5 entity-header field, as defined in + RFC 1864, is an MD5 digest of the entity-body for the purpose of + providing an end-to-end message integrity check (MIC) of the + entity-body. (Note: a MIC is good for detecting accidental + modification of the entity-body in transit, but is not proof + against malicious attacks.)""", + ) + date = header_property( + "Date", + None, + parse_date, + http_date, + doc="""The Date general-header field represents the date and + time at which the message was originated, having the same + semantics as orig-date in RFC 822. + + .. versionchanged:: 2.0 + The datetime object is timezone-aware. + """, + ) + expires = header_property( + "Expires", + None, + parse_date, + http_date, + doc="""The Expires entity-header field gives the date/time after + which the response is considered stale. A stale cache entry may + not normally be returned by a cache. + + .. versionchanged:: 2.0 + The datetime object is timezone-aware. + """, + ) + last_modified = header_property( + "Last-Modified", + None, + parse_date, + http_date, + doc="""The Last-Modified entity-header field indicates the date + and time at which the origin server believes the variant was + last modified. + + .. versionchanged:: 2.0 + The datetime object is timezone-aware. + """, + ) + + @property + def retry_after(self) -> t.Optional[datetime]: + """The Retry-After response-header field can be used with a + 503 (Service Unavailable) response to indicate how long the + service is expected to be unavailable to the requesting client. + + Time in seconds until expiration or date. + + .. versionchanged:: 2.0 + The datetime object is timezone-aware. + """ + value = self.headers.get("retry-after") + if value is None: + return None + + try: + seconds = int(value) + except ValueError: + return parse_date(value) + + return datetime.now(timezone.utc) + timedelta(seconds=seconds) + + @retry_after.setter + def retry_after(self, value: t.Optional[t.Union[datetime, int, str]]) -> None: + if value is None: + if "retry-after" in self.headers: + del self.headers["retry-after"] + return + elif isinstance(value, datetime): + value = http_date(value) + else: + value = str(value) + self.headers["Retry-After"] = value + + vary = _set_property( + "Vary", + doc="""The Vary field value indicates the set of request-header + fields that fully determines, while the response is fresh, + whether a cache is permitted to use the response to reply to a + subsequent request without revalidation.""", + ) + content_language = _set_property( + "Content-Language", + doc="""The Content-Language entity-header field describes the + natural language(s) of the intended audience for the enclosed + entity. Note that this might not be equivalent to all the + languages used within the entity-body.""", + ) + allow = _set_property( + "Allow", + doc="""The Allow entity-header field lists the set of methods + supported by the resource identified by the Request-URI. The + purpose of this field is strictly to inform the recipient of + valid methods associated with the resource. An Allow header + field MUST be present in a 405 (Method Not Allowed) + response.""", + ) + + # ETag + + @property + def cache_control(self) -> ResponseCacheControl: + """The Cache-Control general-header field is used to specify + directives that MUST be obeyed by all caching mechanisms along the + request/response chain. + """ + + def on_update(cache_control: ResponseCacheControl) -> None: + if not cache_control and "cache-control" in self.headers: + del self.headers["cache-control"] + elif cache_control: + self.headers["Cache-Control"] = cache_control.to_header() + + return parse_cache_control_header( + self.headers.get("cache-control"), on_update, ResponseCacheControl + ) + + def set_etag(self, etag: str, weak: bool = False) -> None: + """Set the etag, and override the old one if there was one.""" + self.headers["ETag"] = quote_etag(etag, weak) + + def get_etag(self) -> t.Union[t.Tuple[str, bool], t.Tuple[None, None]]: + """Return a tuple in the form ``(etag, is_weak)``. If there is no + ETag the return value is ``(None, None)``. + """ + return unquote_etag(self.headers.get("ETag")) + + accept_ranges = header_property[str]( + "Accept-Ranges", + doc="""The `Accept-Ranges` header. Even though the name would + indicate that multiple values are supported, it must be one + string token only. + + The values ``'bytes'`` and ``'none'`` are common. + + .. versionadded:: 0.7""", + ) + + @property + def content_range(self) -> ContentRange: + """The ``Content-Range`` header as a + :class:`~werkzeug.datastructures.ContentRange` object. Available + even if the header is not set. + + .. versionadded:: 0.7 + """ + + def on_update(rng: ContentRange) -> None: + if not rng: + del self.headers["content-range"] + else: + self.headers["Content-Range"] = rng.to_header() + + rv = parse_content_range_header(self.headers.get("content-range"), on_update) + # always provide a content range object to make the descriptor + # more user friendly. It provides an unset() method that can be + # used to remove the header quickly. + if rv is None: + rv = ContentRange(None, None, None, on_update=on_update) + return rv + + @content_range.setter + def content_range(self, value: t.Optional[t.Union[ContentRange, str]]) -> None: + if not value: + del self.headers["content-range"] + elif isinstance(value, str): + self.headers["Content-Range"] = value + else: + self.headers["Content-Range"] = value.to_header() + + # Authorization + + @property + def www_authenticate(self) -> WWWAuthenticate: + """The ``WWW-Authenticate`` header in a parsed form.""" + + def on_update(www_auth: WWWAuthenticate) -> None: + if not www_auth and "www-authenticate" in self.headers: + del self.headers["www-authenticate"] + elif www_auth: + self.headers["WWW-Authenticate"] = www_auth.to_header() + + header = self.headers.get("www-authenticate") + return parse_www_authenticate_header(header, on_update) + + # CSP + + @property + def content_security_policy(self) -> ContentSecurityPolicy: + """The ``Content-Security-Policy`` header as a + :class:`~werkzeug.datastructures.ContentSecurityPolicy` object. Available + even if the header is not set. + + The Content-Security-Policy header adds an additional layer of + security to help detect and mitigate certain types of attacks. + """ + + def on_update(csp: ContentSecurityPolicy) -> None: + if not csp: + del self.headers["content-security-policy"] + else: + self.headers["Content-Security-Policy"] = csp.to_header() + + rv = parse_csp_header(self.headers.get("content-security-policy"), on_update) + if rv is None: + rv = ContentSecurityPolicy(None, on_update=on_update) + return rv + + @content_security_policy.setter + def content_security_policy( + self, value: t.Optional[t.Union[ContentSecurityPolicy, str]] + ) -> None: + if not value: + del self.headers["content-security-policy"] + elif isinstance(value, str): + self.headers["Content-Security-Policy"] = value + else: + self.headers["Content-Security-Policy"] = value.to_header() + + @property + def content_security_policy_report_only(self) -> ContentSecurityPolicy: + """The ``Content-Security-policy-report-only`` header as a + :class:`~werkzeug.datastructures.ContentSecurityPolicy` object. Available + even if the header is not set. + + The Content-Security-Policy-Report-Only header adds a csp policy + that is not enforced but is reported thereby helping detect + certain types of attacks. + """ + + def on_update(csp: ContentSecurityPolicy) -> None: + if not csp: + del self.headers["content-security-policy-report-only"] + else: + self.headers["Content-Security-policy-report-only"] = csp.to_header() + + rv = parse_csp_header( + self.headers.get("content-security-policy-report-only"), on_update + ) + if rv is None: + rv = ContentSecurityPolicy(None, on_update=on_update) + return rv + + @content_security_policy_report_only.setter + def content_security_policy_report_only( + self, value: t.Optional[t.Union[ContentSecurityPolicy, str]] + ) -> None: + if not value: + del self.headers["content-security-policy-report-only"] + elif isinstance(value, str): + self.headers["Content-Security-policy-report-only"] = value + else: + self.headers["Content-Security-policy-report-only"] = value.to_header() + + # CORS + + @property + def access_control_allow_credentials(self) -> bool: + """Whether credentials can be shared by the browser to + JavaScript code. As part of the preflight request it indicates + whether credentials can be used on the cross origin request. + """ + return "Access-Control-Allow-Credentials" in self.headers + + @access_control_allow_credentials.setter + def access_control_allow_credentials(self, value: t.Optional[bool]) -> None: + if value is True: + self.headers["Access-Control-Allow-Credentials"] = "true" + else: + self.headers.pop("Access-Control-Allow-Credentials", None) + + access_control_allow_headers = header_property( + "Access-Control-Allow-Headers", + load_func=parse_set_header, + dump_func=dump_header, + doc="Which headers can be sent with the cross origin request.", + ) + + access_control_allow_methods = header_property( + "Access-Control-Allow-Methods", + load_func=parse_set_header, + dump_func=dump_header, + doc="Which methods can be used for the cross origin request.", + ) + + access_control_allow_origin = header_property[str]( + "Access-Control-Allow-Origin", + doc="The origin or '*' for any origin that may make cross origin requests.", + ) + + access_control_expose_headers = header_property( + "Access-Control-Expose-Headers", + load_func=parse_set_header, + dump_func=dump_header, + doc="Which headers can be shared by the browser to JavaScript code.", + ) + + access_control_max_age = header_property( + "Access-Control-Max-Age", + load_func=int, + dump_func=str, + doc="The maximum age in seconds the access control settings can be cached for.", + ) + + cross_origin_opener_policy = header_property[COOP]( + "Cross-Origin-Opener-Policy", + load_func=lambda value: COOP(value), + dump_func=lambda value: value.value, + default=COOP.UNSAFE_NONE, + doc="""Allows control over sharing of browsing context group with cross-origin + documents. Values must be a member of the :class:`werkzeug.http.COOP` enum.""", + ) + + cross_origin_embedder_policy = header_property[COEP]( + "Cross-Origin-Embedder-Policy", + load_func=lambda value: COEP(value), + dump_func=lambda value: value.value, + default=COEP.UNSAFE_NONE, + doc="""Prevents a document from loading any cross-origin resources that do not + explicitly grant the document permission. Values must be a member of the + :class:`werkzeug.http.COEP` enum.""", + ) diff --git a/.vtodo/Lib/site-packages/werkzeug/sansio/utils.py b/.vtodo/Lib/site-packages/werkzeug/sansio/utils.py new file mode 100644 index 0000000..e639dcb --- /dev/null +++ b/.vtodo/Lib/site-packages/werkzeug/sansio/utils.py @@ -0,0 +1,165 @@ +import typing as t + +from .._internal import _encode_idna +from ..exceptions import SecurityError +from ..urls import uri_to_iri +from ..urls import url_quote + + +def host_is_trusted(hostname: str, trusted_list: t.Iterable[str]) -> bool: + """Check if a host matches a list of trusted names. + + :param hostname: The name to check. + :param trusted_list: A list of valid names to match. If a name + starts with a dot it will match all subdomains. + + .. versionadded:: 0.9 + """ + if not hostname: + return False + + if isinstance(trusted_list, str): + trusted_list = [trusted_list] + + def _normalize(hostname: str) -> bytes: + if ":" in hostname: + hostname = hostname.rsplit(":", 1)[0] + + return _encode_idna(hostname) + + try: + hostname_bytes = _normalize(hostname) + except UnicodeError: + return False + + for ref in trusted_list: + if ref.startswith("."): + ref = ref[1:] + suffix_match = True + else: + suffix_match = False + + try: + ref_bytes = _normalize(ref) + except UnicodeError: + return False + + if ref_bytes == hostname_bytes: + return True + + if suffix_match and hostname_bytes.endswith(b"." + ref_bytes): + return True + + return False + + +def get_host( + scheme: str, + host_header: t.Optional[str], + server: t.Optional[t.Tuple[str, t.Optional[int]]] = None, + trusted_hosts: t.Optional[t.Iterable[str]] = None, +) -> str: + """Return the host for the given parameters. + + This first checks the ``host_header``. If it's not present, then + ``server`` is used. The host will only contain the port if it is + different than the standard port for the protocol. + + Optionally, verify that the host is trusted using + :func:`host_is_trusted` and raise a + :exc:`~werkzeug.exceptions.SecurityError` if it is not. + + :param scheme: The protocol the request used, like ``"https"``. + :param host_header: The ``Host`` header value. + :param server: Address of the server. ``(host, port)``, or + ``(path, None)`` for unix sockets. + :param trusted_hosts: A list of trusted host names. + + :return: Host, with port if necessary. + :raise ~werkzeug.exceptions.SecurityError: If the host is not + trusted. + """ + host = "" + + if host_header is not None: + host = host_header + elif server is not None: + host = server[0] + + if server[1] is not None: + host = f"{host}:{server[1]}" + + if scheme in {"http", "ws"} and host.endswith(":80"): + host = host[:-3] + elif scheme in {"https", "wss"} and host.endswith(":443"): + host = host[:-4] + + if trusted_hosts is not None: + if not host_is_trusted(host, trusted_hosts): + raise SecurityError(f"Host {host!r} is not trusted.") + + return host + + +def get_current_url( + scheme: str, + host: str, + root_path: t.Optional[str] = None, + path: t.Optional[str] = None, + query_string: t.Optional[bytes] = None, +) -> str: + """Recreate the URL for a request. If an optional part isn't + provided, it and subsequent parts are not included in the URL. + + The URL is an IRI, not a URI, so it may contain Unicode characters. + Use :func:`~werkzeug.urls.iri_to_uri` to convert it to ASCII. + + :param scheme: The protocol the request used, like ``"https"``. + :param host: The host the request was made to. See :func:`get_host`. + :param root_path: Prefix that the application is mounted under. This + is prepended to ``path``. + :param path: The path part of the URL after ``root_path``. + :param query_string: The portion of the URL after the "?". + """ + url = [scheme, "://", host] + + if root_path is None: + url.append("/") + return uri_to_iri("".join(url)) + + url.append(url_quote(root_path.rstrip("/"))) + url.append("/") + + if path is None: + return uri_to_iri("".join(url)) + + url.append(url_quote(path.lstrip("/"))) + + if query_string: + url.append("?") + url.append(url_quote(query_string, safe=":&%=+$!*'(),")) + + return uri_to_iri("".join(url)) + + +def get_content_length( + http_content_length: t.Union[str, None] = None, + http_transfer_encoding: t.Union[str, None] = "", +) -> t.Optional[int]: + """Returns the content length as an integer or ``None`` if + unavailable or chunked transfer encoding is used. + + :param http_content_length: The Content-Length HTTP header. + :param http_transfer_encoding: The Transfer-Encoding HTTP header. + + .. versionadded:: 2.2 + """ + if http_transfer_encoding == "chunked": + return None + + if http_content_length is not None: + try: + return max(0, int(http_content_length)) + except (ValueError, TypeError): + pass + return None diff --git a/.vtodo/Lib/site-packages/werkzeug/security.py b/.vtodo/Lib/site-packages/werkzeug/security.py new file mode 100644 index 0000000..18d0919 --- /dev/null +++ b/.vtodo/Lib/site-packages/werkzeug/security.py @@ -0,0 +1,140 @@ +import hashlib +import hmac +import os +import posixpath +import secrets +import typing as t + +if t.TYPE_CHECKING: + pass + +SALT_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" +DEFAULT_PBKDF2_ITERATIONS = 260000 + +_os_alt_seps: t.List[str] = list( + sep for sep in [os.path.sep, os.path.altsep] if sep is not None and sep != "/" +) + + +def gen_salt(length: int) -> str: + """Generate a random string of SALT_CHARS with specified ``length``.""" + if length <= 0: + raise ValueError("Salt length must be positive") + + return "".join(secrets.choice(SALT_CHARS) for _ in range(length)) + + +def _hash_internal(method: str, salt: str, password: str) -> t.Tuple[str, str]: + """Internal password hash helper. Supports plaintext without salt, + unsalted and salted passwords. In case salted passwords are used + hmac is used. + """ + if method == "plain": + return password, method + + salt = salt.encode("utf-8") + password = password.encode("utf-8") + + if method.startswith("pbkdf2:"): + if not salt: + raise ValueError("Salt is required for PBKDF2") + + args = method[7:].split(":") + + if len(args) not in (1, 2): + raise ValueError("Invalid number of arguments for PBKDF2") + + method = args.pop(0) + iterations = int(args[0] or 0) if args else DEFAULT_PBKDF2_ITERATIONS + return ( + hashlib.pbkdf2_hmac(method, password, salt, iterations).hex(), + f"pbkdf2:{method}:{iterations}", + ) + + if salt: + return hmac.new(salt, password, method).hexdigest(), method + + return hashlib.new(method, password).hexdigest(), method + + +def generate_password_hash( + password: str, method: str = "pbkdf2:sha256", salt_length: int = 16 +) -> str: + """Hash a password with the given method and salt with a string of + the given length. The format of the string returned includes the method + that was used so that :func:`check_password_hash` can check the hash. + + The format for the hashed string looks like this:: + + method$salt$hash + + This method can **not** generate unsalted passwords but it is possible + to set param method='plain' in order to enforce plaintext passwords. + If a salt is used, hmac is used internally to salt the password. + + If PBKDF2 is wanted it can be enabled by setting the method to + ``pbkdf2:method:iterations`` where iterations is optional:: + + pbkdf2:sha256:80000$salt$hash + pbkdf2:sha256$salt$hash + + :param password: the password to hash. + :param method: the hash method to use (one that hashlib supports). Can + optionally be in the format ``pbkdf2:method:iterations`` + to enable PBKDF2. + :param salt_length: the length of the salt in letters. + """ + salt = gen_salt(salt_length) if method != "plain" else "" + h, actual_method = _hash_internal(method, salt, password) + return f"{actual_method}${salt}${h}" + + +def check_password_hash(pwhash: str, password: str) -> bool: + """Check a password against a given salted and hashed password value. + In order to support unsalted legacy passwords this method supports + plain text passwords, md5 and sha1 hashes (both salted and unsalted). + + Returns `True` if the password matched, `False` otherwise. + + :param pwhash: a hashed string like returned by + :func:`generate_password_hash`. + :param password: the plaintext password to compare against the hash. + """ + if pwhash.count("$") < 2: + return False + + method, salt, hashval = pwhash.split("$", 2) + return hmac.compare_digest(_hash_internal(method, salt, password)[0], hashval) + + +def safe_join(directory: str, *pathnames: str) -> t.Optional[str]: + """Safely join zero or more untrusted path components to a base + directory to avoid escaping the base directory. + + :param directory: The trusted base directory. + :param pathnames: The untrusted path components relative to the + base directory. + :return: A safe path, otherwise ``None``. + """ + if not directory: + # Ensure we end up with ./path if directory="" is given, + # otherwise the first untrusted part could become trusted. + directory = "." + + parts = [directory] + + for filename in pathnames: + if filename != "": + filename = posixpath.normpath(filename) + + if ( + any(sep in filename for sep in _os_alt_seps) + or os.path.isabs(filename) + or filename == ".." + or filename.startswith("../") + ): + return None + + parts.append(filename) + + return posixpath.join(*parts) diff --git a/.vtodo/Lib/site-packages/werkzeug/serving.py b/.vtodo/Lib/site-packages/werkzeug/serving.py new file mode 100644 index 0000000..c482469 --- /dev/null +++ b/.vtodo/Lib/site-packages/werkzeug/serving.py @@ -0,0 +1,1098 @@ +"""A WSGI and HTTP server for use **during development only**. This +server is convenient to use, but is not designed to be particularly +stable, secure, or efficient. Use a dedicate WSGI server and HTTP +server when deploying to production. + +It provides features like interactive debugging and code reloading. Use +``run_simple`` to start the server. Put this in a ``run.py`` script: + +.. code-block:: python + + from myapp import create_app + from werkzeug import run_simple +""" +import errno +import io +import os +import socket +import socketserver +import sys +import typing as t +from datetime import datetime as dt +from datetime import timedelta +from datetime import timezone +from http.server import BaseHTTPRequestHandler +from http.server import HTTPServer + +from ._internal import _log +from ._internal import _wsgi_encoding_dance +from .exceptions import InternalServerError +from .urls import uri_to_iri +from .urls import url_parse +from .urls import url_unquote + +try: + import ssl +except ImportError: + + class _SslDummy: + def __getattr__(self, name: str) -> t.Any: + raise RuntimeError( # noqa: B904 + "SSL is unavailable because this Python runtime was not" + " compiled with SSL/TLS support." + ) + + ssl = _SslDummy() # type: ignore + +_log_add_style = True + +if os.name == "nt": + try: + __import__("colorama") + except ImportError: + _log_add_style = False + +can_fork = hasattr(os, "fork") + +if can_fork: + ForkingMixIn = socketserver.ForkingMixIn +else: + + class ForkingMixIn: # type: ignore + pass + + +try: + af_unix = socket.AF_UNIX +except AttributeError: + af_unix = None # type: ignore + +LISTEN_QUEUE = 128 + +_TSSLContextArg = t.Optional[ + t.Union["ssl.SSLContext", t.Tuple[str, t.Optional[str]], "te.Literal['adhoc']"] +] + +if t.TYPE_CHECKING: + import typing_extensions as te # noqa: F401 + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + from cryptography.hazmat.primitives.asymmetric.rsa import ( + RSAPrivateKeyWithSerialization, + ) + from cryptography.x509 import Certificate + + +class DechunkedInput(io.RawIOBase): + """An input stream that handles Transfer-Encoding 'chunked'""" + + def __init__(self, rfile: t.IO[bytes]) -> None: + self._rfile = rfile + self._done = False + self._len = 0 + + def readable(self) -> bool: + return True + + def read_chunk_len(self) -> int: + try: + line = self._rfile.readline().decode("latin1") + _len = int(line.strip(), 16) + except ValueError as e: + raise OSError("Invalid chunk header") from e + if _len < 0: + raise OSError("Negative chunk length not allowed") + return _len + + def readinto(self, buf: bytearray) -> int: # type: ignore + read = 0 + while not self._done and read < len(buf): + if self._len == 0: + # This is the first chunk or we fully consumed the previous + # one. Read the next length of the next chunk + self._len = self.read_chunk_len() + + if self._len == 0: + # Found the final chunk of size 0. The stream is now exhausted, + # but there is still a final newline that should be consumed + self._done = True + + if self._len > 0: + # There is data (left) in this chunk, so append it to the + # buffer. If this operation fully consumes the chunk, this will + # reset self._len to 0. + n = min(len(buf), self._len) + + # If (read + chunk size) becomes more than len(buf), buf will + # grow beyond the original size and read more data than + # required. So only read as much data as can fit in buf. + if read + n > len(buf): + buf[read:] = self._rfile.read(len(buf) - read) + self._len -= len(buf) - read + read = len(buf) + else: + buf[read : read + n] = self._rfile.read(n) + self._len -= n + read += n + + if self._len == 0: + # Skip the terminating newline of a chunk that has been fully + # consumed. This also applies to the 0-sized final chunk + terminator = self._rfile.readline() + if terminator not in (b"\n", b"\r\n", b"\r"): + raise OSError("Missing chunk terminating newline") + + return read + + +class WSGIRequestHandler(BaseHTTPRequestHandler): + """A request handler that implements WSGI dispatching.""" + + server: "BaseWSGIServer" + + @property + def server_version(self) -> str: # type: ignore + from . import __version__ + + return f"Werkzeug/{__version__}" + + def make_environ(self) -> "WSGIEnvironment": + request_url = url_parse(self.path) + url_scheme = "http" if self.server.ssl_context is None else "https" + + if not self.client_address: + self.client_address = ("", 0) + elif isinstance(self.client_address, str): + self.client_address = (self.client_address, 0) + + # If there was no scheme but the path started with two slashes, + # the first segment may have been incorrectly parsed as the + # netloc, prepend it to the path again. + if not request_url.scheme and request_url.netloc: + path_info = f"/{request_url.netloc}{request_url.path}" + else: + path_info = request_url.path + + path_info = url_unquote(path_info) + + environ: "WSGIEnvironment" = { + "wsgi.version": (1, 0), + "wsgi.url_scheme": url_scheme, + "wsgi.input": self.rfile, + "wsgi.errors": sys.stderr, + "wsgi.multithread": self.server.multithread, + "wsgi.multiprocess": self.server.multiprocess, + "wsgi.run_once": False, + "werkzeug.socket": self.connection, + "SERVER_SOFTWARE": self.server_version, + "REQUEST_METHOD": self.command, + "SCRIPT_NAME": "", + "PATH_INFO": _wsgi_encoding_dance(path_info), + "QUERY_STRING": _wsgi_encoding_dance(request_url.query), + # Non-standard, added by mod_wsgi, uWSGI + "REQUEST_URI": _wsgi_encoding_dance(self.path), + # Non-standard, added by gunicorn + "RAW_URI": _wsgi_encoding_dance(self.path), + "REMOTE_ADDR": self.address_string(), + "REMOTE_PORT": self.port_integer(), + "SERVER_NAME": self.server.server_address[0], + "SERVER_PORT": str(self.server.server_address[1]), + "SERVER_PROTOCOL": self.request_version, + } + + for key, value in self.headers.items(): + key = key.upper().replace("-", "_") + value = value.replace("\r\n", "") + if key not in ("CONTENT_TYPE", "CONTENT_LENGTH"): + key = f"HTTP_{key}" + if key in environ: + value = f"{environ[key]},{value}" + environ[key] = value + + if environ.get("HTTP_TRANSFER_ENCODING", "").strip().lower() == "chunked": + environ["wsgi.input_terminated"] = True + environ["wsgi.input"] = DechunkedInput(environ["wsgi.input"]) + + # Per RFC 2616, if the URL is absolute, use that as the host. + # We're using "has a scheme" to indicate an absolute URL. + if request_url.scheme and request_url.netloc: + environ["HTTP_HOST"] = request_url.netloc + + try: + # binary_form=False gives nicer information, but wouldn't be compatible with + # what Nginx or Apache could return. + peer_cert = self.connection.getpeercert( # type: ignore[attr-defined] + binary_form=True + ) + if peer_cert is not None: + # Nginx and Apache use PEM format. + environ["SSL_CLIENT_CERT"] = ssl.DER_cert_to_PEM_cert(peer_cert) + except ValueError: + # SSL handshake hasn't finished. + self.server.log("error", "Cannot fetch SSL peer certificate info") + except AttributeError: + # Not using TLS, the socket will not have getpeercert(). + pass + + return environ + + def run_wsgi(self) -> None: + if self.headers.get("Expect", "").lower().strip() == "100-continue": + self.wfile.write(b"HTTP/1.1 100 Continue\r\n\r\n") + + self.environ = environ = self.make_environ() + status_set: t.Optional[str] = None + headers_set: t.Optional[t.List[t.Tuple[str, str]]] = None + status_sent: t.Optional[str] = None + headers_sent: t.Optional[t.List[t.Tuple[str, str]]] = None + chunk_response: bool = False + + def write(data: bytes) -> None: + nonlocal status_sent, headers_sent, chunk_response + assert status_set is not None, "write() before start_response" + assert headers_set is not None, "write() before start_response" + if status_sent is None: + status_sent = status_set + headers_sent = headers_set + try: + code_str, msg = status_sent.split(None, 1) + except ValueError: + code_str, msg = status_sent, "" + code = int(code_str) + self.send_response(code, msg) + header_keys = set() + for key, value in headers_sent: + self.send_header(key, value) + header_keys.add(key.lower()) + + # Use chunked transfer encoding if there is no content + # length. Do not use for 1xx and 204 responses. 304 + # responses and HEAD requests are also excluded, which + # is the more conservative behavior and matches other + # parts of the code. + # https://httpwg.org/specs/rfc7230.html#rfc.section.3.3.1 + if ( + not ( + "content-length" in header_keys + or environ["REQUEST_METHOD"] == "HEAD" + or (100 <= code < 200) + or code in {204, 304} + ) + and self.protocol_version >= "HTTP/1.1" + ): + chunk_response = True + self.send_header("Transfer-Encoding", "chunked") + + # Always close the connection. This disables HTTP/1.1 + # keep-alive connections. They aren't handled well by + # Python's http.server because it doesn't know how to + # drain the stream before the next request line. + self.send_header("Connection", "close") + self.end_headers() + + assert isinstance(data, bytes), "applications must write bytes" + + if data: + if chunk_response: + self.wfile.write(hex(len(data))[2:].encode()) + self.wfile.write(b"\r\n") + + self.wfile.write(data) + + if chunk_response: + self.wfile.write(b"\r\n") + + self.wfile.flush() + + def start_response(status, headers, exc_info=None): # type: ignore + nonlocal status_set, headers_set + if exc_info: + try: + if headers_sent: + raise exc_info[1].with_traceback(exc_info[2]) + finally: + exc_info = None + elif headers_set: + raise AssertionError("Headers already set") + status_set = status + headers_set = headers + return write + + def execute(app: "WSGIApplication") -> None: + application_iter = app(environ, start_response) + try: + for data in application_iter: + write(data) + if not headers_sent: + write(b"") + if chunk_response: + self.wfile.write(b"0\r\n\r\n") + finally: + if hasattr(application_iter, "close"): + application_iter.close() # type: ignore + + try: + execute(self.server.app) + except (ConnectionError, socket.timeout) as e: + self.connection_dropped(e, environ) + except Exception as e: + if self.server.passthrough_errors: + raise + + if status_sent is not None and chunk_response: + self.close_connection = True + + try: + # if we haven't yet sent the headers but they are set + # we roll back to be able to set them again. + if status_sent is None: + status_set = None + headers_set = None + execute(InternalServerError()) + except Exception: + pass + + from .debug.tbtools import DebugTraceback + + msg = DebugTraceback(e).render_traceback_text() + self.server.log("error", f"Error on request:\n{msg}") + + def handle(self) -> None: + """Handles a request ignoring dropped connections.""" + try: + super().handle() + except (ConnectionError, socket.timeout) as e: + self.connection_dropped(e) + except Exception as e: + if self.server.ssl_context is not None and is_ssl_error(e): + self.log_error("SSL error occurred: %s", e) + else: + raise + + def connection_dropped( + self, error: BaseException, environ: t.Optional["WSGIEnvironment"] = None + ) -> None: + """Called if the connection was closed by the client. By default + nothing happens. + """ + + def __getattr__(self, name: str) -> t.Any: + # All HTTP methods are handled by run_wsgi. + if name.startswith("do_"): + return self.run_wsgi + + # All other attributes are forwarded to the base class. + return getattr(super(), name) + + def address_string(self) -> str: + if getattr(self, "environ", None): + return self.environ["REMOTE_ADDR"] # type: ignore + + if not self.client_address: + return "" + + return self.client_address[0] + + def port_integer(self) -> int: + return self.client_address[1] + + def log_request( + self, code: t.Union[int, str] = "-", size: t.Union[int, str] = "-" + ) -> None: + try: + path = uri_to_iri(self.path) + msg = f"{self.command} {path} {self.request_version}" + except AttributeError: + # path isn't set if the requestline was bad + msg = self.requestline + + code = str(code) + + if code[0] == "1": # 1xx - Informational + msg = _ansi_style(msg, "bold") + elif code == "200": # 2xx - Success + pass + elif code == "304": # 304 - Resource Not Modified + msg = _ansi_style(msg, "cyan") + elif code[0] == "3": # 3xx - Redirection + msg = _ansi_style(msg, "green") + elif code == "404": # 404 - Resource Not Found + msg = _ansi_style(msg, "yellow") + elif code[0] == "4": # 4xx - Client Error + msg = _ansi_style(msg, "bold", "red") + else: # 5xx, or any other response + msg = _ansi_style(msg, "bold", "magenta") + + self.log("info", '"%s" %s %s', msg, code, size) + + def log_error(self, format: str, *args: t.Any) -> None: + self.log("error", format, *args) + + def log_message(self, format: str, *args: t.Any) -> None: + self.log("info", format, *args) + + def log(self, type: str, message: str, *args: t.Any) -> None: + _log( + type, + f"{self.address_string()} - - [{self.log_date_time_string()}] {message}\n", + *args, + ) + + +def _ansi_style(value: str, *styles: str) -> str: + if not _log_add_style: + return value + + codes = { + "bold": 1, + "red": 31, + "green": 32, + "yellow": 33, + "magenta": 35, + "cyan": 36, + } + + for style in styles: + value = f"\x1b[{codes[style]}m{value}" + + return f"{value}\x1b[0m" + + +def generate_adhoc_ssl_pair( + cn: t.Optional[str] = None, +) -> t.Tuple["Certificate", "RSAPrivateKeyWithSerialization"]: + try: + from cryptography import x509 + from cryptography.x509.oid import NameOID + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.asymmetric import rsa + except ImportError: + raise TypeError( + "Using ad-hoc certificates requires the cryptography library." + ) from None + + backend = default_backend() + pkey = rsa.generate_private_key( + public_exponent=65537, key_size=2048, backend=backend + ) + + # pretty damn sure that this is not actually accepted by anyone + if cn is None: + cn = "*" + + subject = x509.Name( + [ + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Dummy Certificate"), + x509.NameAttribute(NameOID.COMMON_NAME, cn), + ] + ) + + backend = default_backend() + cert = ( + x509.CertificateBuilder() + .subject_name(subject) + .issuer_name(subject) + .public_key(pkey.public_key()) + .serial_number(x509.random_serial_number()) + .not_valid_before(dt.now(timezone.utc)) + .not_valid_after(dt.now(timezone.utc) + timedelta(days=365)) + .add_extension(x509.ExtendedKeyUsage([x509.OID_SERVER_AUTH]), critical=False) + .add_extension(x509.SubjectAlternativeName([x509.DNSName(cn)]), critical=False) + .sign(pkey, hashes.SHA256(), backend) + ) + return cert, pkey + + +def make_ssl_devcert( + base_path: str, host: t.Optional[str] = None, cn: t.Optional[str] = None +) -> t.Tuple[str, str]: + """Creates an SSL key for development. This should be used instead of + the ``'adhoc'`` key which generates a new cert on each server start. + It accepts a path for where it should store the key and cert and + either a host or CN. If a host is given it will use the CN + ``*.host/CN=host``. + + For more information see :func:`run_simple`. + + .. versionadded:: 0.9 + + :param base_path: the path to the certificate and key. The extension + ``.crt`` is added for the certificate, ``.key`` is + added for the key. + :param host: the name of the host. This can be used as an alternative + for the `cn`. + :param cn: the `CN` to use. + """ + + if host is not None: + cn = f"*.{host}/CN={host}" + cert, pkey = generate_adhoc_ssl_pair(cn=cn) + + from cryptography.hazmat.primitives import serialization + + cert_file = f"{base_path}.crt" + pkey_file = f"{base_path}.key" + + with open(cert_file, "wb") as f: + f.write(cert.public_bytes(serialization.Encoding.PEM)) + with open(pkey_file, "wb") as f: + f.write( + pkey.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption(), + ) + ) + + return cert_file, pkey_file + + +def generate_adhoc_ssl_context() -> "ssl.SSLContext": + """Generates an adhoc SSL context for the development server.""" + import tempfile + import atexit + + cert, pkey = generate_adhoc_ssl_pair() + + from cryptography.hazmat.primitives import serialization + + cert_handle, cert_file = tempfile.mkstemp() + pkey_handle, pkey_file = tempfile.mkstemp() + atexit.register(os.remove, pkey_file) + atexit.register(os.remove, cert_file) + + os.write(cert_handle, cert.public_bytes(serialization.Encoding.PEM)) + os.write( + pkey_handle, + pkey.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption(), + ), + ) + + os.close(cert_handle) + os.close(pkey_handle) + ctx = load_ssl_context(cert_file, pkey_file) + return ctx + + +def load_ssl_context( + cert_file: str, pkey_file: t.Optional[str] = None, protocol: t.Optional[int] = None +) -> "ssl.SSLContext": + """Loads SSL context from cert/private key files and optional protocol. + Many parameters are directly taken from the API of + :py:class:`ssl.SSLContext`. + + :param cert_file: Path of the certificate to use. + :param pkey_file: Path of the private key to use. If not given, the key + will be obtained from the certificate file. + :param protocol: A ``PROTOCOL`` constant from the :mod:`ssl` module. + Defaults to :data:`ssl.PROTOCOL_TLS_SERVER`. + """ + if protocol is None: + protocol = ssl.PROTOCOL_TLS_SERVER + + ctx = ssl.SSLContext(protocol) + ctx.load_cert_chain(cert_file, pkey_file) + return ctx + + +def is_ssl_error(error: t.Optional[Exception] = None) -> bool: + """Checks if the given error (or the current one) is an SSL error.""" + if error is None: + error = t.cast(Exception, sys.exc_info()[1]) + return isinstance(error, ssl.SSLError) + + +def select_address_family(host: str, port: int) -> socket.AddressFamily: + """Return ``AF_INET4``, ``AF_INET6``, or ``AF_UNIX`` depending on + the host and port.""" + if host.startswith("unix://"): + return socket.AF_UNIX + elif ":" in host and hasattr(socket, "AF_INET6"): + return socket.AF_INET6 + return socket.AF_INET + + +def get_sockaddr( + host: str, port: int, family: socket.AddressFamily +) -> t.Union[t.Tuple[str, int], str]: + """Return a fully qualified socket address that can be passed to + :func:`socket.bind`.""" + if family == af_unix: + return host.split("://", 1)[1] + try: + res = socket.getaddrinfo( + host, port, family, socket.SOCK_STREAM, socket.IPPROTO_TCP + ) + except socket.gaierror: + return host, port + return res[0][4] # type: ignore + + +def get_interface_ip(family: socket.AddressFamily) -> str: + """Get the IP address of an external interface. Used when binding to + 0.0.0.0 or ::1 to show a more useful URL. + + :meta private: + """ + # arbitrary private address + host = "fd31:f903:5ab5:1::1" if family == socket.AF_INET6 else "10.253.155.219" + + with socket.socket(family, socket.SOCK_DGRAM) as s: + try: + s.connect((host, 58162)) + except OSError: + return "::1" if family == socket.AF_INET6 else "127.0.0.1" + + return s.getsockname()[0] # type: ignore + + +class BaseWSGIServer(HTTPServer): + """A WSGI server that that handles one request at a time. + + Use :func:`make_server` to create a server instance. + """ + + multithread = False + multiprocess = False + request_queue_size = LISTEN_QUEUE + + def __init__( + self, + host: str, + port: int, + app: "WSGIApplication", + handler: t.Optional[t.Type[WSGIRequestHandler]] = None, + passthrough_errors: bool = False, + ssl_context: t.Optional[_TSSLContextArg] = None, + fd: t.Optional[int] = None, + ) -> None: + if handler is None: + handler = WSGIRequestHandler + + # If the handler doesn't directly set a protocol version and + # thread or process workers are used, then allow chunked + # responses and keep-alive connections by enabling HTTP/1.1. + if "protocol_version" not in vars(handler) and ( + self.multithread or self.multiprocess + ): + handler.protocol_version = "HTTP/1.1" + + self.host = host + self.port = port + self.app = app + self.passthrough_errors = passthrough_errors + + self.address_family = address_family = select_address_family(host, port) + server_address = get_sockaddr(host, int(port), address_family) + + # Remove a leftover Unix socket file from a previous run. Don't + # remove a file that was set up by run_simple. + if address_family == af_unix and fd is None: + server_address = t.cast(str, server_address) + + if os.path.exists(server_address): + os.unlink(server_address) + + # Bind and activate will be handled manually, it should only + # happen if we're not using a socket that was already set up. + super().__init__( + server_address, # type: ignore[arg-type] + handler, + bind_and_activate=False, + ) + + if fd is None: + # No existing socket descriptor, do bind_and_activate=True. + try: + self.server_bind() + self.server_activate() + except BaseException: + self.server_close() + raise + else: + # Use the passed in socket directly. + self.socket = socket.fromfd(fd, address_family, socket.SOCK_STREAM) + self.server_address = self.socket.getsockname() + + if address_family != af_unix: + # If port was 0, this will record the bound port. + self.port = self.server_address[1] + + if ssl_context is not None: + if isinstance(ssl_context, tuple): + ssl_context = load_ssl_context(*ssl_context) + elif ssl_context == "adhoc": + ssl_context = generate_adhoc_ssl_context() + + self.socket = ssl_context.wrap_socket(self.socket, server_side=True) + self.ssl_context: t.Optional["ssl.SSLContext"] = ssl_context + else: + self.ssl_context = None + + def log(self, type: str, message: str, *args: t.Any) -> None: + _log(type, message, *args) + + def serve_forever(self, poll_interval: float = 0.5) -> None: + try: + super().serve_forever(poll_interval=poll_interval) + except KeyboardInterrupt: + pass + finally: + self.server_close() + + def handle_error( + self, request: t.Any, client_address: t.Union[t.Tuple[str, int], str] + ) -> None: + if self.passthrough_errors: + raise + + return super().handle_error(request, client_address) + + def log_startup(self) -> None: + """Show information about the address when starting the server.""" + dev_warning = ( + "WARNING: This is a development server. Do not use it in a production" + " deployment. Use a production WSGI server instead." + ) + dev_warning = _ansi_style(dev_warning, "bold", "red") + messages = [dev_warning] + + if self.address_family == af_unix: + messages.append(f" * Running on {self.host}") + else: + scheme = "http" if self.ssl_context is None else "https" + display_hostname = self.host + + if self.host in {"0.0.0.0", "::"}: + messages.append(f" * Running on all addresses ({self.host})") + + if self.host == "0.0.0.0": + localhost = "127.0.0.1" + display_hostname = get_interface_ip(socket.AF_INET) + else: + localhost = "[::1]" + display_hostname = get_interface_ip(socket.AF_INET6) + + messages.append(f" * Running on {scheme}://{localhost}:{self.port}") + + if ":" in display_hostname: + display_hostname = f"[{display_hostname}]" + + messages.append(f" * Running on {scheme}://{display_hostname}:{self.port}") + + _log("info", "\n".join(messages)) + + +class ThreadedWSGIServer(socketserver.ThreadingMixIn, BaseWSGIServer): + """A WSGI server that handles concurrent requests in separate + threads. + + Use :func:`make_server` to create a server instance. + """ + + multithread = True + daemon_threads = True + + +class ForkingWSGIServer(ForkingMixIn, BaseWSGIServer): + """A WSGI server that handles concurrent requests in separate forked + processes. + + Use :func:`make_server` to create a server instance. + """ + + multiprocess = True + + def __init__( + self, + host: str, + port: int, + app: "WSGIApplication", + processes: int = 40, + handler: t.Optional[t.Type[WSGIRequestHandler]] = None, + passthrough_errors: bool = False, + ssl_context: t.Optional[_TSSLContextArg] = None, + fd: t.Optional[int] = None, + ) -> None: + if not can_fork: + raise ValueError("Your platform does not support forking.") + + super().__init__(host, port, app, handler, passthrough_errors, ssl_context, fd) + self.max_children = processes + + +def make_server( + host: str, + port: int, + app: "WSGIApplication", + threaded: bool = False, + processes: int = 1, + request_handler: t.Optional[t.Type[WSGIRequestHandler]] = None, + passthrough_errors: bool = False, + ssl_context: t.Optional[_TSSLContextArg] = None, + fd: t.Optional[int] = None, +) -> BaseWSGIServer: + """Create an appropriate WSGI server instance based on the value of + ``threaded`` and ``processes``. + + This is called from :func:`run_simple`, but can be used separately + to have access to the server object, such as to run it in a separate + thread. + + See :func:`run_simple` for parameter docs. + """ + if threaded and processes > 1: + raise ValueError("Cannot have a multi-thread and multi-process server.") + + if threaded: + return ThreadedWSGIServer( + host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd + ) + + if processes > 1: + return ForkingWSGIServer( + host, + port, + app, + processes, + request_handler, + passthrough_errors, + ssl_context, + fd=fd, + ) + + return BaseWSGIServer( + host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd + ) + + +def is_running_from_reloader() -> bool: + """Check if the server is running as a subprocess within the + Werkzeug reloader. + + .. versionadded:: 0.10 + """ + return os.environ.get("WERKZEUG_RUN_MAIN") == "true" + + +def prepare_socket(hostname: str, port: int) -> socket.socket: + """Prepare a socket for use by the WSGI server and reloader. + + The socket is marked inheritable so that it can be kept across + reloads instead of breaking connections. + + Catch errors during bind and show simpler error messages. For + "address already in use", show instructions for resolving the issue, + with special instructions for macOS. + + This is called from :func:`run_simple`, but can be used separately + to control server creation with :func:`make_server`. + """ + address_family = select_address_family(hostname, port) + server_address = get_sockaddr(hostname, port, address_family) + s = socket.socket(address_family, socket.SOCK_STREAM) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.set_inheritable(True) + + # Remove the socket file if it already exists. + if address_family == af_unix: + server_address = t.cast(str, server_address) + + if os.path.exists(server_address): + os.unlink(server_address) + + # Catch connection issues and show them without the traceback. Show + # extra instructions for address not found, and for macOS. + try: + s.bind(server_address) + except OSError as e: + print(e.strerror, file=sys.stderr) + + if e.errno == errno.EADDRINUSE: + print( + f"Port {port} is in use by another program. Either" + " identify and stop that program, or start the" + " server with a different port.", + file=sys.stderr, + ) + + if sys.platform == "darwin" and port == 5000: + print( + "On macOS, try disabling the 'AirPlay Receiver'" + " service from System Preferences -> Sharing.", + file=sys.stderr, + ) + + sys.exit(1) + + s.listen(LISTEN_QUEUE) + return s + + +def run_simple( + hostname: str, + port: int, + application: "WSGIApplication", + use_reloader: bool = False, + use_debugger: bool = False, + use_evalex: bool = True, + extra_files: t.Optional[t.Iterable[str]] = None, + exclude_patterns: t.Optional[t.Iterable[str]] = None, + reloader_interval: int = 1, + reloader_type: str = "auto", + threaded: bool = False, + processes: int = 1, + request_handler: t.Optional[t.Type[WSGIRequestHandler]] = None, + static_files: t.Optional[t.Dict[str, t.Union[str, t.Tuple[str, str]]]] = None, + passthrough_errors: bool = False, + ssl_context: t.Optional[_TSSLContextArg] = None, +) -> None: + """Start a development server for a WSGI application. Various + optional features can be enabled. + + .. warning:: + + Do not use the development server when deploying to production. + It is intended for use only during local development. It is not + designed to be particularly efficient, stable, or secure. + + :param hostname: The host to bind to, for example ``'localhost'``. + Can be a domain, IPv4 or IPv6 address, or file path starting + with ``unix://`` for a Unix socket. + :param port: The port to bind to, for example ``8080``. Using ``0`` + tells the OS to pick a random free port. + :param application: The WSGI application to run. + :param use_reloader: Use a reloader process to restart the server + process when files are changed. + :param use_debugger: Use Werkzeug's debugger, which will show + formatted tracebacks on unhandled exceptions. + :param use_evalex: Make the debugger interactive. A Python terminal + can be opened for any frame in the traceback. Some protection is + provided by requiring a PIN, but this should never be enabled + on a publicly visible server. + :param extra_files: The reloader will watch these files for changes + in addition to Python modules. For example, watch a + configuration file. + :param exclude_patterns: The reloader will ignore changes to any + files matching these :mod:`fnmatch` patterns. For example, + ignore cache files. + :param reloader_interval: How often the reloader tries to check for + changes. + :param reloader_type: The reloader to use. The ``'stat'`` reloader + is built in, but may require significant CPU to watch files. The + ``'watchdog'`` reloader is much more efficient but requires + installing the ``watchdog`` package first. + :param threaded: Handle concurrent requests using threads. Cannot be + used with ``processes``. + :param processes: Handle concurrent requests using up to this number + of processes. Cannot be used with ``threaded``. + :param request_handler: Use a different + :class:`~BaseHTTPServer.BaseHTTPRequestHandler` subclass to + handle requests. + :param static_files: A dict mapping URL prefixes to directories to + serve static files from using + :class:`~werkzeug.middleware.SharedDataMiddleware`. + :param passthrough_errors: Don't catch unhandled exceptions at the + server level, let the serve crash instead. If ``use_debugger`` + is enabled, the debugger will still catch such errors. + :param ssl_context: Configure TLS to serve over HTTPS. Can be an + :class:`ssl.SSLContext` object, a ``(cert_file, key_file)`` + tuple to create a typical context, or the string ``'adhoc'`` to + generate a temporary self-signed certificate. + + .. versionchanged:: 2.1 + Instructions are shown for dealing with an "address already in + use" error. + + .. versionchanged:: 2.1 + Running on ``0.0.0.0`` or ``::`` shows the loopback IP in + addition to a real IP. + + .. versionchanged:: 2.1 + The command-line interface was removed. + + .. versionchanged:: 2.0 + Running on ``0.0.0.0`` or ``::`` shows a real IP address that + was bound as well as a warning not to run the development server + in production. + + .. versionchanged:: 2.0 + The ``exclude_patterns`` parameter was added. + + .. versionchanged:: 0.15 + Bind to a Unix socket by passing a ``hostname`` that starts with + ``unix://``. + + .. versionchanged:: 0.10 + Improved the reloader and added support for changing the backend + through the ``reloader_type`` parameter. + + .. versionchanged:: 0.9 + A command-line interface was added. + + .. versionchanged:: 0.8 + ``ssl_context`` can be a tuple of paths to the certificate and + private key files. + + .. versionchanged:: 0.6 + The ``ssl_context`` parameter was added. + + .. versionchanged:: 0.5 + The ``static_files`` and ``passthrough_errors`` parameters were + added. + """ + if not isinstance(port, int): + raise TypeError("port must be an integer") + + if static_files: + from .middleware.shared_data import SharedDataMiddleware + + application = SharedDataMiddleware(application, static_files) + + if use_debugger: + from .debug import DebuggedApplication + + application = DebuggedApplication(application, evalex=use_evalex) + + if not is_running_from_reloader(): + s = prepare_socket(hostname, port) + fd = s.fileno() + # Silence a ResourceWarning about an unclosed socket. This object is no longer + # used, the server will create another with fromfd. + s.detach() + os.environ["WERKZEUG_SERVER_FD"] = str(fd) + else: + fd = int(os.environ["WERKZEUG_SERVER_FD"]) + + srv = make_server( + hostname, + port, + application, + threaded, + processes, + request_handler, + passthrough_errors, + ssl_context, + fd=fd, + ) + + if not is_running_from_reloader(): + srv.log_startup() + _log("info", _ansi_style("Press CTRL+C to quit", "yellow")) + + if use_reloader: + from ._reloader import run_with_reloader + + run_with_reloader( + srv.serve_forever, + extra_files=extra_files, + exclude_patterns=exclude_patterns, + interval=reloader_interval, + reloader_type=reloader_type, + ) + else: + srv.serve_forever() diff --git a/.vtodo/Lib/site-packages/werkzeug/test.py b/.vtodo/Lib/site-packages/werkzeug/test.py new file mode 100644 index 0000000..edb4d4a --- /dev/null +++ b/.vtodo/Lib/site-packages/werkzeug/test.py @@ -0,0 +1,1337 @@ +import mimetypes +import sys +import typing as t +from collections import defaultdict +from datetime import datetime +from datetime import timedelta +from http.cookiejar import CookieJar +from io import BytesIO +from itertools import chain +from random import random +from tempfile import TemporaryFile +from time import time +from urllib.request import Request as _UrllibRequest + +from ._internal import _get_environ +from ._internal import _make_encode_wrapper +from ._internal import _wsgi_decoding_dance +from ._internal import _wsgi_encoding_dance +from .datastructures import Authorization +from .datastructures import CallbackDict +from .datastructures import CombinedMultiDict +from .datastructures import EnvironHeaders +from .datastructures import FileMultiDict +from .datastructures import Headers +from .datastructures import MultiDict +from .http import dump_cookie +from .http import dump_options_header +from .http import parse_options_header +from .sansio.multipart import Data +from .sansio.multipart import Epilogue +from .sansio.multipart import Field +from .sansio.multipart import File +from .sansio.multipart import MultipartEncoder +from .sansio.multipart import Preamble +from .urls import iri_to_uri +from .urls import url_encode +from .urls import url_fix +from .urls import url_parse +from .urls import url_unparse +from .urls import url_unquote +from .utils import cached_property +from .utils import get_content_type +from .wrappers.request import Request +from .wrappers.response import Response +from .wsgi import ClosingIterator +from .wsgi import get_current_url + +if t.TYPE_CHECKING: + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + + +def stream_encode_multipart( + data: t.Mapping[str, t.Any], + use_tempfile: bool = True, + threshold: int = 1024 * 500, + boundary: t.Optional[str] = None, + charset: str = "utf-8", +) -> t.Tuple[t.IO[bytes], int, str]: + """Encode a dict of values (either strings or file descriptors or + :class:`FileStorage` objects.) into a multipart encoded string stored + in a file descriptor. + """ + if boundary is None: + boundary = f"---------------WerkzeugFormPart_{time()}{random()}" + + stream: t.IO[bytes] = BytesIO() + total_length = 0 + on_disk = False + write_binary: t.Callable[[bytes], int] + + if use_tempfile: + + def write_binary(s: bytes) -> int: + nonlocal stream, total_length, on_disk + + if on_disk: + return stream.write(s) + else: + length = len(s) + + if length + total_length <= threshold: + stream.write(s) + else: + new_stream = t.cast(t.IO[bytes], TemporaryFile("wb+")) + new_stream.write(stream.getvalue()) # type: ignore + new_stream.write(s) + stream = new_stream + on_disk = True + + total_length += length + return length + + else: + write_binary = stream.write + + encoder = MultipartEncoder(boundary.encode()) + write_binary(encoder.send_event(Preamble(data=b""))) + for key, value in _iter_data(data): + reader = getattr(value, "read", None) + if reader is not None: + filename = getattr(value, "filename", getattr(value, "name", None)) + content_type = getattr(value, "content_type", None) + if content_type is None: + content_type = ( + filename + and mimetypes.guess_type(filename)[0] + or "application/octet-stream" + ) + headers = Headers([("Content-Type", content_type)]) + if filename is None: + write_binary(encoder.send_event(Field(name=key, headers=headers))) + else: + write_binary( + encoder.send_event( + File(name=key, filename=filename, headers=headers) + ) + ) + while True: + chunk = reader(16384) + + if not chunk: + break + + write_binary(encoder.send_event(Data(data=chunk, more_data=True))) + else: + if not isinstance(value, str): + value = str(value) + write_binary(encoder.send_event(Field(name=key, headers=Headers()))) + write_binary( + encoder.send_event(Data(data=value.encode(charset), more_data=False)) + ) + + write_binary(encoder.send_event(Epilogue(data=b""))) + + length = stream.tell() + stream.seek(0) + return stream, length, boundary + + +def encode_multipart( + values: t.Mapping[str, t.Any], + boundary: t.Optional[str] = None, + charset: str = "utf-8", +) -> t.Tuple[str, bytes]: + """Like `stream_encode_multipart` but returns a tuple in the form + (``boundary``, ``data``) where data is bytes. + """ + stream, length, boundary = stream_encode_multipart( + values, use_tempfile=False, boundary=boundary, charset=charset + ) + return boundary, stream.read() + + +class _TestCookieHeaders: + """A headers adapter for cookielib""" + + def __init__(self, headers: t.Union[Headers, t.List[t.Tuple[str, str]]]) -> None: + self.headers = headers + + def getheaders(self, name: str) -> t.Iterable[str]: + headers = [] + name = name.lower() + for k, v in self.headers: + if k.lower() == name: + headers.append(v) + return headers + + def get_all( + self, name: str, default: t.Optional[t.Iterable[str]] = None + ) -> t.Iterable[str]: + headers = self.getheaders(name) + + if not headers: + return default # type: ignore + + return headers + + +class _TestCookieResponse: + """Something that looks like a httplib.HTTPResponse, but is actually just an + adapter for our test responses to make them available for cookielib. + """ + + def __init__(self, headers: t.Union[Headers, t.List[t.Tuple[str, str]]]) -> None: + self.headers = _TestCookieHeaders(headers) + + def info(self) -> _TestCookieHeaders: + return self.headers + + +class _TestCookieJar(CookieJar): + """A cookielib.CookieJar modified to inject and read cookie headers from + and to wsgi environments, and wsgi application responses. + """ + + def inject_wsgi(self, environ: "WSGIEnvironment") -> None: + """Inject the cookies as client headers into the server's wsgi + environment. + """ + cvals = [f"{c.name}={c.value}" for c in self] + + if cvals: + environ["HTTP_COOKIE"] = "; ".join(cvals) + else: + environ.pop("HTTP_COOKIE", None) + + def extract_wsgi( + self, + environ: "WSGIEnvironment", + headers: t.Union[Headers, t.List[t.Tuple[str, str]]], + ) -> None: + """Extract the server's set-cookie headers as cookies into the + cookie jar. + """ + self.extract_cookies( + _TestCookieResponse(headers), # type: ignore + _UrllibRequest(get_current_url(environ)), + ) + + +def _iter_data(data: t.Mapping[str, t.Any]) -> t.Iterator[t.Tuple[str, t.Any]]: + """Iterate over a mapping that might have a list of values, yielding + all key, value pairs. Almost like iter_multi_items but only allows + lists, not tuples, of values so tuples can be used for files. + """ + if isinstance(data, MultiDict): + yield from data.items(multi=True) + else: + for key, value in data.items(): + if isinstance(value, list): + for v in value: + yield key, v + else: + yield key, value + + +_TAnyMultiDict = t.TypeVar("_TAnyMultiDict", bound=MultiDict) + + +class EnvironBuilder: + """This class can be used to conveniently create a WSGI environment + for testing purposes. It can be used to quickly create WSGI environments + or request objects from arbitrary data. + + The signature of this class is also used in some other places as of + Werkzeug 0.5 (:func:`create_environ`, :meth:`Response.from_values`, + :meth:`Client.open`). Because of this most of the functionality is + available through the constructor alone. + + Files and regular form data can be manipulated independently of each + other with the :attr:`form` and :attr:`files` attributes, but are + passed with the same argument to the constructor: `data`. + + `data` can be any of these values: + + - a `str` or `bytes` object: The object is converted into an + :attr:`input_stream`, the :attr:`content_length` is set and you have to + provide a :attr:`content_type`. + - a `dict` or :class:`MultiDict`: The keys have to be strings. The values + have to be either any of the following objects, or a list of any of the + following objects: + + - a :class:`file`-like object: These are converted into + :class:`FileStorage` objects automatically. + - a `tuple`: The :meth:`~FileMultiDict.add_file` method is called + with the key and the unpacked `tuple` items as positional + arguments. + - a `str`: The string is set as form data for the associated key. + - a file-like object: The object content is loaded in memory and then + handled like a regular `str` or a `bytes`. + + :param path: the path of the request. In the WSGI environment this will + end up as `PATH_INFO`. If the `query_string` is not defined + and there is a question mark in the `path` everything after + it is used as query string. + :param base_url: the base URL is a URL that is used to extract the WSGI + URL scheme, host (server name + server port) and the + script root (`SCRIPT_NAME`). + :param query_string: an optional string or dict with URL parameters. + :param method: the HTTP method to use, defaults to `GET`. + :param input_stream: an optional input stream. Do not specify this and + `data`. As soon as an input stream is set you can't + modify :attr:`args` and :attr:`files` unless you + set the :attr:`input_stream` to `None` again. + :param content_type: The content type for the request. As of 0.5 you + don't have to provide this when specifying files + and form data via `data`. + :param content_length: The content length for the request. You don't + have to specify this when providing data via + `data`. + :param errors_stream: an optional error stream that is used for + `wsgi.errors`. Defaults to :data:`stderr`. + :param multithread: controls `wsgi.multithread`. Defaults to `False`. + :param multiprocess: controls `wsgi.multiprocess`. Defaults to `False`. + :param run_once: controls `wsgi.run_once`. Defaults to `False`. + :param headers: an optional list or :class:`Headers` object of headers. + :param data: a string or dict of form data or a file-object. + See explanation above. + :param json: An object to be serialized and assigned to ``data``. + Defaults the content type to ``"application/json"``. + Serialized with the function assigned to :attr:`json_dumps`. + :param environ_base: an optional dict of environment defaults. + :param environ_overrides: an optional dict of environment overrides. + :param charset: the charset used to encode string data. + :param auth: An authorization object to use for the + ``Authorization`` header value. A ``(username, password)`` tuple + is a shortcut for ``Basic`` authorization. + + .. versionchanged:: 2.1 + ``CONTENT_TYPE`` and ``CONTENT_LENGTH`` are not duplicated as + header keys in the environ. + + .. versionchanged:: 2.0 + ``REQUEST_URI`` and ``RAW_URI`` is the full raw URI including + the query string, not only the path. + + .. versionchanged:: 2.0 + The default :attr:`request_class` is ``Request`` instead of + ``BaseRequest``. + + .. versionadded:: 2.0 + Added the ``auth`` parameter. + + .. versionadded:: 0.15 + The ``json`` param and :meth:`json_dumps` method. + + .. versionadded:: 0.15 + The environ has keys ``REQUEST_URI`` and ``RAW_URI`` containing + the path before percent-decoding. This is not part of the WSGI + PEP, but many WSGI servers include it. + + .. versionchanged:: 0.6 + ``path`` and ``base_url`` can now be unicode strings that are + encoded with :func:`iri_to_uri`. + """ + + #: the server protocol to use. defaults to HTTP/1.1 + server_protocol = "HTTP/1.1" + + #: the wsgi version to use. defaults to (1, 0) + wsgi_version = (1, 0) + + #: The default request class used by :meth:`get_request`. + request_class = Request + + import json + + #: The serialization function used when ``json`` is passed. + json_dumps = staticmethod(json.dumps) + del json + + _args: t.Optional[MultiDict] + _query_string: t.Optional[str] + _input_stream: t.Optional[t.IO[bytes]] + _form: t.Optional[MultiDict] + _files: t.Optional[FileMultiDict] + + def __init__( + self, + path: str = "/", + base_url: t.Optional[str] = None, + query_string: t.Optional[t.Union[t.Mapping[str, str], str]] = None, + method: str = "GET", + input_stream: t.Optional[t.IO[bytes]] = None, + content_type: t.Optional[str] = None, + content_length: t.Optional[int] = None, + errors_stream: t.Optional[t.IO[str]] = None, + multithread: bool = False, + multiprocess: bool = False, + run_once: bool = False, + headers: t.Optional[t.Union[Headers, t.Iterable[t.Tuple[str, str]]]] = None, + data: t.Optional[ + t.Union[t.IO[bytes], str, bytes, t.Mapping[str, t.Any]] + ] = None, + environ_base: t.Optional[t.Mapping[str, t.Any]] = None, + environ_overrides: t.Optional[t.Mapping[str, t.Any]] = None, + charset: str = "utf-8", + mimetype: t.Optional[str] = None, + json: t.Optional[t.Mapping[str, t.Any]] = None, + auth: t.Optional[t.Union[Authorization, t.Tuple[str, str]]] = None, + ) -> None: + path_s = _make_encode_wrapper(path) + if query_string is not None and path_s("?") in path: + raise ValueError("Query string is defined in the path and as an argument") + request_uri = url_parse(path) + if query_string is None and path_s("?") in path: + query_string = request_uri.query + self.charset = charset + self.path = iri_to_uri(request_uri.path) + self.request_uri = path + if base_url is not None: + base_url = url_fix(iri_to_uri(base_url, charset), charset) + self.base_url = base_url # type: ignore + if isinstance(query_string, (bytes, str)): + self.query_string = query_string + else: + if query_string is None: + query_string = MultiDict() + elif not isinstance(query_string, MultiDict): + query_string = MultiDict(query_string) + self.args = query_string + self.method = method + if headers is None: + headers = Headers() + elif not isinstance(headers, Headers): + headers = Headers(headers) + self.headers = headers + if content_type is not None: + self.content_type = content_type + if errors_stream is None: + errors_stream = sys.stderr + self.errors_stream = errors_stream + self.multithread = multithread + self.multiprocess = multiprocess + self.run_once = run_once + self.environ_base = environ_base + self.environ_overrides = environ_overrides + self.input_stream = input_stream + self.content_length = content_length + self.closed = False + + if auth is not None: + if isinstance(auth, tuple): + auth = Authorization( + "basic", {"username": auth[0], "password": auth[1]} + ) + + self.headers.set("Authorization", auth.to_header()) + + if json is not None: + if data is not None: + raise TypeError("can't provide both json and data") + + data = self.json_dumps(json) + + if self.content_type is None: + self.content_type = "application/json" + + if data: + if input_stream is not None: + raise TypeError("can't provide input stream and data") + if hasattr(data, "read"): + data = data.read() # type: ignore + if isinstance(data, str): + data = data.encode(self.charset) + if isinstance(data, bytes): + self.input_stream = BytesIO(data) + if self.content_length is None: + self.content_length = len(data) + else: + for key, value in _iter_data(data): # type: ignore + if isinstance(value, (tuple, dict)) or hasattr(value, "read"): + self._add_file_from_data(key, value) + else: + self.form.setlistdefault(key).append(value) + + if mimetype is not None: + self.mimetype = mimetype + + @classmethod + def from_environ( + cls, environ: "WSGIEnvironment", **kwargs: t.Any + ) -> "EnvironBuilder": + """Turn an environ dict back into a builder. Any extra kwargs + override the args extracted from the environ. + + .. versionchanged:: 2.0 + Path and query values are passed through the WSGI decoding + dance to avoid double encoding. + + .. versionadded:: 0.15 + """ + headers = Headers(EnvironHeaders(environ)) + out = { + "path": _wsgi_decoding_dance(environ["PATH_INFO"]), + "base_url": cls._make_base_url( + environ["wsgi.url_scheme"], + headers.pop("Host"), + _wsgi_decoding_dance(environ["SCRIPT_NAME"]), + ), + "query_string": _wsgi_decoding_dance(environ["QUERY_STRING"]), + "method": environ["REQUEST_METHOD"], + "input_stream": environ["wsgi.input"], + "content_type": headers.pop("Content-Type", None), + "content_length": headers.pop("Content-Length", None), + "errors_stream": environ["wsgi.errors"], + "multithread": environ["wsgi.multithread"], + "multiprocess": environ["wsgi.multiprocess"], + "run_once": environ["wsgi.run_once"], + "headers": headers, + } + out.update(kwargs) + return cls(**out) + + def _add_file_from_data( + self, + key: str, + value: t.Union[ + t.IO[bytes], t.Tuple[t.IO[bytes], str], t.Tuple[t.IO[bytes], str, str] + ], + ) -> None: + """Called in the EnvironBuilder to add files from the data dict.""" + if isinstance(value, tuple): + self.files.add_file(key, *value) + else: + self.files.add_file(key, value) + + @staticmethod + def _make_base_url(scheme: str, host: str, script_root: str) -> str: + return url_unparse((scheme, host, script_root, "", "")).rstrip("/") + "/" + + @property + def base_url(self) -> str: + """The base URL is used to extract the URL scheme, host name, + port, and root path. + """ + return self._make_base_url(self.url_scheme, self.host, self.script_root) + + @base_url.setter + def base_url(self, value: t.Optional[str]) -> None: + if value is None: + scheme = "http" + netloc = "localhost" + script_root = "" + else: + scheme, netloc, script_root, qs, anchor = url_parse(value) + if qs or anchor: + raise ValueError("base url must not contain a query string or fragment") + self.script_root = script_root.rstrip("/") + self.host = netloc + self.url_scheme = scheme + + @property + def content_type(self) -> t.Optional[str]: + """The content type for the request. Reflected from and to + the :attr:`headers`. Do not set if you set :attr:`files` or + :attr:`form` for auto detection. + """ + ct = self.headers.get("Content-Type") + if ct is None and not self._input_stream: + if self._files: + return "multipart/form-data" + if self._form: + return "application/x-www-form-urlencoded" + return None + return ct + + @content_type.setter + def content_type(self, value: t.Optional[str]) -> None: + if value is None: + self.headers.pop("Content-Type", None) + else: + self.headers["Content-Type"] = value + + @property + def mimetype(self) -> t.Optional[str]: + """The mimetype (content type without charset etc.) + + .. versionadded:: 0.14 + """ + ct = self.content_type + return ct.split(";")[0].strip() if ct else None + + @mimetype.setter + def mimetype(self, value: str) -> None: + self.content_type = get_content_type(value, self.charset) + + @property + def mimetype_params(self) -> t.Mapping[str, str]: + """The mimetype parameters as dict. For example if the + content type is ``text/html; charset=utf-8`` the params would be + ``{'charset': 'utf-8'}``. + + .. versionadded:: 0.14 + """ + + def on_update(d: CallbackDict) -> None: + self.headers["Content-Type"] = dump_options_header(self.mimetype, d) + + d = parse_options_header(self.headers.get("content-type", ""))[1] + return CallbackDict(d, on_update) + + @property + def content_length(self) -> t.Optional[int]: + """The content length as integer. Reflected from and to the + :attr:`headers`. Do not set if you set :attr:`files` or + :attr:`form` for auto detection. + """ + return self.headers.get("Content-Length", type=int) + + @content_length.setter + def content_length(self, value: t.Optional[int]) -> None: + if value is None: + self.headers.pop("Content-Length", None) + else: + self.headers["Content-Length"] = str(value) + + def _get_form(self, name: str, storage: t.Type[_TAnyMultiDict]) -> _TAnyMultiDict: + """Common behavior for getting the :attr:`form` and + :attr:`files` properties. + + :param name: Name of the internal cached attribute. + :param storage: Storage class used for the data. + """ + if self.input_stream is not None: + raise AttributeError("an input stream is defined") + + rv = getattr(self, name) + + if rv is None: + rv = storage() + setattr(self, name, rv) + + return rv # type: ignore + + def _set_form(self, name: str, value: MultiDict) -> None: + """Common behavior for setting the :attr:`form` and + :attr:`files` properties. + + :param name: Name of the internal cached attribute. + :param value: Value to assign to the attribute. + """ + self._input_stream = None + setattr(self, name, value) + + @property + def form(self) -> MultiDict: + """A :class:`MultiDict` of form values.""" + return self._get_form("_form", MultiDict) + + @form.setter + def form(self, value: MultiDict) -> None: + self._set_form("_form", value) + + @property + def files(self) -> FileMultiDict: + """A :class:`FileMultiDict` of uploaded files. Use + :meth:`~FileMultiDict.add_file` to add new files. + """ + return self._get_form("_files", FileMultiDict) + + @files.setter + def files(self, value: FileMultiDict) -> None: + self._set_form("_files", value) + + @property + def input_stream(self) -> t.Optional[t.IO[bytes]]: + """An optional input stream. This is mutually exclusive with + setting :attr:`form` and :attr:`files`, setting it will clear + those. Do not provide this if the method is not ``POST`` or + another method that has a body. + """ + return self._input_stream + + @input_stream.setter + def input_stream(self, value: t.Optional[t.IO[bytes]]) -> None: + self._input_stream = value + self._form = None + self._files = None + + @property + def query_string(self) -> str: + """The query string. If you set this to a string + :attr:`args` will no longer be available. + """ + if self._query_string is None: + if self._args is not None: + return url_encode(self._args, charset=self.charset) + return "" + return self._query_string + + @query_string.setter + def query_string(self, value: t.Optional[str]) -> None: + self._query_string = value + self._args = None + + @property + def args(self) -> MultiDict: + """The URL arguments as :class:`MultiDict`.""" + if self._query_string is not None: + raise AttributeError("a query string is defined") + if self._args is None: + self._args = MultiDict() + return self._args + + @args.setter + def args(self, value: t.Optional[MultiDict]) -> None: + self._query_string = None + self._args = value + + @property + def server_name(self) -> str: + """The server name (read-only, use :attr:`host` to set)""" + return self.host.split(":", 1)[0] + + @property + def server_port(self) -> int: + """The server port as integer (read-only, use :attr:`host` to set)""" + pieces = self.host.split(":", 1) + + if len(pieces) == 2: + try: + return int(pieces[1]) + except ValueError: + pass + + if self.url_scheme == "https": + return 443 + return 80 + + def __del__(self) -> None: + try: + self.close() + except Exception: + pass + + def close(self) -> None: + """Closes all files. If you put real :class:`file` objects into the + :attr:`files` dict you can call this method to automatically close + them all in one go. + """ + if self.closed: + return + try: + files = self.files.values() + except AttributeError: + files = () # type: ignore + for f in files: + try: + f.close() + except Exception: + pass + self.closed = True + + def get_environ(self) -> "WSGIEnvironment": + """Return the built environ. + + .. versionchanged:: 0.15 + The content type and length headers are set based on + input stream detection. Previously this only set the WSGI + keys. + """ + input_stream = self.input_stream + content_length = self.content_length + + mimetype = self.mimetype + content_type = self.content_type + + if input_stream is not None: + start_pos = input_stream.tell() + input_stream.seek(0, 2) + end_pos = input_stream.tell() + input_stream.seek(start_pos) + content_length = end_pos - start_pos + elif mimetype == "multipart/form-data": + input_stream, content_length, boundary = stream_encode_multipart( + CombinedMultiDict([self.form, self.files]), charset=self.charset + ) + content_type = f'{mimetype}; boundary="{boundary}"' + elif mimetype == "application/x-www-form-urlencoded": + form_encoded = url_encode(self.form, charset=self.charset).encode("ascii") + content_length = len(form_encoded) + input_stream = BytesIO(form_encoded) + else: + input_stream = BytesIO() + + result: "WSGIEnvironment" = {} + if self.environ_base: + result.update(self.environ_base) + + def _path_encode(x: str) -> str: + return _wsgi_encoding_dance(url_unquote(x, self.charset), self.charset) + + raw_uri = _wsgi_encoding_dance(self.request_uri, self.charset) + result.update( + { + "REQUEST_METHOD": self.method, + "SCRIPT_NAME": _path_encode(self.script_root), + "PATH_INFO": _path_encode(self.path), + "QUERY_STRING": _wsgi_encoding_dance(self.query_string, self.charset), + # Non-standard, added by mod_wsgi, uWSGI + "REQUEST_URI": raw_uri, + # Non-standard, added by gunicorn + "RAW_URI": raw_uri, + "SERVER_NAME": self.server_name, + "SERVER_PORT": str(self.server_port), + "HTTP_HOST": self.host, + "SERVER_PROTOCOL": self.server_protocol, + "wsgi.version": self.wsgi_version, + "wsgi.url_scheme": self.url_scheme, + "wsgi.input": input_stream, + "wsgi.errors": self.errors_stream, + "wsgi.multithread": self.multithread, + "wsgi.multiprocess": self.multiprocess, + "wsgi.run_once": self.run_once, + } + ) + + headers = self.headers.copy() + # Don't send these as headers, they're part of the environ. + headers.remove("Content-Type") + headers.remove("Content-Length") + + if content_type is not None: + result["CONTENT_TYPE"] = content_type + + if content_length is not None: + result["CONTENT_LENGTH"] = str(content_length) + + combined_headers = defaultdict(list) + + for key, value in headers.to_wsgi_list(): + combined_headers[f"HTTP_{key.upper().replace('-', '_')}"].append(value) + + for key, values in combined_headers.items(): + result[key] = ", ".join(values) + + if self.environ_overrides: + result.update(self.environ_overrides) + + return result + + def get_request(self, cls: t.Optional[t.Type[Request]] = None) -> Request: + """Returns a request with the data. If the request class is not + specified :attr:`request_class` is used. + + :param cls: The request wrapper to use. + """ + if cls is None: + cls = self.request_class + + return cls(self.get_environ()) + + +class ClientRedirectError(Exception): + """If a redirect loop is detected when using follow_redirects=True with + the :cls:`Client`, then this exception is raised. + """ + + +class Client: + """This class allows you to send requests to a wrapped application. + + The use_cookies parameter indicates whether cookies should be stored and + sent for subsequent requests. This is True by default, but passing False + will disable this behaviour. + + If you want to request some subdomain of your application you may set + `allow_subdomain_redirects` to `True` as if not no external redirects + are allowed. + + .. versionchanged:: 2.1 + Removed deprecated behavior of treating the response as a + tuple. All data is available as properties on the returned + response object. + + .. versionchanged:: 2.0 + ``response_wrapper`` is always a subclass of + :class:``TestResponse``. + + .. versionchanged:: 0.5 + Added the ``use_cookies`` parameter. + """ + + def __init__( + self, + application: "WSGIApplication", + response_wrapper: t.Optional[t.Type["Response"]] = None, + use_cookies: bool = True, + allow_subdomain_redirects: bool = False, + ) -> None: + self.application = application + + if response_wrapper in {None, Response}: + response_wrapper = TestResponse + elif not isinstance(response_wrapper, TestResponse): + response_wrapper = type( + "WrapperTestResponse", + (TestResponse, response_wrapper), # type: ignore + {}, + ) + + self.response_wrapper = t.cast(t.Type["TestResponse"], response_wrapper) + + if use_cookies: + self.cookie_jar: t.Optional[_TestCookieJar] = _TestCookieJar() + else: + self.cookie_jar = None + + self.allow_subdomain_redirects = allow_subdomain_redirects + + def set_cookie( + self, + server_name: str, + key: str, + value: str = "", + max_age: t.Optional[t.Union[timedelta, int]] = None, + expires: t.Optional[t.Union[str, datetime, int, float]] = None, + path: str = "/", + domain: t.Optional[str] = None, + secure: bool = False, + httponly: bool = False, + samesite: t.Optional[str] = None, + charset: str = "utf-8", + ) -> None: + """Sets a cookie in the client's cookie jar. The server name + is required and has to match the one that is also passed to + the open call. + """ + assert self.cookie_jar is not None, "cookies disabled" + header = dump_cookie( + key, + value, + max_age, + expires, + path, + domain, + secure, + httponly, + charset, + samesite=samesite, + ) + environ = create_environ(path, base_url=f"http://{server_name}") + headers = [("Set-Cookie", header)] + self.cookie_jar.extract_wsgi(environ, headers) + + def delete_cookie( + self, + server_name: str, + key: str, + path: str = "/", + domain: t.Optional[str] = None, + secure: bool = False, + httponly: bool = False, + samesite: t.Optional[str] = None, + ) -> None: + """Deletes a cookie in the test client.""" + self.set_cookie( + server_name, + key, + expires=0, + max_age=0, + path=path, + domain=domain, + secure=secure, + httponly=httponly, + samesite=samesite, + ) + + def run_wsgi_app( + self, environ: "WSGIEnvironment", buffered: bool = False + ) -> t.Tuple[t.Iterable[bytes], str, Headers]: + """Runs the wrapped WSGI app with the given environment. + + :meta private: + """ + if self.cookie_jar is not None: + self.cookie_jar.inject_wsgi(environ) + + rv = run_wsgi_app(self.application, environ, buffered=buffered) + + if self.cookie_jar is not None: + self.cookie_jar.extract_wsgi(environ, rv[2]) + + return rv + + def resolve_redirect( + self, response: "TestResponse", buffered: bool = False + ) -> "TestResponse": + """Perform a new request to the location given by the redirect + response to the previous request. + + :meta private: + """ + scheme, netloc, path, qs, anchor = url_parse(response.location) + builder = EnvironBuilder.from_environ( + response.request.environ, path=path, query_string=qs + ) + + to_name_parts = netloc.split(":", 1)[0].split(".") + from_name_parts = builder.server_name.split(".") + + if to_name_parts != [""]: + # The new location has a host, use it for the base URL. + builder.url_scheme = scheme + builder.host = netloc + else: + # A local redirect with autocorrect_location_header=False + # doesn't have a host, so use the request's host. + to_name_parts = from_name_parts + + # Explain why a redirect to a different server name won't be followed. + if to_name_parts != from_name_parts: + if to_name_parts[-len(from_name_parts) :] == from_name_parts: + if not self.allow_subdomain_redirects: + raise RuntimeError("Following subdomain redirects is not enabled.") + else: + raise RuntimeError("Following external redirects is not supported.") + + path_parts = path.split("/") + root_parts = builder.script_root.split("/") + + if path_parts[: len(root_parts)] == root_parts: + # Strip the script root from the path. + builder.path = path[len(builder.script_root) :] + else: + # The new location is not under the script root, so use the + # whole path and clear the previous root. + builder.path = path + builder.script_root = "" + + # Only 307 and 308 preserve all of the original request. + if response.status_code not in {307, 308}: + # HEAD is preserved, everything else becomes GET. + if builder.method != "HEAD": + builder.method = "GET" + + # Clear the body and the headers that describe it. + + if builder.input_stream is not None: + builder.input_stream.close() + builder.input_stream = None + + builder.content_type = None + builder.content_length = None + builder.headers.pop("Transfer-Encoding", None) + + return self.open(builder, buffered=buffered) + + def open( + self, + *args: t.Any, + buffered: bool = False, + follow_redirects: bool = False, + **kwargs: t.Any, + ) -> "TestResponse": + """Generate an environ dict from the given arguments, make a + request to the application using it, and return the response. + + :param args: Passed to :class:`EnvironBuilder` to create the + environ for the request. If a single arg is passed, it can + be an existing :class:`EnvironBuilder` or an environ dict. + :param buffered: Convert the iterator returned by the app into + a list. If the iterator has a ``close()`` method, it is + called automatically. + :param follow_redirects: Make additional requests to follow HTTP + redirects until a non-redirect status is returned. + :attr:`TestResponse.history` lists the intermediate + responses. + + .. versionchanged:: 2.1 + Removed the ``as_tuple`` parameter. + + .. versionchanged:: 2.0 + ``as_tuple`` is deprecated and will be removed in Werkzeug + 2.1. Use :attr:`TestResponse.request` and + ``request.environ`` instead. + + .. versionchanged:: 2.0 + The request input stream is closed when calling + ``response.close()``. Input streams for redirects are + automatically closed. + + .. versionchanged:: 0.5 + If a dict is provided as file in the dict for the ``data`` + parameter the content type has to be called ``content_type`` + instead of ``mimetype``. This change was made for + consistency with :class:`werkzeug.FileWrapper`. + + .. versionchanged:: 0.5 + Added the ``follow_redirects`` parameter. + """ + request: t.Optional["Request"] = None + + if not kwargs and len(args) == 1: + arg = args[0] + + if isinstance(arg, EnvironBuilder): + request = arg.get_request() + elif isinstance(arg, dict): + request = EnvironBuilder.from_environ(arg).get_request() + elif isinstance(arg, Request): + request = arg + + if request is None: + builder = EnvironBuilder(*args, **kwargs) + + try: + request = builder.get_request() + finally: + builder.close() + + response = self.run_wsgi_app(request.environ, buffered=buffered) + response = self.response_wrapper(*response, request=request) + + redirects = set() + history: t.List["TestResponse"] = [] + + if not follow_redirects: + return response + + while response.status_code in { + 301, + 302, + 303, + 305, + 307, + 308, + }: + # Exhaust intermediate response bodies to ensure middleware + # that returns an iterator runs any cleanup code. + if not buffered: + response.make_sequence() + response.close() + + new_redirect_entry = (response.location, response.status_code) + + if new_redirect_entry in redirects: + raise ClientRedirectError( + f"Loop detected: A {response.status_code} redirect" + f" to {response.location} was already made." + ) + + redirects.add(new_redirect_entry) + response.history = tuple(history) + history.append(response) + response = self.resolve_redirect(response, buffered=buffered) + else: + # This is the final request after redirects. + response.history = tuple(history) + # Close the input stream when closing the response, in case + # the input is an open temporary file. + response.call_on_close(request.input_stream.close) + return response + + def get(self, *args: t.Any, **kw: t.Any) -> "TestResponse": + """Call :meth:`open` with ``method`` set to ``GET``.""" + kw["method"] = "GET" + return self.open(*args, **kw) + + def post(self, *args: t.Any, **kw: t.Any) -> "TestResponse": + """Call :meth:`open` with ``method`` set to ``POST``.""" + kw["method"] = "POST" + return self.open(*args, **kw) + + def put(self, *args: t.Any, **kw: t.Any) -> "TestResponse": + """Call :meth:`open` with ``method`` set to ``PUT``.""" + kw["method"] = "PUT" + return self.open(*args, **kw) + + def delete(self, *args: t.Any, **kw: t.Any) -> "TestResponse": + """Call :meth:`open` with ``method`` set to ``DELETE``.""" + kw["method"] = "DELETE" + return self.open(*args, **kw) + + def patch(self, *args: t.Any, **kw: t.Any) -> "TestResponse": + """Call :meth:`open` with ``method`` set to ``PATCH``.""" + kw["method"] = "PATCH" + return self.open(*args, **kw) + + def options(self, *args: t.Any, **kw: t.Any) -> "TestResponse": + """Call :meth:`open` with ``method`` set to ``OPTIONS``.""" + kw["method"] = "OPTIONS" + return self.open(*args, **kw) + + def head(self, *args: t.Any, **kw: t.Any) -> "TestResponse": + """Call :meth:`open` with ``method`` set to ``HEAD``.""" + kw["method"] = "HEAD" + return self.open(*args, **kw) + + def trace(self, *args: t.Any, **kw: t.Any) -> "TestResponse": + """Call :meth:`open` with ``method`` set to ``TRACE``.""" + kw["method"] = "TRACE" + return self.open(*args, **kw) + + def __repr__(self) -> str: + return f"<{type(self).__name__} {self.application!r}>" + + +def create_environ(*args: t.Any, **kwargs: t.Any) -> "WSGIEnvironment": + """Create a new WSGI environ dict based on the values passed. The first + parameter should be the path of the request which defaults to '/'. The + second one can either be an absolute path (in that case the host is + localhost:80) or a full path to the request with scheme, netloc port and + the path to the script. + + This accepts the same arguments as the :class:`EnvironBuilder` + constructor. + + .. versionchanged:: 0.5 + This function is now a thin wrapper over :class:`EnvironBuilder` which + was added in 0.5. The `headers`, `environ_base`, `environ_overrides` + and `charset` parameters were added. + """ + builder = EnvironBuilder(*args, **kwargs) + + try: + return builder.get_environ() + finally: + builder.close() + + +def run_wsgi_app( + app: "WSGIApplication", environ: "WSGIEnvironment", buffered: bool = False +) -> t.Tuple[t.Iterable[bytes], str, Headers]: + """Return a tuple in the form (app_iter, status, headers) of the + application output. This works best if you pass it an application that + returns an iterator all the time. + + Sometimes applications may use the `write()` callable returned + by the `start_response` function. This tries to resolve such edge + cases automatically. But if you don't get the expected output you + should set `buffered` to `True` which enforces buffering. + + If passed an invalid WSGI application the behavior of this function is + undefined. Never pass non-conforming WSGI applications to this function. + + :param app: the application to execute. + :param buffered: set to `True` to enforce buffering. + :return: tuple in the form ``(app_iter, status, headers)`` + """ + # Copy environ to ensure any mutations by the app (ProxyFix, for + # example) don't affect subsequent requests (such as redirects). + environ = _get_environ(environ).copy() + status: str + response: t.Optional[t.Tuple[str, t.List[t.Tuple[str, str]]]] = None + buffer: t.List[bytes] = [] + + def start_response(status, headers, exc_info=None): # type: ignore + nonlocal response + + if exc_info: + try: + raise exc_info[1].with_traceback(exc_info[2]) + finally: + exc_info = None + + response = (status, headers) + return buffer.append + + app_rv = app(environ, start_response) + close_func = getattr(app_rv, "close", None) + app_iter: t.Iterable[bytes] = iter(app_rv) + + # when buffering we emit the close call early and convert the + # application iterator into a regular list + if buffered: + try: + app_iter = list(app_iter) + finally: + if close_func is not None: + close_func() + + # otherwise we iterate the application iter until we have a response, chain + # the already received data with the already collected data and wrap it in + # a new `ClosingIterator` if we need to restore a `close` callable from the + # original return value. + else: + for item in app_iter: + buffer.append(item) + + if response is not None: + break + + if buffer: + app_iter = chain(buffer, app_iter) + + if close_func is not None and app_iter is not app_rv: + app_iter = ClosingIterator(app_iter, close_func) + + status, headers = response # type: ignore + return app_iter, status, Headers(headers) + + +class TestResponse(Response): + """:class:`~werkzeug.wrappers.Response` subclass that provides extra + information about requests made with the test :class:`Client`. + + Test client requests will always return an instance of this class. + If a custom response class is passed to the client, it is + subclassed along with this to support test information. + + If the test request included large files, or if the application is + serving a file, call :meth:`close` to close any open files and + prevent Python showing a ``ResourceWarning``. + + .. versionchanged:: 2.2 + Set the ``default_mimetype`` to None to prevent a mimetype being + assumed if missing. + + .. versionchanged:: 2.1 + Removed deprecated behavior for treating the response instance + as a tuple. + + .. versionadded:: 2.0 + Test client methods always return instances of this class. + """ + + default_mimetype = None + # Don't assume a mimetype, instead use whatever the response provides + + request: Request + """A request object with the environ used to make the request that + resulted in this response. + """ + + history: t.Tuple["TestResponse", ...] + """A list of intermediate responses. Populated when the test request + is made with ``follow_redirects`` enabled. + """ + + # Tell Pytest to ignore this, it's not a test class. + __test__ = False + + def __init__( + self, + response: t.Iterable[bytes], + status: str, + headers: Headers, + request: Request, + history: t.Tuple["TestResponse"] = (), # type: ignore + **kwargs: t.Any, + ) -> None: + super().__init__(response, status, headers, **kwargs) + self.request = request + self.history = history + self._compat_tuple = response, status, headers + + @cached_property + def text(self) -> str: + """The response data as text. A shortcut for + ``response.get_data(as_text=True)``. + + .. versionadded:: 2.1 + """ + return self.get_data(as_text=True) diff --git a/.vtodo/Lib/site-packages/werkzeug/testapp.py b/.vtodo/Lib/site-packages/werkzeug/testapp.py new file mode 100644 index 0000000..0d7ffbb --- /dev/null +++ b/.vtodo/Lib/site-packages/werkzeug/testapp.py @@ -0,0 +1,241 @@ +"""A small application that can be used to test a WSGI server and check +it for WSGI compliance. +""" +import base64 +import os +import sys +import typing as t +from textwrap import wrap + +from markupsafe import escape + +from . import __version__ as _werkzeug_version +from .wrappers.request import Request +from .wrappers.response import Response + +if t.TYPE_CHECKING: + from _typeshed.wsgi import StartResponse + from _typeshed.wsgi import WSGIEnvironment + + +logo = Response( + base64.b64decode( + """ +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 +=""" + ), + mimetype="image/png", +) + + +TEMPLATE = """\ + + +WSGI Information + +
    + +

    WSGI Information

    +

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

    Python Interpreter

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

    WSGI Environment

    + %(wsgi_env)s
    +

    Installed Eggs

    +

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

      %(python_eggs)s
    +

    System Path

    +

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

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

      %(sys_path)s
    +
    +""" + + +def iter_sys_path() -> t.Iterator[t.Tuple[str, bool, bool]]: + if os.name == "posix": + + def strip(x: str) -> str: + prefix = os.path.expanduser("~") + if x.startswith(prefix): + x = f"~{x[len(prefix) :]}" + return x + + else: + + def strip(x: str) -> str: + return x + + cwd = os.path.abspath(os.getcwd()) + for item in sys.path: + path = os.path.join(cwd, item or os.path.curdir) + yield strip(os.path.normpath(path)), not os.path.isdir(path), path != item + + +def render_testapp(req: Request) -> bytes: + try: + import pkg_resources + except ImportError: + eggs: t.Iterable[t.Any] = () + else: + eggs = sorted( + pkg_resources.working_set, + key=lambda x: x.project_name.lower(), # type: ignore + ) + python_eggs = [] + for egg in eggs: + try: + version = egg.version + except (ValueError, AttributeError): + version = "unknown" + python_eggs.append( + f"
  • {escape(egg.project_name)} [{escape(version)}]" + ) + + wsgi_env = [] + sorted_environ = sorted(req.environ.items(), key=lambda x: repr(x[0]).lower()) + for key, value in sorted_environ: + value = "".join(wrap(str(escape(repr(value))))) + wsgi_env.append(f"{escape(key)}{value}") + + sys_path = [] + for item, virtual, expanded in iter_sys_path(): + class_ = [] + if virtual: + class_.append("virtual") + if expanded: + class_.append("exp") + class_ = f' class="{" ".join(class_)}"' if class_ else "" + sys_path.append(f"{escape(item)}") + + return ( + TEMPLATE + % { + "python_version": "
    ".join(escape(sys.version).splitlines()), + "platform": escape(sys.platform), + "os": escape(os.name), + "api_version": sys.api_version, + "byteorder": sys.byteorder, + "werkzeug_version": _werkzeug_version, + "python_eggs": "\n".join(python_eggs), + "wsgi_env": "\n".join(wsgi_env), + "sys_path": "\n".join(sys_path), + } + ).encode("utf-8") + + +def test_app( + environ: "WSGIEnvironment", start_response: "StartResponse" +) -> t.Iterable[bytes]: + """Simple test application that dumps the environment. You can use + it to check if Werkzeug is working properly: + + .. sourcecode:: pycon + + >>> from werkzeug.serving import run_simple + >>> from werkzeug.testapp import test_app + >>> run_simple('localhost', 3000, test_app) + * Running on http://localhost:3000/ + + The application displays important information from the WSGI environment, + the Python interpreter and the installed libraries. + """ + req = Request(environ, populate_request=False) + if req.args.get("resource") == "logo": + response = logo + else: + response = Response(render_testapp(req), mimetype="text/html") + return response(environ, start_response) + + +if __name__ == "__main__": + from .serving import run_simple + + run_simple("localhost", 5000, test_app, use_reloader=True) diff --git a/.vtodo/Lib/site-packages/werkzeug/urls.py b/.vtodo/Lib/site-packages/werkzeug/urls.py new file mode 100644 index 0000000..67c08b0 --- /dev/null +++ b/.vtodo/Lib/site-packages/werkzeug/urls.py @@ -0,0 +1,1067 @@ +"""Functions for working with URLs. + +Contains implementations of functions from :mod:`urllib.parse` that +handle bytes and strings. +""" +import codecs +import os +import re +import typing as t + +from ._internal import _check_str_tuple +from ._internal import _decode_idna +from ._internal import _encode_idna +from ._internal import _make_encode_wrapper +from ._internal import _to_str + +if t.TYPE_CHECKING: + from . import datastructures as ds + +# A regular expression for what a valid schema looks like +_scheme_re = re.compile(r"^[a-zA-Z0-9+-.]+$") + +# Characters that are safe in any part of an URL. +_always_safe = frozenset( + bytearray( + b"abcdefghijklmnopqrstuvwxyz" + b"ABCDEFGHIJKLMNOPQRSTUVWXYZ" + b"0123456789" + b"-._~" + b"$!'()*+,;" # RFC3986 sub-delims set, not including query string delimiters &= + ) +) + +_hexdigits = "0123456789ABCDEFabcdef" +_hextobyte = { + f"{a}{b}".encode("ascii"): int(f"{a}{b}", 16) + for a in _hexdigits + for b in _hexdigits +} +_bytetohex = [f"%{char:02X}".encode("ascii") for char in range(256)] + + +class _URLTuple(t.NamedTuple): + scheme: str + netloc: str + path: str + query: str + fragment: str + + +class BaseURL(_URLTuple): + """Superclass of :py:class:`URL` and :py:class:`BytesURL`.""" + + __slots__ = () + _at: str + _colon: str + _lbracket: str + _rbracket: str + + def __str__(self) -> str: + return self.to_url() + + def replace(self, **kwargs: t.Any) -> "BaseURL": + """Return an URL with the same values, except for those parameters + given new values by whichever keyword arguments are specified.""" + return self._replace(**kwargs) + + @property + def host(self) -> t.Optional[str]: + """The host part of the URL if available, otherwise `None`. The + host is either the hostname or the IP address mentioned in the + URL. It will not contain the port. + """ + return self._split_host()[0] + + @property + def ascii_host(self) -> t.Optional[str]: + """Works exactly like :attr:`host` but will return a result that + is restricted to ASCII. If it finds a netloc that is not ASCII + it will attempt to idna decode it. This is useful for socket + operations when the URL might include internationalized characters. + """ + rv = self.host + if rv is not None and isinstance(rv, str): + try: + rv = _encode_idna(rv) # type: ignore + except UnicodeError: + rv = rv.encode("ascii", "ignore") # type: ignore + return _to_str(rv, "ascii", "ignore") + + @property + def port(self) -> t.Optional[int]: + """The port in the URL as an integer if it was present, `None` + otherwise. This does not fill in default ports. + """ + try: + rv = int(_to_str(self._split_host()[1])) + if 0 <= rv <= 65535: + return rv + except (ValueError, TypeError): + pass + return None + + @property + def auth(self) -> t.Optional[str]: + """The authentication part in the URL if available, `None` + otherwise. + """ + return self._split_netloc()[0] + + @property + def username(self) -> t.Optional[str]: + """The username if it was part of the URL, `None` otherwise. + This undergoes URL decoding and will always be a string. + """ + rv = self._split_auth()[0] + if rv is not None: + return _url_unquote_legacy(rv) + return None + + @property + def raw_username(self) -> t.Optional[str]: + """The username if it was part of the URL, `None` otherwise. + Unlike :attr:`username` this one is not being decoded. + """ + return self._split_auth()[0] + + @property + def password(self) -> t.Optional[str]: + """The password if it was part of the URL, `None` otherwise. + This undergoes URL decoding and will always be a string. + """ + rv = self._split_auth()[1] + if rv is not None: + return _url_unquote_legacy(rv) + return None + + @property + def raw_password(self) -> t.Optional[str]: + """The password if it was part of the URL, `None` otherwise. + Unlike :attr:`password` this one is not being decoded. + """ + return self._split_auth()[1] + + def decode_query(self, *args: t.Any, **kwargs: t.Any) -> "ds.MultiDict[str, str]": + """Decodes the query part of the URL. Ths is a shortcut for + calling :func:`url_decode` on the query argument. The arguments and + keyword arguments are forwarded to :func:`url_decode` unchanged. + """ + return url_decode(self.query, *args, **kwargs) + + def join(self, *args: t.Any, **kwargs: t.Any) -> "BaseURL": + """Joins this URL with another one. This is just a convenience + function for calling into :meth:`url_join` and then parsing the + return value again. + """ + return url_parse(url_join(self, *args, **kwargs)) + + def to_url(self) -> str: + """Returns a URL string or bytes depending on the type of the + information stored. This is just a convenience function + for calling :meth:`url_unparse` for this URL. + """ + return url_unparse(self) + + def encode_netloc(self) -> str: + """Encodes the netloc part to an ASCII safe URL as bytes.""" + rv = self.ascii_host or "" + if ":" in rv: + rv = f"[{rv}]" + port = self.port + if port is not None: + rv = f"{rv}:{port}" + auth = ":".join( + filter( + None, + [ + url_quote(self.raw_username or "", "utf-8", "strict", "/:%"), + url_quote(self.raw_password or "", "utf-8", "strict", "/:%"), + ], + ) + ) + if auth: + rv = f"{auth}@{rv}" + return rv + + def decode_netloc(self) -> str: + """Decodes the netloc part into a string.""" + rv = _decode_idna(self.host or "") + + if ":" in rv: + rv = f"[{rv}]" + port = self.port + if port is not None: + rv = f"{rv}:{port}" + auth = ":".join( + filter( + None, + [ + _url_unquote_legacy(self.raw_username or "", "/:%@"), + _url_unquote_legacy(self.raw_password or "", "/:%@"), + ], + ) + ) + if auth: + rv = f"{auth}@{rv}" + return rv + + def to_uri_tuple(self) -> "BaseURL": + """Returns a :class:`BytesURL` tuple that holds a URI. This will + encode all the information in the URL properly to ASCII using the + rules a web browser would follow. + + It's usually more interesting to directly call :meth:`iri_to_uri` which + will return a string. + """ + return url_parse(iri_to_uri(self)) + + def to_iri_tuple(self) -> "BaseURL": + """Returns a :class:`URL` tuple that holds a IRI. This will try + to decode as much information as possible in the URL without + losing information similar to how a web browser does it for the + URL bar. + + It's usually more interesting to directly call :meth:`uri_to_iri` which + will return a string. + """ + return url_parse(uri_to_iri(self)) + + def get_file_location( + self, pathformat: t.Optional[str] = None + ) -> t.Tuple[t.Optional[str], t.Optional[str]]: + """Returns a tuple with the location of the file in the form + ``(server, location)``. If the netloc is empty in the URL or + points to localhost, it's represented as ``None``. + + The `pathformat` by default is autodetection but needs to be set + when working with URLs of a specific system. The supported values + are ``'windows'`` when working with Windows or DOS paths and + ``'posix'`` when working with posix paths. + + If the URL does not point to a local file, the server and location + are both represented as ``None``. + + :param pathformat: The expected format of the path component. + Currently ``'windows'`` and ``'posix'`` are + supported. Defaults to ``None`` which is + autodetect. + """ + if self.scheme != "file": + return None, None + + path = url_unquote(self.path) + host = self.netloc or None + + if pathformat is None: + if os.name == "nt": + pathformat = "windows" + else: + pathformat = "posix" + + if pathformat == "windows": + if path[:1] == "/" and path[1:2].isalpha() and path[2:3] in "|:": + path = f"{path[1:2]}:{path[3:]}" + windows_share = path[:3] in ("\\" * 3, "/" * 3) + import ntpath + + path = ntpath.normpath(path) + # Windows shared drives are represented as ``\\host\\directory``. + # That results in a URL like ``file://///host/directory``, and a + # path like ``///host/directory``. We need to special-case this + # because the path contains the hostname. + if windows_share and host is None: + parts = path.lstrip("\\").split("\\", 1) + if len(parts) == 2: + host, path = parts + else: + host = parts[0] + path = "" + elif pathformat == "posix": + import posixpath + + path = posixpath.normpath(path) + else: + raise TypeError(f"Invalid path format {pathformat!r}") + + if host in ("127.0.0.1", "::1", "localhost"): + host = None + + return host, path + + def _split_netloc(self) -> t.Tuple[t.Optional[str], str]: + if self._at in self.netloc: + auth, _, netloc = self.netloc.partition(self._at) + return auth, netloc + return None, self.netloc + + def _split_auth(self) -> t.Tuple[t.Optional[str], t.Optional[str]]: + auth = self._split_netloc()[0] + if not auth: + return None, None + if self._colon not in auth: + return auth, None + + username, _, password = auth.partition(self._colon) + return username, password + + def _split_host(self) -> t.Tuple[t.Optional[str], t.Optional[str]]: + rv = self._split_netloc()[1] + if not rv: + return None, None + + if not rv.startswith(self._lbracket): + if self._colon in rv: + host, _, port = rv.partition(self._colon) + return host, port + return rv, None + + idx = rv.find(self._rbracket) + if idx < 0: + return rv, None + + host = rv[1:idx] + rest = rv[idx + 1 :] + if rest.startswith(self._colon): + return host, rest[1:] + return host, None + + +class URL(BaseURL): + """Represents a parsed URL. This behaves like a regular tuple but + also has some extra attributes that give further insight into the + URL. + """ + + __slots__ = () + _at = "@" + _colon = ":" + _lbracket = "[" + _rbracket = "]" + + def encode(self, charset: str = "utf-8", errors: str = "replace") -> "BytesURL": + """Encodes the URL to a tuple made out of bytes. The charset is + only being used for the path, query and fragment. + """ + return BytesURL( + self.scheme.encode("ascii"), # type: ignore + self.encode_netloc(), + self.path.encode(charset, errors), # type: ignore + self.query.encode(charset, errors), # type: ignore + self.fragment.encode(charset, errors), # type: ignore + ) + + +class BytesURL(BaseURL): + """Represents a parsed URL in bytes.""" + + __slots__ = () + _at = b"@" # type: ignore + _colon = b":" # type: ignore + _lbracket = b"[" # type: ignore + _rbracket = b"]" # type: ignore + + def __str__(self) -> str: + return self.to_url().decode("utf-8", "replace") # type: ignore + + def encode_netloc(self) -> bytes: # type: ignore + """Returns the netloc unchanged as bytes.""" + return self.netloc # type: ignore + + def decode(self, charset: str = "utf-8", errors: str = "replace") -> "URL": + """Decodes the URL to a tuple made out of strings. The charset is + only being used for the path, query and fragment. + """ + return URL( + self.scheme.decode("ascii"), # type: ignore + self.decode_netloc(), + self.path.decode(charset, errors), # type: ignore + self.query.decode(charset, errors), # type: ignore + self.fragment.decode(charset, errors), # type: ignore + ) + + +_unquote_maps: t.Dict[t.FrozenSet[int], t.Dict[bytes, int]] = {frozenset(): _hextobyte} + + +def _unquote_to_bytes( + string: t.Union[str, bytes], unsafe: t.Union[str, bytes] = "" +) -> bytes: + if isinstance(string, str): + string = string.encode("utf-8") + + if isinstance(unsafe, str): + unsafe = unsafe.encode("utf-8") + + unsafe = frozenset(bytearray(unsafe)) + groups = iter(string.split(b"%")) + result = bytearray(next(groups, b"")) + + try: + hex_to_byte = _unquote_maps[unsafe] + except KeyError: + hex_to_byte = _unquote_maps[unsafe] = { + h: b for h, b in _hextobyte.items() if b not in unsafe + } + + for group in groups: + code = group[:2] + + if code in hex_to_byte: + result.append(hex_to_byte[code]) + result.extend(group[2:]) + else: + result.append(37) # % + result.extend(group) + + return bytes(result) + + +def _url_encode_impl( + obj: t.Union[t.Mapping[str, str], t.Iterable[t.Tuple[str, str]]], + charset: str, + sort: bool, + key: t.Optional[t.Callable[[t.Tuple[str, str]], t.Any]], +) -> t.Iterator[str]: + from .datastructures import iter_multi_items + + iterable: t.Iterable[t.Tuple[str, str]] = iter_multi_items(obj) + + if sort: + iterable = sorted(iterable, key=key) + + for key_str, value_str in iterable: + if value_str is None: + continue + + if not isinstance(key_str, bytes): + key_bytes = str(key_str).encode(charset) + else: + key_bytes = key_str + + if not isinstance(value_str, bytes): + value_bytes = str(value_str).encode(charset) + else: + value_bytes = value_str + + yield f"{_fast_url_quote_plus(key_bytes)}={_fast_url_quote_plus(value_bytes)}" + + +def _url_unquote_legacy(value: str, unsafe: str = "") -> str: + try: + return url_unquote(value, charset="utf-8", errors="strict", unsafe=unsafe) + except UnicodeError: + return url_unquote(value, charset="latin1", unsafe=unsafe) + + +def url_parse( + url: str, scheme: t.Optional[str] = None, allow_fragments: bool = True +) -> BaseURL: + """Parses a URL from a string into a :class:`URL` tuple. If the URL + is lacking a scheme it can be provided as second argument. Otherwise, + it is ignored. Optionally fragments can be stripped from the URL + by setting `allow_fragments` to `False`. + + The inverse of this function is :func:`url_unparse`. + + :param url: the URL to parse. + :param scheme: the default schema to use if the URL is schemaless. + :param allow_fragments: if set to `False` a fragment will be removed + from the URL. + """ + s = _make_encode_wrapper(url) + is_text_based = isinstance(url, str) + + if scheme is None: + scheme = s("") + netloc = query = fragment = s("") + i = url.find(s(":")) + if i > 0 and _scheme_re.match(_to_str(url[:i], errors="replace")): + # make sure "iri" is not actually a port number (in which case + # "scheme" is really part of the path) + rest = url[i + 1 :] + if not rest or any(c not in s("0123456789") for c in rest): + # not a port number + scheme, url = url[:i].lower(), rest + + if url[:2] == s("//"): + delim = len(url) + for c in s("/?#"): + wdelim = url.find(c, 2) + if wdelim >= 0: + delim = min(delim, wdelim) + netloc, url = url[2:delim], url[delim:] + if (s("[") in netloc and s("]") not in netloc) or ( + s("]") in netloc and s("[") not in netloc + ): + raise ValueError("Invalid IPv6 URL") + + if allow_fragments and s("#") in url: + url, fragment = url.split(s("#"), 1) + if s("?") in url: + url, query = url.split(s("?"), 1) + + result_type = URL if is_text_based else BytesURL + return result_type(scheme, netloc, url, query, fragment) + + +def _make_fast_url_quote( + charset: str = "utf-8", + errors: str = "strict", + safe: t.Union[str, bytes] = "/:", + unsafe: t.Union[str, bytes] = "", +) -> t.Callable[[bytes], str]: + """Precompile the translation table for a URL encoding function. + + Unlike :func:`url_quote`, the generated function only takes the + string to quote. + + :param charset: The charset to encode the result with. + :param errors: How to handle encoding errors. + :param safe: An optional sequence of safe characters to never encode. + :param unsafe: An optional sequence of unsafe characters to always encode. + """ + if isinstance(safe, str): + safe = safe.encode(charset, errors) + + if isinstance(unsafe, str): + unsafe = unsafe.encode(charset, errors) + + safe = (frozenset(bytearray(safe)) | _always_safe) - frozenset(bytearray(unsafe)) + table = [chr(c) if c in safe else f"%{c:02X}" for c in range(256)] + + def quote(string: bytes) -> str: + return "".join([table[c] for c in string]) + + return quote + + +_fast_url_quote = _make_fast_url_quote() +_fast_quote_plus = _make_fast_url_quote(safe=" ", unsafe="+") + + +def _fast_url_quote_plus(string: bytes) -> str: + return _fast_quote_plus(string).replace(" ", "+") + + +def url_quote( + string: t.Union[str, bytes], + charset: str = "utf-8", + errors: str = "strict", + safe: t.Union[str, bytes] = "/:", + unsafe: t.Union[str, bytes] = "", +) -> str: + """URL encode a single string with a given encoding. + + :param s: the string to quote. + :param charset: the charset to be used. + :param safe: an optional sequence of safe characters. + :param unsafe: an optional sequence of unsafe characters. + + .. versionadded:: 0.9.2 + The `unsafe` parameter was added. + """ + if not isinstance(string, (str, bytes, bytearray)): + string = str(string) + if isinstance(string, str): + string = string.encode(charset, errors) + if isinstance(safe, str): + safe = safe.encode(charset, errors) + if isinstance(unsafe, str): + unsafe = unsafe.encode(charset, errors) + safe = (frozenset(bytearray(safe)) | _always_safe) - frozenset(bytearray(unsafe)) + rv = bytearray() + for char in bytearray(string): + if char in safe: + rv.append(char) + else: + rv.extend(_bytetohex[char]) + return bytes(rv).decode(charset) + + +def url_quote_plus( + string: str, charset: str = "utf-8", errors: str = "strict", safe: str = "" +) -> str: + """URL encode a single string with the given encoding and convert + whitespace to "+". + + :param s: The string to quote. + :param charset: The charset to be used. + :param safe: An optional sequence of safe characters. + """ + return url_quote(string, charset, errors, safe + " ", "+").replace(" ", "+") + + +def url_unparse(components: t.Tuple[str, str, str, str, str]) -> str: + """The reverse operation to :meth:`url_parse`. This accepts arbitrary + as well as :class:`URL` tuples and returns a URL as a string. + + :param components: the parsed URL as tuple which should be converted + into a URL string. + """ + _check_str_tuple(components) + scheme, netloc, path, query, fragment = components + s = _make_encode_wrapper(scheme) + url = s("") + + # We generally treat file:///x and file:/x the same which is also + # what browsers seem to do. This also allows us to ignore a schema + # register for netloc utilization or having to differentiate between + # empty and missing netloc. + if netloc or (scheme and path.startswith(s("/"))): + if path and path[:1] != s("/"): + path = s("/") + path + url = s("//") + (netloc or s("")) + path + elif path: + url += path + if scheme: + url = scheme + s(":") + url + if query: + url = url + s("?") + query + if fragment: + url = url + s("#") + fragment + return url + + +def url_unquote( + s: t.Union[str, bytes], + charset: str = "utf-8", + errors: str = "replace", + unsafe: str = "", +) -> str: + """URL decode a single string with a given encoding. If the charset + is set to `None` no decoding is performed and raw bytes are + returned. + + :param s: the string to unquote. + :param charset: the charset of the query string. If set to `None` + no decoding will take place. + :param errors: the error handling for the charset decoding. + """ + rv = _unquote_to_bytes(s, unsafe) + if charset is None: + return rv + return rv.decode(charset, errors) + + +def url_unquote_plus( + s: t.Union[str, bytes], charset: str = "utf-8", errors: str = "replace" +) -> str: + """URL decode a single string with the given `charset` and decode "+" to + whitespace. + + Per default encoding errors are ignored. If you want a different behavior + you can set `errors` to ``'replace'`` or ``'strict'``. + + :param s: The string to unquote. + :param charset: the charset of the query string. If set to `None` + no decoding will take place. + :param errors: The error handling for the `charset` decoding. + """ + if isinstance(s, str): + s = s.replace("+", " ") + else: + s = s.replace(b"+", b" ") + return url_unquote(s, charset, errors) + + +def url_fix(s: str, charset: str = "utf-8") -> str: + r"""Sometimes you get an URL by a user that just isn't a real URL because + it contains unsafe characters like ' ' and so on. This function can fix + some of the problems in a similar way browsers handle data entered by the + user: + + >>> url_fix('http://de.wikipedia.org/wiki/Elf (Begriffskl\xe4rung)') + 'http://de.wikipedia.org/wiki/Elf%20(Begriffskl%C3%A4rung)' + + :param s: the string with the URL to fix. + :param charset: The target charset for the URL if the url was given + as a string. + """ + # First step is to switch to text processing and to convert + # backslashes (which are invalid in URLs anyways) to slashes. This is + # consistent with what Chrome does. + s = _to_str(s, charset, "replace").replace("\\", "/") + + # For the specific case that we look like a malformed windows URL + # we want to fix this up manually: + if s.startswith("file://") and s[7:8].isalpha() and s[8:10] in (":/", "|/"): + s = f"file:///{s[7:]}" + + url = url_parse(s) + path = url_quote(url.path, charset, safe="/%+$!*'(),") + qs = url_quote_plus(url.query, charset, safe=":&%=+$!*'(),") + anchor = url_quote_plus(url.fragment, charset, safe=":&%=+$!*'(),") + return url_unparse((url.scheme, url.encode_netloc(), path, qs, anchor)) + + +# not-unreserved characters remain quoted when unquoting to IRI +_to_iri_unsafe = "".join([chr(c) for c in range(128) if c not in _always_safe]) + + +def _codec_error_url_quote(e: UnicodeError) -> t.Tuple[str, int]: + """Used in :func:`uri_to_iri` after unquoting to re-quote any + invalid bytes. + """ + # the docs state that UnicodeError does have these attributes, + # but mypy isn't picking them up + out = _fast_url_quote(e.object[e.start : e.end]) # type: ignore + return out, e.end # type: ignore + + +codecs.register_error("werkzeug.url_quote", _codec_error_url_quote) + + +def uri_to_iri( + uri: t.Union[str, t.Tuple[str, str, str, str, str]], + charset: str = "utf-8", + errors: str = "werkzeug.url_quote", +) -> str: + """Convert a URI to an IRI. All valid UTF-8 characters are unquoted, + leaving all reserved and invalid characters quoted. If the URL has + a domain, it is decoded from Punycode. + + >>> uri_to_iri("http://xn--n3h.net/p%C3%A5th?q=%C3%A8ry%DF") + 'http://\\u2603.net/p\\xe5th?q=\\xe8ry%DF' + + :param uri: The URI to convert. + :param charset: The encoding to encode unquoted bytes with. + :param errors: Error handler to use during ``bytes.encode``. By + default, invalid bytes are left quoted. + + .. versionchanged:: 0.15 + All reserved and invalid characters remain quoted. Previously, + only some reserved characters were preserved, and invalid bytes + were replaced instead of left quoted. + + .. versionadded:: 0.6 + """ + if isinstance(uri, tuple): + uri = url_unparse(uri) + + uri = url_parse(_to_str(uri, charset)) + path = url_unquote(uri.path, charset, errors, _to_iri_unsafe) + query = url_unquote(uri.query, charset, errors, _to_iri_unsafe) + fragment = url_unquote(uri.fragment, charset, errors, _to_iri_unsafe) + return url_unparse((uri.scheme, uri.decode_netloc(), path, query, fragment)) + + +# reserved characters remain unquoted when quoting to URI +_to_uri_safe = ":/?#[]@!$&'()*+,;=%" + + +def iri_to_uri( + iri: t.Union[str, t.Tuple[str, str, str, str, str]], + charset: str = "utf-8", + errors: str = "strict", + safe_conversion: bool = False, +) -> str: + """Convert an IRI to a URI. All non-ASCII and unsafe characters are + quoted. If the URL has a domain, it is encoded to Punycode. + + >>> iri_to_uri('http://\\u2603.net/p\\xe5th?q=\\xe8ry%DF') + 'http://xn--n3h.net/p%C3%A5th?q=%C3%A8ry%DF' + + :param iri: The IRI to convert. + :param charset: The encoding of the IRI. + :param errors: Error handler to use during ``bytes.encode``. + :param safe_conversion: Return the URL unchanged if it only contains + ASCII characters and no whitespace. See the explanation below. + + There is a general problem with IRI conversion with some protocols + that are in violation of the URI specification. Consider the + following two IRIs:: + + magnet:?xt=uri:whatever + itms-services://?action=download-manifest + + After parsing, we don't know if the scheme requires the ``//``, + which is dropped if empty, but conveys different meanings in the + final URL if it's present or not. In this case, you can use + ``safe_conversion``, which will return the URL unchanged if it only + contains ASCII characters and no whitespace. This can result in a + URI with unquoted characters if it was not already quoted correctly, + but preserves the URL's semantics. Werkzeug uses this for the + ``Location`` header for redirects. + + .. versionchanged:: 0.15 + All reserved characters remain unquoted. Previously, only some + reserved characters were left unquoted. + + .. versionchanged:: 0.9.6 + The ``safe_conversion`` parameter was added. + + .. versionadded:: 0.6 + """ + if isinstance(iri, tuple): + iri = url_unparse(iri) + + if safe_conversion: + # If we're not sure if it's safe to convert the URL, and it only + # contains ASCII characters, return it unconverted. + try: + native_iri = _to_str(iri) + ascii_iri = native_iri.encode("ascii") + + # Only return if it doesn't have whitespace. (Why?) + if len(ascii_iri.split()) == 1: + return native_iri + except UnicodeError: + pass + + iri = url_parse(_to_str(iri, charset, errors)) + path = url_quote(iri.path, charset, errors, _to_uri_safe) + query = url_quote(iri.query, charset, errors, _to_uri_safe) + fragment = url_quote(iri.fragment, charset, errors, _to_uri_safe) + return url_unparse((iri.scheme, iri.encode_netloc(), path, query, fragment)) + + +def url_decode( + s: t.AnyStr, + charset: str = "utf-8", + include_empty: bool = True, + errors: str = "replace", + separator: str = "&", + cls: t.Optional[t.Type["ds.MultiDict"]] = None, +) -> "ds.MultiDict[str, str]": + """Parse a query string and return it as a :class:`MultiDict`. + + :param s: The query string to parse. + :param charset: Decode bytes to string with this charset. If not + given, bytes are returned as-is. + :param include_empty: Include keys with empty values in the dict. + :param errors: Error handling behavior when decoding bytes. + :param separator: Separator character between pairs. + :param cls: Container to hold result instead of :class:`MultiDict`. + + .. versionchanged:: 2.0 + The ``decode_keys`` parameter is deprecated and will be removed + in Werkzeug 2.1. + + .. versionchanged:: 0.5 + In previous versions ";" and "&" could be used for url decoding. + Now only "&" is supported. If you want to use ";", a different + ``separator`` can be provided. + + .. versionchanged:: 0.5 + The ``cls`` parameter was added. + """ + if cls is None: + from .datastructures import MultiDict # noqa: F811 + + cls = MultiDict + if isinstance(s, str) and not isinstance(separator, str): + separator = separator.decode(charset or "ascii") + elif isinstance(s, bytes) and not isinstance(separator, bytes): + separator = separator.encode(charset or "ascii") # type: ignore + return cls( + _url_decode_impl( + s.split(separator), charset, include_empty, errors # type: ignore + ) + ) + + +def url_decode_stream( + stream: t.IO[bytes], + charset: str = "utf-8", + include_empty: bool = True, + errors: str = "replace", + separator: bytes = b"&", + cls: t.Optional[t.Type["ds.MultiDict"]] = None, + limit: t.Optional[int] = None, +) -> "ds.MultiDict[str, str]": + """Works like :func:`url_decode` but decodes a stream. The behavior + of stream and limit follows functions like + :func:`~werkzeug.wsgi.make_line_iter`. The generator of pairs is + directly fed to the `cls` so you can consume the data while it's + parsed. + + :param stream: a stream with the encoded querystring + :param charset: the charset of the query string. If set to `None` + no decoding will take place. + :param include_empty: Set to `False` if you don't want empty values to + appear in the dict. + :param errors: the decoding error behavior. + :param separator: the pair separator to be used, defaults to ``&`` + :param cls: an optional dict class to use. If this is not specified + or `None` the default :class:`MultiDict` is used. + :param limit: the content length of the URL data. Not necessary if + a limited stream is provided. + + .. versionchanged:: 2.0 + The ``decode_keys`` and ``return_iterator`` parameters are + deprecated and will be removed in Werkzeug 2.1. + + .. versionadded:: 0.8 + """ + from .wsgi import make_chunk_iter + + pair_iter = make_chunk_iter(stream, separator, limit) + decoder = _url_decode_impl(pair_iter, charset, include_empty, errors) + + if cls is None: + from .datastructures import MultiDict # noqa: F811 + + cls = MultiDict + + return cls(decoder) + + +def _url_decode_impl( + pair_iter: t.Iterable[t.AnyStr], charset: str, include_empty: bool, errors: str +) -> t.Iterator[t.Tuple[str, str]]: + for pair in pair_iter: + if not pair: + continue + s = _make_encode_wrapper(pair) + equal = s("=") + if equal in pair: + key, value = pair.split(equal, 1) + else: + if not include_empty: + continue + key = pair + value = s("") + yield ( + url_unquote_plus(key, charset, errors), + url_unquote_plus(value, charset, errors), + ) + + +def url_encode( + obj: t.Union[t.Mapping[str, str], t.Iterable[t.Tuple[str, str]]], + charset: str = "utf-8", + sort: bool = False, + key: t.Optional[t.Callable[[t.Tuple[str, str]], t.Any]] = None, + separator: str = "&", +) -> str: + """URL encode a dict/`MultiDict`. If a value is `None` it will not appear + in the result string. Per default only values are encoded into the target + charset strings. + + :param obj: the object to encode into a query string. + :param charset: the charset of the query string. + :param sort: set to `True` if you want parameters to be sorted by `key`. + :param separator: the separator to be used for the pairs. + :param key: an optional function to be used for sorting. For more details + check out the :func:`sorted` documentation. + + .. versionchanged:: 2.0 + The ``encode_keys`` parameter is deprecated and will be removed + in Werkzeug 2.1. + + .. versionchanged:: 0.5 + Added the ``sort``, ``key``, and ``separator`` parameters. + """ + separator = _to_str(separator, "ascii") + return separator.join(_url_encode_impl(obj, charset, sort, key)) + + +def url_encode_stream( + obj: t.Union[t.Mapping[str, str], t.Iterable[t.Tuple[str, str]]], + stream: t.Optional[t.IO[str]] = None, + charset: str = "utf-8", + sort: bool = False, + key: t.Optional[t.Callable[[t.Tuple[str, str]], t.Any]] = None, + separator: str = "&", +) -> None: + """Like :meth:`url_encode` but writes the results to a stream + object. If the stream is `None` a generator over all encoded + pairs is returned. + + :param obj: the object to encode into a query string. + :param stream: a stream to write the encoded object into or `None` if + an iterator over the encoded pairs should be returned. In + that case the separator argument is ignored. + :param charset: the charset of the query string. + :param sort: set to `True` if you want parameters to be sorted by `key`. + :param separator: the separator to be used for the pairs. + :param key: an optional function to be used for sorting. For more details + check out the :func:`sorted` documentation. + + .. versionchanged:: 2.0 + The ``encode_keys`` parameter is deprecated and will be removed + in Werkzeug 2.1. + + .. versionadded:: 0.8 + """ + separator = _to_str(separator, "ascii") + gen = _url_encode_impl(obj, charset, sort, key) + if stream is None: + return gen # type: ignore + for idx, chunk in enumerate(gen): + if idx: + stream.write(separator) + stream.write(chunk) + return None + + +def url_join( + base: t.Union[str, t.Tuple[str, str, str, str, str]], + url: t.Union[str, t.Tuple[str, str, str, str, str]], + allow_fragments: bool = True, +) -> str: + """Join a base URL and a possibly relative URL to form an absolute + interpretation of the latter. + + :param base: the base URL for the join operation. + :param url: the URL to join. + :param allow_fragments: indicates whether fragments should be allowed. + """ + if isinstance(base, tuple): + base = url_unparse(base) + if isinstance(url, tuple): + url = url_unparse(url) + + _check_str_tuple((base, url)) + s = _make_encode_wrapper(base) + + if not base: + return url + if not url: + return base + + bscheme, bnetloc, bpath, bquery, bfragment = url_parse( + base, allow_fragments=allow_fragments + ) + scheme, netloc, path, query, fragment = url_parse(url, bscheme, allow_fragments) + if scheme != bscheme: + return url + if netloc: + return url_unparse((scheme, netloc, path, query, fragment)) + netloc = bnetloc + + if path[:1] == s("/"): + segments = path.split(s("/")) + elif not path: + segments = bpath.split(s("/")) + if not query: + query = bquery + else: + segments = bpath.split(s("/"))[:-1] + path.split(s("/")) + + # If the rightmost part is "./" we want to keep the slash but + # remove the dot. + if segments[-1] == s("."): + segments[-1] = s("") + + # Resolve ".." and "." + segments = [segment for segment in segments if segment != s(".")] + while True: + i = 1 + n = len(segments) - 1 + while i < n: + if segments[i] == s("..") and segments[i - 1] not in (s(""), s("..")): + del segments[i - 1 : i + 1] + break + i += 1 + else: + break + + # Remove trailing ".." if the URL is absolute + unwanted_marker = [s(""), s("..")] + while segments[:2] == unwanted_marker: + del segments[1] + + path = s("/").join(segments) + return url_unparse((scheme, netloc, path, query, fragment)) diff --git a/.vtodo/Lib/site-packages/werkzeug/user_agent.py b/.vtodo/Lib/site-packages/werkzeug/user_agent.py new file mode 100644 index 0000000..66ffcbe --- /dev/null +++ b/.vtodo/Lib/site-packages/werkzeug/user_agent.py @@ -0,0 +1,47 @@ +import typing as t + + +class UserAgent: + """Represents a parsed user agent header value. + + The default implementation does no parsing, only the :attr:`string` + attribute is set. A subclass may parse the string to set the + common attributes or expose other information. Set + :attr:`werkzeug.wrappers.Request.user_agent_class` to use a + subclass. + + :param string: The header value to parse. + + .. versionadded:: 2.0 + This replaces the previous ``useragents`` module, but does not + provide a built-in parser. + """ + + platform: t.Optional[str] = None + """The OS name, if it could be parsed from the string.""" + + browser: t.Optional[str] = None + """The browser name, if it could be parsed from the string.""" + + version: t.Optional[str] = None + """The browser version, if it could be parsed from the string.""" + + language: t.Optional[str] = None + """The browser language, if it could be parsed from the string.""" + + def __init__(self, string: str) -> None: + self.string: str = string + """The original header value.""" + + def __repr__(self) -> str: + return f"<{type(self).__name__} {self.browser}/{self.version}>" + + def __str__(self) -> str: + return self.string + + def __bool__(self) -> bool: + return bool(self.browser) + + def to_header(self) -> str: + """Convert to a header value.""" + return self.string diff --git a/.vtodo/Lib/site-packages/werkzeug/utils.py b/.vtodo/Lib/site-packages/werkzeug/utils.py new file mode 100644 index 0000000..672e6e5 --- /dev/null +++ b/.vtodo/Lib/site-packages/werkzeug/utils.py @@ -0,0 +1,705 @@ +import io +import mimetypes +import os +import pkgutil +import re +import sys +import typing as t +import unicodedata +from datetime import datetime +from time import time +from zlib import adler32 + +from markupsafe import escape + +from ._internal import _DictAccessorProperty +from ._internal import _missing +from ._internal import _TAccessorValue +from .datastructures import Headers +from .exceptions import NotFound +from .exceptions import RequestedRangeNotSatisfiable +from .security import safe_join +from .urls import url_quote +from .wsgi import wrap_file + +if t.TYPE_CHECKING: + from _typeshed.wsgi import WSGIEnvironment + from .wrappers.request import Request + from .wrappers.response import Response + +_T = t.TypeVar("_T") + +_entity_re = re.compile(r"&([^;]+);") +_filename_ascii_strip_re = re.compile(r"[^A-Za-z0-9_.-]") +_windows_device_files = ( + "CON", + "AUX", + "COM1", + "COM2", + "COM3", + "COM4", + "LPT1", + "LPT2", + "LPT3", + "PRN", + "NUL", +) + + +class cached_property(property, t.Generic[_T]): + """A :func:`property` that is only evaluated once. Subsequent access + returns the cached value. Setting the property sets the cached + value. Deleting the property clears the cached value, accessing it + again will evaluate it again. + + .. code-block:: python + + class Example: + @cached_property + def value(self): + # calculate something important here + return 42 + + e = Example() + e.value # evaluates + e.value # uses cache + e.value = 16 # sets cache + del e.value # clears cache + + If the class defines ``__slots__``, it must add ``_cache_{name}`` as + a slot. Alternatively, it can add ``__dict__``, but that's usually + not desirable. + + .. versionchanged:: 2.1 + Works with ``__slots__``. + + .. versionchanged:: 2.0 + ``del obj.name`` clears the cached value. + """ + + def __init__( + self, + fget: t.Callable[[t.Any], _T], + name: t.Optional[str] = None, + doc: t.Optional[str] = None, + ) -> None: + super().__init__(fget, doc=doc) + self.__name__ = name or fget.__name__ + self.slot_name = f"_cache_{self.__name__}" + self.__module__ = fget.__module__ + + def __set__(self, obj: object, value: _T) -> None: + if hasattr(obj, "__dict__"): + obj.__dict__[self.__name__] = value + else: + setattr(obj, self.slot_name, value) + + def __get__(self, obj: object, type: type = None) -> _T: # type: ignore + if obj is None: + return self # type: ignore + + obj_dict = getattr(obj, "__dict__", None) + + if obj_dict is not None: + value: _T = obj_dict.get(self.__name__, _missing) + else: + value = getattr(obj, self.slot_name, _missing) # type: ignore[arg-type] + + if value is _missing: + value = self.fget(obj) # type: ignore + + if obj_dict is not None: + obj.__dict__[self.__name__] = value + else: + setattr(obj, self.slot_name, value) + + return value + + def __delete__(self, obj: object) -> None: + if hasattr(obj, "__dict__"): + del obj.__dict__[self.__name__] + else: + setattr(obj, self.slot_name, _missing) + + +class environ_property(_DictAccessorProperty[_TAccessorValue]): + """Maps request attributes to environment variables. This works not only + for the Werkzeug request object, but also any other class with an + environ attribute: + + >>> class Test(object): + ... environ = {'key': 'value'} + ... test = environ_property('key') + >>> var = Test() + >>> var.test + 'value' + + If you pass it a second value it's used as default if the key does not + exist, the third one can be a converter that takes a value and converts + it. If it raises :exc:`ValueError` or :exc:`TypeError` the default value + is used. If no default value is provided `None` is used. + + Per default the property is read only. You have to explicitly enable it + by passing ``read_only=False`` to the constructor. + """ + + read_only = True + + def lookup(self, obj: "Request") -> "WSGIEnvironment": + return obj.environ + + +class header_property(_DictAccessorProperty[_TAccessorValue]): + """Like `environ_property` but for headers.""" + + def lookup(self, obj: t.Union["Request", "Response"]) -> Headers: + return obj.headers + + +# https://cgit.freedesktop.org/xdg/shared-mime-info/tree/freedesktop.org.xml.in +# https://www.iana.org/assignments/media-types/media-types.xhtml +# Types listed in the XDG mime info that have a charset in the IANA registration. +_charset_mimetypes = { + "application/ecmascript", + "application/javascript", + "application/sql", + "application/xml", + "application/xml-dtd", + "application/xml-external-parsed-entity", +} + + +def get_content_type(mimetype: str, charset: str) -> str: + """Returns the full content type string with charset for a mimetype. + + If the mimetype represents text, the charset parameter will be + appended, otherwise the mimetype is returned unchanged. + + :param mimetype: The mimetype to be used as content type. + :param charset: The charset to be appended for text mimetypes. + :return: The content type. + + .. versionchanged:: 0.15 + Any type that ends with ``+xml`` gets a charset, not just those + that start with ``application/``. Known text types such as + ``application/javascript`` are also given charsets. + """ + if ( + mimetype.startswith("text/") + or mimetype in _charset_mimetypes + or mimetype.endswith("+xml") + ): + mimetype += f"; charset={charset}" + + return mimetype + + +def secure_filename(filename: str) -> str: + r"""Pass it a filename and it will return a secure version of it. This + filename can then safely be stored on a regular file system and passed + to :func:`os.path.join`. The filename returned is an ASCII only string + for maximum portability. + + On windows systems the function also makes sure that the file is not + named after one of the special device files. + + >>> secure_filename("My cool movie.mov") + 'My_cool_movie.mov' + >>> secure_filename("../../../etc/passwd") + 'etc_passwd' + >>> secure_filename('i contain cool \xfcml\xe4uts.txt') + 'i_contain_cool_umlauts.txt' + + The function might return an empty filename. It's your responsibility + to ensure that the filename is unique and that you abort or + generate a random filename if the function returned an empty one. + + .. versionadded:: 0.5 + + :param filename: the filename to secure + """ + filename = unicodedata.normalize("NFKD", filename) + filename = filename.encode("ascii", "ignore").decode("ascii") + + for sep in os.path.sep, os.path.altsep: + if sep: + filename = filename.replace(sep, " ") + filename = str(_filename_ascii_strip_re.sub("", "_".join(filename.split()))).strip( + "._" + ) + + # on nt a couple of special files are present in each folder. We + # have to ensure that the target file is not such a filename. In + # this case we prepend an underline + if ( + os.name == "nt" + and filename + and filename.split(".")[0].upper() in _windows_device_files + ): + filename = f"_{filename}" + + return filename + + +def redirect( + location: str, code: int = 302, Response: t.Optional[t.Type["Response"]] = None +) -> "Response": + """Returns a response object (a WSGI application) that, if called, + redirects the client to the target location. Supported codes are + 301, 302, 303, 305, 307, and 308. 300 is not supported because + it's not a real redirect and 304 because it's the answer for a + request with a request with defined If-Modified-Since headers. + + .. versionadded:: 0.6 + The location can now be a unicode string that is encoded using + the :func:`iri_to_uri` function. + + .. versionadded:: 0.10 + The class used for the Response object can now be passed in. + + :param location: the location the response should redirect to. + :param code: the redirect status code. defaults to 302. + :param class Response: a Response class to use when instantiating a + response. The default is :class:`werkzeug.wrappers.Response` if + unspecified. + """ + if Response is None: + from .wrappers import Response # type: ignore + + display_location = escape(location) + if isinstance(location, str): + # Safe conversion is necessary here as we might redirect + # to a broken URI scheme (for instance itms-services). + from .urls import iri_to_uri + + location = iri_to_uri(location, safe_conversion=True) + + response = Response( # type: ignore + "\n" + "\n" + "Redirecting...\n" + "

    Redirecting...

    \n" + "

    You should be redirected automatically to the target URL: " + f'{display_location}. If' + " not, click the link.\n", + code, + mimetype="text/html", + ) + response.headers["Location"] = location + return response + + +def append_slash_redirect(environ: "WSGIEnvironment", code: int = 308) -> "Response": + """Redirect to the current URL with a slash appended. + + If the current URL is ``/user/42``, the redirect URL will be + ``42/``. When joined to the current URL during response + processing or by the browser, this will produce ``/user/42/``. + + The behavior is undefined if the path ends with a slash already. If + called unconditionally on a URL, it may produce a redirect loop. + + :param environ: Use the path and query from this WSGI environment + to produce the redirect URL. + :param code: the status code for the redirect. + + .. versionchanged:: 2.1 + Produce a relative URL that only modifies the last segment. + Relevant when the current path has multiple segments. + + .. versionchanged:: 2.1 + The default status code is 308 instead of 301. This preserves + the request method and body. + """ + tail = environ["PATH_INFO"].rpartition("/")[2] + + if not tail: + new_path = "./" + else: + new_path = f"{tail}/" + + query_string = environ.get("QUERY_STRING") + + if query_string: + new_path = f"{new_path}?{query_string}" + + return redirect(new_path, code) + + +def send_file( + path_or_file: t.Union[os.PathLike, str, t.IO[bytes]], + environ: "WSGIEnvironment", + mimetype: t.Optional[str] = None, + as_attachment: bool = False, + download_name: t.Optional[str] = None, + conditional: bool = True, + etag: t.Union[bool, str] = True, + last_modified: t.Optional[t.Union[datetime, int, float]] = None, + max_age: t.Optional[ + t.Union[int, t.Callable[[t.Optional[str]], t.Optional[int]]] + ] = None, + use_x_sendfile: bool = False, + response_class: t.Optional[t.Type["Response"]] = None, + _root_path: t.Optional[t.Union[os.PathLike, str]] = 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. + + If the WSGI server sets a ``file_wrapper`` in ``environ``, it is + used, otherwise Werkzeug's built-in wrapper is used. Alternatively, + if the HTTP server supports ``X-Sendfile``, ``use_x_sendfile=True`` + will tell the server to send the given path, which is much more + efficient than reading it in Python. + + :param path_or_file: The path to the file to send, relative to the + current working directory if a relative path is given. + Alternatively, a file-like object opened in binary mode. Make + sure the file pointer is seeked to the start of the data. + :param environ: The WSGI environ for the current request. + :param mimetype: The MIME type to send for the file. If not + provided, it will try to detect it from the file name. + :param as_attachment: Indicate to a browser that it should offer to + save the file instead of displaying it. + :param download_name: The default name browsers will use when saving + the file. Defaults to the passed file name. + :param conditional: Enable conditional and range responses based on + request headers. Requires passing a file path and ``environ``. + :param etag: Calculate an ETag for the file, which requires passing + a file path. Can also be a string to use instead. + :param last_modified: The last modified time to send for the file, + in seconds. If not provided, it will try to detect it from the + file path. + :param max_age: How long the client should cache the file, in + seconds. If set, ``Cache-Control`` will be ``public``, otherwise + it will be ``no-cache`` to prefer conditional caching. + :param use_x_sendfile: Set the ``X-Sendfile`` header to let the + server to efficiently send the file. Requires support from the + HTTP server. Requires passing a file path. + :param response_class: Build the response using this class. Defaults + to :class:`~werkzeug.wrappers.Response`. + :param _root_path: Do not use. For internal use only. Use + :func:`send_from_directory` to safely send files under a path. + + .. versionchanged:: 2.0.2 + ``send_file`` only sets a detected ``Content-Encoding`` if + ``as_attachment`` is disabled. + + .. versionadded:: 2.0 + Adapted from Flask's implementation. + + .. versionchanged:: 2.0 + ``download_name`` replaces Flask's ``attachment_filename`` + parameter. If ``as_attachment=False``, it is passed with + ``Content-Disposition: inline`` instead. + + .. versionchanged:: 2.0 + ``max_age`` replaces Flask's ``cache_timeout`` parameter. + ``conditional`` is enabled and ``max_age`` is not set by + default. + + .. versionchanged:: 2.0 + ``etag`` replaces Flask's ``add_etags`` parameter. It can be a + string to use instead of generating one. + + .. versionchanged:: 2.0 + If an encoding is returned when guessing ``mimetype`` from + ``download_name``, set the ``Content-Encoding`` header. + """ + if response_class is None: + from .wrappers import Response + + response_class = Response + + path: t.Optional[str] = None + file: t.Optional[t.IO[bytes]] = None + size: t.Optional[int] = None + mtime: t.Optional[float] = None + headers = Headers() + + if isinstance(path_or_file, (os.PathLike, str)) or hasattr( + path_or_file, "__fspath__" + ): + path_or_file = t.cast(t.Union[os.PathLike, str], path_or_file) + + # Flask will pass app.root_path, allowing its send_file wrapper + # to not have to deal with paths. + if _root_path is not None: + path = os.path.join(_root_path, path_or_file) + else: + path = os.path.abspath(path_or_file) + + stat = os.stat(path) + size = stat.st_size + mtime = stat.st_mtime + else: + file = path_or_file + + if download_name is None and path is not None: + download_name = os.path.basename(path) + + if mimetype is None: + if download_name is None: + raise TypeError( + "Unable to detect the MIME type because a file name is" + " not available. Either set 'download_name', pass a" + " path instead of a file, or set 'mimetype'." + ) + + mimetype, encoding = mimetypes.guess_type(download_name) + + if mimetype is None: + mimetype = "application/octet-stream" + + # Don't send encoding for attachments, it causes browsers to + # save decompress tar.gz files. + if encoding is not None and not as_attachment: + headers.set("Content-Encoding", encoding) + + if download_name is not None: + try: + download_name.encode("ascii") + except UnicodeEncodeError: + simple = unicodedata.normalize("NFKD", download_name) + simple = simple.encode("ascii", "ignore").decode("ascii") + quoted = url_quote(download_name, safe="") + names = {"filename": simple, "filename*": f"UTF-8''{quoted}"} + else: + names = {"filename": download_name} + + value = "attachment" if as_attachment else "inline" + headers.set("Content-Disposition", value, **names) + elif as_attachment: + raise TypeError( + "No name provided for attachment. Either set" + " 'download_name' or pass a path instead of a file." + ) + + if use_x_sendfile and path is not None: + headers["X-Sendfile"] = path + data = None + else: + if file is None: + file = open(path, "rb") # type: ignore + elif isinstance(file, io.BytesIO): + size = file.getbuffer().nbytes + elif isinstance(file, io.TextIOBase): + raise ValueError("Files must be opened in binary mode or use BytesIO.") + + data = wrap_file(environ, file) + + rv = response_class( + data, mimetype=mimetype, headers=headers, direct_passthrough=True + ) + + if size is not None: + rv.content_length = size + + if last_modified is not None: + rv.last_modified = last_modified # type: ignore + elif mtime is not None: + rv.last_modified = mtime # type: ignore + + rv.cache_control.no_cache = True + + # Flask will pass app.get_send_file_max_age, allowing its send_file + # wrapper to not have to deal with paths. + if callable(max_age): + max_age = max_age(path) + + if max_age is not None: + if max_age > 0: + rv.cache_control.no_cache = None + rv.cache_control.public = True + + rv.cache_control.max_age = max_age + rv.expires = int(time() + max_age) # type: ignore + + if isinstance(etag, str): + rv.set_etag(etag) + elif etag and path is not None: + check = adler32(path.encode("utf-8")) & 0xFFFFFFFF + rv.set_etag(f"{mtime}-{size}-{check}") + + if conditional: + try: + rv = rv.make_conditional(environ, accept_ranges=True, complete_length=size) + except RequestedRangeNotSatisfiable: + if file is not None: + file.close() + + raise + + # Some x-sendfile implementations incorrectly ignore the 304 + # status code and send the file anyway. + if rv.status_code == 304: + rv.headers.pop("x-sendfile", None) + + return rv + + +def send_from_directory( + directory: t.Union[os.PathLike, str], + path: t.Union[os.PathLike, str], + environ: "WSGIEnvironment", + **kwargs: t.Any, +) -> "Response": + """Send a file from within a directory using :func:`send_file`. + + This is a secure way to serve files from a folder, such as static + files or uploads. Uses :func:`~werkzeug.security.safe_join` to + ensure the path coming from the client is not maliciously crafted to + point outside the specified directory. + + If the final path does not point to an existing regular file, + returns a 404 :exc:`~werkzeug.exceptions.NotFound` error. + + :param directory: The directory that ``path`` must be located under. + :param path: The path to the file to send, relative to + ``directory``. + :param environ: The WSGI environ for the current request. + :param kwargs: Arguments to pass to :func:`send_file`. + + .. versionadded:: 2.0 + Adapted from Flask's implementation. + """ + path = safe_join(os.fspath(directory), os.fspath(path)) + + if path is None: + raise NotFound() + + # Flask will pass app.root_path, allowing its send_from_directory + # wrapper to not have to deal with paths. + if "_root_path" in kwargs: + path = os.path.join(kwargs["_root_path"], path) + + try: + if not os.path.isfile(path): + raise NotFound() + except ValueError: + # path contains null byte on Python < 3.8 + raise NotFound() from None + + return send_file(path, environ, **kwargs) + + +def import_string(import_name: str, silent: bool = False) -> t.Any: + """Imports an object based on a string. This is useful if you want to + use import paths as endpoints or something similar. An import path can + be specified either in dotted notation (``xml.sax.saxutils.escape``) + or with a colon as object delimiter (``xml.sax.saxutils:escape``). + + If `silent` is True the return value will be `None` if the import fails. + + :param import_name: the dotted name for the object to import. + :param silent: if set to `True` import errors are ignored and + `None` is returned instead. + :return: imported object + """ + import_name = import_name.replace(":", ".") + try: + try: + __import__(import_name) + except ImportError: + if "." not in import_name: + raise + else: + return sys.modules[import_name] + + module_name, obj_name = import_name.rsplit(".", 1) + module = __import__(module_name, globals(), locals(), [obj_name]) + try: + return getattr(module, obj_name) + except AttributeError as e: + raise ImportError(e) from None + + except ImportError as e: + if not silent: + raise ImportStringError(import_name, e).with_traceback( + sys.exc_info()[2] + ) from None + + return None + + +def find_modules( + import_path: str, include_packages: bool = False, recursive: bool = False +) -> t.Iterator[str]: + """Finds all the modules below a package. This can be useful to + automatically import all views / controllers so that their metaclasses / + function decorators have a chance to register themselves on the + application. + + Packages are not returned unless `include_packages` is `True`. This can + also recursively list modules but in that case it will import all the + packages to get the correct load path of that module. + + :param import_path: the dotted name for the package to find child modules. + :param include_packages: set to `True` if packages should be returned, too. + :param recursive: set to `True` if recursion should happen. + :return: generator + """ + module = import_string(import_path) + path = getattr(module, "__path__", None) + if path is None: + raise ValueError(f"{import_path!r} is not a package") + basename = f"{module.__name__}." + for _importer, modname, ispkg in pkgutil.iter_modules(path): + modname = basename + modname + if ispkg: + if include_packages: + yield modname + if recursive: + yield from find_modules(modname, include_packages, True) + else: + yield modname + + +class ImportStringError(ImportError): + """Provides information about a failed :func:`import_string` attempt.""" + + #: String in dotted notation that failed to be imported. + import_name: str + #: Wrapped exception. + exception: BaseException + + def __init__(self, import_name: str, exception: BaseException) -> None: + self.import_name = import_name + self.exception = exception + msg = import_name + name = "" + tracked = [] + for part in import_name.replace(":", ".").split("."): + name = f"{name}.{part}" if name else part + imported = import_string(name, silent=True) + if imported: + tracked.append((name, getattr(imported, "__file__", None))) + else: + track = [f"- {n!r} found in {i!r}." for n, i in tracked] + track.append(f"- {name!r} not found.") + track_str = "\n".join(track) + msg = ( + f"import_string() failed for {import_name!r}. Possible reasons" + f" are:\n\n" + "- missing __init__.py in a package;\n" + "- package or module path not included in sys.path;\n" + "- duplicated package or module name taking precedence in" + " sys.path;\n" + "- missing module, class, function or variable;\n\n" + f"Debugged import:\n\n{track_str}\n\n" + f"Original exception:\n\n{type(exception).__name__}: {exception}" + ) + break + + super().__init__(msg) + + def __repr__(self) -> str: + return f"<{type(self).__name__}({self.import_name!r}, {self.exception!r})>" diff --git a/.vtodo/Lib/site-packages/werkzeug/wrappers/__init__.py b/.vtodo/Lib/site-packages/werkzeug/wrappers/__init__.py new file mode 100644 index 0000000..b8c45d7 --- /dev/null +++ b/.vtodo/Lib/site-packages/werkzeug/wrappers/__init__.py @@ -0,0 +1,3 @@ +from .request import Request as Request +from .response import Response as Response +from .response import ResponseStream diff --git a/.vtodo/Lib/site-packages/werkzeug/wrappers/__pycache__/__init__.cpython-310.pyc b/.vtodo/Lib/site-packages/werkzeug/wrappers/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..da2dc3adf576cdd3b8267522a248cfdfb7ad5afb GIT binary patch literal 299 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 ASpWb4 literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/werkzeug/wrappers/__pycache__/response.cpython-310.pyc b/.vtodo/Lib/site-packages/werkzeug/wrappers/__pycache__/response.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..685e64262e97cba7106c3669891a767c4337a37a GIT binary patch literal 29157 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 R;B%@vUCsS?`fsN6zX7avhc5sC literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/werkzeug/wrappers/request.py b/.vtodo/Lib/site-packages/werkzeug/wrappers/request.py new file mode 100644 index 0000000..57b739c --- /dev/null +++ b/.vtodo/Lib/site-packages/werkzeug/wrappers/request.py @@ -0,0 +1,614 @@ +import functools +import json +import typing +import typing as t +from io import BytesIO + +from .._internal import _wsgi_decoding_dance +from ..datastructures import CombinedMultiDict +from ..datastructures import EnvironHeaders +from ..datastructures import FileStorage +from ..datastructures import ImmutableMultiDict +from ..datastructures import iter_multi_items +from ..datastructures import MultiDict +from ..formparser import default_stream_factory +from ..formparser import FormDataParser +from ..sansio.request import Request as _SansIORequest +from ..utils import cached_property +from ..utils import environ_property +from ..wsgi import _get_server +from ..wsgi import get_input_stream +from werkzeug.exceptions import BadRequest + +if t.TYPE_CHECKING: + import typing_extensions as te + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + + +class Request(_SansIORequest): + """Represents an incoming WSGI HTTP request, with headers and body + taken from the WSGI environment. Has properties and methods for + using the functionality defined by various HTTP specs. The data in + requests object is read-only. + + Text data is assumed to use UTF-8 encoding, which should be true for + the vast majority of modern clients. Using an encoding set by the + client is unsafe in Python due to extra encodings it provides, such + as ``zip``. To change the assumed encoding, subclass and replace + :attr:`charset`. + + :param environ: The WSGI environ is generated by the WSGI server and + contains information about the server configuration and client + request. + :param populate_request: Add this request object to the WSGI environ + as ``environ['werkzeug.request']``. Can be useful when + debugging. + :param shallow: Makes reading from :attr:`stream` (and any method + that would read from it) raise a :exc:`RuntimeError`. Useful to + prevent consuming the form data in middleware, which would make + it unavailable to the final application. + + .. versionchanged:: 2.1 + Remove the ``disable_data_descriptor`` attribute. + + .. versionchanged:: 2.0 + Combine ``BaseRequest`` and mixins into a single ``Request`` + class. Using the old classes is deprecated and will be removed + in Werkzeug 2.1. + + .. versionchanged:: 0.5 + Read-only mode is enforced with immutable classes for all data. + """ + + #: the maximum content length. This is forwarded to the form data + #: parsing function (:func:`parse_form_data`). When set and the + #: :attr:`form` or :attr:`files` attribute is accessed and the + #: parsing fails because more than the specified value is transmitted + #: a :exc:`~werkzeug.exceptions.RequestEntityTooLarge` exception is raised. + #: + #: Have a look at :doc:`/request_data` for more details. + #: + #: .. versionadded:: 0.5 + max_content_length: t.Optional[int] = None + + #: the maximum form field size. This is forwarded to the form data + #: parsing function (:func:`parse_form_data`). When set and the + #: :attr:`form` or :attr:`files` attribute is accessed and the + #: data in memory for post data is longer than the specified value a + #: :exc:`~werkzeug.exceptions.RequestEntityTooLarge` exception is raised. + #: + #: Have a look at :doc:`/request_data` for more details. + #: + #: .. versionadded:: 0.5 + max_form_memory_size: t.Optional[int] = None + + #: The form data parser that should be used. Can be replaced to customize + #: the form date parsing. + form_data_parser_class: t.Type[FormDataParser] = FormDataParser + + #: The WSGI environment containing HTTP headers and information from + #: the WSGI server. + environ: "WSGIEnvironment" + + #: Set when creating the request object. If ``True``, reading from + #: the request body will cause a ``RuntimeException``. Useful to + #: prevent modifying the stream from middleware. + shallow: bool + + def __init__( + self, + environ: "WSGIEnvironment", + populate_request: bool = True, + shallow: bool = False, + ) -> None: + super().__init__( + method=environ.get("REQUEST_METHOD", "GET"), + scheme=environ.get("wsgi.url_scheme", "http"), + server=_get_server(environ), + root_path=_wsgi_decoding_dance( + environ.get("SCRIPT_NAME") or "", self.charset, self.encoding_errors + ), + path=_wsgi_decoding_dance( + environ.get("PATH_INFO") or "", self.charset, self.encoding_errors + ), + query_string=environ.get("QUERY_STRING", "").encode("latin1"), + headers=EnvironHeaders(environ), + remote_addr=environ.get("REMOTE_ADDR"), + ) + self.environ = environ + self.shallow = shallow + + if populate_request and not shallow: + self.environ["werkzeug.request"] = self + + @classmethod + def from_values(cls, *args: t.Any, **kwargs: t.Any) -> "Request": + """Create a new request object based on the values provided. If + environ is given missing values are filled from there. This method is + useful for small scripts when you need to simulate a request from an URL. + Do not use this method for unittesting, there is a full featured client + object (:class:`Client`) that allows to create multipart requests, + support for cookies etc. + + This accepts the same options as the + :class:`~werkzeug.test.EnvironBuilder`. + + .. versionchanged:: 0.5 + This method now accepts the same arguments as + :class:`~werkzeug.test.EnvironBuilder`. Because of this the + `environ` parameter is now called `environ_overrides`. + + :return: request object + """ + from ..test import EnvironBuilder + + charset = kwargs.pop("charset", cls.charset) + kwargs["charset"] = charset + builder = EnvironBuilder(*args, **kwargs) + try: + return builder.get_request(cls) + finally: + builder.close() + + @classmethod + def application( + cls, f: t.Callable[["Request"], "WSGIApplication"] + ) -> "WSGIApplication": + """Decorate a function as responder that accepts the request as + the last argument. This works like the :func:`responder` + decorator but the function is passed the request object as the + last argument and the request object will be closed + automatically:: + + @Request.application + def my_wsgi_app(request): + return Response('Hello World!') + + As of Werkzeug 0.14 HTTP exceptions are automatically caught and + converted to responses instead of failing. + + :param f: the WSGI callable to decorate + :return: a new WSGI callable + """ + #: return a callable that wraps the -2nd argument with the request + #: and calls the function with all the arguments up to that one and + #: the request. The return value is then called with the latest + #: two arguments. This makes it possible to use this decorator for + #: both standalone WSGI functions as well as bound methods and + #: partially applied functions. + from ..exceptions import HTTPException + + @functools.wraps(f) + def application(*args): # type: ignore + request = cls(args[-2]) + with request: + try: + resp = f(*args[:-2] + (request,)) + except HTTPException as e: + resp = e.get_response(args[-2]) + return resp(*args[-2:]) + + return t.cast("WSGIApplication", application) + + def _get_file_stream( + self, + total_content_length: t.Optional[int], + content_type: t.Optional[str], + filename: t.Optional[str] = None, + content_length: t.Optional[int] = None, + ) -> t.IO[bytes]: + """Called to get a stream for the file upload. + + This must provide a file-like class with `read()`, `readline()` + and `seek()` methods that is both writeable and readable. + + The default implementation returns a temporary file if the total + content length is higher than 500KB. Because many browsers do not + provide a content length for the files only the total content + length matters. + + :param total_content_length: the total content length of all the + data in the request combined. This value + is guaranteed to be there. + :param content_type: the mimetype of the uploaded file. + :param filename: the filename of the uploaded file. May be `None`. + :param content_length: the length of this file. This value is usually + not provided because webbrowsers do not provide + this value. + """ + return default_stream_factory( + total_content_length=total_content_length, + filename=filename, + content_type=content_type, + content_length=content_length, + ) + + @property + def want_form_data_parsed(self) -> bool: + """``True`` if the request method carries content. By default + this is true if a ``Content-Type`` is sent. + + .. versionadded:: 0.8 + """ + return bool(self.environ.get("CONTENT_TYPE")) + + def make_form_data_parser(self) -> FormDataParser: + """Creates the form data parser. Instantiates the + :attr:`form_data_parser_class` with some parameters. + + .. versionadded:: 0.8 + """ + return self.form_data_parser_class( + self._get_file_stream, + self.charset, + self.encoding_errors, + self.max_form_memory_size, + self.max_content_length, + self.parameter_storage_class, + ) + + def _load_form_data(self) -> None: + """Method used internally to retrieve submitted data. After calling + this sets `form` and `files` on the request object to multi dicts + filled with the incoming form data. As a matter of fact the input + stream will be empty afterwards. You can also call this method to + force the parsing of the form data. + + .. versionadded:: 0.8 + """ + # abort early if we have already consumed the stream + if "form" in self.__dict__: + return + + if self.want_form_data_parsed: + parser = self.make_form_data_parser() + data = parser.parse( + self._get_stream_for_parsing(), + self.mimetype, + self.content_length, + self.mimetype_params, + ) + else: + data = ( + self.stream, + self.parameter_storage_class(), + self.parameter_storage_class(), + ) + + # inject the values into the instance dict so that we bypass + # our cached_property non-data descriptor. + d = self.__dict__ + d["stream"], d["form"], d["files"] = data + + def _get_stream_for_parsing(self) -> t.IO[bytes]: + """This is the same as accessing :attr:`stream` with the difference + that if it finds cached data from calling :meth:`get_data` first it + will create a new stream out of the cached data. + + .. versionadded:: 0.9.3 + """ + cached_data = getattr(self, "_cached_data", None) + if cached_data is not None: + return BytesIO(cached_data) + return self.stream + + def close(self) -> None: + """Closes associated resources of this request object. This + closes all file handles explicitly. You can also use the request + object in a with statement which will automatically close it. + + .. versionadded:: 0.9 + """ + files = self.__dict__.get("files") + for _key, value in iter_multi_items(files or ()): + value.close() + + def __enter__(self) -> "Request": + return self + + def __exit__(self, exc_type, exc_value, tb) -> None: # type: ignore + self.close() + + @cached_property + def stream(self) -> t.IO[bytes]: + """ + If the incoming form data was not encoded with a known mimetype + the data is stored unmodified in this stream for consumption. Most + of the time it is a better idea to use :attr:`data` which will give + you that data as a string. The stream only returns the data once. + + Unlike :attr:`input_stream` this stream is properly guarded that you + can't accidentally read past the length of the input. Werkzeug will + internally always refer to this stream to read data which makes it + possible to wrap this object with a stream that does filtering. + + .. versionchanged:: 0.9 + This stream is now always available but might be consumed by the + form parser later on. Previously the stream was only set if no + parsing happened. + """ + if self.shallow: + raise RuntimeError( + "This request was created with 'shallow=True', reading" + " from the input stream is disabled." + ) + + return get_input_stream(self.environ) + + input_stream = environ_property[t.IO[bytes]]( + "wsgi.input", + doc="""The WSGI input stream. + + In general it's a bad idea to use this one because you can + easily read past the boundary. Use the :attr:`stream` + instead.""", + ) + + @cached_property + def data(self) -> bytes: + """ + Contains the incoming request data as string in case it came with + a mimetype Werkzeug does not handle. + """ + return self.get_data(parse_form_data=True) + + @typing.overload + def get_data( # type: ignore + self, + cache: bool = True, + as_text: "te.Literal[False]" = False, + parse_form_data: bool = False, + ) -> bytes: + ... + + @typing.overload + def get_data( + self, + cache: bool = True, + as_text: "te.Literal[True]" = ..., + parse_form_data: bool = False, + ) -> str: + ... + + def get_data( + self, cache: bool = True, as_text: bool = False, parse_form_data: bool = False + ) -> t.Union[bytes, str]: + """This reads the buffered incoming data from the client into one + bytes object. By default this is cached but that behavior can be + changed by setting `cache` to `False`. + + Usually it's a bad idea to call this method without checking the + content length first as a client could send dozens of megabytes or more + to cause memory problems on the server. + + Note that if the form data was already parsed this method will not + return anything as form data parsing does not cache the data like + this method does. To implicitly invoke form data parsing function + set `parse_form_data` to `True`. When this is done the return value + of this method will be an empty string if the form parser handles + the data. This generally is not necessary as if the whole data is + cached (which is the default) the form parser will used the cached + data to parse the form data. Please be generally aware of checking + the content length first in any case before calling this method + to avoid exhausting server memory. + + If `as_text` is set to `True` the return value will be a decoded + string. + + .. versionadded:: 0.9 + """ + rv = getattr(self, "_cached_data", None) + if rv is None: + if parse_form_data: + self._load_form_data() + rv = self.stream.read() + if cache: + self._cached_data = rv + if as_text: + rv = rv.decode(self.charset, self.encoding_errors) + return rv + + @cached_property + def form(self) -> "ImmutableMultiDict[str, str]": + """The form parameters. By default an + :class:`~werkzeug.datastructures.ImmutableMultiDict` + is returned from this function. This can be changed by setting + :attr:`parameter_storage_class` to a different type. This might + be necessary if the order of the form data is important. + + Please keep in mind that file uploads will not end up here, but instead + in the :attr:`files` attribute. + + .. versionchanged:: 0.9 + + Previous to Werkzeug 0.9 this would only contain form data for POST + and PUT requests. + """ + self._load_form_data() + return self.form + + @cached_property + def values(self) -> "CombinedMultiDict[str, str]": + """A :class:`werkzeug.datastructures.CombinedMultiDict` that + combines :attr:`args` and :attr:`form`. + + For GET requests, only ``args`` are present, not ``form``. + + .. versionchanged:: 2.0 + For GET requests, only ``args`` are present, not ``form``. + """ + sources = [self.args] + + if self.method != "GET": + # GET requests can have a body, and some caching proxies + # might not treat that differently than a normal GET + # request, allowing form data to "invisibly" affect the + # cache without indication in the query string / URL. + sources.append(self.form) + + args = [] + + for d in sources: + if not isinstance(d, MultiDict): + d = MultiDict(d) + + args.append(d) + + return CombinedMultiDict(args) + + @cached_property + def files(self) -> "ImmutableMultiDict[str, FileStorage]": + """:class:`~werkzeug.datastructures.MultiDict` object containing + all uploaded files. Each key in :attr:`files` is the name from the + ````. Each value in :attr:`files` is a + Werkzeug :class:`~werkzeug.datastructures.FileStorage` object. + + It basically behaves like a standard file object you know from Python, + with the difference that it also has a + :meth:`~werkzeug.datastructures.FileStorage.save` function that can + store the file on the filesystem. + + Note that :attr:`files` will only contain data if the request method was + POST, PUT or PATCH and the ``

    `` that posted to the request had + ``enctype="multipart/form-data"``. It will be empty otherwise. + + See the :class:`~werkzeug.datastructures.MultiDict` / + :class:`~werkzeug.datastructures.FileStorage` documentation for + more details about the used data structure. + """ + self._load_form_data() + return self.files + + @property + def script_root(self) -> str: + """Alias for :attr:`self.root_path`. ``environ["SCRIPT_ROOT"]`` + without a trailing slash. + """ + return self.root_path + + @cached_property + def url_root(self) -> str: + """Alias for :attr:`root_url`. The URL with scheme, host, and + root path. For example, ``https://example.com/app/``. + """ + return self.root_url + + remote_user = environ_property[str]( + "REMOTE_USER", + doc="""If the server supports user authentication, and the + script is protected, this attribute contains the username the + user has authenticated as.""", + ) + is_multithread = environ_property[bool]( + "wsgi.multithread", + doc="""boolean that is `True` if the application is served by a + multithreaded WSGI server.""", + ) + is_multiprocess = environ_property[bool]( + "wsgi.multiprocess", + doc="""boolean that is `True` if the application is served by a + WSGI server that spawns multiple processes.""", + ) + is_run_once = environ_property[bool]( + "wsgi.run_once", + doc="""boolean that is `True` if the application will be + executed only once in a process lifetime. This is the case for + CGI for example, but it's not guaranteed that the execution only + happens one time.""", + ) + + # JSON + + #: A module or other object that has ``dumps`` and ``loads`` + #: functions that match the API of the built-in :mod:`json` module. + json_module = json + + @property + def json(self) -> t.Optional[t.Any]: + """The parsed JSON data if :attr:`mimetype` indicates JSON + (:mimetype:`application/json`, see :attr:`is_json`). + + Calls :meth:`get_json` with default arguments. + + If the request content type is not ``application/json``, this + will raise a 400 Bad Request error. + + .. versionchanged:: 2.1 + Raise a 400 error if the content type is incorrect. + """ + return self.get_json() + + # Cached values for ``(silent=False, silent=True)``. Initialized + # with sentinel values. + _cached_json: t.Tuple[t.Any, t.Any] = (Ellipsis, Ellipsis) + + def get_json( + self, force: bool = False, silent: bool = False, cache: bool = True + ) -> t.Optional[t.Any]: + """Parse :attr:`data` as JSON. + + If the mimetype does not indicate JSON + (:mimetype:`application/json`, see :attr:`is_json`), or parsing + fails, :meth:`on_json_loading_failed` is called and + its return value is used as the return value. By default this + raises a 400 Bad Request error. + + :param force: Ignore the mimetype and always try to parse JSON. + :param silent: Silence mimetype and parsing errors, and + return ``None`` instead. + :param cache: Store the parsed JSON to return for subsequent + calls. + + .. versionchanged:: 2.1 + Raise a 400 error if the content type is incorrect. + """ + if cache and self._cached_json[silent] is not Ellipsis: + return self._cached_json[silent] + + if not (force or self.is_json): + if not silent: + return self.on_json_loading_failed(None) + else: + return None + + data = self.get_data(cache=cache) + + try: + rv = self.json_module.loads(data) + except ValueError as e: + if silent: + rv = None + + if cache: + normal_rv, _ = self._cached_json + self._cached_json = (normal_rv, rv) + else: + rv = self.on_json_loading_failed(e) + + if cache: + _, silent_rv = self._cached_json + self._cached_json = (rv, silent_rv) + else: + if cache: + self._cached_json = (rv, rv) + + return rv + + def on_json_loading_failed(self, e: t.Optional[ValueError]) -> t.Any: + """Called if :meth:`get_json` fails and isn't silenced. + + If this method returns a value, it is used as the return value + for :meth:`get_json`. The default implementation raises + :exc:`~werkzeug.exceptions.BadRequest`. + + :param e: If parsing failed, this is the exception. It will be + ``None`` if the content type wasn't ``application/json``. + """ + if e is not None: + raise BadRequest(f"Failed to decode JSON object: {e}") + + raise BadRequest( + "Did not attempt to load JSON data because the request" + " Content-Type was not 'application/json'." + ) diff --git a/.vtodo/Lib/site-packages/werkzeug/wrappers/response.py b/.vtodo/Lib/site-packages/werkzeug/wrappers/response.py new file mode 100644 index 0000000..7e888cb --- /dev/null +++ b/.vtodo/Lib/site-packages/werkzeug/wrappers/response.py @@ -0,0 +1,877 @@ +import json +import typing +import typing as t +import warnings +from http import HTTPStatus + +from .._internal import _to_bytes +from ..datastructures import Headers +from ..http import remove_entity_headers +from ..sansio.response import Response as _SansIOResponse +from ..urls import iri_to_uri +from ..urls import url_join +from ..utils import cached_property +from ..wsgi import ClosingIterator +from ..wsgi import get_current_url +from werkzeug._internal import _get_environ +from werkzeug.http import generate_etag +from werkzeug.http import http_date +from werkzeug.http import is_resource_modified +from werkzeug.http import parse_etags +from werkzeug.http import parse_range_header +from werkzeug.wsgi import _RangeWrapper + +if t.TYPE_CHECKING: + import typing_extensions as te + from _typeshed.wsgi import StartResponse + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + from .request import Request + + +def _warn_if_string(iterable: t.Iterable) -> None: + """Helper for the response objects to check if the iterable returned + to the WSGI server is not a string. + """ + if isinstance(iterable, str): + warnings.warn( + "Response iterable was set to a string. This will appear to" + " work but means that the server will send the data to the" + " client one character at a time. This is almost never" + " intended behavior, use 'response.data' to assign strings" + " to the response object.", + stacklevel=2, + ) + + +def _iter_encoded( + iterable: t.Iterable[t.Union[str, bytes]], charset: str +) -> t.Iterator[bytes]: + for item in iterable: + if isinstance(item, str): + yield item.encode(charset) + else: + yield item + + +def _clean_accept_ranges(accept_ranges: t.Union[bool, str]) -> str: + if accept_ranges is True: + return "bytes" + elif accept_ranges is False: + return "none" + elif isinstance(accept_ranges, str): + return accept_ranges + raise ValueError("Invalid accept_ranges value") + + +class Response(_SansIOResponse): + """Represents an outgoing WSGI HTTP response with body, status, and + headers. Has properties and methods for using the functionality + defined by various HTTP specs. + + The response body is flexible to support different use cases. The + simple form is passing bytes, or a string which will be encoded as + UTF-8. Passing an iterable of bytes or strings makes this a + streaming response. A generator is particularly useful for building + a CSV file in memory or using SSE (Server Sent Events). A file-like + object is also iterable, although the + :func:`~werkzeug.utils.send_file` helper should be used in that + case. + + The response object is itself a WSGI application callable. When + called (:meth:`__call__`) with ``environ`` and ``start_response``, + it will pass its status and headers to ``start_response`` then + return its body as an iterable. + + .. code-block:: python + + from werkzeug.wrappers.response import Response + + def index(): + return Response("Hello, World!") + + def application(environ, start_response): + path = environ.get("PATH_INFO") or "/" + + if path == "/": + response = index() + else: + response = Response("Not Found", status=404) + + return response(environ, start_response) + + :param response: The data for the body of the response. A string or + bytes, or tuple or list of strings or bytes, for a fixed-length + response, or any other iterable of strings or bytes for a + streaming response. Defaults to an empty body. + :param status: The status code for the response. Either an int, in + which case the default status message is added, or a string in + the form ``{code} {message}``, like ``404 Not Found``. Defaults + to 200. + :param headers: A :class:`~werkzeug.datastructures.Headers` object, + or a list of ``(key, value)`` tuples that will be converted to a + ``Headers`` object. + :param mimetype: The mime type (content type without charset or + other parameters) of the response. If the value starts with + ``text/`` (or matches some other special cases), the charset + will be added to create the ``content_type``. + :param content_type: The full content type of the response. + Overrides building the value from ``mimetype``. + :param direct_passthrough: Pass the response body directly through + as the WSGI iterable. This can be used when the body is a binary + file or other iterator of bytes, to skip some unnecessary + checks. Use :func:`~werkzeug.utils.send_file` instead of setting + this manually. + + .. versionchanged:: 2.0 + Combine ``BaseResponse`` and mixins into a single ``Response`` + class. Using the old classes is deprecated and will be removed + in Werkzeug 2.1. + + .. versionchanged:: 0.5 + The ``direct_passthrough`` parameter was added. + """ + + #: if set to `False` accessing properties on the response object will + #: not try to consume the response iterator and convert it into a list. + #: + #: .. versionadded:: 0.6.2 + #: + #: That attribute was previously called `implicit_seqence_conversion`. + #: (Notice the typo). If you did use this feature, you have to adapt + #: your code to the name change. + implicit_sequence_conversion = True + + #: If a redirect ``Location`` header is a relative URL, make it an + #: absolute URL, including scheme and domain. + #: + #: .. versionchanged:: 2.1 + #: This is disabled by default, so responses will send relative + #: redirects. + #: + #: .. versionadded:: 0.8 + autocorrect_location_header = False + + #: Should this response object automatically set the content-length + #: header if possible? This is true by default. + #: + #: .. versionadded:: 0.8 + automatically_set_content_length = True + + #: The response body to send as the WSGI iterable. A list of strings + #: or bytes represents a fixed-length response, any other iterable + #: is a streaming response. Strings are encoded to bytes as UTF-8. + #: + #: Do not set to a plain string or bytes, that will cause sending + #: the response to be very inefficient as it will iterate one byte + #: at a time. + response: t.Union[t.Iterable[str], t.Iterable[bytes]] + + def __init__( + self, + response: t.Optional[ + t.Union[t.Iterable[bytes], bytes, t.Iterable[str], str] + ] = None, + status: t.Optional[t.Union[int, str, HTTPStatus]] = None, + headers: t.Optional[ + t.Union[ + t.Mapping[str, t.Union[str, int, t.Iterable[t.Union[str, int]]]], + t.Iterable[t.Tuple[str, t.Union[str, int]]], + ] + ] = None, + mimetype: t.Optional[str] = None, + content_type: t.Optional[str] = None, + direct_passthrough: bool = False, + ) -> None: + super().__init__( + status=status, + headers=headers, + mimetype=mimetype, + content_type=content_type, + ) + + #: Pass the response body directly through as the WSGI iterable. + #: This can be used when the body is a binary file or other + #: iterator of bytes, to skip some unnecessary checks. Use + #: :func:`~werkzeug.utils.send_file` instead of setting this + #: manually. + self.direct_passthrough = direct_passthrough + self._on_close: t.List[t.Callable[[], t.Any]] = [] + + # we set the response after the headers so that if a class changes + # the charset attribute, the data is set in the correct charset. + if response is None: + self.response = [] + elif isinstance(response, (str, bytes, bytearray)): + self.set_data(response) + else: + self.response = response + + def call_on_close(self, func: t.Callable[[], t.Any]) -> t.Callable[[], t.Any]: + """Adds a function to the internal list of functions that should + be called as part of closing down the response. Since 0.7 this + function also returns the function that was passed so that this + can be used as a decorator. + + .. versionadded:: 0.6 + """ + self._on_close.append(func) + return func + + def __repr__(self) -> str: + if self.is_sequence: + body_info = f"{sum(map(len, self.iter_encoded()))} bytes" + else: + body_info = "streamed" if self.is_streamed else "likely-streamed" + return f"<{type(self).__name__} {body_info} [{self.status}]>" + + @classmethod + def force_type( + cls, response: "Response", environ: t.Optional["WSGIEnvironment"] = None + ) -> "Response": + """Enforce that the WSGI response is a response object of the current + type. Werkzeug will use the :class:`Response` internally in many + situations like the exceptions. If you call :meth:`get_response` on an + exception you will get back a regular :class:`Response` object, even + if you are using a custom subclass. + + This method can enforce a given response type, and it will also + convert arbitrary WSGI callables into response objects if an environ + is provided:: + + # convert a Werkzeug response object into an instance of the + # MyResponseClass subclass. + response = MyResponseClass.force_type(response) + + # convert any WSGI application into a response object + response = MyResponseClass.force_type(response, environ) + + This is especially useful if you want to post-process responses in + the main dispatcher and use functionality provided by your subclass. + + Keep in mind that this will modify response objects in place if + possible! + + :param response: a response object or wsgi application. + :param environ: a WSGI environment object. + :return: a response object. + """ + if not isinstance(response, Response): + if environ is None: + raise TypeError( + "cannot convert WSGI application into response" + " objects without an environ" + ) + + from ..test import run_wsgi_app + + response = Response(*run_wsgi_app(response, environ)) + + response.__class__ = cls + return response + + @classmethod + def from_app( + cls, app: "WSGIApplication", environ: "WSGIEnvironment", buffered: bool = False + ) -> "Response": + """Create a new response object from an application output. This + works best if you pass it an application that returns a generator all + the time. Sometimes applications may use the `write()` callable + returned by the `start_response` function. This tries to resolve such + edge cases automatically. But if you don't get the expected output + you should set `buffered` to `True` which enforces buffering. + + :param app: the WSGI application to execute. + :param environ: the WSGI environment to execute against. + :param buffered: set to `True` to enforce buffering. + :return: a response object. + """ + from ..test import run_wsgi_app + + return cls(*run_wsgi_app(app, environ, buffered)) + + @typing.overload + def get_data(self, as_text: "te.Literal[False]" = False) -> bytes: + ... + + @typing.overload + def get_data(self, as_text: "te.Literal[True]") -> str: + ... + + def get_data(self, as_text: bool = False) -> t.Union[bytes, str]: + """The string representation of the response body. Whenever you call + this property the response iterable is encoded and flattened. This + can lead to unwanted behavior if you stream big data. + + This behavior can be disabled by setting + :attr:`implicit_sequence_conversion` to `False`. + + If `as_text` is set to `True` the return value will be a decoded + string. + + .. versionadded:: 0.9 + """ + self._ensure_sequence() + rv = b"".join(self.iter_encoded()) + + if as_text: + return rv.decode(self.charset) + + return rv + + def set_data(self, value: t.Union[bytes, str]) -> None: + """Sets a new string as response. The value must be a string or + bytes. If a string is set it's encoded to the charset of the + response (utf-8 by default). + + .. versionadded:: 0.9 + """ + # if a string is set, it's encoded directly so that we + # can set the content length + if isinstance(value, str): + value = value.encode(self.charset) + else: + value = bytes(value) + self.response = [value] + if self.automatically_set_content_length: + self.headers["Content-Length"] = str(len(value)) + + data = property( + get_data, + set_data, + doc="A descriptor that calls :meth:`get_data` and :meth:`set_data`.", + ) + + def calculate_content_length(self) -> t.Optional[int]: + """Returns the content length if available or `None` otherwise.""" + try: + self._ensure_sequence() + except RuntimeError: + return None + return sum(len(x) for x in self.iter_encoded()) + + def _ensure_sequence(self, mutable: bool = False) -> None: + """This method can be called by methods that need a sequence. If + `mutable` is true, it will also ensure that the response sequence + is a standard Python list. + + .. versionadded:: 0.6 + """ + if self.is_sequence: + # if we need a mutable object, we ensure it's a list. + if mutable and not isinstance(self.response, list): + self.response = list(self.response) # type: ignore + return + if self.direct_passthrough: + raise RuntimeError( + "Attempted implicit sequence conversion but the" + " response object is in direct passthrough mode." + ) + if not self.implicit_sequence_conversion: + raise RuntimeError( + "The response object required the iterable to be a" + " sequence, but the implicit conversion was disabled." + " Call make_sequence() yourself." + ) + self.make_sequence() + + def make_sequence(self) -> None: + """Converts the response iterator in a list. By default this happens + automatically if required. If `implicit_sequence_conversion` is + disabled, this method is not automatically called and some properties + might raise exceptions. This also encodes all the items. + + .. versionadded:: 0.6 + """ + if not self.is_sequence: + # if we consume an iterable we have to ensure that the close + # method of the iterable is called if available when we tear + # down the response + close = getattr(self.response, "close", None) + self.response = list(self.iter_encoded()) + if close is not None: + self.call_on_close(close) + + def iter_encoded(self) -> t.Iterator[bytes]: + """Iter the response encoded with the encoding of the response. + If the response object is invoked as WSGI application the return + value of this method is used as application iterator unless + :attr:`direct_passthrough` was activated. + """ + if __debug__: + _warn_if_string(self.response) + # Encode in a separate function so that self.response is fetched + # early. This allows us to wrap the response with the return + # value from get_app_iter or iter_encoded. + return _iter_encoded(self.response, self.charset) + + @property + def is_streamed(self) -> bool: + """If the response is streamed (the response is not an iterable with + a length information) this property is `True`. In this case streamed + means that there is no information about the number of iterations. + This is usually `True` if a generator is passed to the response object. + + This is useful for checking before applying some sort of post + filtering that should not take place for streamed responses. + """ + try: + len(self.response) # type: ignore + except (TypeError, AttributeError): + return True + return False + + @property + def is_sequence(self) -> bool: + """If the iterator is buffered, this property will be `True`. A + response object will consider an iterator to be buffered if the + response attribute is a list or tuple. + + .. versionadded:: 0.6 + """ + return isinstance(self.response, (tuple, list)) + + def close(self) -> None: + """Close the wrapped response if possible. You can also use the object + in a with statement which will automatically close it. + + .. versionadded:: 0.9 + Can now be used in a with statement. + """ + if hasattr(self.response, "close"): + self.response.close() # type: ignore + for func in self._on_close: + func() + + def __enter__(self) -> "Response": + return self + + def __exit__(self, exc_type, exc_value, tb): # type: ignore + self.close() + + def freeze(self) -> None: + """Make the response object ready to be pickled. Does the + following: + + * Buffer the response into a list, ignoring + :attr:`implicity_sequence_conversion` and + :attr:`direct_passthrough`. + * Set the ``Content-Length`` header. + * Generate an ``ETag`` header if one is not already set. + + .. versionchanged:: 2.1 + Removed the ``no_etag`` parameter. + + .. versionchanged:: 2.0 + An ``ETag`` header is added, the ``no_etag`` parameter is + deprecated and will be removed in Werkzeug 2.1. + + .. versionchanged:: 0.6 + The ``Content-Length`` header is set. + """ + # Always freeze the encoded response body, ignore + # implicit_sequence_conversion and direct_passthrough. + self.response = list(self.iter_encoded()) + self.headers["Content-Length"] = str(sum(map(len, self.response))) + self.add_etag() + + def get_wsgi_headers(self, environ: "WSGIEnvironment") -> Headers: + """This is automatically called right before the response is started + and returns headers modified for the given environment. It returns a + copy of the headers from the response with some modifications applied + if necessary. + + For example the location header (if present) is joined with the root + URL of the environment. Also the content length is automatically set + to zero here for certain status codes. + + .. versionchanged:: 0.6 + Previously that function was called `fix_headers` and modified + the response object in place. Also since 0.6, IRIs in location + and content-location headers are handled properly. + + Also starting with 0.6, Werkzeug will attempt to set the content + length if it is able to figure it out on its own. This is the + case if all the strings in the response iterable are already + encoded and the iterable is buffered. + + :param environ: the WSGI environment of the request. + :return: returns a new :class:`~werkzeug.datastructures.Headers` + object. + """ + headers = Headers(self.headers) + location: t.Optional[str] = None + content_location: t.Optional[str] = None + content_length: t.Optional[t.Union[str, int]] = None + status = self.status_code + + # iterate over the headers to find all values in one go. Because + # get_wsgi_headers is used each response that gives us a tiny + # speedup. + for key, value in headers: + ikey = key.lower() + if ikey == "location": + location = value + elif ikey == "content-location": + content_location = value + elif ikey == "content-length": + content_length = value + + # make sure the location header is an absolute URL + if location is not None: + old_location = location + if isinstance(location, str): + # Safe conversion is necessary here as we might redirect + # to a broken URI scheme (for instance itms-services). + location = iri_to_uri(location, safe_conversion=True) + + if self.autocorrect_location_header: + current_url = get_current_url(environ, strip_querystring=True) + if isinstance(current_url, str): + current_url = iri_to_uri(current_url) + location = url_join(current_url, location) + if location != old_location: + headers["Location"] = location + + # make sure the content location is a URL + if content_location is not None and isinstance(content_location, str): + headers["Content-Location"] = iri_to_uri(content_location) + + if 100 <= status < 200 or status == 204: + # Per section 3.3.2 of RFC 7230, "a server MUST NOT send a + # Content-Length header field in any response with a status + # code of 1xx (Informational) or 204 (No Content)." + headers.remove("Content-Length") + elif status == 304: + remove_entity_headers(headers) + + # if we can determine the content length automatically, we + # should try to do that. But only if this does not involve + # flattening the iterator or encoding of strings in the + # response. We however should not do that if we have a 304 + # response. + if ( + self.automatically_set_content_length + and self.is_sequence + and content_length is None + and status not in (204, 304) + and not (100 <= status < 200) + ): + try: + content_length = sum(len(_to_bytes(x, "ascii")) for x in self.response) + except UnicodeError: + # Something other than bytes, can't safely figure out + # the length of the response. + pass + else: + headers["Content-Length"] = str(content_length) + + return headers + + def get_app_iter(self, environ: "WSGIEnvironment") -> t.Iterable[bytes]: + """Returns the application iterator for the given environ. Depending + on the request method and the current status code the return value + might be an empty response rather than the one from the response. + + If the request method is `HEAD` or the status code is in a range + where the HTTP specification requires an empty response, an empty + iterable is returned. + + .. versionadded:: 0.6 + + :param environ: the WSGI environment of the request. + :return: a response iterable. + """ + status = self.status_code + if ( + environ["REQUEST_METHOD"] == "HEAD" + or 100 <= status < 200 + or status in (204, 304) + ): + iterable: t.Iterable[bytes] = () + elif self.direct_passthrough: + if __debug__: + _warn_if_string(self.response) + return self.response # type: ignore + else: + iterable = self.iter_encoded() + return ClosingIterator(iterable, self.close) + + def get_wsgi_response( + self, environ: "WSGIEnvironment" + ) -> t.Tuple[t.Iterable[bytes], str, t.List[t.Tuple[str, str]]]: + """Returns the final WSGI response as tuple. The first item in + the tuple is the application iterator, the second the status and + the third the list of headers. The response returned is created + specially for the given environment. For example if the request + method in the WSGI environment is ``'HEAD'`` the response will + be empty and only the headers and status code will be present. + + .. versionadded:: 0.6 + + :param environ: the WSGI environment of the request. + :return: an ``(app_iter, status, headers)`` tuple. + """ + headers = self.get_wsgi_headers(environ) + app_iter = self.get_app_iter(environ) + return app_iter, self.status, headers.to_wsgi_list() + + def __call__( + self, environ: "WSGIEnvironment", start_response: "StartResponse" + ) -> t.Iterable[bytes]: + """Process this response as WSGI application. + + :param environ: the WSGI environment. + :param start_response: the response callable provided by the WSGI + server. + :return: an application iterator + """ + app_iter, status, headers = self.get_wsgi_response(environ) + start_response(status, headers) + return app_iter + + # JSON + + #: A module or other object that has ``dumps`` and ``loads`` + #: functions that match the API of the built-in :mod:`json` module. + json_module = json + + @property + def json(self) -> t.Optional[t.Any]: + """The parsed JSON data if :attr:`mimetype` indicates JSON + (:mimetype:`application/json`, see :attr:`is_json`). + + Calls :meth:`get_json` with default arguments. + """ + return self.get_json() + + def get_json(self, force: bool = False, silent: bool = False) -> t.Optional[t.Any]: + """Parse :attr:`data` as JSON. Useful during testing. + + If the mimetype does not indicate JSON + (:mimetype:`application/json`, see :attr:`is_json`), this + returns ``None``. + + Unlike :meth:`Request.get_json`, the result is not cached. + + :param force: Ignore the mimetype and always try to parse JSON. + :param silent: Silence parsing errors and return ``None`` + instead. + """ + if not (force or self.is_json): + return None + + data = self.get_data() + + try: + return self.json_module.loads(data) + except ValueError: + if not silent: + raise + + return None + + # Stream + + @cached_property + def stream(self) -> "ResponseStream": + """The response iterable as write-only stream.""" + return ResponseStream(self) + + def _wrap_range_response(self, start: int, length: int) -> None: + """Wrap existing Response in case of Range Request context.""" + if self.status_code == 206: + self.response = _RangeWrapper(self.response, start, length) # type: ignore + + def _is_range_request_processable(self, environ: "WSGIEnvironment") -> bool: + """Return ``True`` if `Range` header is present and if underlying + resource is considered unchanged when compared with `If-Range` header. + """ + return ( + "HTTP_IF_RANGE" not in environ + or not is_resource_modified( + environ, + self.headers.get("etag"), + None, + self.headers.get("last-modified"), + ignore_if_range=False, + ) + ) and "HTTP_RANGE" in environ + + def _process_range_request( + self, + environ: "WSGIEnvironment", + complete_length: t.Optional[int] = None, + accept_ranges: t.Optional[t.Union[bool, str]] = None, + ) -> bool: + """Handle Range Request related headers (RFC7233). If `Accept-Ranges` + header is valid, and Range Request is processable, we set the headers + as described by the RFC, and wrap the underlying response in a + RangeWrapper. + + Returns ``True`` if Range Request can be fulfilled, ``False`` otherwise. + + :raises: :class:`~werkzeug.exceptions.RequestedRangeNotSatisfiable` + if `Range` header could not be parsed or satisfied. + + .. versionchanged:: 2.0 + Returns ``False`` if the length is 0. + """ + from ..exceptions import RequestedRangeNotSatisfiable + + if ( + accept_ranges is None + or complete_length is None + or complete_length == 0 + or not self._is_range_request_processable(environ) + ): + return False + + parsed_range = parse_range_header(environ.get("HTTP_RANGE")) + + if parsed_range is None: + raise RequestedRangeNotSatisfiable(complete_length) + + range_tuple = parsed_range.range_for_length(complete_length) + content_range_header = parsed_range.to_content_range_header(complete_length) + + if range_tuple is None or content_range_header is None: + raise RequestedRangeNotSatisfiable(complete_length) + + content_length = range_tuple[1] - range_tuple[0] + self.headers["Content-Length"] = content_length + self.headers["Accept-Ranges"] = accept_ranges + self.content_range = content_range_header # type: ignore + self.status_code = 206 + self._wrap_range_response(range_tuple[0], content_length) + return True + + def make_conditional( + self, + request_or_environ: t.Union["WSGIEnvironment", "Request"], + accept_ranges: t.Union[bool, str] = False, + complete_length: t.Optional[int] = None, + ) -> "Response": + """Make the response conditional to the request. This method works + best if an etag was defined for the response already. The `add_etag` + method can be used to do that. If called without etag just the date + header is set. + + This does nothing if the request method in the request or environ is + anything but GET or HEAD. + + For optimal performance when handling range requests, it's recommended + that your response data object implements `seekable`, `seek` and `tell` + methods as described by :py:class:`io.IOBase`. Objects returned by + :meth:`~werkzeug.wsgi.wrap_file` automatically implement those methods. + + It does not remove the body of the response because that's something + the :meth:`__call__` function does for us automatically. + + Returns self so that you can do ``return resp.make_conditional(req)`` + but modifies the object in-place. + + :param request_or_environ: a request object or WSGI environment to be + used to make the response conditional + against. + :param accept_ranges: This parameter dictates the value of + `Accept-Ranges` header. If ``False`` (default), + the header is not set. If ``True``, it will be set + to ``"bytes"``. If ``None``, it will be set to + ``"none"``. If it's a string, it will use this + value. + :param complete_length: Will be used only in valid Range Requests. + It will set `Content-Range` complete length + value and compute `Content-Length` real value. + This parameter is mandatory for successful + Range Requests completion. + :raises: :class:`~werkzeug.exceptions.RequestedRangeNotSatisfiable` + if `Range` header could not be parsed or satisfied. + + .. versionchanged:: 2.0 + Range processing is skipped if length is 0 instead of + raising a 416 Range Not Satisfiable error. + """ + environ = _get_environ(request_or_environ) + if environ["REQUEST_METHOD"] in ("GET", "HEAD"): + # if the date is not in the headers, add it now. We however + # will not override an already existing header. Unfortunately + # this header will be overridden by many WSGI servers including + # wsgiref. + if "date" not in self.headers: + self.headers["Date"] = http_date() + accept_ranges = _clean_accept_ranges(accept_ranges) + is206 = self._process_range_request(environ, complete_length, accept_ranges) + if not is206 and not is_resource_modified( + environ, + self.headers.get("etag"), + None, + self.headers.get("last-modified"), + ): + if parse_etags(environ.get("HTTP_IF_MATCH")): + self.status_code = 412 + else: + self.status_code = 304 + if ( + self.automatically_set_content_length + and "content-length" not in self.headers + ): + length = self.calculate_content_length() + if length is not None: + self.headers["Content-Length"] = length + return self + + def add_etag(self, overwrite: bool = False, weak: bool = False) -> None: + """Add an etag for the current response if there is none yet. + + .. versionchanged:: 2.0 + SHA-1 is used to generate the value. MD5 may not be + available in some environments. + """ + if overwrite or "etag" not in self.headers: + self.set_etag(generate_etag(self.get_data()), weak) + + +class ResponseStream: + """A file descriptor like object used by :meth:`Response.stream` to + represent the body of the stream. It directly pushes into the + response iterable of the response object. + """ + + mode = "wb+" + + def __init__(self, response: Response): + self.response = response + self.closed = False + + def write(self, value: bytes) -> int: + if self.closed: + raise ValueError("I/O operation on closed file") + self.response._ensure_sequence(mutable=True) + self.response.response.append(value) # type: ignore + self.response.headers.pop("Content-Length", None) + return len(value) + + def writelines(self, seq: t.Iterable[bytes]) -> None: + for item in seq: + self.write(item) + + def close(self) -> None: + self.closed = True + + def flush(self) -> None: + if self.closed: + raise ValueError("I/O operation on closed file") + + def isatty(self) -> bool: + if self.closed: + raise ValueError("I/O operation on closed file") + return False + + def tell(self) -> int: + self.response._ensure_sequence() + return sum(map(len, self.response.response)) + + @property + def encoding(self) -> str: + return self.response.charset diff --git a/.vtodo/Lib/site-packages/werkzeug/wsgi.py b/.vtodo/Lib/site-packages/werkzeug/wsgi.py new file mode 100644 index 0000000..24ece0b --- /dev/null +++ b/.vtodo/Lib/site-packages/werkzeug/wsgi.py @@ -0,0 +1,1020 @@ +import io +import re +import typing as t +import warnings +from functools import partial +from functools import update_wrapper +from itertools import chain + +from ._internal import _make_encode_wrapper +from ._internal import _to_bytes +from ._internal import _to_str +from .sansio import utils as _sansio_utils +from .sansio.utils import host_is_trusted # noqa: F401 # Imported as part of API +from .urls import _URLTuple +from .urls import uri_to_iri +from .urls import url_join +from .urls import url_parse +from .urls import url_quote + +if t.TYPE_CHECKING: + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + + +def responder(f: t.Callable[..., "WSGIApplication"]) -> "WSGIApplication": + """Marks a function as responder. Decorate a function with it and it + will automatically call the return value as WSGI application. + + Example:: + + @responder + def application(environ, start_response): + return Response('Hello World!') + """ + return update_wrapper(lambda *a: f(*a)(*a[-2:]), f) + + +def get_current_url( + environ: "WSGIEnvironment", + root_only: bool = False, + strip_querystring: bool = False, + host_only: bool = False, + trusted_hosts: t.Optional[t.Iterable[str]] = None, +) -> str: + """Recreate the URL for a request from the parts in a WSGI + environment. + + The URL is an IRI, not a URI, so it may contain Unicode characters. + Use :func:`~werkzeug.urls.iri_to_uri` to convert it to ASCII. + + :param environ: The WSGI environment to get the URL parts from. + :param root_only: Only build the root path, don't include the + remaining path or query string. + :param strip_querystring: Don't include the query string. + :param host_only: Only build the scheme and host. + :param trusted_hosts: A list of trusted host names to validate the + host against. + """ + parts = { + "scheme": environ["wsgi.url_scheme"], + "host": get_host(environ, trusted_hosts), + } + + if not host_only: + parts["root_path"] = environ.get("SCRIPT_NAME", "") + + if not root_only: + parts["path"] = environ.get("PATH_INFO", "") + + if not strip_querystring: + parts["query_string"] = environ.get("QUERY_STRING", "").encode("latin1") + + return _sansio_utils.get_current_url(**parts) + + +def _get_server( + environ: "WSGIEnvironment", +) -> t.Optional[t.Tuple[str, t.Optional[int]]]: + name = environ.get("SERVER_NAME") + + if name is None: + return None + + try: + port: t.Optional[int] = int(environ.get("SERVER_PORT", None)) + except (TypeError, ValueError): + # unix socket + port = None + + return name, port + + +def get_host( + environ: "WSGIEnvironment", trusted_hosts: t.Optional[t.Iterable[str]] = None +) -> str: + """Return the host for the given WSGI environment. + + The ``Host`` header is preferred, then ``SERVER_NAME`` if it's not + set. The returned host will only contain the port if it is different + than the standard port for the protocol. + + Optionally, verify that the host is trusted using + :func:`host_is_trusted` and raise a + :exc:`~werkzeug.exceptions.SecurityError` if it is not. + + :param environ: A WSGI environment dict. + :param trusted_hosts: A list of trusted host names. + + :return: Host, with port if necessary. + :raise ~werkzeug.exceptions.SecurityError: If the host is not + trusted. + """ + return _sansio_utils.get_host( + environ["wsgi.url_scheme"], + environ.get("HTTP_HOST"), + _get_server(environ), + trusted_hosts, + ) + + +def get_content_length(environ: "WSGIEnvironment") -> t.Optional[int]: + """Returns the content length from the WSGI environment as + integer. If it's not available or chunked transfer encoding is used, + ``None`` is returned. + + .. versionadded:: 0.9 + + :param environ: the WSGI environ to fetch the content length from. + """ + return _sansio_utils.get_content_length( + http_content_length=environ.get("CONTENT_LENGTH"), + http_transfer_encoding=environ.get("HTTP_TRANSFER_ENCODING", ""), + ) + + +def get_input_stream( + environ: "WSGIEnvironment", safe_fallback: bool = True +) -> t.IO[bytes]: + """Returns the input stream from the WSGI environment and wraps it + in the most sensible way possible. The stream returned is not the + raw WSGI stream in most cases but one that is safe to read from + without taking into account the content length. + + If content length is not set, the stream will be empty for safety reasons. + If the WSGI server supports chunked or infinite streams, it should set + the ``wsgi.input_terminated`` value in the WSGI environ to indicate that. + + .. versionadded:: 0.9 + + :param environ: the WSGI environ to fetch the stream from. + :param safe_fallback: use an empty stream as a safe fallback when the + content length is not set. Disabling this allows infinite streams, + which can be a denial-of-service risk. + """ + stream = t.cast(t.IO[bytes], environ["wsgi.input"]) + content_length = get_content_length(environ) + + # A wsgi extension that tells us if the input is terminated. In + # that case we return the stream unchanged as we know we can safely + # read it until the end. + if environ.get("wsgi.input_terminated"): + return stream + + # If the request doesn't specify a content length, returning the stream is + # potentially dangerous because it could be infinite, malicious or not. If + # safe_fallback is true, return an empty stream instead for safety. + if content_length is None: + return io.BytesIO() if safe_fallback else stream + + # Otherwise limit the stream to the content length + return t.cast(t.IO[bytes], LimitedStream(stream, content_length)) + + +def get_query_string(environ: "WSGIEnvironment") -> str: + """Returns the ``QUERY_STRING`` from the WSGI environment. This also + takes care of the WSGI decoding dance. The string returned will be + restricted to ASCII characters. + + :param environ: WSGI environment to get the query string from. + + .. deprecated:: 2.2 + Will be removed in Werkzeug 2.3. + + .. versionadded:: 0.9 + """ + warnings.warn( + "'get_query_string' is deprecated and will be removed in Werkzeug 2.3.", + DeprecationWarning, + stacklevel=2, + ) + qs = environ.get("QUERY_STRING", "").encode("latin1") + # QUERY_STRING really should be ascii safe but some browsers + # will send us some unicode stuff (I am looking at you IE). + # In that case we want to urllib quote it badly. + return url_quote(qs, safe=":&%=+$!*'(),") + + +def get_path_info( + environ: "WSGIEnvironment", charset: str = "utf-8", errors: str = "replace" +) -> str: + """Return the ``PATH_INFO`` from the WSGI environment and decode it + unless ``charset`` is ``None``. + + :param environ: WSGI environment to get the path from. + :param charset: The charset for the path info, or ``None`` if no + decoding should be performed. + :param errors: The decoding error handling. + + .. versionadded:: 0.9 + """ + path = environ.get("PATH_INFO", "").encode("latin1") + return _to_str(path, charset, errors, allow_none_charset=True) # type: ignore + + +def get_script_name( + environ: "WSGIEnvironment", charset: str = "utf-8", errors: str = "replace" +) -> str: + """Return the ``SCRIPT_NAME`` from the WSGI environment and decode + it unless `charset` is set to ``None``. + + :param environ: WSGI environment to get the path from. + :param charset: The charset for the path, or ``None`` if no decoding + should be performed. + :param errors: The decoding error handling. + + .. deprecated:: 2.2 + Will be removed in Werkzeug 2.3. + + .. versionadded:: 0.9 + """ + warnings.warn( + "'get_script_name' is deprecated and will be removed in Werkzeug 2.3.", + DeprecationWarning, + stacklevel=2, + ) + path = environ.get("SCRIPT_NAME", "").encode("latin1") + return _to_str(path, charset, errors, allow_none_charset=True) # type: ignore + + +def pop_path_info( + environ: "WSGIEnvironment", charset: str = "utf-8", errors: str = "replace" +) -> t.Optional[str]: + """Removes and returns the next segment of `PATH_INFO`, pushing it onto + `SCRIPT_NAME`. Returns `None` if there is nothing left on `PATH_INFO`. + + If the `charset` is set to `None` bytes are returned. + + If there are empty segments (``'/foo//bar``) these are ignored but + properly pushed to the `SCRIPT_NAME`: + + >>> env = {'SCRIPT_NAME': '/foo', 'PATH_INFO': '/a/b'} + >>> pop_path_info(env) + 'a' + >>> env['SCRIPT_NAME'] + '/foo/a' + >>> pop_path_info(env) + 'b' + >>> env['SCRIPT_NAME'] + '/foo/a/b' + + .. deprecated:: 2.2 + Will be removed in Werkzeug 2.3. + + .. versionadded:: 0.5 + + .. versionchanged:: 0.9 + The path is now decoded and a charset and encoding + parameter can be provided. + + :param environ: the WSGI environment that is modified. + :param charset: The ``encoding`` parameter passed to + :func:`bytes.decode`. + :param errors: The ``errors`` paramater passed to + :func:`bytes.decode`. + """ + warnings.warn( + "'pop_path_info' is deprecated and will be removed in Werkzeug 2.3.", + DeprecationWarning, + stacklevel=2, + ) + + path = environ.get("PATH_INFO") + if not path: + return None + + script_name = environ.get("SCRIPT_NAME", "") + + # shift multiple leading slashes over + old_path = path + path = path.lstrip("/") + if path != old_path: + script_name += "/" * (len(old_path) - len(path)) + + if "/" not in path: + environ["PATH_INFO"] = "" + environ["SCRIPT_NAME"] = script_name + path + rv = path.encode("latin1") + else: + segment, path = path.split("/", 1) + environ["PATH_INFO"] = f"/{path}" + environ["SCRIPT_NAME"] = script_name + segment + rv = segment.encode("latin1") + + return _to_str(rv, charset, errors, allow_none_charset=True) # type: ignore + + +def peek_path_info( + environ: "WSGIEnvironment", charset: str = "utf-8", errors: str = "replace" +) -> t.Optional[str]: + """Returns the next segment on the `PATH_INFO` or `None` if there + is none. Works like :func:`pop_path_info` without modifying the + environment: + + >>> env = {'SCRIPT_NAME': '/foo', 'PATH_INFO': '/a/b'} + >>> peek_path_info(env) + 'a' + >>> peek_path_info(env) + 'a' + + If the `charset` is set to `None` bytes are returned. + + .. deprecated:: 2.2 + Will be removed in Werkzeug 2.3. + + .. versionadded:: 0.5 + + .. versionchanged:: 0.9 + The path is now decoded and a charset and encoding + parameter can be provided. + + :param environ: the WSGI environment that is checked. + """ + warnings.warn( + "'peek_path_info' is deprecated and will be removed in Werkzeug 2.3.", + DeprecationWarning, + stacklevel=2, + ) + + segments = environ.get("PATH_INFO", "").lstrip("/").split("/", 1) + if segments: + return _to_str( # type: ignore + segments[0].encode("latin1"), charset, errors, allow_none_charset=True + ) + return None + + +def extract_path_info( + environ_or_baseurl: t.Union[str, "WSGIEnvironment"], + path_or_url: t.Union[str, _URLTuple], + charset: str = "utf-8", + errors: str = "werkzeug.url_quote", + collapse_http_schemes: bool = True, +) -> t.Optional[str]: + """Extracts the path info from the given URL (or WSGI environment) and + path. The path info returned is a string. The URLs might also be IRIs. + + If the path info could not be determined, `None` is returned. + + Some examples: + + >>> extract_path_info('http://example.com/app', '/app/hello') + '/hello' + >>> extract_path_info('http://example.com/app', + ... 'https://example.com/app/hello') + '/hello' + >>> extract_path_info('http://example.com/app', + ... 'https://example.com/app/hello', + ... collapse_http_schemes=False) is None + True + + Instead of providing a base URL you can also pass a WSGI environment. + + :param environ_or_baseurl: a WSGI environment dict, a base URL or + base IRI. This is the root of the + application. + :param path_or_url: an absolute path from the server root, a + relative path (in which case it's the path info) + or a full URL. + :param charset: the charset for byte data in URLs + :param errors: the error handling on decode + :param collapse_http_schemes: if set to `False` the algorithm does + not assume that http and https on the + same server point to the same + resource. + + .. deprecated:: 2.2 + Will be removed in Werkzeug 2.3. + + .. versionchanged:: 0.15 + The ``errors`` parameter defaults to leaving invalid bytes + quoted instead of replacing them. + + .. versionadded:: 0.6 + + """ + warnings.warn( + "'extract_path_info' is deprecated and will be removed in Werkzeug 2.3.", + DeprecationWarning, + stacklevel=2, + ) + + def _normalize_netloc(scheme: str, netloc: str) -> str: + parts = netloc.split("@", 1)[-1].split(":", 1) + port: t.Optional[str] + + if len(parts) == 2: + netloc, port = parts + if (scheme == "http" and port == "80") or ( + scheme == "https" and port == "443" + ): + port = None + else: + netloc = parts[0] + port = None + + if port is not None: + netloc += f":{port}" + + return netloc + + # make sure whatever we are working on is a IRI and parse it + path = uri_to_iri(path_or_url, charset, errors) + if isinstance(environ_or_baseurl, dict): + environ_or_baseurl = get_current_url(environ_or_baseurl, root_only=True) + base_iri = uri_to_iri(environ_or_baseurl, charset, errors) + base_scheme, base_netloc, base_path = url_parse(base_iri)[:3] + cur_scheme, cur_netloc, cur_path = url_parse(url_join(base_iri, path))[:3] + + # normalize the network location + base_netloc = _normalize_netloc(base_scheme, base_netloc) + cur_netloc = _normalize_netloc(cur_scheme, cur_netloc) + + # is that IRI even on a known HTTP scheme? + if collapse_http_schemes: + for scheme in base_scheme, cur_scheme: + if scheme not in ("http", "https"): + return None + else: + if not (base_scheme in ("http", "https") and base_scheme == cur_scheme): + return None + + # are the netlocs compatible? + if base_netloc != cur_netloc: + return None + + # are we below the application path? + base_path = base_path.rstrip("/") + if not cur_path.startswith(base_path): + return None + + return f"/{cur_path[len(base_path) :].lstrip('/')}" + + +class ClosingIterator: + """The WSGI specification requires that all middlewares and gateways + respect the `close` callback of the iterable returned by the application. + Because it is useful to add another close action to a returned iterable + and adding a custom iterable is a boring task this class can be used for + that:: + + return ClosingIterator(app(environ, start_response), [cleanup_session, + cleanup_locals]) + + If there is just one close function it can be passed instead of the list. + + A closing iterator is not needed if the application uses response objects + and finishes the processing if the response is started:: + + try: + return response(environ, start_response) + finally: + cleanup_session() + cleanup_locals() + """ + + def __init__( + self, + iterable: t.Iterable[bytes], + callbacks: t.Optional[ + t.Union[t.Callable[[], None], t.Iterable[t.Callable[[], None]]] + ] = None, + ) -> None: + iterator = iter(iterable) + self._next = t.cast(t.Callable[[], bytes], partial(next, iterator)) + if callbacks is None: + callbacks = [] + elif callable(callbacks): + callbacks = [callbacks] + else: + callbacks = list(callbacks) + iterable_close = getattr(iterable, "close", None) + if iterable_close: + callbacks.insert(0, iterable_close) + self._callbacks = callbacks + + def __iter__(self) -> "ClosingIterator": + return self + + def __next__(self) -> bytes: + return self._next() + + def close(self) -> None: + for callback in self._callbacks: + callback() + + +def wrap_file( + environ: "WSGIEnvironment", file: t.IO[bytes], buffer_size: int = 8192 +) -> t.Iterable[bytes]: + """Wraps a file. This uses the WSGI server's file wrapper if available + or otherwise the generic :class:`FileWrapper`. + + .. versionadded:: 0.5 + + If the file wrapper from the WSGI server is used it's important to not + iterate over it from inside the application but to pass it through + unchanged. If you want to pass out a file wrapper inside a response + object you have to set :attr:`Response.direct_passthrough` to `True`. + + More information about file wrappers are available in :pep:`333`. + + :param file: a :class:`file`-like object with a :meth:`~file.read` method. + :param buffer_size: number of bytes for one iteration. + """ + return environ.get("wsgi.file_wrapper", FileWrapper)( # type: ignore + file, buffer_size + ) + + +class FileWrapper: + """This class can be used to convert a :class:`file`-like object into + an iterable. It yields `buffer_size` blocks until the file is fully + read. + + You should not use this class directly but rather use the + :func:`wrap_file` function that uses the WSGI server's file wrapper + support if it's available. + + .. versionadded:: 0.5 + + If you're using this object together with a :class:`Response` you have + to use the `direct_passthrough` mode. + + :param file: a :class:`file`-like object with a :meth:`~file.read` method. + :param buffer_size: number of bytes for one iteration. + """ + + def __init__(self, file: t.IO[bytes], buffer_size: int = 8192) -> None: + self.file = file + self.buffer_size = buffer_size + + def close(self) -> None: + if hasattr(self.file, "close"): + self.file.close() + + def seekable(self) -> bool: + if hasattr(self.file, "seekable"): + return self.file.seekable() + if hasattr(self.file, "seek"): + return True + return False + + def seek(self, *args: t.Any) -> None: + if hasattr(self.file, "seek"): + self.file.seek(*args) + + def tell(self) -> t.Optional[int]: + if hasattr(self.file, "tell"): + return self.file.tell() + return None + + def __iter__(self) -> "FileWrapper": + return self + + def __next__(self) -> bytes: + data = self.file.read(self.buffer_size) + if data: + return data + raise StopIteration() + + +class _RangeWrapper: + # private for now, but should we make it public in the future ? + + """This class can be used to convert an iterable object into + an iterable that will only yield a piece of the underlying content. + It yields blocks until the underlying stream range is fully read. + The yielded blocks will have a size that can't exceed the original + iterator defined block size, but that can be smaller. + + If you're using this object together with a :class:`Response` you have + to use the `direct_passthrough` mode. + + :param iterable: an iterable object with a :meth:`__next__` method. + :param start_byte: byte from which read will start. + :param byte_range: how many bytes to read. + """ + + def __init__( + self, + iterable: t.Union[t.Iterable[bytes], t.IO[bytes]], + start_byte: int = 0, + byte_range: t.Optional[int] = None, + ): + self.iterable = iter(iterable) + self.byte_range = byte_range + self.start_byte = start_byte + self.end_byte = None + + if byte_range is not None: + self.end_byte = start_byte + byte_range + + self.read_length = 0 + self.seekable = ( + hasattr(iterable, "seekable") and iterable.seekable() # type: ignore + ) + self.end_reached = False + + def __iter__(self) -> "_RangeWrapper": + return self + + def _next_chunk(self) -> bytes: + try: + chunk = next(self.iterable) + self.read_length += len(chunk) + return chunk + except StopIteration: + self.end_reached = True + raise + + def _first_iteration(self) -> t.Tuple[t.Optional[bytes], int]: + chunk = None + if self.seekable: + self.iterable.seek(self.start_byte) # type: ignore + self.read_length = self.iterable.tell() # type: ignore + contextual_read_length = self.read_length + else: + while self.read_length <= self.start_byte: + chunk = self._next_chunk() + if chunk is not None: + chunk = chunk[self.start_byte - self.read_length :] + contextual_read_length = self.start_byte + return chunk, contextual_read_length + + def _next(self) -> bytes: + if self.end_reached: + raise StopIteration() + chunk = None + contextual_read_length = self.read_length + if self.read_length == 0: + chunk, contextual_read_length = self._first_iteration() + if chunk is None: + chunk = self._next_chunk() + if self.end_byte is not None and self.read_length >= self.end_byte: + self.end_reached = True + return chunk[: self.end_byte - contextual_read_length] + return chunk + + def __next__(self) -> bytes: + chunk = self._next() + if chunk: + return chunk + self.end_reached = True + raise StopIteration() + + def close(self) -> None: + if hasattr(self.iterable, "close"): + self.iterable.close() # type: ignore + + +def _make_chunk_iter( + stream: t.Union[t.Iterable[bytes], t.IO[bytes]], + limit: t.Optional[int], + buffer_size: int, +) -> t.Iterator[bytes]: + """Helper for the line and chunk iter functions.""" + if isinstance(stream, (bytes, bytearray, str)): + raise TypeError( + "Passed a string or byte object instead of true iterator or stream." + ) + if not hasattr(stream, "read"): + for item in stream: + if item: + yield item + return + stream = t.cast(t.IO[bytes], stream) + if not isinstance(stream, LimitedStream) and limit is not None: + stream = t.cast(t.IO[bytes], LimitedStream(stream, limit)) + _read = stream.read + while True: + item = _read(buffer_size) + if not item: + break + yield item + + +def make_line_iter( + stream: t.Union[t.Iterable[bytes], t.IO[bytes]], + limit: t.Optional[int] = None, + buffer_size: int = 10 * 1024, + cap_at_buffer: bool = False, +) -> t.Iterator[bytes]: + """Safely iterates line-based over an input stream. If the input stream + is not a :class:`LimitedStream` the `limit` parameter is mandatory. + + This uses the stream's :meth:`~file.read` method internally as opposite + to the :meth:`~file.readline` method that is unsafe and can only be used + in violation of the WSGI specification. The same problem applies to the + `__iter__` function of the input stream which calls :meth:`~file.readline` + without arguments. + + If you need line-by-line processing it's strongly recommended to iterate + over the input stream using this helper function. + + .. versionchanged:: 0.8 + This function now ensures that the limit was reached. + + .. versionadded:: 0.9 + added support for iterators as input stream. + + .. versionadded:: 0.11.10 + added support for the `cap_at_buffer` parameter. + + :param stream: the stream or iterate to iterate over. + :param limit: the limit in bytes for the stream. (Usually + content length. Not necessary if the `stream` + is a :class:`LimitedStream`. + :param buffer_size: The optional buffer size. + :param cap_at_buffer: if this is set chunks are split if they are longer + than the buffer size. Internally this is implemented + that the buffer size might be exhausted by a factor + of two however. + """ + _iter = _make_chunk_iter(stream, limit, buffer_size) + + first_item = next(_iter, "") + if not first_item: + return + + s = _make_encode_wrapper(first_item) + empty = t.cast(bytes, s("")) + cr = t.cast(bytes, s("\r")) + lf = t.cast(bytes, s("\n")) + crlf = t.cast(bytes, s("\r\n")) + + _iter = t.cast(t.Iterator[bytes], chain((first_item,), _iter)) + + def _iter_basic_lines() -> t.Iterator[bytes]: + _join = empty.join + buffer: t.List[bytes] = [] + while True: + new_data = next(_iter, "") + if not new_data: + break + new_buf: t.List[bytes] = [] + buf_size = 0 + for item in t.cast( + t.Iterator[bytes], chain(buffer, new_data.splitlines(True)) + ): + new_buf.append(item) + buf_size += len(item) + if item and item[-1:] in crlf: + yield _join(new_buf) + new_buf = [] + elif cap_at_buffer and buf_size >= buffer_size: + rv = _join(new_buf) + while len(rv) >= buffer_size: + yield rv[:buffer_size] + rv = rv[buffer_size:] + new_buf = [rv] + buffer = new_buf + if buffer: + yield _join(buffer) + + # This hackery is necessary to merge 'foo\r' and '\n' into one item + # of 'foo\r\n' if we were unlucky and we hit a chunk boundary. + previous = empty + for item in _iter_basic_lines(): + if item == lf and previous[-1:] == cr: + previous += item + item = empty + if previous: + yield previous + previous = item + if previous: + yield previous + + +def make_chunk_iter( + stream: t.Union[t.Iterable[bytes], t.IO[bytes]], + separator: bytes, + limit: t.Optional[int] = None, + buffer_size: int = 10 * 1024, + cap_at_buffer: bool = False, +) -> t.Iterator[bytes]: + """Works like :func:`make_line_iter` but accepts a separator + which divides chunks. If you want newline based processing + you should use :func:`make_line_iter` instead as it + supports arbitrary newline markers. + + .. versionadded:: 0.8 + + .. versionadded:: 0.9 + added support for iterators as input stream. + + .. versionadded:: 0.11.10 + added support for the `cap_at_buffer` parameter. + + :param stream: the stream or iterate to iterate over. + :param separator: the separator that divides chunks. + :param limit: the limit in bytes for the stream. (Usually + content length. Not necessary if the `stream` + is otherwise already limited). + :param buffer_size: The optional buffer size. + :param cap_at_buffer: if this is set chunks are split if they are longer + than the buffer size. Internally this is implemented + that the buffer size might be exhausted by a factor + of two however. + """ + _iter = _make_chunk_iter(stream, limit, buffer_size) + + first_item = next(_iter, b"") + if not first_item: + return + + _iter = t.cast(t.Iterator[bytes], chain((first_item,), _iter)) + if isinstance(first_item, str): + separator = _to_str(separator) + _split = re.compile(f"({re.escape(separator)})").split + _join = "".join + else: + separator = _to_bytes(separator) + _split = re.compile(b"(" + re.escape(separator) + b")").split + _join = b"".join + + buffer: t.List[bytes] = [] + while True: + new_data = next(_iter, b"") + if not new_data: + break + chunks = _split(new_data) + new_buf: t.List[bytes] = [] + buf_size = 0 + for item in chain(buffer, chunks): + if item == separator: + yield _join(new_buf) + new_buf = [] + buf_size = 0 + else: + buf_size += len(item) + new_buf.append(item) + + if cap_at_buffer and buf_size >= buffer_size: + rv = _join(new_buf) + while len(rv) >= buffer_size: + yield rv[:buffer_size] + rv = rv[buffer_size:] + new_buf = [rv] + buf_size = len(rv) + + buffer = new_buf + if buffer: + yield _join(buffer) + + +class LimitedStream(io.IOBase): + """Wraps a stream so that it doesn't read more than n bytes. If the + stream is exhausted and the caller tries to get more bytes from it + :func:`on_exhausted` is called which by default returns an empty + string. The return value of that function is forwarded + to the reader function. So if it returns an empty string + :meth:`read` will return an empty string as well. + + The limit however must never be higher than what the stream can + output. Otherwise :meth:`readlines` will try to read past the + limit. + + .. admonition:: Note on WSGI compliance + + calls to :meth:`readline` and :meth:`readlines` are not + WSGI compliant because it passes a size argument to the + readline methods. Unfortunately the WSGI PEP is not safely + implementable without a size argument to :meth:`readline` + because there is no EOF marker in the stream. As a result + of that the use of :meth:`readline` is discouraged. + + For the same reason iterating over the :class:`LimitedStream` + is not portable. It internally calls :meth:`readline`. + + We strongly suggest using :meth:`read` only or using the + :func:`make_line_iter` which safely iterates line-based + over a WSGI input stream. + + :param stream: the stream to wrap. + :param limit: the limit for the stream, must not be longer than + what the string can provide if the stream does not + end with `EOF` (like `wsgi.input`) + """ + + def __init__(self, stream: t.IO[bytes], limit: int) -> None: + self._read = stream.read + self._readline = stream.readline + self._pos = 0 + self.limit = limit + + def __iter__(self) -> "LimitedStream": + return self + + @property + def is_exhausted(self) -> bool: + """If the stream is exhausted this attribute is `True`.""" + return self._pos >= self.limit + + def on_exhausted(self) -> bytes: + """This is called when the stream tries to read past the limit. + The return value of this function is returned from the reading + function. + """ + # Read null bytes from the stream so that we get the + # correct end of stream marker. + return self._read(0) + + def on_disconnect(self) -> bytes: + """What should happen if a disconnect is detected? The return + value of this function is returned from read functions in case + the client went away. By default a + :exc:`~werkzeug.exceptions.ClientDisconnected` exception is raised. + """ + from .exceptions import ClientDisconnected + + raise ClientDisconnected() + + def exhaust(self, chunk_size: int = 1024 * 64) -> None: + """Exhaust the stream. This consumes all the data left until the + limit is reached. + + :param chunk_size: the size for a chunk. It will read the chunk + until the stream is exhausted and throw away + the results. + """ + to_read = self.limit - self._pos + chunk = chunk_size + while to_read > 0: + chunk = min(to_read, chunk) + self.read(chunk) + to_read -= chunk + + def read(self, size: t.Optional[int] = None) -> bytes: + """Read `size` bytes or if size is not provided everything is read. + + :param size: the number of bytes read. + """ + if self._pos >= self.limit: + return self.on_exhausted() + if size is None or size == -1: # -1 is for consistence with file + size = self.limit + to_read = min(self.limit - self._pos, size) + try: + read = self._read(to_read) + except (OSError, ValueError): + return self.on_disconnect() + if to_read and len(read) != to_read: + return self.on_disconnect() + self._pos += len(read) + return read + + def readline(self, size: t.Optional[int] = None) -> bytes: + """Reads one line from the stream.""" + if self._pos >= self.limit: + return self.on_exhausted() + if size is None: + size = self.limit - self._pos + else: + size = min(size, self.limit - self._pos) + try: + line = self._readline(size) + except (ValueError, OSError): + return self.on_disconnect() + if size and not line: + return self.on_disconnect() + self._pos += len(line) + return line + + def readlines(self, size: t.Optional[int] = None) -> t.List[bytes]: + """Reads a file into a list of strings. It calls :meth:`readline` + until the file is read to the end. It does support the optional + `size` argument if the underlying stream supports it for + `readline`. + """ + last_pos = self._pos + result = [] + if size is not None: + end = min(self.limit, last_pos + size) + else: + end = self.limit + while True: + if size is not None: + size -= last_pos - self._pos + if self._pos >= end: + break + result.append(self.readline(size)) + if size is not None: + last_pos = self._pos + return result + + def tell(self) -> int: + """Returns the position of the stream. + + .. versionadded:: 0.9 + """ + return self._pos + + def __next__(self) -> bytes: + line = self.readline() + if not line: + raise StopIteration() + return line + + def readable(self) -> bool: + return True diff --git a/.vtodo/Scripts/flask.exe b/.vtodo/Scripts/flask.exe new file mode 100644 index 0000000000000000000000000000000000000000..aea6745dc2d00342bae85fed9f17a26885405566 GIT binary patch literal 106358 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 literal 0 HcmV?d00001 diff --git a/__pycache__/app.cpython-310.pyc b/__pycache__/app.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..833a6175b780f8930673ee51c79775ec70039c04 GIT binary patch literal 1824 zcmZ{lPj4JG6u@oI-<{oT+EQqXL~##=4l1ccZ!}GTbPI*DLAxoaqoCF7*qcl#(DdS?Kc)3%git9k|-90Xq_9kGkr9;74 zE_kMNuqTSqq!O8Nde$%~4)j&%@&gbGonVIN_z<+0q(&pBMi2_=&)O_i={S{qiH&1d z2XeQ_6~C{8teQ??fy5b9`$=0=`=d#ksY)C`w62+agTw;3ejBfTx$#_bAvd_lvvfn6 zO;xGdP!+2_Kj5iU8|lu@($0at47(h$@#xfdUQJsjLLWqgL+oO)2s7R-*DydW=pKi{gVQ+QNTceFL5 zA#-$yb96|QQ)5P$bBvh#nmk6oMjPbNIdqjGx|-`?ScVlC+@z-`4uz$(W6D zMjwJJc&rT`_XhNv7iW$iWG1<1&FBRB5V{OO)WGTWL`U<5j!HN_$yR0VXCju_rq4NIZjx6oycZw>93p7@}uaGxPEa2%i{8$UE1$ zPP#ZG9V|YA;aPYO@PK=AFDBagwzsa`pVo%!5OPp*wN)|sA2>`D7LF588aNbwNf_QH z;t^;LBR2pMt2v;N8q1r?WdH?W;298(@DUywqypyGWP<19B%C7_fHmAM7!7M&I}{fFipPqrq8W0=ye)+C9=+TDHhxE>)_<<%ln|<-LKkNC^uD+9|Sk{^7a7F{VF% z{p@*f2w&Z$kK)<&(*@4KZr6A)Yd5J_GK7ftA{T7Xy&dPagH5v%NR#y(UA=z39Ozoi_=Ar{3fbT;3N4N2%khG z!Vz|Xs1Ejtp~}bZJD(UC+R-ktgxv=7_&wc%%Pdq?71Ba2%#SCD_W5`WwGp@$4h%H) zd0Uv;W23dZzPL|QcjR zz%J>4)pi<{y3DGPy0a8VdMp(CW8w9N1NC;b`m47aP*>0%IF%Y{IaaL+qkecnwybv5 zfpXinWwrg^A(xrl7t|Nvke9OHMA5B=O)A^UZgtTYZA#N@v!02Xskhn&+k(bH2;RHRHkJgEoKM89x-` zLLRW801BW03ZMWApa2S>01BW03ZMWA{O1BQnx0YPPdKJ$RCyVHexOJD|LOE+#rS4? zGCmlu3^RR^{wg0}Lje>(0Te(16hHwKKmim$0Te)iI}(8Dq@ot*^s%?ug + + + + + + + + + + {% block title %} {% endblock %} + + + +
    + {% block content %} {% endblock %} +
    + + + + + + diff --git a/templates/create.html b/templates/create.html new file mode 100644 index 0000000..6370e2c --- /dev/null +++ b/templates/create.html @@ -0,0 +1,34 @@ +{% extends 'base.html' %} + +{% block content %} +

    {% block title %} Create a New Item {% endblock %}

    + + +
    + + +
    + +
    + + +
    +
    + +
    + +{% endblock %} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..c97680d --- /dev/null +++ b/templates/index.html @@ -0,0 +1,17 @@ +{% extends 'base.html' %} + +{% block content %} +

    {% block title %} Welcome to FlaskTodo {% endblock %}

    + {% for list, items in lists.items() %} +
    +
    +

    {{ list }}

    +
    +
      + {% for item in items %} +
    • {{ item['content'] }}
    • + {% endfor %} +
    +
    + {% endfor %} +{% endblock %} \ No newline at end of file
  • e( z=a5ygkdCQCtov;ao5s5RnYN<`E+TZBARbOQNm1cR7RS6I8-X-)^$xS``by00IeeLP zOpUT7I0PqZgfvGc@gb!98e76Uep!-U!7;aFejB@)<&4YxH=Sa8sH=@zoFpf_-3$r~ zbNF^dtEq&=8)I{}D4X?8WXq zO%Ap|rdJ%3un_5^wSJB>F#Zv!WZ>8XAC52}P&oD&o+Z#i9f!w;iKBFnb7BNWU@p*W z<9+LfdXx^l_#kT*Z?H)J=i@qi(tY&=d(wA0zG_a||2?o%jzZ76AA>xCGzY^e!5{M5W{(3NsxCx&v zq#ek#$Agim#Fbza{u$Ud>*AnhZbWz`6O8Lxp*I>^`%HXLQv4KN8in=`C!5NJfdl); zy})z>On4~qtDYkRlUUkh(ngGm@DRe8$lh*p7)T3!Me}g-87&xLBF9MZSa8gOy9N0< ze$u%bTM(q24%@|P?IJzbe&ShtN*CX1(^ea>Z{QW-lczsn5U{B(q{P@?QYt^vHYcZW zn9c=^wHhaKe3r2d1|k^=!U*bq51)Aqo?-A082l20Ut=Ib&A&j5A5Dlq6&(&gi{ski zo7ZY;r;>lfq#r>5IZY@7zGn|_kwtuE3(Tfa#Lyzg)w`MuA>Ho~&zafEjY1-`FjI$t z>2SvW3}!6828++-VCIhEiv9Ze*Wzr(PP`)KG_;p+*S75vKJbKm-%>1BD^H))su=?h z!aQIDJBMZ|I5iZijx;fx9p!?2O$`bgE*<(K5S%eIEs&u{e|=Tt1AJtr-te`nPKghO z=3ag2xysB7&&_-kUt=hb>6$9omCh6EkuNj@eEbA^L_Im|TGp-dXqVhck|MIJjLUOu z0>L7m?vT$b$N`c1n58fA#Sby~6$adYC>!!*V~Xq<5XWzE5>S80w=&`<7(2;8AoMz8 zGWIg;GU6QU4(IiKyp@1tG_;?FQVQP_dj{~#_}`1b#&O@+bD{r<`@D|lF>X3GoXvcC uG(T#O?iwwP?isx_Ix{*x`tfmlbPulgjqb+t04bh2@_7FFyrusYv;P+p_O@dH literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/click/__pycache__/utils.cpython-310.pyc b/.vtodo/Lib/site-packages/click/__pycache__/utils.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7a7d137886a3e6f6a4a3f8ff01c837f37dac1154 GIT binary patch literal 17626 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 f$K6MA%t^cISN6BqSLtUgW4J%ZXCIZ) str: + return sys.getfilesystemencoding() or sys.getdefaultencoding() + + +def _make_text_stream( + stream: t.BinaryIO, + encoding: t.Optional[str], + errors: t.Optional[str], + force_readable: bool = False, + force_writable: bool = False, +) -> t.TextIO: + if encoding is None: + encoding = get_best_encoding(stream) + if errors is None: + errors = "replace" + return _NonClosingTextIOWrapper( + stream, + encoding, + errors, + line_buffering=True, + force_readable=force_readable, + force_writable=force_writable, + ) + + +def is_ascii_encoding(encoding: str) -> bool: + """Checks if a given encoding is ascii.""" + try: + return codecs.lookup(encoding).name == "ascii" + except LookupError: + return False + + +def get_best_encoding(stream: t.IO) -> str: + """Returns the default stream encoding if not found.""" + rv = getattr(stream, "encoding", None) or sys.getdefaultencoding() + if is_ascii_encoding(rv): + return "utf-8" + return rv + + +class _NonClosingTextIOWrapper(io.TextIOWrapper): + def __init__( + self, + stream: t.BinaryIO, + encoding: t.Optional[str], + errors: t.Optional[str], + force_readable: bool = False, + force_writable: bool = False, + **extra: t.Any, + ) -> None: + self._stream = stream = t.cast( + t.BinaryIO, _FixupStream(stream, force_readable, force_writable) + ) + super().__init__(stream, encoding, errors, **extra) + + def __del__(self) -> None: + try: + self.detach() + except Exception: + pass + + def isatty(self) -> bool: + # https://bitbucket.org/pypy/pypy/issue/1803 + return self._stream.isatty() + + +class _FixupStream: + """The new io interface needs more from streams than streams + traditionally implement. As such, this fix-up code is necessary in + some circumstances. + + The forcing of readable and writable flags are there because some tools + put badly patched objects on sys (one such offender are certain version + of jupyter notebook). + """ + + def __init__( + self, + stream: t.BinaryIO, + force_readable: bool = False, + force_writable: bool = False, + ): + self._stream = stream + self._force_readable = force_readable + self._force_writable = force_writable + + def __getattr__(self, name: str) -> t.Any: + return getattr(self._stream, name) + + def read1(self, size: int) -> bytes: + f = getattr(self._stream, "read1", None) + + if f is not None: + return t.cast(bytes, f(size)) + + return self._stream.read(size) + + def readable(self) -> bool: + if self._force_readable: + return True + x = getattr(self._stream, "readable", None) + if x is not None: + return t.cast(bool, x()) + try: + self._stream.read(0) + except Exception: + return False + return True + + def writable(self) -> bool: + if self._force_writable: + return True + x = getattr(self._stream, "writable", None) + if x is not None: + return t.cast(bool, x()) + try: + self._stream.write("") # type: ignore + except Exception: + try: + self._stream.write(b"") + except Exception: + return False + return True + + def seekable(self) -> bool: + x = getattr(self._stream, "seekable", None) + if x is not None: + return t.cast(bool, x()) + try: + self._stream.seek(self._stream.tell()) + except Exception: + return False + return True + + +def _is_binary_reader(stream: t.IO, default: bool = False) -> bool: + try: + return isinstance(stream.read(0), bytes) + except Exception: + return default + # This happens in some cases where the stream was already + # closed. In this case, we assume the default. + + +def _is_binary_writer(stream: t.IO, default: bool = False) -> bool: + try: + stream.write(b"") + except Exception: + try: + stream.write("") + return False + except Exception: + pass + return default + return True + + +def _find_binary_reader(stream: t.IO) -> t.Optional[t.BinaryIO]: + # We need to figure out if the given stream is already binary. + # This can happen because the official docs recommend detaching + # the streams to get binary streams. Some code might do this, so + # we need to deal with this case explicitly. + if _is_binary_reader(stream, False): + return t.cast(t.BinaryIO, stream) + + buf = getattr(stream, "buffer", None) + + # Same situation here; this time we assume that the buffer is + # actually binary in case it's closed. + if buf is not None and _is_binary_reader(buf, True): + return t.cast(t.BinaryIO, buf) + + return None + + +def _find_binary_writer(stream: t.IO) -> t.Optional[t.BinaryIO]: + # We need to figure out if the given stream is already binary. + # This can happen because the official docs recommend detaching + # the streams to get binary streams. Some code might do this, so + # we need to deal with this case explicitly. + if _is_binary_writer(stream, False): + return t.cast(t.BinaryIO, stream) + + buf = getattr(stream, "buffer", None) + + # Same situation here; this time we assume that the buffer is + # actually binary in case it's closed. + if buf is not None and _is_binary_writer(buf, True): + return t.cast(t.BinaryIO, buf) + + return None + + +def _stream_is_misconfigured(stream: t.TextIO) -> bool: + """A stream is misconfigured if its encoding is ASCII.""" + # If the stream does not have an encoding set, we assume it's set + # to ASCII. This appears to happen in certain unittest + # environments. It's not quite clear what the correct behavior is + # but this at least will force Click to recover somehow. + return is_ascii_encoding(getattr(stream, "encoding", None) or "ascii") + + +def _is_compat_stream_attr(stream: t.TextIO, attr: str, value: t.Optional[str]) -> bool: + """A stream attribute is compatible if it is equal to the + desired value or the desired value is unset and the attribute + has a value. + """ + stream_value = getattr(stream, attr, None) + return stream_value == value or (value is None and stream_value is not None) + + +def _is_compatible_text_stream( + stream: t.TextIO, encoding: t.Optional[str], errors: t.Optional[str] +) -> bool: + """Check if a stream's encoding and errors attributes are + compatible with the desired values. + """ + return _is_compat_stream_attr( + stream, "encoding", encoding + ) and _is_compat_stream_attr(stream, "errors", errors) + + +def _force_correct_text_stream( + text_stream: t.IO, + encoding: t.Optional[str], + errors: t.Optional[str], + is_binary: t.Callable[[t.IO, bool], bool], + find_binary: t.Callable[[t.IO], t.Optional[t.BinaryIO]], + force_readable: bool = False, + force_writable: bool = False, +) -> t.TextIO: + if is_binary(text_stream, False): + binary_reader = t.cast(t.BinaryIO, text_stream) + else: + text_stream = t.cast(t.TextIO, text_stream) + # If the stream looks compatible, and won't default to a + # misconfigured ascii encoding, return it as-is. + if _is_compatible_text_stream(text_stream, encoding, errors) and not ( + encoding is None and _stream_is_misconfigured(text_stream) + ): + return text_stream + + # Otherwise, get the underlying binary reader. + possible_binary_reader = find_binary(text_stream) + + # If that's not possible, silently use the original reader + # and get mojibake instead of exceptions. + if possible_binary_reader is None: + return text_stream + + binary_reader = possible_binary_reader + + # Default errors to replace instead of strict in order to get + # something that works. + if errors is None: + errors = "replace" + + # Wrap the binary stream in a text stream with the correct + # encoding parameters. + return _make_text_stream( + binary_reader, + encoding, + errors, + force_readable=force_readable, + force_writable=force_writable, + ) + + +def _force_correct_text_reader( + text_reader: t.IO, + encoding: t.Optional[str], + errors: t.Optional[str], + force_readable: bool = False, +) -> t.TextIO: + return _force_correct_text_stream( + text_reader, + encoding, + errors, + _is_binary_reader, + _find_binary_reader, + force_readable=force_readable, + ) + + +def _force_correct_text_writer( + text_writer: t.IO, + encoding: t.Optional[str], + errors: t.Optional[str], + force_writable: bool = False, +) -> t.TextIO: + return _force_correct_text_stream( + text_writer, + encoding, + errors, + _is_binary_writer, + _find_binary_writer, + force_writable=force_writable, + ) + + +def get_binary_stdin() -> t.BinaryIO: + reader = _find_binary_reader(sys.stdin) + if reader is None: + raise RuntimeError("Was not able to determine binary stream for sys.stdin.") + return reader + + +def get_binary_stdout() -> t.BinaryIO: + writer = _find_binary_writer(sys.stdout) + if writer is None: + raise RuntimeError("Was not able to determine binary stream for sys.stdout.") + return writer + + +def get_binary_stderr() -> t.BinaryIO: + writer = _find_binary_writer(sys.stderr) + if writer is None: + raise RuntimeError("Was not able to determine binary stream for sys.stderr.") + return writer + + +def get_text_stdin( + encoding: t.Optional[str] = None, errors: t.Optional[str] = None +) -> t.TextIO: + rv = _get_windows_console_stream(sys.stdin, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_reader(sys.stdin, encoding, errors, force_readable=True) + + +def get_text_stdout( + encoding: t.Optional[str] = None, errors: t.Optional[str] = None +) -> t.TextIO: + rv = _get_windows_console_stream(sys.stdout, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_writer(sys.stdout, encoding, errors, force_writable=True) + + +def get_text_stderr( + encoding: t.Optional[str] = None, errors: t.Optional[str] = None +) -> t.TextIO: + rv = _get_windows_console_stream(sys.stderr, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_writer(sys.stderr, encoding, errors, force_writable=True) + + +def _wrap_io_open( + file: t.Union[str, os.PathLike, int], + mode: str, + encoding: t.Optional[str], + errors: t.Optional[str], +) -> t.IO: + """Handles not passing ``encoding`` and ``errors`` in binary mode.""" + if "b" in mode: + return open(file, mode) + + return open(file, mode, encoding=encoding, errors=errors) + + +def open_stream( + filename: str, + mode: str = "r", + encoding: t.Optional[str] = None, + errors: t.Optional[str] = "strict", + atomic: bool = False, +) -> t.Tuple[t.IO, bool]: + binary = "b" in mode + + # Standard streams first. These are simple because they ignore the + # atomic flag. Use fsdecode to handle Path("-"). + if os.fsdecode(filename) == "-": + if any(m in mode for m in ["w", "a", "x"]): + if binary: + return get_binary_stdout(), False + return get_text_stdout(encoding=encoding, errors=errors), False + if binary: + return get_binary_stdin(), False + return get_text_stdin(encoding=encoding, errors=errors), False + + # Non-atomic writes directly go out through the regular open functions. + if not atomic: + return _wrap_io_open(filename, mode, encoding, errors), True + + # Some usability stuff for atomic writes + if "a" in mode: + raise ValueError( + "Appending to an existing file is not supported, because that" + " would involve an expensive `copy`-operation to a temporary" + " file. Open the file in normal `w`-mode and copy explicitly" + " if that's what you're after." + ) + if "x" in mode: + raise ValueError("Use the `overwrite`-parameter instead.") + if "w" not in mode: + raise ValueError("Atomic writes only make sense with `w`-mode.") + + # Atomic writes are more complicated. They work by opening a file + # as a proxy in the same folder and then using the fdopen + # functionality to wrap it in a Python file. Then we wrap it in an + # atomic file that moves the file over on close. + import errno + import random + + try: + perm: t.Optional[int] = os.stat(filename).st_mode + except OSError: + perm = None + + flags = os.O_RDWR | os.O_CREAT | os.O_EXCL + + if binary: + flags |= getattr(os, "O_BINARY", 0) + + while True: + tmp_filename = os.path.join( + os.path.dirname(filename), + f".__atomic-write{random.randrange(1 << 32):08x}", + ) + try: + fd = os.open(tmp_filename, flags, 0o666 if perm is None else perm) + break + except OSError as e: + if e.errno == errno.EEXIST or ( + os.name == "nt" + and e.errno == errno.EACCES + and os.path.isdir(e.filename) + and os.access(e.filename, os.W_OK) + ): + continue + raise + + if perm is not None: + os.chmod(tmp_filename, perm) # in case perm includes bits in umask + + f = _wrap_io_open(fd, mode, encoding, errors) + af = _AtomicFile(f, tmp_filename, os.path.realpath(filename)) + return t.cast(t.IO, af), True + + +class _AtomicFile: + def __init__(self, f: t.IO, tmp_filename: str, real_filename: str) -> None: + self._f = f + self._tmp_filename = tmp_filename + self._real_filename = real_filename + self.closed = False + + @property + def name(self) -> str: + return self._real_filename + + def close(self, delete: bool = False) -> None: + if self.closed: + return + self._f.close() + os.replace(self._tmp_filename, self._real_filename) + self.closed = True + + def __getattr__(self, name: str) -> t.Any: + return getattr(self._f, name) + + def __enter__(self) -> "_AtomicFile": + return self + + def __exit__(self, exc_type, exc_value, tb): # type: ignore + self.close(delete=exc_type is not None) + + def __repr__(self) -> str: + return repr(self._f) + + +def strip_ansi(value: str) -> str: + return _ansi_re.sub("", value) + + +def _is_jupyter_kernel_output(stream: t.IO) -> bool: + while isinstance(stream, (_FixupStream, _NonClosingTextIOWrapper)): + stream = stream._stream + + return stream.__class__.__module__.startswith("ipykernel.") + + +def should_strip_ansi( + stream: t.Optional[t.IO] = None, color: t.Optional[bool] = None +) -> bool: + if color is None: + if stream is None: + stream = sys.stdin + return not isatty(stream) and not _is_jupyter_kernel_output(stream) + return not color + + +# On Windows, wrap the output streams with colorama to support ANSI +# color codes. +# NOTE: double check is needed so mypy does not analyze this on Linux +if sys.platform.startswith("win") and WIN: + from ._winconsole import _get_windows_console_stream + + def _get_argv_encoding() -> str: + import locale + + return locale.getpreferredencoding() + + _ansi_stream_wrappers: t.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary() + + def auto_wrap_for_ansi( + stream: t.TextIO, color: t.Optional[bool] = None + ) -> t.TextIO: + """Support ANSI color and style codes on Windows by wrapping a + stream with colorama. + """ + try: + cached = _ansi_stream_wrappers.get(stream) + except Exception: + cached = None + + if cached is not None: + return cached + + import colorama + + strip = should_strip_ansi(stream, color) + ansi_wrapper = colorama.AnsiToWin32(stream, strip=strip) + rv = t.cast(t.TextIO, ansi_wrapper.stream) + _write = rv.write + + def _safe_write(s): + try: + return _write(s) + except BaseException: + ansi_wrapper.reset_all() + raise + + rv.write = _safe_write + + try: + _ansi_stream_wrappers[stream] = rv + except Exception: + pass + + return rv + +else: + + def _get_argv_encoding() -> str: + return getattr(sys.stdin, "encoding", None) or get_filesystem_encoding() + + def _get_windows_console_stream( + f: t.TextIO, encoding: t.Optional[str], errors: t.Optional[str] + ) -> t.Optional[t.TextIO]: + return None + + +def term_len(x: str) -> int: + return len(strip_ansi(x)) + + +def isatty(stream: t.IO) -> bool: + try: + return stream.isatty() + except Exception: + return False + + +def _make_cached_stream_func( + src_func: t.Callable[[], t.TextIO], wrapper_func: t.Callable[[], t.TextIO] +) -> t.Callable[[], t.TextIO]: + cache: t.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary() + + def func() -> t.TextIO: + stream = src_func() + try: + rv = cache.get(stream) + except Exception: + rv = None + if rv is not None: + return rv + rv = wrapper_func() + try: + cache[stream] = rv + except Exception: + pass + return rv + + return func + + +_default_text_stdin = _make_cached_stream_func(lambda: sys.stdin, get_text_stdin) +_default_text_stdout = _make_cached_stream_func(lambda: sys.stdout, get_text_stdout) +_default_text_stderr = _make_cached_stream_func(lambda: sys.stderr, get_text_stderr) + + +binary_streams: t.Mapping[str, t.Callable[[], t.BinaryIO]] = { + "stdin": get_binary_stdin, + "stdout": get_binary_stdout, + "stderr": get_binary_stderr, +} + +text_streams: t.Mapping[ + str, t.Callable[[t.Optional[str], t.Optional[str]], t.TextIO] +] = { + "stdin": get_text_stdin, + "stdout": get_text_stdout, + "stderr": get_text_stderr, +} diff --git a/.vtodo/Lib/site-packages/click/_termui_impl.py b/.vtodo/Lib/site-packages/click/_termui_impl.py new file mode 100644 index 0000000..4b979bc --- /dev/null +++ b/.vtodo/Lib/site-packages/click/_termui_impl.py @@ -0,0 +1,717 @@ +""" +This module contains implementations for the termui module. To keep the +import time of Click down, some infrequently used functionality is +placed in this module and only imported as needed. +""" +import contextlib +import math +import os +import sys +import time +import typing as t +from gettext import gettext as _ + +from ._compat import _default_text_stdout +from ._compat import CYGWIN +from ._compat import get_best_encoding +from ._compat import isatty +from ._compat import open_stream +from ._compat import strip_ansi +from ._compat import term_len +from ._compat import WIN +from .exceptions import ClickException +from .utils import echo + +V = t.TypeVar("V") + +if os.name == "nt": + BEFORE_BAR = "\r" + AFTER_BAR = "\n" +else: + BEFORE_BAR = "\r\033[?25l" + AFTER_BAR = "\033[?25h\n" + + +class ProgressBar(t.Generic[V]): + def __init__( + self, + iterable: t.Optional[t.Iterable[V]], + length: t.Optional[int] = None, + fill_char: str = "#", + empty_char: str = " ", + bar_template: str = "%(bar)s", + info_sep: str = " ", + show_eta: bool = True, + show_percent: t.Optional[bool] = None, + show_pos: bool = False, + item_show_func: t.Optional[t.Callable[[t.Optional[V]], t.Optional[str]]] = None, + label: t.Optional[str] = None, + file: t.Optional[t.TextIO] = None, + color: t.Optional[bool] = None, + update_min_steps: int = 1, + width: int = 30, + ) -> None: + self.fill_char = fill_char + self.empty_char = empty_char + self.bar_template = bar_template + self.info_sep = info_sep + self.show_eta = show_eta + self.show_percent = show_percent + self.show_pos = show_pos + self.item_show_func = item_show_func + self.label = label or "" + if file is None: + file = _default_text_stdout() + self.file = file + self.color = color + self.update_min_steps = update_min_steps + self._completed_intervals = 0 + self.width = width + self.autowidth = width == 0 + + if length is None: + from operator import length_hint + + length = length_hint(iterable, -1) + + if length == -1: + length = None + if iterable is None: + if length is None: + raise TypeError("iterable or length is required") + iterable = t.cast(t.Iterable[V], range(length)) + self.iter = iter(iterable) + self.length = length + self.pos = 0 + self.avg: t.List[float] = [] + self.start = self.last_eta = time.time() + self.eta_known = False + self.finished = False + self.max_width: t.Optional[int] = None + self.entered = False + self.current_item: t.Optional[V] = None + self.is_hidden = not isatty(self.file) + self._last_line: t.Optional[str] = None + + def __enter__(self) -> "ProgressBar": + self.entered = True + self.render_progress() + return self + + def __exit__(self, exc_type, exc_value, tb): # type: ignore + self.render_finish() + + def __iter__(self) -> t.Iterator[V]: + if not self.entered: + raise RuntimeError("You need to use progress bars in a with block.") + self.render_progress() + return self.generator() + + def __next__(self) -> V: + # Iteration is defined in terms of a generator function, + # returned by iter(self); use that to define next(). This works + # because `self.iter` is an iterable consumed by that generator, + # so it is re-entry safe. Calling `next(self.generator())` + # twice works and does "what you want". + return next(iter(self)) + + def render_finish(self) -> None: + if self.is_hidden: + return + self.file.write(AFTER_BAR) + self.file.flush() + + @property + def pct(self) -> float: + if self.finished: + return 1.0 + return min(self.pos / (float(self.length or 1) or 1), 1.0) + + @property + def time_per_iteration(self) -> float: + if not self.avg: + return 0.0 + return sum(self.avg) / float(len(self.avg)) + + @property + def eta(self) -> float: + if self.length is not None and not self.finished: + return self.time_per_iteration * (self.length - self.pos) + return 0.0 + + def format_eta(self) -> str: + if self.eta_known: + t = int(self.eta) + seconds = t % 60 + t //= 60 + minutes = t % 60 + t //= 60 + hours = t % 24 + t //= 24 + if t > 0: + return f"{t}d {hours:02}:{minutes:02}:{seconds:02}" + else: + return f"{hours:02}:{minutes:02}:{seconds:02}" + return "" + + def format_pos(self) -> str: + pos = str(self.pos) + if self.length is not None: + pos += f"/{self.length}" + return pos + + def format_pct(self) -> str: + return f"{int(self.pct * 100): 4}%"[1:] + + def format_bar(self) -> str: + if self.length is not None: + bar_length = int(self.pct * self.width) + bar = self.fill_char * bar_length + bar += self.empty_char * (self.width - bar_length) + elif self.finished: + bar = self.fill_char * self.width + else: + chars = list(self.empty_char * (self.width or 1)) + if self.time_per_iteration != 0: + chars[ + int( + (math.cos(self.pos * self.time_per_iteration) / 2.0 + 0.5) + * self.width + ) + ] = self.fill_char + bar = "".join(chars) + return bar + + def format_progress_line(self) -> str: + show_percent = self.show_percent + + info_bits = [] + if self.length is not None and show_percent is None: + show_percent = not self.show_pos + + if self.show_pos: + info_bits.append(self.format_pos()) + if show_percent: + info_bits.append(self.format_pct()) + if self.show_eta and self.eta_known and not self.finished: + info_bits.append(self.format_eta()) + if self.item_show_func is not None: + item_info = self.item_show_func(self.current_item) + if item_info is not None: + info_bits.append(item_info) + + return ( + self.bar_template + % { + "label": self.label, + "bar": self.format_bar(), + "info": self.info_sep.join(info_bits), + } + ).rstrip() + + def render_progress(self) -> None: + import shutil + + if self.is_hidden: + # Only output the label as it changes if the output is not a + # TTY. Use file=stderr if you expect to be piping stdout. + if self._last_line != self.label: + self._last_line = self.label + echo(self.label, file=self.file, color=self.color) + + return + + buf = [] + # Update width in case the terminal has been resized + if self.autowidth: + old_width = self.width + self.width = 0 + clutter_length = term_len(self.format_progress_line()) + new_width = max(0, shutil.get_terminal_size().columns - clutter_length) + if new_width < old_width: + buf.append(BEFORE_BAR) + buf.append(" " * self.max_width) # type: ignore + self.max_width = new_width + self.width = new_width + + clear_width = self.width + if self.max_width is not None: + clear_width = self.max_width + + buf.append(BEFORE_BAR) + line = self.format_progress_line() + line_len = term_len(line) + if self.max_width is None or self.max_width < line_len: + self.max_width = line_len + + buf.append(line) + buf.append(" " * (clear_width - line_len)) + line = "".join(buf) + # Render the line only if it changed. + + if line != self._last_line: + self._last_line = line + echo(line, file=self.file, color=self.color, nl=False) + self.file.flush() + + def make_step(self, n_steps: int) -> None: + self.pos += n_steps + if self.length is not None and self.pos >= self.length: + self.finished = True + + if (time.time() - self.last_eta) < 1.0: + return + + self.last_eta = time.time() + + # self.avg is a rolling list of length <= 7 of steps where steps are + # defined as time elapsed divided by the total progress through + # self.length. + if self.pos: + step = (time.time() - self.start) / self.pos + else: + step = time.time() - self.start + + self.avg = self.avg[-6:] + [step] + + self.eta_known = self.length is not None + + def update(self, n_steps: int, current_item: t.Optional[V] = None) -> None: + """Update the progress bar by advancing a specified number of + steps, and optionally set the ``current_item`` for this new + position. + + :param n_steps: Number of steps to advance. + :param current_item: Optional item to set as ``current_item`` + for the updated position. + + .. versionchanged:: 8.0 + Added the ``current_item`` optional parameter. + + .. versionchanged:: 8.0 + Only render when the number of steps meets the + ``update_min_steps`` threshold. + """ + if current_item is not None: + self.current_item = current_item + + self._completed_intervals += n_steps + + if self._completed_intervals >= self.update_min_steps: + self.make_step(self._completed_intervals) + self.render_progress() + self._completed_intervals = 0 + + def finish(self) -> None: + self.eta_known = False + self.current_item = None + self.finished = True + + def generator(self) -> t.Iterator[V]: + """Return a generator which yields the items added to the bar + during construction, and updates the progress bar *after* the + yielded block returns. + """ + # WARNING: the iterator interface for `ProgressBar` relies on + # this and only works because this is a simple generator which + # doesn't create or manage additional state. If this function + # changes, the impact should be evaluated both against + # `iter(bar)` and `next(bar)`. `next()` in particular may call + # `self.generator()` repeatedly, and this must remain safe in + # order for that interface to work. + if not self.entered: + raise RuntimeError("You need to use progress bars in a with block.") + + if self.is_hidden: + yield from self.iter + else: + for rv in self.iter: + self.current_item = rv + + # This allows show_item_func to be updated before the + # item is processed. Only trigger at the beginning of + # the update interval. + if self._completed_intervals == 0: + self.render_progress() + + yield rv + self.update(1) + + self.finish() + self.render_progress() + + +def pager(generator: t.Iterable[str], color: t.Optional[bool] = None) -> None: + """Decide what method to use for paging through text.""" + stdout = _default_text_stdout() + if not isatty(sys.stdin) or not isatty(stdout): + return _nullpager(stdout, generator, color) + pager_cmd = (os.environ.get("PAGER", None) or "").strip() + if pager_cmd: + if WIN: + return _tempfilepager(generator, pager_cmd, color) + return _pipepager(generator, pager_cmd, color) + if os.environ.get("TERM") in ("dumb", "emacs"): + return _nullpager(stdout, generator, color) + if WIN or sys.platform.startswith("os2"): + return _tempfilepager(generator, "more <", color) + if hasattr(os, "system") and os.system("(less) 2>/dev/null") == 0: + return _pipepager(generator, "less", color) + + import tempfile + + fd, filename = tempfile.mkstemp() + os.close(fd) + try: + if hasattr(os, "system") and os.system(f'more "{filename}"') == 0: + return _pipepager(generator, "more", color) + return _nullpager(stdout, generator, color) + finally: + os.unlink(filename) + + +def _pipepager(generator: t.Iterable[str], cmd: str, color: t.Optional[bool]) -> None: + """Page through text by feeding it to another program. Invoking a + pager through this might support colors. + """ + import subprocess + + env = dict(os.environ) + + # If we're piping to less we might support colors under the + # condition that + cmd_detail = cmd.rsplit("/", 1)[-1].split() + if color is None and cmd_detail[0] == "less": + less_flags = f"{os.environ.get('LESS', '')}{' '.join(cmd_detail[1:])}" + if not less_flags: + env["LESS"] = "-R" + color = True + elif "r" in less_flags or "R" in less_flags: + color = True + + c = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, env=env) + stdin = t.cast(t.BinaryIO, c.stdin) + encoding = get_best_encoding(stdin) + try: + for text in generator: + if not color: + text = strip_ansi(text) + + stdin.write(text.encode(encoding, "replace")) + except (OSError, KeyboardInterrupt): + pass + else: + stdin.close() + + # Less doesn't respect ^C, but catches it for its own UI purposes (aborting + # search or other commands inside less). + # + # That means when the user hits ^C, the parent process (click) terminates, + # but less is still alive, paging the output and messing up the terminal. + # + # If the user wants to make the pager exit on ^C, they should set + # `LESS='-K'`. It's not our decision to make. + while True: + try: + c.wait() + except KeyboardInterrupt: + pass + else: + break + + +def _tempfilepager( + generator: t.Iterable[str], cmd: str, color: t.Optional[bool] +) -> None: + """Page through text by invoking a program on a temporary file.""" + import tempfile + + fd, filename = tempfile.mkstemp() + # TODO: This never terminates if the passed generator never terminates. + text = "".join(generator) + if not color: + text = strip_ansi(text) + encoding = get_best_encoding(sys.stdout) + with open_stream(filename, "wb")[0] as f: + f.write(text.encode(encoding)) + try: + os.system(f'{cmd} "{filename}"') + finally: + os.close(fd) + os.unlink(filename) + + +def _nullpager( + stream: t.TextIO, generator: t.Iterable[str], color: t.Optional[bool] +) -> None: + """Simply print unformatted text. This is the ultimate fallback.""" + for text in generator: + if not color: + text = strip_ansi(text) + stream.write(text) + + +class Editor: + def __init__( + self, + editor: t.Optional[str] = None, + env: t.Optional[t.Mapping[str, str]] = None, + require_save: bool = True, + extension: str = ".txt", + ) -> None: + self.editor = editor + self.env = env + self.require_save = require_save + self.extension = extension + + def get_editor(self) -> str: + if self.editor is not None: + return self.editor + for key in "VISUAL", "EDITOR": + rv = os.environ.get(key) + if rv: + return rv + if WIN: + return "notepad" + for editor in "sensible-editor", "vim", "nano": + if os.system(f"which {editor} >/dev/null 2>&1") == 0: + return editor + return "vi" + + def edit_file(self, filename: str) -> None: + import subprocess + + editor = self.get_editor() + environ: t.Optional[t.Dict[str, str]] = None + + if self.env: + environ = os.environ.copy() + environ.update(self.env) + + try: + c = subprocess.Popen(f'{editor} "{filename}"', env=environ, shell=True) + exit_code = c.wait() + if exit_code != 0: + raise ClickException( + _("{editor}: Editing failed").format(editor=editor) + ) + except OSError as e: + raise ClickException( + _("{editor}: Editing failed: {e}").format(editor=editor, e=e) + ) from e + + def edit(self, text: t.Optional[t.AnyStr]) -> t.Optional[t.AnyStr]: + import tempfile + + if not text: + data = b"" + elif isinstance(text, (bytes, bytearray)): + data = text + else: + if text and not text.endswith("\n"): + text += "\n" + + if WIN: + data = text.replace("\n", "\r\n").encode("utf-8-sig") + else: + data = text.encode("utf-8") + + fd, name = tempfile.mkstemp(prefix="editor-", suffix=self.extension) + f: t.BinaryIO + + try: + with os.fdopen(fd, "wb") as f: + f.write(data) + + # If the filesystem resolution is 1 second, like Mac OS + # 10.12 Extended, or 2 seconds, like FAT32, and the editor + # closes very fast, require_save can fail. Set the modified + # time to be 2 seconds in the past to work around this. + os.utime(name, (os.path.getatime(name), os.path.getmtime(name) - 2)) + # Depending on the resolution, the exact value might not be + # recorded, so get the new recorded value. + timestamp = os.path.getmtime(name) + + self.edit_file(name) + + if self.require_save and os.path.getmtime(name) == timestamp: + return None + + with open(name, "rb") as f: + rv = f.read() + + if isinstance(text, (bytes, bytearray)): + return rv + + return rv.decode("utf-8-sig").replace("\r\n", "\n") # type: ignore + finally: + os.unlink(name) + + +def open_url(url: str, wait: bool = False, locate: bool = False) -> int: + import subprocess + + def _unquote_file(url: str) -> str: + from urllib.parse import unquote + + if url.startswith("file://"): + url = unquote(url[7:]) + + return url + + if sys.platform == "darwin": + args = ["open"] + if wait: + args.append("-W") + if locate: + args.append("-R") + args.append(_unquote_file(url)) + null = open("/dev/null", "w") + try: + return subprocess.Popen(args, stderr=null).wait() + finally: + null.close() + elif WIN: + if locate: + url = _unquote_file(url.replace('"', "")) + args = f'explorer /select,"{url}"' + else: + url = url.replace('"', "") + wait_str = "/WAIT" if wait else "" + args = f'start {wait_str} "" "{url}"' + return os.system(args) + elif CYGWIN: + if locate: + url = os.path.dirname(_unquote_file(url).replace('"', "")) + args = f'cygstart "{url}"' + else: + url = url.replace('"', "") + wait_str = "-w" if wait else "" + args = f'cygstart {wait_str} "{url}"' + return os.system(args) + + try: + if locate: + url = os.path.dirname(_unquote_file(url)) or "." + else: + url = _unquote_file(url) + c = subprocess.Popen(["xdg-open", url]) + if wait: + return c.wait() + return 0 + except OSError: + if url.startswith(("http://", "https://")) and not locate and not wait: + import webbrowser + + webbrowser.open(url) + return 0 + return 1 + + +def _translate_ch_to_exc(ch: str) -> t.Optional[BaseException]: + if ch == "\x03": + raise KeyboardInterrupt() + + if ch == "\x04" and not WIN: # Unix-like, Ctrl+D + raise EOFError() + + if ch == "\x1a" and WIN: # Windows, Ctrl+Z + raise EOFError() + + return None + + +if WIN: + import msvcrt + + @contextlib.contextmanager + def raw_terminal() -> t.Iterator[int]: + yield -1 + + def getchar(echo: bool) -> str: + # The function `getch` will return a bytes object corresponding to + # the pressed character. Since Windows 10 build 1803, it will also + # return \x00 when called a second time after pressing a regular key. + # + # `getwch` does not share this probably-bugged behavior. Moreover, it + # returns a Unicode object by default, which is what we want. + # + # Either of these functions will return \x00 or \xe0 to indicate + # a special key, and you need to call the same function again to get + # the "rest" of the code. The fun part is that \u00e0 is + # "latin small letter a with grave", so if you type that on a French + # keyboard, you _also_ get a \xe0. + # E.g., consider the Up arrow. This returns \xe0 and then \x48. The + # resulting Unicode string reads as "a with grave" + "capital H". + # This is indistinguishable from when the user actually types + # "a with grave" and then "capital H". + # + # When \xe0 is returned, we assume it's part of a special-key sequence + # and call `getwch` again, but that means that when the user types + # the \u00e0 character, `getchar` doesn't return until a second + # character is typed. + # The alternative is returning immediately, but that would mess up + # cross-platform handling of arrow keys and others that start with + # \xe0. Another option is using `getch`, but then we can't reliably + # read non-ASCII characters, because return values of `getch` are + # limited to the current 8-bit codepage. + # + # Anyway, Click doesn't claim to do this Right(tm), and using `getwch` + # is doing the right thing in more situations than with `getch`. + func: t.Callable[[], str] + + if echo: + func = msvcrt.getwche # type: ignore + else: + func = msvcrt.getwch # type: ignore + + rv = func() + + if rv in ("\x00", "\xe0"): + # \x00 and \xe0 are control characters that indicate special key, + # see above. + rv += func() + + _translate_ch_to_exc(rv) + return rv + +else: + import tty + import termios + + @contextlib.contextmanager + def raw_terminal() -> t.Iterator[int]: + f: t.Optional[t.TextIO] + fd: int + + if not isatty(sys.stdin): + f = open("/dev/tty") + fd = f.fileno() + else: + fd = sys.stdin.fileno() + f = None + + try: + old_settings = termios.tcgetattr(fd) + + try: + tty.setraw(fd) + yield fd + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + sys.stdout.flush() + + if f is not None: + f.close() + except termios.error: + pass + + def getchar(echo: bool) -> str: + with raw_terminal() as fd: + ch = os.read(fd, 32).decode(get_best_encoding(sys.stdin), "replace") + + if echo and isatty(sys.stdout): + sys.stdout.write(ch) + + _translate_ch_to_exc(ch) + return ch diff --git a/.vtodo/Lib/site-packages/click/_textwrap.py b/.vtodo/Lib/site-packages/click/_textwrap.py new file mode 100644 index 0000000..b47dcbd --- /dev/null +++ b/.vtodo/Lib/site-packages/click/_textwrap.py @@ -0,0 +1,49 @@ +import textwrap +import typing as t +from contextlib import contextmanager + + +class TextWrapper(textwrap.TextWrapper): + def _handle_long_word( + self, + reversed_chunks: t.List[str], + cur_line: t.List[str], + cur_len: int, + width: int, + ) -> None: + space_left = max(width - cur_len, 1) + + if self.break_long_words: + last = reversed_chunks[-1] + cut = last[:space_left] + res = last[space_left:] + cur_line.append(cut) + reversed_chunks[-1] = res + elif not cur_line: + cur_line.append(reversed_chunks.pop()) + + @contextmanager + def extra_indent(self, indent: str) -> t.Iterator[None]: + old_initial_indent = self.initial_indent + old_subsequent_indent = self.subsequent_indent + self.initial_indent += indent + self.subsequent_indent += indent + + try: + yield + finally: + self.initial_indent = old_initial_indent + self.subsequent_indent = old_subsequent_indent + + def indent_only(self, text: str) -> str: + rv = [] + + for idx, line in enumerate(text.splitlines()): + indent = self.initial_indent + + if idx > 0: + indent = self.subsequent_indent + + rv.append(f"{indent}{line}") + + return "\n".join(rv) diff --git a/.vtodo/Lib/site-packages/click/_winconsole.py b/.vtodo/Lib/site-packages/click/_winconsole.py new file mode 100644 index 0000000..6b20df3 --- /dev/null +++ b/.vtodo/Lib/site-packages/click/_winconsole.py @@ -0,0 +1,279 @@ +# This module is based on the excellent work by Adam Bartoš who +# provided a lot of what went into the implementation here in +# the discussion to issue1602 in the Python bug tracker. +# +# There are some general differences in regards to how this works +# compared to the original patches as we do not need to patch +# the entire interpreter but just work in our little world of +# echo and prompt. +import io +import sys +import time +import typing as t +from ctypes import byref +from ctypes import c_char +from ctypes import c_char_p +from ctypes import c_int +from ctypes import c_ssize_t +from ctypes import c_ulong +from ctypes import c_void_p +from ctypes import POINTER +from ctypes import py_object +from ctypes import Structure +from ctypes.wintypes import DWORD +from ctypes.wintypes import HANDLE +from ctypes.wintypes import LPCWSTR +from ctypes.wintypes import LPWSTR + +from ._compat import _NonClosingTextIOWrapper + +assert sys.platform == "win32" +import msvcrt # noqa: E402 +from ctypes import windll # noqa: E402 +from ctypes import WINFUNCTYPE # noqa: E402 + +c_ssize_p = POINTER(c_ssize_t) + +kernel32 = windll.kernel32 +GetStdHandle = kernel32.GetStdHandle +ReadConsoleW = kernel32.ReadConsoleW +WriteConsoleW = kernel32.WriteConsoleW +GetConsoleMode = kernel32.GetConsoleMode +GetLastError = kernel32.GetLastError +GetCommandLineW = WINFUNCTYPE(LPWSTR)(("GetCommandLineW", windll.kernel32)) +CommandLineToArgvW = WINFUNCTYPE(POINTER(LPWSTR), LPCWSTR, POINTER(c_int))( + ("CommandLineToArgvW", windll.shell32) +) +LocalFree = WINFUNCTYPE(c_void_p, c_void_p)(("LocalFree", windll.kernel32)) + +STDIN_HANDLE = GetStdHandle(-10) +STDOUT_HANDLE = GetStdHandle(-11) +STDERR_HANDLE = GetStdHandle(-12) + +PyBUF_SIMPLE = 0 +PyBUF_WRITABLE = 1 + +ERROR_SUCCESS = 0 +ERROR_NOT_ENOUGH_MEMORY = 8 +ERROR_OPERATION_ABORTED = 995 + +STDIN_FILENO = 0 +STDOUT_FILENO = 1 +STDERR_FILENO = 2 + +EOF = b"\x1a" +MAX_BYTES_WRITTEN = 32767 + +try: + from ctypes import pythonapi +except ImportError: + # On PyPy we cannot get buffers so our ability to operate here is + # severely limited. + get_buffer = None +else: + + class Py_buffer(Structure): + _fields_ = [ + ("buf", c_void_p), + ("obj", py_object), + ("len", c_ssize_t), + ("itemsize", c_ssize_t), + ("readonly", c_int), + ("ndim", c_int), + ("format", c_char_p), + ("shape", c_ssize_p), + ("strides", c_ssize_p), + ("suboffsets", c_ssize_p), + ("internal", c_void_p), + ] + + PyObject_GetBuffer = pythonapi.PyObject_GetBuffer + PyBuffer_Release = pythonapi.PyBuffer_Release + + def get_buffer(obj, writable=False): + buf = Py_buffer() + flags = PyBUF_WRITABLE if writable else PyBUF_SIMPLE + PyObject_GetBuffer(py_object(obj), byref(buf), flags) + + try: + buffer_type = c_char * buf.len + return buffer_type.from_address(buf.buf) + finally: + PyBuffer_Release(byref(buf)) + + +class _WindowsConsoleRawIOBase(io.RawIOBase): + def __init__(self, handle): + self.handle = handle + + def isatty(self): + super().isatty() + return True + + +class _WindowsConsoleReader(_WindowsConsoleRawIOBase): + def readable(self): + return True + + def readinto(self, b): + bytes_to_be_read = len(b) + if not bytes_to_be_read: + return 0 + elif bytes_to_be_read % 2: + raise ValueError( + "cannot read odd number of bytes from UTF-16-LE encoded console" + ) + + buffer = get_buffer(b, writable=True) + code_units_to_be_read = bytes_to_be_read // 2 + code_units_read = c_ulong() + + rv = ReadConsoleW( + HANDLE(self.handle), + buffer, + code_units_to_be_read, + byref(code_units_read), + None, + ) + if GetLastError() == ERROR_OPERATION_ABORTED: + # wait for KeyboardInterrupt + time.sleep(0.1) + if not rv: + raise OSError(f"Windows error: {GetLastError()}") + + if buffer[0] == EOF: + return 0 + return 2 * code_units_read.value + + +class _WindowsConsoleWriter(_WindowsConsoleRawIOBase): + def writable(self): + return True + + @staticmethod + def _get_error_message(errno): + if errno == ERROR_SUCCESS: + return "ERROR_SUCCESS" + elif errno == ERROR_NOT_ENOUGH_MEMORY: + return "ERROR_NOT_ENOUGH_MEMORY" + return f"Windows error {errno}" + + def write(self, b): + bytes_to_be_written = len(b) + buf = get_buffer(b) + code_units_to_be_written = min(bytes_to_be_written, MAX_BYTES_WRITTEN) // 2 + code_units_written = c_ulong() + + WriteConsoleW( + HANDLE(self.handle), + buf, + code_units_to_be_written, + byref(code_units_written), + None, + ) + bytes_written = 2 * code_units_written.value + + if bytes_written == 0 and bytes_to_be_written > 0: + raise OSError(self._get_error_message(GetLastError())) + return bytes_written + + +class ConsoleStream: + def __init__(self, text_stream: t.TextIO, byte_stream: t.BinaryIO) -> None: + self._text_stream = text_stream + self.buffer = byte_stream + + @property + def name(self) -> str: + return self.buffer.name + + def write(self, x: t.AnyStr) -> int: + if isinstance(x, str): + return self._text_stream.write(x) + try: + self.flush() + except Exception: + pass + return self.buffer.write(x) + + def writelines(self, lines: t.Iterable[t.AnyStr]) -> None: + for line in lines: + self.write(line) + + def __getattr__(self, name: str) -> t.Any: + return getattr(self._text_stream, name) + + def isatty(self) -> bool: + return self.buffer.isatty() + + def __repr__(self): + return f"" + + +def _get_text_stdin(buffer_stream: t.BinaryIO) -> t.TextIO: + text_stream = _NonClosingTextIOWrapper( + io.BufferedReader(_WindowsConsoleReader(STDIN_HANDLE)), + "utf-16-le", + "strict", + line_buffering=True, + ) + return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream)) + + +def _get_text_stdout(buffer_stream: t.BinaryIO) -> t.TextIO: + text_stream = _NonClosingTextIOWrapper( + io.BufferedWriter(_WindowsConsoleWriter(STDOUT_HANDLE)), + "utf-16-le", + "strict", + line_buffering=True, + ) + return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream)) + + +def _get_text_stderr(buffer_stream: t.BinaryIO) -> t.TextIO: + text_stream = _NonClosingTextIOWrapper( + io.BufferedWriter(_WindowsConsoleWriter(STDERR_HANDLE)), + "utf-16-le", + "strict", + line_buffering=True, + ) + return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream)) + + +_stream_factories: t.Mapping[int, t.Callable[[t.BinaryIO], t.TextIO]] = { + 0: _get_text_stdin, + 1: _get_text_stdout, + 2: _get_text_stderr, +} + + +def _is_console(f: t.TextIO) -> bool: + if not hasattr(f, "fileno"): + return False + + try: + fileno = f.fileno() + except (OSError, io.UnsupportedOperation): + return False + + handle = msvcrt.get_osfhandle(fileno) + return bool(GetConsoleMode(handle, byref(DWORD()))) + + +def _get_windows_console_stream( + f: t.TextIO, encoding: t.Optional[str], errors: t.Optional[str] +) -> t.Optional[t.TextIO]: + if ( + get_buffer is not None + and encoding in {"utf-16-le", None} + and errors in {"strict", None} + and _is_console(f) + ): + func = _stream_factories.get(f.fileno()) + if func is not None: + b = getattr(f, "buffer", None) + + if b is None: + return None + + return func(b) diff --git a/.vtodo/Lib/site-packages/click/core.py b/.vtodo/Lib/site-packages/click/core.py new file mode 100644 index 0000000..5abfb0f --- /dev/null +++ b/.vtodo/Lib/site-packages/click/core.py @@ -0,0 +1,2998 @@ +import enum +import errno +import inspect +import os +import sys +import typing as t +from collections import abc +from contextlib import contextmanager +from contextlib import ExitStack +from functools import partial +from functools import update_wrapper +from gettext import gettext as _ +from gettext import ngettext +from itertools import repeat + +from . import types +from .exceptions import Abort +from .exceptions import BadParameter +from .exceptions import ClickException +from .exceptions import Exit +from .exceptions import MissingParameter +from .exceptions import UsageError +from .formatting import HelpFormatter +from .formatting import join_options +from .globals import pop_context +from .globals import push_context +from .parser import _flag_needs_value +from .parser import OptionParser +from .parser import split_opt +from .termui import confirm +from .termui import prompt +from .termui import style +from .utils import _detect_program_name +from .utils import _expand_args +from .utils import echo +from .utils import make_default_short_help +from .utils import make_str +from .utils import PacifyFlushWrapper + +if t.TYPE_CHECKING: + import typing_extensions as te + from .shell_completion import CompletionItem + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) +V = t.TypeVar("V") + + +def _complete_visible_commands( + ctx: "Context", incomplete: str +) -> t.Iterator[t.Tuple[str, "Command"]]: + """List all the subcommands of a group that start with the + incomplete value and aren't hidden. + + :param ctx: Invocation context for the group. + :param incomplete: Value being completed. May be empty. + """ + multi = t.cast(MultiCommand, ctx.command) + + for name in multi.list_commands(ctx): + if name.startswith(incomplete): + command = multi.get_command(ctx, name) + + if command is not None and not command.hidden: + yield name, command + + +def _check_multicommand( + base_command: "MultiCommand", cmd_name: str, cmd: "Command", register: bool = False +) -> None: + if not base_command.chain or not isinstance(cmd, MultiCommand): + return + if register: + hint = ( + "It is not possible to add multi commands as children to" + " another multi command that is in chain mode." + ) + else: + hint = ( + "Found a multi command as subcommand to a multi command" + " that is in chain mode. This is not supported." + ) + raise RuntimeError( + f"{hint}. Command {base_command.name!r} is set to chain and" + f" {cmd_name!r} was added as a subcommand but it in itself is a" + f" multi command. ({cmd_name!r} is a {type(cmd).__name__}" + f" within a chained {type(base_command).__name__} named" + f" {base_command.name!r})." + ) + + +def batch(iterable: t.Iterable[V], batch_size: int) -> t.List[t.Tuple[V, ...]]: + return list(zip(*repeat(iter(iterable), batch_size))) + + +@contextmanager +def augment_usage_errors( + ctx: "Context", param: t.Optional["Parameter"] = None +) -> t.Iterator[None]: + """Context manager that attaches extra information to exceptions.""" + try: + yield + except BadParameter as e: + if e.ctx is None: + e.ctx = ctx + if param is not None and e.param is None: + e.param = param + raise + except UsageError as e: + if e.ctx is None: + e.ctx = ctx + raise + + +def iter_params_for_processing( + invocation_order: t.Sequence["Parameter"], + declaration_order: t.Sequence["Parameter"], +) -> t.List["Parameter"]: + """Given a sequence of parameters in the order as should be considered + for processing and an iterable of parameters that exist, this returns + a list in the correct order as they should be processed. + """ + + def sort_key(item: "Parameter") -> t.Tuple[bool, float]: + try: + idx: float = invocation_order.index(item) + except ValueError: + idx = float("inf") + + return not item.is_eager, idx + + return sorted(declaration_order, key=sort_key) + + +class ParameterSource(enum.Enum): + """This is an :class:`~enum.Enum` that indicates the source of a + parameter's value. + + Use :meth:`click.Context.get_parameter_source` to get the + source for a parameter by name. + + .. versionchanged:: 8.0 + Use :class:`~enum.Enum` and drop the ``validate`` method. + + .. versionchanged:: 8.0 + Added the ``PROMPT`` value. + """ + + COMMANDLINE = enum.auto() + """The value was provided by the command line args.""" + ENVIRONMENT = enum.auto() + """The value was provided with an environment variable.""" + DEFAULT = enum.auto() + """Used the default specified by the parameter.""" + DEFAULT_MAP = enum.auto() + """Used a default provided by :attr:`Context.default_map`.""" + PROMPT = enum.auto() + """Used a prompt to confirm a default or provide a value.""" + + +class Context: + """The context is a special internal object that holds state relevant + for the script execution at every single level. It's normally invisible + to commands unless they opt-in to getting access to it. + + The context is useful as it can pass internal objects around and can + control special execution features such as reading data from + environment variables. + + A context can be used as context manager in which case it will call + :meth:`close` on teardown. + + :param command: the command class for this context. + :param parent: the parent context. + :param info_name: the info name for this invocation. Generally this + is the most descriptive name for the script or + command. For the toplevel script it is usually + the name of the script, for commands below it it's + the name of the script. + :param obj: an arbitrary object of user data. + :param auto_envvar_prefix: the prefix to use for automatic environment + variables. If this is `None` then reading + from environment variables is disabled. This + does not affect manually set environment + variables which are always read. + :param default_map: a dictionary (like object) with default values + for parameters. + :param terminal_width: the width of the terminal. The default is + inherit from parent context. If no context + defines the terminal width then auto + detection will be applied. + :param max_content_width: the maximum width for content rendered by + Click (this currently only affects help + pages). This defaults to 80 characters if + not overridden. In other words: even if the + terminal is larger than that, Click will not + format things wider than 80 characters by + default. In addition to that, formatters might + add some safety mapping on the right. + :param resilient_parsing: if this flag is enabled then Click will + parse without any interactivity or callback + invocation. Default values will also be + ignored. This is useful for implementing + things such as completion support. + :param allow_extra_args: if this is set to `True` then extra arguments + at the end will not raise an error and will be + kept on the context. The default is to inherit + from the command. + :param allow_interspersed_args: if this is set to `False` then options + and arguments cannot be mixed. The + default is to inherit from the command. + :param ignore_unknown_options: instructs click to ignore options it does + not know and keeps them for later + processing. + :param help_option_names: optionally a list of strings that define how + the default help parameter is named. The + default is ``['--help']``. + :param token_normalize_func: an optional function that is used to + normalize tokens (options, choices, + etc.). This for instance can be used to + implement case insensitive behavior. + :param color: controls if the terminal supports ANSI colors or not. The + default is autodetection. This is only needed if ANSI + codes are used in texts that Click prints which is by + default not the case. This for instance would affect + help output. + :param show_default: Show the default value for commands. If this + value is not set, it defaults to the value from the parent + context. ``Command.show_default`` overrides this default for the + specific command. + + .. versionchanged:: 8.1 + The ``show_default`` parameter is overridden by + ``Command.show_default``, instead of the other way around. + + .. versionchanged:: 8.0 + The ``show_default`` parameter defaults to the value from the + parent context. + + .. versionchanged:: 7.1 + Added the ``show_default`` parameter. + + .. versionchanged:: 4.0 + Added the ``color``, ``ignore_unknown_options``, and + ``max_content_width`` parameters. + + .. versionchanged:: 3.0 + Added the ``allow_extra_args`` and ``allow_interspersed_args`` + parameters. + + .. versionchanged:: 2.0 + Added the ``resilient_parsing``, ``help_option_names``, and + ``token_normalize_func`` parameters. + """ + + #: The formatter class to create with :meth:`make_formatter`. + #: + #: .. versionadded:: 8.0 + formatter_class: t.Type["HelpFormatter"] = HelpFormatter + + def __init__( + self, + command: "Command", + parent: t.Optional["Context"] = None, + info_name: t.Optional[str] = None, + obj: t.Optional[t.Any] = None, + auto_envvar_prefix: t.Optional[str] = None, + default_map: t.Optional[t.Dict[str, t.Any]] = None, + terminal_width: t.Optional[int] = None, + max_content_width: t.Optional[int] = None, + resilient_parsing: bool = False, + allow_extra_args: t.Optional[bool] = None, + allow_interspersed_args: t.Optional[bool] = None, + ignore_unknown_options: t.Optional[bool] = None, + help_option_names: t.Optional[t.List[str]] = None, + token_normalize_func: t.Optional[t.Callable[[str], str]] = None, + color: t.Optional[bool] = None, + show_default: t.Optional[bool] = None, + ) -> None: + #: the parent context or `None` if none exists. + self.parent = parent + #: the :class:`Command` for this context. + self.command = command + #: the descriptive information name + self.info_name = info_name + #: Map of parameter names to their parsed values. Parameters + #: with ``expose_value=False`` are not stored. + self.params: t.Dict[str, t.Any] = {} + #: the leftover arguments. + self.args: t.List[str] = [] + #: protected arguments. These are arguments that are prepended + #: to `args` when certain parsing scenarios are encountered but + #: must be never propagated to another arguments. This is used + #: to implement nested parsing. + self.protected_args: t.List[str] = [] + #: the collected prefixes of the command's options. + self._opt_prefixes: t.Set[str] = set(parent._opt_prefixes) if parent else set() + + if obj is None and parent is not None: + obj = parent.obj + + #: the user object stored. + self.obj: t.Any = obj + self._meta: t.Dict[str, t.Any] = getattr(parent, "meta", {}) + + #: A dictionary (-like object) with defaults for parameters. + if ( + default_map is None + and info_name is not None + and parent is not None + and parent.default_map is not None + ): + default_map = parent.default_map.get(info_name) + + self.default_map: t.Optional[t.Dict[str, t.Any]] = default_map + + #: This flag indicates if a subcommand is going to be executed. A + #: group callback can use this information to figure out if it's + #: being executed directly or because the execution flow passes + #: onwards to a subcommand. By default it's None, but it can be + #: the name of the subcommand to execute. + #: + #: If chaining is enabled this will be set to ``'*'`` in case + #: any commands are executed. It is however not possible to + #: figure out which ones. If you require this knowledge you + #: should use a :func:`result_callback`. + self.invoked_subcommand: t.Optional[str] = None + + if terminal_width is None and parent is not None: + terminal_width = parent.terminal_width + + #: The width of the terminal (None is autodetection). + self.terminal_width: t.Optional[int] = terminal_width + + if max_content_width is None and parent is not None: + max_content_width = parent.max_content_width + + #: The maximum width of formatted content (None implies a sensible + #: default which is 80 for most things). + self.max_content_width: t.Optional[int] = max_content_width + + if allow_extra_args is None: + allow_extra_args = command.allow_extra_args + + #: Indicates if the context allows extra args or if it should + #: fail on parsing. + #: + #: .. versionadded:: 3.0 + self.allow_extra_args = allow_extra_args + + if allow_interspersed_args is None: + allow_interspersed_args = command.allow_interspersed_args + + #: Indicates if the context allows mixing of arguments and + #: options or not. + #: + #: .. versionadded:: 3.0 + self.allow_interspersed_args: bool = allow_interspersed_args + + if ignore_unknown_options is None: + ignore_unknown_options = command.ignore_unknown_options + + #: Instructs click to ignore options that a command does not + #: understand and will store it on the context for later + #: processing. This is primarily useful for situations where you + #: want to call into external programs. Generally this pattern is + #: strongly discouraged because it's not possibly to losslessly + #: forward all arguments. + #: + #: .. versionadded:: 4.0 + self.ignore_unknown_options: bool = ignore_unknown_options + + if help_option_names is None: + if parent is not None: + help_option_names = parent.help_option_names + else: + help_option_names = ["--help"] + + #: The names for the help options. + self.help_option_names: t.List[str] = help_option_names + + if token_normalize_func is None and parent is not None: + token_normalize_func = parent.token_normalize_func + + #: An optional normalization function for tokens. This is + #: options, choices, commands etc. + self.token_normalize_func: t.Optional[ + t.Callable[[str], str] + ] = token_normalize_func + + #: Indicates if resilient parsing is enabled. In that case Click + #: will do its best to not cause any failures and default values + #: will be ignored. Useful for completion. + self.resilient_parsing: bool = resilient_parsing + + # If there is no envvar prefix yet, but the parent has one and + # the command on this level has a name, we can expand the envvar + # prefix automatically. + if auto_envvar_prefix is None: + if ( + parent is not None + and parent.auto_envvar_prefix is not None + and self.info_name is not None + ): + auto_envvar_prefix = ( + f"{parent.auto_envvar_prefix}_{self.info_name.upper()}" + ) + else: + auto_envvar_prefix = auto_envvar_prefix.upper() + + if auto_envvar_prefix is not None: + auto_envvar_prefix = auto_envvar_prefix.replace("-", "_") + + self.auto_envvar_prefix: t.Optional[str] = auto_envvar_prefix + + if color is None and parent is not None: + color = parent.color + + #: Controls if styling output is wanted or not. + self.color: t.Optional[bool] = color + + if show_default is None and parent is not None: + show_default = parent.show_default + + #: Show option default values when formatting help text. + self.show_default: t.Optional[bool] = show_default + + self._close_callbacks: t.List[t.Callable[[], t.Any]] = [] + self._depth = 0 + self._parameter_source: t.Dict[str, ParameterSource] = {} + self._exit_stack = ExitStack() + + def to_info_dict(self) -> t.Dict[str, t.Any]: + """Gather information that could be useful for a tool generating + user-facing documentation. This traverses the entire CLI + structure. + + .. code-block:: python + + with Context(cli) as ctx: + info = ctx.to_info_dict() + + .. versionadded:: 8.0 + """ + return { + "command": self.command.to_info_dict(self), + "info_name": self.info_name, + "allow_extra_args": self.allow_extra_args, + "allow_interspersed_args": self.allow_interspersed_args, + "ignore_unknown_options": self.ignore_unknown_options, + "auto_envvar_prefix": self.auto_envvar_prefix, + } + + def __enter__(self) -> "Context": + self._depth += 1 + push_context(self) + return self + + def __exit__(self, exc_type, exc_value, tb): # type: ignore + self._depth -= 1 + if self._depth == 0: + self.close() + pop_context() + + @contextmanager + def scope(self, cleanup: bool = True) -> t.Iterator["Context"]: + """This helper method can be used with the context object to promote + it to the current thread local (see :func:`get_current_context`). + The default behavior of this is to invoke the cleanup functions which + can be disabled by setting `cleanup` to `False`. The cleanup + functions are typically used for things such as closing file handles. + + If the cleanup is intended the context object can also be directly + used as a context manager. + + Example usage:: + + with ctx.scope(): + assert get_current_context() is ctx + + This is equivalent:: + + with ctx: + assert get_current_context() is ctx + + .. versionadded:: 5.0 + + :param cleanup: controls if the cleanup functions should be run or + not. The default is to run these functions. In + some situations the context only wants to be + temporarily pushed in which case this can be disabled. + Nested pushes automatically defer the cleanup. + """ + if not cleanup: + self._depth += 1 + try: + with self as rv: + yield rv + finally: + if not cleanup: + self._depth -= 1 + + @property + def meta(self) -> t.Dict[str, t.Any]: + """This is a dictionary which is shared with all the contexts + that are nested. It exists so that click utilities can store some + state here if they need to. It is however the responsibility of + that code to manage this dictionary well. + + The keys are supposed to be unique dotted strings. For instance + module paths are a good choice for it. What is stored in there is + irrelevant for the operation of click. However what is important is + that code that places data here adheres to the general semantics of + the system. + + Example usage:: + + LANG_KEY = f'{__name__}.lang' + + def set_language(value): + ctx = get_current_context() + ctx.meta[LANG_KEY] = value + + def get_language(): + return get_current_context().meta.get(LANG_KEY, 'en_US') + + .. versionadded:: 5.0 + """ + return self._meta + + def make_formatter(self) -> HelpFormatter: + """Creates the :class:`~click.HelpFormatter` for the help and + usage output. + + To quickly customize the formatter class used without overriding + this method, set the :attr:`formatter_class` attribute. + + .. versionchanged:: 8.0 + Added the :attr:`formatter_class` attribute. + """ + return self.formatter_class( + width=self.terminal_width, max_width=self.max_content_width + ) + + def with_resource(self, context_manager: t.ContextManager[V]) -> V: + """Register a resource as if it were used in a ``with`` + statement. The resource will be cleaned up when the context is + popped. + + Uses :meth:`contextlib.ExitStack.enter_context`. It calls the + resource's ``__enter__()`` method and returns the result. When + the context is popped, it closes the stack, which calls the + resource's ``__exit__()`` method. + + To register a cleanup function for something that isn't a + context manager, use :meth:`call_on_close`. Or use something + from :mod:`contextlib` to turn it into a context manager first. + + .. code-block:: python + + @click.group() + @click.option("--name") + @click.pass_context + def cli(ctx): + ctx.obj = ctx.with_resource(connect_db(name)) + + :param context_manager: The context manager to enter. + :return: Whatever ``context_manager.__enter__()`` returns. + + .. versionadded:: 8.0 + """ + return self._exit_stack.enter_context(context_manager) + + def call_on_close(self, f: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]: + """Register a function to be called when the context tears down. + + This can be used to close resources opened during the script + execution. Resources that support Python's context manager + protocol which would be used in a ``with`` statement should be + registered with :meth:`with_resource` instead. + + :param f: The function to execute on teardown. + """ + return self._exit_stack.callback(f) + + def close(self) -> None: + """Invoke all close callbacks registered with + :meth:`call_on_close`, and exit all context managers entered + with :meth:`with_resource`. + """ + self._exit_stack.close() + # In case the context is reused, create a new exit stack. + self._exit_stack = ExitStack() + + @property + def command_path(self) -> str: + """The computed command path. This is used for the ``usage`` + information on the help page. It's automatically created by + combining the info names of the chain of contexts to the root. + """ + rv = "" + if self.info_name is not None: + rv = self.info_name + if self.parent is not None: + parent_command_path = [self.parent.command_path] + + if isinstance(self.parent.command, Command): + for param in self.parent.command.get_params(self): + parent_command_path.extend(param.get_usage_pieces(self)) + + rv = f"{' '.join(parent_command_path)} {rv}" + return rv.lstrip() + + def find_root(self) -> "Context": + """Finds the outermost context.""" + node = self + while node.parent is not None: + node = node.parent + return node + + def find_object(self, object_type: t.Type[V]) -> t.Optional[V]: + """Finds the closest object of a given type.""" + node: t.Optional["Context"] = self + + while node is not None: + if isinstance(node.obj, object_type): + return node.obj + + node = node.parent + + return None + + def ensure_object(self, object_type: t.Type[V]) -> V: + """Like :meth:`find_object` but sets the innermost object to a + new instance of `object_type` if it does not exist. + """ + rv = self.find_object(object_type) + if rv is None: + self.obj = rv = object_type() + return rv + + @t.overload + def lookup_default( + self, name: str, call: "te.Literal[True]" = True + ) -> t.Optional[t.Any]: + ... + + @t.overload + def lookup_default( + self, name: str, call: "te.Literal[False]" = ... + ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]: + ... + + def lookup_default(self, name: str, call: bool = True) -> t.Optional[t.Any]: + """Get the default for a parameter from :attr:`default_map`. + + :param name: Name of the parameter. + :param call: If the default is a callable, call it. Disable to + return the callable instead. + + .. versionchanged:: 8.0 + Added the ``call`` parameter. + """ + if self.default_map is not None: + value = self.default_map.get(name) + + if call and callable(value): + return value() + + return value + + return None + + def fail(self, message: str) -> "te.NoReturn": + """Aborts the execution of the program with a specific error + message. + + :param message: the error message to fail with. + """ + raise UsageError(message, self) + + def abort(self) -> "te.NoReturn": + """Aborts the script.""" + raise Abort() + + def exit(self, code: int = 0) -> "te.NoReturn": + """Exits the application with a given exit code.""" + raise Exit(code) + + def get_usage(self) -> str: + """Helper method to get formatted usage string for the current + context and command. + """ + return self.command.get_usage(self) + + def get_help(self) -> str: + """Helper method to get formatted help page for the current + context and command. + """ + return self.command.get_help(self) + + def _make_sub_context(self, command: "Command") -> "Context": + """Create a new context of the same type as this context, but + for a new command. + + :meta private: + """ + return type(self)(command, info_name=command.name, parent=self) + + def invoke( + __self, # noqa: B902 + __callback: t.Union["Command", t.Callable[..., t.Any]], + *args: t.Any, + **kwargs: t.Any, + ) -> t.Any: + """Invokes a command callback in exactly the way it expects. There + are two ways to invoke this method: + + 1. the first argument can be a callback and all other arguments and + keyword arguments are forwarded directly to the function. + 2. the first argument is a click command object. In that case all + arguments are forwarded as well but proper click parameters + (options and click arguments) must be keyword arguments and Click + will fill in defaults. + + Note that before Click 3.2 keyword arguments were not properly filled + in against the intention of this code and no context was created. For + more information about this change and why it was done in a bugfix + release see :ref:`upgrade-to-3.2`. + + .. versionchanged:: 8.0 + All ``kwargs`` are tracked in :attr:`params` so they will be + passed if :meth:`forward` is called at multiple levels. + """ + if isinstance(__callback, Command): + other_cmd = __callback + + if other_cmd.callback is None: + raise TypeError( + "The given command does not have a callback that can be invoked." + ) + else: + __callback = other_cmd.callback + + ctx = __self._make_sub_context(other_cmd) + + for param in other_cmd.params: + if param.name not in kwargs and param.expose_value: + kwargs[param.name] = param.type_cast_value( # type: ignore + ctx, param.get_default(ctx) + ) + + # Track all kwargs as params, so that forward() will pass + # them on in subsequent calls. + ctx.params.update(kwargs) + else: + ctx = __self + + with augment_usage_errors(__self): + with ctx: + return __callback(*args, **kwargs) + + def forward( + __self, __cmd: "Command", *args: t.Any, **kwargs: t.Any # noqa: B902 + ) -> t.Any: + """Similar to :meth:`invoke` but fills in default keyword + arguments from the current context if the other command expects + it. This cannot invoke callbacks directly, only other commands. + + .. versionchanged:: 8.0 + All ``kwargs`` are tracked in :attr:`params` so they will be + passed if ``forward`` is called at multiple levels. + """ + # Can only forward to other commands, not direct callbacks. + if not isinstance(__cmd, Command): + raise TypeError("Callback is not a command.") + + for param in __self.params: + if param not in kwargs: + kwargs[param] = __self.params[param] + + return __self.invoke(__cmd, *args, **kwargs) + + def set_parameter_source(self, name: str, source: ParameterSource) -> None: + """Set the source of a parameter. This indicates the location + from which the value of the parameter was obtained. + + :param name: The name of the parameter. + :param source: A member of :class:`~click.core.ParameterSource`. + """ + self._parameter_source[name] = source + + def get_parameter_source(self, name: str) -> t.Optional[ParameterSource]: + """Get the source of a parameter. This indicates the location + from which the value of the parameter was obtained. + + This can be useful for determining when a user specified a value + on the command line that is the same as the default value. It + will be :attr:`~click.core.ParameterSource.DEFAULT` only if the + value was actually taken from the default. + + :param name: The name of the parameter. + :rtype: ParameterSource + + .. versionchanged:: 8.0 + Returns ``None`` if the parameter was not provided from any + source. + """ + return self._parameter_source.get(name) + + +class BaseCommand: + """The base command implements the minimal API contract of commands. + Most code will never use this as it does not implement a lot of useful + functionality but it can act as the direct subclass of alternative + parsing methods that do not depend on the Click parser. + + For instance, this can be used to bridge Click and other systems like + argparse or docopt. + + Because base commands do not implement a lot of the API that other + parts of Click take for granted, they are not supported for all + operations. For instance, they cannot be used with the decorators + usually and they have no built-in callback system. + + .. versionchanged:: 2.0 + Added the `context_settings` parameter. + + :param name: the name of the command to use unless a group overrides it. + :param context_settings: an optional dictionary with defaults that are + passed to the context object. + """ + + #: The context class to create with :meth:`make_context`. + #: + #: .. versionadded:: 8.0 + context_class: t.Type[Context] = Context + #: the default for the :attr:`Context.allow_extra_args` flag. + allow_extra_args = False + #: the default for the :attr:`Context.allow_interspersed_args` flag. + allow_interspersed_args = True + #: the default for the :attr:`Context.ignore_unknown_options` flag. + ignore_unknown_options = False + + def __init__( + self, + name: t.Optional[str], + context_settings: t.Optional[t.Dict[str, t.Any]] = None, + ) -> None: + #: the name the command thinks it has. Upon registering a command + #: on a :class:`Group` the group will default the command name + #: with this information. You should instead use the + #: :class:`Context`\'s :attr:`~Context.info_name` attribute. + self.name = name + + if context_settings is None: + context_settings = {} + + #: an optional dictionary with defaults passed to the context. + self.context_settings: t.Dict[str, t.Any] = context_settings + + def to_info_dict(self, ctx: Context) -> t.Dict[str, t.Any]: + """Gather information that could be useful for a tool generating + user-facing documentation. This traverses the entire structure + below this command. + + Use :meth:`click.Context.to_info_dict` to traverse the entire + CLI structure. + + :param ctx: A :class:`Context` representing this command. + + .. versionadded:: 8.0 + """ + return {"name": self.name} + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} {self.name}>" + + def get_usage(self, ctx: Context) -> str: + raise NotImplementedError("Base commands cannot get usage") + + def get_help(self, ctx: Context) -> str: + raise NotImplementedError("Base commands cannot get help") + + def make_context( + self, + info_name: t.Optional[str], + args: t.List[str], + parent: t.Optional[Context] = None, + **extra: t.Any, + ) -> Context: + """This function when given an info name and arguments will kick + off the parsing and create a new :class:`Context`. It does not + invoke the actual command callback though. + + To quickly customize the context class used without overriding + this method, set the :attr:`context_class` attribute. + + :param info_name: the info name for this invocation. Generally this + is the most descriptive name for the script or + command. For the toplevel script it's usually + the name of the script, for commands below it it's + the name of the command. + :param args: the arguments to parse as list of strings. + :param parent: the parent context if available. + :param extra: extra keyword arguments forwarded to the context + constructor. + + .. versionchanged:: 8.0 + Added the :attr:`context_class` attribute. + """ + for key, value in self.context_settings.items(): + if key not in extra: + extra[key] = value + + ctx = self.context_class( + self, info_name=info_name, parent=parent, **extra # type: ignore + ) + + with ctx.scope(cleanup=False): + self.parse_args(ctx, args) + return ctx + + def parse_args(self, ctx: Context, args: t.List[str]) -> t.List[str]: + """Given a context and a list of arguments this creates the parser + and parses the arguments, then modifies the context as necessary. + This is automatically invoked by :meth:`make_context`. + """ + raise NotImplementedError("Base commands do not know how to parse arguments.") + + def invoke(self, ctx: Context) -> t.Any: + """Given a context, this invokes the command. The default + implementation is raising a not implemented error. + """ + raise NotImplementedError("Base commands are not invokable by default") + + def shell_complete(self, ctx: Context, incomplete: str) -> t.List["CompletionItem"]: + """Return a list of completions for the incomplete value. Looks + at the names of chained multi-commands. + + Any command could be part of a chained multi-command, so sibling + commands are valid at any point during command completion. Other + command classes will return more completions. + + :param ctx: Invocation context for this command. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + results: t.List["CompletionItem"] = [] + + while ctx.parent is not None: + ctx = ctx.parent + + if isinstance(ctx.command, MultiCommand) and ctx.command.chain: + results.extend( + CompletionItem(name, help=command.get_short_help_str()) + for name, command in _complete_visible_commands(ctx, incomplete) + if name not in ctx.protected_args + ) + + return results + + @t.overload + def main( + self, + args: t.Optional[t.Sequence[str]] = None, + prog_name: t.Optional[str] = None, + complete_var: t.Optional[str] = None, + standalone_mode: "te.Literal[True]" = True, + **extra: t.Any, + ) -> "te.NoReturn": + ... + + @t.overload + def main( + self, + args: t.Optional[t.Sequence[str]] = None, + prog_name: t.Optional[str] = None, + complete_var: t.Optional[str] = None, + standalone_mode: bool = ..., + **extra: t.Any, + ) -> t.Any: + ... + + def main( + self, + args: t.Optional[t.Sequence[str]] = None, + prog_name: t.Optional[str] = None, + complete_var: t.Optional[str] = None, + standalone_mode: bool = True, + windows_expand_args: bool = True, + **extra: t.Any, + ) -> t.Any: + """This is the way to invoke a script with all the bells and + whistles as a command line application. This will always terminate + the application after a call. If this is not wanted, ``SystemExit`` + needs to be caught. + + This method is also available by directly calling the instance of + a :class:`Command`. + + :param args: the arguments that should be used for parsing. If not + provided, ``sys.argv[1:]`` is used. + :param prog_name: the program name that should be used. By default + the program name is constructed by taking the file + name from ``sys.argv[0]``. + :param complete_var: the environment variable that controls the + bash completion support. The default is + ``"__COMPLETE"`` with prog_name in + uppercase. + :param standalone_mode: the default behavior is to invoke the script + in standalone mode. Click will then + handle exceptions and convert them into + error messages and the function will never + return but shut down the interpreter. If + this is set to `False` they will be + propagated to the caller and the return + value of this function is the return value + of :meth:`invoke`. + :param windows_expand_args: Expand glob patterns, user dir, and + env vars in command line args on Windows. + :param extra: extra keyword arguments are forwarded to the context + constructor. See :class:`Context` for more information. + + .. versionchanged:: 8.0.1 + Added the ``windows_expand_args`` parameter to allow + disabling command line arg expansion on Windows. + + .. versionchanged:: 8.0 + When taking arguments from ``sys.argv`` on Windows, glob + patterns, user dir, and env vars are expanded. + + .. versionchanged:: 3.0 + Added the ``standalone_mode`` parameter. + """ + if args is None: + args = sys.argv[1:] + + if os.name == "nt" and windows_expand_args: + args = _expand_args(args) + else: + args = list(args) + + if prog_name is None: + prog_name = _detect_program_name() + + # Process shell completion requests and exit early. + self._main_shell_completion(extra, prog_name, complete_var) + + try: + try: + with self.make_context(prog_name, args, **extra) as ctx: + rv = self.invoke(ctx) + if not standalone_mode: + return rv + # it's not safe to `ctx.exit(rv)` here! + # note that `rv` may actually contain data like "1" which + # has obvious effects + # more subtle case: `rv=[None, None]` can come out of + # chained commands which all returned `None` -- so it's not + # even always obvious that `rv` indicates success/failure + # by its truthiness/falsiness + ctx.exit() + except (EOFError, KeyboardInterrupt): + echo(file=sys.stderr) + raise Abort() from None + except ClickException as e: + if not standalone_mode: + raise + e.show() + sys.exit(e.exit_code) + except OSError as e: + if e.errno == errno.EPIPE: + sys.stdout = t.cast(t.TextIO, PacifyFlushWrapper(sys.stdout)) + sys.stderr = t.cast(t.TextIO, PacifyFlushWrapper(sys.stderr)) + sys.exit(1) + else: + raise + except Exit as e: + if standalone_mode: + sys.exit(e.exit_code) + else: + # in non-standalone mode, return the exit code + # note that this is only reached if `self.invoke` above raises + # an Exit explicitly -- thus bypassing the check there which + # would return its result + # the results of non-standalone execution may therefore be + # somewhat ambiguous: if there are codepaths which lead to + # `ctx.exit(1)` and to `return 1`, the caller won't be able to + # tell the difference between the two + return e.exit_code + except Abort: + if not standalone_mode: + raise + echo(_("Aborted!"), file=sys.stderr) + sys.exit(1) + + def _main_shell_completion( + self, + ctx_args: t.Dict[str, t.Any], + prog_name: str, + complete_var: t.Optional[str] = None, + ) -> None: + """Check if the shell is asking for tab completion, process + that, then exit early. Called from :meth:`main` before the + program is invoked. + + :param prog_name: Name of the executable in the shell. + :param complete_var: Name of the environment variable that holds + the completion instruction. Defaults to + ``_{PROG_NAME}_COMPLETE``. + """ + if complete_var is None: + complete_var = f"_{prog_name}_COMPLETE".replace("-", "_").upper() + + instruction = os.environ.get(complete_var) + + if not instruction: + return + + from .shell_completion import shell_complete + + rv = shell_complete(self, ctx_args, prog_name, complete_var, instruction) + sys.exit(rv) + + def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any: + """Alias for :meth:`main`.""" + return self.main(*args, **kwargs) + + +class Command(BaseCommand): + """Commands are the basic building block of command line interfaces in + Click. A basic command handles command line parsing and might dispatch + more parsing to commands nested below it. + + :param name: the name of the command to use unless a group overrides it. + :param context_settings: an optional dictionary with defaults that are + passed to the context object. + :param callback: the callback to invoke. This is optional. + :param params: the parameters to register with this command. This can + be either :class:`Option` or :class:`Argument` objects. + :param help: the help string to use for this command. + :param epilog: like the help string but it's printed at the end of the + help page after everything else. + :param short_help: the short help to use for this command. This is + shown on the command listing of the parent command. + :param add_help_option: by default each command registers a ``--help`` + option. This can be disabled by this parameter. + :param no_args_is_help: this controls what happens if no arguments are + provided. This option is disabled by default. + If enabled this will add ``--help`` as argument + if no arguments are passed + :param hidden: hide this command from help outputs. + + :param deprecated: issues a message indicating that + the command is deprecated. + + .. versionchanged:: 8.1 + ``help``, ``epilog``, and ``short_help`` are stored unprocessed, + all formatting is done when outputting help text, not at init, + and is done even if not using the ``@command`` decorator. + + .. versionchanged:: 8.0 + Added a ``repr`` showing the command name. + + .. versionchanged:: 7.1 + Added the ``no_args_is_help`` parameter. + + .. versionchanged:: 2.0 + Added the ``context_settings`` parameter. + """ + + def __init__( + self, + name: t.Optional[str], + context_settings: t.Optional[t.Dict[str, t.Any]] = None, + callback: t.Optional[t.Callable[..., t.Any]] = None, + params: t.Optional[t.List["Parameter"]] = None, + help: t.Optional[str] = None, + epilog: t.Optional[str] = None, + short_help: t.Optional[str] = None, + options_metavar: t.Optional[str] = "[OPTIONS]", + add_help_option: bool = True, + no_args_is_help: bool = False, + hidden: bool = False, + deprecated: bool = False, + ) -> None: + super().__init__(name, context_settings) + #: the callback to execute when the command fires. This might be + #: `None` in which case nothing happens. + self.callback = callback + #: the list of parameters for this command in the order they + #: should show up in the help page and execute. Eager parameters + #: will automatically be handled before non eager ones. + self.params: t.List["Parameter"] = params or [] + self.help = help + self.epilog = epilog + self.options_metavar = options_metavar + self.short_help = short_help + self.add_help_option = add_help_option + self.no_args_is_help = no_args_is_help + self.hidden = hidden + self.deprecated = deprecated + + def to_info_dict(self, ctx: Context) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict(ctx) + info_dict.update( + params=[param.to_info_dict() for param in self.get_params(ctx)], + help=self.help, + epilog=self.epilog, + short_help=self.short_help, + hidden=self.hidden, + deprecated=self.deprecated, + ) + return info_dict + + def get_usage(self, ctx: Context) -> str: + """Formats the usage line into a string and returns it. + + Calls :meth:`format_usage` internally. + """ + formatter = ctx.make_formatter() + self.format_usage(ctx, formatter) + return formatter.getvalue().rstrip("\n") + + def get_params(self, ctx: Context) -> t.List["Parameter"]: + rv = self.params + help_option = self.get_help_option(ctx) + + if help_option is not None: + rv = [*rv, help_option] + + return rv + + def format_usage(self, ctx: Context, formatter: HelpFormatter) -> None: + """Writes the usage line into the formatter. + + This is a low-level method called by :meth:`get_usage`. + """ + pieces = self.collect_usage_pieces(ctx) + formatter.write_usage(ctx.command_path, " ".join(pieces)) + + def collect_usage_pieces(self, ctx: Context) -> t.List[str]: + """Returns all the pieces that go into the usage line and returns + it as a list of strings. + """ + rv = [self.options_metavar] if self.options_metavar else [] + + for param in self.get_params(ctx): + rv.extend(param.get_usage_pieces(ctx)) + + return rv + + def get_help_option_names(self, ctx: Context) -> t.List[str]: + """Returns the names for the help option.""" + all_names = set(ctx.help_option_names) + for param in self.params: + all_names.difference_update(param.opts) + all_names.difference_update(param.secondary_opts) + return list(all_names) + + def get_help_option(self, ctx: Context) -> t.Optional["Option"]: + """Returns the help option object.""" + help_options = self.get_help_option_names(ctx) + + if not help_options or not self.add_help_option: + return None + + def show_help(ctx: Context, param: "Parameter", value: str) -> None: + if value and not ctx.resilient_parsing: + echo(ctx.get_help(), color=ctx.color) + ctx.exit() + + return Option( + help_options, + is_flag=True, + is_eager=True, + expose_value=False, + callback=show_help, + help=_("Show this message and exit."), + ) + + def make_parser(self, ctx: Context) -> OptionParser: + """Creates the underlying option parser for this command.""" + parser = OptionParser(ctx) + for param in self.get_params(ctx): + param.add_to_parser(parser, ctx) + return parser + + def get_help(self, ctx: Context) -> str: + """Formats the help into a string and returns it. + + Calls :meth:`format_help` internally. + """ + formatter = ctx.make_formatter() + self.format_help(ctx, formatter) + return formatter.getvalue().rstrip("\n") + + def get_short_help_str(self, limit: int = 45) -> str: + """Gets short help for the command or makes it by shortening the + long help string. + """ + if self.short_help: + text = inspect.cleandoc(self.short_help) + elif self.help: + text = make_default_short_help(self.help, limit) + else: + text = "" + + if self.deprecated: + text = _("(Deprecated) {text}").format(text=text) + + return text.strip() + + def format_help(self, ctx: Context, formatter: HelpFormatter) -> None: + """Writes the help into the formatter if it exists. + + This is a low-level method called by :meth:`get_help`. + + This calls the following methods: + + - :meth:`format_usage` + - :meth:`format_help_text` + - :meth:`format_options` + - :meth:`format_epilog` + """ + self.format_usage(ctx, formatter) + self.format_help_text(ctx, formatter) + self.format_options(ctx, formatter) + self.format_epilog(ctx, formatter) + + def format_help_text(self, ctx: Context, formatter: HelpFormatter) -> None: + """Writes the help text to the formatter if it exists.""" + text = self.help if self.help is not None else "" + + if self.deprecated: + text = _("(Deprecated) {text}").format(text=text) + + if text: + text = inspect.cleandoc(text).partition("\f")[0] + formatter.write_paragraph() + + with formatter.indentation(): + formatter.write_text(text) + + def format_options(self, ctx: Context, formatter: HelpFormatter) -> None: + """Writes all the options into the formatter if they exist.""" + opts = [] + for param in self.get_params(ctx): + rv = param.get_help_record(ctx) + if rv is not None: + opts.append(rv) + + if opts: + with formatter.section(_("Options")): + formatter.write_dl(opts) + + def format_epilog(self, ctx: Context, formatter: HelpFormatter) -> None: + """Writes the epilog into the formatter if it exists.""" + if self.epilog: + epilog = inspect.cleandoc(self.epilog) + formatter.write_paragraph() + + with formatter.indentation(): + formatter.write_text(epilog) + + def parse_args(self, ctx: Context, args: t.List[str]) -> t.List[str]: + if not args and self.no_args_is_help and not ctx.resilient_parsing: + echo(ctx.get_help(), color=ctx.color) + ctx.exit() + + parser = self.make_parser(ctx) + opts, args, param_order = parser.parse_args(args=args) + + for param in iter_params_for_processing(param_order, self.get_params(ctx)): + value, args = param.handle_parse_result(ctx, opts, args) + + if args and not ctx.allow_extra_args and not ctx.resilient_parsing: + ctx.fail( + ngettext( + "Got unexpected extra argument ({args})", + "Got unexpected extra arguments ({args})", + len(args), + ).format(args=" ".join(map(str, args))) + ) + + ctx.args = args + ctx._opt_prefixes.update(parser._opt_prefixes) + return args + + def invoke(self, ctx: Context) -> t.Any: + """Given a context, this invokes the attached callback (if it exists) + in the right way. + """ + if self.deprecated: + message = _( + "DeprecationWarning: The command {name!r} is deprecated." + ).format(name=self.name) + echo(style(message, fg="red"), err=True) + + if self.callback is not None: + return ctx.invoke(self.callback, **ctx.params) + + def shell_complete(self, ctx: Context, incomplete: str) -> t.List["CompletionItem"]: + """Return a list of completions for the incomplete value. Looks + at the names of options and chained multi-commands. + + :param ctx: Invocation context for this command. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + results: t.List["CompletionItem"] = [] + + if incomplete and not incomplete[0].isalnum(): + for param in self.get_params(ctx): + if ( + not isinstance(param, Option) + or param.hidden + or ( + not param.multiple + and ctx.get_parameter_source(param.name) # type: ignore + is ParameterSource.COMMANDLINE + ) + ): + continue + + results.extend( + CompletionItem(name, help=param.help) + for name in [*param.opts, *param.secondary_opts] + if name.startswith(incomplete) + ) + + results.extend(super().shell_complete(ctx, incomplete)) + return results + + +class MultiCommand(Command): + """A multi command is the basic implementation of a command that + dispatches to subcommands. The most common version is the + :class:`Group`. + + :param invoke_without_command: this controls how the multi command itself + is invoked. By default it's only invoked + if a subcommand is provided. + :param no_args_is_help: this controls what happens if no arguments are + provided. This option is enabled by default if + `invoke_without_command` is disabled or disabled + if it's enabled. If enabled this will add + ``--help`` as argument if no arguments are + passed. + :param subcommand_metavar: the string that is used in the documentation + to indicate the subcommand place. + :param chain: if this is set to `True` chaining of multiple subcommands + is enabled. This restricts the form of commands in that + they cannot have optional arguments but it allows + multiple commands to be chained together. + :param result_callback: The result callback to attach to this multi + command. This can be set or changed later with the + :meth:`result_callback` decorator. + """ + + allow_extra_args = True + allow_interspersed_args = False + + def __init__( + self, + name: t.Optional[str] = None, + invoke_without_command: bool = False, + no_args_is_help: t.Optional[bool] = None, + subcommand_metavar: t.Optional[str] = None, + chain: bool = False, + result_callback: t.Optional[t.Callable[..., t.Any]] = None, + **attrs: t.Any, + ) -> None: + super().__init__(name, **attrs) + + if no_args_is_help is None: + no_args_is_help = not invoke_without_command + + self.no_args_is_help = no_args_is_help + self.invoke_without_command = invoke_without_command + + if subcommand_metavar is None: + if chain: + subcommand_metavar = "COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]..." + else: + subcommand_metavar = "COMMAND [ARGS]..." + + self.subcommand_metavar = subcommand_metavar + self.chain = chain + # The result callback that is stored. This can be set or + # overridden with the :func:`result_callback` decorator. + self._result_callback = result_callback + + if self.chain: + for param in self.params: + if isinstance(param, Argument) and not param.required: + raise RuntimeError( + "Multi commands in chain mode cannot have" + " optional arguments." + ) + + def to_info_dict(self, ctx: Context) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict(ctx) + commands = {} + + for name in self.list_commands(ctx): + command = self.get_command(ctx, name) + + if command is None: + continue + + sub_ctx = ctx._make_sub_context(command) + + with sub_ctx.scope(cleanup=False): + commands[name] = command.to_info_dict(sub_ctx) + + info_dict.update(commands=commands, chain=self.chain) + return info_dict + + def collect_usage_pieces(self, ctx: Context) -> t.List[str]: + rv = super().collect_usage_pieces(ctx) + rv.append(self.subcommand_metavar) + return rv + + def format_options(self, ctx: Context, formatter: HelpFormatter) -> None: + super().format_options(ctx, formatter) + self.format_commands(ctx, formatter) + + def result_callback(self, replace: bool = False) -> t.Callable[[F], F]: + """Adds a result callback to the command. By default if a + result callback is already registered this will chain them but + this can be disabled with the `replace` parameter. The result + callback is invoked with the return value of the subcommand + (or the list of return values from all subcommands if chaining + is enabled) as well as the parameters as they would be passed + to the main callback. + + Example:: + + @click.group() + @click.option('-i', '--input', default=23) + def cli(input): + return 42 + + @cli.result_callback() + def process_result(result, input): + return result + input + + :param replace: if set to `True` an already existing result + callback will be removed. + + .. versionchanged:: 8.0 + Renamed from ``resultcallback``. + + .. versionadded:: 3.0 + """ + + def decorator(f: F) -> F: + old_callback = self._result_callback + + if old_callback is None or replace: + self._result_callback = f + return f + + def function(__value, *args, **kwargs): # type: ignore + inner = old_callback(__value, *args, **kwargs) # type: ignore + return f(inner, *args, **kwargs) + + self._result_callback = rv = update_wrapper(t.cast(F, function), f) + return rv + + return decorator + + def format_commands(self, ctx: Context, formatter: HelpFormatter) -> None: + """Extra format methods for multi methods that adds all the commands + after the options. + """ + commands = [] + for subcommand in self.list_commands(ctx): + cmd = self.get_command(ctx, subcommand) + # What is this, the tool lied about a command. Ignore it + if cmd is None: + continue + if cmd.hidden: + continue + + commands.append((subcommand, cmd)) + + # allow for 3 times the default spacing + if len(commands): + limit = formatter.width - 6 - max(len(cmd[0]) for cmd in commands) + + rows = [] + for subcommand, cmd in commands: + help = cmd.get_short_help_str(limit) + rows.append((subcommand, help)) + + if rows: + with formatter.section(_("Commands")): + formatter.write_dl(rows) + + def parse_args(self, ctx: Context, args: t.List[str]) -> t.List[str]: + if not args and self.no_args_is_help and not ctx.resilient_parsing: + echo(ctx.get_help(), color=ctx.color) + ctx.exit() + + rest = super().parse_args(ctx, args) + + if self.chain: + ctx.protected_args = rest + ctx.args = [] + elif rest: + ctx.protected_args, ctx.args = rest[:1], rest[1:] + + return ctx.args + + def invoke(self, ctx: Context) -> t.Any: + def _process_result(value: t.Any) -> t.Any: + if self._result_callback is not None: + value = ctx.invoke(self._result_callback, value, **ctx.params) + return value + + if not ctx.protected_args: + if self.invoke_without_command: + # No subcommand was invoked, so the result callback is + # invoked with the group return value for regular + # groups, or an empty list for chained groups. + with ctx: + rv = super().invoke(ctx) + return _process_result([] if self.chain else rv) + ctx.fail(_("Missing command.")) + + # Fetch args back out + args = [*ctx.protected_args, *ctx.args] + ctx.args = [] + ctx.protected_args = [] + + # If we're not in chain mode, we only allow the invocation of a + # single command but we also inform the current context about the + # name of the command to invoke. + if not self.chain: + # Make sure the context is entered so we do not clean up + # resources until the result processor has worked. + with ctx: + cmd_name, cmd, args = self.resolve_command(ctx, args) + assert cmd is not None + ctx.invoked_subcommand = cmd_name + super().invoke(ctx) + sub_ctx = cmd.make_context(cmd_name, args, parent=ctx) + with sub_ctx: + return _process_result(sub_ctx.command.invoke(sub_ctx)) + + # In chain mode we create the contexts step by step, but after the + # base command has been invoked. Because at that point we do not + # know the subcommands yet, the invoked subcommand attribute is + # set to ``*`` to inform the command that subcommands are executed + # but nothing else. + with ctx: + ctx.invoked_subcommand = "*" if args else None + super().invoke(ctx) + + # Otherwise we make every single context and invoke them in a + # chain. In that case the return value to the result processor + # is the list of all invoked subcommand's results. + contexts = [] + while args: + cmd_name, cmd, args = self.resolve_command(ctx, args) + assert cmd is not None + sub_ctx = cmd.make_context( + cmd_name, + args, + parent=ctx, + allow_extra_args=True, + allow_interspersed_args=False, + ) + contexts.append(sub_ctx) + args, sub_ctx.args = sub_ctx.args, [] + + rv = [] + for sub_ctx in contexts: + with sub_ctx: + rv.append(sub_ctx.command.invoke(sub_ctx)) + return _process_result(rv) + + def resolve_command( + self, ctx: Context, args: t.List[str] + ) -> t.Tuple[t.Optional[str], t.Optional[Command], t.List[str]]: + cmd_name = make_str(args[0]) + original_cmd_name = cmd_name + + # Get the command + cmd = self.get_command(ctx, cmd_name) + + # If we can't find the command but there is a normalization + # function available, we try with that one. + if cmd is None and ctx.token_normalize_func is not None: + cmd_name = ctx.token_normalize_func(cmd_name) + cmd = self.get_command(ctx, cmd_name) + + # If we don't find the command we want to show an error message + # to the user that it was not provided. However, there is + # something else we should do: if the first argument looks like + # an option we want to kick off parsing again for arguments to + # resolve things like --help which now should go to the main + # place. + if cmd is None and not ctx.resilient_parsing: + if split_opt(cmd_name)[0]: + self.parse_args(ctx, ctx.args) + ctx.fail(_("No such command {name!r}.").format(name=original_cmd_name)) + return cmd_name if cmd else None, cmd, args[1:] + + def get_command(self, ctx: Context, cmd_name: str) -> t.Optional[Command]: + """Given a context and a command name, this returns a + :class:`Command` object if it exists or returns `None`. + """ + raise NotImplementedError + + def list_commands(self, ctx: Context) -> t.List[str]: + """Returns a list of subcommand names in the order they should + appear. + """ + return [] + + def shell_complete(self, ctx: Context, incomplete: str) -> t.List["CompletionItem"]: + """Return a list of completions for the incomplete value. Looks + at the names of options, subcommands, and chained + multi-commands. + + :param ctx: Invocation context for this command. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + results = [ + CompletionItem(name, help=command.get_short_help_str()) + for name, command in _complete_visible_commands(ctx, incomplete) + ] + results.extend(super().shell_complete(ctx, incomplete)) + return results + + +class Group(MultiCommand): + """A group allows a command to have subcommands attached. This is + the most common way to implement nesting in Click. + + :param name: The name of the group command. + :param commands: A dict mapping names to :class:`Command` objects. + Can also be a list of :class:`Command`, which will use + :attr:`Command.name` to create the dict. + :param attrs: Other command arguments described in + :class:`MultiCommand`, :class:`Command`, and + :class:`BaseCommand`. + + .. versionchanged:: 8.0 + The ``commmands`` argument can be a list of command objects. + """ + + #: If set, this is used by the group's :meth:`command` decorator + #: as the default :class:`Command` class. This is useful to make all + #: subcommands use a custom command class. + #: + #: .. versionadded:: 8.0 + command_class: t.Optional[t.Type[Command]] = None + + #: If set, this is used by the group's :meth:`group` decorator + #: as the default :class:`Group` class. This is useful to make all + #: subgroups use a custom group class. + #: + #: If set to the special value :class:`type` (literally + #: ``group_class = type``), this group's class will be used as the + #: default class. This makes a custom group class continue to make + #: custom groups. + #: + #: .. versionadded:: 8.0 + group_class: t.Optional[t.Union[t.Type["Group"], t.Type[type]]] = None + # Literal[type] isn't valid, so use Type[type] + + def __init__( + self, + name: t.Optional[str] = None, + commands: t.Optional[t.Union[t.Dict[str, Command], t.Sequence[Command]]] = None, + **attrs: t.Any, + ) -> None: + super().__init__(name, **attrs) + + if commands is None: + commands = {} + elif isinstance(commands, abc.Sequence): + commands = {c.name: c for c in commands if c.name is not None} + + #: The registered subcommands by their exported names. + self.commands: t.Dict[str, Command] = commands + + def add_command(self, cmd: Command, name: t.Optional[str] = None) -> None: + """Registers another :class:`Command` with this group. If the name + is not provided, the name of the command is used. + """ + name = name or cmd.name + if name is None: + raise TypeError("Command has no name.") + _check_multicommand(self, name, cmd, register=True) + self.commands[name] = cmd + + @t.overload + def command(self, __func: t.Callable[..., t.Any]) -> Command: + ... + + @t.overload + def command( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], Command]: + ... + + def command( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Union[t.Callable[[t.Callable[..., t.Any]], Command], Command]: + """A shortcut decorator for declaring and attaching a command to + the group. This takes the same arguments as :func:`command` and + immediately registers the created command with this group by + calling :meth:`add_command`. + + To customize the command class used, set the + :attr:`command_class` attribute. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. + + .. versionchanged:: 8.0 + Added the :attr:`command_class` attribute. + """ + from .decorators import command + + if self.command_class and kwargs.get("cls") is None: + kwargs["cls"] = self.command_class + + func: t.Optional[t.Callable] = None + + if args and callable(args[0]): + assert ( + len(args) == 1 and not kwargs + ), "Use 'command(**kwargs)(callable)' to provide arguments." + (func,) = args + args = () + + def decorator(f: t.Callable[..., t.Any]) -> Command: + cmd: Command = command(*args, **kwargs)(f) + self.add_command(cmd) + return cmd + + if func is not None: + return decorator(func) + + return decorator + + @t.overload + def group(self, __func: t.Callable[..., t.Any]) -> "Group": + ... + + @t.overload + def group( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], "Group"]: + ... + + def group( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Union[t.Callable[[t.Callable[..., t.Any]], "Group"], "Group"]: + """A shortcut decorator for declaring and attaching a group to + the group. This takes the same arguments as :func:`group` and + immediately registers the created group with this group by + calling :meth:`add_command`. + + To customize the group class used, set the :attr:`group_class` + attribute. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. + + .. versionchanged:: 8.0 + Added the :attr:`group_class` attribute. + """ + from .decorators import group + + func: t.Optional[t.Callable] = None + + if args and callable(args[0]): + assert ( + len(args) == 1 and not kwargs + ), "Use 'group(**kwargs)(callable)' to provide arguments." + (func,) = args + args = () + + if self.group_class is not None and kwargs.get("cls") is None: + if self.group_class is type: + kwargs["cls"] = type(self) + else: + kwargs["cls"] = self.group_class + + def decorator(f: t.Callable[..., t.Any]) -> "Group": + cmd: Group = group(*args, **kwargs)(f) + self.add_command(cmd) + return cmd + + if func is not None: + return decorator(func) + + return decorator + + def get_command(self, ctx: Context, cmd_name: str) -> t.Optional[Command]: + return self.commands.get(cmd_name) + + def list_commands(self, ctx: Context) -> t.List[str]: + return sorted(self.commands) + + +class CommandCollection(MultiCommand): + """A command collection is a multi command that merges multiple multi + commands together into one. This is a straightforward implementation + that accepts a list of different multi commands as sources and + provides all the commands for each of them. + """ + + def __init__( + self, + name: t.Optional[str] = None, + sources: t.Optional[t.List[MultiCommand]] = None, + **attrs: t.Any, + ) -> None: + super().__init__(name, **attrs) + #: The list of registered multi commands. + self.sources: t.List[MultiCommand] = sources or [] + + def add_source(self, multi_cmd: MultiCommand) -> None: + """Adds a new multi command to the chain dispatcher.""" + self.sources.append(multi_cmd) + + def get_command(self, ctx: Context, cmd_name: str) -> t.Optional[Command]: + for source in self.sources: + rv = source.get_command(ctx, cmd_name) + + if rv is not None: + if self.chain: + _check_multicommand(self, cmd_name, rv) + + return rv + + return None + + def list_commands(self, ctx: Context) -> t.List[str]: + rv: t.Set[str] = set() + + for source in self.sources: + rv.update(source.list_commands(ctx)) + + return sorted(rv) + + +def _check_iter(value: t.Any) -> t.Iterator[t.Any]: + """Check if the value is iterable but not a string. Raises a type + error, or return an iterator over the value. + """ + if isinstance(value, str): + raise TypeError + + return iter(value) + + +class Parameter: + r"""A parameter to a command comes in two versions: they are either + :class:`Option`\s or :class:`Argument`\s. Other subclasses are currently + not supported by design as some of the internals for parsing are + intentionally not finalized. + + Some settings are supported by both options and arguments. + + :param param_decls: the parameter declarations for this option or + argument. This is a list of flags or argument + names. + :param type: the type that should be used. Either a :class:`ParamType` + or a Python type. The later is converted into the former + automatically if supported. + :param required: controls if this is optional or not. + :param default: the default value if omitted. This can also be a callable, + in which case it's invoked when the default is needed + without any arguments. + :param callback: A function to further process or validate the value + after type conversion. It is called as ``f(ctx, param, value)`` + and must return the value. It is called for all sources, + including prompts. + :param nargs: the number of arguments to match. If not ``1`` the return + value is a tuple instead of single value. The default for + nargs is ``1`` (except if the type is a tuple, then it's + the arity of the tuple). If ``nargs=-1``, all remaining + parameters are collected. + :param metavar: how the value is represented in the help page. + :param expose_value: if this is `True` then the value is passed onwards + to the command callback and stored on the context, + otherwise it's skipped. + :param is_eager: eager values are processed before non eager ones. This + should not be set for arguments or it will inverse the + order of processing. + :param envvar: a string or list of strings that are environment variables + that should be checked. + :param shell_complete: A function that returns custom shell + completions. Used instead of the param's type completion if + given. Takes ``ctx, param, incomplete`` and must return a list + of :class:`~click.shell_completion.CompletionItem` or a list of + strings. + + .. versionchanged:: 8.0 + ``process_value`` validates required parameters and bounded + ``nargs``, and invokes the parameter callback before returning + the value. This allows the callback to validate prompts. + ``full_process_value`` is removed. + + .. versionchanged:: 8.0 + ``autocompletion`` is renamed to ``shell_complete`` and has new + semantics described above. The old name is deprecated and will + be removed in 8.1, until then it will be wrapped to match the + new requirements. + + .. versionchanged:: 8.0 + For ``multiple=True, nargs>1``, the default must be a list of + tuples. + + .. versionchanged:: 8.0 + Setting a default is no longer required for ``nargs>1``, it will + default to ``None``. ``multiple=True`` or ``nargs=-1`` will + default to ``()``. + + .. versionchanged:: 7.1 + Empty environment variables are ignored rather than taking the + empty string value. This makes it possible for scripts to clear + variables if they can't unset them. + + .. versionchanged:: 2.0 + Changed signature for parameter callback to also be passed the + parameter. The old callback format will still work, but it will + raise a warning to give you a chance to migrate the code easier. + """ + + param_type_name = "parameter" + + def __init__( + self, + param_decls: t.Optional[t.Sequence[str]] = None, + type: t.Optional[t.Union[types.ParamType, t.Any]] = None, + required: bool = False, + default: t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]] = None, + callback: t.Optional[t.Callable[[Context, "Parameter", t.Any], t.Any]] = None, + nargs: t.Optional[int] = None, + multiple: bool = False, + metavar: t.Optional[str] = None, + expose_value: bool = True, + is_eager: bool = False, + envvar: t.Optional[t.Union[str, t.Sequence[str]]] = None, + shell_complete: t.Optional[ + t.Callable[ + [Context, "Parameter", str], + t.Union[t.List["CompletionItem"], t.List[str]], + ] + ] = None, + ) -> None: + self.name, self.opts, self.secondary_opts = self._parse_decls( + param_decls or (), expose_value + ) + self.type = types.convert_type(type, default) + + # Default nargs to what the type tells us if we have that + # information available. + if nargs is None: + if self.type.is_composite: + nargs = self.type.arity + else: + nargs = 1 + + self.required = required + self.callback = callback + self.nargs = nargs + self.multiple = multiple + self.expose_value = expose_value + self.default = default + self.is_eager = is_eager + self.metavar = metavar + self.envvar = envvar + self._custom_shell_complete = shell_complete + + if __debug__: + if self.type.is_composite and nargs != self.type.arity: + raise ValueError( + f"'nargs' must be {self.type.arity} (or None) for" + f" type {self.type!r}, but it was {nargs}." + ) + + # Skip no default or callable default. + check_default = default if not callable(default) else None + + if check_default is not None: + if multiple: + try: + # Only check the first value against nargs. + check_default = next(_check_iter(check_default), None) + except TypeError: + raise ValueError( + "'default' must be a list when 'multiple' is true." + ) from None + + # Can be None for multiple with empty default. + if nargs != 1 and check_default is not None: + try: + _check_iter(check_default) + except TypeError: + if multiple: + message = ( + "'default' must be a list of lists when 'multiple' is" + " true and 'nargs' != 1." + ) + else: + message = "'default' must be a list when 'nargs' != 1." + + raise ValueError(message) from None + + if nargs > 1 and len(check_default) != nargs: + subject = "item length" if multiple else "length" + raise ValueError( + f"'default' {subject} must match nargs={nargs}." + ) + + def to_info_dict(self) -> t.Dict[str, t.Any]: + """Gather information that could be useful for a tool generating + user-facing documentation. + + Use :meth:`click.Context.to_info_dict` to traverse the entire + CLI structure. + + .. versionadded:: 8.0 + """ + return { + "name": self.name, + "param_type_name": self.param_type_name, + "opts": self.opts, + "secondary_opts": self.secondary_opts, + "type": self.type.to_info_dict(), + "required": self.required, + "nargs": self.nargs, + "multiple": self.multiple, + "default": self.default, + "envvar": self.envvar, + } + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} {self.name}>" + + def _parse_decls( + self, decls: t.Sequence[str], expose_value: bool + ) -> t.Tuple[t.Optional[str], t.List[str], t.List[str]]: + raise NotImplementedError() + + @property + def human_readable_name(self) -> str: + """Returns the human readable name of this parameter. This is the + same as the name for options, but the metavar for arguments. + """ + return self.name # type: ignore + + def make_metavar(self) -> str: + if self.metavar is not None: + return self.metavar + + metavar = self.type.get_metavar(self) + + if metavar is None: + metavar = self.type.name.upper() + + if self.nargs != 1: + metavar += "..." + + return metavar + + @t.overload + def get_default( + self, ctx: Context, call: "te.Literal[True]" = True + ) -> t.Optional[t.Any]: + ... + + @t.overload + def get_default( + self, ctx: Context, call: bool = ... + ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]: + ... + + def get_default( + self, ctx: Context, call: bool = True + ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]: + """Get the default for the parameter. Tries + :meth:`Context.lookup_default` first, then the local default. + + :param ctx: Current context. + :param call: If the default is a callable, call it. Disable to + return the callable instead. + + .. versionchanged:: 8.0.2 + Type casting is no longer performed when getting a default. + + .. versionchanged:: 8.0.1 + Type casting can fail in resilient parsing mode. Invalid + defaults will not prevent showing help text. + + .. versionchanged:: 8.0 + Looks at ``ctx.default_map`` first. + + .. versionchanged:: 8.0 + Added the ``call`` parameter. + """ + value = ctx.lookup_default(self.name, call=False) # type: ignore + + if value is None: + value = self.default + + if call and callable(value): + value = value() + + return value + + def add_to_parser(self, parser: OptionParser, ctx: Context) -> None: + raise NotImplementedError() + + def consume_value( + self, ctx: Context, opts: t.Mapping[str, t.Any] + ) -> t.Tuple[t.Any, ParameterSource]: + value = opts.get(self.name) # type: ignore + source = ParameterSource.COMMANDLINE + + if value is None: + value = self.value_from_envvar(ctx) + source = ParameterSource.ENVIRONMENT + + if value is None: + value = ctx.lookup_default(self.name) # type: ignore + source = ParameterSource.DEFAULT_MAP + + if value is None: + value = self.get_default(ctx) + source = ParameterSource.DEFAULT + + return value, source + + def type_cast_value(self, ctx: Context, value: t.Any) -> t.Any: + """Convert and validate a value against the option's + :attr:`type`, :attr:`multiple`, and :attr:`nargs`. + """ + if value is None: + return () if self.multiple or self.nargs == -1 else None + + def check_iter(value: t.Any) -> t.Iterator: + try: + return _check_iter(value) + except TypeError: + # This should only happen when passing in args manually, + # the parser should construct an iterable when parsing + # the command line. + raise BadParameter( + _("Value must be an iterable."), ctx=ctx, param=self + ) from None + + if self.nargs == 1 or self.type.is_composite: + convert: t.Callable[[t.Any], t.Any] = partial( + self.type, param=self, ctx=ctx + ) + elif self.nargs == -1: + + def convert(value: t.Any) -> t.Tuple: + return tuple(self.type(x, self, ctx) for x in check_iter(value)) + + else: # nargs > 1 + + def convert(value: t.Any) -> t.Tuple: + value = tuple(check_iter(value)) + + if len(value) != self.nargs: + raise BadParameter( + ngettext( + "Takes {nargs} values but 1 was given.", + "Takes {nargs} values but {len} were given.", + len(value), + ).format(nargs=self.nargs, len=len(value)), + ctx=ctx, + param=self, + ) + + return tuple(self.type(x, self, ctx) for x in value) + + if self.multiple: + return tuple(convert(x) for x in check_iter(value)) + + return convert(value) + + def value_is_missing(self, value: t.Any) -> bool: + if value is None: + return True + + if (self.nargs != 1 or self.multiple) and value == (): + return True + + return False + + def process_value(self, ctx: Context, value: t.Any) -> t.Any: + value = self.type_cast_value(ctx, value) + + if self.required and self.value_is_missing(value): + raise MissingParameter(ctx=ctx, param=self) + + if self.callback is not None: + value = self.callback(ctx, self, value) + + return value + + def resolve_envvar_value(self, ctx: Context) -> t.Optional[str]: + if self.envvar is None: + return None + + if isinstance(self.envvar, str): + rv = os.environ.get(self.envvar) + + if rv: + return rv + else: + for envvar in self.envvar: + rv = os.environ.get(envvar) + + if rv: + return rv + + return None + + def value_from_envvar(self, ctx: Context) -> t.Optional[t.Any]: + rv: t.Optional[t.Any] = self.resolve_envvar_value(ctx) + + if rv is not None and self.nargs != 1: + rv = self.type.split_envvar_value(rv) + + return rv + + def handle_parse_result( + self, ctx: Context, opts: t.Mapping[str, t.Any], args: t.List[str] + ) -> t.Tuple[t.Any, t.List[str]]: + with augment_usage_errors(ctx, param=self): + value, source = self.consume_value(ctx, opts) + ctx.set_parameter_source(self.name, source) # type: ignore + + try: + value = self.process_value(ctx, value) + except Exception: + if not ctx.resilient_parsing: + raise + + value = None + + if self.expose_value: + ctx.params[self.name] = value # type: ignore + + return value, args + + def get_help_record(self, ctx: Context) -> t.Optional[t.Tuple[str, str]]: + pass + + def get_usage_pieces(self, ctx: Context) -> t.List[str]: + return [] + + def get_error_hint(self, ctx: Context) -> str: + """Get a stringified version of the param for use in error messages to + indicate which param caused the error. + """ + hint_list = self.opts or [self.human_readable_name] + return " / ".join(f"'{x}'" for x in hint_list) + + def shell_complete(self, ctx: Context, incomplete: str) -> t.List["CompletionItem"]: + """Return a list of completions for the incomplete value. If a + ``shell_complete`` function was given during init, it is used. + Otherwise, the :attr:`type` + :meth:`~click.types.ParamType.shell_complete` function is used. + + :param ctx: Invocation context for this command. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + if self._custom_shell_complete is not None: + results = self._custom_shell_complete(ctx, self, incomplete) + + if results and isinstance(results[0], str): + from click.shell_completion import CompletionItem + + results = [CompletionItem(c) for c in results] + + return t.cast(t.List["CompletionItem"], results) + + return self.type.shell_complete(ctx, self, incomplete) + + +class Option(Parameter): + """Options are usually optional values on the command line and + have some extra features that arguments don't have. + + All other parameters are passed onwards to the parameter constructor. + + :param show_default: Show the default value for this option in its + help text. Values are not shown by default, unless + :attr:`Context.show_default` is ``True``. If this value is a + string, it shows that string in parentheses instead of the + actual value. This is particularly useful for dynamic options. + For single option boolean flags, the default remains hidden if + its value is ``False``. + :param show_envvar: Controls if an environment variable should be + shown on the help page. Normally, environment variables are not + shown. + :param prompt: If set to ``True`` or a non empty string then the + user will be prompted for input. If set to ``True`` the prompt + will be the option name capitalized. + :param confirmation_prompt: Prompt a second time to confirm the + value if it was prompted for. Can be set to a string instead of + ``True`` to customize the message. + :param prompt_required: If set to ``False``, the user will be + prompted for input only when the option was specified as a flag + without a value. + :param hide_input: If this is ``True`` then the input on the prompt + will be hidden from the user. This is useful for password input. + :param is_flag: forces this option to act as a flag. The default is + auto detection. + :param flag_value: which value should be used for this flag if it's + enabled. This is set to a boolean automatically if + the option string contains a slash to mark two options. + :param multiple: if this is set to `True` then the argument is accepted + multiple times and recorded. This is similar to ``nargs`` + in how it works but supports arbitrary number of + arguments. + :param count: this flag makes an option increment an integer. + :param allow_from_autoenv: if this is enabled then the value of this + parameter will be pulled from an environment + variable in case a prefix is defined on the + context. + :param help: the help string. + :param hidden: hide this option from help outputs. + + .. versionchanged:: 8.1.0 + Help text indentation is cleaned here instead of only in the + ``@option`` decorator. + + .. versionchanged:: 8.1.0 + The ``show_default`` parameter overrides + ``Context.show_default``. + + .. versionchanged:: 8.1.0 + The default of a single option boolean flag is not shown if the + default value is ``False``. + + .. versionchanged:: 8.0.1 + ``type`` is detected from ``flag_value`` if given. + """ + + param_type_name = "option" + + def __init__( + self, + param_decls: t.Optional[t.Sequence[str]] = None, + show_default: t.Union[bool, str, None] = None, + prompt: t.Union[bool, str] = False, + confirmation_prompt: t.Union[bool, str] = False, + prompt_required: bool = True, + hide_input: bool = False, + is_flag: t.Optional[bool] = None, + flag_value: t.Optional[t.Any] = None, + multiple: bool = False, + count: bool = False, + allow_from_autoenv: bool = True, + type: t.Optional[t.Union[types.ParamType, t.Any]] = None, + help: t.Optional[str] = None, + hidden: bool = False, + show_choices: bool = True, + show_envvar: bool = False, + **attrs: t.Any, + ) -> None: + if help: + help = inspect.cleandoc(help) + + default_is_missing = "default" not in attrs + super().__init__(param_decls, type=type, multiple=multiple, **attrs) + + if prompt is True: + if self.name is None: + raise TypeError("'name' is required with 'prompt=True'.") + + prompt_text: t.Optional[str] = self.name.replace("_", " ").capitalize() + elif prompt is False: + prompt_text = None + else: + prompt_text = prompt + + self.prompt = prompt_text + self.confirmation_prompt = confirmation_prompt + self.prompt_required = prompt_required + self.hide_input = hide_input + self.hidden = hidden + + # If prompt is enabled but not required, then the option can be + # used as a flag to indicate using prompt or flag_value. + self._flag_needs_value = self.prompt is not None and not self.prompt_required + + if is_flag is None: + if flag_value is not None: + # Implicitly a flag because flag_value was set. + is_flag = True + elif self._flag_needs_value: + # Not a flag, but when used as a flag it shows a prompt. + is_flag = False + else: + # Implicitly a flag because flag options were given. + is_flag = bool(self.secondary_opts) + elif is_flag is False and not self._flag_needs_value: + # Not a flag, and prompt is not enabled, can be used as a + # flag if flag_value is set. + self._flag_needs_value = flag_value is not None + + if is_flag and default_is_missing and not self.required: + self.default: t.Union[t.Any, t.Callable[[], t.Any]] = False + + if flag_value is None: + flag_value = not self.default + + if is_flag and type is None: + # Re-guess the type from the flag value instead of the + # default. + self.type = types.convert_type(None, flag_value) + + self.is_flag: bool = is_flag + self.is_bool_flag = is_flag and isinstance(self.type, types.BoolParamType) + self.flag_value: t.Any = flag_value + + # Counting + self.count = count + if count: + if type is None: + self.type = types.IntRange(min=0) + if default_is_missing: + self.default = 0 + + self.allow_from_autoenv = allow_from_autoenv + self.help = help + self.show_default = show_default + self.show_choices = show_choices + self.show_envvar = show_envvar + + if __debug__: + if self.nargs == -1: + raise TypeError("nargs=-1 is not supported for options.") + + if self.prompt and self.is_flag and not self.is_bool_flag: + raise TypeError("'prompt' is not valid for non-boolean flag.") + + if not self.is_bool_flag and self.secondary_opts: + raise TypeError("Secondary flag is not valid for non-boolean flag.") + + if self.is_bool_flag and self.hide_input and self.prompt is not None: + raise TypeError( + "'prompt' with 'hide_input' is not valid for boolean flag." + ) + + if self.count: + if self.multiple: + raise TypeError("'count' is not valid with 'multiple'.") + + if self.is_flag: + raise TypeError("'count' is not valid with 'is_flag'.") + + if self.multiple and self.is_flag: + raise TypeError("'multiple' is not valid with 'is_flag', use 'count'.") + + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict.update( + help=self.help, + prompt=self.prompt, + is_flag=self.is_flag, + flag_value=self.flag_value, + count=self.count, + hidden=self.hidden, + ) + return info_dict + + def _parse_decls( + self, decls: t.Sequence[str], expose_value: bool + ) -> t.Tuple[t.Optional[str], t.List[str], t.List[str]]: + opts = [] + secondary_opts = [] + name = None + possible_names = [] + + for decl in decls: + if decl.isidentifier(): + if name is not None: + raise TypeError(f"Name '{name}' defined twice") + name = decl + else: + split_char = ";" if decl[:1] == "/" else "/" + if split_char in decl: + first, second = decl.split(split_char, 1) + first = first.rstrip() + if first: + possible_names.append(split_opt(first)) + opts.append(first) + second = second.lstrip() + if second: + secondary_opts.append(second.lstrip()) + if first == second: + raise ValueError( + f"Boolean option {decl!r} cannot use the" + " same flag for true/false." + ) + else: + possible_names.append(split_opt(decl)) + opts.append(decl) + + if name is None and possible_names: + possible_names.sort(key=lambda x: -len(x[0])) # group long options first + name = possible_names[0][1].replace("-", "_").lower() + if not name.isidentifier(): + name = None + + if name is None: + if not expose_value: + return None, opts, secondary_opts + raise TypeError("Could not determine name for option") + + if not opts and not secondary_opts: + raise TypeError( + f"No options defined but a name was passed ({name})." + " Did you mean to declare an argument instead? Did" + f" you mean to pass '--{name}'?" + ) + + return name, opts, secondary_opts + + def add_to_parser(self, parser: OptionParser, ctx: Context) -> None: + if self.multiple: + action = "append" + elif self.count: + action = "count" + else: + action = "store" + + if self.is_flag: + action = f"{action}_const" + + if self.is_bool_flag and self.secondary_opts: + parser.add_option( + obj=self, opts=self.opts, dest=self.name, action=action, const=True + ) + parser.add_option( + obj=self, + opts=self.secondary_opts, + dest=self.name, + action=action, + const=False, + ) + else: + parser.add_option( + obj=self, + opts=self.opts, + dest=self.name, + action=action, + const=self.flag_value, + ) + else: + parser.add_option( + obj=self, + opts=self.opts, + dest=self.name, + action=action, + nargs=self.nargs, + ) + + def get_help_record(self, ctx: Context) -> t.Optional[t.Tuple[str, str]]: + if self.hidden: + return None + + any_prefix_is_slash = False + + def _write_opts(opts: t.Sequence[str]) -> str: + nonlocal any_prefix_is_slash + + rv, any_slashes = join_options(opts) + + if any_slashes: + any_prefix_is_slash = True + + if not self.is_flag and not self.count: + rv += f" {self.make_metavar()}" + + return rv + + rv = [_write_opts(self.opts)] + + if self.secondary_opts: + rv.append(_write_opts(self.secondary_opts)) + + help = self.help or "" + extra = [] + + if self.show_envvar: + envvar = self.envvar + + if envvar is None: + if ( + self.allow_from_autoenv + and ctx.auto_envvar_prefix is not None + and self.name is not None + ): + envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}" + + if envvar is not None: + var_str = ( + envvar + if isinstance(envvar, str) + else ", ".join(str(d) for d in envvar) + ) + extra.append(_("env var: {var}").format(var=var_str)) + + # Temporarily enable resilient parsing to avoid type casting + # failing for the default. Might be possible to extend this to + # help formatting in general. + resilient = ctx.resilient_parsing + ctx.resilient_parsing = True + + try: + default_value = self.get_default(ctx, call=False) + finally: + ctx.resilient_parsing = resilient + + show_default = False + show_default_is_str = False + + if self.show_default is not None: + if isinstance(self.show_default, str): + show_default_is_str = show_default = True + else: + show_default = self.show_default + elif ctx.show_default is not None: + show_default = ctx.show_default + + if show_default_is_str or (show_default and (default_value is not None)): + if show_default_is_str: + default_string = f"({self.show_default})" + elif isinstance(default_value, (list, tuple)): + default_string = ", ".join(str(d) for d in default_value) + elif inspect.isfunction(default_value): + default_string = _("(dynamic)") + elif self.is_bool_flag and self.secondary_opts: + # For boolean flags that have distinct True/False opts, + # use the opt without prefix instead of the value. + default_string = split_opt( + (self.opts if self.default else self.secondary_opts)[0] + )[1] + elif self.is_bool_flag and not self.secondary_opts and not default_value: + default_string = "" + else: + default_string = str(default_value) + + if default_string: + extra.append(_("default: {default}").format(default=default_string)) + + if ( + isinstance(self.type, types._NumberRangeBase) + # skip count with default range type + and not (self.count and self.type.min == 0 and self.type.max is None) + ): + range_str = self.type._describe_range() + + if range_str: + extra.append(range_str) + + if self.required: + extra.append(_("required")) + + if extra: + extra_str = "; ".join(extra) + help = f"{help} [{extra_str}]" if help else f"[{extra_str}]" + + return ("; " if any_prefix_is_slash else " / ").join(rv), help + + @t.overload + def get_default( + self, ctx: Context, call: "te.Literal[True]" = True + ) -> t.Optional[t.Any]: + ... + + @t.overload + def get_default( + self, ctx: Context, call: bool = ... + ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]: + ... + + def get_default( + self, ctx: Context, call: bool = True + ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]: + # If we're a non boolean flag our default is more complex because + # we need to look at all flags in the same group to figure out + # if we're the default one in which case we return the flag + # value as default. + if self.is_flag and not self.is_bool_flag: + for param in ctx.command.params: + if param.name == self.name and param.default: + return param.flag_value # type: ignore + + return None + + return super().get_default(ctx, call=call) + + def prompt_for_value(self, ctx: Context) -> t.Any: + """This is an alternative flow that can be activated in the full + value processing if a value does not exist. It will prompt the + user until a valid value exists and then returns the processed + value as result. + """ + assert self.prompt is not None + + # Calculate the default before prompting anything to be stable. + default = self.get_default(ctx) + + # If this is a prompt for a flag we need to handle this + # differently. + if self.is_bool_flag: + return confirm(self.prompt, default) + + return prompt( + self.prompt, + default=default, + type=self.type, + hide_input=self.hide_input, + show_choices=self.show_choices, + confirmation_prompt=self.confirmation_prompt, + value_proc=lambda x: self.process_value(ctx, x), + ) + + def resolve_envvar_value(self, ctx: Context) -> t.Optional[str]: + rv = super().resolve_envvar_value(ctx) + + if rv is not None: + return rv + + if ( + self.allow_from_autoenv + and ctx.auto_envvar_prefix is not None + and self.name is not None + ): + envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}" + rv = os.environ.get(envvar) + + if rv: + return rv + + return None + + def value_from_envvar(self, ctx: Context) -> t.Optional[t.Any]: + rv: t.Optional[t.Any] = self.resolve_envvar_value(ctx) + + if rv is None: + return None + + value_depth = (self.nargs != 1) + bool(self.multiple) + + if value_depth > 0: + rv = self.type.split_envvar_value(rv) + + if self.multiple and self.nargs != 1: + rv = batch(rv, self.nargs) + + return rv + + def consume_value( + self, ctx: Context, opts: t.Mapping[str, "Parameter"] + ) -> t.Tuple[t.Any, ParameterSource]: + value, source = super().consume_value(ctx, opts) + + # The parser will emit a sentinel value if the option can be + # given as a flag without a value. This is different from None + # to distinguish from the flag not being given at all. + if value is _flag_needs_value: + if self.prompt is not None and not ctx.resilient_parsing: + value = self.prompt_for_value(ctx) + source = ParameterSource.PROMPT + else: + value = self.flag_value + source = ParameterSource.COMMANDLINE + + elif ( + self.multiple + and value is not None + and any(v is _flag_needs_value for v in value) + ): + value = [self.flag_value if v is _flag_needs_value else v for v in value] + source = ParameterSource.COMMANDLINE + + # The value wasn't set, or used the param's default, prompt if + # prompting is enabled. + elif ( + source in {None, ParameterSource.DEFAULT} + and self.prompt is not None + and (self.required or self.prompt_required) + and not ctx.resilient_parsing + ): + value = self.prompt_for_value(ctx) + source = ParameterSource.PROMPT + + return value, source + + +class Argument(Parameter): + """Arguments are positional parameters to a command. They generally + provide fewer features than options but can have infinite ``nargs`` + and are required by default. + + All parameters are passed onwards to the parameter constructor. + """ + + param_type_name = "argument" + + def __init__( + self, + param_decls: t.Sequence[str], + required: t.Optional[bool] = None, + **attrs: t.Any, + ) -> None: + if required is None: + if attrs.get("default") is not None: + required = False + else: + required = attrs.get("nargs", 1) > 0 + + if "multiple" in attrs: + raise TypeError("__init__() got an unexpected keyword argument 'multiple'.") + + super().__init__(param_decls, required=required, **attrs) + + if __debug__: + if self.default is not None and self.nargs == -1: + raise TypeError("'default' is not supported for nargs=-1.") + + @property + def human_readable_name(self) -> str: + if self.metavar is not None: + return self.metavar + return self.name.upper() # type: ignore + + def make_metavar(self) -> str: + if self.metavar is not None: + return self.metavar + var = self.type.get_metavar(self) + if not var: + var = self.name.upper() # type: ignore + if not self.required: + var = f"[{var}]" + if self.nargs != 1: + var += "..." + return var + + def _parse_decls( + self, decls: t.Sequence[str], expose_value: bool + ) -> t.Tuple[t.Optional[str], t.List[str], t.List[str]]: + if not decls: + if not expose_value: + return None, [], [] + raise TypeError("Could not determine name for argument") + if len(decls) == 1: + name = arg = decls[0] + name = name.replace("-", "_").lower() + else: + raise TypeError( + "Arguments take exactly one parameter declaration, got" + f" {len(decls)}." + ) + return name, [arg], [] + + def get_usage_pieces(self, ctx: Context) -> t.List[str]: + return [self.make_metavar()] + + def get_error_hint(self, ctx: Context) -> str: + return f"'{self.make_metavar()}'" + + def add_to_parser(self, parser: OptionParser, ctx: Context) -> None: + parser.add_argument(dest=self.name, nargs=self.nargs, obj=self) diff --git a/.vtodo/Lib/site-packages/click/decorators.py b/.vtodo/Lib/site-packages/click/decorators.py new file mode 100644 index 0000000..28618dc --- /dev/null +++ b/.vtodo/Lib/site-packages/click/decorators.py @@ -0,0 +1,497 @@ +import inspect +import types +import typing as t +from functools import update_wrapper +from gettext import gettext as _ + +from .core import Argument +from .core import Command +from .core import Context +from .core import Group +from .core import Option +from .core import Parameter +from .globals import get_current_context +from .utils import echo + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) +FC = t.TypeVar("FC", bound=t.Union[t.Callable[..., t.Any], Command]) + + +def pass_context(f: F) -> F: + """Marks a callback as wanting to receive the current context + object as first argument. + """ + + def new_func(*args, **kwargs): # type: ignore + return f(get_current_context(), *args, **kwargs) + + return update_wrapper(t.cast(F, new_func), f) + + +def pass_obj(f: F) -> F: + """Similar to :func:`pass_context`, but only pass the object on the + context onwards (:attr:`Context.obj`). This is useful if that object + represents the state of a nested system. + """ + + def new_func(*args, **kwargs): # type: ignore + return f(get_current_context().obj, *args, **kwargs) + + return update_wrapper(t.cast(F, new_func), f) + + +def make_pass_decorator( + object_type: t.Type, ensure: bool = False +) -> "t.Callable[[F], F]": + """Given an object type this creates a decorator that will work + similar to :func:`pass_obj` but instead of passing the object of the + current context, it will find the innermost context of type + :func:`object_type`. + + This generates a decorator that works roughly like this:: + + from functools import update_wrapper + + def decorator(f): + @pass_context + def new_func(ctx, *args, **kwargs): + obj = ctx.find_object(object_type) + return ctx.invoke(f, obj, *args, **kwargs) + return update_wrapper(new_func, f) + return decorator + + :param object_type: the type of the object to pass. + :param ensure: if set to `True`, a new object will be created and + remembered on the context if it's not there yet. + """ + + def decorator(f: F) -> F: + def new_func(*args, **kwargs): # type: ignore + ctx = get_current_context() + + if ensure: + obj = ctx.ensure_object(object_type) + else: + obj = ctx.find_object(object_type) + + if obj is None: + raise RuntimeError( + "Managed to invoke callback without a context" + f" object of type {object_type.__name__!r}" + " existing." + ) + + return ctx.invoke(f, obj, *args, **kwargs) + + return update_wrapper(t.cast(F, new_func), f) + + return decorator + + +def pass_meta_key( + key: str, *, doc_description: t.Optional[str] = None +) -> "t.Callable[[F], F]": + """Create a decorator that passes a key from + :attr:`click.Context.meta` as the first argument to the decorated + function. + + :param key: Key in ``Context.meta`` to pass. + :param doc_description: Description of the object being passed, + inserted into the decorator's docstring. Defaults to "the 'key' + key from Context.meta". + + .. versionadded:: 8.0 + """ + + def decorator(f: F) -> F: + def new_func(*args, **kwargs): # type: ignore + ctx = get_current_context() + obj = ctx.meta[key] + return ctx.invoke(f, obj, *args, **kwargs) + + return update_wrapper(t.cast(F, new_func), f) + + if doc_description is None: + doc_description = f"the {key!r} key from :attr:`click.Context.meta`" + + decorator.__doc__ = ( + f"Decorator that passes {doc_description} as the first argument" + " to the decorated function." + ) + return decorator + + +CmdType = t.TypeVar("CmdType", bound=Command) + + +@t.overload +def command( + __func: t.Callable[..., t.Any], +) -> Command: + ... + + +@t.overload +def command( + name: t.Optional[str] = None, + **attrs: t.Any, +) -> t.Callable[..., Command]: + ... + + +@t.overload +def command( + name: t.Optional[str] = None, + cls: t.Type[CmdType] = ..., + **attrs: t.Any, +) -> t.Callable[..., CmdType]: + ... + + +def command( + name: t.Union[str, t.Callable[..., t.Any], None] = None, + cls: t.Optional[t.Type[Command]] = None, + **attrs: t.Any, +) -> t.Union[Command, t.Callable[..., Command]]: + r"""Creates a new :class:`Command` and uses the decorated function as + callback. This will also automatically attach all decorated + :func:`option`\s and :func:`argument`\s as parameters to the command. + + The name of the command defaults to the name of the function with + underscores replaced by dashes. If you want to change that, you can + pass the intended name as the first argument. + + All keyword arguments are forwarded to the underlying command class. + For the ``params`` argument, any decorated params are appended to + the end of the list. + + Once decorated the function turns into a :class:`Command` instance + that can be invoked as a command line utility or be attached to a + command :class:`Group`. + + :param name: the name of the command. This defaults to the function + name with underscores replaced by dashes. + :param cls: the command class to instantiate. This defaults to + :class:`Command`. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. + + .. versionchanged:: 8.1 + The ``params`` argument can be used. Decorated params are + appended to the end of the list. + """ + + func: t.Optional[t.Callable[..., t.Any]] = None + + if callable(name): + func = name + name = None + assert cls is None, "Use 'command(cls=cls)(callable)' to specify a class." + assert not attrs, "Use 'command(**kwargs)(callable)' to provide arguments." + + if cls is None: + cls = Command + + def decorator(f: t.Callable[..., t.Any]) -> Command: + if isinstance(f, Command): + raise TypeError("Attempted to convert a callback into a command twice.") + + attr_params = attrs.pop("params", None) + params = attr_params if attr_params is not None else [] + + try: + decorator_params = f.__click_params__ # type: ignore + except AttributeError: + pass + else: + del f.__click_params__ # type: ignore + params.extend(reversed(decorator_params)) + + if attrs.get("help") is None: + attrs["help"] = f.__doc__ + + cmd = cls( # type: ignore[misc] + name=name or f.__name__.lower().replace("_", "-"), # type: ignore[arg-type] + callback=f, + params=params, + **attrs, + ) + cmd.__doc__ = f.__doc__ + return cmd + + if func is not None: + return decorator(func) + + return decorator + + +@t.overload +def group( + __func: t.Callable[..., t.Any], +) -> Group: + ... + + +@t.overload +def group( + name: t.Optional[str] = None, + **attrs: t.Any, +) -> t.Callable[[F], Group]: + ... + + +def group( + name: t.Union[str, t.Callable[..., t.Any], None] = None, **attrs: t.Any +) -> t.Union[Group, t.Callable[[F], Group]]: + """Creates a new :class:`Group` with a function as callback. This + works otherwise the same as :func:`command` just that the `cls` + parameter is set to :class:`Group`. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. + """ + if attrs.get("cls") is None: + attrs["cls"] = Group + + if callable(name): + grp: t.Callable[[F], Group] = t.cast(Group, command(**attrs)) + return grp(name) + + return t.cast(Group, command(name, **attrs)) + + +def _param_memo(f: FC, param: Parameter) -> None: + if isinstance(f, Command): + f.params.append(param) + else: + if not hasattr(f, "__click_params__"): + f.__click_params__ = [] # type: ignore + + f.__click_params__.append(param) # type: ignore + + +def argument(*param_decls: str, **attrs: t.Any) -> t.Callable[[FC], FC]: + """Attaches an argument to the command. All positional arguments are + passed as parameter declarations to :class:`Argument`; all keyword + arguments are forwarded unchanged (except ``cls``). + This is equivalent to creating an :class:`Argument` instance manually + and attaching it to the :attr:`Command.params` list. + + :param cls: the argument class to instantiate. This defaults to + :class:`Argument`. + """ + + def decorator(f: FC) -> FC: + ArgumentClass = attrs.pop("cls", None) or Argument + _param_memo(f, ArgumentClass(param_decls, **attrs)) + return f + + return decorator + + +def option(*param_decls: str, **attrs: t.Any) -> t.Callable[[FC], FC]: + """Attaches an option to the command. All positional arguments are + passed as parameter declarations to :class:`Option`; all keyword + arguments are forwarded unchanged (except ``cls``). + This is equivalent to creating an :class:`Option` instance manually + and attaching it to the :attr:`Command.params` list. + + :param cls: the option class to instantiate. This defaults to + :class:`Option`. + """ + + def decorator(f: FC) -> FC: + # Issue 926, copy attrs, so pre-defined options can re-use the same cls= + option_attrs = attrs.copy() + OptionClass = option_attrs.pop("cls", None) or Option + _param_memo(f, OptionClass(param_decls, **option_attrs)) + return f + + return decorator + + +def confirmation_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: + """Add a ``--yes`` option which shows a prompt before continuing if + not passed. If the prompt is declined, the program will exit. + + :param param_decls: One or more option names. Defaults to the single + value ``"--yes"``. + :param kwargs: Extra arguments are passed to :func:`option`. + """ + + def callback(ctx: Context, param: Parameter, value: bool) -> None: + if not value: + ctx.abort() + + if not param_decls: + param_decls = ("--yes",) + + kwargs.setdefault("is_flag", True) + kwargs.setdefault("callback", callback) + kwargs.setdefault("expose_value", False) + kwargs.setdefault("prompt", "Do you want to continue?") + kwargs.setdefault("help", "Confirm the action without prompting.") + return option(*param_decls, **kwargs) + + +def password_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: + """Add a ``--password`` option which prompts for a password, hiding + input and asking to enter the value again for confirmation. + + :param param_decls: One or more option names. Defaults to the single + value ``"--password"``. + :param kwargs: Extra arguments are passed to :func:`option`. + """ + if not param_decls: + param_decls = ("--password",) + + kwargs.setdefault("prompt", True) + kwargs.setdefault("confirmation_prompt", True) + kwargs.setdefault("hide_input", True) + return option(*param_decls, **kwargs) + + +def version_option( + version: t.Optional[str] = None, + *param_decls: str, + package_name: t.Optional[str] = None, + prog_name: t.Optional[str] = None, + message: t.Optional[str] = None, + **kwargs: t.Any, +) -> t.Callable[[FC], FC]: + """Add a ``--version`` option which immediately prints the version + number and exits the program. + + If ``version`` is not provided, Click will try to detect it using + :func:`importlib.metadata.version` to get the version for the + ``package_name``. On Python < 3.8, the ``importlib_metadata`` + backport must be installed. + + If ``package_name`` is not provided, Click will try to detect it by + inspecting the stack frames. This will be used to detect the + version, so it must match the name of the installed package. + + :param version: The version number to show. If not provided, Click + will try to detect it. + :param param_decls: One or more option names. Defaults to the single + value ``"--version"``. + :param package_name: The package name to detect the version from. If + not provided, Click will try to detect it. + :param prog_name: The name of the CLI to show in the message. If not + provided, it will be detected from the command. + :param message: The message to show. The values ``%(prog)s``, + ``%(package)s``, and ``%(version)s`` are available. Defaults to + ``"%(prog)s, version %(version)s"``. + :param kwargs: Extra arguments are passed to :func:`option`. + :raise RuntimeError: ``version`` could not be detected. + + .. versionchanged:: 8.0 + Add the ``package_name`` parameter, and the ``%(package)s`` + value for messages. + + .. versionchanged:: 8.0 + Use :mod:`importlib.metadata` instead of ``pkg_resources``. The + version is detected based on the package name, not the entry + point name. The Python package name must match the installed + package name, or be passed with ``package_name=``. + """ + if message is None: + message = _("%(prog)s, version %(version)s") + + if version is None and package_name is None: + frame = inspect.currentframe() + f_back = frame.f_back if frame is not None else None + f_globals = f_back.f_globals if f_back is not None else None + # break reference cycle + # https://docs.python.org/3/library/inspect.html#the-interpreter-stack + del frame + + if f_globals is not None: + package_name = f_globals.get("__name__") + + if package_name == "__main__": + package_name = f_globals.get("__package__") + + if package_name: + package_name = package_name.partition(".")[0] + + def callback(ctx: Context, param: Parameter, value: bool) -> None: + if not value or ctx.resilient_parsing: + return + + nonlocal prog_name + nonlocal version + + if prog_name is None: + prog_name = ctx.find_root().info_name + + if version is None and package_name is not None: + metadata: t.Optional[types.ModuleType] + + try: + from importlib import metadata # type: ignore + except ImportError: + # Python < 3.8 + import importlib_metadata as metadata # type: ignore + + try: + version = metadata.version(package_name) # type: ignore + except metadata.PackageNotFoundError: # type: ignore + raise RuntimeError( + f"{package_name!r} is not installed. Try passing" + " 'package_name' instead." + ) from None + + if version is None: + raise RuntimeError( + f"Could not determine the version for {package_name!r} automatically." + ) + + echo( + t.cast(str, message) + % {"prog": prog_name, "package": package_name, "version": version}, + color=ctx.color, + ) + ctx.exit() + + if not param_decls: + param_decls = ("--version",) + + kwargs.setdefault("is_flag", True) + kwargs.setdefault("expose_value", False) + kwargs.setdefault("is_eager", True) + kwargs.setdefault("help", _("Show the version and exit.")) + kwargs["callback"] = callback + return option(*param_decls, **kwargs) + + +def help_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: + """Add a ``--help`` option which immediately prints the help page + and exits the program. + + This is usually unnecessary, as the ``--help`` option is added to + each command automatically unless ``add_help_option=False`` is + passed. + + :param param_decls: One or more option names. Defaults to the single + value ``"--help"``. + :param kwargs: Extra arguments are passed to :func:`option`. + """ + + def callback(ctx: Context, param: Parameter, value: bool) -> None: + if not value or ctx.resilient_parsing: + return + + echo(ctx.get_help(), color=ctx.color) + ctx.exit() + + if not param_decls: + param_decls = ("--help",) + + kwargs.setdefault("is_flag", True) + kwargs.setdefault("expose_value", False) + kwargs.setdefault("is_eager", True) + kwargs.setdefault("help", _("Show this message and exit.")) + kwargs["callback"] = callback + return option(*param_decls, **kwargs) diff --git a/.vtodo/Lib/site-packages/click/exceptions.py b/.vtodo/Lib/site-packages/click/exceptions.py new file mode 100644 index 0000000..9e20b3e --- /dev/null +++ b/.vtodo/Lib/site-packages/click/exceptions.py @@ -0,0 +1,287 @@ +import os +import typing as t +from gettext import gettext as _ +from gettext import ngettext + +from ._compat import get_text_stderr +from .utils import echo + +if t.TYPE_CHECKING: + from .core import Context + from .core import Parameter + + +def _join_param_hints( + param_hint: t.Optional[t.Union[t.Sequence[str], str]] +) -> t.Optional[str]: + if param_hint is not None and not isinstance(param_hint, str): + return " / ".join(repr(x) for x in param_hint) + + return param_hint + + +class ClickException(Exception): + """An exception that Click can handle and show to the user.""" + + #: The exit code for this exception. + exit_code = 1 + + def __init__(self, message: str) -> None: + super().__init__(message) + self.message = message + + def format_message(self) -> str: + return self.message + + def __str__(self) -> str: + return self.message + + def show(self, file: t.Optional[t.IO] = None) -> None: + if file is None: + file = get_text_stderr() + + echo(_("Error: {message}").format(message=self.format_message()), file=file) + + +class UsageError(ClickException): + """An internal exception that signals a usage error. This typically + aborts any further handling. + + :param message: the error message to display. + :param ctx: optionally the context that caused this error. Click will + fill in the context automatically in some situations. + """ + + exit_code = 2 + + def __init__(self, message: str, ctx: t.Optional["Context"] = None) -> None: + super().__init__(message) + self.ctx = ctx + self.cmd = self.ctx.command if self.ctx else None + + def show(self, file: t.Optional[t.IO] = None) -> None: + if file is None: + file = get_text_stderr() + color = None + hint = "" + if ( + self.ctx is not None + and self.ctx.command.get_help_option(self.ctx) is not None + ): + hint = _("Try '{command} {option}' for help.").format( + command=self.ctx.command_path, option=self.ctx.help_option_names[0] + ) + hint = f"{hint}\n" + if self.ctx is not None: + color = self.ctx.color + echo(f"{self.ctx.get_usage()}\n{hint}", file=file, color=color) + echo( + _("Error: {message}").format(message=self.format_message()), + file=file, + color=color, + ) + + +class BadParameter(UsageError): + """An exception that formats out a standardized error message for a + bad parameter. This is useful when thrown from a callback or type as + Click will attach contextual information to it (for instance, which + parameter it is). + + .. versionadded:: 2.0 + + :param param: the parameter object that caused this error. This can + be left out, and Click will attach this info itself + if possible. + :param param_hint: a string that shows up as parameter name. This + can be used as alternative to `param` in cases + where custom validation should happen. If it is + a string it's used as such, if it's a list then + each item is quoted and separated. + """ + + def __init__( + self, + message: str, + ctx: t.Optional["Context"] = None, + param: t.Optional["Parameter"] = None, + param_hint: t.Optional[str] = None, + ) -> None: + super().__init__(message, ctx) + self.param = param + self.param_hint = param_hint + + def format_message(self) -> str: + if self.param_hint is not None: + param_hint = self.param_hint + elif self.param is not None: + param_hint = self.param.get_error_hint(self.ctx) # type: ignore + else: + return _("Invalid value: {message}").format(message=self.message) + + return _("Invalid value for {param_hint}: {message}").format( + param_hint=_join_param_hints(param_hint), message=self.message + ) + + +class MissingParameter(BadParameter): + """Raised if click required an option or argument but it was not + provided when invoking the script. + + .. versionadded:: 4.0 + + :param param_type: a string that indicates the type of the parameter. + The default is to inherit the parameter type from + the given `param`. Valid values are ``'parameter'``, + ``'option'`` or ``'argument'``. + """ + + def __init__( + self, + message: t.Optional[str] = None, + ctx: t.Optional["Context"] = None, + param: t.Optional["Parameter"] = None, + param_hint: t.Optional[str] = None, + param_type: t.Optional[str] = None, + ) -> None: + super().__init__(message or "", ctx, param, param_hint) + self.param_type = param_type + + def format_message(self) -> str: + if self.param_hint is not None: + param_hint: t.Optional[str] = self.param_hint + elif self.param is not None: + param_hint = self.param.get_error_hint(self.ctx) # type: ignore + else: + param_hint = None + + param_hint = _join_param_hints(param_hint) + param_hint = f" {param_hint}" if param_hint else "" + + param_type = self.param_type + if param_type is None and self.param is not None: + param_type = self.param.param_type_name + + msg = self.message + if self.param is not None: + msg_extra = self.param.type.get_missing_message(self.param) + if msg_extra: + if msg: + msg += f". {msg_extra}" + else: + msg = msg_extra + + msg = f" {msg}" if msg else "" + + # Translate param_type for known types. + if param_type == "argument": + missing = _("Missing argument") + elif param_type == "option": + missing = _("Missing option") + elif param_type == "parameter": + missing = _("Missing parameter") + else: + missing = _("Missing {param_type}").format(param_type=param_type) + + return f"{missing}{param_hint}.{msg}" + + def __str__(self) -> str: + if not self.message: + param_name = self.param.name if self.param else None + return _("Missing parameter: {param_name}").format(param_name=param_name) + else: + return self.message + + +class NoSuchOption(UsageError): + """Raised if click attempted to handle an option that does not + exist. + + .. versionadded:: 4.0 + """ + + def __init__( + self, + option_name: str, + message: t.Optional[str] = None, + possibilities: t.Optional[t.Sequence[str]] = None, + ctx: t.Optional["Context"] = None, + ) -> None: + if message is None: + message = _("No such option: {name}").format(name=option_name) + + super().__init__(message, ctx) + self.option_name = option_name + self.possibilities = possibilities + + def format_message(self) -> str: + if not self.possibilities: + return self.message + + possibility_str = ", ".join(sorted(self.possibilities)) + suggest = ngettext( + "Did you mean {possibility}?", + "(Possible options: {possibilities})", + len(self.possibilities), + ).format(possibility=possibility_str, possibilities=possibility_str) + return f"{self.message} {suggest}" + + +class BadOptionUsage(UsageError): + """Raised if an option is generally supplied but the use of the option + was incorrect. This is for instance raised if the number of arguments + for an option is not correct. + + .. versionadded:: 4.0 + + :param option_name: the name of the option being used incorrectly. + """ + + def __init__( + self, option_name: str, message: str, ctx: t.Optional["Context"] = None + ) -> None: + super().__init__(message, ctx) + self.option_name = option_name + + +class BadArgumentUsage(UsageError): + """Raised if an argument is generally supplied but the use of the argument + was incorrect. This is for instance raised if the number of values + for an argument is not correct. + + .. versionadded:: 6.0 + """ + + +class FileError(ClickException): + """Raised if a file cannot be opened.""" + + def __init__(self, filename: str, hint: t.Optional[str] = None) -> None: + if hint is None: + hint = _("unknown error") + + super().__init__(hint) + self.ui_filename = os.fsdecode(filename) + self.filename = filename + + def format_message(self) -> str: + return _("Could not open file {filename!r}: {message}").format( + filename=self.ui_filename, message=self.message + ) + + +class Abort(RuntimeError): + """An internal signalling exception that signals Click to abort.""" + + +class Exit(RuntimeError): + """An exception that indicates that the application should exit with some + status code. + + :param code: the status code to exit with. + """ + + __slots__ = ("exit_code",) + + def __init__(self, code: int = 0) -> None: + self.exit_code = code diff --git a/.vtodo/Lib/site-packages/click/formatting.py b/.vtodo/Lib/site-packages/click/formatting.py new file mode 100644 index 0000000..ddd2a2f --- /dev/null +++ b/.vtodo/Lib/site-packages/click/formatting.py @@ -0,0 +1,301 @@ +import typing as t +from contextlib import contextmanager +from gettext import gettext as _ + +from ._compat import term_len +from .parser import split_opt + +# Can force a width. This is used by the test system +FORCED_WIDTH: t.Optional[int] = None + + +def measure_table(rows: t.Iterable[t.Tuple[str, str]]) -> t.Tuple[int, ...]: + widths: t.Dict[int, int] = {} + + for row in rows: + for idx, col in enumerate(row): + widths[idx] = max(widths.get(idx, 0), term_len(col)) + + return tuple(y for x, y in sorted(widths.items())) + + +def iter_rows( + rows: t.Iterable[t.Tuple[str, str]], col_count: int +) -> t.Iterator[t.Tuple[str, ...]]: + for row in rows: + yield row + ("",) * (col_count - len(row)) + + +def wrap_text( + text: str, + width: int = 78, + initial_indent: str = "", + subsequent_indent: str = "", + preserve_paragraphs: bool = False, +) -> str: + """A helper function that intelligently wraps text. By default, it + assumes that it operates on a single paragraph of text but if the + `preserve_paragraphs` parameter is provided it will intelligently + handle paragraphs (defined by two empty lines). + + If paragraphs are handled, a paragraph can be prefixed with an empty + line containing the ``\\b`` character (``\\x08``) to indicate that + no rewrapping should happen in that block. + + :param text: the text that should be rewrapped. + :param width: the maximum width for the text. + :param initial_indent: the initial indent that should be placed on the + first line as a string. + :param subsequent_indent: the indent string that should be placed on + each consecutive line. + :param preserve_paragraphs: if this flag is set then the wrapping will + intelligently handle paragraphs. + """ + from ._textwrap import TextWrapper + + text = text.expandtabs() + wrapper = TextWrapper( + width, + initial_indent=initial_indent, + subsequent_indent=subsequent_indent, + replace_whitespace=False, + ) + if not preserve_paragraphs: + return wrapper.fill(text) + + p: t.List[t.Tuple[int, bool, str]] = [] + buf: t.List[str] = [] + indent = None + + def _flush_par() -> None: + if not buf: + return + if buf[0].strip() == "\b": + p.append((indent or 0, True, "\n".join(buf[1:]))) + else: + p.append((indent or 0, False, " ".join(buf))) + del buf[:] + + for line in text.splitlines(): + if not line: + _flush_par() + indent = None + else: + if indent is None: + orig_len = term_len(line) + line = line.lstrip() + indent = orig_len - term_len(line) + buf.append(line) + _flush_par() + + rv = [] + for indent, raw, text in p: + with wrapper.extra_indent(" " * indent): + if raw: + rv.append(wrapper.indent_only(text)) + else: + rv.append(wrapper.fill(text)) + + return "\n\n".join(rv) + + +class HelpFormatter: + """This class helps with formatting text-based help pages. It's + usually just needed for very special internal cases, but it's also + exposed so that developers can write their own fancy outputs. + + At present, it always writes into memory. + + :param indent_increment: the additional increment for each level. + :param width: the width for the text. This defaults to the terminal + width clamped to a maximum of 78. + """ + + def __init__( + self, + indent_increment: int = 2, + width: t.Optional[int] = None, + max_width: t.Optional[int] = None, + ) -> None: + import shutil + + self.indent_increment = indent_increment + if max_width is None: + max_width = 80 + if width is None: + width = FORCED_WIDTH + if width is None: + width = max(min(shutil.get_terminal_size().columns, max_width) - 2, 50) + self.width = width + self.current_indent = 0 + self.buffer: t.List[str] = [] + + def write(self, string: str) -> None: + """Writes a unicode string into the internal buffer.""" + self.buffer.append(string) + + def indent(self) -> None: + """Increases the indentation.""" + self.current_indent += self.indent_increment + + def dedent(self) -> None: + """Decreases the indentation.""" + self.current_indent -= self.indent_increment + + def write_usage( + self, prog: str, args: str = "", prefix: t.Optional[str] = None + ) -> None: + """Writes a usage line into the buffer. + + :param prog: the program name. + :param args: whitespace separated list of arguments. + :param prefix: The prefix for the first line. Defaults to + ``"Usage: "``. + """ + if prefix is None: + prefix = f"{_('Usage:')} " + + usage_prefix = f"{prefix:>{self.current_indent}}{prog} " + text_width = self.width - self.current_indent + + if text_width >= (term_len(usage_prefix) + 20): + # The arguments will fit to the right of the prefix. + indent = " " * term_len(usage_prefix) + self.write( + wrap_text( + args, + text_width, + initial_indent=usage_prefix, + subsequent_indent=indent, + ) + ) + else: + # The prefix is too long, put the arguments on the next line. + self.write(usage_prefix) + self.write("\n") + indent = " " * (max(self.current_indent, term_len(prefix)) + 4) + self.write( + wrap_text( + args, text_width, initial_indent=indent, subsequent_indent=indent + ) + ) + + self.write("\n") + + def write_heading(self, heading: str) -> None: + """Writes a heading into the buffer.""" + self.write(f"{'':>{self.current_indent}}{heading}:\n") + + def write_paragraph(self) -> None: + """Writes a paragraph into the buffer.""" + if self.buffer: + self.write("\n") + + def write_text(self, text: str) -> None: + """Writes re-indented text into the buffer. This rewraps and + preserves paragraphs. + """ + indent = " " * self.current_indent + self.write( + wrap_text( + text, + self.width, + initial_indent=indent, + subsequent_indent=indent, + preserve_paragraphs=True, + ) + ) + self.write("\n") + + def write_dl( + self, + rows: t.Sequence[t.Tuple[str, str]], + col_max: int = 30, + col_spacing: int = 2, + ) -> None: + """Writes a definition list into the buffer. This is how options + and commands are usually formatted. + + :param rows: a list of two item tuples for the terms and values. + :param col_max: the maximum width of the first column. + :param col_spacing: the number of spaces between the first and + second column. + """ + rows = list(rows) + widths = measure_table(rows) + if len(widths) != 2: + raise TypeError("Expected two columns for definition list") + + first_col = min(widths[0], col_max) + col_spacing + + for first, second in iter_rows(rows, len(widths)): + self.write(f"{'':>{self.current_indent}}{first}") + if not second: + self.write("\n") + continue + if term_len(first) <= first_col - col_spacing: + self.write(" " * (first_col - term_len(first))) + else: + self.write("\n") + self.write(" " * (first_col + self.current_indent)) + + text_width = max(self.width - first_col - 2, 10) + wrapped_text = wrap_text(second, text_width, preserve_paragraphs=True) + lines = wrapped_text.splitlines() + + if lines: + self.write(f"{lines[0]}\n") + + for line in lines[1:]: + self.write(f"{'':>{first_col + self.current_indent}}{line}\n") + else: + self.write("\n") + + @contextmanager + def section(self, name: str) -> t.Iterator[None]: + """Helpful context manager that writes a paragraph, a heading, + and the indents. + + :param name: the section name that is written as heading. + """ + self.write_paragraph() + self.write_heading(name) + self.indent() + try: + yield + finally: + self.dedent() + + @contextmanager + def indentation(self) -> t.Iterator[None]: + """A context manager that increases the indentation.""" + self.indent() + try: + yield + finally: + self.dedent() + + def getvalue(self) -> str: + """Returns the buffer contents.""" + return "".join(self.buffer) + + +def join_options(options: t.Sequence[str]) -> t.Tuple[str, bool]: + """Given a list of option strings this joins them in the most appropriate + way and returns them in the form ``(formatted_string, + any_prefix_is_slash)`` where the second item in the tuple is a flag that + indicates if any of the option prefixes was a slash. + """ + rv = [] + any_prefix_is_slash = False + + for opt in options: + prefix = split_opt(opt)[0] + + if prefix == "/": + any_prefix_is_slash = True + + rv.append((len(prefix), opt)) + + rv.sort(key=lambda x: x[0]) + return ", ".join(x[1] for x in rv), any_prefix_is_slash diff --git a/.vtodo/Lib/site-packages/click/globals.py b/.vtodo/Lib/site-packages/click/globals.py new file mode 100644 index 0000000..480058f --- /dev/null +++ b/.vtodo/Lib/site-packages/click/globals.py @@ -0,0 +1,68 @@ +import typing as t +from threading import local + +if t.TYPE_CHECKING: + import typing_extensions as te + from .core import Context + +_local = local() + + +@t.overload +def get_current_context(silent: "te.Literal[False]" = False) -> "Context": + ... + + +@t.overload +def get_current_context(silent: bool = ...) -> t.Optional["Context"]: + ... + + +def get_current_context(silent: bool = False) -> t.Optional["Context"]: + """Returns the current click context. This can be used as a way to + access the current context object from anywhere. This is a more implicit + alternative to the :func:`pass_context` decorator. This function is + primarily useful for helpers such as :func:`echo` which might be + interested in changing its behavior based on the current context. + + To push the current context, :meth:`Context.scope` can be used. + + .. versionadded:: 5.0 + + :param silent: if set to `True` the return value is `None` if no context + is available. The default behavior is to raise a + :exc:`RuntimeError`. + """ + try: + return t.cast("Context", _local.stack[-1]) + except (AttributeError, IndexError) as e: + if not silent: + raise RuntimeError("There is no active click context.") from e + + return None + + +def push_context(ctx: "Context") -> None: + """Pushes a new context to the current stack.""" + _local.__dict__.setdefault("stack", []).append(ctx) + + +def pop_context() -> None: + """Removes the top level from the stack.""" + _local.stack.pop() + + +def resolve_color_default(color: t.Optional[bool] = None) -> t.Optional[bool]: + """Internal helper to get the default value of the color flag. If a + value is passed it's returned unchanged, otherwise it's looked up from + the current context. + """ + if color is not None: + return color + + ctx = get_current_context(silent=True) + + if ctx is not None: + return ctx.color + + return None diff --git a/.vtodo/Lib/site-packages/click/parser.py b/.vtodo/Lib/site-packages/click/parser.py new file mode 100644 index 0000000..2d5a2ed --- /dev/null +++ b/.vtodo/Lib/site-packages/click/parser.py @@ -0,0 +1,529 @@ +""" +This module started out as largely a copy paste from the stdlib's +optparse module with the features removed that we do not need from +optparse because we implement them in Click on a higher level (for +instance type handling, help formatting and a lot more). + +The plan is to remove more and more from here over time. + +The reason this is a different module and not optparse from the stdlib +is that there are differences in 2.x and 3.x about the error messages +generated and optparse in the stdlib uses gettext for no good reason +and might cause us issues. + +Click uses parts of optparse written by Gregory P. Ward and maintained +by the Python Software Foundation. This is limited to code in parser.py. + +Copyright 2001-2006 Gregory P. Ward. All rights reserved. +Copyright 2002-2006 Python Software Foundation. All rights reserved. +""" +# This code uses parts of optparse written by Gregory P. Ward and +# maintained by the Python Software Foundation. +# Copyright 2001-2006 Gregory P. Ward +# Copyright 2002-2006 Python Software Foundation +import typing as t +from collections import deque +from gettext import gettext as _ +from gettext import ngettext + +from .exceptions import BadArgumentUsage +from .exceptions import BadOptionUsage +from .exceptions import NoSuchOption +from .exceptions import UsageError + +if t.TYPE_CHECKING: + import typing_extensions as te + from .core import Argument as CoreArgument + from .core import Context + from .core import Option as CoreOption + from .core import Parameter as CoreParameter + +V = t.TypeVar("V") + +# Sentinel value that indicates an option was passed as a flag without a +# value but is not a flag option. Option.consume_value uses this to +# prompt or use the flag_value. +_flag_needs_value = object() + + +def _unpack_args( + args: t.Sequence[str], nargs_spec: t.Sequence[int] +) -> t.Tuple[t.Sequence[t.Union[str, t.Sequence[t.Optional[str]], None]], t.List[str]]: + """Given an iterable of arguments and an iterable of nargs specifications, + it returns a tuple with all the unpacked arguments at the first index + and all remaining arguments as the second. + + The nargs specification is the number of arguments that should be consumed + or `-1` to indicate that this position should eat up all the remainders. + + Missing items are filled with `None`. + """ + args = deque(args) + nargs_spec = deque(nargs_spec) + rv: t.List[t.Union[str, t.Tuple[t.Optional[str], ...], None]] = [] + spos: t.Optional[int] = None + + def _fetch(c: "te.Deque[V]") -> t.Optional[V]: + try: + if spos is None: + return c.popleft() + else: + return c.pop() + except IndexError: + return None + + while nargs_spec: + nargs = _fetch(nargs_spec) + + if nargs is None: + continue + + if nargs == 1: + rv.append(_fetch(args)) + elif nargs > 1: + x = [_fetch(args) for _ in range(nargs)] + + # If we're reversed, we're pulling in the arguments in reverse, + # so we need to turn them around. + if spos is not None: + x.reverse() + + rv.append(tuple(x)) + elif nargs < 0: + if spos is not None: + raise TypeError("Cannot have two nargs < 0") + + spos = len(rv) + rv.append(None) + + # spos is the position of the wildcard (star). If it's not `None`, + # we fill it with the remainder. + if spos is not None: + rv[spos] = tuple(args) + args = [] + rv[spos + 1 :] = reversed(rv[spos + 1 :]) + + return tuple(rv), list(args) + + +def split_opt(opt: str) -> t.Tuple[str, str]: + first = opt[:1] + if first.isalnum(): + return "", opt + if opt[1:2] == first: + return opt[:2], opt[2:] + return first, opt[1:] + + +def normalize_opt(opt: str, ctx: t.Optional["Context"]) -> str: + if ctx is None or ctx.token_normalize_func is None: + return opt + prefix, opt = split_opt(opt) + return f"{prefix}{ctx.token_normalize_func(opt)}" + + +def split_arg_string(string: str) -> t.List[str]: + """Split an argument string as with :func:`shlex.split`, but don't + fail if the string is incomplete. Ignores a missing closing quote or + incomplete escape sequence and uses the partial token as-is. + + .. code-block:: python + + split_arg_string("example 'my file") + ["example", "my file"] + + split_arg_string("example my\\") + ["example", "my"] + + :param string: String to split. + """ + import shlex + + lex = shlex.shlex(string, posix=True) + lex.whitespace_split = True + lex.commenters = "" + out = [] + + try: + for token in lex: + out.append(token) + except ValueError: + # Raised when end-of-string is reached in an invalid state. Use + # the partial token as-is. The quote or escape character is in + # lex.state, not lex.token. + out.append(lex.token) + + return out + + +class Option: + def __init__( + self, + obj: "CoreOption", + opts: t.Sequence[str], + dest: t.Optional[str], + action: t.Optional[str] = None, + nargs: int = 1, + const: t.Optional[t.Any] = None, + ): + self._short_opts = [] + self._long_opts = [] + self.prefixes = set() + + for opt in opts: + prefix, value = split_opt(opt) + if not prefix: + raise ValueError(f"Invalid start character for option ({opt})") + self.prefixes.add(prefix[0]) + if len(prefix) == 1 and len(value) == 1: + self._short_opts.append(opt) + else: + self._long_opts.append(opt) + self.prefixes.add(prefix) + + if action is None: + action = "store" + + self.dest = dest + self.action = action + self.nargs = nargs + self.const = const + self.obj = obj + + @property + def takes_value(self) -> bool: + return self.action in ("store", "append") + + def process(self, value: str, state: "ParsingState") -> None: + if self.action == "store": + state.opts[self.dest] = value # type: ignore + elif self.action == "store_const": + state.opts[self.dest] = self.const # type: ignore + elif self.action == "append": + state.opts.setdefault(self.dest, []).append(value) # type: ignore + elif self.action == "append_const": + state.opts.setdefault(self.dest, []).append(self.const) # type: ignore + elif self.action == "count": + state.opts[self.dest] = state.opts.get(self.dest, 0) + 1 # type: ignore + else: + raise ValueError(f"unknown action '{self.action}'") + state.order.append(self.obj) + + +class Argument: + def __init__(self, obj: "CoreArgument", dest: t.Optional[str], nargs: int = 1): + self.dest = dest + self.nargs = nargs + self.obj = obj + + def process( + self, + value: t.Union[t.Optional[str], t.Sequence[t.Optional[str]]], + state: "ParsingState", + ) -> None: + if self.nargs > 1: + assert value is not None + holes = sum(1 for x in value if x is None) + if holes == len(value): + value = None + elif holes != 0: + raise BadArgumentUsage( + _("Argument {name!r} takes {nargs} values.").format( + name=self.dest, nargs=self.nargs + ) + ) + + if self.nargs == -1 and self.obj.envvar is not None and value == (): + # Replace empty tuple with None so that a value from the + # environment may be tried. + value = None + + state.opts[self.dest] = value # type: ignore + state.order.append(self.obj) + + +class ParsingState: + def __init__(self, rargs: t.List[str]) -> None: + self.opts: t.Dict[str, t.Any] = {} + self.largs: t.List[str] = [] + self.rargs = rargs + self.order: t.List["CoreParameter"] = [] + + +class OptionParser: + """The option parser is an internal class that is ultimately used to + parse options and arguments. It's modelled after optparse and brings + a similar but vastly simplified API. It should generally not be used + directly as the high level Click classes wrap it for you. + + It's not nearly as extensible as optparse or argparse as it does not + implement features that are implemented on a higher level (such as + types or defaults). + + :param ctx: optionally the :class:`~click.Context` where this parser + should go with. + """ + + def __init__(self, ctx: t.Optional["Context"] = None) -> None: + #: The :class:`~click.Context` for this parser. This might be + #: `None` for some advanced use cases. + self.ctx = ctx + #: This controls how the parser deals with interspersed arguments. + #: If this is set to `False`, the parser will stop on the first + #: non-option. Click uses this to implement nested subcommands + #: safely. + self.allow_interspersed_args = True + #: This tells the parser how to deal with unknown options. By + #: default it will error out (which is sensible), but there is a + #: second mode where it will ignore it and continue processing + #: after shifting all the unknown options into the resulting args. + self.ignore_unknown_options = False + + if ctx is not None: + self.allow_interspersed_args = ctx.allow_interspersed_args + self.ignore_unknown_options = ctx.ignore_unknown_options + + self._short_opt: t.Dict[str, Option] = {} + self._long_opt: t.Dict[str, Option] = {} + self._opt_prefixes = {"-", "--"} + self._args: t.List[Argument] = [] + + def add_option( + self, + obj: "CoreOption", + opts: t.Sequence[str], + dest: t.Optional[str], + action: t.Optional[str] = None, + nargs: int = 1, + const: t.Optional[t.Any] = None, + ) -> None: + """Adds a new option named `dest` to the parser. The destination + is not inferred (unlike with optparse) and needs to be explicitly + provided. Action can be any of ``store``, ``store_const``, + ``append``, ``append_const`` or ``count``. + + The `obj` can be used to identify the option in the order list + that is returned from the parser. + """ + opts = [normalize_opt(opt, self.ctx) for opt in opts] + option = Option(obj, opts, dest, action=action, nargs=nargs, const=const) + self._opt_prefixes.update(option.prefixes) + for opt in option._short_opts: + self._short_opt[opt] = option + for opt in option._long_opts: + self._long_opt[opt] = option + + def add_argument( + self, obj: "CoreArgument", dest: t.Optional[str], nargs: int = 1 + ) -> None: + """Adds a positional argument named `dest` to the parser. + + The `obj` can be used to identify the option in the order list + that is returned from the parser. + """ + self._args.append(Argument(obj, dest=dest, nargs=nargs)) + + def parse_args( + self, args: t.List[str] + ) -> t.Tuple[t.Dict[str, t.Any], t.List[str], t.List["CoreParameter"]]: + """Parses positional arguments and returns ``(values, args, order)`` + for the parsed options and arguments as well as the leftover + arguments if there are any. The order is a list of objects as they + appear on the command line. If arguments appear multiple times they + will be memorized multiple times as well. + """ + state = ParsingState(args) + try: + self._process_args_for_options(state) + self._process_args_for_args(state) + except UsageError: + if self.ctx is None or not self.ctx.resilient_parsing: + raise + return state.opts, state.largs, state.order + + def _process_args_for_args(self, state: ParsingState) -> None: + pargs, args = _unpack_args( + state.largs + state.rargs, [x.nargs for x in self._args] + ) + + for idx, arg in enumerate(self._args): + arg.process(pargs[idx], state) + + state.largs = args + state.rargs = [] + + def _process_args_for_options(self, state: ParsingState) -> None: + while state.rargs: + arg = state.rargs.pop(0) + arglen = len(arg) + # Double dashes always handled explicitly regardless of what + # prefixes are valid. + if arg == "--": + return + elif arg[:1] in self._opt_prefixes and arglen > 1: + self._process_opts(arg, state) + elif self.allow_interspersed_args: + state.largs.append(arg) + else: + state.rargs.insert(0, arg) + return + + # Say this is the original argument list: + # [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)] + # ^ + # (we are about to process arg(i)). + # + # Then rargs is [arg(i), ..., arg(N-1)] and largs is a *subset* of + # [arg0, ..., arg(i-1)] (any options and their arguments will have + # been removed from largs). + # + # The while loop will usually consume 1 or more arguments per pass. + # If it consumes 1 (eg. arg is an option that takes no arguments), + # then after _process_arg() is done the situation is: + # + # largs = subset of [arg0, ..., arg(i)] + # rargs = [arg(i+1), ..., arg(N-1)] + # + # If allow_interspersed_args is false, largs will always be + # *empty* -- still a subset of [arg0, ..., arg(i-1)], but + # not a very interesting subset! + + def _match_long_opt( + self, opt: str, explicit_value: t.Optional[str], state: ParsingState + ) -> None: + if opt not in self._long_opt: + from difflib import get_close_matches + + possibilities = get_close_matches(opt, self._long_opt) + raise NoSuchOption(opt, possibilities=possibilities, ctx=self.ctx) + + option = self._long_opt[opt] + if option.takes_value: + # At this point it's safe to modify rargs by injecting the + # explicit value, because no exception is raised in this + # branch. This means that the inserted value will be fully + # consumed. + if explicit_value is not None: + state.rargs.insert(0, explicit_value) + + value = self._get_value_from_state(opt, option, state) + + elif explicit_value is not None: + raise BadOptionUsage( + opt, _("Option {name!r} does not take a value.").format(name=opt) + ) + + else: + value = None + + option.process(value, state) + + def _match_short_opt(self, arg: str, state: ParsingState) -> None: + stop = False + i = 1 + prefix = arg[0] + unknown_options = [] + + for ch in arg[1:]: + opt = normalize_opt(f"{prefix}{ch}", self.ctx) + option = self._short_opt.get(opt) + i += 1 + + if not option: + if self.ignore_unknown_options: + unknown_options.append(ch) + continue + raise NoSuchOption(opt, ctx=self.ctx) + if option.takes_value: + # Any characters left in arg? Pretend they're the + # next arg, and stop consuming characters of arg. + if i < len(arg): + state.rargs.insert(0, arg[i:]) + stop = True + + value = self._get_value_from_state(opt, option, state) + + else: + value = None + + option.process(value, state) + + if stop: + break + + # If we got any unknown options we re-combinate the string of the + # remaining options and re-attach the prefix, then report that + # to the state as new larg. This way there is basic combinatorics + # that can be achieved while still ignoring unknown arguments. + if self.ignore_unknown_options and unknown_options: + state.largs.append(f"{prefix}{''.join(unknown_options)}") + + def _get_value_from_state( + self, option_name: str, option: Option, state: ParsingState + ) -> t.Any: + nargs = option.nargs + + if len(state.rargs) < nargs: + if option.obj._flag_needs_value: + # Option allows omitting the value. + value = _flag_needs_value + else: + raise BadOptionUsage( + option_name, + ngettext( + "Option {name!r} requires an argument.", + "Option {name!r} requires {nargs} arguments.", + nargs, + ).format(name=option_name, nargs=nargs), + ) + elif nargs == 1: + next_rarg = state.rargs[0] + + if ( + option.obj._flag_needs_value + and isinstance(next_rarg, str) + and next_rarg[:1] in self._opt_prefixes + and len(next_rarg) > 1 + ): + # The next arg looks like the start of an option, don't + # use it as the value if omitting the value is allowed. + value = _flag_needs_value + else: + value = state.rargs.pop(0) + else: + value = tuple(state.rargs[:nargs]) + del state.rargs[:nargs] + + return value + + def _process_opts(self, arg: str, state: ParsingState) -> None: + explicit_value = None + # Long option handling happens in two parts. The first part is + # supporting explicitly attached values. In any case, we will try + # to long match the option first. + if "=" in arg: + long_opt, explicit_value = arg.split("=", 1) + else: + long_opt = arg + norm_long_opt = normalize_opt(long_opt, self.ctx) + + # At this point we will match the (assumed) long option through + # the long option matching code. Note that this allows options + # like "-foo" to be matched as long options. + try: + self._match_long_opt(norm_long_opt, explicit_value, state) + except NoSuchOption: + # At this point the long option matching failed, and we need + # to try with short options. However there is a special rule + # which says, that if we have a two character options prefix + # (applies to "--foo" for instance), we do not dispatch to the + # short option code and will instead raise the no option + # error. + if arg[:2] not in self._opt_prefixes: + self._match_short_opt(arg, state) + return + + if not self.ignore_unknown_options: + raise + + state.largs.append(arg) diff --git a/.vtodo/Lib/site-packages/click/py.typed b/.vtodo/Lib/site-packages/click/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/.vtodo/Lib/site-packages/click/shell_completion.py b/.vtodo/Lib/site-packages/click/shell_completion.py new file mode 100644 index 0000000..c17a8e6 --- /dev/null +++ b/.vtodo/Lib/site-packages/click/shell_completion.py @@ -0,0 +1,580 @@ +import os +import re +import typing as t +from gettext import gettext as _ + +from .core import Argument +from .core import BaseCommand +from .core import Context +from .core import MultiCommand +from .core import Option +from .core import Parameter +from .core import ParameterSource +from .parser import split_arg_string +from .utils import echo + + +def shell_complete( + cli: BaseCommand, + ctx_args: t.Dict[str, t.Any], + prog_name: str, + complete_var: str, + instruction: str, +) -> int: + """Perform shell completion for the given CLI program. + + :param cli: Command being called. + :param ctx_args: Extra arguments to pass to + ``cli.make_context``. + :param prog_name: Name of the executable in the shell. + :param complete_var: Name of the environment variable that holds + the completion instruction. + :param instruction: Value of ``complete_var`` with the completion + instruction and shell, in the form ``instruction_shell``. + :return: Status code to exit with. + """ + shell, _, instruction = instruction.partition("_") + comp_cls = get_completion_class(shell) + + if comp_cls is None: + return 1 + + comp = comp_cls(cli, ctx_args, prog_name, complete_var) + + if instruction == "source": + echo(comp.source()) + return 0 + + if instruction == "complete": + echo(comp.complete()) + return 0 + + return 1 + + +class CompletionItem: + """Represents a completion value and metadata about the value. The + default metadata is ``type`` to indicate special shell handling, + and ``help`` if a shell supports showing a help string next to the + value. + + Arbitrary parameters can be passed when creating the object, and + accessed using ``item.attr``. If an attribute wasn't passed, + accessing it returns ``None``. + + :param value: The completion suggestion. + :param type: Tells the shell script to provide special completion + support for the type. Click uses ``"dir"`` and ``"file"``. + :param help: String shown next to the value if supported. + :param kwargs: Arbitrary metadata. The built-in implementations + don't use this, but custom type completions paired with custom + shell support could use it. + """ + + __slots__ = ("value", "type", "help", "_info") + + def __init__( + self, + value: t.Any, + type: str = "plain", + help: t.Optional[str] = None, + **kwargs: t.Any, + ) -> None: + self.value = value + self.type = type + self.help = help + self._info = kwargs + + def __getattr__(self, name: str) -> t.Any: + return self._info.get(name) + + +# Only Bash >= 4.4 has the nosort option. +_SOURCE_BASH = """\ +%(complete_func)s() { + local IFS=$'\\n' + local response + + response=$(env COMP_WORDS="${COMP_WORDS[*]}" COMP_CWORD=$COMP_CWORD \ +%(complete_var)s=bash_complete $1) + + for completion in $response; do + IFS=',' read type value <<< "$completion" + + if [[ $type == 'dir' ]]; then + COMPREPLY=() + compopt -o dirnames + elif [[ $type == 'file' ]]; then + COMPREPLY=() + compopt -o default + elif [[ $type == 'plain' ]]; then + COMPREPLY+=($value) + fi + done + + return 0 +} + +%(complete_func)s_setup() { + complete -o nosort -F %(complete_func)s %(prog_name)s +} + +%(complete_func)s_setup; +""" + +_SOURCE_ZSH = """\ +#compdef %(prog_name)s + +%(complete_func)s() { + local -a completions + local -a completions_with_descriptions + local -a response + (( ! $+commands[%(prog_name)s] )) && return 1 + + response=("${(@f)$(env COMP_WORDS="${words[*]}" COMP_CWORD=$((CURRENT-1)) \ +%(complete_var)s=zsh_complete %(prog_name)s)}") + + for type key descr in ${response}; do + if [[ "$type" == "plain" ]]; then + if [[ "$descr" == "_" ]]; then + completions+=("$key") + else + completions_with_descriptions+=("$key":"$descr") + fi + elif [[ "$type" == "dir" ]]; then + _path_files -/ + elif [[ "$type" == "file" ]]; then + _path_files -f + fi + done + + if [ -n "$completions_with_descriptions" ]; then + _describe -V unsorted completions_with_descriptions -U + fi + + if [ -n "$completions" ]; then + compadd -U -V unsorted -a completions + fi +} + +compdef %(complete_func)s %(prog_name)s; +""" + +_SOURCE_FISH = """\ +function %(complete_func)s; + set -l response; + + for value in (env %(complete_var)s=fish_complete COMP_WORDS=(commandline -cp) \ +COMP_CWORD=(commandline -t) %(prog_name)s); + set response $response $value; + end; + + for completion in $response; + set -l metadata (string split "," $completion); + + if test $metadata[1] = "dir"; + __fish_complete_directories $metadata[2]; + else if test $metadata[1] = "file"; + __fish_complete_path $metadata[2]; + else if test $metadata[1] = "plain"; + echo $metadata[2]; + end; + end; +end; + +complete --no-files --command %(prog_name)s --arguments \ +"(%(complete_func)s)"; +""" + + +class ShellComplete: + """Base class for providing shell completion support. A subclass for + a given shell will override attributes and methods to implement the + completion instructions (``source`` and ``complete``). + + :param cli: Command being called. + :param prog_name: Name of the executable in the shell. + :param complete_var: Name of the environment variable that holds + the completion instruction. + + .. versionadded:: 8.0 + """ + + name: t.ClassVar[str] + """Name to register the shell as with :func:`add_completion_class`. + This is used in completion instructions (``{name}_source`` and + ``{name}_complete``). + """ + + source_template: t.ClassVar[str] + """Completion script template formatted by :meth:`source`. This must + be provided by subclasses. + """ + + def __init__( + self, + cli: BaseCommand, + ctx_args: t.Dict[str, t.Any], + prog_name: str, + complete_var: str, + ) -> None: + self.cli = cli + self.ctx_args = ctx_args + self.prog_name = prog_name + self.complete_var = complete_var + + @property + def func_name(self) -> str: + """The name of the shell function defined by the completion + script. + """ + safe_name = re.sub(r"\W*", "", self.prog_name.replace("-", "_"), re.ASCII) + return f"_{safe_name}_completion" + + def source_vars(self) -> t.Dict[str, t.Any]: + """Vars for formatting :attr:`source_template`. + + By default this provides ``complete_func``, ``complete_var``, + and ``prog_name``. + """ + return { + "complete_func": self.func_name, + "complete_var": self.complete_var, + "prog_name": self.prog_name, + } + + def source(self) -> str: + """Produce the shell script that defines the completion + function. By default this ``%``-style formats + :attr:`source_template` with the dict returned by + :meth:`source_vars`. + """ + return self.source_template % self.source_vars() + + def get_completion_args(self) -> t.Tuple[t.List[str], str]: + """Use the env vars defined by the shell script to return a + tuple of ``args, incomplete``. This must be implemented by + subclasses. + """ + raise NotImplementedError + + def get_completions( + self, args: t.List[str], incomplete: str + ) -> t.List[CompletionItem]: + """Determine the context and last complete command or parameter + from the complete args. Call that object's ``shell_complete`` + method to get the completions for the incomplete value. + + :param args: List of complete args before the incomplete value. + :param incomplete: Value being completed. May be empty. + """ + ctx = _resolve_context(self.cli, self.ctx_args, self.prog_name, args) + obj, incomplete = _resolve_incomplete(ctx, args, incomplete) + return obj.shell_complete(ctx, incomplete) + + def format_completion(self, item: CompletionItem) -> str: + """Format a completion item into the form recognized by the + shell script. This must be implemented by subclasses. + + :param item: Completion item to format. + """ + raise NotImplementedError + + def complete(self) -> str: + """Produce the completion data to send back to the shell. + + By default this calls :meth:`get_completion_args`, gets the + completions, then calls :meth:`format_completion` for each + completion. + """ + args, incomplete = self.get_completion_args() + completions = self.get_completions(args, incomplete) + out = [self.format_completion(item) for item in completions] + return "\n".join(out) + + +class BashComplete(ShellComplete): + """Shell completion for Bash.""" + + name = "bash" + source_template = _SOURCE_BASH + + def _check_version(self) -> None: + import subprocess + + output = subprocess.run( + ["bash", "-c", "echo ${BASH_VERSION}"], stdout=subprocess.PIPE + ) + match = re.search(r"^(\d+)\.(\d+)\.\d+", output.stdout.decode()) + + if match is not None: + major, minor = match.groups() + + if major < "4" or major == "4" and minor < "4": + raise RuntimeError( + _( + "Shell completion is not supported for Bash" + " versions older than 4.4." + ) + ) + else: + raise RuntimeError( + _("Couldn't detect Bash version, shell completion is not supported.") + ) + + def source(self) -> str: + self._check_version() + return super().source() + + def get_completion_args(self) -> t.Tuple[t.List[str], str]: + cwords = split_arg_string(os.environ["COMP_WORDS"]) + cword = int(os.environ["COMP_CWORD"]) + args = cwords[1:cword] + + try: + incomplete = cwords[cword] + except IndexError: + incomplete = "" + + return args, incomplete + + def format_completion(self, item: CompletionItem) -> str: + return f"{item.type},{item.value}" + + +class ZshComplete(ShellComplete): + """Shell completion for Zsh.""" + + name = "zsh" + source_template = _SOURCE_ZSH + + def get_completion_args(self) -> t.Tuple[t.List[str], str]: + cwords = split_arg_string(os.environ["COMP_WORDS"]) + cword = int(os.environ["COMP_CWORD"]) + args = cwords[1:cword] + + try: + incomplete = cwords[cword] + except IndexError: + incomplete = "" + + return args, incomplete + + def format_completion(self, item: CompletionItem) -> str: + return f"{item.type}\n{item.value}\n{item.help if item.help else '_'}" + + +class FishComplete(ShellComplete): + """Shell completion for Fish.""" + + name = "fish" + source_template = _SOURCE_FISH + + def get_completion_args(self) -> t.Tuple[t.List[str], str]: + cwords = split_arg_string(os.environ["COMP_WORDS"]) + incomplete = os.environ["COMP_CWORD"] + args = cwords[1:] + + # Fish stores the partial word in both COMP_WORDS and + # COMP_CWORD, remove it from complete args. + if incomplete and args and args[-1] == incomplete: + args.pop() + + return args, incomplete + + def format_completion(self, item: CompletionItem) -> str: + if item.help: + return f"{item.type},{item.value}\t{item.help}" + + return f"{item.type},{item.value}" + + +_available_shells: t.Dict[str, t.Type[ShellComplete]] = { + "bash": BashComplete, + "fish": FishComplete, + "zsh": ZshComplete, +} + + +def add_completion_class( + cls: t.Type[ShellComplete], name: t.Optional[str] = None +) -> None: + """Register a :class:`ShellComplete` subclass under the given name. + The name will be provided by the completion instruction environment + variable during completion. + + :param cls: The completion class that will handle completion for the + shell. + :param name: Name to register the class under. Defaults to the + class's ``name`` attribute. + """ + if name is None: + name = cls.name + + _available_shells[name] = cls + + +def get_completion_class(shell: str) -> t.Optional[t.Type[ShellComplete]]: + """Look up a registered :class:`ShellComplete` subclass by the name + provided by the completion instruction environment variable. If the + name isn't registered, returns ``None``. + + :param shell: Name the class is registered under. + """ + return _available_shells.get(shell) + + +def _is_incomplete_argument(ctx: Context, param: Parameter) -> bool: + """Determine if the given parameter is an argument that can still + accept values. + + :param ctx: Invocation context for the command represented by the + parsed complete args. + :param param: Argument object being checked. + """ + if not isinstance(param, Argument): + return False + + assert param.name is not None + value = ctx.params[param.name] + return ( + param.nargs == -1 + or ctx.get_parameter_source(param.name) is not ParameterSource.COMMANDLINE + or ( + param.nargs > 1 + and isinstance(value, (tuple, list)) + and len(value) < param.nargs + ) + ) + + +def _start_of_option(ctx: Context, value: str) -> bool: + """Check if the value looks like the start of an option.""" + if not value: + return False + + c = value[0] + return c in ctx._opt_prefixes + + +def _is_incomplete_option(ctx: Context, args: t.List[str], param: Parameter) -> bool: + """Determine if the given parameter is an option that needs a value. + + :param args: List of complete args before the incomplete value. + :param param: Option object being checked. + """ + if not isinstance(param, Option): + return False + + if param.is_flag or param.count: + return False + + last_option = None + + for index, arg in enumerate(reversed(args)): + if index + 1 > param.nargs: + break + + if _start_of_option(ctx, arg): + last_option = arg + + return last_option is not None and last_option in param.opts + + +def _resolve_context( + cli: BaseCommand, ctx_args: t.Dict[str, t.Any], prog_name: str, args: t.List[str] +) -> Context: + """Produce the context hierarchy starting with the command and + traversing the complete arguments. This only follows the commands, + it doesn't trigger input prompts or callbacks. + + :param cli: Command being called. + :param prog_name: Name of the executable in the shell. + :param args: List of complete args before the incomplete value. + """ + ctx_args["resilient_parsing"] = True + ctx = cli.make_context(prog_name, args.copy(), **ctx_args) + args = ctx.protected_args + ctx.args + + while args: + command = ctx.command + + if isinstance(command, MultiCommand): + if not command.chain: + name, cmd, args = command.resolve_command(ctx, args) + + if cmd is None: + return ctx + + ctx = cmd.make_context(name, args, parent=ctx, resilient_parsing=True) + args = ctx.protected_args + ctx.args + else: + while args: + name, cmd, args = command.resolve_command(ctx, args) + + if cmd is None: + return ctx + + sub_ctx = cmd.make_context( + name, + args, + parent=ctx, + allow_extra_args=True, + allow_interspersed_args=False, + resilient_parsing=True, + ) + args = sub_ctx.args + + ctx = sub_ctx + args = [*sub_ctx.protected_args, *sub_ctx.args] + else: + break + + return ctx + + +def _resolve_incomplete( + ctx: Context, args: t.List[str], incomplete: str +) -> t.Tuple[t.Union[BaseCommand, Parameter], str]: + """Find the Click object that will handle the completion of the + incomplete value. Return the object and the incomplete value. + + :param ctx: Invocation context for the command represented by + the parsed complete args. + :param args: List of complete args before the incomplete value. + :param incomplete: Value being completed. May be empty. + """ + # Different shells treat an "=" between a long option name and + # value differently. Might keep the value joined, return the "=" + # as a separate item, or return the split name and value. Always + # split and discard the "=" to make completion easier. + if incomplete == "=": + incomplete = "" + elif "=" in incomplete and _start_of_option(ctx, incomplete): + name, _, incomplete = incomplete.partition("=") + args.append(name) + + # The "--" marker tells Click to stop treating values as options + # even if they start with the option character. If it hasn't been + # given and the incomplete arg looks like an option, the current + # command will provide option name completions. + if "--" not in args and _start_of_option(ctx, incomplete): + return ctx.command, incomplete + + params = ctx.command.get_params(ctx) + + # If the last complete arg is an option name with an incomplete + # value, the option will provide value completions. + for param in params: + if _is_incomplete_option(ctx, args, param): + return param, incomplete + + # It's not an option name or value. The first argument without a + # parsed value will provide value completions. + for param in params: + if _is_incomplete_argument(ctx, param): + return param, incomplete + + # There were no unparsed arguments, the command may be a group that + # will provide command name completions. + return ctx.command, incomplete diff --git a/.vtodo/Lib/site-packages/click/termui.py b/.vtodo/Lib/site-packages/click/termui.py new file mode 100644 index 0000000..bfb2f5a --- /dev/null +++ b/.vtodo/Lib/site-packages/click/termui.py @@ -0,0 +1,787 @@ +import inspect +import io +import itertools +import os +import sys +import typing as t +from gettext import gettext as _ + +from ._compat import isatty +from ._compat import strip_ansi +from ._compat import WIN +from .exceptions import Abort +from .exceptions import UsageError +from .globals import resolve_color_default +from .types import Choice +from .types import convert_type +from .types import ParamType +from .utils import echo +from .utils import LazyFile + +if t.TYPE_CHECKING: + from ._termui_impl import ProgressBar + +V = t.TypeVar("V") + +# The prompt functions to use. The doc tools currently override these +# functions to customize how they work. +visible_prompt_func: t.Callable[[str], str] = input + +_ansi_colors = { + "black": 30, + "red": 31, + "green": 32, + "yellow": 33, + "blue": 34, + "magenta": 35, + "cyan": 36, + "white": 37, + "reset": 39, + "bright_black": 90, + "bright_red": 91, + "bright_green": 92, + "bright_yellow": 93, + "bright_blue": 94, + "bright_magenta": 95, + "bright_cyan": 96, + "bright_white": 97, +} +_ansi_reset_all = "\033[0m" + + +def hidden_prompt_func(prompt: str) -> str: + import getpass + + return getpass.getpass(prompt) + + +def _build_prompt( + text: str, + suffix: str, + show_default: bool = False, + default: t.Optional[t.Any] = None, + show_choices: bool = True, + type: t.Optional[ParamType] = None, +) -> str: + prompt = text + if type is not None and show_choices and isinstance(type, Choice): + prompt += f" ({', '.join(map(str, type.choices))})" + if default is not None and show_default: + prompt = f"{prompt} [{_format_default(default)}]" + return f"{prompt}{suffix}" + + +def _format_default(default: t.Any) -> t.Any: + if isinstance(default, (io.IOBase, LazyFile)) and hasattr(default, "name"): + return default.name # type: ignore + + return default + + +def prompt( + text: str, + default: t.Optional[t.Any] = None, + hide_input: bool = False, + confirmation_prompt: t.Union[bool, str] = False, + type: t.Optional[t.Union[ParamType, t.Any]] = None, + value_proc: t.Optional[t.Callable[[str], t.Any]] = None, + prompt_suffix: str = ": ", + show_default: bool = True, + err: bool = False, + show_choices: bool = True, +) -> t.Any: + """Prompts a user for input. This is a convenience function that can + be used to prompt a user for input later. + + If the user aborts the input by sending an interrupt signal, this + function will catch it and raise a :exc:`Abort` exception. + + :param text: the text to show for the prompt. + :param default: the default value to use if no input happens. If this + is not given it will prompt until it's aborted. + :param hide_input: if this is set to true then the input value will + be hidden. + :param confirmation_prompt: Prompt a second time to confirm the + value. Can be set to a string instead of ``True`` to customize + the message. + :param type: the type to use to check the value against. + :param value_proc: if this parameter is provided it's a function that + is invoked instead of the type conversion to + convert a value. + :param prompt_suffix: a suffix that should be added to the prompt. + :param show_default: shows or hides the default value in the prompt. + :param err: if set to true the file defaults to ``stderr`` instead of + ``stdout``, the same as with echo. + :param show_choices: Show or hide choices if the passed type is a Choice. + For example if type is a Choice of either day or week, + show_choices is true and text is "Group by" then the + prompt will be "Group by (day, week): ". + + .. versionadded:: 8.0 + ``confirmation_prompt`` can be a custom string. + + .. versionadded:: 7.0 + Added the ``show_choices`` parameter. + + .. versionadded:: 6.0 + Added unicode support for cmd.exe on Windows. + + .. versionadded:: 4.0 + Added the `err` parameter. + + """ + + def prompt_func(text: str) -> str: + f = hidden_prompt_func if hide_input else visible_prompt_func + try: + # Write the prompt separately so that we get nice + # coloring through colorama on Windows + echo(text.rstrip(" "), nl=False, err=err) + # Echo a space to stdout to work around an issue where + # readline causes backspace to clear the whole line. + return f(" ") + except (KeyboardInterrupt, EOFError): + # getpass doesn't print a newline if the user aborts input with ^C. + # Allegedly this behavior is inherited from getpass(3). + # A doc bug has been filed at https://bugs.python.org/issue24711 + if hide_input: + echo(None, err=err) + raise Abort() from None + + if value_proc is None: + value_proc = convert_type(type, default) + + prompt = _build_prompt( + text, prompt_suffix, show_default, default, show_choices, type + ) + + if confirmation_prompt: + if confirmation_prompt is True: + confirmation_prompt = _("Repeat for confirmation") + + confirmation_prompt = _build_prompt(confirmation_prompt, prompt_suffix) + + while True: + while True: + value = prompt_func(prompt) + if value: + break + elif default is not None: + value = default + break + try: + result = value_proc(value) + except UsageError as e: + if hide_input: + echo(_("Error: The value you entered was invalid."), err=err) + else: + echo(_("Error: {e.message}").format(e=e), err=err) # noqa: B306 + continue + if not confirmation_prompt: + return result + while True: + value2 = prompt_func(confirmation_prompt) + is_empty = not value and not value2 + if value2 or is_empty: + break + if value == value2: + return result + echo(_("Error: The two entered values do not match."), err=err) + + +def confirm( + text: str, + default: t.Optional[bool] = False, + abort: bool = False, + prompt_suffix: str = ": ", + show_default: bool = True, + err: bool = False, +) -> bool: + """Prompts for confirmation (yes/no question). + + If the user aborts the input by sending a interrupt signal this + function will catch it and raise a :exc:`Abort` exception. + + :param text: the question to ask. + :param default: The default value to use when no input is given. If + ``None``, repeat until input is given. + :param abort: if this is set to `True` a negative answer aborts the + exception by raising :exc:`Abort`. + :param prompt_suffix: a suffix that should be added to the prompt. + :param show_default: shows or hides the default value in the prompt. + :param err: if set to true the file defaults to ``stderr`` instead of + ``stdout``, the same as with echo. + + .. versionchanged:: 8.0 + Repeat until input is given if ``default`` is ``None``. + + .. versionadded:: 4.0 + Added the ``err`` parameter. + """ + prompt = _build_prompt( + text, + prompt_suffix, + show_default, + "y/n" if default is None else ("Y/n" if default else "y/N"), + ) + + while True: + try: + # Write the prompt separately so that we get nice + # coloring through colorama on Windows + echo(prompt.rstrip(" "), nl=False, err=err) + # Echo a space to stdout to work around an issue where + # readline causes backspace to clear the whole line. + value = visible_prompt_func(" ").lower().strip() + except (KeyboardInterrupt, EOFError): + raise Abort() from None + if value in ("y", "yes"): + rv = True + elif value in ("n", "no"): + rv = False + elif default is not None and value == "": + rv = default + else: + echo(_("Error: invalid input"), err=err) + continue + break + if abort and not rv: + raise Abort() + return rv + + +def echo_via_pager( + text_or_generator: t.Union[t.Iterable[str], t.Callable[[], t.Iterable[str]], str], + color: t.Optional[bool] = None, +) -> None: + """This function takes a text and shows it via an environment specific + pager on stdout. + + .. versionchanged:: 3.0 + Added the `color` flag. + + :param text_or_generator: the text to page, or alternatively, a + generator emitting the text to page. + :param color: controls if the pager supports ANSI colors or not. The + default is autodetection. + """ + color = resolve_color_default(color) + + if inspect.isgeneratorfunction(text_or_generator): + i = t.cast(t.Callable[[], t.Iterable[str]], text_or_generator)() + elif isinstance(text_or_generator, str): + i = [text_or_generator] + else: + i = iter(t.cast(t.Iterable[str], text_or_generator)) + + # convert every element of i to a text type if necessary + text_generator = (el if isinstance(el, str) else str(el) for el in i) + + from ._termui_impl import pager + + return pager(itertools.chain(text_generator, "\n"), color) + + +def progressbar( + iterable: t.Optional[t.Iterable[V]] = None, + length: t.Optional[int] = None, + label: t.Optional[str] = None, + show_eta: bool = True, + show_percent: t.Optional[bool] = None, + show_pos: bool = False, + item_show_func: t.Optional[t.Callable[[t.Optional[V]], t.Optional[str]]] = None, + fill_char: str = "#", + empty_char: str = "-", + bar_template: str = "%(label)s [%(bar)s] %(info)s", + info_sep: str = " ", + width: int = 36, + file: t.Optional[t.TextIO] = None, + color: t.Optional[bool] = None, + update_min_steps: int = 1, +) -> "ProgressBar[V]": + """This function creates an iterable context manager that can be used + to iterate over something while showing a progress bar. It will + either iterate over the `iterable` or `length` items (that are counted + up). While iteration happens, this function will print a rendered + progress bar to the given `file` (defaults to stdout) and will attempt + to calculate remaining time and more. By default, this progress bar + will not be rendered if the file is not a terminal. + + The context manager creates the progress bar. When the context + manager is entered the progress bar is already created. With every + iteration over the progress bar, the iterable passed to the bar is + advanced and the bar is updated. When the context manager exits, + a newline is printed and the progress bar is finalized on screen. + + Note: The progress bar is currently designed for use cases where the + total progress can be expected to take at least several seconds. + Because of this, the ProgressBar class object won't display + progress that is considered too fast, and progress where the time + between steps is less than a second. + + No printing must happen or the progress bar will be unintentionally + destroyed. + + Example usage:: + + with progressbar(items) as bar: + for item in bar: + do_something_with(item) + + Alternatively, if no iterable is specified, one can manually update the + progress bar through the `update()` method instead of directly + iterating over the progress bar. The update method accepts the number + of steps to increment the bar with:: + + with progressbar(length=chunks.total_bytes) as bar: + for chunk in chunks: + process_chunk(chunk) + bar.update(chunks.bytes) + + The ``update()`` method also takes an optional value specifying the + ``current_item`` at the new position. This is useful when used + together with ``item_show_func`` to customize the output for each + manual step:: + + with click.progressbar( + length=total_size, + label='Unzipping archive', + item_show_func=lambda a: a.filename + ) as bar: + for archive in zip_file: + archive.extract() + bar.update(archive.size, archive) + + :param iterable: an iterable to iterate over. If not provided the length + is required. + :param length: the number of items to iterate over. By default the + progressbar will attempt to ask the iterator about its + length, which might or might not work. If an iterable is + also provided this parameter can be used to override the + length. If an iterable is not provided the progress bar + will iterate over a range of that length. + :param label: the label to show next to the progress bar. + :param show_eta: enables or disables the estimated time display. This is + automatically disabled if the length cannot be + determined. + :param show_percent: enables or disables the percentage display. The + default is `True` if the iterable has a length or + `False` if not. + :param show_pos: enables or disables the absolute position display. The + default is `False`. + :param item_show_func: A function called with the current item which + can return a string to show next to the progress bar. If the + function returns ``None`` nothing is shown. The current item can + be ``None``, such as when entering and exiting the bar. + :param fill_char: the character to use to show the filled part of the + progress bar. + :param empty_char: the character to use to show the non-filled part of + the progress bar. + :param bar_template: the format string to use as template for the bar. + The parameters in it are ``label`` for the label, + ``bar`` for the progress bar and ``info`` for the + info section. + :param info_sep: the separator between multiple info items (eta etc.) + :param width: the width of the progress bar in characters, 0 means full + terminal width + :param file: The file to write to. If this is not a terminal then + only the label is printed. + :param color: controls if the terminal supports ANSI colors or not. The + default is autodetection. This is only needed if ANSI + codes are included anywhere in the progress bar output + which is not the case by default. + :param update_min_steps: Render only when this many updates have + completed. This allows tuning for very fast iterators. + + .. versionchanged:: 8.0 + Output is shown even if execution time is less than 0.5 seconds. + + .. versionchanged:: 8.0 + ``item_show_func`` shows the current item, not the previous one. + + .. versionchanged:: 8.0 + Labels are echoed if the output is not a TTY. Reverts a change + in 7.0 that removed all output. + + .. versionadded:: 8.0 + Added the ``update_min_steps`` parameter. + + .. versionchanged:: 4.0 + Added the ``color`` parameter. Added the ``update`` method to + the object. + + .. versionadded:: 2.0 + """ + from ._termui_impl import ProgressBar + + color = resolve_color_default(color) + return ProgressBar( + iterable=iterable, + length=length, + show_eta=show_eta, + show_percent=show_percent, + show_pos=show_pos, + item_show_func=item_show_func, + fill_char=fill_char, + empty_char=empty_char, + bar_template=bar_template, + info_sep=info_sep, + file=file, + label=label, + width=width, + color=color, + update_min_steps=update_min_steps, + ) + + +def clear() -> None: + """Clears the terminal screen. This will have the effect of clearing + the whole visible space of the terminal and moving the cursor to the + top left. This does not do anything if not connected to a terminal. + + .. versionadded:: 2.0 + """ + if not isatty(sys.stdout): + return + if WIN: + os.system("cls") + else: + sys.stdout.write("\033[2J\033[1;1H") + + +def _interpret_color( + color: t.Union[int, t.Tuple[int, int, int], str], offset: int = 0 +) -> str: + if isinstance(color, int): + return f"{38 + offset};5;{color:d}" + + if isinstance(color, (tuple, list)): + r, g, b = color + return f"{38 + offset};2;{r:d};{g:d};{b:d}" + + return str(_ansi_colors[color] + offset) + + +def style( + text: t.Any, + fg: t.Optional[t.Union[int, t.Tuple[int, int, int], str]] = None, + bg: t.Optional[t.Union[int, t.Tuple[int, int, int], str]] = None, + bold: t.Optional[bool] = None, + dim: t.Optional[bool] = None, + underline: t.Optional[bool] = None, + overline: t.Optional[bool] = None, + italic: t.Optional[bool] = None, + blink: t.Optional[bool] = None, + reverse: t.Optional[bool] = None, + strikethrough: t.Optional[bool] = None, + reset: bool = True, +) -> str: + """Styles a text with ANSI styles and returns the new string. By + default the styling is self contained which means that at the end + of the string a reset code is issued. This can be prevented by + passing ``reset=False``. + + Examples:: + + click.echo(click.style('Hello World!', fg='green')) + click.echo(click.style('ATTENTION!', blink=True)) + click.echo(click.style('Some things', reverse=True, fg='cyan')) + click.echo(click.style('More colors', fg=(255, 12, 128), bg=117)) + + Supported color names: + + * ``black`` (might be a gray) + * ``red`` + * ``green`` + * ``yellow`` (might be an orange) + * ``blue`` + * ``magenta`` + * ``cyan`` + * ``white`` (might be light gray) + * ``bright_black`` + * ``bright_red`` + * ``bright_green`` + * ``bright_yellow`` + * ``bright_blue`` + * ``bright_magenta`` + * ``bright_cyan`` + * ``bright_white`` + * ``reset`` (reset the color code only) + + If the terminal supports it, color may also be specified as: + + - An integer in the interval [0, 255]. The terminal must support + 8-bit/256-color mode. + - An RGB tuple of three integers in [0, 255]. The terminal must + support 24-bit/true-color mode. + + See https://en.wikipedia.org/wiki/ANSI_color and + https://gist.github.com/XVilka/8346728 for more information. + + :param text: the string to style with ansi codes. + :param fg: if provided this will become the foreground color. + :param bg: if provided this will become the background color. + :param bold: if provided this will enable or disable bold mode. + :param dim: if provided this will enable or disable dim mode. This is + badly supported. + :param underline: if provided this will enable or disable underline. + :param overline: if provided this will enable or disable overline. + :param italic: if provided this will enable or disable italic. + :param blink: if provided this will enable or disable blinking. + :param reverse: if provided this will enable or disable inverse + rendering (foreground becomes background and the + other way round). + :param strikethrough: if provided this will enable or disable + striking through text. + :param reset: by default a reset-all code is added at the end of the + string which means that styles do not carry over. This + can be disabled to compose styles. + + .. versionchanged:: 8.0 + A non-string ``message`` is converted to a string. + + .. versionchanged:: 8.0 + Added support for 256 and RGB color codes. + + .. versionchanged:: 8.0 + Added the ``strikethrough``, ``italic``, and ``overline`` + parameters. + + .. versionchanged:: 7.0 + Added support for bright colors. + + .. versionadded:: 2.0 + """ + if not isinstance(text, str): + text = str(text) + + bits = [] + + if fg: + try: + bits.append(f"\033[{_interpret_color(fg)}m") + except KeyError: + raise TypeError(f"Unknown color {fg!r}") from None + + if bg: + try: + bits.append(f"\033[{_interpret_color(bg, 10)}m") + except KeyError: + raise TypeError(f"Unknown color {bg!r}") from None + + if bold is not None: + bits.append(f"\033[{1 if bold else 22}m") + if dim is not None: + bits.append(f"\033[{2 if dim else 22}m") + if underline is not None: + bits.append(f"\033[{4 if underline else 24}m") + if overline is not None: + bits.append(f"\033[{53 if overline else 55}m") + if italic is not None: + bits.append(f"\033[{3 if italic else 23}m") + if blink is not None: + bits.append(f"\033[{5 if blink else 25}m") + if reverse is not None: + bits.append(f"\033[{7 if reverse else 27}m") + if strikethrough is not None: + bits.append(f"\033[{9 if strikethrough else 29}m") + bits.append(text) + if reset: + bits.append(_ansi_reset_all) + return "".join(bits) + + +def unstyle(text: str) -> str: + """Removes ANSI styling information from a string. Usually it's not + necessary to use this function as Click's echo function will + automatically remove styling if necessary. + + .. versionadded:: 2.0 + + :param text: the text to remove style information from. + """ + return strip_ansi(text) + + +def secho( + message: t.Optional[t.Any] = None, + file: t.Optional[t.IO[t.AnyStr]] = None, + nl: bool = True, + err: bool = False, + color: t.Optional[bool] = None, + **styles: t.Any, +) -> None: + """This function combines :func:`echo` and :func:`style` into one + call. As such the following two calls are the same:: + + click.secho('Hello World!', fg='green') + click.echo(click.style('Hello World!', fg='green')) + + All keyword arguments are forwarded to the underlying functions + depending on which one they go with. + + Non-string types will be converted to :class:`str`. However, + :class:`bytes` are passed directly to :meth:`echo` without applying + style. If you want to style bytes that represent text, call + :meth:`bytes.decode` first. + + .. versionchanged:: 8.0 + A non-string ``message`` is converted to a string. Bytes are + passed through without style applied. + + .. versionadded:: 2.0 + """ + if message is not None and not isinstance(message, (bytes, bytearray)): + message = style(message, **styles) + + return echo(message, file=file, nl=nl, err=err, color=color) + + +def edit( + text: t.Optional[t.AnyStr] = None, + editor: t.Optional[str] = None, + env: t.Optional[t.Mapping[str, str]] = None, + require_save: bool = True, + extension: str = ".txt", + filename: t.Optional[str] = None, +) -> t.Optional[t.AnyStr]: + r"""Edits the given text in the defined editor. If an editor is given + (should be the full path to the executable but the regular operating + system search path is used for finding the executable) it overrides + the detected editor. Optionally, some environment variables can be + used. If the editor is closed without changes, `None` is returned. In + case a file is edited directly the return value is always `None` and + `require_save` and `extension` are ignored. + + If the editor cannot be opened a :exc:`UsageError` is raised. + + Note for Windows: to simplify cross-platform usage, the newlines are + automatically converted from POSIX to Windows and vice versa. As such, + the message here will have ``\n`` as newline markers. + + :param text: the text to edit. + :param editor: optionally the editor to use. Defaults to automatic + detection. + :param env: environment variables to forward to the editor. + :param require_save: if this is true, then not saving in the editor + will make the return value become `None`. + :param extension: the extension to tell the editor about. This defaults + to `.txt` but changing this might change syntax + highlighting. + :param filename: if provided it will edit this file instead of the + provided text contents. It will not use a temporary + file as an indirection in that case. + """ + from ._termui_impl import Editor + + ed = Editor(editor=editor, env=env, require_save=require_save, extension=extension) + + if filename is None: + return ed.edit(text) + + ed.edit_file(filename) + return None + + +def launch(url: str, wait: bool = False, locate: bool = False) -> int: + """This function launches the given URL (or filename) in the default + viewer application for this file type. If this is an executable, it + might launch the executable in a new session. The return value is + the exit code of the launched application. Usually, ``0`` indicates + success. + + Examples:: + + click.launch('https://click.palletsprojects.com/') + click.launch('/my/downloaded/file', locate=True) + + .. versionadded:: 2.0 + + :param url: URL or filename of the thing to launch. + :param wait: Wait for the program to exit before returning. This + only works if the launched program blocks. In particular, + ``xdg-open`` on Linux does not block. + :param locate: if this is set to `True` then instead of launching the + application associated with the URL it will attempt to + launch a file manager with the file located. This + might have weird effects if the URL does not point to + the filesystem. + """ + from ._termui_impl import open_url + + return open_url(url, wait=wait, locate=locate) + + +# If this is provided, getchar() calls into this instead. This is used +# for unittesting purposes. +_getchar: t.Optional[t.Callable[[bool], str]] = None + + +def getchar(echo: bool = False) -> str: + """Fetches a single character from the terminal and returns it. This + will always return a unicode character and under certain rare + circumstances this might return more than one character. The + situations which more than one character is returned is when for + whatever reason multiple characters end up in the terminal buffer or + standard input was not actually a terminal. + + Note that this will always read from the terminal, even if something + is piped into the standard input. + + Note for Windows: in rare cases when typing non-ASCII characters, this + function might wait for a second character and then return both at once. + This is because certain Unicode characters look like special-key markers. + + .. versionadded:: 2.0 + + :param echo: if set to `True`, the character read will also show up on + the terminal. The default is to not show it. + """ + global _getchar + + if _getchar is None: + from ._termui_impl import getchar as f + + _getchar = f + + return _getchar(echo) + + +def raw_terminal() -> t.ContextManager[int]: + from ._termui_impl import raw_terminal as f + + return f() + + +def pause(info: t.Optional[str] = None, err: bool = False) -> None: + """This command stops execution and waits for the user to press any + key to continue. This is similar to the Windows batch "pause" + command. If the program is not run through a terminal, this command + will instead do nothing. + + .. versionadded:: 2.0 + + .. versionadded:: 4.0 + Added the `err` parameter. + + :param info: The message to print before pausing. Defaults to + ``"Press any key to continue..."``. + :param err: if set to message goes to ``stderr`` instead of + ``stdout``, the same as with echo. + """ + if not isatty(sys.stdin) or not isatty(sys.stdout): + return + + if info is None: + info = _("Press any key to continue...") + + try: + if info: + echo(info, nl=False, err=err) + try: + getchar() + except (KeyboardInterrupt, EOFError): + pass + finally: + if info: + echo(err=err) diff --git a/.vtodo/Lib/site-packages/click/testing.py b/.vtodo/Lib/site-packages/click/testing.py new file mode 100644 index 0000000..e395c2e --- /dev/null +++ b/.vtodo/Lib/site-packages/click/testing.py @@ -0,0 +1,479 @@ +import contextlib +import io +import os +import shlex +import shutil +import sys +import tempfile +import typing as t +from types import TracebackType + +from . import formatting +from . import termui +from . import utils +from ._compat import _find_binary_reader + +if t.TYPE_CHECKING: + from .core import BaseCommand + + +class EchoingStdin: + def __init__(self, input: t.BinaryIO, output: t.BinaryIO) -> None: + self._input = input + self._output = output + self._paused = False + + def __getattr__(self, x: str) -> t.Any: + return getattr(self._input, x) + + def _echo(self, rv: bytes) -> bytes: + if not self._paused: + self._output.write(rv) + + return rv + + def read(self, n: int = -1) -> bytes: + return self._echo(self._input.read(n)) + + def read1(self, n: int = -1) -> bytes: + return self._echo(self._input.read1(n)) # type: ignore + + def readline(self, n: int = -1) -> bytes: + return self._echo(self._input.readline(n)) + + def readlines(self) -> t.List[bytes]: + return [self._echo(x) for x in self._input.readlines()] + + def __iter__(self) -> t.Iterator[bytes]: + return iter(self._echo(x) for x in self._input) + + def __repr__(self) -> str: + return repr(self._input) + + +@contextlib.contextmanager +def _pause_echo(stream: t.Optional[EchoingStdin]) -> t.Iterator[None]: + if stream is None: + yield + else: + stream._paused = True + yield + stream._paused = False + + +class _NamedTextIOWrapper(io.TextIOWrapper): + def __init__( + self, buffer: t.BinaryIO, name: str, mode: str, **kwargs: t.Any + ) -> None: + super().__init__(buffer, **kwargs) + self._name = name + self._mode = mode + + @property + def name(self) -> str: + return self._name + + @property + def mode(self) -> str: + return self._mode + + +def make_input_stream( + input: t.Optional[t.Union[str, bytes, t.IO]], charset: str +) -> t.BinaryIO: + # Is already an input stream. + if hasattr(input, "read"): + rv = _find_binary_reader(t.cast(t.IO, input)) + + if rv is not None: + return rv + + raise TypeError("Could not find binary reader for input stream.") + + if input is None: + input = b"" + elif isinstance(input, str): + input = input.encode(charset) + + return io.BytesIO(t.cast(bytes, input)) + + +class Result: + """Holds the captured result of an invoked CLI script.""" + + def __init__( + self, + runner: "CliRunner", + stdout_bytes: bytes, + stderr_bytes: t.Optional[bytes], + return_value: t.Any, + exit_code: int, + exception: t.Optional[BaseException], + exc_info: t.Optional[ + t.Tuple[t.Type[BaseException], BaseException, TracebackType] + ] = None, + ): + #: The runner that created the result + self.runner = runner + #: The standard output as bytes. + self.stdout_bytes = stdout_bytes + #: The standard error as bytes, or None if not available + self.stderr_bytes = stderr_bytes + #: The value returned from the invoked command. + #: + #: .. versionadded:: 8.0 + self.return_value = return_value + #: The exit code as integer. + self.exit_code = exit_code + #: The exception that happened if one did. + self.exception = exception + #: The traceback + self.exc_info = exc_info + + @property + def output(self) -> str: + """The (standard) output as unicode string.""" + return self.stdout + + @property + def stdout(self) -> str: + """The standard output as unicode string.""" + return self.stdout_bytes.decode(self.runner.charset, "replace").replace( + "\r\n", "\n" + ) + + @property + def stderr(self) -> str: + """The standard error as unicode string.""" + if self.stderr_bytes is None: + raise ValueError("stderr not separately captured") + return self.stderr_bytes.decode(self.runner.charset, "replace").replace( + "\r\n", "\n" + ) + + def __repr__(self) -> str: + exc_str = repr(self.exception) if self.exception else "okay" + return f"<{type(self).__name__} {exc_str}>" + + +class CliRunner: + """The CLI runner provides functionality to invoke a Click command line + script for unittesting purposes in a isolated environment. This only + works in single-threaded systems without any concurrency as it changes the + global interpreter state. + + :param charset: the character set for the input and output data. + :param env: a dictionary with environment variables for overriding. + :param echo_stdin: if this is set to `True`, then reading from stdin writes + to stdout. This is useful for showing examples in + some circumstances. Note that regular prompts + will automatically echo the input. + :param mix_stderr: if this is set to `False`, then stdout and stderr are + preserved as independent streams. This is useful for + Unix-philosophy apps that have predictable stdout and + noisy stderr, such that each may be measured + independently + """ + + def __init__( + self, + charset: str = "utf-8", + env: t.Optional[t.Mapping[str, t.Optional[str]]] = None, + echo_stdin: bool = False, + mix_stderr: bool = True, + ) -> None: + self.charset = charset + self.env = env or {} + self.echo_stdin = echo_stdin + self.mix_stderr = mix_stderr + + def get_default_prog_name(self, cli: "BaseCommand") -> str: + """Given a command object it will return the default program name + for it. The default is the `name` attribute or ``"root"`` if not + set. + """ + return cli.name or "root" + + def make_env( + self, overrides: t.Optional[t.Mapping[str, t.Optional[str]]] = None + ) -> t.Mapping[str, t.Optional[str]]: + """Returns the environment overrides for invoking a script.""" + rv = dict(self.env) + if overrides: + rv.update(overrides) + return rv + + @contextlib.contextmanager + def isolation( + self, + input: t.Optional[t.Union[str, bytes, t.IO]] = None, + env: t.Optional[t.Mapping[str, t.Optional[str]]] = None, + color: bool = False, + ) -> t.Iterator[t.Tuple[io.BytesIO, t.Optional[io.BytesIO]]]: + """A context manager that sets up the isolation for invoking of a + command line tool. This sets up stdin with the given input data + and `os.environ` with the overrides from the given dictionary. + This also rebinds some internals in Click to be mocked (like the + prompt functionality). + + This is automatically done in the :meth:`invoke` method. + + :param input: the input stream to put into sys.stdin. + :param env: the environment overrides as dictionary. + :param color: whether the output should contain color codes. The + application can still override this explicitly. + + .. versionchanged:: 8.0 + ``stderr`` is opened with ``errors="backslashreplace"`` + instead of the default ``"strict"``. + + .. versionchanged:: 4.0 + Added the ``color`` parameter. + """ + bytes_input = make_input_stream(input, self.charset) + echo_input = None + + old_stdin = sys.stdin + old_stdout = sys.stdout + old_stderr = sys.stderr + old_forced_width = formatting.FORCED_WIDTH + formatting.FORCED_WIDTH = 80 + + env = self.make_env(env) + + bytes_output = io.BytesIO() + + if self.echo_stdin: + bytes_input = echo_input = t.cast( + t.BinaryIO, EchoingStdin(bytes_input, bytes_output) + ) + + sys.stdin = text_input = _NamedTextIOWrapper( + bytes_input, encoding=self.charset, name="", mode="r" + ) + + if self.echo_stdin: + # Force unbuffered reads, otherwise TextIOWrapper reads a + # large chunk which is echoed early. + text_input._CHUNK_SIZE = 1 # type: ignore + + sys.stdout = _NamedTextIOWrapper( + bytes_output, encoding=self.charset, name="", mode="w" + ) + + bytes_error = None + if self.mix_stderr: + sys.stderr = sys.stdout + else: + bytes_error = io.BytesIO() + sys.stderr = _NamedTextIOWrapper( + bytes_error, + encoding=self.charset, + name="", + mode="w", + errors="backslashreplace", + ) + + @_pause_echo(echo_input) # type: ignore + def visible_input(prompt: t.Optional[str] = None) -> str: + sys.stdout.write(prompt or "") + val = text_input.readline().rstrip("\r\n") + sys.stdout.write(f"{val}\n") + sys.stdout.flush() + return val + + @_pause_echo(echo_input) # type: ignore + def hidden_input(prompt: t.Optional[str] = None) -> str: + sys.stdout.write(f"{prompt or ''}\n") + sys.stdout.flush() + return text_input.readline().rstrip("\r\n") + + @_pause_echo(echo_input) # type: ignore + def _getchar(echo: bool) -> str: + char = sys.stdin.read(1) + + if echo: + sys.stdout.write(char) + + sys.stdout.flush() + return char + + default_color = color + + def should_strip_ansi( + stream: t.Optional[t.IO] = None, color: t.Optional[bool] = None + ) -> bool: + if color is None: + return not default_color + return not color + + old_visible_prompt_func = termui.visible_prompt_func + old_hidden_prompt_func = termui.hidden_prompt_func + old__getchar_func = termui._getchar + old_should_strip_ansi = utils.should_strip_ansi # type: ignore + termui.visible_prompt_func = visible_input + termui.hidden_prompt_func = hidden_input + termui._getchar = _getchar + utils.should_strip_ansi = should_strip_ansi # type: ignore + + old_env = {} + try: + for key, value in env.items(): + old_env[key] = os.environ.get(key) + if value is None: + try: + del os.environ[key] + except Exception: + pass + else: + os.environ[key] = value + yield (bytes_output, bytes_error) + finally: + for key, value in old_env.items(): + if value is None: + try: + del os.environ[key] + except Exception: + pass + else: + os.environ[key] = value + sys.stdout = old_stdout + sys.stderr = old_stderr + sys.stdin = old_stdin + termui.visible_prompt_func = old_visible_prompt_func + termui.hidden_prompt_func = old_hidden_prompt_func + termui._getchar = old__getchar_func + utils.should_strip_ansi = old_should_strip_ansi # type: ignore + formatting.FORCED_WIDTH = old_forced_width + + def invoke( + self, + cli: "BaseCommand", + args: t.Optional[t.Union[str, t.Sequence[str]]] = None, + input: t.Optional[t.Union[str, bytes, t.IO]] = None, + env: t.Optional[t.Mapping[str, t.Optional[str]]] = None, + catch_exceptions: bool = True, + color: bool = False, + **extra: t.Any, + ) -> Result: + """Invokes a command in an isolated environment. The arguments are + forwarded directly to the command line script, the `extra` keyword + arguments are passed to the :meth:`~clickpkg.Command.main` function of + the command. + + This returns a :class:`Result` object. + + :param cli: the command to invoke + :param args: the arguments to invoke. It may be given as an iterable + or a string. When given as string it will be interpreted + as a Unix shell command. More details at + :func:`shlex.split`. + :param input: the input data for `sys.stdin`. + :param env: the environment overrides. + :param catch_exceptions: Whether to catch any other exceptions than + ``SystemExit``. + :param extra: the keyword arguments to pass to :meth:`main`. + :param color: whether the output should contain color codes. The + application can still override this explicitly. + + .. versionchanged:: 8.0 + The result object has the ``return_value`` attribute with + the value returned from the invoked command. + + .. versionchanged:: 4.0 + Added the ``color`` parameter. + + .. versionchanged:: 3.0 + Added the ``catch_exceptions`` parameter. + + .. versionchanged:: 3.0 + The result object has the ``exc_info`` attribute with the + traceback if available. + """ + exc_info = None + with self.isolation(input=input, env=env, color=color) as outstreams: + return_value = None + exception: t.Optional[BaseException] = None + exit_code = 0 + + if isinstance(args, str): + args = shlex.split(args) + + try: + prog_name = extra.pop("prog_name") + except KeyError: + prog_name = self.get_default_prog_name(cli) + + try: + return_value = cli.main(args=args or (), prog_name=prog_name, **extra) + except SystemExit as e: + exc_info = sys.exc_info() + e_code = t.cast(t.Optional[t.Union[int, t.Any]], e.code) + + if e_code is None: + e_code = 0 + + if e_code != 0: + exception = e + + if not isinstance(e_code, int): + sys.stdout.write(str(e_code)) + sys.stdout.write("\n") + e_code = 1 + + exit_code = e_code + + except Exception as e: + if not catch_exceptions: + raise + exception = e + exit_code = 1 + exc_info = sys.exc_info() + finally: + sys.stdout.flush() + stdout = outstreams[0].getvalue() + if self.mix_stderr: + stderr = None + else: + stderr = outstreams[1].getvalue() # type: ignore + + return Result( + runner=self, + stdout_bytes=stdout, + stderr_bytes=stderr, + return_value=return_value, + exit_code=exit_code, + exception=exception, + exc_info=exc_info, # type: ignore + ) + + @contextlib.contextmanager + def isolated_filesystem( + self, temp_dir: t.Optional[t.Union[str, os.PathLike]] = None + ) -> t.Iterator[str]: + """A context manager that creates a temporary directory and + changes the current working directory to it. This isolates tests + that affect the contents of the CWD to prevent them from + interfering with each other. + + :param temp_dir: Create the temporary directory under this + directory. If given, the created directory is not removed + when exiting. + + .. versionchanged:: 8.0 + Added the ``temp_dir`` parameter. + """ + cwd = os.getcwd() + dt = tempfile.mkdtemp(dir=temp_dir) # type: ignore[type-var] + os.chdir(dt) + + try: + yield t.cast(str, dt) + finally: + os.chdir(cwd) + + if temp_dir is None: + try: + shutil.rmtree(dt) + except OSError: # noqa: B014 + pass diff --git a/.vtodo/Lib/site-packages/click/types.py b/.vtodo/Lib/site-packages/click/types.py new file mode 100644 index 0000000..b45ee53 --- /dev/null +++ b/.vtodo/Lib/site-packages/click/types.py @@ -0,0 +1,1073 @@ +import os +import stat +import typing as t +from datetime import datetime +from gettext import gettext as _ +from gettext import ngettext + +from ._compat import _get_argv_encoding +from ._compat import get_filesystem_encoding +from ._compat import open_stream +from .exceptions import BadParameter +from .utils import LazyFile +from .utils import safecall + +if t.TYPE_CHECKING: + import typing_extensions as te + from .core import Context + from .core import Parameter + from .shell_completion import CompletionItem + + +class ParamType: + """Represents the type of a parameter. Validates and converts values + from the command line or Python into the correct type. + + To implement a custom type, subclass and implement at least the + following: + + - The :attr:`name` class attribute must be set. + - Calling an instance of the type with ``None`` must return + ``None``. This is already implemented by default. + - :meth:`convert` must convert string values to the correct type. + - :meth:`convert` must accept values that are already the correct + type. + - It must be able to convert a value if the ``ctx`` and ``param`` + arguments are ``None``. This can occur when converting prompt + input. + """ + + is_composite: t.ClassVar[bool] = False + arity: t.ClassVar[int] = 1 + + #: the descriptive name of this type + name: str + + #: if a list of this type is expected and the value is pulled from a + #: string environment variable, this is what splits it up. `None` + #: means any whitespace. For all parameters the general rule is that + #: whitespace splits them up. The exception are paths and files which + #: are split by ``os.path.pathsep`` by default (":" on Unix and ";" on + #: Windows). + envvar_list_splitter: t.ClassVar[t.Optional[str]] = None + + def to_info_dict(self) -> t.Dict[str, t.Any]: + """Gather information that could be useful for a tool generating + user-facing documentation. + + Use :meth:`click.Context.to_info_dict` to traverse the entire + CLI structure. + + .. versionadded:: 8.0 + """ + # The class name without the "ParamType" suffix. + param_type = type(self).__name__.partition("ParamType")[0] + param_type = param_type.partition("ParameterType")[0] + + # Custom subclasses might not remember to set a name. + if hasattr(self, "name"): + name = self.name + else: + name = param_type + + return {"param_type": param_type, "name": name} + + def __call__( + self, + value: t.Any, + param: t.Optional["Parameter"] = None, + ctx: t.Optional["Context"] = None, + ) -> t.Any: + if value is not None: + return self.convert(value, param, ctx) + + def get_metavar(self, param: "Parameter") -> t.Optional[str]: + """Returns the metavar default for this param if it provides one.""" + + def get_missing_message(self, param: "Parameter") -> t.Optional[str]: + """Optionally might return extra information about a missing + parameter. + + .. versionadded:: 2.0 + """ + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + """Convert the value to the correct type. This is not called if + the value is ``None`` (the missing value). + + This must accept string values from the command line, as well as + values that are already the correct type. It may also convert + other compatible types. + + The ``param`` and ``ctx`` arguments may be ``None`` in certain + situations, such as when converting prompt input. + + If the value cannot be converted, call :meth:`fail` with a + descriptive message. + + :param value: The value to convert. + :param param: The parameter that is using this type to convert + its value. May be ``None``. + :param ctx: The current context that arrived at this value. May + be ``None``. + """ + return value + + def split_envvar_value(self, rv: str) -> t.Sequence[str]: + """Given a value from an environment variable this splits it up + into small chunks depending on the defined envvar list splitter. + + If the splitter is set to `None`, which means that whitespace splits, + then leading and trailing whitespace is ignored. Otherwise, leading + and trailing splitters usually lead to empty items being included. + """ + return (rv or "").split(self.envvar_list_splitter) + + def fail( + self, + message: str, + param: t.Optional["Parameter"] = None, + ctx: t.Optional["Context"] = None, + ) -> "t.NoReturn": + """Helper method to fail with an invalid value message.""" + raise BadParameter(message, ctx=ctx, param=param) + + def shell_complete( + self, ctx: "Context", param: "Parameter", incomplete: str + ) -> t.List["CompletionItem"]: + """Return a list of + :class:`~click.shell_completion.CompletionItem` objects for the + incomplete value. Most types do not provide completions, but + some do, and this allows custom types to provide custom + completions as well. + + :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + return [] + + +class CompositeParamType(ParamType): + is_composite = True + + @property + def arity(self) -> int: # type: ignore + raise NotImplementedError() + + +class FuncParamType(ParamType): + def __init__(self, func: t.Callable[[t.Any], t.Any]) -> None: + self.name = func.__name__ + self.func = func + + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict["func"] = self.func + return info_dict + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + try: + return self.func(value) + except ValueError: + try: + value = str(value) + except UnicodeError: + value = value.decode("utf-8", "replace") + + self.fail(value, param, ctx) + + +class UnprocessedParamType(ParamType): + name = "text" + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + return value + + def __repr__(self) -> str: + return "UNPROCESSED" + + +class StringParamType(ParamType): + name = "text" + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + if isinstance(value, bytes): + enc = _get_argv_encoding() + try: + value = value.decode(enc) + except UnicodeError: + fs_enc = get_filesystem_encoding() + if fs_enc != enc: + try: + value = value.decode(fs_enc) + except UnicodeError: + value = value.decode("utf-8", "replace") + else: + value = value.decode("utf-8", "replace") + return value + return str(value) + + def __repr__(self) -> str: + return "STRING" + + +class Choice(ParamType): + """The choice type allows a value to be checked against a fixed set + of supported values. All of these values have to be strings. + + You should only pass a list or tuple of choices. Other iterables + (like generators) may lead to surprising results. + + The resulting value will always be one of the originally passed choices + regardless of ``case_sensitive`` or any ``ctx.token_normalize_func`` + being specified. + + See :ref:`choice-opts` for an example. + + :param case_sensitive: Set to false to make choices case + insensitive. Defaults to true. + """ + + name = "choice" + + def __init__(self, choices: t.Sequence[str], case_sensitive: bool = True) -> None: + self.choices = choices + self.case_sensitive = case_sensitive + + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict["choices"] = self.choices + info_dict["case_sensitive"] = self.case_sensitive + return info_dict + + def get_metavar(self, param: "Parameter") -> str: + choices_str = "|".join(self.choices) + + # Use curly braces to indicate a required argument. + if param.required and param.param_type_name == "argument": + return f"{{{choices_str}}}" + + # Use square braces to indicate an option or optional argument. + return f"[{choices_str}]" + + def get_missing_message(self, param: "Parameter") -> str: + return _("Choose from:\n\t{choices}").format(choices=",\n\t".join(self.choices)) + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + # Match through normalization and case sensitivity + # first do token_normalize_func, then lowercase + # preserve original `value` to produce an accurate message in + # `self.fail` + normed_value = value + normed_choices = {choice: choice for choice in self.choices} + + if ctx is not None and ctx.token_normalize_func is not None: + normed_value = ctx.token_normalize_func(value) + normed_choices = { + ctx.token_normalize_func(normed_choice): original + for normed_choice, original in normed_choices.items() + } + + if not self.case_sensitive: + normed_value = normed_value.casefold() + normed_choices = { + normed_choice.casefold(): original + for normed_choice, original in normed_choices.items() + } + + if normed_value in normed_choices: + return normed_choices[normed_value] + + choices_str = ", ".join(map(repr, self.choices)) + self.fail( + ngettext( + "{value!r} is not {choice}.", + "{value!r} is not one of {choices}.", + len(self.choices), + ).format(value=value, choice=choices_str, choices=choices_str), + param, + ctx, + ) + + def __repr__(self) -> str: + return f"Choice({list(self.choices)})" + + def shell_complete( + self, ctx: "Context", param: "Parameter", incomplete: str + ) -> t.List["CompletionItem"]: + """Complete choices that start with the incomplete value. + + :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + str_choices = map(str, self.choices) + + if self.case_sensitive: + matched = (c for c in str_choices if c.startswith(incomplete)) + else: + incomplete = incomplete.lower() + matched = (c for c in str_choices if c.lower().startswith(incomplete)) + + return [CompletionItem(c) for c in matched] + + +class DateTime(ParamType): + """The DateTime type converts date strings into `datetime` objects. + + The format strings which are checked are configurable, but default to some + common (non-timezone aware) ISO 8601 formats. + + When specifying *DateTime* formats, you should only pass a list or a tuple. + Other iterables, like generators, may lead to surprising results. + + The format strings are processed using ``datetime.strptime``, and this + consequently defines the format strings which are allowed. + + Parsing is tried using each format, in order, and the first format which + parses successfully is used. + + :param formats: A list or tuple of date format strings, in the order in + which they should be tried. Defaults to + ``'%Y-%m-%d'``, ``'%Y-%m-%dT%H:%M:%S'``, + ``'%Y-%m-%d %H:%M:%S'``. + """ + + name = "datetime" + + def __init__(self, formats: t.Optional[t.Sequence[str]] = None): + self.formats = formats or ["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%d %H:%M:%S"] + + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict["formats"] = self.formats + return info_dict + + def get_metavar(self, param: "Parameter") -> str: + return f"[{'|'.join(self.formats)}]" + + def _try_to_convert_date(self, value: t.Any, format: str) -> t.Optional[datetime]: + try: + return datetime.strptime(value, format) + except ValueError: + return None + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + if isinstance(value, datetime): + return value + + for format in self.formats: + converted = self._try_to_convert_date(value, format) + + if converted is not None: + return converted + + formats_str = ", ".join(map(repr, self.formats)) + self.fail( + ngettext( + "{value!r} does not match the format {format}.", + "{value!r} does not match the formats {formats}.", + len(self.formats), + ).format(value=value, format=formats_str, formats=formats_str), + param, + ctx, + ) + + def __repr__(self) -> str: + return "DateTime" + + +class _NumberParamTypeBase(ParamType): + _number_class: t.ClassVar[t.Type] + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + try: + return self._number_class(value) + except ValueError: + self.fail( + _("{value!r} is not a valid {number_type}.").format( + value=value, number_type=self.name + ), + param, + ctx, + ) + + +class _NumberRangeBase(_NumberParamTypeBase): + def __init__( + self, + min: t.Optional[float] = None, + max: t.Optional[float] = None, + min_open: bool = False, + max_open: bool = False, + clamp: bool = False, + ) -> None: + self.min = min + self.max = max + self.min_open = min_open + self.max_open = max_open + self.clamp = clamp + + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict.update( + min=self.min, + max=self.max, + min_open=self.min_open, + max_open=self.max_open, + clamp=self.clamp, + ) + return info_dict + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + import operator + + rv = super().convert(value, param, ctx) + lt_min: bool = self.min is not None and ( + operator.le if self.min_open else operator.lt + )(rv, self.min) + gt_max: bool = self.max is not None and ( + operator.ge if self.max_open else operator.gt + )(rv, self.max) + + if self.clamp: + if lt_min: + return self._clamp(self.min, 1, self.min_open) # type: ignore + + if gt_max: + return self._clamp(self.max, -1, self.max_open) # type: ignore + + if lt_min or gt_max: + self.fail( + _("{value} is not in the range {range}.").format( + value=rv, range=self._describe_range() + ), + param, + ctx, + ) + + return rv + + def _clamp(self, bound: float, dir: "te.Literal[1, -1]", open: bool) -> float: + """Find the valid value to clamp to bound in the given + direction. + + :param bound: The boundary value. + :param dir: 1 or -1 indicating the direction to move. + :param open: If true, the range does not include the bound. + """ + raise NotImplementedError + + def _describe_range(self) -> str: + """Describe the range for use in help text.""" + if self.min is None: + op = "<" if self.max_open else "<=" + return f"x{op}{self.max}" + + if self.max is None: + op = ">" if self.min_open else ">=" + return f"x{op}{self.min}" + + lop = "<" if self.min_open else "<=" + rop = "<" if self.max_open else "<=" + return f"{self.min}{lop}x{rop}{self.max}" + + def __repr__(self) -> str: + clamp = " clamped" if self.clamp else "" + return f"<{type(self).__name__} {self._describe_range()}{clamp}>" + + +class IntParamType(_NumberParamTypeBase): + name = "integer" + _number_class = int + + def __repr__(self) -> str: + return "INT" + + +class IntRange(_NumberRangeBase, IntParamType): + """Restrict an :data:`click.INT` value to a range of accepted + values. See :ref:`ranges`. + + If ``min`` or ``max`` are not passed, any value is accepted in that + direction. If ``min_open`` or ``max_open`` are enabled, the + corresponding boundary is not included in the range. + + If ``clamp`` is enabled, a value outside the range is clamped to the + boundary instead of failing. + + .. versionchanged:: 8.0 + Added the ``min_open`` and ``max_open`` parameters. + """ + + name = "integer range" + + def _clamp( # type: ignore + self, bound: int, dir: "te.Literal[1, -1]", open: bool + ) -> int: + if not open: + return bound + + return bound + dir + + +class FloatParamType(_NumberParamTypeBase): + name = "float" + _number_class = float + + def __repr__(self) -> str: + return "FLOAT" + + +class FloatRange(_NumberRangeBase, FloatParamType): + """Restrict a :data:`click.FLOAT` value to a range of accepted + values. See :ref:`ranges`. + + If ``min`` or ``max`` are not passed, any value is accepted in that + direction. If ``min_open`` or ``max_open`` are enabled, the + corresponding boundary is not included in the range. + + If ``clamp`` is enabled, a value outside the range is clamped to the + boundary instead of failing. This is not supported if either + boundary is marked ``open``. + + .. versionchanged:: 8.0 + Added the ``min_open`` and ``max_open`` parameters. + """ + + name = "float range" + + def __init__( + self, + min: t.Optional[float] = None, + max: t.Optional[float] = None, + min_open: bool = False, + max_open: bool = False, + clamp: bool = False, + ) -> None: + super().__init__( + min=min, max=max, min_open=min_open, max_open=max_open, clamp=clamp + ) + + if (min_open or max_open) and clamp: + raise TypeError("Clamping is not supported for open bounds.") + + def _clamp(self, bound: float, dir: "te.Literal[1, -1]", open: bool) -> float: + if not open: + return bound + + # Could use Python 3.9's math.nextafter here, but clamping an + # open float range doesn't seem to be particularly useful. It's + # left up to the user to write a callback to do it if needed. + raise RuntimeError("Clamping is not supported for open bounds.") + + +class BoolParamType(ParamType): + name = "boolean" + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + if value in {False, True}: + return bool(value) + + norm = value.strip().lower() + + if norm in {"1", "true", "t", "yes", "y", "on"}: + return True + + if norm in {"0", "false", "f", "no", "n", "off"}: + return False + + self.fail( + _("{value!r} is not a valid boolean.").format(value=value), param, ctx + ) + + def __repr__(self) -> str: + return "BOOL" + + +class UUIDParameterType(ParamType): + name = "uuid" + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + import uuid + + if isinstance(value, uuid.UUID): + return value + + value = value.strip() + + try: + return uuid.UUID(value) + except ValueError: + self.fail( + _("{value!r} is not a valid UUID.").format(value=value), param, ctx + ) + + def __repr__(self) -> str: + return "UUID" + + +class File(ParamType): + """Declares a parameter to be a file for reading or writing. The file + is automatically closed once the context tears down (after the command + finished working). + + Files can be opened for reading or writing. The special value ``-`` + indicates stdin or stdout depending on the mode. + + By default, the file is opened for reading text data, but it can also be + opened in binary mode or for writing. The encoding parameter can be used + to force a specific encoding. + + The `lazy` flag controls if the file should be opened immediately or upon + first IO. The default is to be non-lazy for standard input and output + streams as well as files opened for reading, `lazy` otherwise. When opening a + file lazily for reading, it is still opened temporarily for validation, but + will not be held open until first IO. lazy is mainly useful when opening + for writing to avoid creating the file until it is needed. + + Starting with Click 2.0, files can also be opened atomically in which + case all writes go into a separate file in the same folder and upon + completion the file will be moved over to the original location. This + is useful if a file regularly read by other users is modified. + + See :ref:`file-args` for more information. + """ + + name = "filename" + envvar_list_splitter = os.path.pathsep + + def __init__( + self, + mode: str = "r", + encoding: t.Optional[str] = None, + errors: t.Optional[str] = "strict", + lazy: t.Optional[bool] = None, + atomic: bool = False, + ) -> None: + self.mode = mode + self.encoding = encoding + self.errors = errors + self.lazy = lazy + self.atomic = atomic + + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict.update(mode=self.mode, encoding=self.encoding) + return info_dict + + def resolve_lazy_flag(self, value: t.Any) -> bool: + if self.lazy is not None: + return self.lazy + if value == "-": + return False + elif "w" in self.mode: + return True + return False + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + try: + if hasattr(value, "read") or hasattr(value, "write"): + return value + + lazy = self.resolve_lazy_flag(value) + + if lazy: + f: t.IO = t.cast( + t.IO, + LazyFile( + value, self.mode, self.encoding, self.errors, atomic=self.atomic + ), + ) + + if ctx is not None: + ctx.call_on_close(f.close_intelligently) # type: ignore + + return f + + f, should_close = open_stream( + value, self.mode, self.encoding, self.errors, atomic=self.atomic + ) + + # If a context is provided, we automatically close the file + # at the end of the context execution (or flush out). If a + # context does not exist, it's the caller's responsibility to + # properly close the file. This for instance happens when the + # type is used with prompts. + if ctx is not None: + if should_close: + ctx.call_on_close(safecall(f.close)) + else: + ctx.call_on_close(safecall(f.flush)) + + return f + except OSError as e: # noqa: B014 + self.fail(f"'{os.fsdecode(value)}': {e.strerror}", param, ctx) + + def shell_complete( + self, ctx: "Context", param: "Parameter", incomplete: str + ) -> t.List["CompletionItem"]: + """Return a special completion marker that tells the completion + system to use the shell to provide file path completions. + + :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + return [CompletionItem(incomplete, type="file")] + + +class Path(ParamType): + """The ``Path`` type is similar to the :class:`File` type, but + returns the filename instead of an open file. Various checks can be + enabled to validate the type of file and permissions. + + :param exists: The file or directory needs to exist for the value to + be valid. If this is not set to ``True``, and the file does not + exist, then all further checks are silently skipped. + :param file_okay: Allow a file as a value. + :param dir_okay: Allow a directory as a value. + :param readable: if true, a readable check is performed. + :param writable: if true, a writable check is performed. + :param executable: if true, an executable check is performed. + :param resolve_path: Make the value absolute and resolve any + symlinks. A ``~`` is not expanded, as this is supposed to be + done by the shell only. + :param allow_dash: Allow a single dash as a value, which indicates + a standard stream (but does not open it). Use + :func:`~click.open_file` to handle opening this value. + :param path_type: Convert the incoming path value to this type. If + ``None``, keep Python's default, which is ``str``. Useful to + convert to :class:`pathlib.Path`. + + .. versionchanged:: 8.1 + Added the ``executable`` parameter. + + .. versionchanged:: 8.0 + Allow passing ``type=pathlib.Path``. + + .. versionchanged:: 6.0 + Added the ``allow_dash`` parameter. + """ + + envvar_list_splitter = os.path.pathsep + + def __init__( + self, + exists: bool = False, + file_okay: bool = True, + dir_okay: bool = True, + writable: bool = False, + readable: bool = True, + resolve_path: bool = False, + allow_dash: bool = False, + path_type: t.Optional[t.Type] = None, + executable: bool = False, + ): + self.exists = exists + self.file_okay = file_okay + self.dir_okay = dir_okay + self.readable = readable + self.writable = writable + self.executable = executable + self.resolve_path = resolve_path + self.allow_dash = allow_dash + self.type = path_type + + if self.file_okay and not self.dir_okay: + self.name = _("file") + elif self.dir_okay and not self.file_okay: + self.name = _("directory") + else: + self.name = _("path") + + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict.update( + exists=self.exists, + file_okay=self.file_okay, + dir_okay=self.dir_okay, + writable=self.writable, + readable=self.readable, + allow_dash=self.allow_dash, + ) + return info_dict + + def coerce_path_result(self, rv: t.Any) -> t.Any: + if self.type is not None and not isinstance(rv, self.type): + if self.type is str: + rv = os.fsdecode(rv) + elif self.type is bytes: + rv = os.fsencode(rv) + else: + rv = self.type(rv) + + return rv + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + rv = value + + is_dash = self.file_okay and self.allow_dash and rv in (b"-", "-") + + if not is_dash: + if self.resolve_path: + # os.path.realpath doesn't resolve symlinks on Windows + # until Python 3.8. Use pathlib for now. + import pathlib + + rv = os.fsdecode(pathlib.Path(rv).resolve()) + + try: + st = os.stat(rv) + except OSError: + if not self.exists: + return self.coerce_path_result(rv) + self.fail( + _("{name} {filename!r} does not exist.").format( + name=self.name.title(), filename=os.fsdecode(value) + ), + param, + ctx, + ) + + if not self.file_okay and stat.S_ISREG(st.st_mode): + self.fail( + _("{name} {filename!r} is a file.").format( + name=self.name.title(), filename=os.fsdecode(value) + ), + param, + ctx, + ) + if not self.dir_okay and stat.S_ISDIR(st.st_mode): + self.fail( + _("{name} '{filename}' is a directory.").format( + name=self.name.title(), filename=os.fsdecode(value) + ), + param, + ctx, + ) + + if self.readable and not os.access(rv, os.R_OK): + self.fail( + _("{name} {filename!r} is not readable.").format( + name=self.name.title(), filename=os.fsdecode(value) + ), + param, + ctx, + ) + + if self.writable and not os.access(rv, os.W_OK): + self.fail( + _("{name} {filename!r} is not writable.").format( + name=self.name.title(), filename=os.fsdecode(value) + ), + param, + ctx, + ) + + if self.executable and not os.access(value, os.X_OK): + self.fail( + _("{name} {filename!r} is not executable.").format( + name=self.name.title(), filename=os.fsdecode(value) + ), + param, + ctx, + ) + + return self.coerce_path_result(rv) + + def shell_complete( + self, ctx: "Context", param: "Parameter", incomplete: str + ) -> t.List["CompletionItem"]: + """Return a special completion marker that tells the completion + system to use the shell to provide path completions for only + directories or any paths. + + :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + type = "dir" if self.dir_okay and not self.file_okay else "file" + return [CompletionItem(incomplete, type=type)] + + +class Tuple(CompositeParamType): + """The default behavior of Click is to apply a type on a value directly. + This works well in most cases, except for when `nargs` is set to a fixed + count and different types should be used for different items. In this + case the :class:`Tuple` type can be used. This type can only be used + if `nargs` is set to a fixed number. + + For more information see :ref:`tuple-type`. + + This can be selected by using a Python tuple literal as a type. + + :param types: a list of types that should be used for the tuple items. + """ + + def __init__(self, types: t.Sequence[t.Union[t.Type, ParamType]]) -> None: + self.types = [convert_type(ty) for ty in types] + + def to_info_dict(self) -> t.Dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict["types"] = [t.to_info_dict() for t in self.types] + return info_dict + + @property + def name(self) -> str: # type: ignore + return f"<{' '.join(ty.name for ty in self.types)}>" + + @property + def arity(self) -> int: # type: ignore + return len(self.types) + + def convert( + self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"] + ) -> t.Any: + len_type = len(self.types) + len_value = len(value) + + if len_value != len_type: + self.fail( + ngettext( + "{len_type} values are required, but {len_value} was given.", + "{len_type} values are required, but {len_value} were given.", + len_value, + ).format(len_type=len_type, len_value=len_value), + param=param, + ctx=ctx, + ) + + return tuple(ty(x, param, ctx) for ty, x in zip(self.types, value)) + + +def convert_type(ty: t.Optional[t.Any], default: t.Optional[t.Any] = None) -> ParamType: + """Find the most appropriate :class:`ParamType` for the given Python + type. If the type isn't provided, it can be inferred from a default + value. + """ + guessed_type = False + + if ty is None and default is not None: + if isinstance(default, (tuple, list)): + # If the default is empty, ty will remain None and will + # return STRING. + if default: + item = default[0] + + # A tuple of tuples needs to detect the inner types. + # Can't call convert recursively because that would + # incorrectly unwind the tuple to a single type. + if isinstance(item, (tuple, list)): + ty = tuple(map(type, item)) + else: + ty = type(item) + else: + ty = type(default) + + guessed_type = True + + if isinstance(ty, tuple): + return Tuple(ty) + + if isinstance(ty, ParamType): + return ty + + if ty is str or ty is None: + return STRING + + if ty is int: + return INT + + if ty is float: + return FLOAT + + if ty is bool: + return BOOL + + if guessed_type: + return STRING + + if __debug__: + try: + if issubclass(ty, ParamType): + raise AssertionError( + f"Attempted to use an uninstantiated parameter type ({ty})." + ) + except TypeError: + # ty is an instance (correct), so issubclass fails. + pass + + return FuncParamType(ty) + + +#: A dummy parameter type that just does nothing. From a user's +#: perspective this appears to just be the same as `STRING` but +#: internally no string conversion takes place if the input was bytes. +#: This is usually useful when working with file paths as they can +#: appear in bytes and unicode. +#: +#: For path related uses the :class:`Path` type is a better choice but +#: there are situations where an unprocessed type is useful which is why +#: it is is provided. +#: +#: .. versionadded:: 4.0 +UNPROCESSED = UnprocessedParamType() + +#: A unicode string parameter type which is the implicit default. This +#: can also be selected by using ``str`` as type. +STRING = StringParamType() + +#: An integer parameter. This can also be selected by using ``int`` as +#: type. +INT = IntParamType() + +#: A floating point value parameter. This can also be selected by using +#: ``float`` as type. +FLOAT = FloatParamType() + +#: A boolean parameter. This is the default for boolean flags. This can +#: also be selected by using ``bool`` as a type. +BOOL = BoolParamType() + +#: A UUID parameter. +UUID = UUIDParameterType() diff --git a/.vtodo/Lib/site-packages/click/utils.py b/.vtodo/Lib/site-packages/click/utils.py new file mode 100644 index 0000000..8283788 --- /dev/null +++ b/.vtodo/Lib/site-packages/click/utils.py @@ -0,0 +1,580 @@ +import os +import re +import sys +import typing as t +from functools import update_wrapper +from types import ModuleType + +from ._compat import _default_text_stderr +from ._compat import _default_text_stdout +from ._compat import _find_binary_writer +from ._compat import auto_wrap_for_ansi +from ._compat import binary_streams +from ._compat import get_filesystem_encoding +from ._compat import open_stream +from ._compat import should_strip_ansi +from ._compat import strip_ansi +from ._compat import text_streams +from ._compat import WIN +from .globals import resolve_color_default + +if t.TYPE_CHECKING: + import typing_extensions as te + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) + + +def _posixify(name: str) -> str: + return "-".join(name.split()).lower() + + +def safecall(func: F) -> F: + """Wraps a function so that it swallows exceptions.""" + + def wrapper(*args, **kwargs): # type: ignore + try: + return func(*args, **kwargs) + except Exception: + pass + + return update_wrapper(t.cast(F, wrapper), func) + + +def make_str(value: t.Any) -> str: + """Converts a value into a valid string.""" + if isinstance(value, bytes): + try: + return value.decode(get_filesystem_encoding()) + except UnicodeError: + return value.decode("utf-8", "replace") + return str(value) + + +def make_default_short_help(help: str, max_length: int = 45) -> str: + """Returns a condensed version of help string.""" + # Consider only the first paragraph. + paragraph_end = help.find("\n\n") + + if paragraph_end != -1: + help = help[:paragraph_end] + + # Collapse newlines, tabs, and spaces. + words = help.split() + + if not words: + return "" + + # The first paragraph started with a "no rewrap" marker, ignore it. + if words[0] == "\b": + words = words[1:] + + total_length = 0 + last_index = len(words) - 1 + + for i, word in enumerate(words): + total_length += len(word) + (i > 0) + + if total_length > max_length: # too long, truncate + break + + if word[-1] == ".": # sentence end, truncate without "..." + return " ".join(words[: i + 1]) + + if total_length == max_length and i != last_index: + break # not at sentence end, truncate with "..." + else: + return " ".join(words) # no truncation needed + + # Account for the length of the suffix. + total_length += len("...") + + # remove words until the length is short enough + while i > 0: + total_length -= len(words[i]) + (i > 0) + + if total_length <= max_length: + break + + i -= 1 + + return " ".join(words[:i]) + "..." + + +class LazyFile: + """A lazy file works like a regular file but it does not fully open + the file but it does perform some basic checks early to see if the + filename parameter does make sense. This is useful for safely opening + files for writing. + """ + + def __init__( + self, + filename: str, + mode: str = "r", + encoding: t.Optional[str] = None, + errors: t.Optional[str] = "strict", + atomic: bool = False, + ): + self.name = filename + self.mode = mode + self.encoding = encoding + self.errors = errors + self.atomic = atomic + self._f: t.Optional[t.IO] + + if filename == "-": + self._f, self.should_close = open_stream(filename, mode, encoding, errors) + else: + if "r" in mode: + # Open and close the file in case we're opening it for + # reading so that we can catch at least some errors in + # some cases early. + open(filename, mode).close() + self._f = None + self.should_close = True + + def __getattr__(self, name: str) -> t.Any: + return getattr(self.open(), name) + + def __repr__(self) -> str: + if self._f is not None: + return repr(self._f) + return f"" + + def open(self) -> t.IO: + """Opens the file if it's not yet open. This call might fail with + a :exc:`FileError`. Not handling this error will produce an error + that Click shows. + """ + if self._f is not None: + return self._f + try: + rv, self.should_close = open_stream( + self.name, self.mode, self.encoding, self.errors, atomic=self.atomic + ) + except OSError as e: # noqa: E402 + from .exceptions import FileError + + raise FileError(self.name, hint=e.strerror) from e + self._f = rv + return rv + + def close(self) -> None: + """Closes the underlying file, no matter what.""" + if self._f is not None: + self._f.close() + + def close_intelligently(self) -> None: + """This function only closes the file if it was opened by the lazy + file wrapper. For instance this will never close stdin. + """ + if self.should_close: + self.close() + + def __enter__(self) -> "LazyFile": + return self + + def __exit__(self, exc_type, exc_value, tb): # type: ignore + self.close_intelligently() + + def __iter__(self) -> t.Iterator[t.AnyStr]: + self.open() + return iter(self._f) # type: ignore + + +class KeepOpenFile: + def __init__(self, file: t.IO) -> None: + self._file = file + + def __getattr__(self, name: str) -> t.Any: + return getattr(self._file, name) + + def __enter__(self) -> "KeepOpenFile": + return self + + def __exit__(self, exc_type, exc_value, tb): # type: ignore + pass + + def __repr__(self) -> str: + return repr(self._file) + + def __iter__(self) -> t.Iterator[t.AnyStr]: + return iter(self._file) + + +def echo( + message: t.Optional[t.Any] = None, + file: t.Optional[t.IO[t.Any]] = None, + nl: bool = True, + err: bool = False, + color: t.Optional[bool] = None, +) -> None: + """Print a message and newline to stdout or a file. This should be + used instead of :func:`print` because it provides better support + for different data, files, and environments. + + Compared to :func:`print`, this does the following: + + - Ensures that the output encoding is not misconfigured on Linux. + - Supports Unicode in the Windows console. + - Supports writing to binary outputs, and supports writing bytes + to text outputs. + - Supports colors and styles on Windows. + - Removes ANSI color and style codes if the output does not look + like an interactive terminal. + - Always flushes the output. + + :param message: The string or bytes to output. Other objects are + converted to strings. + :param file: The file to write to. Defaults to ``stdout``. + :param err: Write to ``stderr`` instead of ``stdout``. + :param nl: Print a newline after the message. Enabled by default. + :param color: Force showing or hiding colors and other styles. By + default Click will remove color if the output does not look like + an interactive terminal. + + .. versionchanged:: 6.0 + Support Unicode output on the Windows console. Click does not + modify ``sys.stdout``, so ``sys.stdout.write()`` and ``print()`` + will still not support Unicode. + + .. versionchanged:: 4.0 + Added the ``color`` parameter. + + .. versionadded:: 3.0 + Added the ``err`` parameter. + + .. versionchanged:: 2.0 + Support colors on Windows if colorama is installed. + """ + if file is None: + if err: + file = _default_text_stderr() + else: + file = _default_text_stdout() + + # Convert non bytes/text into the native string type. + if message is not None and not isinstance(message, (str, bytes, bytearray)): + out: t.Optional[t.Union[str, bytes]] = str(message) + else: + out = message + + if nl: + out = out or "" + if isinstance(out, str): + out += "\n" + else: + out += b"\n" + + if not out: + file.flush() + return + + # If there is a message and the value looks like bytes, we manually + # need to find the binary stream and write the message in there. + # This is done separately so that most stream types will work as you + # would expect. Eg: you can write to StringIO for other cases. + if isinstance(out, (bytes, bytearray)): + binary_file = _find_binary_writer(file) + + if binary_file is not None: + file.flush() + binary_file.write(out) + binary_file.flush() + return + + # ANSI style code support. For no message or bytes, nothing happens. + # When outputting to a file instead of a terminal, strip codes. + else: + color = resolve_color_default(color) + + if should_strip_ansi(file, color): + out = strip_ansi(out) + elif WIN: + if auto_wrap_for_ansi is not None: + file = auto_wrap_for_ansi(file) # type: ignore + elif not color: + out = strip_ansi(out) + + file.write(out) # type: ignore + file.flush() + + +def get_binary_stream(name: "te.Literal['stdin', 'stdout', 'stderr']") -> t.BinaryIO: + """Returns a system stream for byte processing. + + :param name: the name of the stream to open. Valid names are ``'stdin'``, + ``'stdout'`` and ``'stderr'`` + """ + opener = binary_streams.get(name) + if opener is None: + raise TypeError(f"Unknown standard stream '{name}'") + return opener() + + +def get_text_stream( + name: "te.Literal['stdin', 'stdout', 'stderr']", + encoding: t.Optional[str] = None, + errors: t.Optional[str] = "strict", +) -> t.TextIO: + """Returns a system stream for text processing. This usually returns + a wrapped stream around a binary stream returned from + :func:`get_binary_stream` but it also can take shortcuts for already + correctly configured streams. + + :param name: the name of the stream to open. Valid names are ``'stdin'``, + ``'stdout'`` and ``'stderr'`` + :param encoding: overrides the detected default encoding. + :param errors: overrides the default error mode. + """ + opener = text_streams.get(name) + if opener is None: + raise TypeError(f"Unknown standard stream '{name}'") + return opener(encoding, errors) + + +def open_file( + filename: str, + mode: str = "r", + encoding: t.Optional[str] = None, + errors: t.Optional[str] = "strict", + lazy: bool = False, + atomic: bool = False, +) -> t.IO: + """Open a file, with extra behavior to handle ``'-'`` to indicate + a standard stream, lazy open on write, and atomic write. Similar to + the behavior of the :class:`~click.File` param type. + + If ``'-'`` is given to open ``stdout`` or ``stdin``, the stream is + wrapped so that using it in a context manager will not close it. + This makes it possible to use the function without accidentally + closing a standard stream: + + .. code-block:: python + + with open_file(filename) as f: + ... + + :param filename: The name of the file to open, or ``'-'`` for + ``stdin``/``stdout``. + :param mode: The mode in which to open the file. + :param encoding: The encoding to decode or encode a file opened in + text mode. + :param errors: The error handling mode. + :param lazy: Wait to open the file until it is accessed. For read + mode, the file is temporarily opened to raise access errors + early, then closed until it is read again. + :param atomic: Write to a temporary file and replace the given file + on close. + + .. versionadded:: 3.0 + """ + if lazy: + return t.cast(t.IO, LazyFile(filename, mode, encoding, errors, atomic=atomic)) + + f, should_close = open_stream(filename, mode, encoding, errors, atomic=atomic) + + if not should_close: + f = t.cast(t.IO, KeepOpenFile(f)) + + return f + + +def format_filename( + filename: t.Union[str, bytes, os.PathLike], shorten: bool = False +) -> str: + """Formats a filename for user display. The main purpose of this + function is to ensure that the filename can be displayed at all. This + will decode the filename to unicode if necessary in a way that it will + not fail. Optionally, it can shorten the filename to not include the + full path to the filename. + + :param filename: formats a filename for UI display. This will also convert + the filename into unicode without failing. + :param shorten: this optionally shortens the filename to strip of the + path that leads up to it. + """ + if shorten: + filename = os.path.basename(filename) + + return os.fsdecode(filename) + + +def get_app_dir(app_name: str, roaming: bool = True, force_posix: bool = False) -> str: + r"""Returns the config folder for the application. The default behavior + is to return whatever is most appropriate for the operating system. + + To give you an idea, for an app called ``"Foo Bar"``, something like + the following folders could be returned: + + Mac OS X: + ``~/Library/Application Support/Foo Bar`` + Mac OS X (POSIX): + ``~/.foo-bar`` + Unix: + ``~/.config/foo-bar`` + Unix (POSIX): + ``~/.foo-bar`` + Windows (roaming): + ``C:\Users\\AppData\Roaming\Foo Bar`` + Windows (not roaming): + ``C:\Users\\AppData\Local\Foo Bar`` + + .. versionadded:: 2.0 + + :param app_name: the application name. This should be properly capitalized + and can contain whitespace. + :param roaming: controls if the folder should be roaming or not on Windows. + Has no affect otherwise. + :param force_posix: if this is set to `True` then on any POSIX system the + folder will be stored in the home folder with a leading + dot instead of the XDG config home or darwin's + application support folder. + """ + if WIN: + key = "APPDATA" if roaming else "LOCALAPPDATA" + folder = os.environ.get(key) + if folder is None: + folder = os.path.expanduser("~") + return os.path.join(folder, app_name) + if force_posix: + return os.path.join(os.path.expanduser(f"~/.{_posixify(app_name)}")) + if sys.platform == "darwin": + return os.path.join( + os.path.expanduser("~/Library/Application Support"), app_name + ) + return os.path.join( + os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")), + _posixify(app_name), + ) + + +class PacifyFlushWrapper: + """This wrapper is used to catch and suppress BrokenPipeErrors resulting + from ``.flush()`` being called on broken pipe during the shutdown/final-GC + of the Python interpreter. Notably ``.flush()`` is always called on + ``sys.stdout`` and ``sys.stderr``. So as to have minimal impact on any + other cleanup code, and the case where the underlying file is not a broken + pipe, all calls and attributes are proxied. + """ + + def __init__(self, wrapped: t.IO) -> None: + self.wrapped = wrapped + + def flush(self) -> None: + try: + self.wrapped.flush() + except OSError as e: + import errno + + if e.errno != errno.EPIPE: + raise + + def __getattr__(self, attr: str) -> t.Any: + return getattr(self.wrapped, attr) + + +def _detect_program_name( + path: t.Optional[str] = None, _main: t.Optional[ModuleType] = None +) -> str: + """Determine the command used to run the program, for use in help + text. If a file or entry point was executed, the file name is + returned. If ``python -m`` was used to execute a module or package, + ``python -m name`` is returned. + + This doesn't try to be too precise, the goal is to give a concise + name for help text. Files are only shown as their name without the + path. ``python`` is only shown for modules, and the full path to + ``sys.executable`` is not shown. + + :param path: The Python file being executed. Python puts this in + ``sys.argv[0]``, which is used by default. + :param _main: The ``__main__`` module. This should only be passed + during internal testing. + + .. versionadded:: 8.0 + Based on command args detection in the Werkzeug reloader. + + :meta private: + """ + if _main is None: + _main = sys.modules["__main__"] + + if not path: + path = sys.argv[0] + + # The value of __package__ indicates how Python was called. It may + # not exist if a setuptools script is installed as an egg. It may be + # set incorrectly for entry points created with pip on Windows. + if getattr(_main, "__package__", None) is None or ( + os.name == "nt" + and _main.__package__ == "" + and not os.path.exists(path) + and os.path.exists(f"{path}.exe") + ): + # Executed a file, like "python app.py". + return os.path.basename(path) + + # Executed a module, like "python -m example". + # Rewritten by Python from "-m script" to "/path/to/script.py". + # Need to look at main module to determine how it was executed. + py_module = t.cast(str, _main.__package__) + name = os.path.splitext(os.path.basename(path))[0] + + # A submodule like "example.cli". + if name != "__main__": + py_module = f"{py_module}.{name}" + + return f"python -m {py_module.lstrip('.')}" + + +def _expand_args( + args: t.Iterable[str], + *, + user: bool = True, + env: bool = True, + glob_recursive: bool = True, +) -> t.List[str]: + """Simulate Unix shell expansion with Python functions. + + See :func:`glob.glob`, :func:`os.path.expanduser`, and + :func:`os.path.expandvars`. + + This is intended for use on Windows, where the shell does not do any + expansion. It may not exactly match what a Unix shell would do. + + :param args: List of command line arguments to expand. + :param user: Expand user home directory. + :param env: Expand environment variables. + :param glob_recursive: ``**`` matches directories recursively. + + .. versionchanged:: 8.1 + Invalid glob patterns are treated as empty expansions rather + than raising an error. + + .. versionadded:: 8.0 + + :meta private: + """ + from glob import glob + + out = [] + + for arg in args: + if user: + arg = os.path.expanduser(arg) + + if env: + arg = os.path.expandvars(arg) + + try: + matches = glob(arg, recursive=glob_recursive) + except re.error: + matches = [] + + if not matches: + out.append(arg) + else: + out.extend(matches) + + return out diff --git a/.vtodo/Lib/site-packages/colorama-0.4.5.dist-info/INSTALLER b/.vtodo/Lib/site-packages/colorama-0.4.5.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.vtodo/Lib/site-packages/colorama-0.4.5.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.vtodo/Lib/site-packages/colorama-0.4.5.dist-info/LICENSE.txt b/.vtodo/Lib/site-packages/colorama-0.4.5.dist-info/LICENSE.txt new file mode 100644 index 0000000..3105888 --- /dev/null +++ b/.vtodo/Lib/site-packages/colorama-0.4.5.dist-info/LICENSE.txt @@ -0,0 +1,27 @@ +Copyright (c) 2010 Jonathan Hartley +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holders, nor those 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/.vtodo/Lib/site-packages/colorama-0.4.5.dist-info/METADATA b/.vtodo/Lib/site-packages/colorama-0.4.5.dist-info/METADATA new file mode 100644 index 0000000..cd93935 --- /dev/null +++ b/.vtodo/Lib/site-packages/colorama-0.4.5.dist-info/METADATA @@ -0,0 +1,411 @@ +Metadata-Version: 2.1 +Name: colorama +Version: 0.4.5 +Summary: Cross-platform colored terminal text. +Home-page: https://github.com/tartley/colorama +Author: Jonathan Hartley +Author-email: tartley@tartley.com +Maintainer: Arnon Yaari +License: BSD +Keywords: color colour terminal text ansi windows crossplatform xplatform +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Console +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Terminals +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* +License-File: LICENSE.txt + +.. image:: https://img.shields.io/pypi/v/colorama.svg + :target: https://pypi.org/project/colorama/ + :alt: Latest Version + +.. image:: https://img.shields.io/pypi/pyversions/colorama.svg + :target: https://pypi.org/project/colorama/ + :alt: Supported Python versions + +.. image:: https://github.com/tartley/colorama/actions/workflows/test.yml/badge.svg + :target: https://github.com/tartley/colorama/actions/workflows/test.yml + :alt: Build Status + +Colorama +======== + +Makes ANSI escape character sequences (for producing colored terminal text and +cursor positioning) work under MS Windows. + +.. |donate| image:: https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif + :target: https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=2MZ9D2GMLYCUJ&item_name=Colorama¤cy_code=USD + :alt: Donate with Paypal + +`PyPI for releases `_ | +`Github for source `_ | +`Colorama for enterprise on Tidelift `_ + +If you find Colorama useful, please |donate| to the authors. Thank you! + + +Installation +------------ + +Tested on CPython 2.7, 3.5, 3.6, 3.7, 3.8, 3.9 and 3.10 and Pypy 2.7 and 3.6. + +No requirements other than the standard library. + +.. code-block:: bash + + pip install colorama + # or + conda install -c anaconda colorama + + +Description +----------- + +ANSI escape character sequences have long been used to produce colored terminal +text and cursor positioning on Unix and Macs. Colorama makes this work on +Windows, too, by wrapping ``stdout``, stripping ANSI sequences it finds (which +would appear as gobbledygook in the output), and converting them into the +appropriate win32 calls to modify the state of the terminal. On other platforms, +Colorama does nothing. + +This has the upshot of providing a simple cross-platform API for printing +colored terminal text from Python, and has the happy side-effect that existing +applications or libraries which use ANSI sequences to produce colored output on +Linux or Macs can now also work on Windows, simply by calling +``colorama.init()``. + +An alternative approach is to install ``ansi.sys`` on Windows machines, which +provides the same behaviour for all applications running in terminals. Colorama +is intended for situations where that isn't easy (e.g., maybe your app doesn't +have an installer.) + +Demo scripts in the source code repository print some colored text using +ANSI sequences. Compare their output under Gnome-terminal's built in ANSI +handling, versus on Windows Command-Prompt using Colorama: + +.. image:: https://github.com/tartley/colorama/raw/master/screenshots/ubuntu-demo.png + :width: 661 + :height: 357 + :alt: ANSI sequences on Ubuntu under gnome-terminal. + +.. image:: https://github.com/tartley/colorama/raw/master/screenshots/windows-demo.png + :width: 668 + :height: 325 + :alt: Same ANSI sequences on Windows, using Colorama. + +These screenshots show that, on Windows, Colorama does not support ANSI 'dim +text'; it looks the same as 'normal text'. + +Usage +----- + +Initialisation +.............. + +Applications should initialise Colorama using: + +.. code-block:: python + + from colorama import init + init() + +On Windows, calling ``init()`` will filter ANSI escape sequences out of any +text sent to ``stdout`` or ``stderr``, and replace them with equivalent Win32 +calls. + +On other platforms, calling ``init()`` has no effect (unless you request other +optional functionality, see "Init Keyword Args" below; or if output +is redirected). By design, this permits applications to call ``init()`` +unconditionally on all platforms, after which ANSI output should just work. + +On all platforms, if output is redirected, ANSI escape sequences are completely +stripped out. + +To stop using Colorama before your program exits, simply call ``deinit()``. +This will restore ``stdout`` and ``stderr`` to their original values, so that +Colorama is disabled. To resume using Colorama again, call ``reinit()``; it is +cheaper than calling ``init()`` again (but does the same thing). + + +Colored Output +.............. + +Cross-platform printing of colored text can then be done using Colorama's +constant shorthand for ANSI escape sequences. These are deliberately +rudimentary, see below. + +.. code-block:: python + + from colorama import Fore, Back, Style + print(Fore.RED + 'some red text') + print(Back.GREEN + 'and with a green background') + print(Style.DIM + 'and in dim text') + print(Style.RESET_ALL) + print('back to normal now') + +...or simply by manually printing ANSI sequences from your own code: + +.. code-block:: python + + print('\033[31m' + 'some red text') + print('\033[39m') # and reset to default color + +...or, Colorama can be used in conjunction with existing ANSI libraries +such as the venerable `Termcolor `_ +the fabulous `Blessings `_, +or the incredible `_Rich `_. + +If you wish Colorama's Fore, Back and Style constants were more capable, +then consider using one of the above highly capable libraries to generate +colors, etc, and use Colorama just for its primary purpose: to convert +those ANSI sequences to also work on Windows: + +.. code-block:: python + + from colorama import init + from termcolor import colored + + # use Colorama to make Termcolor work on Windows too + init() + + # then use Termcolor for all colored text output + print(colored('Hello, World!', 'green', 'on_red')) + +Available formatting constants are:: + + Fore: BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET. + Back: BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET. + Style: DIM, NORMAL, BRIGHT, RESET_ALL + +``Style.RESET_ALL`` resets foreground, background, and brightness. Colorama will +perform this reset automatically on program exit. + +These are fairly well supported, but not part of the standard:: + + Fore: LIGHTBLACK_EX, LIGHTRED_EX, LIGHTGREEN_EX, LIGHTYELLOW_EX, LIGHTBLUE_EX, LIGHTMAGENTA_EX, LIGHTCYAN_EX, LIGHTWHITE_EX + Back: LIGHTBLACK_EX, LIGHTRED_EX, LIGHTGREEN_EX, LIGHTYELLOW_EX, LIGHTBLUE_EX, LIGHTMAGENTA_EX, LIGHTCYAN_EX, LIGHTWHITE_EX + + +Cursor Positioning +.................. + +ANSI codes to reposition the cursor are supported. See ``demos/demo06.py`` for +an example of how to generate them. + + +Init Keyword Args +................. + +``init()`` accepts some ``**kwargs`` to override default behaviour. + +init(autoreset=False): + If you find yourself repeatedly sending reset sequences to turn off color + changes at the end of every print, then ``init(autoreset=True)`` will + automate that: + + .. code-block:: python + + from colorama import init + init(autoreset=True) + print(Fore.RED + 'some red text') + print('automatically back to default color again') + +init(strip=None): + Pass ``True`` or ``False`` to override whether ANSI codes should be + stripped from the output. The default behaviour is to strip if on Windows + or if output is redirected (not a tty). + +init(convert=None): + Pass ``True`` or ``False`` to override whether to convert ANSI codes in the + output into win32 calls. The default behaviour is to convert if on Windows + and output is to a tty (terminal). + +init(wrap=True): + On Windows, Colorama works by replacing ``sys.stdout`` and ``sys.stderr`` + with proxy objects, which override the ``.write()`` method to do their work. + If this wrapping causes you problems, then this can be disabled by passing + ``init(wrap=False)``. The default behaviour is to wrap if ``autoreset`` or + ``strip`` or ``convert`` are True. + + When wrapping is disabled, colored printing on non-Windows platforms will + continue to work as normal. To do cross-platform colored output, you can + use Colorama's ``AnsiToWin32`` proxy directly: + + .. code-block:: python + + import sys + from colorama import init, AnsiToWin32 + init(wrap=False) + stream = AnsiToWin32(sys.stderr).stream + + # Python 2 + print >>stream, Fore.BLUE + 'blue text on stderr' + + # Python 3 + print(Fore.BLUE + 'blue text on stderr', file=stream) + + +Recognised ANSI Sequences +......................... + +ANSI sequences generally take the form:: + + ESC [ ; ... + +Where ```` is an integer, and ```` is a single letter. Zero or +more params are passed to a ````. If no params are passed, it is +generally synonymous with passing a single zero. No spaces exist in the +sequence; they have been inserted here simply to read more easily. + +The only ANSI sequences that Colorama converts into win32 calls are:: + + ESC [ 0 m # reset all (colors and brightness) + ESC [ 1 m # bright + ESC [ 2 m # dim (looks same as normal brightness) + ESC [ 22 m # normal brightness + + # FOREGROUND: + ESC [ 30 m # black + ESC [ 31 m # red + ESC [ 32 m # green + ESC [ 33 m # yellow + ESC [ 34 m # blue + ESC [ 35 m # magenta + ESC [ 36 m # cyan + ESC [ 37 m # white + ESC [ 39 m # reset + + # BACKGROUND + ESC [ 40 m # black + ESC [ 41 m # red + ESC [ 42 m # green + ESC [ 43 m # yellow + ESC [ 44 m # blue + ESC [ 45 m # magenta + ESC [ 46 m # cyan + ESC [ 47 m # white + ESC [ 49 m # reset + + # cursor positioning + ESC [ y;x H # position cursor at x across, y down + ESC [ y;x f # position cursor at x across, y down + ESC [ n A # move cursor n lines up + ESC [ n B # move cursor n lines down + ESC [ n C # move cursor n characters forward + ESC [ n D # move cursor n characters backward + + # clear the screen + ESC [ mode J # clear the screen + + # clear the line + ESC [ mode K # clear the line + +Multiple numeric params to the ``'m'`` command can be combined into a single +sequence:: + + ESC [ 36 ; 45 ; 1 m # bright cyan text on magenta background + +All other ANSI sequences of the form ``ESC [ ; ... `` +are silently stripped from the output on Windows. + +Any other form of ANSI sequence, such as single-character codes or alternative +initial characters, are not recognised or stripped. It would be cool to add +them though. Let me know if it would be useful for you, via the Issues on +GitHub. + + +Status & Known Problems +----------------------- + +I've personally only tested it on Windows XP (CMD, Console2), Ubuntu +(gnome-terminal, xterm), and OS X. + +Some presumably valid ANSI sequences aren't recognised (see details below), +but to my knowledge nobody has yet complained about this. Puzzling. + +See outstanding issues and wish-list: +https://github.com/tartley/colorama/issues + +If anything doesn't work for you, or doesn't do what you expected or hoped for, +I'd love to hear about it on that issues list, would be delighted by patches, +and would be happy to grant commit access to anyone who submits a working patch +or two. + +If you're hacking on the code, see `README-hacking.md`_. + +.. _README-hacking.md: README-hacking.md + + +License +------- + +Copyright Jonathan Hartley & Arnon Yaari, 2013-2020. BSD 3-Clause license; see +LICENSE file. + + +Professional support +-------------------- + +.. |tideliftlogo| image:: https://cdn2.hubspot.net/hubfs/4008838/website/logos/logos_for_download/Tidelift_primary-shorthand-logo.png + :alt: Tidelift + :target: https://tidelift.com/subscription/pkg/pypi-colorama?utm_source=pypi-colorama&utm_medium=referral&utm_campaign=readme + +.. list-table:: + :widths: 10 100 + + * - |tideliftlogo| + - Professional support for colorama is available as part of the + `Tidelift Subscription`_. + Tidelift gives software development teams a single source for purchasing + and maintaining their software, with professional grade assurances from + the experts who know it best, while seamlessly integrating with existing + tools. + +.. _Tidelift Subscription: https://tidelift.com/subscription/pkg/pypi-colorama?utm_source=pypi-colorama&utm_medium=referral&utm_campaign=readme + + +Thanks +------ + +* Marc Schlaich (schlamar) for a ``setup.py`` fix for Python2.5. +* Marc Abramowitz, reported & fixed a crash on exit with closed ``stdout``, + providing a solution to issue #7's setuptools/distutils debate, + and other fixes. +* User 'eryksun', for guidance on correctly instantiating ``ctypes.windll``. +* Matthew McCormick for politely pointing out a longstanding crash on non-Win. +* Ben Hoyt, for a magnificent fix under 64-bit Windows. +* Jesse at Empty Square for submitting a fix for examples in the README. +* User 'jamessp', an observant documentation fix for cursor positioning. +* User 'vaal1239', Dave Mckee & Lackner Kristof for a tiny but much-needed Win7 + fix. +* Julien Stuyck, for wisely suggesting Python3 compatible updates to README. +* Daniel Griffith for multiple fabulous patches. +* Oscar Lesta for a valuable fix to stop ANSI chars being sent to non-tty + output. +* Roger Binns, for many suggestions, valuable feedback, & bug reports. +* Tim Golden for thought and much appreciated feedback on the initial idea. +* User 'Zearin' for updates to the README file. +* John Szakmeister for adding support for light colors +* Charles Merriam for adding documentation to demos +* Jurko for a fix on 64-bit Windows CPython2.5 w/o ctypes +* Florian Bruhin for a fix when stdout or stderr are None +* Thomas Weininger for fixing ValueError on Windows +* Remi Rampin for better Github integration and fixes to the README file +* Simeon Visser for closing a file handle using 'with' and updating classifiers + to include Python 3.3 and 3.4 +* Andy Neff for fixing RESET of LIGHT_EX colors. +* Jonathan Hartley for the initial idea and implementation. diff --git a/.vtodo/Lib/site-packages/colorama-0.4.5.dist-info/RECORD b/.vtodo/Lib/site-packages/colorama-0.4.5.dist-info/RECORD new file mode 100644 index 0000000..59f4d9f --- /dev/null +++ b/.vtodo/Lib/site-packages/colorama-0.4.5.dist-info/RECORD @@ -0,0 +1,18 @@ +colorama-0.4.5.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +colorama-0.4.5.dist-info/LICENSE.txt,sha256=ysNcAmhuXQSlpxQL-zs25zrtSWZW6JEQLkKIhteTAxg,1491 +colorama-0.4.5.dist-info/METADATA,sha256=Kb6MoYzWBmkPhFCf0SW7a-5Eeyssj-szefJmxokQFSU,15128 +colorama-0.4.5.dist-info/RECORD,, +colorama-0.4.5.dist-info/WHEEL,sha256=z9j0xAa_JmUKMpmz72K0ZGALSM_n-wQVmGbleXx2VHg,110 +colorama-0.4.5.dist-info/top_level.txt,sha256=_Kx6-Cni2BT1PEATPhrSRxo0d7kSgfBbHf5o7IF1ABw,9 +colorama/__init__.py,sha256=ihDoWQOkapwF7sqQ99AoDoEF3vGYm40OtmgW211cLZw,239 +colorama/__pycache__/__init__.cpython-310.pyc,, +colorama/__pycache__/ansi.cpython-310.pyc,, +colorama/__pycache__/ansitowin32.cpython-310.pyc,, +colorama/__pycache__/initialise.cpython-310.pyc,, +colorama/__pycache__/win32.cpython-310.pyc,, +colorama/__pycache__/winterm.cpython-310.pyc,, +colorama/ansi.py,sha256=Top4EeEuaQdBWdteKMEcGOTeKeF19Q-Wo_6_Cj5kOzQ,2522 +colorama/ansitowin32.py,sha256=gGrO7MVtwc-j1Sq3jKfZpERT1JWmYSOsTVDiTnFbZU4,10830 +colorama/initialise.py,sha256=PprovDNxMTrvoNHFcL2NZjpH2XzDc8BLxLxiErfUl4k,1915 +colorama/win32.py,sha256=bJ8Il9jwaBN5BJ8bmN6FoYZ1QYuMKv2j8fGrXh7TJjw,5404 +colorama/winterm.py,sha256=2y_2b7Zsv34feAsP67mLOVc-Bgq51mdYGo571VprlrM,6438 diff --git a/.vtodo/Lib/site-packages/colorama-0.4.5.dist-info/WHEEL b/.vtodo/Lib/site-packages/colorama-0.4.5.dist-info/WHEEL new file mode 100644 index 0000000..0b18a28 --- /dev/null +++ b/.vtodo/Lib/site-packages/colorama-0.4.5.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.37.1) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/.vtodo/Lib/site-packages/colorama-0.4.5.dist-info/top_level.txt b/.vtodo/Lib/site-packages/colorama-0.4.5.dist-info/top_level.txt new file mode 100644 index 0000000..3fcfb51 --- /dev/null +++ b/.vtodo/Lib/site-packages/colorama-0.4.5.dist-info/top_level.txt @@ -0,0 +1 @@ +colorama diff --git a/.vtodo/Lib/site-packages/colorama/__init__.py b/.vtodo/Lib/site-packages/colorama/__init__.py new file mode 100644 index 0000000..9138a8c --- /dev/null +++ b/.vtodo/Lib/site-packages/colorama/__init__.py @@ -0,0 +1,6 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +from .initialise import init, deinit, reinit, colorama_text +from .ansi import Fore, Back, Style, Cursor +from .ansitowin32 import AnsiToWin32 + +__version__ = '0.4.5' diff --git a/.vtodo/Lib/site-packages/colorama/__pycache__/__init__.cpython-310.pyc b/.vtodo/Lib/site-packages/colorama/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..730a8349d174980b1e4b324b3876df39adf473c9 GIT binary patch literal 424 zcmYjN!Ab)$5Z!FE+ikat*M5MPJ#-bT7ZFh_oXj9Vi9(zMUP@`|yFK!pNFa zt5?f~=8ihu=*dQK>u9wGLa*h523av#hAH@?u=H=rJ;tC{#;WDUXD3QGt5%%ChAcES KMq@NWS^NXUDsi^} literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/colorama/__pycache__/ansi.cpython-310.pyc b/.vtodo/Lib/site-packages/colorama/__pycache__/ansi.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e20cf9939c3c14806a64a45a8d09074d8d88b4fd GIT binary patch literal 2985 zcma)8-E-4M5Z6hvEz6c|LP(&ct=rNTx5eqlw4H$t6UQ+j#W}&uKXCtyr%t& z#OPxJF%2Vr4L~(lqr@dtcXevG1~qBsmFAi>OLOp_p%-bM+OLS4g;{|XVV0ZIs6$Jy zG+JWTp5f+!F4GFo6-C=XpQB?ykFh-P3qYTz<3Nuqx(M_IIsx>Aq8(P))7%m(0+yKr zSYbBcIXc^dqj8fWHwA{C-@wyNq40t_!hweQJsg>d(U`;U=S@Uyu0O6jDJOiC=r7i5NT898xVyi*dM=hVmxgYhH~`JFcIM7Ju<637=J-_$!kK%-z2^$ z^j&S2?CCFbVc<-dyZRp4Gl=%oq{CG5Y8h$JkV`ujJ{R%xP^`<`8WX+{T;?DOI8ni_ z8Pbed|B*BY{sxnoXMTU{gl8?!3kRX_ybr)Gl`lopCN`-M{iXTe&e-9J>cba1a8THV zGCfb)o|jf7%!{7)bj$Cj99{*1BUz#xkEy~Z0WQIaG2)h1Bx>b^!_jNg_}Wc?Lx(#Q zdK7dZ1Nsm4DFS`UQJsoFr*f!8^Qu#os7(vlsb~>GQ9>SYv3(wtJSXyl?&DIX(r!iNqsvj`lL&fabD$JQi^UgPi@<;7Q zPJpf-J~$tSBEAfSYN${LX+wqPnubMJ4a(@eB{w9wt#sFngUI|P$@Xy{8jB0vYalMh zSYIYPr4c6rt5YK!gICklUplw=r?A97L--uwYlLqR>ImN<{Dg26u%k4;0t_6gs4NYN zO2=PG=eQccc;~^SoQCmc5#TW0G0}BMaRZXugbO$W37vHbT1<5A}RRla^f@e(dj0qZ-peqv0 zxWq-ckI+MSfba)`hwvwY575~kKjti%nQ23CrLokSm6^GvR;wek_gd}t!m2c9+AA%Y zyW5y+b(R~_Y~E{hWM=jD{BlcXmRjA`vUJ+>bGMh32(R_KEGQC0A(8TwNF+;&mx@NZ zc(8S)g;ZHl2da+Ps1m7EmQoGmse$|j&i6fxH|QEmGs%gcF}h+Ajyui_FvlBU7?2sb z$djwEkYN2Km|zK3N`hIIV8SMtYRMD=8j(;0hVTes4PhN2M0kww1fav`0Z%;-267IW z7*>wdnYOA5JUba7v9jr<*A?6FO`zHzen}*Fg(fhxPAzvlF|spD7+Yz~&fk^U&cf2& zMqB1p*u6%(eX7j^5QVuH+W^BDfTwXCYZsN)#$?X(XcTxJ&j8Kw>xVlCxJ!MGaJ;=a zS|_v7!^bQT>he(~slulGp~OlPs|9iuLL@WlfHK=uZSgb{rS)ri5#Gc~j9fF ZmSGt-{3>RpP;n{~m5FRNOR{jw{trPdQJ4S# literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/colorama/__pycache__/ansitowin32.cpython-310.pyc b/.vtodo/Lib/site-packages/colorama/__pycache__/ansitowin32.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1b91c63717e72c3a281712a63d1344f8a8732173 GIT binary patch literal 8188 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 literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/colorama/__pycache__/initialise.cpython-310.pyc b/.vtodo/Lib/site-packages/colorama/__pycache__/initialise.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..80ed45b0a2cd9650ab87bb618e5ce6e99f696fb4 GIT binary patch literal 1671 zcmZ`(OK%%D5GJ|Y)oS&y5bR;XV!nsQK!6Yd_A+-Zt*0W zZGW*drlYGvpz#pJ-a_RN=z>GjF~^Vii8y5lk3I78iucn1=yheIfNV0dP8SPT*1=fg zMk>!=F~ESV)xSSHyf^&O8n8nH*;oy2v6$}_$_-tii@S$LSvTBzM#k{_Y;R~YXYS6` z_^Fy0I~*5z0cxr~&t{p+RGwM0H9v$1Z{I|*w1|0!%QL^XJC?Xn4O)|ff7S1}SOvD_ z*ov+AN=$+y4lTzuzVA2g#6h4W>QF_r#@92rshO(H8ZLH{zQFH-qfjy@MuDo4ulL$Q zue$e&-tr1n-6i%L*Engq)+xsb3YAn*OoRT5J4;aW2RkaarZ+BTKjqoj+1@g9`#m)~ z>=ka`Ku^Ks!7eP!ZrUyb+%A*HDqomy0SYJsduYpOod5{&iQX^(m#HD6O+T6&U8Sl) zHX2~m^xC2SQaSTHb0b~MOc||>G6plrtiyAPXe!IPTDZbzysRPiX9ipu;*Ht7lyv(g z10W_`k}~)Ku51t0righIMm!M_ZwK*NHA|WBn9Kj*BXoKvMc>@U({qZ}h@vCmC}kWv zEENI>lYdZ@<4 zw`ve8E^6&FG*%M2RjsW8&Hs=u!&P{~PZ@Mq;emLGVZs(AsF%Et{y^M-_fTCl7rS39 zay=q85v_@=X#}5=-}NuxL) t-(G>~S?Dty-=7wGk()26W&28rF|jdk3K<}DK@tZ^7~hgLtdzJ}`ybA-TwMSF literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/colorama/__pycache__/win32.cpython-310.pyc b/.vtodo/Lib/site-packages/colorama/__pycache__/win32.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0bed4e309166272a088c7f4ff503f1bb11b3634d GIT binary patch literal 3932 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 PQ?%S%!Ez_vLb32aWdnda literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/colorama/__pycache__/winterm.cpython-310.pyc b/.vtodo/Lib/site-packages/colorama/__pycache__/winterm.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4ac2796629d44edc018223ef415f88175e597661 GIT binary patch literal 4548 zcmaJ_Npl;=6`r0A0}v#^MItRrHe<<7kVJH1C2^uS7D;f4%M{>ni7bSvDG)uR0D~B% zXJA>ZVO6T4tK+{w-l81xV{+_kPCWbOLn`0vp221^gX-6>*Llm=@AV*@oGci4{&nv` z^Sw6=<3H3Gerz;WP$DAS;H+buusUPLmcdPKy)w8Jn18eC7TPv<&~{YYM%(2%v~#NM zaBtTr=PwzyDKqK3XmuZa&_rY8q0tqT=%*loQD@w!o1E1xZq{vX)g5lvUGCI#+^u^& zSI={=Uf_A`T#(+AR<{y%LUD<;hM=u3O(GT%(&N%0;u6V$RLc|6^Zjn)B=CJ%@comJ z_d3KEeg7}LMkgI{*0xtGpGtdedw)YZTRR&YRcTkAu2!YBv#~C7k5{)gs(Y)_eY&x| zU3(&JOnLg+5I6A-MZT+59vN_hGU)5GTveUkXx*^oV9bJi}(gKaZ*L>y5jJMOPaa zhIEu6cjK3xKq#Ymn0@(IkZRc#6R1Uz$Rvn#tF@iStJ~6D+xcYc_j__i*Zvv|wzX5+ zudWZQpt%KFO9ITGM6|aSA3{^__k<=*S5TtAgA9b`<|_lJu(*vHxZ^Hrhc9uD=U*{k zPjCiWC)NysiDIpRS0=hFVNTf=_fU!Vi2R7ikBR(*NdGhnC0zbEGX<@P z$g;QD=}ab5f3!&>*+YEav&(2C5N8F#_{5oy5FS;FB`h}E7K*)Qs2)do(y9uIwt}J$ z4%0wZvXCGx#T_Cfdf5~d1f}z3qtgq<#20rl^h1=0qCkO7v71A}_q|-bUV3^raHve0 z;*bmqeZB@NIe6bnWu|bgK%-%3tCx#+uz{SQS2Z;lm8~a-#V@f^{0am@k-Igr2SO>dj0i;sR{WZ#k}l&S zJw)F})c+fhfe^H)Nv6#n;xki63MyBbn$94yvjPlEs0Fn|8H@GlQ<7OJj#bRKQmWr# z!4`^62g*<|(PU;KwQxi{LIRZvE&3TqiUdT^bK?S7h*{e_vw(#XIP1)c*}0uq3A-?X zZSpp&mL2gvR;rjPmcXgtxv{e&7k2vrb}#rU?nlzp@q&D+^pNheMwcT`j>Z;2YClF% zF^}RM9FGE>=K4Af&dtaf9Wgu|5Xv2poAiOPfns3xLqagLL(5KEc4F#GLCFWIQKS-Y zgUAwpQR#^&6rY8-SG2;eWS>j+w5;7C9?=}{c{(SAOXBNj%sMn)Wrm>PWNc_aWk@siwy&FG*N06yb<;=;)K(jAU5 zR>WK<3m+;my82qCJ~RY*|2wvX^hXC!k@UW%Vv7Q!ILsN>7MGKM)Ok!ia_C?YvVax0 zdoRVxGW?mt1)D}Z;3Y*LX3V7?e{{tR?1dSl*l`o*d)P_)6h@qJj0n27l#Ao1nIg4V zAwqdWW1~u(0!WRx&xlaQN{_kt19hk*ucF)zH7P`kKN1=9Q$d@z)9MD_U^rR=$-G1! znuTvl%1=beLyP9=Y8Dc0M(9L8N%B*QpGFP`Qb(B*aTpDj02+XU0BN#eHm~kI7x)enEt8 zXoBuUg03JcZ|lIiNzG4*JR$Nqkv|a`m{oD~DIwkhp;knUXLzPp^sIt|-voZH=M}s{ x0cEN9hMJ4-XsPcV(hU#WK{Hn0>+1VgO}OeU()&pNJ@GLGm^2NS?9#-%^FJIGp3eXP literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/colorama/ansi.py b/.vtodo/Lib/site-packages/colorama/ansi.py new file mode 100644 index 0000000..11ec695 --- /dev/null +++ b/.vtodo/Lib/site-packages/colorama/ansi.py @@ -0,0 +1,102 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +''' +This module generates ANSI character codes to printing colors to terminals. +See: http://en.wikipedia.org/wiki/ANSI_escape_code +''' + +CSI = '\033[' +OSC = '\033]' +BEL = '\a' + + +def code_to_chars(code): + return CSI + str(code) + 'm' + +def set_title(title): + return OSC + '2;' + title + BEL + +def clear_screen(mode=2): + return CSI + str(mode) + 'J' + +def clear_line(mode=2): + return CSI + str(mode) + 'K' + + +class AnsiCodes(object): + def __init__(self): + # the subclasses declare class attributes which are numbers. + # Upon instantiation we define instance attributes, which are the same + # as the class attributes but wrapped with the ANSI escape sequence + for name in dir(self): + if not name.startswith('_'): + value = getattr(self, name) + setattr(self, name, code_to_chars(value)) + + +class AnsiCursor(object): + def UP(self, n=1): + return CSI + str(n) + 'A' + def DOWN(self, n=1): + return CSI + str(n) + 'B' + def FORWARD(self, n=1): + return CSI + str(n) + 'C' + def BACK(self, n=1): + return CSI + str(n) + 'D' + def POS(self, x=1, y=1): + return CSI + str(y) + ';' + str(x) + 'H' + + +class AnsiFore(AnsiCodes): + BLACK = 30 + RED = 31 + GREEN = 32 + YELLOW = 33 + BLUE = 34 + MAGENTA = 35 + CYAN = 36 + WHITE = 37 + RESET = 39 + + # These are fairly well supported, but not part of the standard. + LIGHTBLACK_EX = 90 + LIGHTRED_EX = 91 + LIGHTGREEN_EX = 92 + LIGHTYELLOW_EX = 93 + LIGHTBLUE_EX = 94 + LIGHTMAGENTA_EX = 95 + LIGHTCYAN_EX = 96 + LIGHTWHITE_EX = 97 + + +class AnsiBack(AnsiCodes): + BLACK = 40 + RED = 41 + GREEN = 42 + YELLOW = 43 + BLUE = 44 + MAGENTA = 45 + CYAN = 46 + WHITE = 47 + RESET = 49 + + # These are fairly well supported, but not part of the standard. + LIGHTBLACK_EX = 100 + LIGHTRED_EX = 101 + LIGHTGREEN_EX = 102 + LIGHTYELLOW_EX = 103 + LIGHTBLUE_EX = 104 + LIGHTMAGENTA_EX = 105 + LIGHTCYAN_EX = 106 + LIGHTWHITE_EX = 107 + + +class AnsiStyle(AnsiCodes): + BRIGHT = 1 + DIM = 2 + NORMAL = 22 + RESET_ALL = 0 + +Fore = AnsiFore() +Back = AnsiBack() +Style = AnsiStyle() +Cursor = AnsiCursor() diff --git a/.vtodo/Lib/site-packages/colorama/ansitowin32.py b/.vtodo/Lib/site-packages/colorama/ansitowin32.py new file mode 100644 index 0000000..3db248b --- /dev/null +++ b/.vtodo/Lib/site-packages/colorama/ansitowin32.py @@ -0,0 +1,266 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +import re +import sys +import os + +from .ansi import AnsiFore, AnsiBack, AnsiStyle, Style, BEL +from .winterm import WinTerm, WinColor, WinStyle +from .win32 import windll, winapi_test + + +winterm = None +if windll is not None: + winterm = WinTerm() + + +class StreamWrapper(object): + ''' + Wraps a stream (such as stdout), acting as a transparent proxy for all + attribute access apart from method 'write()', which is delegated to our + Converter instance. + ''' + def __init__(self, wrapped, converter): + # double-underscore everything to prevent clashes with names of + # attributes on the wrapped stream object. + self.__wrapped = wrapped + self.__convertor = converter + + def __getattr__(self, name): + return getattr(self.__wrapped, name) + + def __enter__(self, *args, **kwargs): + # special method lookup bypasses __getattr__/__getattribute__, see + # https://stackoverflow.com/questions/12632894/why-doesnt-getattr-work-with-exit + # thus, contextlib magic methods are not proxied via __getattr__ + return self.__wrapped.__enter__(*args, **kwargs) + + def __exit__(self, *args, **kwargs): + return self.__wrapped.__exit__(*args, **kwargs) + + def __setstate__(self, state): + self.__dict__ = state + + def __getstate__(self): + return self.__dict__ + + def write(self, text): + self.__convertor.write(text) + + def isatty(self): + stream = self.__wrapped + if 'PYCHARM_HOSTED' in os.environ: + if stream is not None and (stream is sys.__stdout__ or stream is sys.__stderr__): + return True + try: + stream_isatty = stream.isatty + except AttributeError: + return False + else: + return stream_isatty() + + @property + def closed(self): + stream = self.__wrapped + try: + return stream.closed + # AttributeError in the case that the stream doesn't support being closed + # ValueError for the case that the stream has already been detached when atexit runs + except (AttributeError, ValueError): + return True + + +class AnsiToWin32(object): + ''' + Implements a 'write()' method which, on Windows, will strip ANSI character + sequences from the text, and if outputting to a tty, will convert them into + win32 function calls. + ''' + ANSI_CSI_RE = re.compile('\001?\033\\[((?:\\d|;)*)([a-zA-Z])\002?') # Control Sequence Introducer + ANSI_OSC_RE = re.compile('\001?\033\\]([^\a]*)(\a)\002?') # Operating System Command + + def __init__(self, wrapped, convert=None, strip=None, autoreset=False): + # The wrapped stream (normally sys.stdout or sys.stderr) + self.wrapped = wrapped + + # should we reset colors to defaults after every .write() + self.autoreset = autoreset + + # create the proxy wrapping our output stream + self.stream = StreamWrapper(wrapped, self) + + on_windows = os.name == 'nt' + # We test if the WinAPI works, because even if we are on Windows + # we may be using a terminal that doesn't support the WinAPI + # (e.g. Cygwin Terminal). In this case it's up to the terminal + # to support the ANSI codes. + conversion_supported = on_windows and winapi_test() + + # should we strip ANSI sequences from our output? + if strip is None: + strip = conversion_supported or (not self.stream.closed and not self.stream.isatty()) + self.strip = strip + + # should we should convert ANSI sequences into win32 calls? + if convert is None: + convert = conversion_supported and not self.stream.closed and self.stream.isatty() + self.convert = convert + + # dict of ansi codes to win32 functions and parameters + self.win32_calls = self.get_win32_calls() + + # are we wrapping stderr? + self.on_stderr = self.wrapped is sys.stderr + + def should_wrap(self): + ''' + True if this class is actually needed. If false, then the output + stream will not be affected, nor will win32 calls be issued, so + wrapping stdout is not actually required. This will generally be + False on non-Windows platforms, unless optional functionality like + autoreset has been requested using kwargs to init() + ''' + return self.convert or self.strip or self.autoreset + + def get_win32_calls(self): + if self.convert and winterm: + return { + AnsiStyle.RESET_ALL: (winterm.reset_all, ), + AnsiStyle.BRIGHT: (winterm.style, WinStyle.BRIGHT), + AnsiStyle.DIM: (winterm.style, WinStyle.NORMAL), + AnsiStyle.NORMAL: (winterm.style, WinStyle.NORMAL), + AnsiFore.BLACK: (winterm.fore, WinColor.BLACK), + AnsiFore.RED: (winterm.fore, WinColor.RED), + AnsiFore.GREEN: (winterm.fore, WinColor.GREEN), + AnsiFore.YELLOW: (winterm.fore, WinColor.YELLOW), + AnsiFore.BLUE: (winterm.fore, WinColor.BLUE), + AnsiFore.MAGENTA: (winterm.fore, WinColor.MAGENTA), + AnsiFore.CYAN: (winterm.fore, WinColor.CYAN), + AnsiFore.WHITE: (winterm.fore, WinColor.GREY), + AnsiFore.RESET: (winterm.fore, ), + AnsiFore.LIGHTBLACK_EX: (winterm.fore, WinColor.BLACK, True), + AnsiFore.LIGHTRED_EX: (winterm.fore, WinColor.RED, True), + AnsiFore.LIGHTGREEN_EX: (winterm.fore, WinColor.GREEN, True), + AnsiFore.LIGHTYELLOW_EX: (winterm.fore, WinColor.YELLOW, True), + AnsiFore.LIGHTBLUE_EX: (winterm.fore, WinColor.BLUE, True), + AnsiFore.LIGHTMAGENTA_EX: (winterm.fore, WinColor.MAGENTA, True), + AnsiFore.LIGHTCYAN_EX: (winterm.fore, WinColor.CYAN, True), + AnsiFore.LIGHTWHITE_EX: (winterm.fore, WinColor.GREY, True), + AnsiBack.BLACK: (winterm.back, WinColor.BLACK), + AnsiBack.RED: (winterm.back, WinColor.RED), + AnsiBack.GREEN: (winterm.back, WinColor.GREEN), + AnsiBack.YELLOW: (winterm.back, WinColor.YELLOW), + AnsiBack.BLUE: (winterm.back, WinColor.BLUE), + AnsiBack.MAGENTA: (winterm.back, WinColor.MAGENTA), + AnsiBack.CYAN: (winterm.back, WinColor.CYAN), + AnsiBack.WHITE: (winterm.back, WinColor.GREY), + AnsiBack.RESET: (winterm.back, ), + AnsiBack.LIGHTBLACK_EX: (winterm.back, WinColor.BLACK, True), + AnsiBack.LIGHTRED_EX: (winterm.back, WinColor.RED, True), + AnsiBack.LIGHTGREEN_EX: (winterm.back, WinColor.GREEN, True), + AnsiBack.LIGHTYELLOW_EX: (winterm.back, WinColor.YELLOW, True), + AnsiBack.LIGHTBLUE_EX: (winterm.back, WinColor.BLUE, True), + AnsiBack.LIGHTMAGENTA_EX: (winterm.back, WinColor.MAGENTA, True), + AnsiBack.LIGHTCYAN_EX: (winterm.back, WinColor.CYAN, True), + AnsiBack.LIGHTWHITE_EX: (winterm.back, WinColor.GREY, True), + } + return dict() + + def write(self, text): + if self.strip or self.convert: + self.write_and_convert(text) + else: + self.wrapped.write(text) + self.wrapped.flush() + if self.autoreset: + self.reset_all() + + + def reset_all(self): + if self.convert: + self.call_win32('m', (0,)) + elif not self.strip and not self.stream.closed: + self.wrapped.write(Style.RESET_ALL) + + + def write_and_convert(self, text): + ''' + Write the given text to our wrapped stream, stripping any ANSI + sequences from the text, and optionally converting them into win32 + calls. + ''' + cursor = 0 + text = self.convert_osc(text) + for match in self.ANSI_CSI_RE.finditer(text): + start, end = match.span() + self.write_plain_text(text, cursor, start) + self.convert_ansi(*match.groups()) + cursor = end + self.write_plain_text(text, cursor, len(text)) + + + def write_plain_text(self, text, start, end): + if start < end: + self.wrapped.write(text[start:end]) + self.wrapped.flush() + + + def convert_ansi(self, paramstring, command): + if self.convert: + params = self.extract_params(command, paramstring) + self.call_win32(command, params) + + + def extract_params(self, command, paramstring): + if command in 'Hf': + params = tuple(int(p) if len(p) != 0 else 1 for p in paramstring.split(';')) + while len(params) < 2: + # defaults: + params = params + (1,) + else: + params = tuple(int(p) for p in paramstring.split(';') if len(p) != 0) + if len(params) == 0: + # defaults: + if command in 'JKm': + params = (0,) + elif command in 'ABCD': + params = (1,) + + return params + + + def call_win32(self, command, params): + if command == 'm': + for param in params: + if param in self.win32_calls: + func_args = self.win32_calls[param] + func = func_args[0] + args = func_args[1:] + kwargs = dict(on_stderr=self.on_stderr) + func(*args, **kwargs) + elif command in 'J': + winterm.erase_screen(params[0], on_stderr=self.on_stderr) + elif command in 'K': + winterm.erase_line(params[0], on_stderr=self.on_stderr) + elif command in 'Hf': # cursor position - absolute + winterm.set_cursor_position(params, on_stderr=self.on_stderr) + elif command in 'ABCD': # cursor position - relative + n = params[0] + # A - up, B - down, C - forward, D - back + x, y = {'A': (0, -n), 'B': (0, n), 'C': (n, 0), 'D': (-n, 0)}[command] + winterm.cursor_adjust(x, y, on_stderr=self.on_stderr) + + + def convert_osc(self, text): + for match in self.ANSI_OSC_RE.finditer(text): + start, end = match.span() + text = text[:start] + text[end:] + paramstring, command = match.groups() + if command == BEL: + if paramstring.count(";") == 1: + params = paramstring.split(";") + # 0 - change title and icon (we will only change title) + # 1 - change icon (we don't support this) + # 2 - change title + if params[0] in '02': + winterm.set_title(params[1]) + return text diff --git a/.vtodo/Lib/site-packages/colorama/initialise.py b/.vtodo/Lib/site-packages/colorama/initialise.py new file mode 100644 index 0000000..430d066 --- /dev/null +++ b/.vtodo/Lib/site-packages/colorama/initialise.py @@ -0,0 +1,80 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +import atexit +import contextlib +import sys + +from .ansitowin32 import AnsiToWin32 + + +orig_stdout = None +orig_stderr = None + +wrapped_stdout = None +wrapped_stderr = None + +atexit_done = False + + +def reset_all(): + if AnsiToWin32 is not None: # Issue #74: objects might become None at exit + AnsiToWin32(orig_stdout).reset_all() + + +def init(autoreset=False, convert=None, strip=None, wrap=True): + + if not wrap and any([autoreset, convert, strip]): + raise ValueError('wrap=False conflicts with any other arg=True') + + global wrapped_stdout, wrapped_stderr + global orig_stdout, orig_stderr + + orig_stdout = sys.stdout + orig_stderr = sys.stderr + + if sys.stdout is None: + wrapped_stdout = None + else: + sys.stdout = wrapped_stdout = \ + wrap_stream(orig_stdout, convert, strip, autoreset, wrap) + if sys.stderr is None: + wrapped_stderr = None + else: + sys.stderr = wrapped_stderr = \ + wrap_stream(orig_stderr, convert, strip, autoreset, wrap) + + global atexit_done + if not atexit_done: + atexit.register(reset_all) + atexit_done = True + + +def deinit(): + if orig_stdout is not None: + sys.stdout = orig_stdout + if orig_stderr is not None: + sys.stderr = orig_stderr + + +@contextlib.contextmanager +def colorama_text(*args, **kwargs): + init(*args, **kwargs) + try: + yield + finally: + deinit() + + +def reinit(): + if wrapped_stdout is not None: + sys.stdout = wrapped_stdout + if wrapped_stderr is not None: + sys.stderr = wrapped_stderr + + +def wrap_stream(stream, convert, strip, autoreset, wrap): + if wrap: + wrapper = AnsiToWin32(stream, + convert=convert, strip=strip, autoreset=autoreset) + if wrapper.should_wrap(): + stream = wrapper.stream + return stream diff --git a/.vtodo/Lib/site-packages/colorama/win32.py b/.vtodo/Lib/site-packages/colorama/win32.py new file mode 100644 index 0000000..c2d8360 --- /dev/null +++ b/.vtodo/Lib/site-packages/colorama/win32.py @@ -0,0 +1,152 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. + +# from winbase.h +STDOUT = -11 +STDERR = -12 + +try: + import ctypes + from ctypes import LibraryLoader + windll = LibraryLoader(ctypes.WinDLL) + from ctypes import wintypes +except (AttributeError, ImportError): + windll = None + SetConsoleTextAttribute = lambda *_: None + winapi_test = lambda *_: None +else: + from ctypes import byref, Structure, c_char, POINTER + + COORD = wintypes._COORD + + class CONSOLE_SCREEN_BUFFER_INFO(Structure): + """struct in wincon.h.""" + _fields_ = [ + ("dwSize", COORD), + ("dwCursorPosition", COORD), + ("wAttributes", wintypes.WORD), + ("srWindow", wintypes.SMALL_RECT), + ("dwMaximumWindowSize", COORD), + ] + def __str__(self): + return '(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)' % ( + self.dwSize.Y, self.dwSize.X + , self.dwCursorPosition.Y, self.dwCursorPosition.X + , self.wAttributes + , self.srWindow.Top, self.srWindow.Left, self.srWindow.Bottom, self.srWindow.Right + , self.dwMaximumWindowSize.Y, self.dwMaximumWindowSize.X + ) + + _GetStdHandle = windll.kernel32.GetStdHandle + _GetStdHandle.argtypes = [ + wintypes.DWORD, + ] + _GetStdHandle.restype = wintypes.HANDLE + + _GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo + _GetConsoleScreenBufferInfo.argtypes = [ + wintypes.HANDLE, + POINTER(CONSOLE_SCREEN_BUFFER_INFO), + ] + _GetConsoleScreenBufferInfo.restype = wintypes.BOOL + + _SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute + _SetConsoleTextAttribute.argtypes = [ + wintypes.HANDLE, + wintypes.WORD, + ] + _SetConsoleTextAttribute.restype = wintypes.BOOL + + _SetConsoleCursorPosition = windll.kernel32.SetConsoleCursorPosition + _SetConsoleCursorPosition.argtypes = [ + wintypes.HANDLE, + COORD, + ] + _SetConsoleCursorPosition.restype = wintypes.BOOL + + _FillConsoleOutputCharacterA = windll.kernel32.FillConsoleOutputCharacterA + _FillConsoleOutputCharacterA.argtypes = [ + wintypes.HANDLE, + c_char, + wintypes.DWORD, + COORD, + POINTER(wintypes.DWORD), + ] + _FillConsoleOutputCharacterA.restype = wintypes.BOOL + + _FillConsoleOutputAttribute = windll.kernel32.FillConsoleOutputAttribute + _FillConsoleOutputAttribute.argtypes = [ + wintypes.HANDLE, + wintypes.WORD, + wintypes.DWORD, + COORD, + POINTER(wintypes.DWORD), + ] + _FillConsoleOutputAttribute.restype = wintypes.BOOL + + _SetConsoleTitleW = windll.kernel32.SetConsoleTitleW + _SetConsoleTitleW.argtypes = [ + wintypes.LPCWSTR + ] + _SetConsoleTitleW.restype = wintypes.BOOL + + def _winapi_test(handle): + csbi = CONSOLE_SCREEN_BUFFER_INFO() + success = _GetConsoleScreenBufferInfo( + handle, byref(csbi)) + return bool(success) + + def winapi_test(): + return any(_winapi_test(h) for h in + (_GetStdHandle(STDOUT), _GetStdHandle(STDERR))) + + def GetConsoleScreenBufferInfo(stream_id=STDOUT): + handle = _GetStdHandle(stream_id) + csbi = CONSOLE_SCREEN_BUFFER_INFO() + success = _GetConsoleScreenBufferInfo( + handle, byref(csbi)) + return csbi + + def SetConsoleTextAttribute(stream_id, attrs): + handle = _GetStdHandle(stream_id) + return _SetConsoleTextAttribute(handle, attrs) + + def SetConsoleCursorPosition(stream_id, position, adjust=True): + position = COORD(*position) + # If the position is out of range, do nothing. + if position.Y <= 0 or position.X <= 0: + return + # Adjust for Windows' SetConsoleCursorPosition: + # 1. being 0-based, while ANSI is 1-based. + # 2. expecting (x,y), while ANSI uses (y,x). + adjusted_position = COORD(position.Y - 1, position.X - 1) + if adjust: + # Adjust for viewport's scroll position + sr = GetConsoleScreenBufferInfo(STDOUT).srWindow + adjusted_position.Y += sr.Top + adjusted_position.X += sr.Left + # Resume normal processing + handle = _GetStdHandle(stream_id) + return _SetConsoleCursorPosition(handle, adjusted_position) + + def FillConsoleOutputCharacter(stream_id, char, length, start): + handle = _GetStdHandle(stream_id) + char = c_char(char.encode()) + length = wintypes.DWORD(length) + num_written = wintypes.DWORD(0) + # Note that this is hard-coded for ANSI (vs wide) bytes. + success = _FillConsoleOutputCharacterA( + handle, char, length, start, byref(num_written)) + return num_written.value + + def FillConsoleOutputAttribute(stream_id, attr, length, start): + ''' FillConsoleOutputAttribute( hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten )''' + handle = _GetStdHandle(stream_id) + attribute = wintypes.WORD(attr) + length = wintypes.DWORD(length) + num_written = wintypes.DWORD(0) + # Note that this is hard-coded for ANSI (vs wide) bytes. + return _FillConsoleOutputAttribute( + handle, attribute, length, start, byref(num_written)) + + def SetConsoleTitle(title): + return _SetConsoleTitleW(title) diff --git a/.vtodo/Lib/site-packages/colorama/winterm.py b/.vtodo/Lib/site-packages/colorama/winterm.py new file mode 100644 index 0000000..0fdb4ec --- /dev/null +++ b/.vtodo/Lib/site-packages/colorama/winterm.py @@ -0,0 +1,169 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +from . import win32 + + +# from wincon.h +class WinColor(object): + BLACK = 0 + BLUE = 1 + GREEN = 2 + CYAN = 3 + RED = 4 + MAGENTA = 5 + YELLOW = 6 + GREY = 7 + +# from wincon.h +class WinStyle(object): + NORMAL = 0x00 # dim text, dim background + BRIGHT = 0x08 # bright text, dim background + BRIGHT_BACKGROUND = 0x80 # dim text, bright background + +class WinTerm(object): + + def __init__(self): + self._default = win32.GetConsoleScreenBufferInfo(win32.STDOUT).wAttributes + self.set_attrs(self._default) + self._default_fore = self._fore + self._default_back = self._back + self._default_style = self._style + # In order to emulate LIGHT_EX in windows, we borrow the BRIGHT style. + # So that LIGHT_EX colors and BRIGHT style do not clobber each other, + # we track them separately, since LIGHT_EX is overwritten by Fore/Back + # and BRIGHT is overwritten by Style codes. + self._light = 0 + + def get_attrs(self): + return self._fore + self._back * 16 + (self._style | self._light) + + def set_attrs(self, value): + self._fore = value & 7 + self._back = (value >> 4) & 7 + self._style = value & (WinStyle.BRIGHT | WinStyle.BRIGHT_BACKGROUND) + + def reset_all(self, on_stderr=None): + self.set_attrs(self._default) + self.set_console(attrs=self._default) + self._light = 0 + + def fore(self, fore=None, light=False, on_stderr=False): + if fore is None: + fore = self._default_fore + self._fore = fore + # Emulate LIGHT_EX with BRIGHT Style + if light: + self._light |= WinStyle.BRIGHT + else: + self._light &= ~WinStyle.BRIGHT + self.set_console(on_stderr=on_stderr) + + def back(self, back=None, light=False, on_stderr=False): + if back is None: + back = self._default_back + self._back = back + # Emulate LIGHT_EX with BRIGHT_BACKGROUND Style + if light: + self._light |= WinStyle.BRIGHT_BACKGROUND + else: + self._light &= ~WinStyle.BRIGHT_BACKGROUND + self.set_console(on_stderr=on_stderr) + + def style(self, style=None, on_stderr=False): + if style is None: + style = self._default_style + self._style = style + self.set_console(on_stderr=on_stderr) + + def set_console(self, attrs=None, on_stderr=False): + if attrs is None: + attrs = self.get_attrs() + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + win32.SetConsoleTextAttribute(handle, attrs) + + def get_position(self, handle): + position = win32.GetConsoleScreenBufferInfo(handle).dwCursorPosition + # Because Windows coordinates are 0-based, + # and win32.SetConsoleCursorPosition expects 1-based. + position.X += 1 + position.Y += 1 + return position + + def set_cursor_position(self, position=None, on_stderr=False): + if position is None: + # I'm not currently tracking the position, so there is no default. + # position = self.get_position() + return + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + win32.SetConsoleCursorPosition(handle, position) + + def cursor_adjust(self, x, y, on_stderr=False): + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + position = self.get_position(handle) + adjusted_position = (position.Y + y, position.X + x) + win32.SetConsoleCursorPosition(handle, adjusted_position, adjust=False) + + def erase_screen(self, mode=0, on_stderr=False): + # 0 should clear from the cursor to the end of the screen. + # 1 should clear from the cursor to the beginning of the screen. + # 2 should clear the entire screen, and move cursor to (1,1) + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + csbi = win32.GetConsoleScreenBufferInfo(handle) + # get the number of character cells in the current buffer + cells_in_screen = csbi.dwSize.X * csbi.dwSize.Y + # get number of character cells before current cursor position + cells_before_cursor = csbi.dwSize.X * csbi.dwCursorPosition.Y + csbi.dwCursorPosition.X + if mode == 0: + from_coord = csbi.dwCursorPosition + cells_to_erase = cells_in_screen - cells_before_cursor + elif mode == 1: + from_coord = win32.COORD(0, 0) + cells_to_erase = cells_before_cursor + elif mode == 2: + from_coord = win32.COORD(0, 0) + cells_to_erase = cells_in_screen + else: + # invalid mode + return + # fill the entire screen with blanks + win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord) + # now set the buffer's attributes accordingly + win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord) + if mode == 2: + # put the cursor where needed + win32.SetConsoleCursorPosition(handle, (1, 1)) + + def erase_line(self, mode=0, on_stderr=False): + # 0 should clear from the cursor to the end of the line. + # 1 should clear from the cursor to the beginning of the line. + # 2 should clear the entire line. + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + csbi = win32.GetConsoleScreenBufferInfo(handle) + if mode == 0: + from_coord = csbi.dwCursorPosition + cells_to_erase = csbi.dwSize.X - csbi.dwCursorPosition.X + elif mode == 1: + from_coord = win32.COORD(0, csbi.dwCursorPosition.Y) + cells_to_erase = csbi.dwCursorPosition.X + elif mode == 2: + from_coord = win32.COORD(0, csbi.dwCursorPosition.Y) + cells_to_erase = csbi.dwSize.X + else: + # invalid mode + return + # fill the entire screen with blanks + win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord) + # now set the buffer's attributes accordingly + win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord) + + def set_title(self, title): + win32.SetConsoleTitle(title) diff --git a/.vtodo/Lib/site-packages/flask/__init__.py b/.vtodo/Lib/site-packages/flask/__init__.py new file mode 100644 index 0000000..e02531c --- /dev/null +++ b/.vtodo/Lib/site-packages/flask/__init__.py @@ -0,0 +1,71 @@ +from markupsafe import escape +from markupsafe import Markup + +from . import json as json +from .app import Flask as Flask +from .app import Request as Request +from .app import Response as Response +from .blueprints import Blueprint as Blueprint +from .config import Config as Config +from .ctx import after_this_request as after_this_request +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 signals_available as signals_available +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 + +__version__ = "2.2.2" + + +def __getattr__(name): + if name == "_app_ctx_stack": + import warnings + from .globals import __app_ctx_stack + + warnings.warn( + "'_app_ctx_stack' is deprecated and will be removed in Flask 2.3.", + DeprecationWarning, + stacklevel=2, + ) + return __app_ctx_stack + + if name == "_request_ctx_stack": + import warnings + from .globals import __request_ctx_stack + + warnings.warn( + "'_request_ctx_stack' is deprecated and will be removed in Flask 2.3.", + DeprecationWarning, + stacklevel=2, + ) + return __request_ctx_stack + + raise AttributeError(name) diff --git a/.vtodo/Lib/site-packages/flask/__main__.py b/.vtodo/Lib/site-packages/flask/__main__.py new file mode 100644 index 0000000..4e28416 --- /dev/null +++ b/.vtodo/Lib/site-packages/flask/__main__.py @@ -0,0 +1,3 @@ +from .cli import main + +main() diff --git a/.vtodo/Lib/site-packages/flask/__pycache__/__init__.cpython-310.pyc b/.vtodo/Lib/site-packages/flask/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..42178392823425c0c70a631c1875ffd4f5f111ec GIT binary patch literal 2382 zcmb`I%Wm676oyHP5@}hsEX$Vfw(QuJ<%{Cjy`w17B<-STvPe+07ziLJaU?MwUOF@K zB}g`Hw|$Uq`#9Qml~*WGpywP(vEr->R0Mx~{O53Z=FCOO%uL>Z-=DkZ?eC@y<1c)e z{8{ku5N_oH1x8>BBQlw3B3r-~vw$;!Ei#eKY~rlQMmd%v&H+2jA$EXW<`TPND$27w z@sya33amh!7cHWi+0&A@h0#d+aulr-e>#7Rp1({5pM$@umj>9;5w@l-vd5m zhs3+UN9>4r5BQiJ6Ym3`uoL1MaDz3755)cGl${dSfgi93#D~CV?2Pyb_?(>sAG42w zlc4dN!9EV|zcA{jZ*h&(%|VW0YQj`R>K_c9PzB?Y@*e)??pmC$Z+& z_~je3Ai@8(&#V|fZDB*uv@0%0}B9pP~ASfk)q z*|P>*ZCupjpAC?O`!U?A2l5(D#S2R=2UcJRdwHu@?{KDrv^?}BrRAF@&pna*ys=DpNvKXtMJER zpghIq*{{c2@Ea*%lh^}0_9HGIKqGZPVSgC%Q!xI1`uL0H4~k3G8#eL6%Rjxg+k=_iZJr9lpJOn%svr+pX zuIZWu)3z)VuiQU&*0nx#VVa0c8ed7Uc}3h%$##LYW6)PLGQeN+Qc>Eubu- zR8W>smQhwvR#Dba)=@S5+{-$; zhbTuV#~@hbOGpnU9+n?gjiz!!KEo*Y%P0x@f`5gZMg0U)FbZbDg8P5GY^#v@7n?nU ARR910 literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/flask/__pycache__/__main__.cpython-310.pyc b/.vtodo/Lib/site-packages/flask/__pycache__/__main__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3f74071c831d846a3e9a5b0af12cfc080f4e185b GIT binary patch literal 210 zcmd1j<>g`kf=w#M$#OvYF^GcN9iz literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/flask/__pycache__/app.cpython-310.pyc b/.vtodo/Lib/site-packages/flask/__pycache__/app.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..127b29ae8bf775248524d1142bce4e3c448c19f0 GIT binary patch literal 74896 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:Sf2Z^d6i&?aY|rhGjiU$?wIn+VnT0#ABG0 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 literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/flask/__pycache__/blueprints.cpython-310.pyc b/.vtodo/Lib/site-packages/flask/__pycache__/blueprints.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bd29c3ef50e077dfd265949c82d248bb56c6bba1 GIT binary patch literal 24187 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 literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/flask/__pycache__/config.cpython-310.pyc b/.vtodo/Lib/site-packages/flask/__pycache__/config.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b3a21d426a5e4e057908fb183c4cdfacdeda1dc3 GIT binary patch literal 12427 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 literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/flask/__pycache__/ctx.cpython-310.pyc b/.vtodo/Lib/site-packages/flask/__pycache__/ctx.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a9cd34c8af16004ab563804b4c22e982f686a04 GIT binary patch literal 14461 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 literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/flask/__pycache__/debughelpers.cpython-310.pyc b/.vtodo/Lib/site-packages/flask/__pycache__/debughelpers.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cd2f9d5c48f15d944090a9183a175552320abf78 GIT binary patch literal 5924 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 U+XiZ{cq{eg*MirV&z?T{cQ~s)0ssI2 literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/flask/__pycache__/globals.cpython-310.pyc b/.vtodo/Lib/site-packages/flask/__pycache__/globals.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3468b292211ede6e21b70805aa9491a8f6ff7f83 GIT binary patch literal 3300 zcmcImTW{RP73PrKU21Q-`XX7jX-A2=t-WcJ7A*>+u%S41QCBr8B}NDV1jQMxC~-+< zIJ=S->Pzj{{DPuD{OH&I9QLtK{tFF~^gF{{Yb6UP`cM))Jaf))=FE3)Ok1tGf$JY@ zpN4tUF#bh@>8palLp-_14C4!f8=S?4$M7~|vu}A8BbgQ3{fbv%hF}L(`5SKYN?hwZ zo&&ATtMN>~?$tG41K;o(ns>lAy{6`8;#R-ywKZRlXZv&B95bF6yuq8V4BmWcdh@*H zEeMOZUovkI@7ZNTEb+MqhM4E`Vi7a0h>BPg3w+@l)4R%NpopZu7f9$=$R02Yl_7?cL#Q%jkUgmQdg9u%f<^CQ2NuF9S)V zXK5J3&t-ai@)jO2AgX*G2l*kGw*M&0Hq`NxINc56oMw}t{G9ZE7GDoUuFfRQEs^I@ zn*1?3juOnMjc0_k`SexA;31wo0TJ5ap2-%^$*{R_hrzR+(bjx_dHan(7XdAKx(y>WN(h=S4q;PycN+RX^WK(W~R9T&k7SB%Boy-Wyuak?;Lg4H3bir_(A~ycQ zPT2wDIMspqH$Xpr+kdYpi(K%H&O?A#+my82$XxeFFZ@s)`#Hh5?nb%GMJ7cUD8bzz z;qFls$L_9hrRb+a$fCs6k>!5U{j}=>3U|G??yA(yRVszc0~OqJQ|Ydc)@Wg^3c?T( z<*I$*h65=@qTH~IzwTRd%^Sp|HXs&55qIpuISOPFCB3|`iPLT<7CswWhR}Rb>ftj& zPF^EI{wc`t$E5`Gn68PkDP_FW62e~bO04rF4V-j8zjA8ot+Kx{o7DzuRCmxjb^6| zBJFz~e`m@XzB_LUH(oP%pUkp|hl!{crCIj0Sv{fTDn1|ER#@=SMcb?_%|0O091w>! zaocRvo;EMd&RIvn^v*hh{C(>t)=YsUZ((MKl`g!1kEPPA19b_H**w2LDch7il=i-l zQsDPfK8T5L`u^90ARcS1TuDiOESM^+N6Cp?#GpjvCUGw5oJ^*fkO~?3r%WNff>_=m z?U_}Yqrj345b}54262p4=GaS(IYibf)4I`jFH(7k=ylV9$|Ek^bk7a4EJnScwco{l zy}rGVgi|LOG;w_5_C=6Tngq%{Ne6DAl;~%ah)AyiGHq{=gr#+nIw|KRLU6(B)2TJJ zRoC4{Vy5v>V0M(>a}ky9AVXqbW>EboQv0On;_=5B`jbxA-4gUYk~<9tePnNWj6Gy| zpK?A*!1lGUJ8J17SFV82ssW>-=7&QcYZewrhk3JuWH%io-0!0-pfVOUy&q)9~qkmoksw_L#{{v7$2v`HSr`y}lQT=p}?7HZZ`5;!8zyZf9v{CXq;HXc5 z;s!Xaa`)3Ca%3<}BY2cNkEq*?MZY_mFF_mcLUmJ8?iCFJZY(Wo6Q(gekABRGT3&W+ zIfGHzv?w^w$DN{N5i@2;KHGCiC7g;TR|4ZzU-vx0PbtaS1 zUH#s!IOfH%{57U^?iN)l6iH8hM8-A7={XE!uIroEJ`(b9BnG{1Oh1E?>T%K9{_6Q- zf8*)njX(T;^NIW!WID$rwH}>VIz!87bH-7W>o7tSmT)G1btJ`@E^S`JPg0FaKkvz( z(hh`DlB8u#$brx=Y4SZH_leNoN%cAsLk3xnpX84we8F0*DL=4?#N9X literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/flask/__pycache__/helpers.cpython-310.pyc b/.vtodo/Lib/site-packages/flask/__pycache__/helpers.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bb3742f09b9662be6eade32477e8860a0c55a5b7 GIT binary patch literal 24017 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 A&j0`b literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/flask/__pycache__/logging.cpython-310.pyc b/.vtodo/Lib/site-packages/flask/__pycache__/logging.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5302945463eda00774f8a1efb25c78384b146a75 GIT binary patch literal 2453 zcmZuzUvC>l5Z^tY|Hf{bkX9{4XbY`cOR0-g!Gje=h%{*{qJY#CgmINy-|pGxobTM- zby7#60<{wG0eC??B#(XNiO+JcJoN+c01v>--Z@PPC*AsHZf17ow==(U(rDBOT)$qs zjEGjSD=d@E7RY`^o(4PxjQ4@17+|IltN9Sezn06NA!qFl~ zZwZi&Da3Dxb6^V|U_CF+gY}Z24@q<36=X=Znl!C#CO(foSIN`qD+)SLm6d!lk=npx zaUGb7*um-nYg2fP3He^Tzy-Xew;|vS1QGx#iQ8&U^Ff)*BlxV~F zc&w7K3b~O?DwQavjgoxC0(e9#Fo;5*n=px6>_Nad%QLDmxI)diZ^Avf%%Y^vOu{^G zU-!e7jio(rnI#ZK?T1>j3EZBir@Ez$00Yl!)oc_9J546cPvRi#Pn1MSv1C7HZ69jZ zZ4ZL?!?@piXp_6oV-ZOO!O?BRr);0c26ng6a2qp#%w{EI71`|3p2G$c4bfN-Mv_6D zBJ|D4?zU70JJ?`8k8cd_C4KS(*mGn(C%{GVk07xJKtb~Ptr?zgMC!d=3?b`t! zS!^Bnms7rw`Y3vlZVSPsb%IVL~C?>F?2s;h>6PXcI0;k>9E(xG#g zJg3j8x+tJ6K`!WbszkW<*+masL8)0f_<3z0{aqFY*o$y(*l%o*9X|_W+o5>@%pY*8 z;fC#pQIwmZiG{ciT^eJTLiRT_YmcNEv^^OFaGLOnMRHF@Y~|Suk4Pxdev)M$G z)%?iLAb{21K?L50P#1SxzO}h0GhS_e{bW*4FL0Zl$Qs=-k zJKZ^WN~5!I_E@pL2usCvsq0*#E-g6@ zb=}h6?w@W|U4~Z&AANoOCf5M=NH+CjcKxQUAm%9ISzf<6O2i}rsg2K4Yv|IZhJ1me zkk0=X@ipAK4T~X1^pFglV+yE~VL>ed_O6EYs0dV8LJl_NL+Cqw1#}ftMa>~Hu)}|Y zcD^TZ!X>jbi@XP2lC=x6!3jS=69ORwES6G8ku%sf_~o`(P}YDw=DkRYWv252@TEiLDsX%+f?(@t*cB9Lj( z0;T4mL(&SAxQR2I7A*j_FVh-ieLtg=bY94SPa-p$D;NdrZ95OIX;;}XX!!7?$l|;slgzq_d(ITpsrvH_?H6oX{KYSFr}t*ZeOb113Br#_W@>CA&aKv zew6fJkh2d!WBaSm*LT+LudjXjVC!C%3Qj^=+J^7TgU@nGo)z3nk|?XG@dn%q=_9Q} z^&1v9BUM^Gd9+EMD$aggY=xDTY$8{Y?De~_R7izZC=}?Fph%~%NSErh3ohu~e=ZgO E1(X%ORR910 literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/flask/__pycache__/scaffold.cpython-310.pyc b/.vtodo/Lib/site-packages/flask/__pycache__/scaffold.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e694ffef6374905786a54f1c2c94e430d28c31f3 GIT binary patch literal 24712 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 p2GVMFKW;?Re-32V{O9A@I5ctge*nDSn1 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 s#^!DOsKU?d&)OIGBe7Xg`a^#A|> literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/flask/__pycache__/templating.cpython-310.pyc b/.vtodo/Lib/site-packages/flask/__pycache__/templating.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..333733e4932772010e8abbf47584d478a11c444b GIT binary patch literal 6957 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 literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/flask/__pycache__/testing.cpython-310.pyc b/.vtodo/Lib/site-packages/flask/__pycache__/testing.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3aedf5eed3689b9f94f2a48ab44955f1e015fb45 GIT binary patch literal 9399 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 literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/flask/__pycache__/typing.cpython-310.pyc b/.vtodo/Lib/site-packages/flask/__pycache__/typing.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..af8c417e18bc46173763fd85e870eaab4b9fdf14 GIT binary patch literal 1673 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 literal 0 HcmV?d00001 diff --git a/.vtodo/Lib/site-packages/flask/app.py b/.vtodo/Lib/site-packages/flask/app.py new file mode 100644 index 0000000..db442c9 --- /dev/null +++ b/.vtodo/Lib/site-packages/flask/app.py @@ -0,0 +1,2548 @@ +import functools +import inspect +import json +import logging +import os +import sys +import typing as t +import weakref +from collections.abc import Iterator as _abc_Iterator +from datetime import timedelta +from itertools import chain +from threading import Lock +from types import TracebackType + +import click +from werkzeug.datastructures import Headers +from werkzeug.datastructures import ImmutableDict +from werkzeug.exceptions import Aborter +from werkzeug.exceptions import BadRequest +from werkzeug.exceptions import BadRequestKeyError +from werkzeug.exceptions import HTTPException +from werkzeug.exceptions import InternalServerError +from werkzeug.routing import BuildError +from werkzeug.routing import Map +from werkzeug.routing import MapAdapter +from werkzeug.routing import RequestRedirect +from werkzeug.routing import RoutingException +from werkzeug.routing import Rule +from werkzeug.serving import is_running_from_reloader +from werkzeug.urls import url_quote +from werkzeug.utils import redirect as _wz_redirect +from werkzeug.wrappers import Response as BaseResponse + +from . import cli +from . import typing as ft +from .config import Config +from .config import ConfigAttribute +from .ctx import _AppCtxGlobals +from .ctx import AppContext +from .ctx import RequestContext +from .globals import _cv_app +from .globals import _cv_request +from .globals import g +from .globals import request +from .globals import request_ctx +from .globals import session +from .helpers import _split_blueprint_path +from .helpers import get_debug_flag +from .helpers import get_flashed_messages +from .helpers import get_load_dotenv +from .helpers import locked_cached_property +from .json.provider import DefaultJSONProvider +from .json.provider import JSONProvider +from .logging import create_logger +from .scaffold import _endpoint_from_view_func +from .scaffold import _sentinel +from .scaffold import find_package +from .scaffold import Scaffold +from .scaffold import setupmethod +from .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 DispatchingJinjaLoader +from .templating import Environment +from .wrappers import Request +from .wrappers import Response + +if t.TYPE_CHECKING: # pragma: no cover + import typing_extensions as te + from .blueprints import Blueprint + from .testing import FlaskClient + from .testing import FlaskCliRunner + +T_before_first_request = t.TypeVar( + "T_before_first_request", bound=ft.BeforeFirstRequestCallable +) +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) + +if sys.version_info >= (3, 8): + iscoroutinefunction = inspect.iscoroutinefunction +else: + + def iscoroutinefunction(func: t.Any) -> bool: + while inspect.ismethod(func): + func = func.__func__ + + while isinstance(func, functools.partial): + func = func.func + + return inspect.iscoroutinefunction(func) + + +def _make_timedelta(value: t.Union[timedelta, int, None]) -> t.Optional[timedelta]: + if value is None or isinstance(value, timedelta): + return value + + return timedelta(seconds=value) + + +class Flask(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 that is used for request objects. See :class:`~flask.Request` + #: for more information. + request_class = Request + + #: The class that is used for response objects. See + #: :class:`~flask.Response` for more information. + response_class = Response + + #: The class of the object assigned to :attr:`aborter`, created by + #: :meth:`create_aborter`. That object is called by + #: :func:`flask.abort` to raise HTTP errors, and can be + #: called directly as well. + #: + #: Defaults to :class:`werkzeug.exceptions.Aborter`. + #: + #: .. versionadded:: 2.2 + aborter_class = Aborter + + #: The class that is used for the Jinja environment. + #: + #: .. versionadded:: 0.11 + jinja_environment = Environment + + #: The class that is used for the :data:`~flask.g` instance. + #: + #: Example use cases for a custom class: + #: + #: 1. Store arbitrary attributes on flask.g. + #: 2. Add a property for lazy per-request database connectors. + #: 3. Return None instead of AttributeError on unexpected attributes. + #: 4. Raise exception if an unexpected attr is set, a "controlled" flask.g. + #: + #: In Flask 0.9 this property was called `request_globals_class` but it + #: was changed in 0.10 to :attr:`app_ctx_globals_class` because the + #: flask.g object is now application context scoped. + #: + #: .. versionadded:: 0.10 + app_ctx_globals_class = _AppCtxGlobals + + #: The class that is used for the ``config`` attribute of this app. + #: Defaults to :class:`~flask.Config`. + #: + #: Example use cases for a custom class: + #: + #: 1. Default values for certain config options. + #: 2. Access to config values through attributes in addition to keys. + #: + #: .. versionadded:: 0.11 + config_class = Config + + #: The testing flag. Set this to ``True`` to enable the test mode of + #: Flask extensions (and in the future probably also Flask itself). + #: For example this might activate test helpers that have an + #: additional runtime cost which should not be enabled by default. + #: + #: If this is enabled and PROPAGATE_EXCEPTIONS is not changed from the + #: default it's implicitly enabled. + #: + #: This attribute can also be configured from the config with the + #: ``TESTING`` configuration key. Defaults to ``False``. + testing = ConfigAttribute("TESTING") + + #: If a secret key is set, cryptographic components can use this to + #: sign cookies and other things. Set this to a complex random value + #: when you want to use the secure cookie for instance. + #: + #: This attribute can also be configured from the config with the + #: :data:`SECRET_KEY` configuration key. Defaults to ``None``. + secret_key = ConfigAttribute("SECRET_KEY") + + @property + def session_cookie_name(self) -> str: + """The name of the cookie set by the session interface. + + .. deprecated:: 2.2 + Will be removed in Flask 2.3. Use ``app.config["SESSION_COOKIE_NAME"]`` + instead. + """ + import warnings + + warnings.warn( + "'session_cookie_name' is deprecated and will be removed in Flask 2.3. Use" + " 'SESSION_COOKIE_NAME' in 'app.config' instead.", + DeprecationWarning, + stacklevel=2, + ) + return self.config["SESSION_COOKIE_NAME"] + + @session_cookie_name.setter + def session_cookie_name(self, value: str) -> None: + import warnings + + warnings.warn( + "'session_cookie_name' is deprecated and will be removed in Flask 2.3. Use" + " 'SESSION_COOKIE_NAME' in 'app.config' instead.", + DeprecationWarning, + stacklevel=2, + ) + self.config["SESSION_COOKIE_NAME"] = value + + #: A :class:`~datetime.timedelta` which is used to set the expiration + #: date of a permanent session. The default is 31 days which makes a + #: permanent session survive for roughly one month. + #: + #: This attribute can also be configured from the config with the + #: ``PERMANENT_SESSION_LIFETIME`` configuration key. Defaults to + #: ``timedelta(days=31)`` + permanent_session_lifetime = ConfigAttribute( + "PERMANENT_SESSION_LIFETIME", get_converter=_make_timedelta + ) + + @property + def send_file_max_age_default(self) -> t.Optional[timedelta]: + """The default value for ``max_age`` for :func:`~flask.send_file`. The default + is ``None``, which tells the browser to use conditional requests instead of a + timed cache. + + .. deprecated:: 2.2 + Will be removed in Flask 2.3. Use + ``app.config["SEND_FILE_MAX_AGE_DEFAULT"]`` instead. + + .. versionchanged:: 2.0 + Defaults to ``None`` instead of 12 hours. + """ + import warnings + + warnings.warn( + "'send_file_max_age_default' is deprecated and will be removed in Flask" + " 2.3. Use 'SEND_FILE_MAX_AGE_DEFAULT' in 'app.config' instead.", + DeprecationWarning, + stacklevel=2, + ) + return _make_timedelta(self.config["SEND_FILE_MAX_AGE_DEFAULT"]) + + @send_file_max_age_default.setter + def send_file_max_age_default(self, value: t.Union[int, timedelta, None]) -> None: + import warnings + + warnings.warn( + "'send_file_max_age_default' is deprecated and will be removed in Flask" + " 2.3. Use 'SEND_FILE_MAX_AGE_DEFAULT' in 'app.config' instead.", + DeprecationWarning, + stacklevel=2, + ) + self.config["SEND_FILE_MAX_AGE_DEFAULT"] = _make_timedelta(value) + + @property + def use_x_sendfile(self) -> bool: + """Enable this to use the ``X-Sendfile`` feature, assuming the server supports + it, from :func:`~flask.send_file`. + + .. deprecated:: 2.2 + Will be removed in Flask 2.3. Use ``app.config["USE_X_SENDFILE"]`` instead. + """ + import warnings + + warnings.warn( + "'use_x_sendfile' is deprecated and will be removed in Flask 2.3. Use" + " 'USE_X_SENDFILE' in 'app.config' instead.", + DeprecationWarning, + stacklevel=2, + ) + return self.config["USE_X_SENDFILE"] + + @use_x_sendfile.setter + def use_x_sendfile(self, value: bool) -> None: + import warnings + + warnings.warn( + "'use_x_sendfile' is deprecated and will be removed in Flask 2.3. Use" + " 'USE_X_SENDFILE' in 'app.config' instead.", + DeprecationWarning, + stacklevel=2, + ) + self.config["USE_X_SENDFILE"] = value + + _json_encoder: t.Union[t.Type[json.JSONEncoder], None] = None + _json_decoder: t.Union[t.Type[json.JSONDecoder], None] = None + + @property # type: ignore[override] + def json_encoder(self) -> t.Type[json.JSONEncoder]: # type: ignore[override] + """The JSON encoder class to use. Defaults to + :class:`~flask.json.JSONEncoder`. + + .. deprecated:: 2.2 + Will be removed in Flask 2.3. Customize + :attr:`json_provider_class` instead. + + .. versionadded:: 0.10 + """ + import warnings + + warnings.warn( + "'app.json_encoder' is deprecated and will be removed in Flask 2.3." + " Customize 'app.json_provider_class' or 'app.json' instead.", + DeprecationWarning, + stacklevel=2, + ) + + if self._json_encoder is None: + from . import json + + return json.JSONEncoder + + return self._json_encoder + + @json_encoder.setter + def json_encoder(self, value: t.Type[json.JSONEncoder]) -> None: + import warnings + + warnings.warn( + "'app.json_encoder' is deprecated and will be removed in Flask 2.3." + " Customize 'app.json_provider_class' or 'app.json' instead.", + DeprecationWarning, + stacklevel=2, + ) + self._json_encoder = value + + @property # type: ignore[override] + def json_decoder(self) -> t.Type[json.JSONDecoder]: # type: ignore[override] + """The JSON decoder class to use. Defaults to + :class:`~flask.json.JSONDecoder`. + + .. deprecated:: 2.2 + Will be removed in Flask 2.3. Customize + :attr:`json_provider_class` instead. + + .. versionadded:: 0.10 + """ + import warnings + + warnings.warn( + "'app.json_decoder' is deprecated and will be removed in Flask 2.3." + " Customize 'app.json_provider_class' or 'app.json' instead.", + DeprecationWarning, + stacklevel=2, + ) + + if self._json_decoder is None: + from . import json + + return json.JSONDecoder + + return self._json_decoder + + @json_decoder.setter + def json_decoder(self, value: t.Type[json.JSONDecoder]) -> None: + import warnings + + warnings.warn( + "'app.json_decoder' is deprecated and will be removed in Flask 2.3." + " Customize 'app.json_provider_class' or 'app.json' instead.", + DeprecationWarning, + stacklevel=2, + ) + self._json_decoder = value + + json_provider_class: t.Type[JSONProvider] = DefaultJSONProvider + """A subclass of :class:`~flask.json.provider.JSONProvider`. An + instance is created and assigned to :attr:`app.json` when creating + the app. + + The default, :class:`~flask.json.provider.DefaultJSONProvider`, uses + Python's built-in :mod:`json` library. A different provider can use + a different JSON library. + + .. versionadded:: 2.2 + """ + + #: Options that are passed to the Jinja environment in + #: :meth:`create_jinja_environment`. Changing these options after + #: the environment is created (accessing :attr:`jinja_env`) will + #: have no effect. + #: + #: .. versionchanged:: 1.1.0 + #: This is a ``dict`` instead of an ``ImmutableDict`` to allow + #: easier configuration. + #: + jinja_options: dict = {} + + #: Default configuration parameters. + default_config = ImmutableDict( + { + "ENV": None, + "DEBUG": None, + "TESTING": False, + "PROPAGATE_EXCEPTIONS": None, + "SECRET_KEY": None, + "PERMANENT_SESSION_LIFETIME": timedelta(days=31), + "USE_X_SENDFILE": False, + "SERVER_NAME": None, + "APPLICATION_ROOT": "/", + "SESSION_COOKIE_NAME": "session", + "SESSION_COOKIE_DOMAIN": None, + "SESSION_COOKIE_PATH": None, + "SESSION_COOKIE_HTTPONLY": True, + "SESSION_COOKIE_SECURE": False, + "SESSION_COOKIE_SAMESITE": None, + "SESSION_REFRESH_EACH_REQUEST": True, + "MAX_CONTENT_LENGTH": None, + "SEND_FILE_MAX_AGE_DEFAULT": None, + "TRAP_BAD_REQUEST_ERRORS": None, + "TRAP_HTTP_EXCEPTIONS": False, + "EXPLAIN_TEMPLATE_LOADING": False, + "PREFERRED_URL_SCHEME": "http", + "JSON_AS_ASCII": None, + "JSON_SORT_KEYS": None, + "JSONIFY_PRETTYPRINT_REGULAR": None, + "JSONIFY_MIMETYPE": None, + "TEMPLATES_AUTO_RELOAD": None, + "MAX_COOKIE_SIZE": 4093, + } + ) + + #: 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: t.Optional[t.Type["FlaskClient"]] = 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: t.Optional[t.Type["FlaskCliRunner"]] = None + + #: the session interface to use. By default an instance of + #: :class:`~flask.sessions.SecureCookieSessionInterface` is used here. + #: + #: .. versionadded:: 0.8 + session_interface: SessionInterface = SecureCookieSessionInterface() + + def __init__( + self, + import_name: str, + static_url_path: t.Optional[str] = None, + static_folder: t.Optional[t.Union[str, os.PathLike]] = "static", + static_host: t.Optional[str] = None, + host_matching: bool = False, + subdomain_matching: bool = False, + template_folder: t.Optional[str] = "templates", + instance_path: t.Optional[str] = None, + instance_relative_config: bool = False, + root_path: t.Optional[str] = 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: t.List[ + t.Callable[[Exception, str, t.Dict[str, t.Any]], str] + ] = [] + + #: A list of functions that will be called at the beginning of the + #: first request to this instance. To register a function, use the + #: :meth:`before_first_request` decorator. + #: + #: .. deprecated:: 2.2 + #: Will be removed in Flask 2.3. Run setup code when + #: creating the application instead. + #: + #: .. versionadded:: 0.8 + self.before_first_request_funcs: t.List[ft.BeforeFirstRequestCallable] = [] + + #: 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: t.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: t.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: t.Dict[str, "Blueprint"] = {} + + #: a place where extensions can store application specific state. For + #: example this is where an extension could store database engines and + #: similar things. + #: + #: The key must match the name of the extension module. For example in + #: case of a "Flask-Foo" extension in `flask_foo`, the key would be + #: ``'foo'``. + #: + #: .. versionadded:: 0.7 + self.extensions: dict = {} + + #: The :class:`~werkzeug.routing.Map` for this instance. You can use + #: this to change the routing converters after the class was created + #: but before any routes are connected. Example:: + #: + #: from werkzeug.routing import BaseConverter + #: + #: class ListConverter(BaseConverter): + #: def to_python(self, value): + #: return value.split(',') + #: def to_url(self, values): + #: return ','.join(super(ListConverter, self).to_url(value) + #: for value in values) + #: + #: app = Flask(__name__) + #: app.url_map.converters['list'] = ListConverter + self.url_map = self.url_map_class() + + self.url_map.host_matching = host_matching + self.subdomain_matching = subdomain_matching + + # tracks internally if the application already handled at least one + # request. + self._got_first_request = False + self._before_request_lock = Lock() + + # 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 + ) + + # 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 _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." + ) + + @locked_cached_property + def name(self) -> str: # type: ignore + """The name of the application. This is usually the import name + with the difference that it's guessed from the run file if the + import name is main. This name is used as a display name when + Flask needs the name of the application. It can be set and overridden + to change the value. + + .. versionadded:: 0.8 + """ + if self.import_name == "__main__": + fn = 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 + + @property + def propagate_exceptions(self) -> bool: + """Returns the value of the ``PROPAGATE_EXCEPTIONS`` configuration + value in case it's set, otherwise a sensible default is returned. + + .. deprecated:: 2.2 + Will be removed in Flask 2.3. + + .. versionadded:: 0.7 + """ + import warnings + + warnings.warn( + "'propagate_exceptions' is deprecated and will be removed in Flask 2.3.", + DeprecationWarning, + stacklevel=2, + ) + rv = self.config["PROPAGATE_EXCEPTIONS"] + if rv is not None: + return rv + return self.testing or self.debug + + @locked_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) + + @locked_cached_property + def jinja_env(self) -> Environment: + """The Jinja environment used to load templates. + + The environment is created the first time this property is + accessed. Changing :attr:`jinja_options` after that will have no + effect. + """ + return self.create_jinja_environment() + + @property + def got_first_request(self) -> bool: + """This attribute is set to ``True`` if the application started + handling the first request. + + .. versionadded:: 0.8 + """ + return self._got_first_request + + def make_config(self, instance_relative: bool = False) -> Config: + """Used to create the config attribute by the Flask constructor. + The `instance_relative` parameter is passed in from the constructor + of Flask (there named `instance_relative_config`) and indicates if + the config should be relative to the instance path or the root path + of the application. + + .. versionadded:: 0.8 + """ + root_path = self.root_path + if instance_relative: + root_path = self.instance_path + defaults = dict(self.default_config) + defaults["ENV"] = os.environ.get("FLASK_ENV") or "production" + 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 open_instance_resource(self, resource: str, mode: str = "rb") -> t.IO[t.AnyStr]: + """Opens a resource from the application's instance folder + (:attr:`instance_path`). Otherwise works like + :meth:`open_resource`. Instance resources can also be opened for + writing. + + :param resource: the name of the resource. To access resources within + subfolders use forward slashes as separator. + :param mode: resource file opening mode, default is 'rb'. + """ + return open(os.path.join(self.instance_path, resource), mode) + + @property + def templates_auto_reload(self) -> bool: + """Reload templates when they are changed. Used by + :meth:`create_jinja_environment`. It is enabled by default in debug mode. + + .. deprecated:: 2.2 + Will be removed in Flask 2.3. Use ``app.config["TEMPLATES_AUTO_RELOAD"]`` + instead. + + .. versionadded:: 1.0 + This property was added but the underlying config and behavior + already existed. + """ + import warnings + + warnings.warn( + "'templates_auto_reload' is deprecated and will be removed in Flask 2.3." + " Use 'TEMPLATES_AUTO_RELOAD' in 'app.config' instead.", + DeprecationWarning, + stacklevel=2, + ) + rv = self.config["TEMPLATES_AUTO_RELOAD"] + return rv if rv is not None else self.debug + + @templates_auto_reload.setter + def templates_auto_reload(self, value: bool) -> None: + import warnings + + warnings.warn( + "'templates_auto_reload' is deprecated and will be removed in Flask 2.3." + " Use 'TEMPLATES_AUTO_RELOAD' in 'app.config' instead.", + DeprecationWarning, + stacklevel=2, + ) + self.config["TEMPLATES_AUTO_RELOAD"] = value + + 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_global_jinja_loader(self) -> DispatchingJinjaLoader: + """Creates the loader for the Jinja2 environment. Can be used to + override just the loader and keeping the rest unchanged. It's + discouraged to override this function. Instead one should override + the :meth:`jinja_loader` function instead. + + The global loader dispatches between the loaders of the application + and the individual blueprints. + + .. versionadded:: 0.7 + """ + return DispatchingJinjaLoader(self) + + def select_jinja_autoescape(self, filename: str) -> bool: + """Returns ``True`` if autoescaping should be active for the given + template name. If no template name is given, returns `True`. + + .. versionadded:: 0.5 + """ + if filename is None: + return True + return filename.endswith((".html", ".htm", ".xml", ".xhtml")) + + def update_template_context(self, context: dict) -> 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[t.Optional[str]] = (None,) + + # A template may be rendered outside a request context. + if request: + names = chain(names, reversed(request.blueprints)) + + # The values passed to render_template take precedence. Keep a + # copy to re-apply after all context functions. + orig_ctx = context.copy() + + for name in names: + if name in self.template_context_processors: + for func in self.template_context_processors[name]: + context.update(func()) + + context.update(orig_ctx) + + def make_shell_context(self) -> dict: + """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 + + @property + def env(self) -> str: + """What environment the app is running in. This maps to the :data:`ENV` config + key. + + **Do not enable development when deploying in production.** + + Default: ``'production'`` + + .. deprecated:: 2.2 + Will be removed in Flask 2.3. + """ + import warnings + + warnings.warn( + "'app.env' is deprecated and will be removed in Flask 2.3." + " Use 'app.debug' instead.", + DeprecationWarning, + stacklevel=2, + ) + return self.config["ENV"] + + @env.setter + def env(self, value: str) -> None: + import warnings + + warnings.warn( + "'app.env' is deprecated and will be removed in Flask 2.3." + " Use 'app.debug' instead.", + DeprecationWarning, + stacklevel=2, + ) + self.config["ENV"] = value + + @property + def debug(self) -> bool: + """Whether debug mode is enabled. When using ``flask run`` to start the + development server, an interactive debugger will be shown for unhandled + exceptions, and the server will be reloaded when code changes. This maps to the + :data:`DEBUG` config key. It may not behave as expected if set late. + + **Do not enable debug mode when deploying in production.** + + Default: ``False`` + """ + return self.config["DEBUG"] + + @debug.setter + def debug(self, value: bool) -> None: + self.config["DEBUG"] = value + + if self.config["TEMPLATES_AUTO_RELOAD"] is None: + self.jinja_env.auto_reload = value + + def run( + self, + host: t.Optional[str] = None, + port: t.Optional[int] = None, + debug: t.Optional[bool] = 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, let env vars override previous values + if "FLASK_ENV" in os.environ: + print( + "'FLASK_ENV' is deprecated and will not be used in" + " Flask 2.3. Use 'FLASK_DEBUG' instead.", + file=sys.stderr, + ) + self.config["ENV"] = os.environ.get("FLASK_ENV") or "production" + self.debug = get_debug_flag() + elif "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 # type: ignore + 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 # type: ignore + + return cls(self, **kwargs) # type: ignore + + @setupmethod + def register_blueprint(self, blueprint: "Blueprint", **options: t.Any) -> None: + """Register a :class:`~flask.Blueprint` on the application. Keyword + arguments passed to this method will override the defaults set on the + blueprint. + + Calls the blueprint's :meth:`~flask.Blueprint.register` method after + recording the blueprint in the application's :attr:`blueprints`. + + :param blueprint: The blueprint to register. + :param url_prefix: Blueprint routes will be prefixed with this. + :param subdomain: Blueprint routes will match on this subdomain. + :param url_defaults: Blueprint routes will use these default values for + view arguments. + :param options: Additional keyword arguments are passed to + :class:`~flask.blueprints.BlueprintSetupState`. They can be + accessed in :meth:`~flask.Blueprint.record` callbacks. + + .. versionchanged:: 2.0.1 + The ``name`` option can be used to change the (pre-dotted) + name the blueprint is registered with. This allows the same + blueprint to be registered multiple times with unique names + for ``url_for``. + + .. versionadded:: 0.7 + """ + blueprint.register(self, options) + + def iter_blueprints(self) -> t.ValuesView["Blueprint"]: + """Iterates over all blueprints by the order they were registered. + + .. versionadded:: 0.11 + """ + return self.blueprints.values() + + @setupmethod + def add_url_rule( + self, + rule: str, + endpoint: t.Optional[str] = None, + view_func: t.Optional[ft.RouteCallable] = None, + provide_automatic_options: t.Optional[bool] = None, + **options: t.Any, + ) -> None: + if endpoint is None: + endpoint = _endpoint_from_view_func(view_func) # type: ignore + options["endpoint"] = endpoint + methods = options.pop("methods", None) + + # if the methods are not given and the view_func object knows its + # methods we can use that instead. If neither exists, we go with + # a tuple of only ``GET`` as default. + if methods is None: + methods = getattr(view_func, "methods", None) or ("GET",) + if isinstance(methods, str): + raise TypeError( + "Allowed methods must be a list of strings, for" + ' example: @app.route(..., methods=["POST"])' + ) + methods = {item.upper() for item in methods} + + # Methods that should always be added + required_methods = set(getattr(view_func, "required_methods", ())) + + # starting with Flask 0.8 the view_func object can disable and + # force-enable the automatic options handling. + if provide_automatic_options is None: + provide_automatic_options = getattr( + view_func, "provide_automatic_options", None + ) + + if provide_automatic_options is None: + if "OPTIONS" not in methods: + provide_automatic_options = True + required_methods.add("OPTIONS") + else: + provide_automatic_options = False + + # Add the required methods now. + methods |= required_methods + + rule = self.url_rule_class(rule, methods=methods, **options) + rule.provide_automatic_options = provide_automatic_options # type: ignore + + self.url_map.add(rule) + if view_func is not None: + old_func = self.view_functions.get(endpoint) + if old_func is not None and old_func != view_func: + raise AssertionError( + "View function mapping is overwriting an existing" + f" endpoint function: {endpoint}" + ) + self.view_functions[endpoint] = view_func + + @setupmethod + def template_filter( + self, name: t.Optional[str] = None + ) -> t.Callable[[T_template_filter], T_template_filter]: + """A decorator that is used to register custom template filter. + You can specify a name for the filter, otherwise the function + name will be used. Example:: + + @app.template_filter() + def reverse(s): + return s[::-1] + + :param name: the optional name of the filter, otherwise the + function name will be used. + """ + + def decorator(f: T_template_filter) -> T_template_filter: + self.add_template_filter(f, name=name) + return f + + return decorator + + @setupmethod + def add_template_filter( + self, f: ft.TemplateFilterCallable, name: t.Optional[str] = None + ) -> None: + """Register a custom template filter. Works exactly like the + :meth:`template_filter` decorator. + + :param name: the optional name of the filter, otherwise the + function name will be used. + """ + self.jinja_env.filters[name or f.__name__] = f + + @setupmethod + def template_test( + self, name: t.Optional[str] = None + ) -> t.Callable[[T_template_test], T_template_test]: + """A decorator that is used to register custom template test. + You can specify a name for the test, otherwise the function + name will be used. Example:: + + @app.template_test() + def is_prime(n): + if n == 2: + return True + for i in range(2, int(math.ceil(math.sqrt(n))) + 1): + if n % i == 0: + return False + return True + + .. versionadded:: 0.10 + + :param name: the optional name of the test, otherwise the + function name will be used. + """ + + def decorator(f: T_template_test) -> T_template_test: + self.add_template_test(f, name=name) + return f + + return decorator + + @setupmethod + def add_template_test( + self, f: ft.TemplateTestCallable, name: t.Optional[str] = None + ) -> None: + """Register a custom template test. Works exactly like the + :meth:`template_test` decorator. + + .. versionadded:: 0.10 + + :param name: the optional name of the test, otherwise the + function name will be used. + """ + self.jinja_env.tests[name or f.__name__] = f + + @setupmethod + def template_global( + self, name: t.Optional[str] = None + ) -> t.Callable[[T_template_global], T_template_global]: + """A decorator that is used to register a custom template global function. + You can specify a name for the global function, otherwise the function + name will be used. Example:: + + @app.template_global() + def double(n): + return 2 * n + + .. versionadded:: 0.10 + + :param name: the optional name of the global function, otherwise the + function name will be used. + """ + + def decorator(f: T_template_global) -> T_template_global: + self.add_template_global(f, name=name) + return f + + return decorator + + @setupmethod + def add_template_global( + self, f: ft.TemplateGlobalCallable, name: t.Optional[str] = None + ) -> None: + """Register a custom template global function. Works exactly like the + :meth:`template_global` decorator. + + .. versionadded:: 0.10 + + :param name: the optional name of the global function, otherwise the + function name will be used. + """ + self.jinja_env.globals[name or f.__name__] = f + + @setupmethod + def before_first_request(self, f: T_before_first_request) -> T_before_first_request: + """Registers a function to be run before the first request to this + instance of the application. + + The function will be called without any arguments and its return + value is ignored. + + .. deprecated:: 2.2 + Will be removed in Flask 2.3. Run setup code when creating + the application instead. + + .. versionadded:: 0.8 + """ + import warnings + + warnings.warn( + "'before_first_request' is deprecated and will be removed" + " in Flask 2.3. Run setup code while creating the" + " application instead.", + DeprecationWarning, + stacklevel=2, + ) + self.before_first_request_funcs.append(f) + return f + + @setupmethod + def teardown_appcontext(self, f: T_teardown) -> T_teardown: + """Registers a function to be called when the application + context is popped. The application context is typically popped + after the request context for each request, at the end of CLI + commands, or after a manually pushed context ends. + + .. code-block:: python + + with app.app_context(): + ... + + When the ``with`` block exits (or ``ctx.pop()`` is called), the + teardown functions are called just before the app context is + made inactive. Since a request context typically also manages an + application context it would also be called when you pop a + request context. + + When a teardown function was called because of an unhandled + exception it will be passed an error object. If an + :meth:`errorhandler` is registered, it will handle the exception + and the teardown will not receive it. + + Teardown functions must avoid raising exceptions. If they + execute code that might fail they must surround that code with a + ``try``/``except`` block and log any errors. + + The return values of teardown functions are ignored. + + .. versionadded:: 0.9 + """ + self.teardown_appcontext_funcs.append(f) + return f + + @setupmethod + def shell_context_processor( + self, f: T_shell_context_processor + ) -> T_shell_context_processor: + """Registers a shell context processor function. + + .. versionadded:: 0.11 + """ + self.shell_context_processors.append(f) + return f + + def _find_error_handler(self, e: Exception) -> t.Optional[ft.ErrorHandlerCallable]: + """Return a registered error handler for an exception in this order: + blueprint handler for a specific code, app handler for a specific code, + blueprint handler for an exception class, app handler for an exception + class, or ``None`` if a suitable handler is not found. + """ + exc_class, code = self._get_exc_class_and_code(type(e)) + names = (*request.blueprints, None) + + for c in (code, None) if code is not None else (None,): + for name in names: + handler_map = self.error_handler_spec[name][c] + + if not handler_map: + continue + + for cls in exc_class.__mro__: + handler = handler_map.get(cls) + + if handler is not None: + return handler + return None + + def handle_http_exception( + self, e: HTTPException + ) -> t.Union[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) + if handler is None: + return e + return self.ensure_sync(handler)(e) + + def trap_http_exception(self, e: Exception) -> bool: + """Checks if an HTTP exception should be trapped or not. By default + this will return ``False`` for all exceptions except for a bad request + key error if ``TRAP_BAD_REQUEST_ERRORS`` is set to ``True``. It + also returns ``True`` if ``TRAP_HTTP_EXCEPTIONS`` is set to ``True``. + + This is called for all HTTP exceptions raised by a view function. + If it returns ``True`` for any exception the error handler for this + exception is not called and it shows up as regular exception in the + traceback. This is helpful for debugging implicitly raised HTTP + exceptions. + + .. versionchanged:: 1.0 + Bad request errors are not trapped by default in debug mode. + + .. versionadded:: 0.8 + """ + if self.config["TRAP_HTTP_EXCEPTIONS"]: + return True + + trap_bad_request = self.config["TRAP_BAD_REQUEST_ERRORS"] + + # if unset, trap key errors in debug mode + if ( + trap_bad_request is None + and self.debug + and isinstance(e, BadRequestKeyError) + ): + return True + + if trap_bad_request: + return isinstance(e, BadRequest) + + return False + + def handle_user_exception( + self, e: Exception + ) -> t.Union[HTTPException, ft.ResponseReturnValue]: + """This method is called whenever an exception occurs that + should be handled. A special case is :class:`~werkzeug + .exceptions.HTTPException` which is forwarded to the + :meth:`handle_http_exception` method. This function will either + return a response value or reraise the exception with the same + traceback. + + .. versionchanged:: 1.0 + Key errors raised from request data like ``form`` show the + bad key in debug mode rather than a generic bad request + message. + + .. versionadded:: 0.7 + """ + if isinstance(e, BadRequestKeyError) and ( + self.debug or self.config["TRAP_BAD_REQUEST_ERRORS"] + ): + e.show_exception = True + + if isinstance(e, HTTPException) and not self.trap_http_exception(e): + return self.handle_http_exception(e) + + handler = self._find_error_handler(e) + + if handler is None: + raise + + return self.ensure_sync(handler)(e) + + def handle_exception(self, e: Exception) -> Response: + """Handle an exception that did not have an error handler + associated with it, or that was raised from an error handler. + This always causes a 500 ``InternalServerError``. + + Always sends the :data:`got_request_exception` signal. + + If :attr:`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, exception=e) + propagate = self.config["PROPAGATE_EXCEPTIONS"] + + if propagate is None: + propagate = self.testing or self.debug + + if propagate: + # Re-raise if called with an active exception, otherwise + # raise the passed in exception. + if exc_info[1] is e: + raise + + raise e + + self.log_exception(exc_info) + server_error: t.Union[InternalServerError, ft.ResponseReturnValue] + server_error = InternalServerError(original_exception=e) + handler = self._find_error_handler(server_error) + + if handler is not None: + server_error = self.ensure_sync(handler)(server_error) + + return self.finalize_request(server_error, from_error_handler=True) + + def log_exception( + self, + exc_info: t.Union[ + t.Tuple[type, BaseException, TracebackType], t.Tuple[None, None, None] + ], + ) -> None: + """Logs an exception. This is called by :meth:`handle_exception` + if debugging is disabled and right before the handler is called. + The default implementation logs the exception as error on the + :attr:`logger`. + + .. versionadded:: 0.8 + """ + self.logger.error( + f"Exception on {request.path} [{request.method}]", exc_info=exc_info + ) + + def raise_routing_exception(self, request: Request) -> "te.NoReturn": + """Intercept routing exceptions and possibly do something else. + + In debug mode, intercept a routing redirect and replace it with + an error if the body will be discarded. + + With modern Werkzeug this shouldn't occur, since it now uses a + 308 status which tells the browser to resend the method and + body. + + .. versionchanged:: 2.1 + Don't intercept 307 and 308 redirects. + + :meta private: + :internal: + """ + if ( + not self.debug + or not isinstance(request.routing_exception, RequestRedirect) + or request.routing_exception.code in {307, 308} + or request.method in {"GET", "HEAD", "OPTIONS"} + ): + raise request.routing_exception # type: ignore + + from .debughelpers import FormDataRoutingRedirect + + raise FormDataRoutingRedirect(request) + + def dispatch_request(self) -> ft.ResponseReturnValue: + """Does the request dispatching. Matches the URL and returns the + return value of the view or error handler. This does not have to + be a response object. In order to convert the return value to a + proper response object, call :func:`make_response`. + + .. versionchanged:: 0.7 + This no longer does the exception handling, this code was + moved to the new :meth:`full_dispatch_request`. + """ + req = request_ctx.request + if req.routing_exception is not None: + self.raise_routing_exception(req) + rule: Rule = req.url_rule # type: ignore[assignment] + # if we provide automatic options for this URL and the + # request came with the OPTIONS method, reply automatically + if ( + getattr(rule, "provide_automatic_options", False) + and req.method == "OPTIONS" + ): + return self.make_default_options_response() + # otherwise dispatch to the handler for that endpoint + view_args: t.Dict[str, t.Any] = req.view_args # type: ignore[assignment] + return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args) + + def full_dispatch_request(self) -> Response: + """Dispatches the request and on top of that performs request + pre and postprocessing as well as HTTP exception catching and + error handling. + + .. versionadded:: 0.7 + """ + # Run before_first_request functions if this is the thread's first request. + # Inlined to avoid a method call on subsequent requests. + # This is deprecated, will be removed in Flask 2.3. + if not self._got_first_request: + with self._before_request_lock: + if not self._got_first_request: + for func in self.before_first_request_funcs: + self.ensure_sync(func)() + + self._got_first_request = True + + try: + request_started.send(self) + rv = self.preprocess_request() + if rv is None: + rv = self.dispatch_request() + except Exception as e: + rv = self.handle_user_exception(e) + return self.finalize_request(rv) + + def finalize_request( + self, + rv: t.Union[ft.ResponseReturnValue, HTTPException], + from_error_handler: bool = False, + ) -> Response: + """Given the return value from a view function this finalizes + the request by converting it into a response and invoking the + postprocessing functions. This is invoked for both normal + request dispatching as well as error handlers. + + Because this means that it might be called as a result of a + failure a special safe mode is available which can be enabled + with the `from_error_handler` flag. If enabled, failures in + response processing will be logged and otherwise ignored. + + :internal: + """ + response = self.make_response(rv) + try: + response = self.process_response(response) + request_finished.send(self, response=response) + except Exception: + if not from_error_handler: + raise + self.logger.exception( + "Request finalizing failed with an error while handling an error" + ) + return response + + def make_default_options_response(self) -> Response: + """This method is called to create the default ``OPTIONS`` response. + This can be changed through subclassing to change the default + behavior of ``OPTIONS`` responses. + + .. versionadded:: 0.7 + """ + adapter = request_ctx.url_adapter + methods = adapter.allowed_methods() # type: ignore[union-attr] + rv = self.response_class() + rv.allow.update(methods) + return rv + + def should_ignore_error(self, error: t.Optional[BaseException]) -> bool: + """This is called to figure out if an error should be ignored + or not as far as the teardown system is concerned. If this + function returns ``True`` then the teardown handlers will not be + passed the error. + + .. versionadded:: 0.10 + """ + return False + + def ensure_sync(self, func: t.Callable) -> t.Callable: + """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.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: t.Optional[str] = None, + _method: t.Optional[str] = None, + _scheme: t.Optional[str] = None, + _external: t.Optional[bool] = None, + **values: t.Any, + ) -> str: + """Generate a URL to the given endpoint with the given values. + + This is called by :func:`flask.url_for`, and can be called + directly as well. + + An *endpoint* is the name of a URL rule, usually added with + :meth:`@app.route() `, and usually the same name as the + view function. A route defined in a :class:`~flask.Blueprint` + will prepend the blueprint's name separated by a ``.`` to the + endpoint. + + In some cases, such as email messages, you want URLs to include + the scheme and domain, like ``https://example.com/hello``. When + not in an active request, URLs will be external by default, but + this requires setting :data:`SERVER_NAME` so Flask knows what + domain to use. :data:`APPLICATION_ROOT` and + :data:`PREFERRED_URL_SCHEME` should also be configured as + needed. This config is only used when not in an active request. + + Functions can be decorated with :meth:`url_defaults` to modify + keyword arguments before the URL is built. + + If building fails for some reason, such as an unknown endpoint + or incorrect values, the app's :meth:`handle_url_build_error` + method is called. If that returns a string, that is returned, + otherwise a :exc:`~werkzeug.routing.BuildError` is raised. + + :param endpoint: The endpoint name associated with the URL to + generate. If this starts with a ``.``, the current blueprint + name (if any) will be used. + :param _anchor: If given, append this as ``#anchor`` to the URL. + :param _method: If given, generate the URL associated with this + method for the endpoint. + :param _scheme: If given, the URL will have this scheme if it + is external. + :param _external: If given, prefer the URL to be internal + (False) or require it to be external (True). External URLs + include the scheme and domain. When not in an active + request, URLs are external by default. + :param values: Values to use for the variable parts of the URL + rule. Unknown keys are appended as query string arguments, + like ``?a=b&c=d``. + + .. versionadded:: 2.2 + Moved from ``flask.url_for``, which calls this method. + """ + req_ctx = _cv_request.get(None) + + if req_ctx is not None: + url_adapter = req_ctx.url_adapter + blueprint_name = req_ctx.request.blueprint + + # If the endpoint starts with "." and the request matches a + # blueprint, the endpoint is relative to the blueprint. + if endpoint[:1] == ".": + if blueprint_name is not None: + endpoint = f"{blueprint_name}{endpoint}" + else: + endpoint = endpoint[1:] + + # When in a request, generate a URL without scheme and + # domain by default, unless a scheme is given. + if _external is None: + _external = _scheme is not None + else: + app_ctx = _cv_app.get(None) + + # If called by helpers.url_for, an app context is active, + # use its url_adapter. Otherwise, app.url_for was called + # directly, build an adapter. + if app_ctx is not None: + url_adapter = app_ctx.url_adapter + else: + url_adapter = self.create_url_adapter(None) + + if url_adapter is None: + raise RuntimeError( + "Unable to build URLs outside an active request" + " without 'SERVER_NAME' configured. Also configure" + " 'APPLICATION_ROOT' and 'PREFERRED_URL_SCHEME' as" + " needed." + ) + + # When outside a request, generate a URL with scheme and + # domain by default. + if _external is None: + _external = True + + # It is an error to set _scheme when _external=False, in order + # to avoid accidental insecure URLs. + if _scheme is not None and not _external: + raise ValueError("When specifying '_scheme', '_external' must be True.") + + self.inject_url_defaults(endpoint, values) + + try: + rv = url_adapter.build( # type: ignore[union-attr] + endpoint, + values, + method=_method, + url_scheme=_scheme, + force_external=_external, + ) + except BuildError as error: + values.update( + _anchor=_anchor, _method=_method, _scheme=_scheme, _external=_external + ) + return self.handle_url_build_error(error, endpoint, values) + + if _anchor is not None: + rv = f"{rv}#{url_quote(_anchor)}" + + return rv + + def redirect(self, location: str, code: int = 302) -> BaseResponse: + """Create a redirect response object. + + This is called by :func:`flask.redirect`, and can be called + directly as well. + + :param location: The URL to redirect to. + :param code: The status code for the redirect. + + .. versionadded:: 2.2 + Moved from ``flask.redirect``, which calls this method. + """ + return _wz_redirect(location, code=code, Response=self.response_class) + + def make_response(self, rv: ft.ResponseReturnValue) -> Response: + """Convert the return value from a view function to an instance of + :attr:`response_class`. + + :param rv: the return value from the view function. The view function + must return a response. Returning ``None``, or the view ending + without returning, is not allowed. The following types are allowed + for ``view_rv``: + + ``str`` + A response object is created with the string encoded to UTF-8 + as the body. + + ``bytes`` + A response object is created with the bytes as the body. + + ``dict`` + A dictionary that will be jsonify'd before being returned. + + ``list`` + A list that will be jsonify'd before being returned. + + ``generator`` or ``iterator`` + A generator that returns ``str`` or ``bytes`` to be + streamed as the response. + + ``tuple`` + Either ``(body, status, headers)``, ``(body, status)``, or + ``(body, headers)``, where ``body`` is any of the other types + allowed here, ``status`` is a string or an integer, and + ``headers`` is a dictionary or a list of ``(key, value)`` + tuples. If ``body`` is a :attr:`response_class` instance, + ``status`` overwrites the exiting value and ``headers`` are + extended. + + :attr:`response_class` + The object is returned unchanged. + + other :class:`~werkzeug.wrappers.Response` class + The object is coerced to :attr:`response_class`. + + :func:`callable` + The function is called as a WSGI application. The result is + used to create a response object. + + .. versionchanged:: 2.2 + A generator will be converted to a streaming response. + A list will be converted to a JSON response. + + .. versionchanged:: 1.1 + A dict will be converted to a JSON response. + + .. versionchanged:: 0.9 + Previously a tuple was interpreted as the arguments for the + response object. + """ + + status = headers = None + + # unpack tuple returns + if isinstance(rv, tuple): + len_rv = len(rv) + + # a 3-tuple is unpacked directly + if len_rv == 3: + rv, status, headers = rv # type: ignore[misc] + # decide if a 2-tuple has status or headers + elif len_rv == 2: + if isinstance(rv[1], (Headers, dict, tuple, list)): + rv, headers = rv + else: + rv, status = rv # type: ignore[assignment,misc] + # other sized tuples are not allowed + else: + raise TypeError( + "The view function did not return a valid response tuple." + " The tuple must have the form (body, status, headers)," + " (body, status), or (body, headers)." + ) + + # the body must not be None + if rv is None: + raise TypeError( + f"The view function for {request.endpoint!r} did not" + " return a valid response. The function either returned" + " None or ended without a return statement." + ) + + # make sure the body is an instance of the response class + if not isinstance(rv, self.response_class): + if isinstance(rv, (str, bytes, bytearray)) or isinstance(rv, _abc_Iterator): + # let the response class set the status and headers instead of + # waiting to do it manually, so that the class can handle any + # special logic + rv = self.response_class( + rv, + status=status, + headers=headers, # type: ignore[arg-type] + ) + status = headers = None + elif isinstance(rv, (dict, list)): + rv = self.json.response(rv) + elif isinstance(rv, BaseResponse) or callable(rv): + # evaluate a WSGI callable, or coerce a different response + # class to the correct type + try: + rv = self.response_class.force_type( + rv, request.environ # type: ignore[arg-type] + ) + except TypeError as e: + raise TypeError( + f"{e}\nThe view function did not return a valid" + " response. The return type must be a string," + " dict, list, tuple with headers or status," + " Response instance, or WSGI callable, but it" + f" was a {type(rv).__name__}." + ).with_traceback(sys.exc_info()[2]) from None + else: + raise TypeError( + "The view function did not return a valid" + " response. The return type must be a string," + " dict, list, tuple with headers or status," + " Response instance, or WSGI callable, but it was a" + f" {type(rv).__name__}." + ) + + rv = t.cast(Response, rv) + # prefer the status if it was provided + if status is not None: + if isinstance(status, (str, bytes, bytearray)): + rv.status = status + else: + rv.status_code = status + + # extend existing headers with provided headers + if headers: + rv.headers.update(headers) # type: ignore[arg-type] + + return rv + + def create_url_adapter( + self, request: t.Optional[Request] + ) -> t.Optional[MapAdapter]: + """Creates a URL adapter for the given request. The URL adapter + is created at a point where the request context is not yet set + up so the request is passed explicitly. + + .. versionadded:: 0.6 + + .. versionchanged:: 0.9 + This can now also be called without a request object when the + URL adapter is created for the application context. + + .. versionchanged:: 1.0 + :data:`SERVER_NAME` no longer implicitly enables subdomain + matching. Use :attr:`subdomain_matching` instead. + """ + if request is not None: + # If subdomain matching is disabled (the default), use the + # default subdomain in all cases. This should be the default + # in Werkzeug but it currently does not have that feature. + if not self.subdomain_matching: + subdomain = self.url_map.default_subdomain or None + else: + subdomain = None + + return self.url_map.bind_to_environ( + request.environ, + server_name=self.config["SERVER_NAME"], + subdomain=subdomain, + ) + # We need at the very least the server name to be set for this + # to work. + if self.config["SERVER_NAME"] is not None: + return self.url_map.bind( + self.config["SERVER_NAME"], + script_name=self.config["APPLICATION_ROOT"], + url_scheme=self.config["PREFERRED_URL_SCHEME"], + ) + + return None + + def inject_url_defaults(self, endpoint: str, values: dict) -> None: + """Injects the URL defaults for the given endpoint directly into + the values dictionary passed. This is used internally and + automatically called on URL building. + + .. versionadded:: 0.7 + """ + names: t.Iterable[t.Optional[str]] = (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: t.Dict[str, t.Any] + ) -> str: + """Called by :meth:`.url_for` if a + :exc:`~werkzeug.routing.BuildError` was raised. If this returns + a value, it will be returned by ``url_for``, otherwise the error + will be re-raised. + + Each function in :attr:`url_build_error_handlers` is called with + ``error``, ``endpoint`` and ``values``. If a function returns + ``None`` or raises a ``BuildError``, it is skipped. Otherwise, + its return value is returned by ``url_for``. + + :param error: The active ``BuildError`` being handled. + :param endpoint: The endpoint being built. + :param values: The keyword arguments passed to ``url_for``. + """ + for handler in self.url_build_error_handlers: + try: + rv = handler(error, endpoint, values) + except BuildError as e: + # make error available outside except block + error = e + else: + if rv is not None: + return rv + + # Re-raise if called with an active exception, otherwise raise + # the passed in exception. + if error is sys.exc_info()[1]: + raise + + raise error + + def preprocess_request(self) -> t.Optional[ft.ResponseReturnValue]: + """Called before the request is dispatched. Calls + :attr:`url_value_preprocessors` registered with the app and the + current blueprint (if any). Then calls :attr:`before_request_funcs` + registered with the app and the blueprint. + + If any :meth:`before_request` handler returns a non-None value, the + value is handled as if it was the return value from the view, and + further request handling is stopped. + """ + names = (None, *reversed(request.blueprints)) + + for name in names: + if name in self.url_value_preprocessors: + for url_func in self.url_value_preprocessors[name]: + url_func(request.endpoint, request.view_args) + + for name in names: + if name in self.before_request_funcs: + for before_func in self.before_request_funcs[name]: + rv = self.ensure_sync(before_func)() + + if rv is not None: + return rv + + return None + + def process_response(self, response: Response) -> Response: + """Can be overridden in order to modify the response object + before it's sent to the WSGI server. By default this will + call all the :meth:`after_request` decorated functions. + + .. versionchanged:: 0.5 + As of Flask 0.5 the functions registered for after request + execution are called in reverse order of registration. + + :param response: a :attr:`response_class` object. + :return: a new response object or the same, has to be an + instance of :attr:`response_class`. + """ + ctx = request_ctx._get_current_object() # type: ignore[attr-defined] + + for func in ctx._after_request_functions: + response = self.ensure_sync(func)(response) + + for name in chain(request.blueprints, (None,)): + if name in self.after_request_funcs: + for func in reversed(self.after_request_funcs[name]): + response = self.ensure_sync(func)(response) + + if not self.session_interface.is_null_session(ctx.session): + self.session_interface.save_session(self, ctx.session, response) + + return response + + def do_teardown_request( + self, exc: t.Optional[BaseException] = _sentinel # type: ignore + ) -> None: + """Called after the request is dispatched and the response is + returned, right before the request context is popped. + + This calls all functions decorated with + :meth:`teardown_request`, and :meth:`Blueprint.teardown_request` + if a blueprint handled the request. Finally, the + :data:`request_tearing_down` signal is sent. + + This is called by + :meth:`RequestContext.pop() `, + which may be delayed during testing to maintain access to + resources. + + :param exc: An unhandled exception raised while dispatching the + request. Detected from the current exception information if + not passed. Passed to each teardown function. + + .. versionchanged:: 0.9 + Added the ``exc`` argument. + """ + if exc is _sentinel: + exc = sys.exc_info()[1] + + for name in chain(request.blueprints, (None,)): + if name in self.teardown_request_funcs: + for func in reversed(self.teardown_request_funcs[name]): + self.ensure_sync(func)(exc) + + request_tearing_down.send(self, exc=exc) + + def do_teardown_appcontext( + self, exc: t.Optional[BaseException] = _sentinel # type: ignore + ) -> None: + """Called right before the application context is popped. + + When handling a request, the application context is popped + after the request context. See :meth:`do_teardown_request`. + + This calls all functions decorated with + :meth:`teardown_appcontext`. Then the + :data:`appcontext_tearing_down` signal is sent. + + This is called by + :meth:`AppContext.pop() `. + + .. versionadded:: 0.9 + """ + if exc is _sentinel: + exc = sys.exc_info()[1] + + for func in reversed(self.teardown_appcontext_funcs): + self.ensure_sync(func)(exc) + + appcontext_tearing_down.send(self, exc=exc) + + def app_context(self) -> AppContext: + """Create an :class:`~flask.ctx.AppContext`. Use as a ``with`` + block to push the context, which will make :data:`current_app` + point at this application. + + An application context is automatically pushed by + :meth:`RequestContext.push() ` + when handling a request, and when running a CLI command. Use + this to manually create a context outside of these situations. + + :: + + with app.app_context(): + init_db() + + See :doc:`/appcontext`. + + .. versionadded:: 0.9 + """ + return AppContext(self) + + def request_context(self, environ: dict) -> RequestContext: + """Create a :class:`~flask.ctx.RequestContext` representing a + WSGI environment. Use a ``with`` block to push the context, + which will make :data:`request` point at this request. + + See :doc:`/reqcontext`. + + Typically you should not call this from your own code. A request + context is automatically pushed by the :meth:`wsgi_app` when + handling a request. Use :meth:`test_request_context` to create + an environment and context instead of this method. + + :param environ: a WSGI environment + """ + return RequestContext(self, environ) + + def test_request_context(self, *args: t.Any, **kwargs: t.Any) -> RequestContext: + """Create a :class:`~flask.ctx.RequestContext` for a WSGI + environment created from the given values. This is mostly useful + during testing, where you may want to run a function that uses + request data without dispatching a full request. + + See :doc:`/reqcontext`. + + Use a ``with`` block to push the context, which will make + :data:`request` point at the request for the created + environment. :: + + with test_request_context(...): + generate_report() + + When using the shell, it may be easier to push and pop the + context manually to avoid indentation. :: + + ctx = app.test_request_context(...) + ctx.push() + ... + ctx.pop() + + Takes the same arguments as Werkzeug's + :class:`~werkzeug.test.EnvironBuilder`, with some defaults from + the application. See the linked Werkzeug docs for most of the + available arguments. Flask-specific behavior is listed here. + + :param path: URL path being requested. + :param base_url: Base URL where the app is being served, which + ``path`` is relative to. If not given, built from + :data:`PREFERRED_URL_SCHEME`, ``subdomain``, + :data:`SERVER_NAME`, and :data:`APPLICATION_ROOT`. + :param subdomain: Subdomain name to append to + :data:`SERVER_NAME`. + :param url_scheme: Scheme to use instead of + :data:`PREFERRED_URL_SCHEME`. + :param data: The request body, either as a string or a dict of + form keys and values. + :param json: If given, this is serialized as JSON and passed as + ``data``. Also defaults ``content_type`` to + ``application/json``. + :param args: other positional arguments passed to + :class:`~werkzeug.test.EnvironBuilder`. + :param kwargs: other keyword arguments passed to + :class:`~werkzeug.test.EnvironBuilder`. + """ + from .testing import EnvironBuilder + + builder = EnvironBuilder(self, *args, **kwargs) + + try: + return self.request_context(builder.get_environ()) + finally: + builder.close() + + def wsgi_app(self, environ: dict, start_response: t.Callable) -> t.Any: + """The actual WSGI application. This is not implemented in + :meth:`__call__` so that middlewares can be applied without + losing a reference to the app object. Instead of doing this:: + + app = MyMiddleware(app) + + It's a better idea to do this instead:: + + app.wsgi_app = MyMiddleware(app.wsgi_app) + + Then you still have the original application object around and + can continue to call methods on it. + + .. versionchanged:: 0.7 + Teardown events for the request and app contexts are called + even if an unhandled error occurs. Other events may not be + called depending on when an error occurs during dispatch. + See :ref:`callbacks-and-errors`. + + :param environ: A WSGI environment. + :param start_response: A callable accepting a status code, + a list of headers, and an optional exception context to + start the response. + """ + ctx = self.request_context(environ) + error: t.Optional[BaseException] = None + try: + try: + ctx.push() + response = self.full_dispatch_request() + except Exception as e: + error = e + response = self.handle_exception(e) + except: # noqa: B001 + error = sys.exc_info()[1] + raise + return response(environ, start_response) + finally: + if "werkzeug.debug.preserve_context" in environ: + environ["werkzeug.debug.preserve_context"](_cv_app.get()) + environ["werkzeug.debug.preserve_context"](_cv_request.get()) + + if error is not None and self.should_ignore_error(error): + error = None + + ctx.pop(error) + + def __call__(self, environ: dict, start_response: t.Callable) -> t.Any: + """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/.vtodo/Lib/site-packages/flask/blueprints.py b/.vtodo/Lib/site-packages/flask/blueprints.py new file mode 100644 index 0000000..104f8ac --- /dev/null +++ b/.vtodo/Lib/site-packages/flask/blueprints.py @@ -0,0 +1,706 @@ +import json +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 Flask + +DeferredSetupFunction = t.Callable[["BlueprintSetupState"], t.Callable] +T_after_request = t.TypeVar("T_after_request", bound=ft.AfterRequestCallable) +T_before_first_request = t.TypeVar( + "T_before_first_request", bound=ft.BeforeFirstRequestCallable +) +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: "Flask", + 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: t.Optional[str] = None, + view_func: t.Optional[t.Callable] = 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 + + _json_encoder: t.Union[t.Type[json.JSONEncoder], None] = None + _json_decoder: t.Union[t.Type[json.JSONDecoder], None] = None + + @property # type: ignore[override] + def json_encoder( # type: ignore[override] + self, + ) -> t.Union[t.Type[json.JSONEncoder], None]: + """Blueprint-local JSON encoder class to use. Set to ``None`` to use the app's. + + .. deprecated:: 2.2 + Will be removed in Flask 2.3. Customize + :attr:`json_provider_class` instead. + + .. versionadded:: 0.10 + """ + import warnings + + warnings.warn( + "'bp.json_encoder' is deprecated and will be removed in Flask 2.3." + " Customize 'app.json_provider_class' or 'app.json' instead.", + DeprecationWarning, + stacklevel=2, + ) + return self._json_encoder + + @json_encoder.setter + def json_encoder(self, value: t.Union[t.Type[json.JSONEncoder], None]) -> None: + import warnings + + warnings.warn( + "'bp.json_encoder' is deprecated and will be removed in Flask 2.3." + " Customize 'app.json_provider_class' or 'app.json' instead.", + DeprecationWarning, + stacklevel=2, + ) + self._json_encoder = value + + @property # type: ignore[override] + def json_decoder( # type: ignore[override] + self, + ) -> t.Union[t.Type[json.JSONDecoder], None]: + """Blueprint-local JSON decoder class to use. Set to ``None`` to use the app's. + + .. deprecated:: 2.2 + Will be removed in Flask 2.3. Customize + :attr:`json_provider_class` instead. + + .. versionadded:: 0.10 + """ + import warnings + + warnings.warn( + "'bp.json_decoder' is deprecated and will be removed in Flask 2.3." + " Customize 'app.json_provider_class' or 'app.json' instead.", + DeprecationWarning, + stacklevel=2, + ) + return self._json_decoder + + @json_decoder.setter + def json_decoder(self, value: t.Union[t.Type[json.JSONDecoder], None]) -> None: + import warnings + + warnings.warn( + "'bp.json_decoder' is deprecated and will be removed in Flask 2.3." + " Customize 'app.json_provider_class' or 'app.json' instead.", + DeprecationWarning, + stacklevel=2, + ) + self._json_decoder = value + + def __init__( + self, + name: str, + import_name: str, + static_folder: t.Optional[t.Union[str, os.PathLike]] = None, + static_url_path: t.Optional[str] = None, + template_folder: t.Optional[str] = None, + url_prefix: t.Optional[str] = None, + subdomain: t.Optional[str] = None, + url_defaults: t.Optional[dict] = None, + root_path: t.Optional[str] = None, + cli_group: t.Optional[str] = _sentinel, # type: ignore + ): + super().__init__( + import_name=import_name, + static_folder=static_folder, + static_url_path=static_url_path, + template_folder=template_folder, + root_path=root_path, + ) + + if "." 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: t.List[DeferredSetupFunction] = [] + + if url_defaults is None: + url_defaults = {} + + self.url_values_defaults = url_defaults + self.cli_group = cli_group + self._blueprints: t.List[t.Tuple["Blueprint", dict]] = [] + + def _check_setup_finished(self, f_name: str) -> None: + if self._got_registered_once: + import warnings + + warnings.warn( + f"The setup method '{f_name}' can no longer be called on" + f" the blueprint '{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.\n" + "This warning will become an exception in Flask 2.3.", + UserWarning, + stacklevel=3, + ) + + @setupmethod + def record(self, func: t.Callable) -> None: + """Registers a function that is called when the blueprint is + registered on the application. This function is called with the + state as argument as returned by the :meth:`make_setup_state` + method. + """ + self.deferred_functions.append(func) + + @setupmethod + def record_once(self, func: t.Callable) -> None: + """Works like :meth:`record` but wraps the function in another + function that will ensure the function is only called once. If the + blueprint is registered a second time on the application, the + function passed is not called. + """ + + def wrapper(state: BlueprintSetupState) -> None: + if state.first_registration: + func(state) + + self.record(update_wrapper(wrapper, func)) + + def make_setup_state( + self, app: "Flask", options: dict, first_registration: bool = False + ) -> BlueprintSetupState: + """Creates an instance of :meth:`~flask.blueprints.BlueprintSetupState` + object that is later passed to the register callback functions. + Subclasses can override this to return a subclass of the setup state. + """ + return BlueprintSetupState(self, app, options, first_registration) + + @setupmethod + def register_blueprint(self, blueprint: "Blueprint", **options: t.Any) -> None: + """Register a :class:`~flask.Blueprint` on this blueprint. Keyword + arguments passed to this method will override the defaults set + on the blueprint. + + .. versionchanged:: 2.0.1 + The ``name`` option can be used to change the (pre-dotted) + name the blueprint is registered with. This allows the same + blueprint to be registered multiple times with unique names + for ``url_for``. + + .. versionadded:: 2.0 + """ + if blueprint is self: + raise ValueError("Cannot register a blueprint on itself") + self._blueprints.append((blueprint, options)) + + def register(self, app: "Flask", options: dict) -> None: + """Called by :meth:`Flask.register_blueprint` to register all + views and callbacks registered on the blueprint with the + application. Creates a :class:`.BlueprintSetupState` and calls + each :meth:`record` callback with it. + + :param app: The application this blueprint is being registered + with. + :param options: Keyword arguments forwarded from + :meth:`~Flask.register_blueprint`. + + .. versionchanged:: 2.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``. + + .. versionchanged:: 2.0.1 + Registering the same blueprint with the same name multiple + times is deprecated and will become an error in Flask 2.1. + """ + name_prefix = options.get("name_prefix", "") + self_name = options.get("name", self.name) + name = f"{name_prefix}.{self_name}".lstrip(".") + + if name in app.blueprints: + bp_desc = "this" if app.blueprints[name] is self else "a different" + existing_at = f" '{name}'" if self_name != name else "" + + raise ValueError( + f"The name '{self_name}' is already registered for" + f" {bp_desc} blueprint{existing_at}. Use 'name=' to" + f" provide a unique name." + ) + + first_bp_registration = not any(bp is self for bp in app.blueprints.values()) + first_name_registration = name not in app.blueprints + + app.blueprints[name] = self + self._got_registered_once = True + state = self.make_setup_state(app, options, first_bp_registration) + + if self.has_static_folder: + state.add_url_rule( + f"{self.static_url_path}/", + view_func=self.send_static_file, + endpoint="static", + ) + + # Merge blueprint data into parent. + if first_bp_registration or first_name_registration: + + def extend(bp_dict, parent_dict): + for key, values in bp_dict.items(): + key = name if key is None else f"{name}.{key}" + parent_dict[key].extend(values) + + for key, value in self.error_handler_spec.items(): + key = name if key is None else f"{name}.{key}" + value = defaultdict( + dict, + { + code: { + exc_class: func for exc_class, func in code_values.items() + } + for code, code_values in value.items() + }, + ) + app.error_handler_spec[key] = value + + for endpoint, func in self.view_functions.items(): + app.view_functions[endpoint] = func + + extend(self.before_request_funcs, app.before_request_funcs) + extend(self.after_request_funcs, app.after_request_funcs) + extend( + self.teardown_request_funcs, + app.teardown_request_funcs, + ) + extend(self.url_default_functions, app.url_default_functions) + extend(self.url_value_preprocessors, app.url_value_preprocessors) + extend(self.template_context_processors, app.template_context_processors) + + for deferred in self.deferred_functions: + deferred(state) + + cli_resolved_group = options.get("cli_group", self.cli_group) + + if self.cli.commands: + if cli_resolved_group is None: + app.cli.commands.update(self.cli.commands) + elif cli_resolved_group is _sentinel: + self.cli.name = name + app.cli.add_command(self.cli) + else: + self.cli.name = cli_resolved_group + app.cli.add_command(self.cli) + + for blueprint, bp_options in self._blueprints: + bp_options = bp_options.copy() + bp_url_prefix = bp_options.get("url_prefix") + + if bp_url_prefix is None: + bp_url_prefix = blueprint.url_prefix + + if state.url_prefix is not None and bp_url_prefix is not None: + bp_options["url_prefix"] = ( + state.url_prefix.rstrip("/") + "/" + bp_url_prefix.lstrip("/") + ) + elif bp_url_prefix is not None: + bp_options["url_prefix"] = bp_url_prefix + elif state.url_prefix is not None: + bp_options["url_prefix"] = state.url_prefix + + bp_options["name_prefix"] = name + blueprint.register(app, bp_options) + + @setupmethod + def add_url_rule( + self, + rule: str, + endpoint: t.Optional[str] = None, + view_func: t.Optional[ft.RouteCallable] = None, + provide_automatic_options: t.Optional[bool] = None, + **options: t.Any, + ) -> None: + """Like :meth:`Flask.add_url_rule` but for a blueprint. The endpoint for + the :func:`url_for` function is prefixed with the name of the blueprint. + """ + if endpoint and "." in endpoint: + raise ValueError("'endpoint' may not contain a dot '.' character.") + + if view_func and hasattr(view_func, "__name__") and "." in view_func.__name__: + raise ValueError("'view_func' name may not contain a dot '.' character.") + + self.record( + lambda s: s.add_url_rule( + rule, + endpoint, + view_func, + provide_automatic_options=provide_automatic_options, + **options, + ) + ) + + @setupmethod + def app_template_filter( + self, name: t.Optional[str] = None + ) -> t.Callable[[T_template_filter], T_template_filter]: + """Register a custom template filter, available application wide. Like + :meth:`Flask.template_filter` but for a blueprint. + + :param name: the optional name of the filter, otherwise the + function name will be used. + """ + + def decorator(f: T_template_filter) -> T_template_filter: + self.add_app_template_filter(f, name=name) + return f + + return decorator + + @setupmethod + def add_app_template_filter( + self, f: ft.TemplateFilterCallable, name: t.Optional[str] = None + ) -> None: + """Register a custom template filter, available application wide. Like + :meth:`Flask.add_template_filter` but for a blueprint. Works exactly + like the :meth:`app_template_filter` decorator. + + :param name: the optional name of the filter, otherwise the + function name will be used. + """ + + def register_template(state: BlueprintSetupState) -> None: + state.app.jinja_env.filters[name or f.__name__] = f + + self.record_once(register_template) + + @setupmethod + def app_template_test( + self, name: t.Optional[str] = None + ) -> t.Callable[[T_template_test], T_template_test]: + """Register a custom template test, available application wide. Like + :meth:`Flask.template_test` but for a blueprint. + + .. versionadded:: 0.10 + + :param name: the optional name of the test, otherwise the + function name will be used. + """ + + def decorator(f: T_template_test) -> T_template_test: + self.add_app_template_test(f, name=name) + return f + + return decorator + + @setupmethod + def add_app_template_test( + self, f: ft.TemplateTestCallable, name: t.Optional[str] = None + ) -> None: + """Register a custom template test, available application wide. Like + :meth:`Flask.add_template_test` but for a blueprint. Works exactly + like the :meth:`app_template_test` decorator. + + .. versionadded:: 0.10 + + :param name: the optional name of the test, otherwise the + function name will be used. + """ + + def register_template(state: BlueprintSetupState) -> None: + state.app.jinja_env.tests[name or f.__name__] = f + + self.record_once(register_template) + + @setupmethod + def app_template_global( + self, name: t.Optional[str] = None + ) -> t.Callable[[T_template_global], T_template_global]: + """Register a custom template global, available application wide. Like + :meth:`Flask.template_global` but for a blueprint. + + .. versionadded:: 0.10 + + :param name: the optional name of the global, otherwise the + function name will be used. + """ + + def decorator(f: T_template_global) -> T_template_global: + self.add_app_template_global(f, name=name) + return f + + return decorator + + @setupmethod + def add_app_template_global( + self, f: ft.TemplateGlobalCallable, name: t.Optional[str] = None + ) -> None: + """Register a custom template global, available application wide. Like + :meth:`Flask.add_template_global` but for a blueprint. Works exactly + like the :meth:`app_template_global` decorator. + + .. versionadded:: 0.10 + + :param name: the optional name of the global, otherwise the + function name will be used. + """ + + def register_template(state: BlueprintSetupState) -> None: + state.app.jinja_env.globals[name or f.__name__] = f + + self.record_once(register_template) + + @setupmethod + def before_app_request(self, f: T_before_request) -> T_before_request: + """Like :meth:`Flask.before_request`. Such a function is executed + before each request, even if outside of a blueprint. + """ + self.record_once( + lambda s: s.app.before_request_funcs.setdefault(None, []).append(f) + ) + return f + + @setupmethod + def before_app_first_request( + self, f: T_before_first_request + ) -> T_before_first_request: + """Like :meth:`Flask.before_first_request`. Such a function is + executed before the first request to the application. + + .. deprecated:: 2.2 + Will be removed in Flask 2.3. Run setup code when creating + the application instead. + """ + import warnings + + warnings.warn( + "'before_app_first_request' is deprecated and will be" + " removed in Flask 2.3. Use 'record_once' instead to run" + " setup code when registering the blueprint.", + DeprecationWarning, + stacklevel=2, + ) + self.record_once(lambda s: s.app.before_first_request_funcs.append(f)) + return f + + @setupmethod + def after_app_request(self, f: T_after_request) -> T_after_request: + """Like :meth:`Flask.after_request` but for a blueprint. Such a function + is executed after each request, even if outside of the blueprint. + """ + 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:`Flask.teardown_request` but for a blueprint. Such a + function is executed when tearing down each request, even if outside of + the blueprint. + """ + 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:`Flask.context_processor` but for a blueprint. Such a + function is executed each request, even if outside of the blueprint. + """ + self.record_once( + lambda s: s.app.template_context_processors.setdefault(None, []).append(f) + ) + return f + + @setupmethod + def app_errorhandler( + self, code: t.Union[t.Type[Exception], int] + ) -> t.Callable[[T_error_handler], T_error_handler]: + """Like :meth:`Flask.errorhandler` but for a blueprint. This + handler is used for all requests, even if outside of the blueprint. + """ + + def decorator(f: T_error_handler) -> T_error_handler: + self.record_once(lambda s: s.app.errorhandler(code)(f)) + return f + + return decorator + + @setupmethod + def app_url_value_preprocessor( + self, f: T_url_value_preprocessor + ) -> T_url_value_preprocessor: + """Same as :meth:`url_value_preprocessor` but application wide.""" + 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: + """Same as :meth:`url_defaults` but application wide.""" + self.record_once( + lambda s: s.app.url_default_functions.setdefault(None, []).append(f) + ) + return f diff --git a/.vtodo/Lib/site-packages/flask/cli.py b/.vtodo/Lib/site-packages/flask/cli.py new file mode 100644 index 0000000..82fe819 --- /dev/null +++ b/.vtodo/Lib/site-packages/flask/cli.py @@ -0,0 +1,1051 @@ +from __future__ import annotations + +import ast +import inspect +import os +import platform +import re +import sys +import traceback +import typing as t +from functools import update_wrapper +from operator import attrgetter + +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: + from .app import Flask + + +class NoAppException(click.UsageError): + """Raised if an application cannot be found or loaded.""" + + +def find_best_app(module): + """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): + """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, app_name): + """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} + 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): + """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]) + + +def locate_app(module_name, app_name, raise_if_not_found=True): + 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: + 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 + + 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, param, value): + if not value or ctx.resilient_parsing: + return + + import werkzeug + from . import __version__ + + click.echo( + f"Python {platform.python_version()}\n" + f"Flask {__version__}\n" + f"Werkzeug {werkzeug.__version__}", + color=ctx.color, + ) + ctx.exit() + + +version_option = click.Option( + ["--version"], + help="Show the Flask version.", + expose_value=False, + callback=get_version, + is_flag=True, + is_eager=True, +) + + +class ScriptInfo: + """Helper object to deal with Flask applications. This is usually not + necessary to interface with as it's used internally in the dispatching + to click. In future versions of Flask this object will most likely play + a bigger role. Typically it's created automatically by the + :class:`FlaskGroup` but you can also manually create it and pass it + onwards as click object. + """ + + def __init__( + self, + app_import_path: str | None = None, + create_app: t.Callable[..., Flask] | None = None, + set_debug_flag: bool = True, + ) -> None: + #: Optionally the import path for the Flask application. + self.app_import_path = app_import_path + #: Optionally a function that is passed the script info to create + #: the instance of the application. + self.create_app = create_app + #: A dictionary with arbitrary data that can be associated with + #: this script info. + self.data: t.Dict[t.Any, t.Any] = {} + self.set_debug_flag = set_debug_flag + self._loaded_app: Flask | None = None + + def load_app(self) -> Flask: + """Loads the Flask app (if not yet loaded) and returns it. Calling + this multiple times will just result in the already loaded app to + be returned. + """ + if self._loaded_app is not None: + return self._loaded_app + + if self.create_app is not None: + app = self.create_app() + else: + if self.app_import_path: + path, name = ( + re.split(r":(?![\\/])", self.app_import_path, 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: + break + + if not app: + 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) + + +def with_appcontext(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, *args, **kwargs): + 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) + + +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(self, *args, **kwargs): + """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): + if wrap_for_ctx: + f = with_appcontext(f) + return click.Group.command(self, *args, **kwargs)(f) + + return decorator + + def group(self, *args, **kwargs): + """This works exactly like the method of the same name on a regular + :class:`click.Group` but it defaults the group class to + :class:`AppGroup`. + """ + kwargs.setdefault("cls", AppGroup) + return click.Group.group(self, *args, **kwargs) + + +def _set_app(ctx: click.Context, param: click.Option, value: str | None) -> str | None: + if value is None: + return None + + info = ctx.ensure_object(ScriptInfo) + info.app_import_path = value + return value + + +# This option is eager so the app will be available if --help is given. +# --help is also eager, so --app must be before it in the param list. +# no_args_is_help bypasses eager processing, so this option must be +# processed manually in that case to ensure FLASK_APP gets picked up. +_app_option = click.Option( + ["-A", "--app"], + metavar="IMPORT", + help=( + "The Flask application or factory function to load, in the form 'module:name'." + " Module can be a dotted import or file path. Name is not required if it is" + " 'app', 'application', 'create_app', or 'make_app', and can be 'name(args)' to" + " pass arguments." + ), + is_eager=True, + expose_value=False, + callback=_set_app, +) + + +def _set_debug(ctx: click.Context, param: click.Option, value: bool) -> bool | None: + # If the flag isn't provided, it will default to False. Don't use + # that, let debug be set by env in that case. + source = ctx.get_parameter_source(param.name) # type: ignore[arg-type] + + if source is not None and source in ( + ParameterSource.DEFAULT, + ParameterSource.DEFAULT_MAP, + ): + return None + + # Set with env var instead of ScriptInfo.load so that it can be + # accessed early during a factory function. + os.environ["FLASK_DEBUG"] = "1" if value else "0" + return value + + +_debug_option = click.Option( + ["--debug/--no-debug"], + help="Set debug mode.", + expose_value=False, + callback=_set_debug, +) + + +def _env_file_callback( + ctx: click.Context, param: click.Option, value: str | None +) -> str | None: + if value is None: + return None + + import importlib + + try: + importlib.import_module("dotenv") + except ImportError: + raise click.BadParameter( + "python-dotenv must be installed to load an env file.", + ctx=ctx, + param=param, + ) from None + + # Don't check FLASK_SKIP_DOTENV, that only disables automatically + # loading .env and .flaskenv files. + load_dotenv(value) + return value + + +# This option is eager so env vars are loaded as early as possible to be +# used by other options. +_env_file_option = click.Option( + ["-e", "--env-file"], + type=click.Path(exists=True, dir_okay=False), + help="Load environment variables from this file. python-dotenv must be installed.", + is_eager=True, + expose_value=False, + callback=_env_file_callback, +) + + +class FlaskGroup(AppGroup): + """Special subclass of the :class:`AppGroup` group that supports + loading more commands from the configured Flask app. Normally a + developer does not have to interface with this class but there are + some very advanced use cases for which it makes sense to create an + instance of this. see :ref:`custom-scripts`. + + :param add_default_commands: if this is True then the default run and + shell commands will be added. + :param add_version_option: adds the ``--version`` option. + :param create_app: an optional callback that is passed the script info and + returns the loaded app. + :param load_dotenv: Load the nearest :file:`.env` and :file:`.flaskenv` + files to set environment variables. Will also change the working + directory to the directory containing the first file found. + :param set_debug_flag: Set the app's debug flag. + + .. versionchanged:: 2.2 + Added the ``-A/--app``, ``--debug/--no-debug``, ``-e/--env-file`` options. + + .. versionchanged:: 2.2 + An app context is pushed when running ``app.cli`` commands, so + ``@with_appcontext`` is no longer required for those commands. + + .. versionchanged:: 1.0 + If installed, python-dotenv will be used to load environment variables + from :file:`.env` and :file:`.flaskenv` files. + """ + + def __init__( + self, + add_default_commands: bool = True, + create_app: t.Callable[..., Flask] | None = None, + add_version_option: bool = True, + load_dotenv: bool = True, + set_debug_flag: bool = True, + **extra: t.Any, + ) -> None: + params = list(extra.pop("params", None) or ()) + # Processing is done with option callbacks instead of a group + # callback. This allows users to make a custom group callback + # without losing the behavior. --env-file must come first so + # that it is eagerly evaluated before --app. + params.extend((_env_file_option, _app_option, _debug_option)) + + if add_version_option: + params.append(version_option) + + if "context_settings" not in extra: + extra["context_settings"] = {} + + extra["context_settings"].setdefault("auto_envvar_prefix", "FLASK") + + super().__init__(params=params, **extra) + + self.create_app = create_app + self.load_dotenv = load_dotenv + self.set_debug_flag = set_debug_flag + + if add_default_commands: + self.add_command(run_command) + self.add_command(shell_command) + self.add_command(routes_command) + + self._loaded_plugin_commands = False + + def _load_plugin_commands(self): + if self._loaded_plugin_commands: + return + + if sys.version_info >= (3, 10): + from importlib import metadata + else: + # Use a backport on Python < 3.10. We technically have + # importlib.metadata on 3.8+, but the API changed in 3.10, + # so use the backport for consistency. + import importlib_metadata as metadata + + for ep in metadata.entry_points(group="flask.commands"): + self.add_command(ep.load(), ep.name) + + self._loaded_plugin_commands = True + + def get_command(self, ctx, name): + 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): + self._load_plugin_commands() + # Start with the built-in and plugin commands. + rv = set(super().list_commands(ctx)) + info = ctx.ensure_object(ScriptInfo) + + # Add commands provided by the app, showing an error and + # continuing if the app couldn't be loaded. + try: + rv.update(info.load_app().cli.list_commands(ctx)) + except NoAppException as e: + # When an app couldn't be loaded, show the error message + # without the traceback. + click.secho(f"Error: {e.format_message()}\n", err=True, fg="red") + except Exception: + # When any other errors occurred during loading, show the + # full traceback. + click.secho(f"{traceback.format_exc()}\n", err=True, fg="red") + + return sorted(rv) + + def make_context( + self, + info_name: str | None, + args: list[str], + parent: click.Context | None = None, + **extra: t.Any, + ) -> click.Context: + # Set a flag to tell app.run to become a no-op. If app.run was + # not in a __name__ == __main__ guard, it would start the server + # when importing, blocking whatever command is being called. + os.environ["FLASK_RUN_FROM_CLI"] = "true" + + # Attempt to load .env and .flask env files. The --env-file + # option can cause another file to be loaded. + if get_load_dotenv(self.load_dotenv): + load_dotenv() + + if "obj" not in extra and "obj" not in self.context_settings: + extra["obj"] = ScriptInfo( + create_app=self.create_app, set_debug_flag=self.set_debug_flag + ) + + return super().make_context(info_name, args, parent=parent, **extra) + + def parse_args(self, ctx: click.Context, args: list[str]) -> list[str]: + if not args and self.no_args_is_help: + # Attempt to load --env-file and --app early in case they + # were given as env vars. Otherwise no_args_is_help will not + # see commands from app.cli. + _env_file_option.handle_parse_result(ctx, {}, []) + _app_option.handle_parse_result(ctx, {}, []) + + return super().parse_args(ctx, args) + + +def _path_is_ancestor(path, other): + """Take ``other`` and remove the length of ``path`` from it. Then join it + to ``path``. If it is the original value, ``path`` is an ancestor of + ``other``.""" + return os.path.join(path, other[len(path) :].lstrip(os.sep)) == other + + +def load_dotenv(path: str | os.PathLike | None = None) -> bool: + """Load "dotenv" files in order of precedence to set environment variables. + + If an env var is already set it is not overwritten, so earlier files in the + list are preferred over later files. + + This is a no-op if `python-dotenv`_ is not installed. + + .. _python-dotenv: https://github.com/theskumar/python-dotenv#readme + + :param path: Load the file at this location instead of searching. + :return: ``True`` if a file was loaded. + + .. versionchanged:: 2.0 + The current directory is not changed to the location of the + loaded file. + + .. versionchanged:: 2.0 + When loading the env files, set the default encoding to UTF-8. + + .. versionchanged:: 1.1.0 + Returns ``False`` when python-dotenv is not installed, or when + the given path isn't a file. + + .. versionadded:: 1.0 + """ + try: + import dotenv + except ImportError: + if path or os.path.isfile(".env") or os.path.isfile(".flaskenv"): + click.secho( + " * Tip: There are .env or .flaskenv files present." + ' Do "pip install python-dotenv" to use them.', + fg="yellow", + err=True, + ) + + return False + + # Always return after attempting to load a given path, don't load + # the default files. + if path is not None: + if os.path.isfile(path): + return dotenv.load_dotenv(path, encoding="utf-8") + + return False + + loaded = False + + for name in (".env", ".flaskenv"): + path = dotenv.find_dotenv(name, usecwd=True) + + if not path: + continue + + dotenv.load_dotenv(path, encoding="utf-8") + loaded = True + + return loaded # True if at least one file was located and loaded. + + +def show_server_banner(debug, app_import_path): + """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): + self.path_type = click.Path(exists=True, dir_okay=False, resolve_path=True) + + def convert(self, value, param, ctx): + 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, param, value): + """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, param, ctx): + items = self.split_envvar_value(value) + 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, + host, + port, + reload, + debugger, + with_threads, + cert, + extra_files, + exclude_patterns, +): + """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 = info.load_app() + except Exception as e: + if is_running_from_reloader(): + # When reloading, print out the error immediately, but raise + # it later so the debugger or server can handle it. + traceback.print_exc() + err = e + + def app(environ, start_response): + 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, + ) + + +@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 = {} + + # 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", "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")) + + if sort in ("endpoint", "rule"): + rules = sorted(rules, key=attrgetter(sort)) + elif sort == "methods": + rules = sorted(rules, key=lambda rule: sorted(rule.methods)) # type: ignore + + rule_methods = [ + ", ".join(sorted(rule.methods - ignored_methods)) # type: ignore + for rule in rules + ] + + headers = ("Endpoint", "Methods", "Rule") + widths = ( + max(len(rule.endpoint) for rule in rules), + max(len(methods) for methods in rule_methods), + max(len(rule.rule) for rule in rules), + ) + widths = [max(len(h), w) for h, w in zip(headers, widths)] + row = "{{0:<{0}}} {{1:<{1}}} {{2:<{2}}}".format(*widths) + + click.echo(row.format(*headers).strip()) + click.echo(row.format(*("-" * width for width in widths))) + + for rule, methods in zip(rules, rule_methods): + click.echo(row.format(rule.endpoint, methods, rule.rule).rstrip()) + + +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/.vtodo/Lib/site-packages/flask/config.py b/.vtodo/Lib/site-packages/flask/config.py new file mode 100644 index 0000000..7b6a137 --- /dev/null +++ b/.vtodo/Lib/site-packages/flask/config.py @@ -0,0 +1,337 @@ +import errno +import json +import os +import types +import typing as t + +from werkzeug.utils import import_string + + +class ConfigAttribute: + """Makes an attribute forward to the config""" + + def __init__(self, name: str, get_converter: t.Optional[t.Callable] = None) -> None: + self.__name__ = name + self.get_converter = get_converter + + def __get__(self, obj: t.Any, owner: t.Any = None) -> t.Any: + if obj is None: + return self + rv = obj.config[self.__name__] + if self.get_converter is not None: + rv = self.get_converter(rv) + return rv + + def __set__(self, obj: t.Any, value: t.Any) -> None: + obj.config[self.__name__] = value + + +class Config(dict): + """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, defaults: t.Optional[dict] = None) -> None: + super().__init__(defaults or {}) + self.root_path = root_path + + def from_envvar(self, variable_name: str, silent: bool = False) -> bool: + """Loads a configuration from an environment variable pointing to + a configuration file. This is basically just a shortcut with nicer + error messages for this line of code:: + + app.config.from_pyfile(os.environ['YOURAPPLICATION_SETTINGS']) + + :param variable_name: name of the environment variable + :param silent: set to ``True`` if you want silent failure for missing + files. + :return: ``True`` if the file was loaded successfully. + """ + rv = os.environ.get(variable_name) + if not rv: + if silent: + return False + raise RuntimeError( + f"The environment variable {variable_name!r} is not set" + " and as such configuration could not be loaded. Set" + " this variable and make it point to a configuration" + " file" + ) + return self.from_pyfile(rv, silent=silent) + + def from_prefixed_env( + self, prefix: str = "FLASK", *, loads: t.Callable[[str], t.Any] = json.loads + ) -> bool: + """Load any environment variables that start with ``FLASK_``, + dropping the prefix from the env key for the config key. Values + are passed through a loading function to attempt to convert them + to more specific types than strings. + + Keys are loaded in :func:`sorted` order. + + The default loading function attempts to parse values as any + valid JSON type, including dicts and lists. + + Specific items in nested dicts can be set by separating the + keys with double underscores (``__``). If an intermediate key + doesn't exist, it will be initialized to an empty dict. + + :param prefix: Load env vars that start with this prefix, + separated with an underscore (``_``). + :param loads: Pass each string value to this function and use + the returned value as the config value. If any error is + raised it is ignored and the value remains a string. The + default is :func:`json.loads`. + + .. versionadded:: 2.1 + """ + prefix = f"{prefix}_" + len_prefix = len(prefix) + + for key in sorted(os.environ): + if not key.startswith(prefix): + continue + + value = os.environ[key] + + try: + value = loads(value) + except Exception: + # Keep the value as a string if loading failed. + pass + + # Change to key.removeprefix(prefix) on Python >= 3.9. + key = key[len_prefix:] + + if "__" not in key: + # A non-nested key, set directly. + self[key] = value + continue + + # Traverse nested dictionaries with keys separated by "__". + current = self + *parts, tail = key.split("__") + + for part in parts: + # If an intermediate dict does not exist, create it. + if part not in current: + current[part] = {} + + current = current[part] + + current[tail] = value + + return True + + def from_pyfile(self, filename: str, 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: t.Union[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, + load: t.Callable[[t.IO[t.Any]], t.Mapping], + silent: bool = False, + ) -> 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 toml + app.config.from_file("config.toml", load=toml.load) + + :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. + :return: ``True`` if the file was loaded successfully. + + .. versionadded:: 2.0 + """ + filename = os.path.join(self.root_path, filename) + + try: + with open(filename) 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.Optional[t.Mapping[str, t.Any]] = 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: t.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 + ) -> t.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/.vtodo/Lib/site-packages/flask/ctx.py b/.vtodo/Lib/site-packages/flask/ctx.py new file mode 100644 index 0000000..ca28449 --- /dev/null +++ b/.vtodo/Lib/site-packages/flask/ctx.py @@ -0,0 +1,438 @@ +import contextvars +import sys +import typing as t +from functools import update_wrapper +from types import TracebackType + +from werkzeug.exceptions import HTTPException + +from . import typing as ft +from .globals import _cv_app +from .globals import _cv_request +from .signals import appcontext_popped +from .signals import appcontext_pushed + +if t.TYPE_CHECKING: # pragma: no cover + from .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.Optional[t.Any] = 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) -> ft.AfterRequestCallable: + """Executes a function after this request. This is useful to modify + response objects. The function is passed the response object and has + to return the same or a new one. + + Example:: + + @app.route('/') + def index(): + @after_this_request + def add_header(response): + response.headers['X-Foo'] = 'Parachute' + return response + return 'Hello World!' + + This is more useful if a function other than the view function wants to + modify a response. For instance think of a decorator that wants to add + some headers without converting the return value into a response object. + + .. versionadded:: 0.9 + """ + ctx = _cv_request.get(None) + + if ctx is None: + raise RuntimeError( + "'after_this_request' can only be used when a request" + " context is active, such as in a view function." + ) + + ctx._after_request_functions.append(f) + return f + + +def copy_current_request_context(f: t.Callable) -> t.Callable: + """A helper function that decorates a function to retain the current + request context. This is useful when working with greenlets. The moment + the function is decorated a copy of the request context is created and + then pushed when the function is called. The current session is also + included in the copied request context. + + Example:: + + import gevent + from flask import copy_current_request_context + + @app.route('/') + def index(): + @copy_current_request_context + def do_some_work(): + # do some work here, it can access flask.request or + # flask.session like you would otherwise in the view function. + ... + gevent.spawn(do_some_work) + return 'Regular response' + + .. versionadded:: 0.10 + """ + ctx = _cv_request.get(None) + + if ctx is None: + raise RuntimeError( + "'copy_current_request_context' can only be used when a" + " request context is active, such as in a view function." + ) + + ctx = ctx.copy() + + def wrapper(*args, **kwargs): + with ctx: + return ctx.app.ensure_sync(f)(*args, **kwargs) + + return update_wrapper(wrapper, f) + + +def has_request_context() -> bool: + """If you have code that wants to test if a request context is there or + not this function can be used. For instance, you may want to take advantage + of request information if the request object is available, but fail + silently if it is unavailable. + + :: + + class User(db.Model): + + def __init__(self, username, remote_addr=None): + self.username = username + if remote_addr is None and has_request_context(): + remote_addr = request.remote_addr + self.remote_addr = remote_addr + + Alternatively you can also just test any of the context bound objects + (such as :class:`request` or :class:`g`) for truthness:: + + class User(db.Model): + + def __init__(self, username, remote_addr=None): + self.username = username + if remote_addr is None and request: + remote_addr = request.remote_addr + self.remote_addr = remote_addr + + .. versionadded:: 0.7 + """ + return _cv_request.get(None) is not None + + +def has_app_context() -> bool: + """Works like :func:`has_request_context` but for the application + context. You can also just do a boolean check on the + :data:`current_app` object instead. + + .. versionadded:: 0.9 + """ + return _cv_app.get(None) is not None + + +class AppContext: + """The app context contains application-specific information. An app + context is created and pushed at the beginning of each request if + one is not already active. An app context is also pushed when + running CLI commands. + """ + + def __init__(self, app: "Flask") -> None: + self.app = app + self.url_adapter = app.create_url_adapter(None) + self.g: _AppCtxGlobals = app.app_ctx_globals_class() + self._cv_tokens: t.List[contextvars.Token] = [] + + def push(self) -> None: + """Binds the app context to the current context.""" + self._cv_tokens.append(_cv_app.set(self)) + appcontext_pushed.send(self.app) + + def pop(self, exc: t.Optional[BaseException] = _sentinel) -> None: # type: ignore + """Pops the app context.""" + try: + if len(self._cv_tokens) == 1: + if exc is _sentinel: + exc = sys.exc_info()[1] + self.app.do_teardown_appcontext(exc) + finally: + ctx = _cv_app.get() + _cv_app.reset(self._cv_tokens.pop()) + + if ctx is not self: + raise AssertionError( + f"Popped wrong app context. ({ctx!r} instead of {self!r})" + ) + + appcontext_popped.send(self.app) + + def __enter__(self) -> "AppContext": + self.push() + return self + + def __exit__( + self, + exc_type: t.Optional[type], + exc_value: t.Optional[BaseException], + tb: t.Optional[TracebackType], + ) -> None: + self.pop(exc_value) + + +class RequestContext: + """The request context contains per-request information. The Flask + app creates and pushes it at the beginning of the request, then pops + it at the end of the request. It will create the URL adapter and + request object for the WSGI environment provided. + + Do not attempt to use this class directly, instead use + :meth:`~flask.Flask.test_request_context` and + :meth:`~flask.Flask.request_context` to create this object. + + When the request context is popped, it will evaluate all the + functions registered on the application for teardown execution + (:meth:`~flask.Flask.teardown_request`). + + The request context is automatically popped at the end of the + request. When using the interactive debugger, the context will be + restored so ``request`` is still accessible. Similarly, the test + client can preserve the context after the request ends. However, + teardown functions may already have closed some resources such as + database connections. + """ + + def __init__( + self, + app: "Flask", + environ: dict, + request: t.Optional["Request"] = None, + session: t.Optional["SessionMixin"] = None, + ) -> None: + self.app = app + if request is None: + request = app.request_class(environ) + request.json_module = app.json # type: ignore[misc] + self.request: Request = request + self.url_adapter = None + try: + self.url_adapter = app.create_url_adapter(self.request) + except HTTPException as e: + self.request.routing_exception = e + self.flashes: t.Optional[t.List[t.Tuple[str, str]]] = None + self.session: t.Optional["SessionMixin"] = session + # Functions that should be executed after the request on the response + # object. These will be called before the regular "after_request" + # functions. + self._after_request_functions: t.List[ft.AfterRequestCallable] = [] + + self._cv_tokens: t.List[t.Tuple[contextvars.Token, t.Optional[AppContext]]] = [] + + def copy(self) -> "RequestContext": + """Creates a copy of this request context with the same request object. + This can be used to move a request context to a different greenlet. + Because the actual request object is the same this cannot be used to + move a request context to a different thread unless access to the + request object is locked. + + .. versionadded:: 0.10 + + .. versionchanged:: 1.1 + The current session object is used instead of reloading the original + data. This prevents `flask.session` pointing to an out-of-date object. + """ + return self.__class__( + self.app, + environ=self.request.environ, + request=self.request, + session=self.session, + ) + + def match_request(self) -> None: + """Can be overridden by a subclass to hook into the matching + of the request. + """ + try: + result = self.url_adapter.match(return_rule=True) # type: ignore + self.request.url_rule, self.request.view_args = result # type: ignore + except HTTPException as e: + self.request.routing_exception = e + + def push(self) -> None: + # Before we push the request context we have to ensure that there + # is an application context. + app_ctx = _cv_app.get(None) + + if app_ctx is None or app_ctx.app is not self.app: + app_ctx = self.app.app_context() + app_ctx.push() + else: + app_ctx = None + + self._cv_tokens.append((_cv_request.set(self), app_ctx)) + + # Open the session at the moment that the request context is available. + # This allows a custom open_session method to use the request context. + # Only open a new session if this is the first time the request was + # pushed, otherwise stream_with_context loses the session. + if self.session is None: + session_interface = self.app.session_interface + self.session = session_interface.open_session(self.app, self.request) + + if self.session is None: + self.session = session_interface.make_null_session(self.app) + + # Match the request URL after loading the session, so that the + # session is available in custom URL converters. + if self.url_adapter is not None: + self.match_request() + + def pop(self, exc: t.Optional[BaseException] = _sentinel) -> None: # type: ignore + """Pops the request context and unbinds it by doing that. This will + also trigger the execution of functions registered by the + :meth:`~flask.Flask.teardown_request` decorator. + + .. versionchanged:: 0.9 + Added the `exc` argument. + """ + clear_request = len(self._cv_tokens) == 1 + + try: + if clear_request: + if exc is _sentinel: + exc = sys.exc_info()[1] + self.app.do_teardown_request(exc) + + request_close = getattr(self.request, "close", None) + if request_close is not None: + request_close() + finally: + ctx = _cv_request.get() + token, app_ctx = self._cv_tokens.pop() + _cv_request.reset(token) + + # get rid of circular dependencies at the end of the request + # so that we don't require the GC to be active. + if clear_request: + ctx.request.environ["werkzeug.request"] = None + + if app_ctx is not None: + app_ctx.pop(exc) + + if ctx is not self: + raise AssertionError( + f"Popped wrong request context. ({ctx!r} instead of {self!r})" + ) + + def __enter__(self) -> "RequestContext": + self.push() + return self + + def __exit__( + self, + exc_type: t.Optional[type], + exc_value: t.Optional[BaseException], + tb: t.Optional[TracebackType], + ) -> None: + self.pop(exc_value) + + def __repr__(self) -> str: + return ( + f"<{type(self).__name__} {self.request.url!r}" + f" [{self.request.method}] of {self.app.name}>" + ) diff --git a/.vtodo/Lib/site-packages/flask/debughelpers.py b/.vtodo/Lib/site-packages/flask/debughelpers.py new file mode 100644 index 0000000..b063989 --- /dev/null +++ b/.vtodo/Lib/site-packages/flask/debughelpers.py @@ -0,0 +1,158 @@ +import typing as t + +from .app import Flask +from .blueprints import Blueprint +from .globals import request_ctx + + +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, key): + 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): + 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): + exc = request.routing_exception + 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): + """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): + def __getitem__(self, key): + 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) -> t.Generator: + 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: Flask, template, attempts) -> None: + """This should help developers understand what failed""" + info = [f"Locating template {template!r}:"] + total_found = 0 + blueprint = None + if request_ctx and request_ctx.request.blueprint is not None: + blueprint = request_ctx.request.blueprint + + for idx, (loader, srcobj, triple) in enumerate(attempts): + if isinstance(srcobj, Flask): + 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/.vtodo/Lib/site-packages/flask/globals.py b/.vtodo/Lib/site-packages/flask/globals.py new file mode 100644 index 0000000..b230ef7 --- /dev/null +++ b/.vtodo/Lib/site-packages/flask/globals.py @@ -0,0 +1,107 @@ +import typing as t +from contextvars import ContextVar + +from werkzeug.local import LocalProxy + +if t.TYPE_CHECKING: # pragma: no cover + from .app import Flask + from .ctx import _AppCtxGlobals + from .ctx import AppContext + from .ctx import RequestContext + from .sessions import SessionMixin + from .wrappers import Request + + +class _FakeStack: + def __init__(self, name: str, cv: ContextVar[t.Any]) -> None: + self.name = name + self.cv = cv + + def _warn(self): + import warnings + + warnings.warn( + f"'_{self.name}_ctx_stack' is deprecated and will be" + " removed in Flask 2.3. Use 'g' to store data, or" + f" '{self.name}_ctx' to access the current context.", + DeprecationWarning, + stacklevel=3, + ) + + def push(self, obj: t.Any) -> None: + self._warn() + self.cv.set(obj) + + def pop(self) -> t.Any: + self._warn() + ctx = self.cv.get(None) + self.cv.set(None) + return ctx + + @property + def top(self) -> t.Optional[t.Any]: + self._warn() + return self.cv.get(None) + + +_no_app_msg = """\ +Working outside of application context. + +This typically means that you attempted to use functionality that needed +the current application. To solve this, set up an application context +with app.app_context(). See the documentation for more information.\ +""" +_cv_app: ContextVar["AppContext"] = ContextVar("flask.app_ctx") +__app_ctx_stack = _FakeStack("app", _cv_app) +app_ctx: "AppContext" = LocalProxy( # type: ignore[assignment] + _cv_app, unbound_message=_no_app_msg +) +current_app: "Flask" = LocalProxy( # type: ignore[assignment] + _cv_app, "app", unbound_message=_no_app_msg +) +g: "_AppCtxGlobals" = LocalProxy( # type: ignore[assignment] + _cv_app, "g", unbound_message=_no_app_msg +) + +_no_req_msg = """\ +Working outside of request context. + +This typically means that you attempted to use functionality that needed +an active HTTP request. Consult the documentation on testing for +information about how to avoid this problem.\ +""" +_cv_request: ContextVar["RequestContext"] = ContextVar("flask.request_ctx") +__request_ctx_stack = _FakeStack("request", _cv_request) +request_ctx: "RequestContext" = LocalProxy( # type: ignore[assignment] + _cv_request, unbound_message=_no_req_msg +) +request: "Request" = LocalProxy( # type: ignore[assignment] + _cv_request, "request", unbound_message=_no_req_msg +) +session: "SessionMixin" = LocalProxy( # type: ignore[assignment] + _cv_request, "session", unbound_message=_no_req_msg +) + + +def __getattr__(name: str) -> t.Any: + if name == "_app_ctx_stack": + import warnings + + warnings.warn( + "'_app_ctx_stack' is deprecated and will be remoevd in Flask 2.3.", + DeprecationWarning, + stacklevel=2, + ) + return __app_ctx_stack + + if name == "_request_ctx_stack": + import warnings + + warnings.warn( + "'_request_ctx_stack' is deprecated and will be remoevd in Flask 2.3.", + DeprecationWarning, + stacklevel=2, + ) + return __request_ctx_stack + + raise AttributeError(name) diff --git a/.vtodo/Lib/site-packages/flask/helpers.py b/.vtodo/Lib/site-packages/flask/helpers.py new file mode 100644 index 0000000..15990d0 --- /dev/null +++ b/.vtodo/Lib/site-packages/flask/helpers.py @@ -0,0 +1,705 @@ +import os +import pkgutil +import socket +import sys +import typing as t +from datetime import datetime +from functools import lru_cache +from functools import update_wrapper +from threading import RLock + +import werkzeug.utils +from werkzeug.exceptions import abort as _wz_abort +from werkzeug.utils import redirect as _wz_redirect + +from .globals import _cv_request +from .globals import current_app +from .globals import request +from .globals import request_ctx +from .globals import session +from .signals import message_flashed + +if t.TYPE_CHECKING: # pragma: no cover + from werkzeug.wrappers import Response as BaseResponse + from .wrappers import Response + import typing_extensions as te + + +def get_env() -> str: + """Get the environment the app is running in, indicated by the + :envvar:`FLASK_ENV` environment variable. The default is + ``'production'``. + + .. deprecated:: 2.2 + Will be removed in Flask 2.3. + """ + import warnings + + warnings.warn( + "'FLASK_ENV' and 'get_env' are deprecated and will be removed" + " in Flask 2.3. Use 'FLASK_DEBUG' instead.", + DeprecationWarning, + stacklevel=2, + ) + return os.environ.get("FLASK_ENV") or "production" + + +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") + + if not val: + env = os.environ.get("FLASK_ENV") + + if env is not None: + print( + "'FLASK_ENV' is deprecated and will not be used in" + " Flask 2.3. Use 'FLASK_DEBUG' instead.", + file=sys.stderr, + ) + return env == "development" + + return False + + return val.lower() not in {"0", "false", "no"} + + +def get_load_dotenv(default: bool = True) -> bool: + """Get whether the user has disabled loading default dotenv files by + setting :envvar:`FLASK_SKIP_DOTENV`. The default is ``True``, load + the files. + + :param default: What to return if the env var isn't set. + """ + val = os.environ.get("FLASK_SKIP_DOTENV") + + if not val: + return default + + return val.lower() in ("0", "false", "no") + + +def stream_with_context( + generator_or_function: t.Union[ + t.Iterator[t.AnyStr], t.Callable[..., t.Iterator[t.AnyStr]] + ] +) -> t.Iterator[t.AnyStr]: + """Request contexts disappear when the response is started on the server. + This is done for efficiency reasons and to make it less likely to encounter + memory leaks with badly written WSGI middlewares. The downside is that if + you are using streamed responses, the generator cannot access request bound + information any more. + + This function however can help you keep the context around for longer:: + + from flask import stream_with_context, request, Response + + @app.route('/stream') + def streamed_response(): + @stream_with_context + def generate(): + yield 'Hello ' + yield request.args['name'] + yield '!' + return Response(generate()) + + Alternatively it can also be used around a specific generator:: + + from flask import stream_with_context, request, Response + + @app.route('/stream') + def streamed_response(): + def generate(): + yield 'Hello ' + yield request.args['name'] + yield '!' + return Response(stream_with_context(generate())) + + .. versionadded:: 0.9 + """ + try: + gen = iter(generator_or_function) # type: ignore + except TypeError: + + def decorator(*args: t.Any, **kwargs: t.Any) -> t.Any: + gen = generator_or_function(*args, **kwargs) # type: ignore + return stream_with_context(gen) + + return update_wrapper(decorator, generator_or_function) # type: ignore + + def generator() -> t.Generator: + ctx = _cv_request.get(None) + if ctx is None: + raise RuntimeError( + "'stream_with_context' can only be used when a request" + " context is active, such as in a view function." + ) + with ctx: + # Dummy sentinel. Has to be inside the context block or we're + # not actually keeping the context around. + yield None + + # The try/finally is here so that if someone passes a WSGI level + # iterator in we're still running the cleanup logic. Generators + # don't need that because they are closed on their destruction + # automatically. + try: + yield from gen + finally: + if hasattr(gen, "close"): + gen.close() # type: ignore + + # The trick is to start the generator. Then the code execution runs until + # the first dummy None is yielded at which point the context was already + # pushed. This item is discarded. Then when the iteration continues the + # real generator is executed. + wrapped_g = generator() + next(wrapped_g) + return wrapped_g + + +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) # type: ignore + + +def url_for( + endpoint: str, + *, + _anchor: t.Optional[str] = None, + _method: t.Optional[str] = None, + _scheme: t.Optional[str] = None, + _external: t.Optional[bool] = 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: t.Optional[t.Type["BaseResponse"]] = None +) -> "BaseResponse": + """Create a redirect response object. + + If :data:`~flask.current_app` is available, it will use its + :meth:`~flask.Flask.redirect` method, otherwise it will use + :func:`werkzeug.utils.redirect`. + + :param location: The URL to redirect to. + :param code: The status code for the redirect. + :param Response: The response class to use. Not used when + ``current_app`` is active, which uses ``app.response_class``. + + .. versionadded:: 2.2 + Calls ``current_app.redirect`` if available instead of always + using Werkzeug's default ``redirect``. + """ + if current_app: + return current_app.redirect(location, code=code) + + return _wz_redirect(location, code=code, Response=Response) + + +def abort( # type: ignore[misc] + code: t.Union[int, "BaseResponse"], *args: t.Any, **kwargs: t.Any +) -> "te.NoReturn": + """Raise an :exc:`~werkzeug.exceptions.HTTPException` for the given + status code. + + If :data:`~flask.current_app` is available, it will call its + :attr:`~flask.Flask.aborter` object, otherwise it will use + :func:`werkzeug.exceptions.abort`. + + :param code: The status code for the exception, which must be + registered in ``app.aborter``. + :param args: Passed to the exception. + :param kwargs: Passed to the exception. + + .. versionadded:: 2.2 + Calls ``current_app.aborter`` if available instead of always + using Werkzeug's default ``abort``. + """ + if current_app: + current_app.aborter(code, *args, **kwargs) + + _wz_abort(code, *args, **kwargs) + + +def get_template_attribute(template_name: str, attribute: str) -> t.Any: + """Loads a macro (or variable) a template exports. This can be used to + invoke a macro from within Python code. If you for example have a + template named :file:`_cider.html` with the following contents: + + .. sourcecode:: html+jinja + + {% macro hello(name) %}Hello {{ name }}!{% endmacro %} + + You can access this from Python code like this:: + + hello = get_template_attribute('_cider.html', 'hello') + return hello('World') + + .. versionadded:: 0.2 + + :param template_name: the name of the template + :param attribute: the name of the variable of macro to access + """ + return getattr(current_app.jinja_env.get_template(template_name).module, attribute) + + +def flash(message: str, category: str = "message") -> None: + """Flashes a message to the next request. In order to remove the + flashed message from the session and to display it to the user, + the template has to call :func:`get_flashed_messages`. + + .. versionchanged:: 0.3 + `category` parameter added. + + :param message: the message to be flashed. + :param category: the category for the message. The following values + are recommended: ``'message'`` for any kind of message, + ``'error'`` for errors, ``'info'`` for information + messages and ``'warning'`` for warnings. However any + kind of string can be used as category. + """ + # Original implementation: + # + # session.setdefault('_flashes', []).append((category, message)) + # + # This assumed that changes made to mutable structures in the session are + # always in sync with the session object, which is not true for session + # implementations that use external storage for keeping their keys/values. + flashes = session.get("_flashes", []) + flashes.append((category, message)) + session["_flashes"] = flashes + message_flashed.send( + current_app._get_current_object(), # type: ignore + message=message, + category=category, + ) + + +def get_flashed_messages( + with_categories: bool = False, category_filter: t.Iterable[str] = () +) -> t.Union[t.List[str], t.List[t.Tuple[str, str]]]: + """Pulls all flashed messages from the session and returns them. + Further calls in the same request to the function will return + the same messages. By default just the messages are returned, + but when `with_categories` is set to ``True``, the return value will + be a list of tuples in the form ``(category, message)`` instead. + + Filter the flashed messages to one or more categories by providing those + categories in `category_filter`. This allows rendering categories in + separate html blocks. The `with_categories` and `category_filter` + arguments are distinct: + + * `with_categories` controls whether categories are returned with message + text (``True`` gives a tuple, where ``False`` gives just the message text). + * `category_filter` filters the messages down to only those matching the + provided categories. + + See :doc:`/patterns/flashing` for examples. + + .. versionchanged:: 0.3 + `with_categories` parameter added. + + .. versionchanged:: 0.9 + `category_filter` parameter added. + + :param with_categories: set to ``True`` to also receive categories. + :param category_filter: filter of categories to limit return values. Only + categories in the list will be returned. + """ + flashes = request_ctx.flashes + if flashes is None: + flashes = session.pop("_flashes") if "_flashes" in session else [] + request_ctx.flashes = flashes + if category_filter: + flashes = list(filter(lambda f: f[0] in category_filter, flashes)) + if not with_categories: + return [x[1] for x in flashes] + return flashes + + +def _prepare_send_file_kwargs(**kwargs: t.Any) -> t.Dict[str, t.Any]: + if kwargs.get("max_age") is None: + kwargs["max_age"] = current_app.get_send_file_max_age + + kwargs.update( + environ=request.environ, + use_x_sendfile=current_app.config["USE_X_SENDFILE"], + response_class=current_app.response_class, + _root_path=current_app.root_path, # type: ignore + ) + return kwargs + + +def send_file( + path_or_file: t.Union[os.PathLike, str, t.BinaryIO], + mimetype: t.Optional[str] = None, + as_attachment: bool = False, + download_name: t.Optional[str] = None, + conditional: bool = True, + etag: t.Union[bool, str] = True, + last_modified: t.Optional[t.Union[datetime, int, float]] = None, + max_age: t.Optional[ + t.Union[int, t.Callable[[t.Optional[str]], t.Optional[int]]] + ] = 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 + deprecated 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: t.Union[os.PathLike, str], + path: t.Union[os.PathLike, str], + **kwargs: t.Any, +) -> "Response": + """Send a file from within a directory using :func:`send_file`. + + .. code-block:: python + + @app.route("/uploads/") + def download_file(name): + return send_from_directory( + app.config['UPLOAD_FOLDER'], name, as_attachment=True + ) + + This is a secure way to serve files from a folder, such as static + files or uploads. Uses :func:`~werkzeug.security.safe_join` to + ensure the path coming from the client is not maliciously crafted to + point outside the specified directory. + + If the final path does not point to an existing regular file, + raises a 404 :exc:`~werkzeug.exceptions.NotFound` error. + + :param directory: The directory that ``path`` must be located under, + relative to the current application's root path. + :param path: The path to the file to send, relative to + ``directory``. + :param kwargs: Arguments to pass to :func:`send_file`. + + .. versionchanged:: 2.0 + ``path`` replaces the ``filename`` parameter. + + .. versionadded:: 2.0 + Moved the implementation to Werkzeug. This is now a wrapper to + pass some Flask-specific arguments. + + .. versionadded:: 0.5 + """ + return werkzeug.utils.send_from_directory( # type: ignore[return-value] + directory, path, **_prepare_send_file_kwargs(**kwargs) + ) + + +def get_root_path(import_name: str) -> str: + """Find the root path of a package, or the path that contains a + module. If it cannot be found, returns the current working + directory. + + Not to be confused with the value returned by :func:`find_package`. + + :meta private: + """ + # Module already imported and has a file attribute. Use that first. + mod = sys.modules.get(import_name) + + if mod is not None and hasattr(mod, "__file__") and mod.__file__ is not None: + return os.path.dirname(os.path.abspath(mod.__file__)) + + # Next attempt: check the loader. + loader = pkgutil.get_loader(import_name) + + # 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 or import_name == "__main__": + return os.getcwd() + + if hasattr(loader, "get_filename"): + filepath = loader.get_filename(import_name) # type: 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)) + + +class locked_cached_property(werkzeug.utils.cached_property): + """A :func:`property` that is only evaluated once. Like + :class:`werkzeug.utils.cached_property` except access uses a lock + for thread safety. + + .. versionchanged:: 2.0 + Inherits from Werkzeug's ``cached_property`` (and ``property``). + """ + + def __init__( + self, + fget: t.Callable[[t.Any], t.Any], + name: t.Optional[str] = None, + doc: t.Optional[str] = None, + ) -> None: + super().__init__(fget, name=name, doc=doc) + self.lock = RLock() + + def __get__(self, obj: object, type: type = None) -> t.Any: # type: ignore + if obj is None: + return self + + with self.lock: + return super().__get__(obj, type=type) + + def __set__(self, obj: object, value: t.Any) -> None: + with self.lock: + super().__set__(obj, value) + + def __delete__(self, obj: object) -> None: + with self.lock: + super().__delete__(obj) + + +def is_ip(value: str) -> bool: + """Determine if the given string is an IP address. + + :param value: value to check + :type value: str + + :return: True if string is an IP address + :rtype: bool + """ + for family in (socket.AF_INET, socket.AF_INET6): + try: + socket.inet_pton(family, value) + except OSError: + pass + else: + return True + + return False + + +@lru_cache(maxsize=None) +def _split_blueprint_path(name: str) -> t.List[str]: + out: t.List[str] = [name] + + if "." in name: + out.extend(_split_blueprint_path(name.rpartition(".")[0])) + + return out diff --git a/.vtodo/Lib/site-packages/flask/json/__init__.py b/.vtodo/Lib/site-packages/flask/json/__init__.py new file mode 100644 index 0000000..65d8829 --- /dev/null +++ b/.vtodo/Lib/site-packages/flask/json/__init__.py @@ -0,0 +1,342 @@ +from __future__ import annotations + +import json as _json +import typing as t + +from jinja2.utils import htmlsafe_json_dumps as _jinja_htmlsafe_dumps + +from ..globals import current_app +from .provider import _default + +if t.TYPE_CHECKING: # pragma: no cover + from ..app import Flask + from ..wrappers import Response + + +class JSONEncoder(_json.JSONEncoder): + """The default JSON encoder. Handles extra types compared to the + built-in :class:`json.JSONEncoder`. + + - :class:`datetime.datetime` and :class:`datetime.date` are + serialized to :rfc:`822` strings. This is the same as the HTTP + date format. + - :class:`decimal.Decimal` is serialized to a string. + - :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. + + Assign a subclass of this to :attr:`flask.Flask.json_encoder` or + :attr:`flask.Blueprint.json_encoder` to override the default. + + .. deprecated:: 2.2 + Will be removed in Flask 2.3. Use ``app.json`` instead. + """ + + def __init__(self, **kwargs) -> None: + import warnings + + warnings.warn( + "'JSONEncoder' is deprecated and will be removed in" + " Flask 2.3. Use 'Flask.json' to provide an alternate" + " JSON implementation instead.", + DeprecationWarning, + stacklevel=3, + ) + super().__init__(**kwargs) + + def default(self, o: t.Any) -> t.Any: + """Convert ``o`` to a JSON serializable type. See + :meth:`json.JSONEncoder.default`. Python does not support + overriding how basic types like ``str`` or ``list`` are + serialized, they are handled before this method. + """ + return _default(o) + + +class JSONDecoder(_json.JSONDecoder): + """The default JSON decoder. + + This does not change any behavior from the built-in + :class:`json.JSONDecoder`. + + Assign a subclass of this to :attr:`flask.Flask.json_decoder` or + :attr:`flask.Blueprint.json_decoder` to override the default. + + .. deprecated:: 2.2 + Will be removed in Flask 2.3. Use ``app.json`` instead. + """ + + def __init__(self, **kwargs) -> None: + import warnings + + warnings.warn( + "'JSONDecoder' is deprecated and will be removed in" + " Flask 2.3. Use 'Flask.json' to provide an alternate" + " JSON implementation instead.", + DeprecationWarning, + stacklevel=3, + ) + super().__init__(**kwargs) + + +def dumps(obj: t.Any, *, app: Flask | None = None, **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.2 + Calls ``current_app.json.dumps``, allowing an app to override + the behavior. + + .. versionchanged:: 2.2 + The ``app`` parameter will be removed in Flask 2.3. + + .. 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 app is not None: + import warnings + + warnings.warn( + "The 'app' parameter is deprecated and will be removed in" + " Flask 2.3. Call 'app.json.dumps' directly instead.", + DeprecationWarning, + stacklevel=2, + ) + else: + app = current_app + + if app: + return app.json.dumps(obj, **kwargs) + + kwargs.setdefault("default", _default) + return _json.dumps(obj, **kwargs) + + +def dump( + obj: t.Any, fp: t.IO[str], *, app: Flask | None = None, **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.2 + Calls ``current_app.json.dump``, allowing an app to override + the behavior. + + .. versionchanged:: 2.2 + The ``app`` parameter will be removed in Flask 2.3. + + .. versionchanged:: 2.0 + Writing to a binary file, and the ``encoding`` argument, will be + removed in Flask 2.1. + """ + if app is not None: + import warnings + + warnings.warn( + "The 'app' parameter is deprecated and will be removed in" + " Flask 2.3. Call 'app.json.dump' directly instead.", + DeprecationWarning, + stacklevel=2, + ) + else: + app = current_app + + if app: + app.json.dump(obj, fp, **kwargs) + else: + kwargs.setdefault("default", _default) + _json.dump(obj, fp, **kwargs) + + +def loads(s: str | bytes, *, app: Flask | None = None, **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.2 + Calls ``current_app.json.loads``, 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 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 app is not None: + import warnings + + warnings.warn( + "The 'app' parameter is deprecated and will be removed in" + " Flask 2.3. Call 'app.json.loads' directly instead.", + DeprecationWarning, + stacklevel=2, + ) + else: + app = current_app + + if app: + return app.json.loads(s, **kwargs) + + return _json.loads(s, **kwargs) + + +def load(fp: t.IO[t.AnyStr], *, app: Flask | None = None, **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.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 app is not None: + import warnings + + warnings.warn( + "The 'app' parameter is deprecated and will be removed in" + " Flask 2.3. Call 'app.json.load' directly instead.", + DeprecationWarning, + stacklevel=2, + ) + else: + app = current_app + + if app: + return app.json.load(fp, **kwargs) + + return _json.load(fp, **kwargs) + + +def htmlsafe_dumps(obj: t.Any, **kwargs: t.Any) -> str: + """Serialize an object to a string of JSON with :func:`dumps`, then + replace HTML-unsafe characters with Unicode escapes and mark the + result safe with :class:`~markupsafe.Markup`. + + This is available in templates as the ``|tojson`` filter. + + The returned string is safe to render in HTML documents and + `` + + + +