From 469f1ec2659f93252b40fa975d629281deb9ab27 Mon Sep 17 00:00:00 2001 From: Benny Rochwerger Date: Sun, 12 Dec 2021 15:22:12 +0200 Subject: [PATCH] added to greering message the hostname --- .venv/bin/activate | 76 + .venv/bin/activate.csh | 37 + .venv/bin/activate.fish | 75 + .venv/bin/easy_install | 11 + .venv/bin/easy_install-3.6 | 11 + .venv/bin/flask | 8 + .venv/bin/pip | 11 + .venv/bin/pip3 | 11 + .venv/bin/pip3.6 | 11 + .venv/bin/python | 1 + .venv/bin/python3 | 1 + .venv/bin/waitress-serve | 11 + .../Flask-2.0.2.dist-info/INSTALLER | 1 + .../Flask-2.0.2.dist-info/LICENSE.rst | 28 + .../Flask-2.0.2.dist-info/METADATA | 125 + .../Flask-2.0.2.dist-info/RECORD | 52 + .../Flask-2.0.2.dist-info/REQUESTED | 0 .../site-packages/Flask-2.0.2.dist-info/WHEEL | 5 + .../Flask-2.0.2.dist-info/entry_points.txt | 3 + .../Flask-2.0.2.dist-info/top_level.txt | 1 + .../Jinja2-3.0.3.dist-info/INSTALLER | 1 + .../Jinja2-3.0.3.dist-info/LICENSE.rst | 28 + .../Jinja2-3.0.3.dist-info/METADATA | 113 + .../Jinja2-3.0.3.dist-info/RECORD | 58 + .../Jinja2-3.0.3.dist-info/WHEEL | 5 + .../Jinja2-3.0.3.dist-info/entry_points.txt | 3 + .../Jinja2-3.0.3.dist-info/top_level.txt | 1 + .../MarkupSafe-2.0.1.dist-info/INSTALLER | 1 + .../MarkupSafe-2.0.1.dist-info/LICENSE.rst | 28 + .../MarkupSafe-2.0.1.dist-info/METADATA | 101 + .../MarkupSafe-2.0.1.dist-info/RECORD | 14 + .../MarkupSafe-2.0.1.dist-info/WHEEL | 8 + .../MarkupSafe-2.0.1.dist-info/top_level.txt | 1 + .../Werkzeug-2.0.2.dist-info/INSTALLER | 1 + .../Werkzeug-2.0.2.dist-info/LICENSE.rst | 28 + .../Werkzeug-2.0.2.dist-info/METADATA | 129 + .../Werkzeug-2.0.2.dist-info/RECORD | 111 + .../Werkzeug-2.0.2.dist-info/WHEEL | 5 + .../Werkzeug-2.0.2.dist-info/top_level.txt | 1 + .../__pycache__/dataclasses.cpython-36.pyc | Bin 0 -> 21751 bytes .../__pycache__/easy_install.cpython-36.pyc | Bin 0 -> 275 bytes .../typing_extensions.cpython-36.pyc | Bin 0 -> 77119 bytes .../__pycache__/zipp.cpython-36.pyc | Bin 0 -> 10108 bytes .../click-8.0.3.dist-info/INSTALLER | 1 + .../click-8.0.3.dist-info/LICENSE.rst | 28 + .../click-8.0.3.dist-info/METADATA | 111 + .../click-8.0.3.dist-info/RECORD | 41 + .../site-packages/click-8.0.3.dist-info/WHEEL | 5 + .../click-8.0.3.dist-info/top_level.txt | 1 + .../python3.6/site-packages/click/__init__.py | 75 + .../click/__pycache__/__init__.cpython-36.pyc | Bin 0 -> 2702 bytes .../click/__pycache__/_compat.cpython-36.pyc | Bin 0 -> 15711 bytes .../__pycache__/_termui_impl.cpython-36.pyc | Bin 0 -> 15704 bytes .../__pycache__/_textwrap.cpython-36.pyc | Bin 0 -> 1519 bytes .../__pycache__/_unicodefun.cpython-36.pyc | Bin 0 -> 2349 bytes .../__pycache__/_winconsole.cpython-36.pyc | Bin 0 -> 7732 bytes .../click/__pycache__/core.cpython-36.pyc | Bin 0 -> 88008 bytes .../__pycache__/decorators.cpython-36.pyc | Bin 0 -> 14387 bytes .../__pycache__/exceptions.cpython-36.pyc | Bin 0 -> 10092 bytes .../__pycache__/formatting.cpython-36.pyc | Bin 0 -> 9293 bytes .../click/__pycache__/globals.cpython-36.pyc | Bin 0 -> 2424 bytes .../click/__pycache__/parser.cpython-36.pyc | Bin 0 -> 13493 bytes .../shell_completion.cpython-36.pyc | Bin 0 -> 16665 bytes .../click/__pycache__/termui.cpython-36.pyc | Bin 0 -> 26356 bytes .../click/__pycache__/testing.cpython-36.pyc | Bin 0 -> 14703 bytes .../click/__pycache__/types.cpython-36.pyc | Bin 0 -> 33310 bytes .../click/__pycache__/utils.cpython-36.pyc | Bin 0 -> 17608 bytes .../python3.6/site-packages/click/_compat.py | 627 ++ .../site-packages/click/_termui_impl.py | 718 ++ .../site-packages/click/_textwrap.py | 49 + .../site-packages/click/_unicodefun.py | 100 + .../site-packages/click/_winconsole.py | 279 + .../lib/python3.6/site-packages/click/core.py | 2953 ++++++ .../site-packages/click/decorators.py | 436 + .../site-packages/click/exceptions.py | 287 + .../site-packages/click/formatting.py | 301 + .../python3.6/site-packages/click/globals.py | 69 + .../python3.6/site-packages/click/parser.py | 529 ++ .../python3.6/site-packages/click/py.typed | 0 .../site-packages/click/shell_completion.py | 581 ++ .../python3.6/site-packages/click/termui.py | 809 ++ .../python3.6/site-packages/click/testing.py | 479 + .../python3.6/site-packages/click/types.py | 1052 ++ .../python3.6/site-packages/click/utils.py | 579 ++ .../dataclasses-0.8.dist-info/INSTALLER | 1 + .../dataclasses-0.8.dist-info/LICENSE.txt | 202 + .../dataclasses-0.8.dist-info/METADATA | 98 + .../dataclasses-0.8.dist-info/RECORD | 8 + .../dataclasses-0.8.dist-info/WHEEL | 5 + .../dataclasses-0.8.dist-info/top_level.txt | 1 + .../python3.6/site-packages/dataclasses.py | 1184 +++ .../python3.6/site-packages/easy_install.py | 5 + .../python3.6/site-packages/flask/__init__.py | 46 + .../python3.6/site-packages/flask/__main__.py | 3 + .../flask/__pycache__/__init__.cpython-36.pyc | Bin 0 -> 1893 bytes .../flask/__pycache__/__main__.cpython-36.pyc | Bin 0 -> 224 bytes .../flask/__pycache__/app.cpython-36.pyc | Bin 0 -> 62892 bytes .../__pycache__/blueprints.cpython-36.pyc | Bin 0 -> 21949 bytes .../flask/__pycache__/cli.cpython-36.pyc | Bin 0 -> 26935 bytes .../flask/__pycache__/config.cpython-36.pyc | Bin 0 -> 11616 bytes .../flask/__pycache__/ctx.cpython-36.pyc | Bin 0 -> 15384 bytes .../__pycache__/debughelpers.cpython-36.pyc | Bin 0 -> 6431 bytes .../flask/__pycache__/globals.cpython-36.pyc | Bin 0 -> 1796 bytes .../flask/__pycache__/helpers.cpython-36.pyc | Bin 0 -> 27048 bytes .../flask/__pycache__/logging.cpython-36.pyc | Bin 0 -> 2454 bytes .../flask/__pycache__/scaffold.cpython-36.pyc | Bin 0 -> 24799 bytes .../flask/__pycache__/sessions.cpython-36.pyc | Bin 0 -> 13073 bytes .../flask/__pycache__/signals.cpython-36.pyc | Bin 0 -> 2386 bytes .../__pycache__/templating.cpython-36.pyc | Bin 0 -> 5503 bytes .../flask/__pycache__/testing.cpython-36.pyc | Bin 0 -> 8869 bytes .../flask/__pycache__/typing.cpython-36.pyc | Bin 0 -> 1767 bytes .../flask/__pycache__/views.cpython-36.pyc | Bin 0 -> 4964 bytes .../flask/__pycache__/wrappers.cpython-36.pyc | Bin 0 -> 4955 bytes .../lib/python3.6/site-packages/flask/app.py | 2091 ++++ .../site-packages/flask/blueprints.py | 609 ++ .../lib/python3.6/site-packages/flask/cli.py | 998 ++ .../python3.6/site-packages/flask/config.py | 295 + .../lib/python3.6/site-packages/flask/ctx.py | 480 + .../site-packages/flask/debughelpers.py | 172 + .../python3.6/site-packages/flask/globals.py | 59 + .../python3.6/site-packages/flask/helpers.py | 836 ++ .../site-packages/flask/json/__init__.py | 357 + .../json/__pycache__/__init__.cpython-36.pyc | Bin 0 -> 11552 bytes .../flask/json/__pycache__/tag.cpython-36.pyc | Bin 0 -> 11792 bytes .../python3.6/site-packages/flask/json/tag.py | 312 + .../python3.6/site-packages/flask/logging.py | 74 + .../python3.6/site-packages/flask/py.typed | 0 .../python3.6/site-packages/flask/scaffold.py | 875 ++ .../python3.6/site-packages/flask/sessions.py | 404 + .../python3.6/site-packages/flask/signals.py | 56 + .../site-packages/flask/templating.py | 165 + .../python3.6/site-packages/flask/testing.py | 280 + .../python3.6/site-packages/flask/typing.py | 56 + .../python3.6/site-packages/flask/views.py | 158 + .../python3.6/site-packages/flask/wrappers.py | 167 + .../INSTALLER | 1 + .../LICENSE | 13 + .../METADATA | 117 + .../importlib_metadata-4.8.2.dist-info/RECORD | 23 + .../importlib_metadata-4.8.2.dist-info/WHEEL | 5 + .../top_level.txt | 1 + .../importlib_metadata/__init__.py | 1054 ++ .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 36934 bytes .../__pycache__/_adapters.cpython-36.pyc | Bin 0 -> 2400 bytes .../__pycache__/_collections.cpython-36.pyc | Bin 0 -> 1547 bytes .../__pycache__/_compat.cpython-36.pyc | Bin 0 -> 2013 bytes .../__pycache__/_functools.cpython-36.pyc | Bin 0 -> 3130 bytes .../__pycache__/_itertools.cpython-36.pyc | Bin 0 -> 2025 bytes .../__pycache__/_meta.cpython-36.pyc | Bin 0 -> 2359 bytes .../__pycache__/_text.cpython-36.pyc | Bin 0 -> 3085 bytes .../importlib_metadata/_adapters.py | 68 + .../importlib_metadata/_collections.py | 30 + .../importlib_metadata/_compat.py | 71 + .../importlib_metadata/_functools.py | 104 + .../importlib_metadata/_itertools.py | 73 + .../site-packages/importlib_metadata/_meta.py | 48 + .../site-packages/importlib_metadata/_text.py | 99 + .../site-packages/importlib_metadata/py.typed | 0 .../itsdangerous-2.0.1.dist-info/INSTALLER | 1 + .../itsdangerous-2.0.1.dist-info/LICENSE.rst | 28 + .../itsdangerous-2.0.1.dist-info/METADATA | 96 + .../itsdangerous-2.0.1.dist-info/RECORD | 25 + .../itsdangerous-2.0.1.dist-info/WHEEL | 5 + .../top_level.txt | 1 + .../site-packages/itsdangerous/__init__.py | 22 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 1019 bytes .../__pycache__/_json.cpython-36.pyc | Bin 0 -> 1537 bytes .../__pycache__/encoding.cpython-36.pyc | Bin 0 -> 1841 bytes .../__pycache__/exc.cpython-36.pyc | Bin 0 -> 3386 bytes .../__pycache__/jws.cpython-36.pyc | Bin 0 -> 7499 bytes .../__pycache__/serializer.cpython-36.pyc | Bin 0 -> 9562 bytes .../__pycache__/signer.cpython-36.pyc | Bin 0 -> 8386 bytes .../__pycache__/timed.cpython-36.pyc | Bin 0 -> 6239 bytes .../__pycache__/url_safe.cpython-36.pyc | Bin 0 -> 2691 bytes .../site-packages/itsdangerous/_json.py | 34 + .../site-packages/itsdangerous/encoding.py | 54 + .../site-packages/itsdangerous/exc.py | 107 + .../site-packages/itsdangerous/jws.py | 259 + .../site-packages/itsdangerous/py.typed | 0 .../site-packages/itsdangerous/serializer.py | 295 + .../site-packages/itsdangerous/signer.py | 257 + .../site-packages/itsdangerous/timed.py | 227 + .../site-packages/itsdangerous/url_safe.py | 80 + .../site-packages/jinja2/__init__.py | 45 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 1918 bytes .../__pycache__/_identifier.cpython-36.pyc | Bin 0 -> 1903 bytes .../__pycache__/async_utils.cpython-36.pyc | Bin 0 -> 2488 bytes .../jinja2/__pycache__/bccache.cpython-36.pyc | Bin 0 -> 13150 bytes .../__pycache__/compiler.cpython-36.pyc | Bin 0 -> 54422 bytes .../__pycache__/constants.cpython-36.pyc | Bin 0 -> 1549 bytes .../jinja2/__pycache__/debug.cpython-36.pyc | Bin 0 -> 5397 bytes .../__pycache__/defaults.cpython-36.pyc | Bin 0 -> 1294 bytes .../__pycache__/environment.cpython-36.pyc | Bin 0 -> 52450 bytes .../__pycache__/exceptions.cpython-36.pyc | Bin 0 -> 5551 bytes .../jinja2/__pycache__/ext.cpython-36.pyc | Bin 0 -> 26327 bytes .../jinja2/__pycache__/filters.cpython-36.pyc | Bin 0 -> 49807 bytes .../__pycache__/idtracking.cpython-36.pyc | Bin 0 -> 11178 bytes .../jinja2/__pycache__/lexer.cpython-36.pyc | Bin 0 -> 20036 bytes .../jinja2/__pycache__/loaders.cpython-36.pyc | Bin 0 -> 20270 bytes .../jinja2/__pycache__/meta.cpython-36.pyc | Bin 0 -> 3799 bytes .../__pycache__/nativetypes.cpython-36.pyc | Bin 0 -> 4858 bytes .../jinja2/__pycache__/nodes.cpython-36.pyc | Bin 0 -> 41246 bytes .../__pycache__/optimizer.cpython-36.pyc | Bin 0 -> 1914 bytes .../jinja2/__pycache__/parser.cpython-36.pyc | Bin 0 -> 27688 bytes .../jinja2/__pycache__/runtime.cpython-36.pyc | Bin 0 -> 32916 bytes .../jinja2/__pycache__/sandbox.cpython-36.pyc | Bin 0 -> 11749 bytes .../jinja2/__pycache__/tests.cpython-36.pyc | Bin 0 -> 6535 bytes .../jinja2/__pycache__/utils.cpython-36.pyc | Bin 0 -> 27270 bytes .../jinja2/__pycache__/visitor.cpython-36.pyc | Bin 0 -> 3892 bytes .../site-packages/jinja2/_identifier.py | 6 + .../site-packages/jinja2/async_utils.py | 75 + .../python3.6/site-packages/jinja2/bccache.py | 364 + .../site-packages/jinja2/compiler.py | 1957 ++++ .../site-packages/jinja2/constants.py | 20 + .../python3.6/site-packages/jinja2/debug.py | 259 + .../site-packages/jinja2/defaults.py | 48 + .../site-packages/jinja2/environment.py | 1661 ++++ .../site-packages/jinja2/exceptions.py | 166 + .../lib/python3.6/site-packages/jinja2/ext.py | 879 ++ .../python3.6/site-packages/jinja2/filters.py | 1824 ++++ .../site-packages/jinja2/idtracking.py | 318 + .../python3.6/site-packages/jinja2/lexer.py | 869 ++ .../python3.6/site-packages/jinja2/loaders.py | 652 ++ .../python3.6/site-packages/jinja2/meta.py | 111 + .../site-packages/jinja2/nativetypes.py | 124 + .../python3.6/site-packages/jinja2/nodes.py | 1204 +++ .../site-packages/jinja2/optimizer.py | 47 + .../python3.6/site-packages/jinja2/parser.py | 1040 ++ .../python3.6/site-packages/jinja2/py.typed | 0 .../python3.6/site-packages/jinja2/runtime.py | 1104 +++ .../python3.6/site-packages/jinja2/sandbox.py | 428 + .../python3.6/site-packages/jinja2/tests.py | 255 + .../python3.6/site-packages/jinja2/utils.py | 854 ++ .../python3.6/site-packages/jinja2/visitor.py | 92 + .../site-packages/markupsafe/__init__.py | 288 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 10795 bytes .../__pycache__/_native.cpython-36.pyc | Bin 0 -> 2317 bytes .../site-packages/markupsafe/_native.py | 75 + .../site-packages/markupsafe/_speedups.c | 339 + .../_speedups.cpython-36m-x86_64-linux-gnu.so | Bin 0 -> 51904 bytes .../site-packages/markupsafe/_speedups.pyi | 9 + .../site-packages/markupsafe/py.typed | 0 .../pip-21.3.1.dist-info/INSTALLER | 1 + .../pip-21.3.1.dist-info/LICENSE.txt | 20 + .../pip-21.3.1.dist-info/METADATA | 93 + .../site-packages/pip-21.3.1.dist-info/RECORD | 818 ++ .../site-packages/pip-21.3.1.dist-info/WHEEL | 5 + .../pip-21.3.1.dist-info/entry_points.txt | 5 + .../pip-21.3.1.dist-info/top_level.txt | 1 + .../python3.6/site-packages/pip/__init__.py | 13 + .../python3.6/site-packages/pip/__main__.py | 31 + .../pip/__pycache__/__init__.cpython-36.pyc | Bin 0 -> 578 bytes .../pip/__pycache__/__main__.cpython-36.pyc | Bin 0 -> 536 bytes .../site-packages/pip/_internal/__init__.py | 19 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 699 bytes .../__pycache__/build_env.cpython-36.pyc | Bin 0 -> 9356 bytes .../__pycache__/cache.cpython-36.pyc | Bin 0 -> 8295 bytes .../__pycache__/configuration.cpython-36.pyc | Bin 0 -> 11004 bytes .../__pycache__/exceptions.cpython-36.pyc | Bin 0 -> 16471 bytes .../_internal/__pycache__/main.cpython-36.pyc | Bin 0 -> 564 bytes .../__pycache__/pyproject.cpython-36.pyc | Bin 0 -> 3620 bytes .../self_outdated_check.cpython-36.pyc | Bin 0 -> 4406 bytes .../__pycache__/wheel_builder.cpython-36.pyc | Bin 0 -> 8923 bytes .../site-packages/pip/_internal/build_env.py | 293 + .../site-packages/pip/_internal/cache.py | 264 + .../pip/_internal/cli/__init__.py | 4 + .../cli/__pycache__/__init__.cpython-36.pyc | Bin 0 -> 223 bytes .../__pycache__/autocompletion.cpython-36.pyc | Bin 0 -> 5118 bytes .../__pycache__/base_command.cpython-36.pyc | Bin 0 -> 5940 bytes .../cli/__pycache__/cmdoptions.cpython-36.pyc | Bin 0 -> 22105 bytes .../command_context.cpython-36.pyc | Bin 0 -> 1223 bytes .../cli/__pycache__/main.cpython-36.pyc | Bin 0 -> 1305 bytes .../__pycache__/main_parser.cpython-36.pyc | Bin 0 -> 2096 bytes .../cli/__pycache__/parser.cpython-36.pyc | Bin 0 -> 9814 bytes .../__pycache__/progress_bars.cpython-36.pyc | Bin 0 -> 7563 bytes .../__pycache__/req_command.cpython-36.pyc | Bin 0 -> 12188 bytes .../cli/__pycache__/spinners.cpython-36.pyc | Bin 0 -> 4845 bytes .../__pycache__/status_codes.cpython-36.pyc | Bin 0 -> 302 bytes .../pip/_internal/cli/autocompletion.py | 163 + .../pip/_internal/cli/base_command.py | 214 + .../pip/_internal/cli/cmdoptions.py | 1010 ++ .../pip/_internal/cli/command_context.py | 27 + .../site-packages/pip/_internal/cli/main.py | 70 + .../pip/_internal/cli/main_parser.py | 87 + .../site-packages/pip/_internal/cli/parser.py | 292 + .../pip/_internal/cli/progress_bars.py | 250 + .../pip/_internal/cli/req_command.py | 469 + .../pip/_internal/cli/spinners.py | 157 + .../pip/_internal/cli/status_codes.py | 6 + .../pip/_internal/commands/__init__.py | 127 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 2932 bytes .../commands/__pycache__/cache.cpython-36.pyc | Bin 0 -> 6148 bytes .../commands/__pycache__/check.cpython-36.pyc | Bin 0 -> 1520 bytes .../__pycache__/completion.cpython-36.pyc | Bin 0 -> 3063 bytes .../__pycache__/configuration.cpython-36.pyc | Bin 0 -> 8190 bytes .../commands/__pycache__/debug.cpython-36.pyc | Bin 0 -> 6465 bytes .../__pycache__/download.cpython-36.pyc | Bin 0 -> 3907 bytes .../__pycache__/freeze.cpython-36.pyc | Bin 0 -> 2568 bytes .../commands/__pycache__/hash.cpython-36.pyc | Bin 0 -> 2048 bytes .../commands/__pycache__/help.cpython-36.pyc | Bin 0 -> 1244 bytes .../commands/__pycache__/index.cpython-36.pyc | Bin 0 -> 4375 bytes .../__pycache__/install.cpython-36.pyc | Bin 0 -> 17507 bytes .../commands/__pycache__/list.cpython-36.pyc | Bin 0 -> 9980 bytes .../__pycache__/search.cpython-36.pyc | Bin 0 -> 5179 bytes .../commands/__pycache__/show.cpython-36.pyc | Bin 0 -> 8324 bytes .../__pycache__/uninstall.cpython-36.pyc | Bin 0 -> 3032 bytes .../commands/__pycache__/wheel.cpython-36.pyc | Bin 0 -> 4770 bytes .../pip/_internal/commands/cache.py | 223 + .../pip/_internal/commands/check.py | 53 + .../pip/_internal/commands/completion.py | 96 + .../pip/_internal/commands/configuration.py | 266 + .../pip/_internal/commands/debug.py | 202 + .../pip/_internal/commands/download.py | 139 + .../pip/_internal/commands/freeze.py | 97 + .../pip/_internal/commands/hash.py | 59 + .../pip/_internal/commands/help.py | 41 + .../pip/_internal/commands/index.py | 138 + .../pip/_internal/commands/install.py | 770 ++ .../pip/_internal/commands/list.py | 361 + .../pip/_internal/commands/search.py | 174 + .../pip/_internal/commands/show.py | 235 + .../pip/_internal/commands/uninstall.py | 105 + .../pip/_internal/commands/wheel.py | 177 + .../pip/_internal/configuration.py | 367 + .../pip/_internal/distributions/__init__.py | 21 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 740 bytes .../__pycache__/base.cpython-36.pyc | Bin 0 -> 1803 bytes .../__pycache__/installed.cpython-36.pyc | Bin 0 -> 1270 bytes .../__pycache__/sdist.cpython-36.pyc | Bin 0 -> 4452 bytes .../__pycache__/wheel.cpython-36.pyc | Bin 0 -> 1548 bytes .../pip/_internal/distributions/base.py | 36 + .../pip/_internal/distributions/installed.py | 22 + .../pip/_internal/distributions/sdist.py | 129 + .../pip/_internal/distributions/wheel.py | 31 + .../site-packages/pip/_internal/exceptions.py | 402 + .../pip/_internal/index/__init__.py | 2 + .../index/__pycache__/__init__.cpython-36.pyc | Bin 0 -> 177 bytes .../__pycache__/collector.cpython-36.pyc | Bin 0 -> 15658 bytes .../__pycache__/package_finder.cpython-36.pyc | Bin 0 -> 27728 bytes .../index/__pycache__/sources.cpython-36.pyc | Bin 0 -> 7150 bytes .../pip/_internal/index/collector.py | 536 ++ .../pip/_internal/index/package_finder.py | 993 ++ .../pip/_internal/index/sources.py | 224 + .../pip/_internal/locations/__init__.py | 446 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 10522 bytes .../__pycache__/_distutils.cpython-36.pyc | Bin 0 -> 4580 bytes .../__pycache__/_sysconfig.cpython-36.pyc | Bin 0 -> 6190 bytes .../locations/__pycache__/base.cpython-36.pyc | Bin 0 -> 1444 bytes .../pip/_internal/locations/_distutils.py | 169 + .../pip/_internal/locations/_sysconfig.py | 219 + .../pip/_internal/locations/base.py | 52 + .../site-packages/pip/_internal/main.py | 12 + .../pip/_internal/metadata/__init__.py | 51 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 1873 bytes .../metadata/__pycache__/base.cpython-36.pyc | Bin 0 -> 12818 bytes .../__pycache__/pkg_resources.cpython-36.pyc | Bin 0 -> 5914 bytes .../pip/_internal/metadata/base.py | 330 + .../pip/_internal/metadata/pkg_resources.py | 146 + .../pip/_internal/models/__init__.py | 2 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 211 bytes .../__pycache__/candidate.cpython-36.pyc | Bin 0 -> 1393 bytes .../__pycache__/direct_url.cpython-36.pyc | Bin 0 -> 7093 bytes .../__pycache__/format_control.cpython-36.pyc | Bin 0 -> 2640 bytes .../models/__pycache__/index.cpython-36.pyc | Bin 0 -> 1173 bytes .../models/__pycache__/link.cpython-36.pyc | Bin 0 -> 10172 bytes .../models/__pycache__/scheme.cpython-36.pyc | Bin 0 -> 973 bytes .../__pycache__/search_scope.cpython-36.pyc | Bin 0 -> 3406 bytes .../selection_prefs.cpython-36.pyc | Bin 0 -> 1625 bytes .../__pycache__/target_python.cpython-36.pyc | Bin 0 -> 3335 bytes .../models/__pycache__/wheel.cpython-36.pyc | Bin 0 -> 4271 bytes .../pip/_internal/models/candidate.py | 34 + .../pip/_internal/models/direct_url.py | 220 + .../pip/_internal/models/format_control.py | 80 + .../pip/_internal/models/index.py | 28 + .../pip/_internal/models/link.py | 288 + .../pip/_internal/models/scheme.py | 31 + .../pip/_internal/models/search_scope.py | 129 + .../pip/_internal/models/selection_prefs.py | 51 + .../pip/_internal/models/target_python.py | 110 + .../pip/_internal/models/wheel.py | 89 + .../pip/_internal/network/__init__.py | 2 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 199 bytes .../network/__pycache__/auth.cpython-36.pyc | Bin 0 -> 7401 bytes .../network/__pycache__/cache.cpython-36.pyc | Bin 0 -> 2707 bytes .../__pycache__/download.cpython-36.pyc | Bin 0 -> 5516 bytes .../__pycache__/lazy_wheel.cpython-36.pyc | Bin 0 -> 8194 bytes .../__pycache__/session.cpython-36.pyc | Bin 0 -> 10714 bytes .../network/__pycache__/utils.cpython-36.pyc | Bin 0 -> 1347 bytes .../network/__pycache__/xmlrpc.cpython-36.pyc | Bin 0 -> 1984 bytes .../pip/_internal/network/auth.py | 323 + .../pip/_internal/network/cache.py | 69 + .../pip/_internal/network/download.py | 184 + .../pip/_internal/network/lazy_wheel.py | 210 + .../pip/_internal/network/session.py | 454 + .../pip/_internal/network/utils.py | 96 + .../pip/_internal/network/xmlrpc.py | 60 + .../pip/_internal/operations/__init__.py | 0 .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 147 bytes .../__pycache__/check.cpython-36.pyc | Bin 0 -> 3899 bytes .../__pycache__/freeze.cpython-36.pyc | Bin 0 -> 6035 bytes .../__pycache__/prepare.cpython-36.pyc | Bin 0 -> 14366 bytes .../_internal/operations/build/__init__.py | 0 .../build/__pycache__/__init__.cpython-36.pyc | Bin 0 -> 153 bytes .../build/__pycache__/metadata.cpython-36.pyc | Bin 0 -> 1093 bytes .../metadata_editable.cpython-36.pyc | Bin 0 -> 1127 bytes .../metadata_legacy.cpython-36.pyc | Bin 0 -> 2045 bytes .../build/__pycache__/wheel.cpython-36.pyc | Bin 0 -> 1134 bytes .../__pycache__/wheel_editable.cpython-36.pyc | Bin 0 -> 1328 bytes .../__pycache__/wheel_legacy.cpython-36.pyc | Bin 0 -> 2600 bytes .../_internal/operations/build/metadata.py | 30 + .../operations/build/metadata_editable.py | 34 + .../operations/build/metadata_legacy.py | 67 + .../pip/_internal/operations/build/wheel.py | 37 + .../operations/build/wheel_editable.py | 46 + .../operations/build/wheel_legacy.py | 105 + .../pip/_internal/operations/check.py | 149 + .../pip/_internal/operations/freeze.py | 254 + .../_internal/operations/install/__init__.py | 2 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 211 bytes .../editable_legacy.cpython-36.pyc | Bin 0 -> 1339 bytes .../install/__pycache__/legacy.cpython-36.pyc | Bin 0 -> 3307 bytes .../install/__pycache__/wheel.cpython-36.pyc | Bin 0 -> 20442 bytes .../operations/install/editable_legacy.py | 46 + .../_internal/operations/install/legacy.py | 125 + .../pip/_internal/operations/install/wheel.py | 738 ++ .../pip/_internal/operations/prepare.py | 632 ++ .../site-packages/pip/_internal/pyproject.py | 183 + .../pip/_internal/req/__init__.py | 94 + .../req/__pycache__/__init__.cpython-36.pyc | Bin 0 -> 2471 bytes .../__pycache__/constructors.cpython-36.pyc | Bin 0 -> 11097 bytes .../req/__pycache__/req_file.cpython-36.pyc | Bin 0 -> 13152 bytes .../__pycache__/req_install.cpython-36.pyc | Bin 0 -> 22543 bytes .../req/__pycache__/req_set.cpython-36.pyc | Bin 0 -> 5813 bytes .../__pycache__/req_tracker.cpython-36.pyc | Bin 0 -> 4064 bytes .../__pycache__/req_uninstall.cpython-36.pyc | Bin 0 -> 18629 bytes .../pip/_internal/req/constructors.py | 466 + .../pip/_internal/req/req_file.py | 536 ++ .../pip/_internal/req/req_install.py | 891 ++ .../pip/_internal/req/req_set.py | 189 + .../pip/_internal/req/req_tracker.py | 124 + .../pip/_internal/req/req_uninstall.py | 633 ++ .../pip/_internal/resolution/__init__.py | 0 .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 147 bytes .../__pycache__/base.cpython-36.pyc | Bin 0 -> 986 bytes .../pip/_internal/resolution/base.py | 20 + .../_internal/resolution/legacy/__init__.py | 0 .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 154 bytes .../__pycache__/resolver.cpython-36.pyc | Bin 0 -> 12147 bytes .../_internal/resolution/legacy/resolver.py | 467 + .../resolution/resolvelib/__init__.py | 0 .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 158 bytes .../__pycache__/base.cpython-36.pyc | Bin 0 -> 6740 bytes .../__pycache__/candidates.cpython-36.pyc | Bin 0 -> 18440 bytes .../__pycache__/factory.cpython-36.pyc | Bin 0 -> 18049 bytes .../found_candidates.cpython-36.pyc | Bin 0 -> 4803 bytes .../__pycache__/provider.cpython-36.pyc | Bin 0 -> 6976 bytes .../__pycache__/reporter.cpython-36.pyc | Bin 0 -> 3233 bytes .../__pycache__/requirements.cpython-36.pyc | Bin 0 -> 7509 bytes .../__pycache__/resolver.cpython-36.pyc | Bin 0 -> 7210 bytes .../_internal/resolution/resolvelib/base.py | 141 + .../resolution/resolvelib/candidates.py | 540 ++ .../resolution/resolvelib/factory.py | 701 ++ .../resolution/resolvelib/found_candidates.py | 155 + .../resolution/resolvelib/provider.py | 215 + .../resolution/resolvelib/reporter.py | 68 + .../resolution/resolvelib/requirements.py | 166 + .../resolution/resolvelib/resolver.py | 251 + .../pip/_internal/self_outdated_check.py | 182 + .../pip/_internal/utils/__init__.py | 0 .../utils/__pycache__/__init__.cpython-36.pyc | Bin 0 -> 142 bytes .../utils/__pycache__/_log.cpython-36.pyc | Bin 0 -> 1445 bytes .../utils/__pycache__/appdirs.cpython-36.pyc | Bin 0 -> 1564 bytes .../utils/__pycache__/compat.cpython-36.pyc | Bin 0 -> 1445 bytes .../compatibility_tags.cpython-36.pyc | Bin 0 -> 3969 bytes .../utils/__pycache__/datetime.cpython-36.pyc | Bin 0 -> 457 bytes .../__pycache__/deprecation.cpython-36.pyc | Bin 0 -> 3107 bytes .../direct_url_helpers.cpython-36.pyc | Bin 0 -> 2021 bytes .../__pycache__/distutils_args.cpython-36.pyc | Bin 0 -> 1154 bytes .../utils/__pycache__/egg_link.cpython-36.pyc | Bin 0 -> 2076 bytes .../utils/__pycache__/encoding.cpython-36.pyc | Bin 0 -> 1235 bytes .../__pycache__/entrypoints.cpython-36.pyc | Bin 0 -> 1246 bytes .../__pycache__/filesystem.cpython-36.pyc | Bin 0 -> 5040 bytes .../__pycache__/filetypes.cpython-36.pyc | Bin 0 -> 893 bytes .../utils/__pycache__/glibc.cpython-36.pyc | Bin 0 -> 1612 bytes .../utils/__pycache__/hashes.cpython-36.pyc | Bin 0 -> 5083 bytes .../inject_securetransport.cpython-36.pyc | Bin 0 -> 927 bytes .../utils/__pycache__/logging.cpython-36.pyc | Bin 0 -> 9129 bytes .../utils/__pycache__/misc.cpython-36.pyc | Bin 0 -> 20681 bytes .../utils/__pycache__/models.cpython-36.pyc | Bin 0 -> 1988 bytes .../__pycache__/packaging.cpython-36.pyc | Bin 0 -> 2568 bytes .../utils/__pycache__/parallel.cpython-36.pyc | Bin 0 -> 3066 bytes .../__pycache__/pkg_resources.cpython-36.pyc | Bin 0 -> 1798 bytes .../setuptools_build.cpython-36.pyc | Bin 0 -> 3381 bytes .../__pycache__/subprocess.cpython-36.pyc | Bin 0 -> 6114 bytes .../utils/__pycache__/temp_dir.cpython-36.pyc | Bin 0 -> 7154 bytes .../__pycache__/unpacking.cpython-36.pyc | Bin 0 -> 6507 bytes .../utils/__pycache__/urls.cpython-36.pyc | Bin 0 -> 1548 bytes .../__pycache__/virtualenv.cpython-36.pyc | Bin 0 -> 3178 bytes .../utils/__pycache__/wheel.cpython-36.pyc | Bin 0 -> 6194 bytes .../site-packages/pip/_internal/utils/_log.py | 38 + .../pip/_internal/utils/appdirs.py | 52 + .../pip/_internal/utils/compat.py | 63 + .../pip/_internal/utils/compatibility_tags.py | 165 + .../pip/_internal/utils/datetime.py | 11 + .../pip/_internal/utils/deprecation.py | 120 + .../pip/_internal/utils/direct_url_helpers.py | 87 + .../pip/_internal/utils/distutils_args.py | 42 + .../pip/_internal/utils/egg_link.py | 75 + .../pip/_internal/utils/encoding.py | 36 + .../pip/_internal/utils/entrypoints.py | 27 + .../pip/_internal/utils/filesystem.py | 182 + .../pip/_internal/utils/filetypes.py | 27 + .../pip/_internal/utils/glibc.py | 88 + .../pip/_internal/utils/hashes.py | 144 + .../_internal/utils/inject_securetransport.py | 35 + .../pip/_internal/utils/logging.py | 358 + .../site-packages/pip/_internal/utils/misc.py | 689 ++ .../pip/_internal/utils/models.py | 39 + .../pip/_internal/utils/packaging.py | 84 + .../pip/_internal/utils/parallel.py | 103 + .../pip/_internal/utils/pkg_resources.py | 33 + .../pip/_internal/utils/setuptools_build.py | 167 + .../pip/_internal/utils/subprocess.py | 289 + .../pip/_internal/utils/temp_dir.py | 246 + .../pip/_internal/utils/unpacking.py | 258 + .../site-packages/pip/_internal/utils/urls.py | 62 + .../pip/_internal/utils/virtualenv.py | 104 + .../pip/_internal/utils/wheel.py | 182 + .../pip/_internal/vcs/__init__.py | 15 + .../vcs/__pycache__/__init__.cpython-36.pyc | Bin 0 -> 465 bytes .../vcs/__pycache__/bazaar.cpython-36.pyc | Bin 0 -> 3202 bytes .../vcs/__pycache__/git.cpython-36.pyc | Bin 0 -> 12328 bytes .../vcs/__pycache__/mercurial.cpython-36.pyc | Bin 0 -> 4802 bytes .../vcs/__pycache__/subversion.cpython-36.pyc | Bin 0 -> 8358 bytes .../__pycache__/versioncontrol.cpython-36.pyc | Bin 0 -> 20701 bytes .../site-packages/pip/_internal/vcs/bazaar.py | 93 + .../site-packages/pip/_internal/vcs/git.py | 513 + .../pip/_internal/vcs/mercurial.py | 153 + .../pip/_internal/vcs/subversion.py | 318 + .../pip/_internal/vcs/versioncontrol.py | 693 ++ .../pip/_internal/wheel_builder.py | 375 + .../site-packages/pip/_vendor/__init__.py | 111 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 2853 bytes .../_vendor/__pycache__/distro.cpython-36.pyc | Bin 0 -> 37998 bytes .../__pycache__/pyparsing.cpython-36.pyc | Bin 0 -> 243100 bytes .../_vendor/__pycache__/six.cpython-36.pyc | Bin 0 -> 27528 bytes .../pip/_vendor/cachecontrol/__init__.py | 11 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 500 bytes .../__pycache__/_cmd.cpython-36.pyc | Bin 0 -> 1503 bytes .../__pycache__/adapter.cpython-36.pyc | Bin 0 -> 2999 bytes .../__pycache__/cache.cpython-36.pyc | Bin 0 -> 1716 bytes .../__pycache__/compat.cpython-36.pyc | Bin 0 -> 707 bytes .../__pycache__/controller.cpython-36.pyc | Bin 0 -> 7741 bytes .../__pycache__/filewrapper.cpython-36.pyc | Bin 0 -> 2104 bytes .../__pycache__/heuristics.cpython-36.pyc | Bin 0 -> 4634 bytes .../__pycache__/serialize.cpython-36.pyc | Bin 0 -> 4177 bytes .../__pycache__/wrapper.cpython-36.pyc | Bin 0 -> 616 bytes .../pip/_vendor/cachecontrol/_cmd.py | 57 + .../pip/_vendor/cachecontrol/adapter.py | 133 + .../pip/_vendor/cachecontrol/cache.py | 39 + .../_vendor/cachecontrol/caches/__init__.py | 2 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 244 bytes .../__pycache__/file_cache.cpython-36.pyc | Bin 0 -> 3154 bytes .../__pycache__/redis_cache.cpython-36.pyc | Bin 0 -> 1500 bytes .../_vendor/cachecontrol/caches/file_cache.py | 146 + .../cachecontrol/caches/redis_cache.py | 33 + .../pip/_vendor/cachecontrol/compat.py | 29 + .../pip/_vendor/cachecontrol/controller.py | 376 + .../pip/_vendor/cachecontrol/filewrapper.py | 80 + .../pip/_vendor/cachecontrol/heuristics.py | 135 + .../pip/_vendor/cachecontrol/serialize.py | 188 + .../pip/_vendor/cachecontrol/wrapper.py | 29 + .../pip/_vendor/certifi/__init__.py | 3 + .../pip/_vendor/certifi/__main__.py | 12 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 226 bytes .../__pycache__/__main__.cpython-36.pyc | Bin 0 -> 403 bytes .../certifi/__pycache__/core.cpython-36.pyc | Bin 0 -> 1466 bytes .../pip/_vendor/certifi/cacert.pem | 4257 +++++++++ .../site-packages/pip/_vendor/certifi/core.py | 76 + .../pip/_vendor/chardet/__init__.py | 83 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 1876 bytes .../__pycache__/big5freq.cpython-36.pyc | Bin 0 -> 54681 bytes .../__pycache__/big5prober.cpython-36.pyc | Bin 0 -> 1070 bytes .../chardistribution.cpython-36.pyc | Bin 0 -> 6266 bytes .../charsetgroupprober.cpython-36.pyc | Bin 0 -> 2187 bytes .../__pycache__/charsetprober.cpython-36.pyc | Bin 0 -> 3403 bytes .../codingstatemachine.cpython-36.pyc | Bin 0 -> 2834 bytes .../chardet/__pycache__/compat.cpython-36.pyc | Bin 0 -> 354 bytes .../__pycache__/cp949prober.cpython-36.pyc | Bin 0 -> 1077 bytes .../chardet/__pycache__/enums.cpython-36.pyc | Bin 0 -> 2568 bytes .../__pycache__/escprober.cpython-36.pyc | Bin 0 -> 2559 bytes .../chardet/__pycache__/escsm.cpython-36.pyc | Bin 0 -> 7316 bytes .../__pycache__/eucjpprober.cpython-36.pyc | Bin 0 -> 2363 bytes .../__pycache__/euckrfreq.cpython-36.pyc | Bin 0 -> 24067 bytes .../__pycache__/euckrprober.cpython-36.pyc | Bin 0 -> 1078 bytes .../__pycache__/euctwfreq.cpython-36.pyc | Bin 0 -> 54690 bytes .../__pycache__/euctwprober.cpython-36.pyc | Bin 0 -> 1078 bytes .../__pycache__/gb2312freq.cpython-36.pyc | Bin 0 -> 38332 bytes .../__pycache__/gb2312prober.cpython-36.pyc | Bin 0 -> 1086 bytes .../__pycache__/hebrewprober.cpython-36.pyc | Bin 0 -> 2920 bytes .../__pycache__/jisfreq.cpython-36.pyc | Bin 0 -> 44476 bytes .../chardet/__pycache__/jpcntx.cpython-36.pyc | Bin 0 -> 38615 bytes .../langbulgarianmodel.cpython-36.pyc | Bin 0 -> 21482 bytes .../__pycache__/langgreekmodel.cpython-36.pyc | Bin 0 -> 20170 bytes .../langhebrewmodel.cpython-36.pyc | Bin 0 -> 20238 bytes .../langhungarianmodel.cpython-36.pyc | Bin 0 -> 21427 bytes .../langrussianmodel.cpython-36.pyc | Bin 0 -> 25982 bytes .../__pycache__/langthaimodel.cpython-36.pyc | Bin 0 -> 20414 bytes .../langturkishmodel.cpython-36.pyc | Bin 0 -> 20254 bytes .../__pycache__/latin1prober.cpython-36.pyc | Bin 0 -> 2891 bytes .../mbcharsetprober.cpython-36.pyc | Bin 0 -> 2182 bytes .../mbcsgroupprober.cpython-36.pyc | Bin 0 -> 1073 bytes .../chardet/__pycache__/mbcssm.cpython-36.pyc | Bin 0 -> 17526 bytes .../sbcharsetprober.cpython-36.pyc | Bin 0 -> 3047 bytes .../sbcsgroupprober.cpython-36.pyc | Bin 0 -> 1636 bytes .../__pycache__/sjisprober.cpython-36.pyc | Bin 0 -> 2389 bytes .../universaldetector.cpython-36.pyc | Bin 0 -> 5784 bytes .../__pycache__/utf8prober.cpython-36.pyc | Bin 0 -> 1920 bytes .../__pycache__/version.cpython-36.pyc | Bin 0 -> 389 bytes .../pip/_vendor/chardet/big5freq.py | 386 + .../pip/_vendor/chardet/big5prober.py | 47 + .../pip/_vendor/chardet/chardistribution.py | 233 + .../pip/_vendor/chardet/charsetgroupprober.py | 107 + .../pip/_vendor/chardet/charsetprober.py | 145 + .../pip/_vendor/chardet/cli/__init__.py | 1 + .../cli/__pycache__/__init__.cpython-36.pyc | Bin 0 -> 146 bytes .../cli/__pycache__/chardetect.cpython-36.pyc | Bin 0 -> 3065 bytes .../pip/_vendor/chardet/cli/chardetect.py | 84 + .../pip/_vendor/chardet/codingstatemachine.py | 88 + .../pip/_vendor/chardet/compat.py | 36 + .../pip/_vendor/chardet/cp949prober.py | 49 + .../pip/_vendor/chardet/enums.py | 76 + .../pip/_vendor/chardet/escprober.py | 101 + .../pip/_vendor/chardet/escsm.py | 246 + .../pip/_vendor/chardet/eucjpprober.py | 92 + .../pip/_vendor/chardet/euckrfreq.py | 195 + .../pip/_vendor/chardet/euckrprober.py | 47 + .../pip/_vendor/chardet/euctwfreq.py | 387 + .../pip/_vendor/chardet/euctwprober.py | 46 + .../pip/_vendor/chardet/gb2312freq.py | 283 + .../pip/_vendor/chardet/gb2312prober.py | 46 + .../pip/_vendor/chardet/hebrewprober.py | 292 + .../pip/_vendor/chardet/jisfreq.py | 325 + .../pip/_vendor/chardet/jpcntx.py | 233 + .../pip/_vendor/chardet/langbulgarianmodel.py | 4650 +++++++++ .../pip/_vendor/chardet/langgreekmodel.py | 4398 +++++++++ .../pip/_vendor/chardet/langhebrewmodel.py | 4383 +++++++++ .../pip/_vendor/chardet/langhungarianmodel.py | 4650 +++++++++ .../pip/_vendor/chardet/langrussianmodel.py | 5718 +++++++++++ .../pip/_vendor/chardet/langthaimodel.py | 4383 +++++++++ .../pip/_vendor/chardet/langturkishmodel.py | 4383 +++++++++ .../pip/_vendor/chardet/latin1prober.py | 145 + .../pip/_vendor/chardet/mbcharsetprober.py | 91 + .../pip/_vendor/chardet/mbcsgroupprober.py | 54 + .../pip/_vendor/chardet/mbcssm.py | 572 ++ .../pip/_vendor/chardet/metadata/__init__.py | 0 .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 151 bytes .../__pycache__/languages.cpython-36.pyc | Bin 0 -> 7769 bytes .../pip/_vendor/chardet/metadata/languages.py | 310 + .../pip/_vendor/chardet/sbcharsetprober.py | 145 + .../pip/_vendor/chardet/sbcsgroupprober.py | 83 + .../pip/_vendor/chardet/sjisprober.py | 92 + .../pip/_vendor/chardet/universaldetector.py | 286 + .../pip/_vendor/chardet/utf8prober.py | 82 + .../pip/_vendor/chardet/version.py | 9 + .../pip/_vendor/colorama/__init__.py | 6 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 394 bytes .../colorama/__pycache__/ansi.cpython-36.pyc | Bin 0 -> 3292 bytes .../__pycache__/ansitowin32.cpython-36.pyc | Bin 0 -> 7602 bytes .../__pycache__/initialise.cpython-36.pyc | Bin 0 -> 1615 bytes .../colorama/__pycache__/win32.cpython-36.pyc | Bin 0 -> 3817 bytes .../__pycache__/winterm.cpython-36.pyc | Bin 0 -> 4555 bytes .../pip/_vendor/colorama/ansi.py | 102 + .../pip/_vendor/colorama/ansitowin32.py | 258 + .../pip/_vendor/colorama/initialise.py | 80 + .../pip/_vendor/colorama/win32.py | 152 + .../pip/_vendor/colorama/winterm.py | 169 + .../pip/_vendor/distlib/__init__.py | 23 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 992 bytes .../distlib/__pycache__/compat.cpython-36.pyc | Bin 0 -> 31700 bytes .../__pycache__/database.cpython-36.pyc | Bin 0 -> 42608 bytes .../distlib/__pycache__/index.cpython-36.pyc | Bin 0 -> 17104 bytes .../__pycache__/locators.cpython-36.pyc | Bin 0 -> 38666 bytes .../__pycache__/manifest.cpython-36.pyc | Bin 0 -> 10311 bytes .../__pycache__/markers.cpython-36.pyc | Bin 0 -> 4967 bytes .../__pycache__/metadata.cpython-36.pyc | Bin 0 -> 27295 bytes .../__pycache__/resources.cpython-36.pyc | Bin 0 -> 10847 bytes .../__pycache__/scripts.cpython-36.pyc | Bin 0 -> 11088 bytes .../distlib/__pycache__/util.cpython-36.pyc | Bin 0 -> 52451 bytes .../__pycache__/version.cpython-36.pyc | Bin 0 -> 20698 bytes .../distlib/__pycache__/wheel.cpython-36.pyc | Bin 0 -> 26679 bytes .../pip/_vendor/distlib/_backport/__init__.py | 6 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 434 bytes .../_backport/__pycache__/misc.cpython-36.pyc | Bin 0 -> 1031 bytes .../__pycache__/shutil.cpython-36.pyc | Bin 0 -> 21450 bytes .../__pycache__/sysconfig.cpython-36.pyc | Bin 0 -> 16016 bytes .../__pycache__/tarfile.cpython-36.pyc | Bin 0 -> 62998 bytes .../pip/_vendor/distlib/_backport/misc.py | 41 + .../pip/_vendor/distlib/_backport/shutil.py | 764 ++ .../_vendor/distlib/_backport/sysconfig.cfg | 84 + .../_vendor/distlib/_backport/sysconfig.py | 786 ++ .../pip/_vendor/distlib/_backport/tarfile.py | 2607 +++++ .../pip/_vendor/distlib/compat.py | 1122 +++ .../pip/_vendor/distlib/database.py | 1339 +++ .../pip/_vendor/distlib/index.py | 509 + .../pip/_vendor/distlib/locators.py | 1300 +++ .../pip/_vendor/distlib/manifest.py | 393 + .../pip/_vendor/distlib/markers.py | 147 + .../pip/_vendor/distlib/metadata.py | 1058 +++ .../pip/_vendor/distlib/resources.py | 358 + .../pip/_vendor/distlib/scripts.py | 429 + .../site-packages/pip/_vendor/distlib/t32.exe | Bin 0 -> 96768 bytes .../pip/_vendor/distlib/t64-arm.exe | Bin 0 -> 180736 bytes .../site-packages/pip/_vendor/distlib/t64.exe | Bin 0 -> 105984 bytes .../site-packages/pip/_vendor/distlib/util.py | 1969 ++++ .../pip/_vendor/distlib/version.py | 739 ++ .../site-packages/pip/_vendor/distlib/w32.exe | Bin 0 -> 90112 bytes .../pip/_vendor/distlib/w64-arm.exe | Bin 0 -> 166400 bytes .../site-packages/pip/_vendor/distlib/w64.exe | Bin 0 -> 99840 bytes .../pip/_vendor/distlib/wheel.py | 1053 ++ .../site-packages/pip/_vendor/distro.py | 1386 +++ .../pip/_vendor/html5lib/__init__.py | 35 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 1261 bytes .../__pycache__/_ihatexml.cpython-36.pyc | Bin 0 -> 13819 bytes .../__pycache__/_inputstream.cpython-36.pyc | Bin 0 -> 22606 bytes .../__pycache__/_tokenizer.cpython-36.pyc | Bin 0 -> 42370 bytes .../__pycache__/_utils.cpython-36.pyc | Bin 0 -> 4668 bytes .../__pycache__/constants.cpython-36.pyc | Bin 0 -> 66353 bytes .../__pycache__/html5parser.cpython-36.pyc | Bin 0 -> 95619 bytes .../__pycache__/serializer.cpython-36.pyc | Bin 0 -> 10882 bytes .../pip/_vendor/html5lib/_ihatexml.py | 289 + .../pip/_vendor/html5lib/_inputstream.py | 918 ++ .../pip/_vendor/html5lib/_tokenizer.py | 1735 ++++ .../pip/_vendor/html5lib/_trie/__init__.py | 5 + .../_trie/__pycache__/__init__.cpython-36.pyc | Bin 0 -> 303 bytes .../_trie/__pycache__/_base.cpython-36.pyc | Bin 0 -> 1533 bytes .../_trie/__pycache__/py.cpython-36.pyc | Bin 0 -> 2183 bytes .../pip/_vendor/html5lib/_trie/_base.py | 40 + .../pip/_vendor/html5lib/_trie/py.py | 67 + .../pip/_vendor/html5lib/_utils.py | 159 + .../pip/_vendor/html5lib/constants.py | 2946 ++++++ .../pip/_vendor/html5lib/filters/__init__.py | 0 .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 151 bytes .../alphabeticalattributes.cpython-36.pyc | Bin 0 -> 1277 bytes .../filters/__pycache__/base.cpython-36.pyc | Bin 0 -> 801 bytes .../inject_meta_charset.cpython-36.pyc | Bin 0 -> 1848 bytes .../filters/__pycache__/lint.cpython-36.pyc | Bin 0 -> 2594 bytes .../__pycache__/optionaltags.cpython-36.pyc | Bin 0 -> 3055 bytes .../__pycache__/sanitizer.cpython-36.pyc | Bin 0 -> 19989 bytes .../__pycache__/whitespace.cpython-36.pyc | Bin 0 -> 1309 bytes .../filters/alphabeticalattributes.py | 29 + .../pip/_vendor/html5lib/filters/base.py | 12 + .../html5lib/filters/inject_meta_charset.py | 73 + .../pip/_vendor/html5lib/filters/lint.py | 93 + .../_vendor/html5lib/filters/optionaltags.py | 207 + .../pip/_vendor/html5lib/filters/sanitizer.py | 916 ++ .../_vendor/html5lib/filters/whitespace.py | 38 + .../pip/_vendor/html5lib/html5parser.py | 2795 ++++++ .../pip/_vendor/html5lib/serializer.py | 409 + .../_vendor/html5lib/treeadapters/__init__.py | 30 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 890 bytes .../__pycache__/genshi.cpython-36.pyc | Bin 0 -> 1634 bytes .../__pycache__/sax.cpython-36.pyc | Bin 0 -> 1452 bytes .../_vendor/html5lib/treeadapters/genshi.py | 54 + .../pip/_vendor/html5lib/treeadapters/sax.py | 50 + .../_vendor/html5lib/treebuilders/__init__.py | 88 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 3271 bytes .../__pycache__/base.cpython-36.pyc | Bin 0 -> 11265 bytes .../__pycache__/dom.cpython-36.pyc | Bin 0 -> 9298 bytes .../__pycache__/etree.cpython-36.pyc | Bin 0 -> 11812 bytes .../__pycache__/etree_lxml.cpython-36.pyc | Bin 0 -> 12949 bytes .../pip/_vendor/html5lib/treebuilders/base.py | 417 + .../pip/_vendor/html5lib/treebuilders/dom.py | 239 + .../_vendor/html5lib/treebuilders/etree.py | 343 + .../html5lib/treebuilders/etree_lxml.py | 392 + .../_vendor/html5lib/treewalkers/__init__.py | 154 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 3963 bytes .../__pycache__/base.cpython-36.pyc | Bin 0 -> 6946 bytes .../__pycache__/dom.cpython-36.pyc | Bin 0 -> 1673 bytes .../__pycache__/etree.cpython-36.pyc | Bin 0 -> 3515 bytes .../__pycache__/etree_lxml.cpython-36.pyc | Bin 0 -> 6662 bytes .../__pycache__/genshi.cpython-36.pyc | Bin 0 -> 1847 bytes .../pip/_vendor/html5lib/treewalkers/base.py | 252 + .../pip/_vendor/html5lib/treewalkers/dom.py | 43 + .../pip/_vendor/html5lib/treewalkers/etree.py | 131 + .../html5lib/treewalkers/etree_lxml.py | 215 + .../_vendor/html5lib/treewalkers/genshi.py | 69 + .../pip/_vendor/idna/__init__.py | 44 + .../idna/__pycache__/__init__.cpython-36.pyc | Bin 0 -> 866 bytes .../idna/__pycache__/codec.cpython-36.pyc | Bin 0 -> 3042 bytes .../idna/__pycache__/compat.cpython-36.pyc | Bin 0 -> 623 bytes .../idna/__pycache__/core.cpython-36.pyc | Bin 0 -> 9246 bytes .../idna/__pycache__/idnadata.cpython-36.pyc | Bin 0 -> 30700 bytes .../idna/__pycache__/intranges.cpython-36.pyc | Bin 0 -> 1820 bytes .../__pycache__/package_data.cpython-36.pyc | Bin 0 -> 162 bytes .../idna/__pycache__/uts46data.cpython-36.pyc | Bin 0 -> 246390 bytes .../site-packages/pip/_vendor/idna/codec.py | 117 + .../site-packages/pip/_vendor/idna/compat.py | 16 + .../site-packages/pip/_vendor/idna/core.py | 409 + .../pip/_vendor/idna/idnadata.py | 2050 ++++ .../pip/_vendor/idna/intranges.py | 58 + .../pip/_vendor/idna/package_data.py | 2 + .../pip/_vendor/idna/uts46data.py | 8438 +++++++++++++++++ .../pip/_vendor/msgpack/__init__.py | 54 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 1343 bytes .../__pycache__/_version.cpython-36.pyc | Bin 0 -> 184 bytes .../__pycache__/exceptions.cpython-36.pyc | Bin 0 -> 1817 bytes .../msgpack/__pycache__/ext.cpython-36.pyc | Bin 0 -> 6217 bytes .../__pycache__/fallback.cpython-36.pyc | Bin 0 -> 26889 bytes .../pip/_vendor/msgpack/_version.py | 1 + .../pip/_vendor/msgpack/exceptions.py | 48 + .../site-packages/pip/_vendor/msgpack/ext.py | 193 + .../pip/_vendor/msgpack/fallback.py | 1087 +++ .../pip/_vendor/packaging/__about__.py | 26 + .../pip/_vendor/packaging/__init__.py | 25 + .../__pycache__/__about__.cpython-36.pyc | Bin 0 -> 565 bytes .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 421 bytes .../__pycache__/_manylinux.cpython-36.pyc | Bin 0 -> 7236 bytes .../__pycache__/_musllinux.cpython-36.pyc | Bin 0 -> 4546 bytes .../__pycache__/_structures.cpython-36.pyc | Bin 0 -> 3064 bytes .../__pycache__/markers.cpython-36.pyc | Bin 0 -> 9381 bytes .../__pycache__/requirements.cpython-36.pyc | Bin 0 -> 3907 bytes .../__pycache__/specifiers.cpython-36.pyc | Bin 0 -> 22271 bytes .../packaging/__pycache__/tags.cpython-36.pyc | Bin 0 -> 12248 bytes .../__pycache__/utils.cpython-36.pyc | Bin 0 -> 3537 bytes .../__pycache__/version.cpython-36.pyc | Bin 0 -> 13078 bytes .../pip/_vendor/packaging/_manylinux.py | 301 + .../pip/_vendor/packaging/_musllinux.py | 136 + .../pip/_vendor/packaging/_structures.py | 67 + .../pip/_vendor/packaging/markers.py | 304 + .../pip/_vendor/packaging/requirements.py | 146 + .../pip/_vendor/packaging/specifiers.py | 828 ++ .../pip/_vendor/packaging/tags.py | 484 + .../pip/_vendor/packaging/utils.py | 136 + .../pip/_vendor/packaging/version.py | 504 + .../pip/_vendor/pep517/__init__.py | 6 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 264 bytes .../pep517/__pycache__/build.cpython-36.pyc | Bin 0 -> 3434 bytes .../pep517/__pycache__/check.cpython-36.pyc | Bin 0 -> 4926 bytes .../__pycache__/colorlog.cpython-36.pyc | Bin 0 -> 2880 bytes .../pep517/__pycache__/compat.cpython-36.pyc | Bin 0 -> 1415 bytes .../__pycache__/dirtools.cpython-36.pyc | Bin 0 -> 1278 bytes .../__pycache__/envbuild.cpython-36.pyc | Bin 0 -> 4336 bytes .../pep517/__pycache__/meta.cpython-36.pyc | Bin 0 -> 2763 bytes .../__pycache__/wrappers.cpython-36.pyc | Bin 0 -> 12377 bytes .../site-packages/pip/_vendor/pep517/build.py | 127 + .../site-packages/pip/_vendor/pep517/check.py | 207 + .../pip/_vendor/pep517/colorlog.py | 115 + .../pip/_vendor/pep517/compat.py | 51 + .../pip/_vendor/pep517/dirtools.py | 44 + .../pip/_vendor/pep517/envbuild.py | 171 + .../pip/_vendor/pep517/in_process/__init__.py | 17 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 870 bytes .../__pycache__/_in_process.cpython-36.pyc | Bin 0 -> 10165 bytes .../_vendor/pep517/in_process/_in_process.py | 363 + .../site-packages/pip/_vendor/pep517/meta.py | 92 + .../pip/_vendor/pep517/wrappers.py | 375 + .../pip/_vendor/pkg_resources/__init__.py | 3296 +++++++ .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 99732 bytes .../__pycache__/py31compat.cpython-36.pyc | Bin 0 -> 610 bytes .../pip/_vendor/pkg_resources/py31compat.py | 23 + .../pip/_vendor/platformdirs/__init__.py | 329 + .../pip/_vendor/platformdirs/__main__.py | 44 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 11284 bytes .../__pycache__/__main__.cpython-36.pyc | Bin 0 -> 1194 bytes .../__pycache__/android.cpython-36.pyc | Bin 0 -> 4313 bytes .../__pycache__/api.cpython-36.pyc | Bin 0 -> 5204 bytes .../__pycache__/macos.cpython-36.pyc | Bin 0 -> 3255 bytes .../__pycache__/unix.cpython-36.pyc | Bin 0 -> 7055 bytes .../__pycache__/version.cpython-36.pyc | Bin 0 -> 261 bytes .../__pycache__/windows.cpython-36.pyc | Bin 0 -> 6385 bytes .../pip/_vendor/platformdirs/android.py | 117 + .../pip/_vendor/platformdirs/api.py | 155 + .../pip/_vendor/platformdirs/macos.py | 62 + .../pip/_vendor/platformdirs/unix.py | 180 + .../pip/_vendor/platformdirs/version.py | 4 + .../pip/_vendor/platformdirs/windows.py | 180 + .../pip/_vendor/progress/__init__.py | 189 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 5537 bytes .../progress/__pycache__/bar.cpython-36.pyc | Bin 0 -> 2800 bytes .../__pycache__/colors.cpython-36.pyc | Bin 0 -> 1455 bytes .../__pycache__/counter.cpython-36.pyc | Bin 0 -> 1650 bytes .../__pycache__/spinner.cpython-36.pyc | Bin 0 -> 1459 bytes .../site-packages/pip/_vendor/progress/bar.py | 93 + .../pip/_vendor/progress/colors.py | 79 + .../pip/_vendor/progress/counter.py | 47 + .../pip/_vendor/progress/spinner.py | 45 + .../site-packages/pip/_vendor/pyparsing.py | 7107 ++++++++++++++ .../pip/_vendor/requests/__init__.py | 154 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 3968 bytes .../__pycache__/__version__.cpython-36.pyc | Bin 0 -> 506 bytes .../_internal_utils.cpython-36.pyc | Bin 0 -> 1259 bytes .../__pycache__/adapters.cpython-36.pyc | Bin 0 -> 16850 bytes .../requests/__pycache__/api.cpython-36.pyc | Bin 0 -> 6638 bytes .../requests/__pycache__/auth.cpython-36.pyc | Bin 0 -> 8303 bytes .../requests/__pycache__/certs.cpython-36.pyc | Bin 0 -> 584 bytes .../__pycache__/compat.cpython-36.pyc | Bin 0 -> 1563 bytes .../__pycache__/cookies.cpython-36.pyc | Bin 0 -> 18738 bytes .../__pycache__/exceptions.cpython-36.pyc | Bin 0 -> 5628 bytes .../requests/__pycache__/help.cpython-36.pyc | Bin 0 -> 2816 bytes .../requests/__pycache__/hooks.cpython-36.pyc | Bin 0 -> 931 bytes .../__pycache__/models.cpython-36.pyc | Bin 0 -> 24733 bytes .../__pycache__/packages.cpython-36.pyc | Bin 0 -> 476 bytes .../__pycache__/sessions.cpython-36.pyc | Bin 0 -> 19772 bytes .../__pycache__/status_codes.cpython-36.pyc | Bin 0 -> 4788 bytes .../__pycache__/structures.cpython-36.pyc | Bin 0 -> 4360 bytes .../requests/__pycache__/utils.cpython-36.pyc | Bin 0 -> 23171 bytes .../pip/_vendor/requests/__version__.py | 14 + .../pip/_vendor/requests/_internal_utils.py | 42 + .../pip/_vendor/requests/adapters.py | 533 ++ .../site-packages/pip/_vendor/requests/api.py | 159 + .../pip/_vendor/requests/auth.py | 305 + .../pip/_vendor/requests/certs.py | 18 + .../pip/_vendor/requests/compat.py | 76 + .../pip/_vendor/requests/cookies.py | 549 ++ .../pip/_vendor/requests/exceptions.py | 127 + .../pip/_vendor/requests/help.py | 132 + .../pip/_vendor/requests/hooks.py | 34 + .../pip/_vendor/requests/models.py | 966 ++ .../pip/_vendor/requests/packages.py | 16 + .../pip/_vendor/requests/sessions.py | 781 ++ .../pip/_vendor/requests/status_codes.py | 123 + .../pip/_vendor/requests/structures.py | 105 + .../pip/_vendor/requests/utils.py | 1013 ++ .../pip/_vendor/resolvelib/__init__.py | 26 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 589 bytes .../__pycache__/providers.cpython-36.pyc | Bin 0 -> 6630 bytes .../__pycache__/reporters.cpython-36.pyc | Bin 0 -> 2214 bytes .../__pycache__/resolvers.cpython-36.pyc | Bin 0 -> 15218 bytes .../__pycache__/structs.cpython-36.pyc | Bin 0 -> 7217 bytes .../pip/_vendor/resolvelib/compat/__init__.py | 0 .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 152 bytes .../collections_abc.cpython-36.pyc | Bin 0 -> 328 bytes .../resolvelib/compat/collections_abc.py | 6 + .../pip/_vendor/resolvelib/providers.py | 133 + .../pip/_vendor/resolvelib/reporters.py | 37 + .../pip/_vendor/resolvelib/resolvers.py | 483 + .../pip/_vendor/resolvelib/structs.py | 165 + .../site-packages/pip/_vendor/six.py | 998 ++ .../pip/_vendor/tenacity/__init__.py | 517 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 16106 bytes .../__pycache__/_asyncio.cpython-36.pyc | Bin 0 -> 2518 bytes .../__pycache__/_utils.cpython-36.pyc | Bin 0 -> 1180 bytes .../tenacity/__pycache__/after.cpython-36.pyc | Bin 0 -> 1148 bytes .../__pycache__/before.cpython-36.pyc | Bin 0 -> 1045 bytes .../__pycache__/before_sleep.cpython-36.pyc | Bin 0 -> 1322 bytes .../tenacity/__pycache__/nap.cpython-36.pyc | Bin 0 -> 1127 bytes .../tenacity/__pycache__/retry.cpython-36.pyc | Bin 0 -> 8983 bytes .../tenacity/__pycache__/stop.cpython-36.pyc | Bin 0 -> 4240 bytes .../__pycache__/tornadoweb.cpython-36.pyc | Bin 0 -> 1683 bytes .../tenacity/__pycache__/wait.cpython-36.pyc | Bin 0 -> 7971 bytes .../pip/_vendor/tenacity/_asyncio.py | 92 + .../pip/_vendor/tenacity/_utils.py | 68 + .../pip/_vendor/tenacity/after.py | 46 + .../pip/_vendor/tenacity/before.py | 41 + .../pip/_vendor/tenacity/before_sleep.py | 58 + .../site-packages/pip/_vendor/tenacity/nap.py | 43 + .../pip/_vendor/tenacity/retry.py | 213 + .../pip/_vendor/tenacity/stop.py | 96 + .../pip/_vendor/tenacity/tornadoweb.py | 59 + .../pip/_vendor/tenacity/wait.py | 191 + .../pip/_vendor/tomli/__init__.py | 6 + .../tomli/__pycache__/__init__.cpython-36.pyc | Bin 0 -> 345 bytes .../tomli/__pycache__/_parser.cpython-36.pyc | Bin 0 -> 16517 bytes .../tomli/__pycache__/_re.cpython-36.pyc | Bin 0 -> 2595 bytes .../pip/_vendor/tomli/_parser.py | 703 ++ .../site-packages/pip/_vendor/tomli/_re.py | 83 + .../pip/_vendor/urllib3/__init__.py | 85 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 2218 bytes .../__pycache__/_collections.cpython-36.pyc | Bin 0 -> 10670 bytes .../__pycache__/_version.cpython-36.pyc | Bin 0 -> 164 bytes .../__pycache__/connection.cpython-36.pyc | Bin 0 -> 13747 bytes .../__pycache__/connectionpool.cpython-36.pyc | Bin 0 -> 24641 bytes .../__pycache__/exceptions.cpython-36.pyc | Bin 0 -> 12003 bytes .../urllib3/__pycache__/fields.cpython-36.pyc | Bin 0 -> 8103 bytes .../__pycache__/filepost.cpython-36.pyc | Bin 0 -> 2711 bytes .../__pycache__/poolmanager.cpython-36.pyc | Bin 0 -> 15260 bytes .../__pycache__/request.cpython-36.pyc | Bin 0 -> 5511 bytes .../__pycache__/response.cpython-36.pyc | Bin 0 -> 20552 bytes .../pip/_vendor/urllib3/_collections.py | 337 + .../pip/_vendor/urllib3/_version.py | 2 + .../pip/_vendor/urllib3/connection.py | 569 ++ .../pip/_vendor/urllib3/connectionpool.py | 1078 +++ .../pip/_vendor/urllib3/contrib/__init__.py | 0 .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 150 bytes .../_appengine_environ.cpython-36.pyc | Bin 0 -> 1359 bytes .../__pycache__/appengine.cpython-36.pyc | Bin 0 -> 8131 bytes .../__pycache__/ntlmpool.cpython-36.pyc | Bin 0 -> 3556 bytes .../__pycache__/pyopenssl.cpython-36.pyc | Bin 0 -> 15443 bytes .../securetransport.cpython-36.pyc | Bin 0 -> 21446 bytes .../contrib/__pycache__/socks.cpython-36.pyc | Bin 0 -> 5520 bytes .../urllib3/contrib/_appengine_environ.py | 36 + .../contrib/_securetransport/__init__.py | 0 .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 167 bytes .../__pycache__/bindings.cpython-36.pyc | Bin 0 -> 10746 bytes .../__pycache__/low_level.cpython-36.pyc | Bin 0 -> 8982 bytes .../contrib/_securetransport/bindings.py | 519 + .../contrib/_securetransport/low_level.py | 397 + .../pip/_vendor/urllib3/contrib/appengine.py | 314 + .../pip/_vendor/urllib3/contrib/ntlmpool.py | 130 + .../pip/_vendor/urllib3/contrib/pyopenssl.py | 511 + .../urllib3/contrib/securetransport.py | 922 ++ .../pip/_vendor/urllib3/contrib/socks.py | 216 + .../pip/_vendor/urllib3/exceptions.py | 323 + .../pip/_vendor/urllib3/fields.py | 274 + .../pip/_vendor/urllib3/filepost.py | 98 + .../pip/_vendor/urllib3/packages/__init__.py | 5 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 276 bytes .../packages/__pycache__/six.cpython-36.pyc | Bin 0 -> 27581 bytes .../urllib3/packages/backports/__init__.py | 0 .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 161 bytes .../__pycache__/makefile.cpython-36.pyc | Bin 0 -> 1251 bytes .../urllib3/packages/backports/makefile.py | 51 + .../pip/_vendor/urllib3/packages/six.py | 1077 +++ .../packages/ssl_match_hostname/__init__.py | 24 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 564 bytes .../_implementation.cpython-36.pyc | Bin 0 -> 3244 bytes .../ssl_match_hostname/_implementation.py | 160 + .../pip/_vendor/urllib3/poolmanager.py | 536 ++ .../pip/_vendor/urllib3/request.py | 170 + .../pip/_vendor/urllib3/response.py | 821 ++ .../pip/_vendor/urllib3/util/__init__.py | 49 + .../util/__pycache__/__init__.cpython-36.pyc | Bin 0 -> 1226 bytes .../__pycache__/connection.cpython-36.pyc | Bin 0 -> 3386 bytes .../util/__pycache__/proxy.cpython-36.pyc | Bin 0 -> 1274 bytes .../util/__pycache__/queue.cpython-36.pyc | Bin 0 -> 991 bytes .../util/__pycache__/request.cpython-36.pyc | Bin 0 -> 3404 bytes .../util/__pycache__/response.cpython-36.pyc | Bin 0 -> 2278 bytes .../util/__pycache__/retry.cpython-36.pyc | Bin 0 -> 16285 bytes .../util/__pycache__/ssl_.cpython-36.pyc | Bin 0 -> 11274 bytes .../__pycache__/ssltransport.cpython-36.pyc | Bin 0 -> 7293 bytes .../util/__pycache__/timeout.cpython-36.pyc | Bin 0 -> 8850 bytes .../util/__pycache__/url.cpython-36.pyc | Bin 0 -> 10631 bytes .../util/__pycache__/wait.cpython-36.pyc | Bin 0 -> 3099 bytes .../pip/_vendor/urllib3/util/connection.py | 150 + .../pip/_vendor/urllib3/util/proxy.py | 57 + .../pip/_vendor/urllib3/util/queue.py | 22 + .../pip/_vendor/urllib3/util/request.py | 143 + .../pip/_vendor/urllib3/util/response.py | 107 + .../pip/_vendor/urllib3/util/retry.py | 602 ++ .../pip/_vendor/urllib3/util/ssl_.py | 495 + .../pip/_vendor/urllib3/util/ssltransport.py | 221 + .../pip/_vendor/urllib3/util/timeout.py | 268 + .../pip/_vendor/urllib3/util/url.py | 432 + .../pip/_vendor/urllib3/util/wait.py | 153 + .../site-packages/pip/_vendor/vendor.txt | 22 + .../pip/_vendor/webencodings/__init__.py | 342 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 9628 bytes .../__pycache__/labels.cpython-36.pyc | Bin 0 -> 4040 bytes .../__pycache__/mklabels.cpython-36.pyc | Bin 0 -> 1862 bytes .../__pycache__/tests.cpython-36.pyc | Bin 0 -> 5018 bytes .../__pycache__/x_user_defined.cpython-36.pyc | Bin 0 -> 2615 bytes .../pip/_vendor/webencodings/labels.py | 231 + .../pip/_vendor/webencodings/mklabels.py | 59 + .../pip/_vendor/webencodings/tests.py | 153 + .../_vendor/webencodings/x_user_defined.py | 325 + .../lib/python3.6/site-packages/pip/py.typed | 4 + .../site-packages/pkg_resources/__init__.py | 3132 ++++++ .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 95710 bytes .../__pycache__/py31compat.cpython-36.pyc | Bin 0 -> 654 bytes .../pkg_resources/_vendor/__init__.py | 0 .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 151 bytes .../__pycache__/appdirs.cpython-36.pyc | Bin 0 -> 18577 bytes .../__pycache__/pyparsing.cpython-36.pyc | Bin 0 -> 201073 bytes .../_vendor/__pycache__/six.cpython-36.pyc | Bin 0 -> 24448 bytes .../pkg_resources/_vendor/appdirs.py | 552 ++ .../_vendor/packaging/__about__.py | 21 + .../_vendor/packaging/__init__.py | 14 + .../__pycache__/__about__.cpython-36.pyc | Bin 0 -> 687 bytes .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 525 bytes .../__pycache__/_compat.cpython-36.pyc | Bin 0 -> 972 bytes .../__pycache__/_structures.cpython-36.pyc | Bin 0 -> 2829 bytes .../__pycache__/markers.cpython-36.pyc | Bin 0 -> 8852 bytes .../__pycache__/requirements.cpython-36.pyc | Bin 0 -> 3848 bytes .../__pycache__/specifiers.cpython-36.pyc | Bin 0 -> 19791 bytes .../__pycache__/utils.cpython-36.pyc | Bin 0 -> 456 bytes .../__pycache__/version.cpython-36.pyc | Bin 0 -> 10566 bytes .../_vendor/packaging/_compat.py | 30 + .../_vendor/packaging/_structures.py | 68 + .../_vendor/packaging/markers.py | 301 + .../_vendor/packaging/requirements.py | 127 + .../_vendor/packaging/specifiers.py | 774 ++ .../pkg_resources/_vendor/packaging/utils.py | 14 + .../_vendor/packaging/version.py | 393 + .../pkg_resources/_vendor/pyparsing.py | 5696 +++++++++++ .../pkg_resources/_vendor/six.py | 868 ++ .../pkg_resources/extern/__init__.py | 73 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 2382 bytes .../site-packages/pkg_resources/py31compat.py | 22 + .../DESCRIPTION.rst | 39 + .../setuptools-39.2.0.dist-info/INSTALLER | 1 + .../setuptools-39.2.0.dist-info/LICENSE.txt | 19 + .../setuptools-39.2.0.dist-info/METADATA | 74 + .../setuptools-39.2.0.dist-info/RECORD | 182 + .../setuptools-39.2.0.dist-info/WHEEL | 6 + .../dependency_links.txt | 2 + .../entry_points.txt | 65 + .../setuptools-39.2.0.dist-info/metadata.json | 1 + .../setuptools-39.2.0.dist-info/top_level.txt | 3 + .../setuptools-39.2.0.dist-info/zip-safe | 1 + .../site-packages/setuptools/__init__.py | 180 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 6246 bytes .../__pycache__/archive_util.cpython-36.pyc | Bin 0 -> 5094 bytes .../__pycache__/build_meta.cpython-36.pyc | Bin 0 -> 5867 bytes .../__pycache__/config.cpython-36.pyc | Bin 0 -> 15018 bytes .../__pycache__/dep_util.cpython-36.pyc | Bin 0 -> 813 bytes .../__pycache__/depends.cpython-36.pyc | Bin 0 -> 5239 bytes .../__pycache__/dist.cpython-36.pyc | Bin 0 -> 36833 bytes .../__pycache__/extension.cpython-36.pyc | Bin 0 -> 1933 bytes .../__pycache__/glibc.cpython-36.pyc | Bin 0 -> 1503 bytes .../__pycache__/glob.cpython-36.pyc | Bin 0 -> 3801 bytes .../__pycache__/launch.cpython-36.pyc | Bin 0 -> 812 bytes .../__pycache__/lib2to3_ex.cpython-36.pyc | Bin 0 -> 2391 bytes .../__pycache__/monkey.cpython-36.pyc | Bin 0 -> 4630 bytes .../__pycache__/msvc.cpython-36.pyc | Bin 0 -> 34498 bytes .../__pycache__/namespaces.cpython-36.pyc | Bin 0 -> 3636 bytes .../__pycache__/package_index.cpython-36.pyc | Bin 0 -> 32212 bytes .../__pycache__/pep425tags.cpython-36.pyc | Bin 0 -> 7300 bytes .../__pycache__/py27compat.cpython-36.pyc | Bin 0 -> 769 bytes .../__pycache__/py31compat.cpython-36.pyc | Bin 0 -> 1508 bytes .../__pycache__/py33compat.cpython-36.pyc | Bin 0 -> 1361 bytes .../__pycache__/py36compat.cpython-36.pyc | Bin 0 -> 2165 bytes .../__pycache__/sandbox.cpython-36.pyc | Bin 0 -> 15681 bytes .../__pycache__/site-patch.cpython-36.pyc | Bin 0 -> 1464 bytes .../__pycache__/ssl_support.cpython-36.pyc | Bin 0 -> 6744 bytes .../__pycache__/unicode_utils.cpython-36.pyc | Bin 0 -> 1127 bytes .../__pycache__/version.cpython-36.pyc | Bin 0 -> 286 bytes .../__pycache__/wheel.cpython-36.pyc | Bin 0 -> 6208 bytes .../windows_support.cpython-36.pyc | Bin 0 -> 969 bytes .../setuptools/_vendor/__init__.py | 0 .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 148 bytes .../__pycache__/pyparsing.cpython-36.pyc | Bin 0 -> 201070 bytes .../_vendor/__pycache__/six.cpython-36.pyc | Bin 0 -> 24445 bytes .../setuptools/_vendor/packaging/__about__.py | 21 + .../setuptools/_vendor/packaging/__init__.py | 14 + .../__pycache__/__about__.cpython-36.pyc | Bin 0 -> 684 bytes .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 522 bytes .../__pycache__/_compat.cpython-36.pyc | Bin 0 -> 969 bytes .../__pycache__/_structures.cpython-36.pyc | Bin 0 -> 2826 bytes .../__pycache__/markers.cpython-36.pyc | Bin 0 -> 8846 bytes .../__pycache__/requirements.cpython-36.pyc | Bin 0 -> 3839 bytes .../__pycache__/specifiers.cpython-36.pyc | Bin 0 -> 19788 bytes .../__pycache__/utils.cpython-36.pyc | Bin 0 -> 453 bytes .../__pycache__/version.cpython-36.pyc | Bin 0 -> 10563 bytes .../setuptools/_vendor/packaging/_compat.py | 30 + .../_vendor/packaging/_structures.py | 68 + .../setuptools/_vendor/packaging/markers.py | 301 + .../_vendor/packaging/requirements.py | 127 + .../_vendor/packaging/specifiers.py | 774 ++ .../setuptools/_vendor/packaging/utils.py | 14 + .../setuptools/_vendor/packaging/version.py | 393 + .../setuptools/_vendor/pyparsing.py | 5696 +++++++++++ .../site-packages/setuptools/_vendor/six.py | 868 ++ .../site-packages/setuptools/archive_util.py | 173 + .../site-packages/setuptools/build_meta.py | 172 + .../setuptools/command/__init__.py | 18 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 699 bytes .../command/__pycache__/alias.cpython-36.pyc | Bin 0 -> 2392 bytes .../__pycache__/bdist_egg.cpython-36.pyc | Bin 0 -> 14373 bytes .../__pycache__/bdist_rpm.cpython-36.pyc | Bin 0 -> 1735 bytes .../__pycache__/bdist_wininst.cpython-36.pyc | Bin 0 -> 936 bytes .../__pycache__/build_clib.cpython-36.pyc | Bin 0 -> 2407 bytes .../__pycache__/build_ext.cpython-36.pyc | Bin 0 -> 10006 bytes .../__pycache__/build_py.cpython-36.pyc | Bin 0 -> 8531 bytes .../__pycache__/develop.cpython-36.pyc | Bin 0 -> 6385 bytes .../__pycache__/dist_info.cpython-36.pyc | Bin 0 -> 1352 bytes .../__pycache__/easy_install.cpython-36.pyc | Bin 0 -> 64982 bytes .../__pycache__/egg_info.cpython-36.pyc | Bin 0 -> 20926 bytes .../__pycache__/install.cpython-36.pyc | Bin 0 -> 3932 bytes .../install_egg_info.cpython-36.pyc | Bin 0 -> 2397 bytes .../__pycache__/install_lib.cpython-36.pyc | Bin 0 -> 4042 bytes .../install_scripts.cpython-36.pyc | Bin 0 -> 2237 bytes .../__pycache__/py36compat.cpython-36.pyc | Bin 0 -> 4582 bytes .../__pycache__/register.cpython-36.pyc | Bin 0 -> 552 bytes .../command/__pycache__/rotate.cpython-36.pyc | Bin 0 -> 2538 bytes .../__pycache__/saveopts.cpython-36.pyc | Bin 0 -> 883 bytes .../command/__pycache__/sdist.cpython-36.pyc | Bin 0 -> 6347 bytes .../command/__pycache__/setopt.cpython-36.pyc | Bin 0 -> 4561 bytes .../command/__pycache__/test.cpython-36.pyc | Bin 0 -> 8120 bytes .../command/__pycache__/upload.cpython-36.pyc | Bin 0 -> 1350 bytes .../__pycache__/upload_docs.cpython-36.pyc | Bin 0 -> 6059 bytes .../site-packages/setuptools/command/alias.py | 80 + .../setuptools/command/bdist_egg.py | 502 + .../setuptools/command/bdist_rpm.py | 43 + .../setuptools/command/bdist_wininst.py | 21 + .../setuptools/command/build_clib.py | 98 + .../setuptools/command/build_ext.py | 331 + .../setuptools/command/build_py.py | 270 + .../setuptools/command/develop.py | 216 + .../setuptools/command/dist_info.py | 36 + .../setuptools/command/easy_install.py | 2339 +++++ .../setuptools/command/egg_info.py | 696 ++ .../setuptools/command/install.py | 125 + .../setuptools/command/install_egg_info.py | 62 + .../setuptools/command/install_lib.py | 121 + .../setuptools/command/install_scripts.py | 65 + .../setuptools/command/launcher manifest.xml | 15 + .../setuptools/command/py36compat.py | 136 + .../setuptools/command/register.py | 10 + .../setuptools/command/rotate.py | 66 + .../setuptools/command/saveopts.py | 22 + .../site-packages/setuptools/command/sdist.py | 200 + .../setuptools/command/setopt.py | 149 + .../site-packages/setuptools/command/test.py | 268 + .../setuptools/command/upload.py | 42 + .../setuptools/command/upload_docs.py | 206 + .../site-packages/setuptools/config.py | 589 ++ .../site-packages/setuptools/dep_util.py | 23 + .../site-packages/setuptools/depends.py | 186 + .../site-packages/setuptools/dist.py | 1071 +++ .../site-packages/setuptools/extension.py | 57 + .../setuptools/extern/__init__.py | 73 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 2387 bytes .../site-packages/setuptools/glibc.py | 86 + .../site-packages/setuptools/glob.py | 176 + .../site-packages/setuptools/launch.py | 35 + .../site-packages/setuptools/lib2to3_ex.py | 62 + .../site-packages/setuptools/monkey.py | 181 + .../site-packages/setuptools/msvc.py | 1302 +++ .../site-packages/setuptools/namespaces.py | 107 + .../site-packages/setuptools/package_index.py | 1119 +++ .../site-packages/setuptools/pep425tags.py | 317 + .../site-packages/setuptools/py27compat.py | 28 + .../site-packages/setuptools/py31compat.py | 41 + .../site-packages/setuptools/py33compat.py | 54 + .../site-packages/setuptools/py36compat.py | 82 + .../site-packages/setuptools/sandbox.py | 491 + .../setuptools/script (dev).tmpl | 5 + .../site-packages/setuptools/script.tmpl | 3 + .../site-packages/setuptools/site-patch.py | 74 + .../site-packages/setuptools/ssl_support.py | 260 + .../site-packages/setuptools/unicode_utils.py | 44 + .../site-packages/setuptools/version.py | 6 + .../site-packages/setuptools/wheel.py | 175 + .../setuptools/windows_support.py | 29 + .../INSTALLER | 1 + .../typing_extensions-4.0.1.dist-info/LICENSE | 254 + .../METADATA | 35 + .../typing_extensions-4.0.1.dist-info/RECORD | 7 + .../typing_extensions-4.0.1.dist-info/WHEEL | 4 + .../site-packages/typing_extensions.py | 2296 +++++ .../waitress-2.0.0.dist-info/INSTALLER | 1 + .../waitress-2.0.0.dist-info/LICENSE.txt | 44 + .../waitress-2.0.0.dist-info/METADATA | 126 + .../waitress-2.0.0.dist-info/RECORD | 40 + .../waitress-2.0.0.dist-info/WHEEL | 5 + .../waitress-2.0.0.dist-info/entry_points.txt | 6 + .../waitress-2.0.0.dist-info/top_level.txt | 1 + .../site-packages/waitress/__init__.py | 46 + .../site-packages/waitress/__main__.py | 3 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 1229 bytes .../__pycache__/__main__.cpython-36.pyc | Bin 0 -> 187 bytes .../__pycache__/adjustments.cpython-36.pyc | Bin 0 -> 9548 bytes .../__pycache__/buffers.cpython-36.pyc | Bin 0 -> 8091 bytes .../__pycache__/channel.cpython-36.pyc | Bin 0 -> 8960 bytes .../__pycache__/compat.cpython-36.pyc | Bin 0 -> 634 bytes .../__pycache__/parser.cpython-36.pyc | Bin 0 -> 7189 bytes .../__pycache__/proxy_headers.cpython-36.pyc | Bin 0 -> 6161 bytes .../__pycache__/receiver.cpython-36.pyc | Bin 0 -> 3198 bytes .../__pycache__/rfc7230.cpython-36.pyc | Bin 0 -> 722 bytes .../__pycache__/runner.cpython-36.pyc | Bin 0 -> 7788 bytes .../__pycache__/server.cpython-36.pyc | Bin 0 -> 9517 bytes .../waitress/__pycache__/task.cpython-36.pyc | Bin 0 -> 11722 bytes .../__pycache__/trigger.cpython-36.pyc | Bin 0 -> 3814 bytes .../__pycache__/utilities.cpython-36.pyc | Bin 0 -> 5818 bytes .../__pycache__/wasyncore.cpython-36.pyc | Bin 0 -> 16389 bytes .../site-packages/waitress/adjustments.py | 523 + .../site-packages/waitress/buffers.py | 308 + .../site-packages/waitress/channel.py | 487 + .../site-packages/waitress/compat.py | 29 + .../site-packages/waitress/parser.py | 439 + .../site-packages/waitress/proxy_headers.py | 330 + .../site-packages/waitress/receiver.py | 186 + .../site-packages/waitress/rfc7230.py | 50 + .../site-packages/waitress/runner.py | 299 + .../site-packages/waitress/server.py | 417 + .../python3.6/site-packages/waitress/task.py | 570 ++ .../site-packages/waitress/trigger.py | 203 + .../site-packages/waitress/utilities.py | 320 + .../site-packages/waitress/wasyncore.py | 691 ++ .../site-packages/werkzeug/__init__.py | 6 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 361 bytes .../__pycache__/_internal.cpython-36.pyc | Bin 0 -> 18319 bytes .../__pycache__/_reloader.cpython-36.pyc | Bin 0 -> 11950 bytes .../__pycache__/datastructures.cpython-36.pyc | Bin 0 -> 107799 bytes .../__pycache__/exceptions.cpython-36.pyc | Bin 0 -> 30601 bytes .../__pycache__/filesystem.cpython-36.pyc | Bin 0 -> 2076 bytes .../__pycache__/formparser.cpython-36.pyc | Bin 0 -> 13865 bytes .../werkzeug/__pycache__/http.cpython-36.pyc | Bin 0 -> 37946 bytes .../werkzeug/__pycache__/local.cpython-36.pyc | Bin 0 -> 22475 bytes .../__pycache__/routing.cpython-36.pyc | Bin 0 -> 72883 bytes .../__pycache__/security.cpython-36.pyc | Bin 0 -> 8091 bytes .../__pycache__/serving.cpython-36.pyc | Bin 0 -> 30446 bytes .../werkzeug/__pycache__/test.cpython-36.pyc | Bin 0 -> 38986 bytes .../__pycache__/testapp.cpython-36.pyc | Bin 0 -> 9600 bytes .../werkzeug/__pycache__/urls.cpython-36.pyc | Bin 0 -> 36465 bytes .../__pycache__/user_agent.cpython-36.pyc | Bin 0 -> 1771 bytes .../__pycache__/useragents.cpython-36.pyc | Bin 0 -> 7468 bytes .../werkzeug/__pycache__/utils.cpython-36.pyc | Bin 0 -> 33011 bytes .../werkzeug/__pycache__/wsgi.cpython-36.pyc | Bin 0 -> 30153 bytes .../site-packages/werkzeug/_internal.py | 626 ++ .../site-packages/werkzeug/_reloader.py | 430 + .../site-packages/werkzeug/datastructures.py | 3059 ++++++ .../site-packages/werkzeug/datastructures.pyi | 912 ++ .../site-packages/werkzeug/debug/__init__.py | 502 + .../debug/__pycache__/__init__.cpython-36.pyc | Bin 0 -> 12916 bytes .../debug/__pycache__/console.cpython-36.pyc | Bin 0 -> 7834 bytes .../debug/__pycache__/repr.cpython-36.pyc | Bin 0 -> 8828 bytes .../debug/__pycache__/tbtools.cpython-36.pyc | Bin 0 -> 18027 bytes .../site-packages/werkzeug/debug/console.py | 211 + .../site-packages/werkzeug/debug/repr.py | 284 + .../werkzeug/debug/shared/FONT_LICENSE | 96 + .../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/source.png | Bin 0 -> 818 bytes .../werkzeug/debug/shared/style.css | 163 + .../werkzeug/debug/shared/ubuntu.ttf | Bin 0 -> 70220 bytes .../site-packages/werkzeug/debug/tbtools.py | 600 ++ .../site-packages/werkzeug/exceptions.py | 943 ++ .../site-packages/werkzeug/filesystem.py | 55 + .../site-packages/werkzeug/formparser.py | 495 + .../python3.6/site-packages/werkzeug/http.py | 1388 +++ .../python3.6/site-packages/werkzeug/local.py | 677 ++ .../werkzeug/middleware/__init__.py | 22 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 706 bytes .../__pycache__/dispatcher.cpython-36.pyc | Bin 0 -> 2747 bytes .../__pycache__/http_proxy.cpython-36.pyc | Bin 0 -> 6854 bytes .../__pycache__/lint.cpython-36.pyc | Bin 0 -> 12760 bytes .../__pycache__/profiler.cpython-36.pyc | Bin 0 -> 4855 bytes .../__pycache__/proxy_fix.cpython-36.pyc | Bin 0 -> 6155 bytes .../__pycache__/shared_data.cpython-36.pyc | Bin 0 -> 9911 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 | 320 + .../python3.6/site-packages/werkzeug/py.typed | 0 .../site-packages/werkzeug/routing.py | 2341 +++++ .../site-packages/werkzeug/sansio/__init__.py | 0 .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 194 bytes .../__pycache__/multipart.cpython-36.pyc | Bin 0 -> 6469 bytes .../sansio/__pycache__/request.cpython-36.pyc | Bin 0 -> 17062 bytes .../__pycache__/response.cpython-36.pyc | Bin 0 -> 22886 bytes .../sansio/__pycache__/utils.cpython-36.pyc | Bin 0 -> 3884 bytes .../werkzeug/sansio/multipart.py | 260 + .../site-packages/werkzeug/sansio/request.py | 548 ++ .../site-packages/werkzeug/sansio/response.py | 704 ++ .../site-packages/werkzeug/sansio/utils.py | 142 + .../site-packages/werkzeug/security.py | 247 + .../site-packages/werkzeug/serving.py | 1081 +++ .../python3.6/site-packages/werkzeug/test.py | 1326 +++ .../site-packages/werkzeug/testapp.py | 240 + .../python3.6/site-packages/werkzeug/urls.py | 1211 +++ .../site-packages/werkzeug/user_agent.py | 47 + .../site-packages/werkzeug/useragents.py | 215 + .../python3.6/site-packages/werkzeug/utils.py | 1099 +++ .../werkzeug/wrappers/__init__.py | 16 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 917 bytes .../__pycache__/accept.cpython-36.pyc | Bin 0 -> 811 bytes .../wrappers/__pycache__/auth.cpython-36.pyc | Bin 0 -> 1359 bytes .../__pycache__/base_request.cpython-36.pyc | Bin 0 -> 1752 bytes .../__pycache__/base_response.cpython-36.pyc | Bin 0 -> 1763 bytes .../common_descriptors.cpython-36.pyc | Bin 0 -> 1436 bytes .../wrappers/__pycache__/cors.cpython-36.pyc | Bin 0 -> 1344 bytes .../wrappers/__pycache__/etag.cpython-36.pyc | Bin 0 -> 1344 bytes .../wrappers/__pycache__/json.cpython-36.pyc | Bin 0 -> 803 bytes .../__pycache__/request.cpython-36.pyc | Bin 0 -> 21117 bytes .../__pycache__/response.cpython-36.pyc | Bin 0 -> 29597 bytes .../__pycache__/user_agent.cpython-36.pyc | Bin 0 -> 824 bytes .../site-packages/werkzeug/wrappers/accept.py | 14 + .../site-packages/werkzeug/wrappers/auth.py | 26 + .../werkzeug/wrappers/base_request.py | 36 + .../werkzeug/wrappers/base_response.py | 36 + .../werkzeug/wrappers/common_descriptors.py | 26 + .../site-packages/werkzeug/wrappers/cors.py | 26 + .../site-packages/werkzeug/wrappers/etag.py | 26 + .../site-packages/werkzeug/wrappers/json.py | 13 + .../werkzeug/wrappers/request.py | 660 ++ .../werkzeug/wrappers/response.py | 890 ++ .../werkzeug/wrappers/user_agent.py | 14 + .../python3.6/site-packages/werkzeug/wsgi.py | 982 ++ .../zipp-3.6.0.dist-info/INSTALLER | 1 + .../zipp-3.6.0.dist-info/LICENSE | 19 + .../zipp-3.6.0.dist-info/METADATA | 58 + .../site-packages/zipp-3.6.0.dist-info/RECORD | 8 + .../site-packages/zipp-3.6.0.dist-info/WHEEL | 5 + .../zipp-3.6.0.dist-info/top_level.txt | 1 + .venv/lib/python3.6/site-packages/zipp.py | 329 + .venv/lib64 | 1 + .venv/pip-selfcheck.json | 1 + .venv/pyvenv.cfg | 3 + app.py | 11 +- 1396 files changed, 259318 insertions(+), 1 deletion(-) create mode 100644 .venv/bin/activate create mode 100644 .venv/bin/activate.csh create mode 100644 .venv/bin/activate.fish create mode 100755 .venv/bin/easy_install create mode 100755 .venv/bin/easy_install-3.6 create mode 100755 .venv/bin/flask create mode 100755 .venv/bin/pip create mode 100755 .venv/bin/pip3 create mode 100755 .venv/bin/pip3.6 create mode 120000 .venv/bin/python create mode 120000 .venv/bin/python3 create mode 100755 .venv/bin/waitress-serve create mode 100644 .venv/lib/python3.6/site-packages/Flask-2.0.2.dist-info/INSTALLER create mode 100644 .venv/lib/python3.6/site-packages/Flask-2.0.2.dist-info/LICENSE.rst create mode 100644 .venv/lib/python3.6/site-packages/Flask-2.0.2.dist-info/METADATA create mode 100644 .venv/lib/python3.6/site-packages/Flask-2.0.2.dist-info/RECORD create mode 100644 .venv/lib/python3.6/site-packages/Flask-2.0.2.dist-info/REQUESTED create mode 100644 .venv/lib/python3.6/site-packages/Flask-2.0.2.dist-info/WHEEL create mode 100644 .venv/lib/python3.6/site-packages/Flask-2.0.2.dist-info/entry_points.txt create mode 100644 .venv/lib/python3.6/site-packages/Flask-2.0.2.dist-info/top_level.txt create mode 100644 .venv/lib/python3.6/site-packages/Jinja2-3.0.3.dist-info/INSTALLER create mode 100644 .venv/lib/python3.6/site-packages/Jinja2-3.0.3.dist-info/LICENSE.rst create mode 100644 .venv/lib/python3.6/site-packages/Jinja2-3.0.3.dist-info/METADATA create mode 100644 .venv/lib/python3.6/site-packages/Jinja2-3.0.3.dist-info/RECORD create mode 100644 .venv/lib/python3.6/site-packages/Jinja2-3.0.3.dist-info/WHEEL create mode 100644 .venv/lib/python3.6/site-packages/Jinja2-3.0.3.dist-info/entry_points.txt create mode 100644 .venv/lib/python3.6/site-packages/Jinja2-3.0.3.dist-info/top_level.txt create mode 100644 .venv/lib/python3.6/site-packages/MarkupSafe-2.0.1.dist-info/INSTALLER create mode 100644 .venv/lib/python3.6/site-packages/MarkupSafe-2.0.1.dist-info/LICENSE.rst create mode 100644 .venv/lib/python3.6/site-packages/MarkupSafe-2.0.1.dist-info/METADATA create mode 100644 .venv/lib/python3.6/site-packages/MarkupSafe-2.0.1.dist-info/RECORD create mode 100644 .venv/lib/python3.6/site-packages/MarkupSafe-2.0.1.dist-info/WHEEL create mode 100644 .venv/lib/python3.6/site-packages/MarkupSafe-2.0.1.dist-info/top_level.txt create mode 100644 .venv/lib/python3.6/site-packages/Werkzeug-2.0.2.dist-info/INSTALLER create mode 100644 .venv/lib/python3.6/site-packages/Werkzeug-2.0.2.dist-info/LICENSE.rst create mode 100644 .venv/lib/python3.6/site-packages/Werkzeug-2.0.2.dist-info/METADATA create mode 100644 .venv/lib/python3.6/site-packages/Werkzeug-2.0.2.dist-info/RECORD create mode 100644 .venv/lib/python3.6/site-packages/Werkzeug-2.0.2.dist-info/WHEEL create mode 100644 .venv/lib/python3.6/site-packages/Werkzeug-2.0.2.dist-info/top_level.txt create mode 100644 .venv/lib/python3.6/site-packages/__pycache__/dataclasses.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/__pycache__/easy_install.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/__pycache__/typing_extensions.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/__pycache__/zipp.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/click-8.0.3.dist-info/INSTALLER create mode 100644 .venv/lib/python3.6/site-packages/click-8.0.3.dist-info/LICENSE.rst create mode 100644 .venv/lib/python3.6/site-packages/click-8.0.3.dist-info/METADATA create mode 100644 .venv/lib/python3.6/site-packages/click-8.0.3.dist-info/RECORD create mode 100644 .venv/lib/python3.6/site-packages/click-8.0.3.dist-info/WHEEL create mode 100644 .venv/lib/python3.6/site-packages/click-8.0.3.dist-info/top_level.txt create mode 100644 .venv/lib/python3.6/site-packages/click/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/click/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/click/__pycache__/_compat.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/click/__pycache__/_termui_impl.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/click/__pycache__/_textwrap.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/click/__pycache__/_unicodefun.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/click/__pycache__/_winconsole.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/click/__pycache__/core.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/click/__pycache__/decorators.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/click/__pycache__/exceptions.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/click/__pycache__/formatting.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/click/__pycache__/globals.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/click/__pycache__/parser.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/click/__pycache__/shell_completion.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/click/__pycache__/termui.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/click/__pycache__/testing.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/click/__pycache__/types.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/click/__pycache__/utils.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/click/_compat.py create mode 100644 .venv/lib/python3.6/site-packages/click/_termui_impl.py create mode 100644 .venv/lib/python3.6/site-packages/click/_textwrap.py create mode 100644 .venv/lib/python3.6/site-packages/click/_unicodefun.py create mode 100644 .venv/lib/python3.6/site-packages/click/_winconsole.py create mode 100644 .venv/lib/python3.6/site-packages/click/core.py create mode 100644 .venv/lib/python3.6/site-packages/click/decorators.py create mode 100644 .venv/lib/python3.6/site-packages/click/exceptions.py create mode 100644 .venv/lib/python3.6/site-packages/click/formatting.py create mode 100644 .venv/lib/python3.6/site-packages/click/globals.py create mode 100644 .venv/lib/python3.6/site-packages/click/parser.py create mode 100644 .venv/lib/python3.6/site-packages/click/py.typed create mode 100644 .venv/lib/python3.6/site-packages/click/shell_completion.py create mode 100644 .venv/lib/python3.6/site-packages/click/termui.py create mode 100644 .venv/lib/python3.6/site-packages/click/testing.py create mode 100644 .venv/lib/python3.6/site-packages/click/types.py create mode 100644 .venv/lib/python3.6/site-packages/click/utils.py create mode 100644 .venv/lib/python3.6/site-packages/dataclasses-0.8.dist-info/INSTALLER create mode 100644 .venv/lib/python3.6/site-packages/dataclasses-0.8.dist-info/LICENSE.txt create mode 100644 .venv/lib/python3.6/site-packages/dataclasses-0.8.dist-info/METADATA create mode 100644 .venv/lib/python3.6/site-packages/dataclasses-0.8.dist-info/RECORD create mode 100644 .venv/lib/python3.6/site-packages/dataclasses-0.8.dist-info/WHEEL create mode 100644 .venv/lib/python3.6/site-packages/dataclasses-0.8.dist-info/top_level.txt create mode 100644 .venv/lib/python3.6/site-packages/dataclasses.py create mode 100644 .venv/lib/python3.6/site-packages/easy_install.py create mode 100644 .venv/lib/python3.6/site-packages/flask/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/flask/__main__.py create mode 100644 .venv/lib/python3.6/site-packages/flask/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/flask/__pycache__/__main__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/flask/__pycache__/app.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/flask/__pycache__/blueprints.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/flask/__pycache__/cli.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/flask/__pycache__/config.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/flask/__pycache__/ctx.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/flask/__pycache__/debughelpers.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/flask/__pycache__/globals.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/flask/__pycache__/helpers.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/flask/__pycache__/logging.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/flask/__pycache__/scaffold.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/flask/__pycache__/sessions.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/flask/__pycache__/signals.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/flask/__pycache__/templating.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/flask/__pycache__/testing.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/flask/__pycache__/typing.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/flask/__pycache__/views.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/flask/__pycache__/wrappers.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/flask/app.py create mode 100644 .venv/lib/python3.6/site-packages/flask/blueprints.py create mode 100644 .venv/lib/python3.6/site-packages/flask/cli.py create mode 100644 .venv/lib/python3.6/site-packages/flask/config.py create mode 100644 .venv/lib/python3.6/site-packages/flask/ctx.py create mode 100644 .venv/lib/python3.6/site-packages/flask/debughelpers.py create mode 100644 .venv/lib/python3.6/site-packages/flask/globals.py create mode 100644 .venv/lib/python3.6/site-packages/flask/helpers.py create mode 100644 .venv/lib/python3.6/site-packages/flask/json/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/flask/json/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/flask/json/__pycache__/tag.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/flask/json/tag.py create mode 100644 .venv/lib/python3.6/site-packages/flask/logging.py create mode 100644 .venv/lib/python3.6/site-packages/flask/py.typed create mode 100644 .venv/lib/python3.6/site-packages/flask/scaffold.py create mode 100644 .venv/lib/python3.6/site-packages/flask/sessions.py create mode 100644 .venv/lib/python3.6/site-packages/flask/signals.py create mode 100644 .venv/lib/python3.6/site-packages/flask/templating.py create mode 100644 .venv/lib/python3.6/site-packages/flask/testing.py create mode 100644 .venv/lib/python3.6/site-packages/flask/typing.py create mode 100644 .venv/lib/python3.6/site-packages/flask/views.py create mode 100644 .venv/lib/python3.6/site-packages/flask/wrappers.py create mode 100644 .venv/lib/python3.6/site-packages/importlib_metadata-4.8.2.dist-info/INSTALLER create mode 100644 .venv/lib/python3.6/site-packages/importlib_metadata-4.8.2.dist-info/LICENSE create mode 100644 .venv/lib/python3.6/site-packages/importlib_metadata-4.8.2.dist-info/METADATA create mode 100644 .venv/lib/python3.6/site-packages/importlib_metadata-4.8.2.dist-info/RECORD create mode 100644 .venv/lib/python3.6/site-packages/importlib_metadata-4.8.2.dist-info/WHEEL create mode 100644 .venv/lib/python3.6/site-packages/importlib_metadata-4.8.2.dist-info/top_level.txt create mode 100644 .venv/lib/python3.6/site-packages/importlib_metadata/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/importlib_metadata/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/importlib_metadata/__pycache__/_adapters.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/importlib_metadata/__pycache__/_collections.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/importlib_metadata/__pycache__/_compat.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/importlib_metadata/__pycache__/_functools.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/importlib_metadata/__pycache__/_itertools.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/importlib_metadata/__pycache__/_meta.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/importlib_metadata/__pycache__/_text.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/importlib_metadata/_adapters.py create mode 100644 .venv/lib/python3.6/site-packages/importlib_metadata/_collections.py create mode 100644 .venv/lib/python3.6/site-packages/importlib_metadata/_compat.py create mode 100644 .venv/lib/python3.6/site-packages/importlib_metadata/_functools.py create mode 100644 .venv/lib/python3.6/site-packages/importlib_metadata/_itertools.py create mode 100644 .venv/lib/python3.6/site-packages/importlib_metadata/_meta.py create mode 100644 .venv/lib/python3.6/site-packages/importlib_metadata/_text.py create mode 100644 .venv/lib/python3.6/site-packages/importlib_metadata/py.typed create mode 100644 .venv/lib/python3.6/site-packages/itsdangerous-2.0.1.dist-info/INSTALLER create mode 100644 .venv/lib/python3.6/site-packages/itsdangerous-2.0.1.dist-info/LICENSE.rst create mode 100644 .venv/lib/python3.6/site-packages/itsdangerous-2.0.1.dist-info/METADATA create mode 100644 .venv/lib/python3.6/site-packages/itsdangerous-2.0.1.dist-info/RECORD create mode 100644 .venv/lib/python3.6/site-packages/itsdangerous-2.0.1.dist-info/WHEEL create mode 100644 .venv/lib/python3.6/site-packages/itsdangerous-2.0.1.dist-info/top_level.txt create mode 100644 .venv/lib/python3.6/site-packages/itsdangerous/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/itsdangerous/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/itsdangerous/__pycache__/_json.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/itsdangerous/__pycache__/encoding.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/itsdangerous/__pycache__/exc.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/itsdangerous/__pycache__/jws.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/itsdangerous/__pycache__/serializer.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/itsdangerous/__pycache__/signer.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/itsdangerous/__pycache__/timed.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/itsdangerous/__pycache__/url_safe.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/itsdangerous/_json.py create mode 100644 .venv/lib/python3.6/site-packages/itsdangerous/encoding.py create mode 100644 .venv/lib/python3.6/site-packages/itsdangerous/exc.py create mode 100644 .venv/lib/python3.6/site-packages/itsdangerous/jws.py create mode 100644 .venv/lib/python3.6/site-packages/itsdangerous/py.typed create mode 100644 .venv/lib/python3.6/site-packages/itsdangerous/serializer.py create mode 100644 .venv/lib/python3.6/site-packages/itsdangerous/signer.py create mode 100644 .venv/lib/python3.6/site-packages/itsdangerous/timed.py create mode 100644 .venv/lib/python3.6/site-packages/itsdangerous/url_safe.py create mode 100644 .venv/lib/python3.6/site-packages/jinja2/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/jinja2/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/jinja2/__pycache__/_identifier.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/jinja2/__pycache__/async_utils.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/jinja2/__pycache__/bccache.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/jinja2/__pycache__/compiler.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/jinja2/__pycache__/constants.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/jinja2/__pycache__/debug.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/jinja2/__pycache__/defaults.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/jinja2/__pycache__/environment.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/jinja2/__pycache__/exceptions.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/jinja2/__pycache__/ext.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/jinja2/__pycache__/filters.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/jinja2/__pycache__/idtracking.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/jinja2/__pycache__/lexer.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/jinja2/__pycache__/loaders.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/jinja2/__pycache__/meta.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/jinja2/__pycache__/nativetypes.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/jinja2/__pycache__/nodes.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/jinja2/__pycache__/optimizer.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/jinja2/__pycache__/parser.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/jinja2/__pycache__/runtime.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/jinja2/__pycache__/sandbox.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/jinja2/__pycache__/tests.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/jinja2/__pycache__/utils.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/jinja2/__pycache__/visitor.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/jinja2/_identifier.py create mode 100644 .venv/lib/python3.6/site-packages/jinja2/async_utils.py create mode 100644 .venv/lib/python3.6/site-packages/jinja2/bccache.py create mode 100644 .venv/lib/python3.6/site-packages/jinja2/compiler.py create mode 100644 .venv/lib/python3.6/site-packages/jinja2/constants.py create mode 100644 .venv/lib/python3.6/site-packages/jinja2/debug.py create mode 100644 .venv/lib/python3.6/site-packages/jinja2/defaults.py create mode 100644 .venv/lib/python3.6/site-packages/jinja2/environment.py create mode 100644 .venv/lib/python3.6/site-packages/jinja2/exceptions.py create mode 100644 .venv/lib/python3.6/site-packages/jinja2/ext.py create mode 100644 .venv/lib/python3.6/site-packages/jinja2/filters.py create mode 100644 .venv/lib/python3.6/site-packages/jinja2/idtracking.py create mode 100644 .venv/lib/python3.6/site-packages/jinja2/lexer.py create mode 100644 .venv/lib/python3.6/site-packages/jinja2/loaders.py create mode 100644 .venv/lib/python3.6/site-packages/jinja2/meta.py create mode 100644 .venv/lib/python3.6/site-packages/jinja2/nativetypes.py create mode 100644 .venv/lib/python3.6/site-packages/jinja2/nodes.py create mode 100644 .venv/lib/python3.6/site-packages/jinja2/optimizer.py create mode 100644 .venv/lib/python3.6/site-packages/jinja2/parser.py create mode 100644 .venv/lib/python3.6/site-packages/jinja2/py.typed create mode 100644 .venv/lib/python3.6/site-packages/jinja2/runtime.py create mode 100644 .venv/lib/python3.6/site-packages/jinja2/sandbox.py create mode 100644 .venv/lib/python3.6/site-packages/jinja2/tests.py create mode 100644 .venv/lib/python3.6/site-packages/jinja2/utils.py create mode 100644 .venv/lib/python3.6/site-packages/jinja2/visitor.py create mode 100644 .venv/lib/python3.6/site-packages/markupsafe/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/markupsafe/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/markupsafe/__pycache__/_native.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/markupsafe/_native.py create mode 100644 .venv/lib/python3.6/site-packages/markupsafe/_speedups.c create mode 100755 .venv/lib/python3.6/site-packages/markupsafe/_speedups.cpython-36m-x86_64-linux-gnu.so create mode 100644 .venv/lib/python3.6/site-packages/markupsafe/_speedups.pyi create mode 100644 .venv/lib/python3.6/site-packages/markupsafe/py.typed create mode 100644 .venv/lib/python3.6/site-packages/pip-21.3.1.dist-info/INSTALLER create mode 100644 .venv/lib/python3.6/site-packages/pip-21.3.1.dist-info/LICENSE.txt create mode 100644 .venv/lib/python3.6/site-packages/pip-21.3.1.dist-info/METADATA create mode 100644 .venv/lib/python3.6/site-packages/pip-21.3.1.dist-info/RECORD create mode 100644 .venv/lib/python3.6/site-packages/pip-21.3.1.dist-info/WHEEL create mode 100644 .venv/lib/python3.6/site-packages/pip-21.3.1.dist-info/entry_points.txt create mode 100644 .venv/lib/python3.6/site-packages/pip-21.3.1.dist-info/top_level.txt create mode 100644 .venv/lib/python3.6/site-packages/pip/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/__main__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/__pycache__/__main__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/__pycache__/build_env.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/__pycache__/cache.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/__pycache__/configuration.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/__pycache__/exceptions.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/__pycache__/main.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/__pycache__/pyproject.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/__pycache__/self_outdated_check.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/__pycache__/wheel_builder.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/build_env.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/cache.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/cli/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/cli/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/cli/__pycache__/autocompletion.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/cli/__pycache__/base_command.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/cli/__pycache__/cmdoptions.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/cli/__pycache__/command_context.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/cli/__pycache__/main.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/cli/__pycache__/main_parser.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/cli/__pycache__/parser.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/cli/__pycache__/progress_bars.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/cli/__pycache__/req_command.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/cli/__pycache__/spinners.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/cli/__pycache__/status_codes.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/cli/autocompletion.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/cli/base_command.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/cli/cmdoptions.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/cli/command_context.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/cli/main.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/cli/main_parser.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/cli/parser.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/cli/progress_bars.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/cli/req_command.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/cli/spinners.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/cli/status_codes.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/__pycache__/cache.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/__pycache__/check.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/__pycache__/completion.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/__pycache__/configuration.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/__pycache__/debug.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/__pycache__/download.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/__pycache__/freeze.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/__pycache__/hash.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/__pycache__/help.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/__pycache__/index.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/__pycache__/install.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/__pycache__/list.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/__pycache__/search.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/__pycache__/show.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/__pycache__/uninstall.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/__pycache__/wheel.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/cache.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/check.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/completion.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/configuration.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/debug.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/download.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/freeze.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/hash.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/help.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/index.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/install.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/list.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/search.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/show.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/uninstall.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/commands/wheel.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/configuration.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/distributions/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/distributions/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/distributions/__pycache__/base.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/distributions/__pycache__/installed.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/distributions/__pycache__/sdist.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/distributions/__pycache__/wheel.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/distributions/base.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/distributions/installed.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/distributions/sdist.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/distributions/wheel.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/exceptions.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/index/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/index/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/index/__pycache__/collector.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/index/__pycache__/package_finder.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/index/__pycache__/sources.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/index/collector.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/index/package_finder.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/index/sources.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/locations/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/locations/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/locations/__pycache__/_distutils.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/locations/__pycache__/_sysconfig.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/locations/__pycache__/base.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/locations/_distutils.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/locations/_sysconfig.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/locations/base.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/main.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/metadata/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/metadata/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/metadata/__pycache__/base.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/metadata/__pycache__/pkg_resources.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/metadata/base.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/metadata/pkg_resources.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/models/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/models/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/models/__pycache__/candidate.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/models/__pycache__/direct_url.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/models/__pycache__/format_control.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/models/__pycache__/index.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/models/__pycache__/link.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/models/__pycache__/scheme.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/models/__pycache__/search_scope.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/models/__pycache__/selection_prefs.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/models/__pycache__/target_python.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/models/__pycache__/wheel.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/models/candidate.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/models/direct_url.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/models/format_control.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/models/index.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/models/link.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/models/scheme.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/models/search_scope.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/models/selection_prefs.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/models/target_python.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/models/wheel.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/network/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/network/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/network/__pycache__/auth.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/network/__pycache__/cache.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/network/__pycache__/download.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/network/__pycache__/lazy_wheel.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/network/__pycache__/session.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/network/__pycache__/utils.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/network/__pycache__/xmlrpc.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/network/auth.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/network/cache.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/network/download.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/network/lazy_wheel.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/network/session.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/network/utils.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/network/xmlrpc.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/operations/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/operations/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/operations/__pycache__/check.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/operations/__pycache__/freeze.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/operations/__pycache__/prepare.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/operations/build/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/operations/build/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/operations/build/__pycache__/metadata.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/operations/build/__pycache__/metadata_editable.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/operations/build/__pycache__/metadata_legacy.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/operations/build/__pycache__/wheel.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/operations/build/__pycache__/wheel_editable.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/operations/build/__pycache__/wheel_legacy.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/operations/build/metadata.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/operations/build/metadata_editable.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/operations/build/metadata_legacy.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/operations/build/wheel.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/operations/build/wheel_editable.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/operations/build/wheel_legacy.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/operations/check.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/operations/freeze.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/operations/install/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/operations/install/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/operations/install/__pycache__/editable_legacy.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/operations/install/__pycache__/legacy.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/operations/install/__pycache__/wheel.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/operations/install/editable_legacy.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/operations/install/legacy.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/operations/install/wheel.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/operations/prepare.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/pyproject.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/req/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/req/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/req/__pycache__/constructors.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/req/__pycache__/req_file.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/req/__pycache__/req_install.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/req/__pycache__/req_set.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/req/__pycache__/req_tracker.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/req/__pycache__/req_uninstall.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/req/constructors.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/req/req_file.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/req/req_install.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/req/req_set.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/req/req_tracker.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/req/req_uninstall.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/resolution/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/resolution/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/resolution/__pycache__/base.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/resolution/base.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/resolution/legacy/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/resolution/legacy/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/resolution/legacy/__pycache__/resolver.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/resolution/legacy/resolver.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/resolution/resolvelib/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/resolution/resolvelib/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/resolution/resolvelib/__pycache__/base.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/resolution/resolvelib/__pycache__/candidates.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/resolution/resolvelib/__pycache__/factory.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/resolution/resolvelib/__pycache__/found_candidates.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/resolution/resolvelib/__pycache__/provider.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/resolution/resolvelib/__pycache__/reporter.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/resolution/resolvelib/__pycache__/requirements.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/resolution/resolvelib/__pycache__/resolver.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/resolution/resolvelib/base.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/resolution/resolvelib/candidates.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/resolution/resolvelib/factory.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/resolution/resolvelib/found_candidates.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/resolution/resolvelib/provider.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/resolution/resolvelib/reporter.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/resolution/resolvelib/requirements.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/resolution/resolvelib/resolver.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/self_outdated_check.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/__pycache__/_log.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/__pycache__/appdirs.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/__pycache__/compat.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/__pycache__/compatibility_tags.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/__pycache__/datetime.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/__pycache__/deprecation.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/__pycache__/direct_url_helpers.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/__pycache__/distutils_args.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/__pycache__/egg_link.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/__pycache__/encoding.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/__pycache__/entrypoints.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/__pycache__/filesystem.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/__pycache__/filetypes.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/__pycache__/glibc.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/__pycache__/hashes.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/__pycache__/inject_securetransport.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/__pycache__/logging.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/__pycache__/misc.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/__pycache__/models.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/__pycache__/packaging.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/__pycache__/parallel.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/__pycache__/pkg_resources.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/__pycache__/setuptools_build.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/__pycache__/subprocess.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/__pycache__/temp_dir.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/__pycache__/unpacking.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/__pycache__/urls.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/__pycache__/virtualenv.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/__pycache__/wheel.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/_log.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/appdirs.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/compat.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/compatibility_tags.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/datetime.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/deprecation.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/direct_url_helpers.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/distutils_args.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/egg_link.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/encoding.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/entrypoints.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/filesystem.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/filetypes.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/glibc.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/hashes.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/inject_securetransport.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/logging.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/misc.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/models.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/packaging.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/parallel.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/pkg_resources.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/setuptools_build.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/subprocess.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/temp_dir.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/unpacking.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/urls.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/virtualenv.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/utils/wheel.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/vcs/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/vcs/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/vcs/__pycache__/bazaar.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/vcs/__pycache__/git.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/vcs/__pycache__/mercurial.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/vcs/__pycache__/subversion.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/vcs/__pycache__/versioncontrol.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/vcs/bazaar.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/vcs/git.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/vcs/mercurial.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/vcs/subversion.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/vcs/versioncontrol.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_internal/wheel_builder.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/__pycache__/distro.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/__pycache__/pyparsing.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/__pycache__/six.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/cachecontrol/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/cachecontrol/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/cachecontrol/__pycache__/_cmd.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/cachecontrol/__pycache__/adapter.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/cachecontrol/__pycache__/cache.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/cachecontrol/__pycache__/compat.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/cachecontrol/__pycache__/controller.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/cachecontrol/__pycache__/filewrapper.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/cachecontrol/__pycache__/heuristics.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/cachecontrol/__pycache__/serialize.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/cachecontrol/__pycache__/wrapper.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/cachecontrol/_cmd.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/cachecontrol/adapter.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/cachecontrol/cache.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/cachecontrol/caches/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/file_cache.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/redis_cache.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/cachecontrol/caches/file_cache.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/cachecontrol/caches/redis_cache.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/cachecontrol/compat.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/cachecontrol/controller.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/cachecontrol/filewrapper.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/cachecontrol/heuristics.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/cachecontrol/serialize.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/cachecontrol/wrapper.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/certifi/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/certifi/__main__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/certifi/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/certifi/__pycache__/__main__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/certifi/__pycache__/core.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/certifi/cacert.pem create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/certifi/core.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/big5freq.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/big5prober.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/chardistribution.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/charsetgroupprober.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/charsetprober.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/codingstatemachine.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/compat.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/cp949prober.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/enums.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/escprober.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/escsm.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/eucjpprober.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/euckrfreq.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/euckrprober.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/euctwfreq.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/euctwprober.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/gb2312freq.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/gb2312prober.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/hebrewprober.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/jisfreq.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/jpcntx.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/langbulgarianmodel.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/langgreekmodel.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/langhebrewmodel.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/langhungarianmodel.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/langrussianmodel.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/langthaimodel.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/langturkishmodel.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/latin1prober.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/mbcharsetprober.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/mbcsgroupprober.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/mbcssm.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/sbcharsetprober.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/sbcsgroupprober.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/sjisprober.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/universaldetector.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/utf8prober.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/__pycache__/version.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/big5freq.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/big5prober.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/chardistribution.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/charsetgroupprober.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/charsetprober.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/cli/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/cli/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/cli/__pycache__/chardetect.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/cli/chardetect.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/codingstatemachine.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/compat.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/cp949prober.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/enums.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/escprober.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/escsm.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/eucjpprober.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/euckrfreq.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/euckrprober.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/euctwfreq.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/euctwprober.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/gb2312freq.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/gb2312prober.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/hebrewprober.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/jisfreq.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/jpcntx.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/langbulgarianmodel.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/langgreekmodel.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/langhebrewmodel.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/langhungarianmodel.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/langrussianmodel.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/langthaimodel.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/langturkishmodel.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/latin1prober.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/mbcharsetprober.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/mbcsgroupprober.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/mbcssm.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/metadata/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/metadata/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/metadata/__pycache__/languages.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/metadata/languages.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/sbcharsetprober.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/sbcsgroupprober.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/sjisprober.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/universaldetector.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/utf8prober.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/chardet/version.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/colorama/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/colorama/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/colorama/__pycache__/ansi.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/colorama/__pycache__/ansitowin32.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/colorama/__pycache__/initialise.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/colorama/__pycache__/win32.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/colorama/__pycache__/winterm.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/colorama/ansi.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/colorama/ansitowin32.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/colorama/initialise.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/colorama/win32.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/colorama/winterm.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/__pycache__/compat.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/__pycache__/database.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/__pycache__/index.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/__pycache__/locators.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/__pycache__/manifest.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/__pycache__/markers.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/__pycache__/metadata.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/__pycache__/resources.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/__pycache__/scripts.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/__pycache__/util.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/__pycache__/version.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/__pycache__/wheel.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/_backport/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/_backport/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/_backport/__pycache__/misc.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/_backport/__pycache__/shutil.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/_backport/__pycache__/sysconfig.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/_backport/__pycache__/tarfile.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/_backport/misc.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/_backport/shutil.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/_backport/sysconfig.cfg create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/_backport/sysconfig.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/_backport/tarfile.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/compat.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/database.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/index.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/locators.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/manifest.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/markers.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/metadata.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/resources.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/scripts.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/t32.exe create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/t64-arm.exe create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/t64.exe create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/util.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/version.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/w32.exe create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/w64-arm.exe create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/w64.exe create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distlib/wheel.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/distro.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/__pycache__/_ihatexml.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/__pycache__/_inputstream.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/__pycache__/_tokenizer.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/__pycache__/_utils.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/__pycache__/constants.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/__pycache__/html5parser.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/__pycache__/serializer.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/_ihatexml.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/_inputstream.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/_tokenizer.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/_trie/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/_trie/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/_trie/__pycache__/_base.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/_trie/__pycache__/py.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/_trie/_base.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/_trie/py.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/_utils.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/constants.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/filters/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/filters/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/filters/__pycache__/alphabeticalattributes.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/filters/__pycache__/base.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/filters/__pycache__/inject_meta_charset.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/filters/__pycache__/lint.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/filters/__pycache__/optionaltags.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/filters/__pycache__/sanitizer.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/filters/__pycache__/whitespace.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/filters/alphabeticalattributes.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/filters/base.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/filters/inject_meta_charset.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/filters/lint.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/filters/optionaltags.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/filters/sanitizer.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/filters/whitespace.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/html5parser.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/serializer.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/treeadapters/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/treeadapters/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/treeadapters/__pycache__/genshi.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/treeadapters/__pycache__/sax.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/treeadapters/genshi.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/treeadapters/sax.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/treebuilders/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/treebuilders/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/treebuilders/__pycache__/base.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/treebuilders/__pycache__/dom.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/treebuilders/__pycache__/etree.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/treebuilders/__pycache__/etree_lxml.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/treebuilders/base.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/treebuilders/dom.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/treebuilders/etree.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/treebuilders/etree_lxml.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/treewalkers/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/treewalkers/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/treewalkers/__pycache__/base.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/treewalkers/__pycache__/dom.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/treewalkers/__pycache__/etree.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/treewalkers/__pycache__/etree_lxml.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/treewalkers/__pycache__/genshi.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/treewalkers/base.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/treewalkers/dom.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/treewalkers/etree.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/treewalkers/etree_lxml.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/html5lib/treewalkers/genshi.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/idna/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/idna/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/idna/__pycache__/codec.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/idna/__pycache__/compat.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/idna/__pycache__/core.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/idna/__pycache__/idnadata.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/idna/__pycache__/intranges.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/idna/__pycache__/package_data.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/idna/__pycache__/uts46data.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/idna/codec.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/idna/compat.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/idna/core.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/idna/idnadata.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/idna/intranges.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/idna/package_data.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/idna/uts46data.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/msgpack/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/msgpack/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/msgpack/__pycache__/_version.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/msgpack/__pycache__/exceptions.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/msgpack/__pycache__/ext.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/msgpack/__pycache__/fallback.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/msgpack/_version.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/msgpack/exceptions.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/msgpack/ext.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/msgpack/fallback.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/packaging/__about__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/packaging/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/packaging/__pycache__/__about__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/packaging/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/packaging/__pycache__/_manylinux.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/packaging/__pycache__/_musllinux.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/packaging/__pycache__/_structures.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/packaging/__pycache__/markers.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/packaging/__pycache__/requirements.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/packaging/__pycache__/specifiers.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/packaging/__pycache__/tags.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/packaging/__pycache__/utils.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/packaging/__pycache__/version.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/packaging/_manylinux.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/packaging/_musllinux.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/packaging/_structures.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/packaging/markers.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/packaging/requirements.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/packaging/specifiers.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/packaging/tags.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/packaging/utils.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/packaging/version.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/pep517/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/pep517/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/pep517/__pycache__/build.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/pep517/__pycache__/check.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/pep517/__pycache__/colorlog.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/pep517/__pycache__/compat.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/pep517/__pycache__/dirtools.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/pep517/__pycache__/envbuild.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/pep517/__pycache__/meta.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/pep517/__pycache__/wrappers.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/pep517/build.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/pep517/check.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/pep517/colorlog.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/pep517/compat.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/pep517/dirtools.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/pep517/envbuild.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/pep517/in_process/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/pep517/in_process/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/pep517/in_process/__pycache__/_in_process.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/pep517/in_process/_in_process.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/pep517/meta.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/pep517/wrappers.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/pkg_resources/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/pkg_resources/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/pkg_resources/__pycache__/py31compat.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/pkg_resources/py31compat.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/platformdirs/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/platformdirs/__main__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/platformdirs/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/platformdirs/__pycache__/__main__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/platformdirs/__pycache__/android.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/platformdirs/__pycache__/api.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/platformdirs/__pycache__/macos.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/platformdirs/__pycache__/unix.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/platformdirs/__pycache__/version.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/platformdirs/__pycache__/windows.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/platformdirs/android.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/platformdirs/api.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/platformdirs/macos.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/platformdirs/unix.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/platformdirs/version.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/platformdirs/windows.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/progress/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/progress/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/progress/__pycache__/bar.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/progress/__pycache__/colors.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/progress/__pycache__/counter.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/progress/__pycache__/spinner.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/progress/bar.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/progress/colors.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/progress/counter.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/progress/spinner.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/pyparsing.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/__pycache__/__version__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/__pycache__/_internal_utils.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/__pycache__/adapters.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/__pycache__/api.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/__pycache__/auth.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/__pycache__/certs.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/__pycache__/compat.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/__pycache__/cookies.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/__pycache__/exceptions.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/__pycache__/help.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/__pycache__/hooks.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/__pycache__/models.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/__pycache__/packages.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/__pycache__/sessions.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/__pycache__/status_codes.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/__pycache__/structures.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/__pycache__/utils.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/__version__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/_internal_utils.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/adapters.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/api.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/auth.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/certs.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/compat.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/cookies.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/exceptions.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/help.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/hooks.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/models.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/packages.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/sessions.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/status_codes.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/structures.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/requests/utils.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/resolvelib/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/resolvelib/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/resolvelib/__pycache__/providers.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/resolvelib/__pycache__/reporters.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/resolvelib/__pycache__/resolvers.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/resolvelib/__pycache__/structs.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/resolvelib/compat/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/resolvelib/compat/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/resolvelib/compat/__pycache__/collections_abc.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/resolvelib/compat/collections_abc.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/resolvelib/providers.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/resolvelib/reporters.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/resolvelib/resolvers.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/resolvelib/structs.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/six.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/tenacity/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/tenacity/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/tenacity/__pycache__/_asyncio.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/tenacity/__pycache__/_utils.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/tenacity/__pycache__/after.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/tenacity/__pycache__/before.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/tenacity/__pycache__/before_sleep.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/tenacity/__pycache__/nap.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/tenacity/__pycache__/retry.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/tenacity/__pycache__/stop.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/tenacity/__pycache__/tornadoweb.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/tenacity/__pycache__/wait.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/tenacity/_asyncio.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/tenacity/_utils.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/tenacity/after.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/tenacity/before.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/tenacity/before_sleep.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/tenacity/nap.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/tenacity/retry.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/tenacity/stop.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/tenacity/tornadoweb.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/tenacity/wait.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/tomli/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/tomli/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/tomli/__pycache__/_parser.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/tomli/__pycache__/_re.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/tomli/_parser.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/tomli/_re.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/__pycache__/_collections.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/__pycache__/_version.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/__pycache__/connection.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/__pycache__/connectionpool.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/__pycache__/exceptions.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/__pycache__/fields.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/__pycache__/filepost.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/__pycache__/poolmanager.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/__pycache__/request.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/__pycache__/response.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/_collections.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/_version.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/connection.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/connectionpool.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/contrib/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/contrib/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/contrib/__pycache__/_appengine_environ.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/contrib/__pycache__/appengine.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/contrib/__pycache__/ntlmpool.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/contrib/__pycache__/pyopenssl.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/contrib/__pycache__/securetransport.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/contrib/__pycache__/socks.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/contrib/_appengine_environ.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/bindings.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/low_level.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/contrib/_securetransport/bindings.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/contrib/_securetransport/low_level.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/contrib/appengine.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/contrib/ntlmpool.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/contrib/pyopenssl.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/contrib/securetransport.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/contrib/socks.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/exceptions.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/fields.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/filepost.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/packages/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/packages/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/packages/__pycache__/six.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/packages/backports/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/makefile.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/packages/backports/makefile.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/packages/six.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/packages/ssl_match_hostname/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/packages/ssl_match_hostname/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/packages/ssl_match_hostname/__pycache__/_implementation.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/packages/ssl_match_hostname/_implementation.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/poolmanager.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/request.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/response.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/util/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/util/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/util/__pycache__/connection.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/util/__pycache__/proxy.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/util/__pycache__/queue.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/util/__pycache__/request.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/util/__pycache__/response.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/util/__pycache__/retry.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/util/__pycache__/ssl_.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/util/__pycache__/ssltransport.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/util/__pycache__/timeout.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/util/__pycache__/url.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/util/__pycache__/wait.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/util/connection.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/util/proxy.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/util/queue.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/util/request.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/util/response.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/util/retry.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/util/ssl_.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/util/ssltransport.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/util/timeout.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/util/url.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/urllib3/util/wait.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/vendor.txt create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/webencodings/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/webencodings/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/webencodings/__pycache__/labels.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/webencodings/__pycache__/mklabels.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/webencodings/__pycache__/tests.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/webencodings/__pycache__/x_user_defined.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/webencodings/labels.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/webencodings/mklabels.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/webencodings/tests.py create mode 100644 .venv/lib/python3.6/site-packages/pip/_vendor/webencodings/x_user_defined.py create mode 100644 .venv/lib/python3.6/site-packages/pip/py.typed create mode 100644 .venv/lib/python3.6/site-packages/pkg_resources/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pkg_resources/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pkg_resources/__pycache__/py31compat.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pkg_resources/_vendor/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pkg_resources/_vendor/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pkg_resources/_vendor/__pycache__/appdirs.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pkg_resources/_vendor/__pycache__/pyparsing.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pkg_resources/_vendor/__pycache__/six.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pkg_resources/_vendor/appdirs.py create mode 100644 .venv/lib/python3.6/site-packages/pkg_resources/_vendor/packaging/__about__.py create mode 100644 .venv/lib/python3.6/site-packages/pkg_resources/_vendor/packaging/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pkg_resources/_vendor/packaging/__pycache__/__about__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pkg_resources/_vendor/packaging/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pkg_resources/_vendor/packaging/__pycache__/_compat.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pkg_resources/_vendor/packaging/__pycache__/_structures.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pkg_resources/_vendor/packaging/__pycache__/markers.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pkg_resources/_vendor/packaging/__pycache__/requirements.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pkg_resources/_vendor/packaging/__pycache__/specifiers.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pkg_resources/_vendor/packaging/__pycache__/utils.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pkg_resources/_vendor/packaging/__pycache__/version.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pkg_resources/_vendor/packaging/_compat.py create mode 100644 .venv/lib/python3.6/site-packages/pkg_resources/_vendor/packaging/_structures.py create mode 100644 .venv/lib/python3.6/site-packages/pkg_resources/_vendor/packaging/markers.py create mode 100644 .venv/lib/python3.6/site-packages/pkg_resources/_vendor/packaging/requirements.py create mode 100644 .venv/lib/python3.6/site-packages/pkg_resources/_vendor/packaging/specifiers.py create mode 100644 .venv/lib/python3.6/site-packages/pkg_resources/_vendor/packaging/utils.py create mode 100644 .venv/lib/python3.6/site-packages/pkg_resources/_vendor/packaging/version.py create mode 100644 .venv/lib/python3.6/site-packages/pkg_resources/_vendor/pyparsing.py create mode 100644 .venv/lib/python3.6/site-packages/pkg_resources/_vendor/six.py create mode 100644 .venv/lib/python3.6/site-packages/pkg_resources/extern/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/pkg_resources/extern/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/pkg_resources/py31compat.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools-39.2.0.dist-info/DESCRIPTION.rst create mode 100644 .venv/lib/python3.6/site-packages/setuptools-39.2.0.dist-info/INSTALLER create mode 100644 .venv/lib/python3.6/site-packages/setuptools-39.2.0.dist-info/LICENSE.txt create mode 100644 .venv/lib/python3.6/site-packages/setuptools-39.2.0.dist-info/METADATA create mode 100644 .venv/lib/python3.6/site-packages/setuptools-39.2.0.dist-info/RECORD create mode 100644 .venv/lib/python3.6/site-packages/setuptools-39.2.0.dist-info/WHEEL create mode 100644 .venv/lib/python3.6/site-packages/setuptools-39.2.0.dist-info/dependency_links.txt create mode 100644 .venv/lib/python3.6/site-packages/setuptools-39.2.0.dist-info/entry_points.txt create mode 100644 .venv/lib/python3.6/site-packages/setuptools-39.2.0.dist-info/metadata.json create mode 100644 .venv/lib/python3.6/site-packages/setuptools-39.2.0.dist-info/top_level.txt create mode 100644 .venv/lib/python3.6/site-packages/setuptools-39.2.0.dist-info/zip-safe create mode 100644 .venv/lib/python3.6/site-packages/setuptools/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/__pycache__/archive_util.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/__pycache__/build_meta.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/__pycache__/config.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/__pycache__/dep_util.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/__pycache__/depends.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/__pycache__/dist.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/__pycache__/extension.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/__pycache__/glibc.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/__pycache__/glob.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/__pycache__/launch.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/__pycache__/lib2to3_ex.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/__pycache__/monkey.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/__pycache__/msvc.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/__pycache__/namespaces.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/__pycache__/package_index.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/__pycache__/pep425tags.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/__pycache__/py27compat.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/__pycache__/py31compat.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/__pycache__/py33compat.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/__pycache__/py36compat.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/__pycache__/sandbox.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/__pycache__/site-patch.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/__pycache__/ssl_support.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/__pycache__/unicode_utils.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/__pycache__/version.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/__pycache__/wheel.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/__pycache__/windows_support.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/_vendor/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/_vendor/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/_vendor/__pycache__/pyparsing.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/_vendor/__pycache__/six.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/_vendor/packaging/__about__.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/_vendor/packaging/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/_vendor/packaging/__pycache__/__about__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/_vendor/packaging/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/_vendor/packaging/__pycache__/_compat.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/_vendor/packaging/__pycache__/_structures.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/_vendor/packaging/__pycache__/markers.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/_vendor/packaging/__pycache__/requirements.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/_vendor/packaging/__pycache__/specifiers.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/_vendor/packaging/__pycache__/utils.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/_vendor/packaging/__pycache__/version.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/_vendor/packaging/_compat.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/_vendor/packaging/_structures.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/_vendor/packaging/markers.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/_vendor/packaging/requirements.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/_vendor/packaging/specifiers.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/_vendor/packaging/utils.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/_vendor/packaging/version.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/_vendor/pyparsing.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/_vendor/six.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/archive_util.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/build_meta.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/__pycache__/alias.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/__pycache__/bdist_egg.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/__pycache__/bdist_rpm.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/__pycache__/bdist_wininst.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/__pycache__/build_clib.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/__pycache__/build_ext.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/__pycache__/build_py.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/__pycache__/develop.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/__pycache__/dist_info.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/__pycache__/easy_install.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/__pycache__/egg_info.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/__pycache__/install.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/__pycache__/install_egg_info.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/__pycache__/install_lib.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/__pycache__/install_scripts.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/__pycache__/py36compat.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/__pycache__/register.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/__pycache__/rotate.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/__pycache__/saveopts.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/__pycache__/sdist.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/__pycache__/setopt.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/__pycache__/test.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/__pycache__/upload.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/__pycache__/upload_docs.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/alias.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/bdist_egg.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/bdist_rpm.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/bdist_wininst.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/build_clib.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/build_ext.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/build_py.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/develop.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/dist_info.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/easy_install.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/egg_info.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/install.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/install_egg_info.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/install_lib.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/install_scripts.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/launcher manifest.xml create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/py36compat.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/register.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/rotate.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/saveopts.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/sdist.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/setopt.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/test.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/upload.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/command/upload_docs.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/config.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/dep_util.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/depends.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/dist.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/extension.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/extern/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/extern/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/setuptools/glibc.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/glob.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/launch.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/lib2to3_ex.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/monkey.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/msvc.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/namespaces.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/package_index.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/pep425tags.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/py27compat.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/py31compat.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/py33compat.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/py36compat.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/sandbox.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/script (dev).tmpl create mode 100644 .venv/lib/python3.6/site-packages/setuptools/script.tmpl create mode 100644 .venv/lib/python3.6/site-packages/setuptools/site-patch.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/ssl_support.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/unicode_utils.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/version.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/wheel.py create mode 100644 .venv/lib/python3.6/site-packages/setuptools/windows_support.py create mode 100644 .venv/lib/python3.6/site-packages/typing_extensions-4.0.1.dist-info/INSTALLER create mode 100644 .venv/lib/python3.6/site-packages/typing_extensions-4.0.1.dist-info/LICENSE create mode 100644 .venv/lib/python3.6/site-packages/typing_extensions-4.0.1.dist-info/METADATA create mode 100644 .venv/lib/python3.6/site-packages/typing_extensions-4.0.1.dist-info/RECORD create mode 100644 .venv/lib/python3.6/site-packages/typing_extensions-4.0.1.dist-info/WHEEL create mode 100644 .venv/lib/python3.6/site-packages/typing_extensions.py create mode 100644 .venv/lib/python3.6/site-packages/waitress-2.0.0.dist-info/INSTALLER create mode 100644 .venv/lib/python3.6/site-packages/waitress-2.0.0.dist-info/LICENSE.txt create mode 100644 .venv/lib/python3.6/site-packages/waitress-2.0.0.dist-info/METADATA create mode 100644 .venv/lib/python3.6/site-packages/waitress-2.0.0.dist-info/RECORD create mode 100644 .venv/lib/python3.6/site-packages/waitress-2.0.0.dist-info/WHEEL create mode 100644 .venv/lib/python3.6/site-packages/waitress-2.0.0.dist-info/entry_points.txt create mode 100644 .venv/lib/python3.6/site-packages/waitress-2.0.0.dist-info/top_level.txt create mode 100644 .venv/lib/python3.6/site-packages/waitress/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/waitress/__main__.py create mode 100644 .venv/lib/python3.6/site-packages/waitress/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/waitress/__pycache__/__main__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/waitress/__pycache__/adjustments.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/waitress/__pycache__/buffers.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/waitress/__pycache__/channel.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/waitress/__pycache__/compat.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/waitress/__pycache__/parser.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/waitress/__pycache__/proxy_headers.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/waitress/__pycache__/receiver.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/waitress/__pycache__/rfc7230.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/waitress/__pycache__/runner.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/waitress/__pycache__/server.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/waitress/__pycache__/task.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/waitress/__pycache__/trigger.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/waitress/__pycache__/utilities.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/waitress/__pycache__/wasyncore.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/waitress/adjustments.py create mode 100644 .venv/lib/python3.6/site-packages/waitress/buffers.py create mode 100644 .venv/lib/python3.6/site-packages/waitress/channel.py create mode 100644 .venv/lib/python3.6/site-packages/waitress/compat.py create mode 100644 .venv/lib/python3.6/site-packages/waitress/parser.py create mode 100644 .venv/lib/python3.6/site-packages/waitress/proxy_headers.py create mode 100644 .venv/lib/python3.6/site-packages/waitress/receiver.py create mode 100644 .venv/lib/python3.6/site-packages/waitress/rfc7230.py create mode 100644 .venv/lib/python3.6/site-packages/waitress/runner.py create mode 100644 .venv/lib/python3.6/site-packages/waitress/server.py create mode 100644 .venv/lib/python3.6/site-packages/waitress/task.py create mode 100644 .venv/lib/python3.6/site-packages/waitress/trigger.py create mode 100644 .venv/lib/python3.6/site-packages/waitress/utilities.py create mode 100644 .venv/lib/python3.6/site-packages/waitress/wasyncore.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/__pycache__/_internal.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/__pycache__/_reloader.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/__pycache__/datastructures.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/__pycache__/exceptions.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/__pycache__/filesystem.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/__pycache__/formparser.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/__pycache__/http.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/__pycache__/local.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/__pycache__/routing.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/__pycache__/security.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/__pycache__/serving.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/__pycache__/test.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/__pycache__/testapp.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/__pycache__/urls.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/__pycache__/user_agent.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/__pycache__/useragents.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/__pycache__/utils.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/__pycache__/wsgi.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/_internal.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/_reloader.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/datastructures.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/datastructures.pyi create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/debug/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/debug/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/debug/__pycache__/console.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/debug/__pycache__/repr.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/debug/__pycache__/tbtools.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/debug/console.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/debug/repr.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/debug/shared/FONT_LICENSE create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/debug/shared/ICON_LICENSE.md create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/debug/shared/console.png create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/debug/shared/debugger.js create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/debug/shared/less.png create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/debug/shared/more.png create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/debug/shared/source.png create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/debug/shared/style.css create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/debug/shared/ubuntu.ttf create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/debug/tbtools.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/exceptions.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/filesystem.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/formparser.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/http.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/local.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/middleware/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/middleware/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/middleware/__pycache__/dispatcher.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/middleware/__pycache__/http_proxy.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/middleware/__pycache__/lint.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/middleware/__pycache__/profiler.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/middleware/__pycache__/proxy_fix.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/middleware/__pycache__/shared_data.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/middleware/dispatcher.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/middleware/http_proxy.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/middleware/lint.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/middleware/profiler.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/middleware/proxy_fix.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/middleware/shared_data.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/py.typed create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/routing.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/sansio/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/sansio/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/sansio/__pycache__/multipart.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/sansio/__pycache__/request.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/sansio/__pycache__/response.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/sansio/__pycache__/utils.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/sansio/multipart.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/sansio/request.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/sansio/response.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/sansio/utils.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/security.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/serving.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/test.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/testapp.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/urls.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/user_agent.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/useragents.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/utils.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/wrappers/__init__.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/__init__.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/accept.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/auth.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/base_request.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/base_response.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/common_descriptors.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/cors.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/etag.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/json.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/request.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/response.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/user_agent.cpython-36.pyc create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/wrappers/accept.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/wrappers/auth.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/wrappers/base_request.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/wrappers/base_response.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/wrappers/common_descriptors.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/wrappers/cors.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/wrappers/etag.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/wrappers/json.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/wrappers/request.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/wrappers/response.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/wrappers/user_agent.py create mode 100644 .venv/lib/python3.6/site-packages/werkzeug/wsgi.py create mode 100644 .venv/lib/python3.6/site-packages/zipp-3.6.0.dist-info/INSTALLER create mode 100644 .venv/lib/python3.6/site-packages/zipp-3.6.0.dist-info/LICENSE create mode 100644 .venv/lib/python3.6/site-packages/zipp-3.6.0.dist-info/METADATA create mode 100644 .venv/lib/python3.6/site-packages/zipp-3.6.0.dist-info/RECORD create mode 100644 .venv/lib/python3.6/site-packages/zipp-3.6.0.dist-info/WHEEL create mode 100644 .venv/lib/python3.6/site-packages/zipp-3.6.0.dist-info/top_level.txt create mode 100644 .venv/lib/python3.6/site-packages/zipp.py create mode 120000 .venv/lib64 create mode 100644 .venv/pip-selfcheck.json create mode 100644 .venv/pyvenv.cfg diff --git a/.venv/bin/activate b/.venv/bin/activate new file mode 100644 index 0000000..bc6ecb1 --- /dev/null +++ b/.venv/bin/activate @@ -0,0 +1,76 @@ +# This file must be used with "source bin/activate" *from bash* +# you cannot run it directly + +deactivate () { + # reset old environment variables + if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then + PATH="${_OLD_VIRTUAL_PATH:-}" + export PATH + unset _OLD_VIRTUAL_PATH + fi + if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then + PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}" + export PYTHONHOME + unset _OLD_VIRTUAL_PYTHONHOME + fi + + # This should detect bash and zsh, which have a hash command that must + # be called to get it to forget past commands. Without forgetting + # past commands the $PATH changes we made may not be respected + if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then + hash -r + fi + + if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then + PS1="${_OLD_VIRTUAL_PS1:-}" + export PS1 + unset _OLD_VIRTUAL_PS1 + fi + + unset VIRTUAL_ENV + if [ ! "$1" = "nondestructive" ] ; then + # Self destruct! + unset -f deactivate + fi +} + +# unset irrelevant variables +deactivate nondestructive + +VIRTUAL_ENV="/home/brochwer/github/devfile-sample-python-basic/.venv" +export VIRTUAL_ENV + +_OLD_VIRTUAL_PATH="$PATH" +PATH="$VIRTUAL_ENV/bin:$PATH" +export PATH + +# unset PYTHONHOME if set +# this will fail if PYTHONHOME is set to the empty string (which is bad anyway) +# could use `if (set -u; : $PYTHONHOME) ;` in bash +if [ -n "${PYTHONHOME:-}" ] ; then + _OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}" + unset PYTHONHOME +fi + +if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then + _OLD_VIRTUAL_PS1="${PS1:-}" + if [ "x(.venv) " != x ] ; then + PS1="(.venv) ${PS1:-}" + else + if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then + # special case for Aspen magic directories + # see http://www.zetadev.com/software/aspen/ + PS1="[`basename \`dirname \"$VIRTUAL_ENV\"\``] $PS1" + else + PS1="(`basename \"$VIRTUAL_ENV\"`)$PS1" + fi + fi + export PS1 +fi + +# This should detect bash and zsh, which have a hash command that must +# be called to get it to forget past commands. Without forgetting +# past commands the $PATH changes we made may not be respected +if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then + hash -r +fi diff --git a/.venv/bin/activate.csh b/.venv/bin/activate.csh new file mode 100644 index 0000000..df66ad3 --- /dev/null +++ b/.venv/bin/activate.csh @@ -0,0 +1,37 @@ +# This file must be used with "source bin/activate.csh" *from csh*. +# You cannot run it directly. +# Created by Davide Di Blasi . +# Ported to Python 3.3 venv by Andrew Svetlov + +alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; test "\!:*" != "nondestructive" && unalias deactivate' + +# Unset irrelevant variables. +deactivate nondestructive + +setenv VIRTUAL_ENV "/home/brochwer/github/devfile-sample-python-basic/.venv" + +set _OLD_VIRTUAL_PATH="$PATH" +setenv PATH "$VIRTUAL_ENV/bin:$PATH" + + +set _OLD_VIRTUAL_PROMPT="$prompt" + +if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then + if (".venv" != "") then + set env_name = ".venv" + else + if (`basename "VIRTUAL_ENV"` == "__") then + # special case for Aspen magic directories + # see http://www.zetadev.com/software/aspen/ + set env_name = `basename \`dirname "$VIRTUAL_ENV"\`` + else + set env_name = `basename "$VIRTUAL_ENV"` + endif + endif + set prompt = "[$env_name] $prompt" + unset env_name +endif + +alias pydoc python -m pydoc + +rehash diff --git a/.venv/bin/activate.fish b/.venv/bin/activate.fish new file mode 100644 index 0000000..d44b7d6 --- /dev/null +++ b/.venv/bin/activate.fish @@ -0,0 +1,75 @@ +# This file must be used with ". bin/activate.fish" *from fish* (http://fishshell.org) +# you cannot run it directly + +function deactivate -d "Exit virtualenv and return to normal shell environment" + # reset old environment variables + if test -n "$_OLD_VIRTUAL_PATH" + set -gx PATH $_OLD_VIRTUAL_PATH + set -e _OLD_VIRTUAL_PATH + end + if test -n "$_OLD_VIRTUAL_PYTHONHOME" + set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME + set -e _OLD_VIRTUAL_PYTHONHOME + end + + if test -n "$_OLD_FISH_PROMPT_OVERRIDE" + functions -e fish_prompt + set -e _OLD_FISH_PROMPT_OVERRIDE + functions -c _old_fish_prompt fish_prompt + functions -e _old_fish_prompt + end + + set -e VIRTUAL_ENV + if test "$argv[1]" != "nondestructive" + # Self destruct! + functions -e deactivate + end +end + +# unset irrelevant variables +deactivate nondestructive + +set -gx VIRTUAL_ENV "/home/brochwer/github/devfile-sample-python-basic/.venv" + +set -gx _OLD_VIRTUAL_PATH $PATH +set -gx PATH "$VIRTUAL_ENV/bin" $PATH + +# unset PYTHONHOME if set +if set -q PYTHONHOME + set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME + set -e PYTHONHOME +end + +if test -z "$VIRTUAL_ENV_DISABLE_PROMPT" + # fish uses a function instead of an env var to generate the prompt. + + # save the current fish_prompt function as the function _old_fish_prompt + functions -c fish_prompt _old_fish_prompt + + # with the original prompt function renamed, we can override with our own. + function fish_prompt + # Save the return status of the last command + set -l old_status $status + + # Prompt override? + if test -n "(.venv) " + printf "%s%s" "(.venv) " (set_color normal) + else + # ...Otherwise, prepend env + set -l _checkbase (basename "$VIRTUAL_ENV") + if test $_checkbase = "__" + # special case for Aspen magic directories + # see http://www.zetadev.com/software/aspen/ + printf "%s[%s]%s " (set_color -b blue white) (basename (dirname "$VIRTUAL_ENV")) (set_color normal) + else + printf "%s(%s)%s" (set_color -b blue white) (basename "$VIRTUAL_ENV") (set_color normal) + end + end + + # Restore the return status of the previous command. + echo "exit $old_status" | . + _old_fish_prompt + end + + set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV" +end diff --git a/.venv/bin/easy_install b/.venv/bin/easy_install new file mode 100755 index 0000000..6d3083a --- /dev/null +++ b/.venv/bin/easy_install @@ -0,0 +1,11 @@ +#!/home/brochwer/github/devfile-sample-python-basic/.venv/bin/python3 + +# -*- coding: utf-8 -*- +import re +import sys + +from setuptools.command.easy_install import main + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/.venv/bin/easy_install-3.6 b/.venv/bin/easy_install-3.6 new file mode 100755 index 0000000..6d3083a --- /dev/null +++ b/.venv/bin/easy_install-3.6 @@ -0,0 +1,11 @@ +#!/home/brochwer/github/devfile-sample-python-basic/.venv/bin/python3 + +# -*- coding: utf-8 -*- +import re +import sys + +from setuptools.command.easy_install import main + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/.venv/bin/flask b/.venv/bin/flask new file mode 100755 index 0000000..b63adeb --- /dev/null +++ b/.venv/bin/flask @@ -0,0 +1,8 @@ +#!/home/brochwer/github/devfile-sample-python-basic/.venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from flask.cli import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/.venv/bin/pip b/.venv/bin/pip new file mode 100755 index 0000000..79c1b92 --- /dev/null +++ b/.venv/bin/pip @@ -0,0 +1,11 @@ +#!/home/brochwer/github/devfile-sample-python-basic/.venv/bin/python3 + +# -*- coding: utf-8 -*- +import re +import sys + +from pip._internal.cli.main import main + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/.venv/bin/pip3 b/.venv/bin/pip3 new file mode 100755 index 0000000..79c1b92 --- /dev/null +++ b/.venv/bin/pip3 @@ -0,0 +1,11 @@ +#!/home/brochwer/github/devfile-sample-python-basic/.venv/bin/python3 + +# -*- coding: utf-8 -*- +import re +import sys + +from pip._internal.cli.main import main + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/.venv/bin/pip3.6 b/.venv/bin/pip3.6 new file mode 100755 index 0000000..79c1b92 --- /dev/null +++ b/.venv/bin/pip3.6 @@ -0,0 +1,11 @@ +#!/home/brochwer/github/devfile-sample-python-basic/.venv/bin/python3 + +# -*- coding: utf-8 -*- +import re +import sys + +from pip._internal.cli.main import main + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/.venv/bin/python b/.venv/bin/python new file mode 120000 index 0000000..b8a0adb --- /dev/null +++ b/.venv/bin/python @@ -0,0 +1 @@ +python3 \ No newline at end of file diff --git a/.venv/bin/python3 b/.venv/bin/python3 new file mode 120000 index 0000000..ae65fda --- /dev/null +++ b/.venv/bin/python3 @@ -0,0 +1 @@ +/usr/bin/python3 \ No newline at end of file diff --git a/.venv/bin/waitress-serve b/.venv/bin/waitress-serve new file mode 100755 index 0000000..83422df --- /dev/null +++ b/.venv/bin/waitress-serve @@ -0,0 +1,11 @@ +#!/home/brochwer/github/devfile-sample-python-basic/.venv/bin/python + +# -*- coding: utf-8 -*- +import re +import sys + +from waitress.runner import run + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit(run()) diff --git a/.venv/lib/python3.6/site-packages/Flask-2.0.2.dist-info/INSTALLER b/.venv/lib/python3.6/site-packages/Flask-2.0.2.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.venv/lib/python3.6/site-packages/Flask-2.0.2.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/lib/python3.6/site-packages/Flask-2.0.2.dist-info/LICENSE.rst b/.venv/lib/python3.6/site-packages/Flask-2.0.2.dist-info/LICENSE.rst new file mode 100644 index 0000000..9d227a0 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/Flask-2.0.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/.venv/lib/python3.6/site-packages/Flask-2.0.2.dist-info/METADATA b/.venv/lib/python3.6/site-packages/Flask-2.0.2.dist-info/METADATA new file mode 100644 index 0000000..aaf27ca --- /dev/null +++ b/.venv/lib/python3.6/site-packages/Flask-2.0.2.dist-info/METADATA @@ -0,0 +1,125 @@ +Metadata-Version: 2.1 +Name: Flask +Version: 2.0.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 +Platform: UNKNOWN +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.6 +Description-Content-Type: text/x-rst +License-File: LICENSE.rst +Requires-Dist: Werkzeug (>=2.0) +Requires-Dist: Jinja2 (>=3.0) +Requires-Dist: itsdangerous (>=2.0) +Requires-Dist: click (>=7.1.2) +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/.venv/lib/python3.6/site-packages/Flask-2.0.2.dist-info/RECORD b/.venv/lib/python3.6/site-packages/Flask-2.0.2.dist-info/RECORD new file mode 100644 index 0000000..f5544bc --- /dev/null +++ b/.venv/lib/python3.6/site-packages/Flask-2.0.2.dist-info/RECORD @@ -0,0 +1,52 @@ +../../../bin/flask,sha256=wCuzOWfT5kwf4LVrPxYRdDe5wcO_SuLlTPUuvpaZcDo,259 +Flask-2.0.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +Flask-2.0.2.dist-info/LICENSE.rst,sha256=SJqOEQhQntmKN7uYPhHg9-HTHwvY-Zp5yESOf_N9B-o,1475 +Flask-2.0.2.dist-info/METADATA,sha256=aKsvjFA_ZjZN1jLh1Ac3aQk-ZUZDPrrwo_TGYW1kdAQ,3839 +Flask-2.0.2.dist-info/RECORD,, +Flask-2.0.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +Flask-2.0.2.dist-info/WHEEL,sha256=ewwEueio1C2XeHTvT17n8dZUJgOvyCWCt0WVNLClP9o,92 +Flask-2.0.2.dist-info/entry_points.txt,sha256=gBLA1aKg0OYR8AhbAfg8lnburHtKcgJLDU52BBctN0k,42 +Flask-2.0.2.dist-info/top_level.txt,sha256=dvi65F6AeGWVU0TBpYiC04yM60-FX1gJFkK31IKQr5c,6 +flask/__init__.py,sha256=9ZCelLoNCpr6eSuLmYlzvbp12B3lrLgoN5U2UWk1vdo,2251 +flask/__main__.py,sha256=bYt9eEaoRQWdejEHFD8REx9jxVEdZptECFsV7F49Ink,30 +flask/__pycache__/__init__.cpython-36.pyc,, +flask/__pycache__/__main__.cpython-36.pyc,, +flask/__pycache__/app.cpython-36.pyc,, +flask/__pycache__/blueprints.cpython-36.pyc,, +flask/__pycache__/cli.cpython-36.pyc,, +flask/__pycache__/config.cpython-36.pyc,, +flask/__pycache__/ctx.cpython-36.pyc,, +flask/__pycache__/debughelpers.cpython-36.pyc,, +flask/__pycache__/globals.cpython-36.pyc,, +flask/__pycache__/helpers.cpython-36.pyc,, +flask/__pycache__/logging.cpython-36.pyc,, +flask/__pycache__/scaffold.cpython-36.pyc,, +flask/__pycache__/sessions.cpython-36.pyc,, +flask/__pycache__/signals.cpython-36.pyc,, +flask/__pycache__/templating.cpython-36.pyc,, +flask/__pycache__/testing.cpython-36.pyc,, +flask/__pycache__/typing.cpython-36.pyc,, +flask/__pycache__/views.cpython-36.pyc,, +flask/__pycache__/wrappers.cpython-36.pyc,, +flask/app.py,sha256=ectBbi9hGmVHAse5TNcFQZIDRkDAxYUAnLgfuKD0Xws,81975 +flask/blueprints.py,sha256=AkAVXZ_MMkjwjklzCAMdBNowTiM0wVQPynnUnXjTL2M,23781 +flask/cli.py,sha256=wn2Un9RO32ZfRmCMem5KJ5h62-5lnmy1H9uxgyV-eBs,32238 +flask/config.py,sha256=70Uyjh1Jzb9MfTCT7NDhuZWAzyIEu-TIyk6-22MP3zQ,11285 +flask/ctx.py,sha256=EM3W0v1ctuFQAGk_HWtQdoJEg_r2f5Le4xcmElxFwwk,17428 +flask/debughelpers.py,sha256=W82-xrRmodjopBngI9roYH-q08EbQwN2HEGfDAi6SA0,6184 +flask/globals.py,sha256=cWd-R2hUH3VqPhnmQNww892tQS6Yjqg_wg8UvW1M7NM,1723 +flask/helpers.py,sha256=00WqA3wYeyjMrnAOPZTUyrnUf7H8ik3CVT0kqGl_qjk,30589 +flask/json/__init__.py,sha256=unAKdZBlxMI5OMiTU0-Z2Hl4CF1CMJmqTUzpStiExNw,11822 +flask/json/__pycache__/__init__.cpython-36.pyc,, +flask/json/__pycache__/tag.cpython-36.pyc,, +flask/json/tag.py,sha256=fys3HBLssWHuMAIJuTcf2K0bCtosePBKXIWASZEEjnU,8857 +flask/logging.py,sha256=1o_hirVGqdj7SBdETnhX7IAjklG89RXlrwz_2CjzQQE,2273 +flask/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +flask/scaffold.py,sha256=fM9mRy7QBh9fhJ0VTogVx900dDa5oxz8FOw6OK5F-TU,32796 +flask/sessions.py,sha256=Kb7zY4qBIOU2cw1xM5mQ_KmgYUBDFbUYWjlkq0EFYis,15189 +flask/signals.py,sha256=H7QwDciK-dtBxinjKpexpglP0E6k0MJILiFWTItfmqU,2136 +flask/templating.py,sha256=l96VD39JQ0nue4Bcj7wZ4-FWWs-ppLxvgBCpwDQ4KAk,5626 +flask/testing.py,sha256=OsHT-2B70abWH3ulY9IbhLchXIeyj3L-cfcDa88wv5E,10281 +flask/typing.py,sha256=hXEVcXoH-QEabmy1F11pYaQ2SonlkMAwfjBAnqj2x18,1982 +flask/views.py,sha256=nhq31TRB5Z-z2mjFGZACaaB2Et5XPCmWhWxJxOvLWww,5948 +flask/wrappers.py,sha256=VndbHPRBSUUOejmd2Y3ydkoCVUtsS2OJIdJEVIkBVD8,5604 diff --git a/.venv/lib/python3.6/site-packages/Flask-2.0.2.dist-info/REQUESTED b/.venv/lib/python3.6/site-packages/Flask-2.0.2.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/.venv/lib/python3.6/site-packages/Flask-2.0.2.dist-info/WHEEL b/.venv/lib/python3.6/site-packages/Flask-2.0.2.dist-info/WHEEL new file mode 100644 index 0000000..5bad85f --- /dev/null +++ b/.venv/lib/python3.6/site-packages/Flask-2.0.2.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.37.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/.venv/lib/python3.6/site-packages/Flask-2.0.2.dist-info/entry_points.txt b/.venv/lib/python3.6/site-packages/Flask-2.0.2.dist-info/entry_points.txt new file mode 100644 index 0000000..1eb0252 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/Flask-2.0.2.dist-info/entry_points.txt @@ -0,0 +1,3 @@ +[console_scripts] +flask = flask.cli:main + diff --git a/.venv/lib/python3.6/site-packages/Flask-2.0.2.dist-info/top_level.txt b/.venv/lib/python3.6/site-packages/Flask-2.0.2.dist-info/top_level.txt new file mode 100644 index 0000000..7e10602 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/Flask-2.0.2.dist-info/top_level.txt @@ -0,0 +1 @@ +flask diff --git a/.venv/lib/python3.6/site-packages/Jinja2-3.0.3.dist-info/INSTALLER b/.venv/lib/python3.6/site-packages/Jinja2-3.0.3.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.venv/lib/python3.6/site-packages/Jinja2-3.0.3.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/lib/python3.6/site-packages/Jinja2-3.0.3.dist-info/LICENSE.rst b/.venv/lib/python3.6/site-packages/Jinja2-3.0.3.dist-info/LICENSE.rst new file mode 100644 index 0000000..c37cae4 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/Jinja2-3.0.3.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/.venv/lib/python3.6/site-packages/Jinja2-3.0.3.dist-info/METADATA b/.venv/lib/python3.6/site-packages/Jinja2-3.0.3.dist-info/METADATA new file mode 100644 index 0000000..3b9355a --- /dev/null +++ b/.venv/lib/python3.6/site-packages/Jinja2-3.0.3.dist-info/METADATA @@ -0,0 +1,113 @@ +Metadata-Version: 2.1 +Name: Jinja2 +Version: 3.0.3 +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.6 +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/.venv/lib/python3.6/site-packages/Jinja2-3.0.3.dist-info/RECORD b/.venv/lib/python3.6/site-packages/Jinja2-3.0.3.dist-info/RECORD new file mode 100644 index 0000000..fe2a7a5 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/Jinja2-3.0.3.dist-info/RECORD @@ -0,0 +1,58 @@ +Jinja2-3.0.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +Jinja2-3.0.3.dist-info/LICENSE.rst,sha256=O0nc7kEF6ze6wQ-vG-JgQI_oXSUrjp3y4JefweCUQ3s,1475 +Jinja2-3.0.3.dist-info/METADATA,sha256=uvKoBSMLvh0qHK-6khEqSe1yOV4jxFzbPSREOp-3BXk,3539 +Jinja2-3.0.3.dist-info/RECORD,, +Jinja2-3.0.3.dist-info/WHEEL,sha256=ewwEueio1C2XeHTvT17n8dZUJgOvyCWCt0WVNLClP9o,92 +Jinja2-3.0.3.dist-info/entry_points.txt,sha256=Qy_DkVo6Xj_zzOtmErrATe8lHZhOqdjpt3e4JJAGyi8,61 +Jinja2-3.0.3.dist-info/top_level.txt,sha256=PkeVWtLb3-CqjWi1fO29OCbj55EhX_chhKrCdrVe_zs,7 +jinja2/__init__.py,sha256=V3JjnTV-nyIHN6rwj03N1M11fegjGvv-weiHMQwH1pk,2205 +jinja2/__pycache__/__init__.cpython-36.pyc,, +jinja2/__pycache__/_identifier.cpython-36.pyc,, +jinja2/__pycache__/async_utils.cpython-36.pyc,, +jinja2/__pycache__/bccache.cpython-36.pyc,, +jinja2/__pycache__/compiler.cpython-36.pyc,, +jinja2/__pycache__/constants.cpython-36.pyc,, +jinja2/__pycache__/debug.cpython-36.pyc,, +jinja2/__pycache__/defaults.cpython-36.pyc,, +jinja2/__pycache__/environment.cpython-36.pyc,, +jinja2/__pycache__/exceptions.cpython-36.pyc,, +jinja2/__pycache__/ext.cpython-36.pyc,, +jinja2/__pycache__/filters.cpython-36.pyc,, +jinja2/__pycache__/idtracking.cpython-36.pyc,, +jinja2/__pycache__/lexer.cpython-36.pyc,, +jinja2/__pycache__/loaders.cpython-36.pyc,, +jinja2/__pycache__/meta.cpython-36.pyc,, +jinja2/__pycache__/nativetypes.cpython-36.pyc,, +jinja2/__pycache__/nodes.cpython-36.pyc,, +jinja2/__pycache__/optimizer.cpython-36.pyc,, +jinja2/__pycache__/parser.cpython-36.pyc,, +jinja2/__pycache__/runtime.cpython-36.pyc,, +jinja2/__pycache__/sandbox.cpython-36.pyc,, +jinja2/__pycache__/tests.cpython-36.pyc,, +jinja2/__pycache__/utils.cpython-36.pyc,, +jinja2/__pycache__/visitor.cpython-36.pyc,, +jinja2/_identifier.py,sha256=EdgGJKi7O1yvr4yFlvqPNEqV6M1qHyQr8Gt8GmVTKVM,1775 +jinja2/async_utils.py,sha256=jBcJSmLoQa2PjJdNcOpwaUmBxFNE9rZNwMF7Ob3dP9I,1947 +jinja2/bccache.py,sha256=v5rKAlYxIvfJEa0uGzAC6yCYSS3KuXT5Eqi-n9qvNi8,12670 +jinja2/compiler.py,sha256=v7zKz-mgSYXmfXD9mRmi2BU0B6Z-1RGZmOXCrsPKzc0,72209 +jinja2/constants.py,sha256=GMoFydBF_kdpaRKPoM5cl5MviquVRLVyZtfp5-16jg0,1433 +jinja2/debug.py,sha256=r0JL0vfO7HPlyKZEdr6eVlg7HoIg2OQGmJ7SeUEyAeI,8494 +jinja2/defaults.py,sha256=boBcSw78h-lp20YbaXSJsqkAI2uN_mD_TtCydpeq5wU,1267 +jinja2/environment.py,sha256=Vz20npBX5-SUH_eguQuxrSQDEsLFjho0qcHLdMhY3hA,60983 +jinja2/exceptions.py,sha256=ioHeHrWwCWNaXX1inHmHVblvc4haO7AXsjCp3GfWvx0,5071 +jinja2/ext.py,sha256=44SjDjeYkkxQTpmC2BetOTxEFMgQ42p2dfSwXmPFcSo,32122 +jinja2/filters.py,sha256=jusKTZbd0ddZMaibZkxMUVKNsOsaYtOq_Il8Imtx4BE,52609 +jinja2/idtracking.py,sha256=WekexMql3u5n3vDxFsQ_i8HW0j24AtjWTjrPBLWrHww,10721 +jinja2/lexer.py,sha256=qNEQqDQw_zO5EaH6rFQsER7Qwn2du0o22prB-TR11HE,29930 +jinja2/loaders.py,sha256=1MjXJOU6p4VywFqtpDZhtvtT_vIlmHnZKMKHHw4SZzA,22754 +jinja2/meta.py,sha256=GNPEvifmSaU3CMxlbheBOZjeZ277HThOPUTf1RkppKQ,4396 +jinja2/nativetypes.py,sha256=KCJl71MogrDih_BHBu6xV5p7Cr_jggAgu-shKTg6L28,3969 +jinja2/nodes.py,sha256=i34GPRAZexXMT6bwuf5SEyvdmS-bRCy9KMjwN5O6pjk,34550 +jinja2/optimizer.py,sha256=tHkMwXxfZkbfA1KmLcqmBMSaz7RLIvvItrJcPoXTyD8,1650 +jinja2/parser.py,sha256=kHnU8v92GwMYkfr0MVakWv8UlSf_kJPx8LUsgQMof70,39767 +jinja2/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +jinja2/runtime.py,sha256=wVRlkEmAgNU67AIQDqLvI6UkNLkzDqpLA-z4Mi3vl3g,35054 +jinja2/sandbox.py,sha256=-8zxR6TO9kUkciAVFsIKu8Oq-C7PTeYEdZ5TtA55-gw,14600 +jinja2/tests.py,sha256=Am5Z6Lmfr2XaH_npIfJJ8MdXtWsbLjMULZJulTAj30E,5905 +jinja2/utils.py,sha256=udQxWIKaq4QDCZiXN31ngKOaGGdaMA5fl0JMaM-F6fg,26971 +jinja2/visitor.py,sha256=ZmeLuTj66ic35-uFH-1m0EKXiw4ObDDb_WuE6h5vPFg,3572 diff --git a/.venv/lib/python3.6/site-packages/Jinja2-3.0.3.dist-info/WHEEL b/.venv/lib/python3.6/site-packages/Jinja2-3.0.3.dist-info/WHEEL new file mode 100644 index 0000000..5bad85f --- /dev/null +++ b/.venv/lib/python3.6/site-packages/Jinja2-3.0.3.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.37.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/.venv/lib/python3.6/site-packages/Jinja2-3.0.3.dist-info/entry_points.txt b/.venv/lib/python3.6/site-packages/Jinja2-3.0.3.dist-info/entry_points.txt new file mode 100644 index 0000000..3619483 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/Jinja2-3.0.3.dist-info/entry_points.txt @@ -0,0 +1,3 @@ +[babel.extractors] +jinja2 = jinja2.ext:babel_extract [i18n] + diff --git a/.venv/lib/python3.6/site-packages/Jinja2-3.0.3.dist-info/top_level.txt b/.venv/lib/python3.6/site-packages/Jinja2-3.0.3.dist-info/top_level.txt new file mode 100644 index 0000000..7f7afbf --- /dev/null +++ b/.venv/lib/python3.6/site-packages/Jinja2-3.0.3.dist-info/top_level.txt @@ -0,0 +1 @@ +jinja2 diff --git a/.venv/lib/python3.6/site-packages/MarkupSafe-2.0.1.dist-info/INSTALLER b/.venv/lib/python3.6/site-packages/MarkupSafe-2.0.1.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.venv/lib/python3.6/site-packages/MarkupSafe-2.0.1.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/lib/python3.6/site-packages/MarkupSafe-2.0.1.dist-info/LICENSE.rst b/.venv/lib/python3.6/site-packages/MarkupSafe-2.0.1.dist-info/LICENSE.rst new file mode 100644 index 0000000..9d227a0 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/MarkupSafe-2.0.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/.venv/lib/python3.6/site-packages/MarkupSafe-2.0.1.dist-info/METADATA b/.venv/lib/python3.6/site-packages/MarkupSafe-2.0.1.dist-info/METADATA new file mode 100644 index 0000000..e87ebb9 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/MarkupSafe-2.0.1.dist-info/METADATA @@ -0,0 +1,101 @@ +Metadata-Version: 2.1 +Name: MarkupSafe +Version: 2.0.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.6 +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/quickstart/ + + +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/.venv/lib/python3.6/site-packages/MarkupSafe-2.0.1.dist-info/RECORD b/.venv/lib/python3.6/site-packages/MarkupSafe-2.0.1.dist-info/RECORD new file mode 100644 index 0000000..a4fc7b4 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/MarkupSafe-2.0.1.dist-info/RECORD @@ -0,0 +1,14 @@ +MarkupSafe-2.0.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +MarkupSafe-2.0.1.dist-info/LICENSE.rst,sha256=SJqOEQhQntmKN7uYPhHg9-HTHwvY-Zp5yESOf_N9B-o,1475 +MarkupSafe-2.0.1.dist-info/METADATA,sha256=lknelt-VPHWai5EJcvZpATGKVbXkg74h7CQuPwDS71U,3237 +MarkupSafe-2.0.1.dist-info/RECORD,, +MarkupSafe-2.0.1.dist-info/WHEEL,sha256=Fqh7DnJbpv2r236LaMb8-xDJTAK8YDFtDn9konrfeVo,221 +MarkupSafe-2.0.1.dist-info/top_level.txt,sha256=qy0Plje5IJuvsCBjejJyhDCjEAdcDLK_2agVcex8Z6U,11 +markupsafe/__init__.py,sha256=9Tez4UIlI7J6_sQcUFK1dKniT_b_8YefpGIyYJ3Sr2Q,8923 +markupsafe/__pycache__/__init__.cpython-36.pyc,, +markupsafe/__pycache__/_native.cpython-36.pyc,, +markupsafe/_native.py,sha256=GTKEV-bWgZuSjklhMHOYRHU9k0DMewTf5mVEZfkbuns,1986 +markupsafe/_speedups.c,sha256=CDDtwaV21D2nYtypnMQzxvvpZpcTvIs8OZ6KDa1g4t0,7400 +markupsafe/_speedups.cpython-36m-x86_64-linux-gnu.so,sha256=Sxz22cri9KThgORcwPLIaWduxE9TuYmKKARBzdedPd8,51904 +markupsafe/_speedups.pyi,sha256=vfMCsOgbAXRNLUXkyuyonG8uEWKYU4PDqNuMaDELAYw,229 +markupsafe/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/.venv/lib/python3.6/site-packages/MarkupSafe-2.0.1.dist-info/WHEEL b/.venv/lib/python3.6/site-packages/MarkupSafe-2.0.1.dist-info/WHEEL new file mode 100644 index 0000000..a66ca36 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/MarkupSafe-2.0.1.dist-info/WHEEL @@ -0,0 +1,8 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.36.2) +Root-Is-Purelib: false +Tag: cp36-cp36m-manylinux_2_5_x86_64 +Tag: cp36-cp36m-manylinux1_x86_64 +Tag: cp36-cp36m-manylinux_2_12_x86_64 +Tag: cp36-cp36m-manylinux2010_x86_64 + diff --git a/.venv/lib/python3.6/site-packages/MarkupSafe-2.0.1.dist-info/top_level.txt b/.venv/lib/python3.6/site-packages/MarkupSafe-2.0.1.dist-info/top_level.txt new file mode 100644 index 0000000..75bf729 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/MarkupSafe-2.0.1.dist-info/top_level.txt @@ -0,0 +1 @@ +markupsafe diff --git a/.venv/lib/python3.6/site-packages/Werkzeug-2.0.2.dist-info/INSTALLER b/.venv/lib/python3.6/site-packages/Werkzeug-2.0.2.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.venv/lib/python3.6/site-packages/Werkzeug-2.0.2.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/lib/python3.6/site-packages/Werkzeug-2.0.2.dist-info/LICENSE.rst b/.venv/lib/python3.6/site-packages/Werkzeug-2.0.2.dist-info/LICENSE.rst new file mode 100644 index 0000000..c37cae4 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/Werkzeug-2.0.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/.venv/lib/python3.6/site-packages/Werkzeug-2.0.2.dist-info/METADATA b/.venv/lib/python3.6/site-packages/Werkzeug-2.0.2.dist-info/METADATA new file mode 100644 index 0000000..b58b9bd --- /dev/null +++ b/.venv/lib/python3.6/site-packages/Werkzeug-2.0.2.dist-info/METADATA @@ -0,0 +1,129 @@ +Metadata-Version: 2.1 +Name: Werkzeug +Version: 2.0.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 +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 :: 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.6 +Description-Content-Type: text/x-rst +License-File: LICENSE.rst +Requires-Dist: dataclasses ; python_version < "3.7" +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/.venv/lib/python3.6/site-packages/Werkzeug-2.0.2.dist-info/RECORD b/.venv/lib/python3.6/site-packages/Werkzeug-2.0.2.dist-info/RECORD new file mode 100644 index 0000000..ab14636 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/Werkzeug-2.0.2.dist-info/RECORD @@ -0,0 +1,111 @@ +Werkzeug-2.0.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +Werkzeug-2.0.2.dist-info/LICENSE.rst,sha256=O0nc7kEF6ze6wQ-vG-JgQI_oXSUrjp3y4JefweCUQ3s,1475 +Werkzeug-2.0.2.dist-info/METADATA,sha256=vh_xrARtpmkFYnWRAgfSiHgl66LH143rMfAfPZo-R_E,4452 +Werkzeug-2.0.2.dist-info/RECORD,, +Werkzeug-2.0.2.dist-info/WHEEL,sha256=ewwEueio1C2XeHTvT17n8dZUJgOvyCWCt0WVNLClP9o,92 +Werkzeug-2.0.2.dist-info/top_level.txt,sha256=QRyj2VjwJoQkrwjwFIOlB8Xg3r9un0NtqVHQF-15xaw,9 +werkzeug/__init__.py,sha256=Wx1PLCftJ7UAS0fBXEO4Prdr6kvEQ124Stwg-XwyhW4,188 +werkzeug/__pycache__/__init__.cpython-36.pyc,, +werkzeug/__pycache__/_internal.cpython-36.pyc,, +werkzeug/__pycache__/_reloader.cpython-36.pyc,, +werkzeug/__pycache__/datastructures.cpython-36.pyc,, +werkzeug/__pycache__/exceptions.cpython-36.pyc,, +werkzeug/__pycache__/filesystem.cpython-36.pyc,, +werkzeug/__pycache__/formparser.cpython-36.pyc,, +werkzeug/__pycache__/http.cpython-36.pyc,, +werkzeug/__pycache__/local.cpython-36.pyc,, +werkzeug/__pycache__/routing.cpython-36.pyc,, +werkzeug/__pycache__/security.cpython-36.pyc,, +werkzeug/__pycache__/serving.cpython-36.pyc,, +werkzeug/__pycache__/test.cpython-36.pyc,, +werkzeug/__pycache__/testapp.cpython-36.pyc,, +werkzeug/__pycache__/urls.cpython-36.pyc,, +werkzeug/__pycache__/user_agent.cpython-36.pyc,, +werkzeug/__pycache__/useragents.cpython-36.pyc,, +werkzeug/__pycache__/utils.cpython-36.pyc,, +werkzeug/__pycache__/wsgi.cpython-36.pyc,, +werkzeug/_internal.py,sha256=_QKkvdaG4pDFwK68c0EpPzYJGe9Y7toRAT1cBbC-CxU,18572 +werkzeug/_reloader.py,sha256=B1hEfgsUOz2IginBQM5Zak_eaIF7gr3GS5-0x2OHvAE,13950 +werkzeug/datastructures.py,sha256=m79A8rHQEt5B7qVqyrjARXzHL66Katn8S92urGscTw4,97929 +werkzeug/datastructures.pyi,sha256=CoVwrQ2Vr9JnbprNL9aE3vOz8mOejT9qysQ-BT53C8Y,34089 +werkzeug/debug/__init__.py,sha256=jYA1e1Gw_8EPOytr-BoMdmm0rzP-Z1H0Ih7wIObnKwQ,17968 +werkzeug/debug/__pycache__/__init__.cpython-36.pyc,, +werkzeug/debug/__pycache__/console.cpython-36.pyc,, +werkzeug/debug/__pycache__/repr.cpython-36.pyc,, +werkzeug/debug/__pycache__/tbtools.cpython-36.pyc,, +werkzeug/debug/console.py,sha256=E1nBMEvFkX673ShQjPtVY-byYatfX9MN-dBMjRI8a8E,5897 +werkzeug/debug/repr.py,sha256=QCSHENKsChEZDCIApkVi_UNjhJ77v8BMXK1OfxO189M,9483 +werkzeug/debug/shared/FONT_LICENSE,sha256=LwAVEI1oYnvXiNMT9SnCH_TaLCxCpeHziDrMg0gPkAI,4673 +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/source.png,sha256=RoGcBTE4CyCB85GBuDGTFlAnUqxwTBiIfDqW15EpnUQ,818 +werkzeug/debug/shared/style.css,sha256=h1ZSUVaKNpfbfcYzRb513WAhPySGDQom1uih3uEDxPw,6704 +werkzeug/debug/shared/ubuntu.ttf,sha256=1eaHFyepmy4FyDvjLVzpITrGEBu_CZYY94jE0nED1c0,70220 +werkzeug/debug/tbtools.py,sha256=AFRrjLDCAps7G5K2-RxNZpXXaEoeFHm68T00f4vlDYA,19362 +werkzeug/exceptions.py,sha256=CUwx0pBiNbk4f9cON17ekgKnmLi6HIVFjUmYZc2x0wM,28681 +werkzeug/filesystem.py,sha256=JS2Dv2QF98WILxY4_thHl-WMcUcwluF_4igkDPaP1l4,1956 +werkzeug/formparser.py,sha256=X-p3Ek4ji8XrKrbmaWxr8StLSc6iuksbpIeweaabs4s,17400 +werkzeug/http.py,sha256=oUCXFFMnkOQ-cHbUY_aiqitshcrSzNDq3fEMf1VI_yk,45141 +werkzeug/local.py,sha256=bwL-y3-qOZAspJ66W1P36SUApLXJy3UY8nLYbM9kfmY,23183 +werkzeug/middleware/__init__.py,sha256=qfqgdT5npwG9ses3-FXQJf3aB95JYP1zchetH_T3PUw,500 +werkzeug/middleware/__pycache__/__init__.cpython-36.pyc,, +werkzeug/middleware/__pycache__/dispatcher.cpython-36.pyc,, +werkzeug/middleware/__pycache__/http_proxy.cpython-36.pyc,, +werkzeug/middleware/__pycache__/lint.cpython-36.pyc,, +werkzeug/middleware/__pycache__/profiler.cpython-36.pyc,, +werkzeug/middleware/__pycache__/proxy_fix.cpython-36.pyc,, +werkzeug/middleware/__pycache__/shared_data.cpython-36.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=sAg3GcOhICIkwYX5bJGG8n8iebX0Yipq_UH0HvrBvoU,13964 +werkzeug/middleware/profiler.py,sha256=QkXk7cqnaPnF8wQu-5SyPCIOT3_kdABUBorQOghVNOA,4899 +werkzeug/middleware/proxy_fix.py,sha256=uRgQ3dEvFV8JxUqajHYYYOPEeA_BFqaa51Yp8VW0uzA,6849 +werkzeug/middleware/shared_data.py,sha256=xydEqOhAGg0aQJEllPDVfz2-8jHwWvJpAxfPsfPCu7k,10960 +werkzeug/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +werkzeug/routing.py,sha256=oqJ32sWIZtIF6zbqfrnwB1Pbv2ShNwPDJd6FYqxdYVo,84527 +werkzeug/sansio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +werkzeug/sansio/__pycache__/__init__.cpython-36.pyc,, +werkzeug/sansio/__pycache__/multipart.cpython-36.pyc,, +werkzeug/sansio/__pycache__/request.cpython-36.pyc,, +werkzeug/sansio/__pycache__/response.cpython-36.pyc,, +werkzeug/sansio/__pycache__/utils.cpython-36.pyc,, +werkzeug/sansio/multipart.py,sha256=bJMCNC2f5xyAaylahNViJ0JqmV4ThLRbDVGVzKwcqrQ,8751 +werkzeug/sansio/request.py,sha256=aA9rABkWiG4MhYMByanst2NXkEclsq8SIxhb0LQf0e0,20228 +werkzeug/sansio/response.py,sha256=zvCq9HSBBZGBd5Gg412BY9RZIwnKsJl5Kzfd3Kl9sSo,26098 +werkzeug/sansio/utils.py,sha256=V5v-UUnX8pm4RehP9Tt_NiUSOJGJGUvKjlW0eOIQldM,4164 +werkzeug/security.py,sha256=gPDRuCjkjWrcqj99tBMq8_nHFZLFQjgoW5Ga5XIw9jo,8158 +werkzeug/serving.py,sha256=AfgLn0yKr9qXknmwO-0KXJ055oloS4h5DIFDHEu8iHA,38088 +werkzeug/test.py,sha256=8gE1l-Y9yAh2i3SI0kgpxIaI4oYZuehIkxxyDFcz6J0,48123 +werkzeug/testapp.py,sha256=f48prWSGJhbSrvYb8e1fnAah4BkrLb0enHSdChgsjBY,9471 +werkzeug/urls.py,sha256=Du2lreBHvgBh5c2_bcx72g3hzV2ZabXYZsp-picUIJs,41023 +werkzeug/user_agent.py,sha256=WclZhpvgLurMF45hsioSbS75H1Zb4iMQGKN3_yZ2oKo,1420 +werkzeug/useragents.py,sha256=G8tmv_6vxJaPrLQH3eODNgIYe0_V6KETROQlJI-WxDE,7264 +werkzeug/utils.py,sha256=D_dnCLUfodQ4k0GRSpnI6qDoVoaX7-Dza57bx7sabG0,37101 +werkzeug/wrappers/__init__.py,sha256=-s75nPbyXHzU_rwmLPDhoMuGbEUk0jZT_n0ZQAOFGf8,654 +werkzeug/wrappers/__pycache__/__init__.cpython-36.pyc,, +werkzeug/wrappers/__pycache__/accept.cpython-36.pyc,, +werkzeug/wrappers/__pycache__/auth.cpython-36.pyc,, +werkzeug/wrappers/__pycache__/base_request.cpython-36.pyc,, +werkzeug/wrappers/__pycache__/base_response.cpython-36.pyc,, +werkzeug/wrappers/__pycache__/common_descriptors.cpython-36.pyc,, +werkzeug/wrappers/__pycache__/cors.cpython-36.pyc,, +werkzeug/wrappers/__pycache__/etag.cpython-36.pyc,, +werkzeug/wrappers/__pycache__/json.cpython-36.pyc,, +werkzeug/wrappers/__pycache__/request.cpython-36.pyc,, +werkzeug/wrappers/__pycache__/response.cpython-36.pyc,, +werkzeug/wrappers/__pycache__/user_agent.cpython-36.pyc,, +werkzeug/wrappers/accept.py,sha256=_oZtAQkahvsrPRkNj2fieg7_St9P0NFC3SgZbJKS6xU,429 +werkzeug/wrappers/auth.py,sha256=rZPCzGxHk9R55PRkmS90kRywUVjjuMWzCGtH68qCq8U,856 +werkzeug/wrappers/base_request.py,sha256=saz9RyNQkvI_XLPYVm29KijNHmD1YzgxDqa0qHTbgss,1174 +werkzeug/wrappers/base_response.py,sha256=q_-TaYywT5G4zA-DWDRDJhJSat2_4O7gOPob6ye4_9A,1186 +werkzeug/wrappers/common_descriptors.py,sha256=v_kWLH3mvCiSRVJ1FNw7nO3w2UJfzY57UKKB5J4zCvE,898 +werkzeug/wrappers/cors.py,sha256=c5UndlZsZvYkbPrp6Gj5iSXxw_VOJDJHskO6-jRmNyQ,846 +werkzeug/wrappers/etag.py,sha256=XHWQQs7Mdd1oWezgBIsl-bYe8ydKkRZVil2Qd01D0Mo,846 +werkzeug/wrappers/json.py,sha256=HM1btPseGeXca0vnwQN_MvZl6h-qNsFY5YBKXKXFwus,410 +werkzeug/wrappers/request.py,sha256=yZGplfC3UqNuykwLJmgywiMhmnoKEGHJOZn_A_ublcQ,24822 +werkzeug/wrappers/response.py,sha256=0n8OcQptiM2e550SALLeg7vC1uWsUbCeE1rPZFfXR78,35177 +werkzeug/wrappers/user_agent.py,sha256=Wl1-A0-1r8o7cHIZQTB55O4Ged6LpCKENaQDlOY5pXA,435 +werkzeug/wsgi.py,sha256=L7s5-Rlt7BRVEZ1m81MaenGfMDP7yL3p1Kxt9Yssqzg,33727 diff --git a/.venv/lib/python3.6/site-packages/Werkzeug-2.0.2.dist-info/WHEEL b/.venv/lib/python3.6/site-packages/Werkzeug-2.0.2.dist-info/WHEEL new file mode 100644 index 0000000..5bad85f --- /dev/null +++ b/.venv/lib/python3.6/site-packages/Werkzeug-2.0.2.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.37.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/.venv/lib/python3.6/site-packages/Werkzeug-2.0.2.dist-info/top_level.txt b/.venv/lib/python3.6/site-packages/Werkzeug-2.0.2.dist-info/top_level.txt new file mode 100644 index 0000000..6fe8da8 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/Werkzeug-2.0.2.dist-info/top_level.txt @@ -0,0 +1 @@ +werkzeug diff --git a/.venv/lib/python3.6/site-packages/__pycache__/dataclasses.cpython-36.pyc b/.venv/lib/python3.6/site-packages/__pycache__/dataclasses.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3f887b75a439c1d06e6a85f425b84da0f26d5d74 GIT binary patch literal 21751 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 PPJI|ge4!q%3!nT0k#^%g literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/__pycache__/zipp.cpython-36.pyc b/.venv/lib/python3.6/site-packages/__pycache__/zipp.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..49fad26c97457aa40630c2d63fb1b28a9345d542 GIT binary patch literal 10108 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 Yz2>;3N~w0CcDdr#Y8$VuU#KnrA1sDg;s5{u literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/click-8.0.3.dist-info/INSTALLER b/.venv/lib/python3.6/site-packages/click-8.0.3.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.venv/lib/python3.6/site-packages/click-8.0.3.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/lib/python3.6/site-packages/click-8.0.3.dist-info/LICENSE.rst b/.venv/lib/python3.6/site-packages/click-8.0.3.dist-info/LICENSE.rst new file mode 100644 index 0000000..d12a849 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/click-8.0.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/.venv/lib/python3.6/site-packages/click-8.0.3.dist-info/METADATA b/.venv/lib/python3.6/site-packages/click-8.0.3.dist-info/METADATA new file mode 100644 index 0000000..2c8da01 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/click-8.0.3.dist-info/METADATA @@ -0,0 +1,111 @@ +Metadata-Version: 2.1 +Name: click +Version: 8.0.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.6 +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/.venv/lib/python3.6/site-packages/click-8.0.3.dist-info/RECORD b/.venv/lib/python3.6/site-packages/click-8.0.3.dist-info/RECORD new file mode 100644 index 0000000..735aac1 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/click-8.0.3.dist-info/RECORD @@ -0,0 +1,41 @@ +click-8.0.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +click-8.0.3.dist-info/LICENSE.rst,sha256=morRBqOU6FO_4h9C9OctWSgZoigF2ZG18ydQKSkrZY0,1475 +click-8.0.3.dist-info/METADATA,sha256=_0jCOf3DdGPvKUZUlBukeb1t6Pnxmm_OMGpaBoDthfc,3247 +click-8.0.3.dist-info/RECORD,, +click-8.0.3.dist-info/WHEEL,sha256=ewwEueio1C2XeHTvT17n8dZUJgOvyCWCt0WVNLClP9o,92 +click-8.0.3.dist-info/top_level.txt,sha256=J1ZQogalYS4pphY_lPECoNMfw0HzTSrZglC4Yfwo4xA,6 +click/__init__.py,sha256=YkIrDg7-0g5aBS6D2pDe58j3MOaFylHED2_8OXh2fnM,3243 +click/__pycache__/__init__.cpython-36.pyc,, +click/__pycache__/_compat.cpython-36.pyc,, +click/__pycache__/_termui_impl.cpython-36.pyc,, +click/__pycache__/_textwrap.cpython-36.pyc,, +click/__pycache__/_unicodefun.cpython-36.pyc,, +click/__pycache__/_winconsole.cpython-36.pyc,, +click/__pycache__/core.cpython-36.pyc,, +click/__pycache__/decorators.cpython-36.pyc,, +click/__pycache__/exceptions.cpython-36.pyc,, +click/__pycache__/formatting.cpython-36.pyc,, +click/__pycache__/globals.cpython-36.pyc,, +click/__pycache__/parser.cpython-36.pyc,, +click/__pycache__/shell_completion.cpython-36.pyc,, +click/__pycache__/termui.cpython-36.pyc,, +click/__pycache__/testing.cpython-36.pyc,, +click/__pycache__/types.cpython-36.pyc,, +click/__pycache__/utils.cpython-36.pyc,, +click/_compat.py,sha256=P15KQumAZC2F2MFe_JSRbvVOJcNosQfMDrdZq0ReCLM,18814 +click/_termui_impl.py,sha256=z78J5HF_RTsOBhjNLjoigaqRap3P2pWwEDDAjoZzgUg,23452 +click/_textwrap.py,sha256=10fQ64OcBUMuK7mFvh8363_uoOxPlRItZBmKzRJDgoY,1353 +click/_unicodefun.py,sha256=JKSh1oSwG_zbjAu4TBCa9tQde2P9FiYcf4MBfy5NdT8,3201 +click/_winconsole.py,sha256=5ju3jQkcZD0W27WEMGqmEP4y_crUVzPCqsX_FYb7BO0,7860 +click/core.py,sha256=k4PA2z0BT_dmed9I52Q2VLi8r6ekTMCtCQzw2y915Xs,111478 +click/decorators.py,sha256=sGkXJGmP7eLtjtmPl_Un2uBTlrhK8s2L22n-yBiDwTw,14864 +click/exceptions.py,sha256=7gDaLGuFZBeCNwY9ERMsF2-Z3R9Fvq09Zc6IZSKjseo,9167 +click/formatting.py,sha256=Frf0-5W33-loyY_i9qrwXR8-STnW3m5gvyxLVUdyxyk,9706 +click/globals.py,sha256=kGPzxq55Ug4dFUrgRV-5oHVPOPdLCUhmYolbrrVBo8g,1985 +click/parser.py,sha256=cAEt1uQR8gq3-S9ysqbVU-fdAZNvilxw4ReJ_T1OQMk,19044 +click/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +click/shell_completion.py,sha256=_hPI12T9Ex-y5a3WunWnlH0Gca2_urzXFXkDnt7G6Ow,18001 +click/termui.py,sha256=Rp2gFE8x7j8sEIoFMOcPmuqxJQVWWTrwEzyC14-sPAw,29006 +click/testing.py,sha256=kLR5Qcny1OlgxaGB3gweTr0gQe1yVlmgQRn2esA2Fz4,16020 +click/types.py,sha256=VoNZnIlRBAtRRgzavdqVnyfzY5y4U4qzVGI1UvvX1ls,35391 +click/utils.py,sha256=avYwX-3l2KkdJNUo8NmncZSoAdEmniQ_M5sdsSYloJ4,18759 diff --git a/.venv/lib/python3.6/site-packages/click-8.0.3.dist-info/WHEEL b/.venv/lib/python3.6/site-packages/click-8.0.3.dist-info/WHEEL new file mode 100644 index 0000000..5bad85f --- /dev/null +++ b/.venv/lib/python3.6/site-packages/click-8.0.3.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.37.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/.venv/lib/python3.6/site-packages/click-8.0.3.dist-info/top_level.txt b/.venv/lib/python3.6/site-packages/click-8.0.3.dist-info/top_level.txt new file mode 100644 index 0000000..dca9a90 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/click-8.0.3.dist-info/top_level.txt @@ -0,0 +1 @@ +click diff --git a/.venv/lib/python3.6/site-packages/click/__init__.py b/.venv/lib/python3.6/site-packages/click/__init__.py new file mode 100644 index 0000000..a2ed5d1 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/click/__init__.py @@ -0,0 +1,75 @@ +""" +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 get_terminal_size as get_terminal_size +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_os_args as get_os_args +from .utils import get_text_stream as get_text_stream +from .utils import open_file as open_file + +__version__ = "8.0.3" diff --git a/.venv/lib/python3.6/site-packages/click/__pycache__/__init__.cpython-36.pyc b/.venv/lib/python3.6/site-packages/click/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..311ccf6a96907c92ea632ba26487be5501a32420 GIT binary patch literal 2702 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 O=5I+F+!a(oRp(!^NXMiA literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/click/__pycache__/_winconsole.cpython-36.pyc b/.venv/lib/python3.6/site-packages/click/__pycache__/_winconsole.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fcb2027609f02f891e53afbd5cb8039c841b7dce GIT binary patch literal 7732 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 literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/click/__pycache__/core.cpython-36.pyc b/.venv/lib/python3.6/site-packages/click/__pycache__/core.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6e24546f41f0ac83d6d1dc090b1d3a57bd6d33e7 GIT binary patch literal 88008 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 literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/click/__pycache__/decorators.cpython-36.pyc b/.venv/lib/python3.6/site-packages/click/__pycache__/decorators.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a44c6a31ab87c0763b54eeb23cecda59f630be04 GIT binary patch literal 14387 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 literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/click/__pycache__/exceptions.cpython-36.pyc b/.venv/lib/python3.6/site-packages/click/__pycache__/exceptions.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f89e5673adf9764bb002212e4211c1a1fa676e8b GIT binary patch literal 10092 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 AS^xk5 literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/click/__pycache__/globals.cpython-36.pyc b/.venv/lib/python3.6/site-packages/click/__pycache__/globals.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5c4c49b51afa014921a2aede8e5781e2ff9b142b GIT binary patch literal 2424 zcmah~L2uhO6c%OKaU3U2+O$pD9ynmb=Ao4b6e|W}7&ZjW7Oe1!Ho#gBgH4OHMTZsz zl5*l8-J$7q|Dbo-U*5S^h3_xn(SJZkJ&J_aE8hslzVN4&UIlp!O#*e#SyX#9TA_hhn$~-DSgD+P zy=CT28uU7?(Ir}c>CWz~~aaVZE{DwngRMbQ5f^(rd7HoubEH>*gzn$LqFGUKKJT;uV4+ zjFxs}V%U+{J>D=Ze83G;MEtNzgl0dgz}onEO>@B#lh+h8nMxwN)i(RK&kj7=+ENcr zLfv1WQ=xZWv>nSSYY&x-;zOp|V{YPX*rseY0_S%$nWpec=O&iPogvXYYKJqH z%-Vtv+r{+V@LpR3z&k044#=44b|iRo&>jmp1iWE7&##P`=|`DT0Jk3%ku@zkA3D!h z%g~4ai{HV1Cj?c8&CbIHd@MYk`LYMB!{fMk03Y+q#+x9Dj6bQ+>b7_4KlTF81g1Lj z4;pG0dCw5?jyF#{ea}GtoT3x--2dKt>d}A(&(KfkiI@0KU<91j$a6U0k)vN@6EnOB z5<3d;BGeG${g`VUkpvGJ&NQQ#XiV@SnPVdx7#fKprr+{W5QXJ%!Xkr5N=`9J=7%v; ztaJ%p37$&DFo&Ds5jTZ50SS{3!)FW*aL78NEQvaUlxSUE!2nYhNkxoQr9W$AxJ)qD z&QYrPlqfFd7WF6-cqA2$nMj$^SZ7ge16jCcQ7i}eFy=5j<>S~uu+9mGR8~wI2%9H3 zib*o&$ry8^!6GIz4n85bU>Od6gT;b@u;JY7OPpppzR2P>?o649JA*~pggTNb8=Pl+ z;Uo<446s9pgi=O39sFtdN#Vbf5=ExCsHzU;BdnRR^bPh^2F@(9qSWw=hzuy#c+iyz zEP-tz%jh3o;bN-7(;PL^5GXT2KHsaF`-oGfXaW(qTt%gO9Sr@F}~m zlvIPz**Qf}6-e*n_x5diSZDxN#(>lMF=Dw3a)be@k2-fX0twFhb7}>j+fQv;t16m zxazh7wF>XN-tSW$nSMWSz_l*O&ub)2SwcZou7J^7VdPDlZ)LTFU{DIyb@+xoE(-WO zYiF`+k1nD(1ub?`?;n5gh)v}Tz}rkRGR1<;7?hMPY&%uJ?zSopv4fviQkkBE<&@hh zUcLegCA7ORQ9|2>Z(z9^Ng>snJXP1n( z2*k&V1EaUG1VYrIEd^&Hq&%<&sbk9}y=APt@KR{qLU(`hlYsoRe-?GaQQ}?~Uh`ED zCn;tO=mgICi*P=JT}A#@>nQLWejU|(b<_GnyDb#DzT4W!Yi6E;o~adB%9l;77@>A- z>CRX8pFX_b-}&bL&bNEruX`JXZ6AK^EU_x2bKfwiLy+*Iii>20G*Lnl>H^4vp_C%8 zf0tTmCPLZtQ}&`AoD0*V{`pkWOt3F(O6ptC)lnTa{aVpi0<;+f=ti)$wO(DT{s+^w BctQXG literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/click/__pycache__/parser.cpython-36.pyc b/.venv/lib/python3.6/site-packages/click/__pycache__/parser.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b367d2cb3872710ccfa9e30ddad8033c15328591 GIT binary patch literal 13493 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 literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/click/__pycache__/shell_completion.cpython-36.pyc b/.venv/lib/python3.6/site-packages/click/__pycache__/shell_completion.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee494bd4f1c5e28036f6c0b2f91e0e9fe6e61a5e GIT binary patch literal 16665 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 literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/click/__pycache__/testing.cpython-36.pyc b/.venv/lib/python3.6/site-packages/click/__pycache__/testing.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..75187732a1054b75b04510501eba377fb8743955 GIT binary patch literal 14703 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 literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/click/__pycache__/types.cpython-36.pyc b/.venv/lib/python3.6/site-packages/click/__pycache__/types.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..29b4a9c893f7ef891793853e314ff39f8ffa15be GIT binary patch literal 33310 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 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 don't need + # special handling for the atomic flag. It's entirely ignored. + if 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/.venv/lib/python3.6/site-packages/click/_termui_impl.py b/.venv/lib/python3.6/site-packages/click/_termui_impl.py new file mode 100644 index 0000000..39c1d08 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/click/_termui_impl.py @@ -0,0 +1,718 @@ +""" +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/.venv/lib/python3.6/site-packages/click/_textwrap.py b/.venv/lib/python3.6/site-packages/click/_textwrap.py new file mode 100644 index 0000000..b47dcbd --- /dev/null +++ b/.venv/lib/python3.6/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/.venv/lib/python3.6/site-packages/click/_unicodefun.py b/.venv/lib/python3.6/site-packages/click/_unicodefun.py new file mode 100644 index 0000000..9cb30c3 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/click/_unicodefun.py @@ -0,0 +1,100 @@ +import codecs +import os +from gettext import gettext as _ + + +def _verify_python_env() -> None: + """Ensures that the environment is good for Unicode.""" + try: + from locale import getpreferredencoding + + fs_enc = codecs.lookup(getpreferredencoding()).name + except Exception: + fs_enc = "ascii" + + if fs_enc != "ascii": + return + + extra = [ + _( + "Click will abort further execution because Python was" + " configured to use ASCII as encoding for the environment." + " Consult https://click.palletsprojects.com/unicode-support/" + " for mitigation steps." + ) + ] + + if os.name == "posix": + import subprocess + + try: + rv = subprocess.Popen( + ["locale", "-a"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + encoding="ascii", + errors="replace", + ).communicate()[0] + except OSError: + rv = "" + + good_locales = set() + has_c_utf8 = False + + for line in rv.splitlines(): + locale = line.strip() + + if locale.lower().endswith((".utf-8", ".utf8")): + good_locales.add(locale) + + if locale.lower() in ("c.utf8", "c.utf-8"): + has_c_utf8 = True + + if not good_locales: + extra.append( + _( + "Additional information: on this system no suitable" + " UTF-8 locales were discovered. This most likely" + " requires resolving by reconfiguring the locale" + " system." + ) + ) + elif has_c_utf8: + extra.append( + _( + "This system supports the C.UTF-8 locale which is" + " recommended. You might be able to resolve your" + " issue by exporting the following environment" + " variables:" + ) + ) + extra.append(" export LC_ALL=C.UTF-8\n export LANG=C.UTF-8") + else: + extra.append( + _( + "This system lists some UTF-8 supporting locales" + " that you can pick from. The following suitable" + " locales were discovered: {locales}" + ).format(locales=", ".join(sorted(good_locales))) + ) + + bad_locale = None + + for env_locale in os.environ.get("LC_ALL"), os.environ.get("LANG"): + if env_locale and env_locale.lower().endswith((".utf-8", ".utf8")): + bad_locale = env_locale + + if env_locale is not None: + break + + if bad_locale is not None: + extra.append( + _( + "Click discovered that you exported a UTF-8 locale" + " but the locale system could not pick up from it" + " because it does not exist. The exported locale is" + " {locale!r} but it is not supported." + ).format(locale=bad_locale) + ) + + raise RuntimeError("\n\n".join(extra)) diff --git a/.venv/lib/python3.6/site-packages/click/_winconsole.py b/.venv/lib/python3.6/site-packages/click/_winconsole.py new file mode 100644 index 0000000..6b20df3 --- /dev/null +++ b/.venv/lib/python3.6/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/.venv/lib/python3.6/site-packages/click/core.py b/.venv/lib/python3.6/site-packages/click/core.py new file mode 100644 index 0000000..f226354 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/click/core.py @@ -0,0 +1,2953 @@ +import enum +import errno +import os +import sys +import typing +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 ._unicodefun import _verify_python_env +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 defaults for all options. If not set, + defaults to the value from a parent context. Overrides an + option's ``show_default`` argument. + + .. 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] = [] + + 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 + + @typing.overload + def lookup_default( + self, name: str, call: "te.Literal[True]" = True + ) -> t.Optional[t.Any]: + ... + + @typing.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 + + @typing.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": + ... + + @typing.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. + """ + # Verify that the environment is configured correctly, or reject + # further execution to avoid a broken script. + _verify_python_env() + + 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. + + .. versionchanged:: 2.0 + Added the `context_settings` parameter. + .. versionchanged:: 8.0 + Added repr showing the command name + .. versionchanged:: 7.1 + Added the `no_args_is_help` 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. + :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. + """ + + 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 [] + + # if a form feed (page break) is found in the help text, truncate help + # text to the content preceding the first form feed + if help and "\f" in help: + help = help.split("\f", 1)[0] + + 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. + """ + text = self.short_help or "" + + if not text and self.help: + text = make_default_short_help(self.help, limit) + + 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 or "" + + if self.deprecated: + text = _("(Deprecated) {text}").format(text=text) + + if text: + 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: + formatter.write_paragraph() + with formatter.indentation(): + formatter.write_text(self.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 + 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 resultcallback(self, replace: bool = False) -> t.Callable[[F], F]: + import warnings + + warnings.warn( + "'resultcallback' has been renamed to 'result_callback'." + " The old name will be removed in Click 8.1.", + DeprecationWarning, + stacklevel=2, + ) + return self.result_callback(replace=replace) + + 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 None for regular groups, or an empty list + # for chained groups. + with ctx: + super().invoke(ctx) + return _process_result([] if self.chain else None) + 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 + + def command( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], 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.0 + Added the :attr:`command_class` attribute. + """ + from .decorators import command + + if self.command_class is not None and "cls" not in kwargs: + kwargs["cls"] = self.command_class + + def decorator(f: t.Callable[..., t.Any]) -> Command: + cmd = command(*args, **kwargs)(f) + self.add_command(cmd) + return cmd + + return decorator + + def group( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], "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.0 + Added the :attr:`group_class` attribute. + """ + from .decorators import group + + if self.group_class is not None and "cls" not in kwargs: + 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(*args, **kwargs)(f) + self.add_command(cmd) + return cmd + + 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, + autocompletion: t.Optional[ + t.Callable[ + [Context, t.List[str], str], t.List[t.Union[t.Tuple[str, str], 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 + + if autocompletion is not None: + import warnings + + warnings.warn( + "'autocompletion' is renamed to 'shell_complete'. The old name is" + " deprecated and will be removed in Click 8.1. See the docs about" + " 'Parameter' for information about new behavior.", + DeprecationWarning, + stacklevel=2, + ) + + def shell_complete( + ctx: Context, param: "Parameter", incomplete: str + ) -> t.List["CompletionItem"]: + from click.shell_completion import CompletionItem + + out = [] + + for c in autocompletion(ctx, [], incomplete): # type: ignore + if isinstance(c, tuple): + c = CompletionItem(c[0], help=c[1]) + elif isinstance(c, str): + c = CompletionItem(c) + + if c.value.startswith(incomplete): + out.append(c) + + return out + + 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 + + @typing.overload + def get_default( + self, ctx: Context, call: "te.Literal[True]" = True + ) -> t.Optional[t.Any]: + ... + + @typing.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: controls if the default value should be shown on the + help page. Normally, defaults are not shown. If this + value is a string, it shows the string instead of the + value. This is particularly useful for dynamic options. + :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.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] = False, + 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: + 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 = t.cast(str, 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: + 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'.") + + 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_is_str = isinstance(self.show_default, str) + + if show_default_is_str or ( + default_value is not None and (self.show_default or ctx.show_default) + ): + 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 callable(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] + 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 + + @typing.overload + def get_default( + self, ctx: Context, call: "te.Literal[True]" = True + ) -> t.Optional[t.Any]: + ... + + @typing.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 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) + + return rv + + 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/.venv/lib/python3.6/site-packages/click/decorators.py b/.venv/lib/python3.6/site-packages/click/decorators.py new file mode 100644 index 0000000..f1cc005 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/click/decorators.py @@ -0,0 +1,436 @@ +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", 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 + + +def _make_command( + f: F, + name: t.Optional[str], + attrs: t.MutableMapping[str, t.Any], + cls: t.Type[Command], +) -> Command: + if isinstance(f, Command): + raise TypeError("Attempted to convert a callback into a command twice.") + + try: + params = f.__click_params__ # type: ignore + params.reverse() + del f.__click_params__ # type: ignore + except AttributeError: + params = [] + + help = attrs.get("help") + + if help is None: + help = inspect.getdoc(f) + else: + help = inspect.cleandoc(help) + + attrs["help"] = help + return cls( + name=name or f.__name__.lower().replace("_", "-"), + callback=f, + params=params, + **attrs, + ) + + +def command( + name: t.Optional[str] = None, + cls: t.Optional[t.Type[Command]] = None, + **attrs: t.Any, +) -> t.Callable[[F], 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. + + 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`. + """ + if cls is None: + cls = Command + + def decorator(f: t.Callable[..., t.Any]) -> Command: + cmd = _make_command(f, name, attrs, cls) # type: ignore + cmd.__doc__ = f.__doc__ + return cmd + + return decorator + + +def group(name: t.Optional[str] = None, **attrs: t.Any) -> 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`. + """ + attrs.setdefault("cls", Group) + 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", 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() + + if "help" in option_attrs: + option_attrs["help"] = inspect.cleandoc(option_attrs["help"]) + OptionClass = option_attrs.pop("cls", 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/.venv/lib/python3.6/site-packages/click/exceptions.py b/.venv/lib/python3.6/site-packages/click/exceptions.py new file mode 100644 index 0000000..9e20b3e --- /dev/null +++ b/.venv/lib/python3.6/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/.venv/lib/python3.6/site-packages/click/formatting.py b/.venv/lib/python3.6/site-packages/click/formatting.py new file mode 100644 index 0000000..ddd2a2f --- /dev/null +++ b/.venv/lib/python3.6/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/.venv/lib/python3.6/site-packages/click/globals.py b/.venv/lib/python3.6/site-packages/click/globals.py new file mode 100644 index 0000000..a7b0c93 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/click/globals.py @@ -0,0 +1,69 @@ +import typing +import typing as t +from threading import local + +if t.TYPE_CHECKING: + import typing_extensions as te + from .core import Context + +_local = local() + + +@typing.overload +def get_current_context(silent: "te.Literal[False]" = False) -> "Context": + ... + + +@typing.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/.venv/lib/python3.6/site-packages/click/parser.py b/.venv/lib/python3.6/site-packages/click/parser.py new file mode 100644 index 0000000..2d5a2ed --- /dev/null +++ b/.venv/lib/python3.6/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/.venv/lib/python3.6/site-packages/click/py.typed b/.venv/lib/python3.6/site-packages/click/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/.venv/lib/python3.6/site-packages/click/shell_completion.py b/.venv/lib/python3.6/site-packages/click/shell_completion.py new file mode 100644 index 0000000..cad080d --- /dev/null +++ b/.venv/lib/python3.6/site-packages/click/shell_completion.py @@ -0,0 +1,581 @@ +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 + COMREPLY=() + compopt -o dirnames + elif [[ $type == 'file' ]]; then + COMREPLY=() + 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(value: str) -> bool: + """Check if the value looks like the start of an option.""" + if not value: + return False + + c = value[0] + # Allow "/" since that starts a path. + return not c.isalnum() and c != "/" + + +def _is_incomplete_option(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: + return False + + last_option = None + + for index, arg in enumerate(reversed(args)): + if index + 1 > param.nargs: + break + + if _start_of_option(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(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(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(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/.venv/lib/python3.6/site-packages/click/termui.py b/.venv/lib/python3.6/site-packages/click/termui.py new file mode 100644 index 0000000..cf8d5f1 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/click/termui.py @@ -0,0 +1,809 @@ +import inspect +import io +import itertools +import os +import sys +import typing +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 a 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 = t.cast(str, confirmation_prompt) + 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: + confirmation_prompt = t.cast(str, confirmation_prompt) + value2 = prompt_func(confirmation_prompt) + if value2: + 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 get_terminal_size() -> os.terminal_size: + """Returns the current size of the terminal as tuple in the form + ``(width, height)`` in columns and rows. + + .. deprecated:: 8.0 + Will be removed in Click 8.1. Use + :func:`shutil.get_terminal_size` instead. + """ + import shutil + import warnings + + warnings.warn( + "'click.get_terminal_size()' is deprecated and will be removed" + " in Click 8.1. Use 'shutil.get_terminal_size()' instead.", + DeprecationWarning, + stacklevel=2, + ) + return shutil.get_terminal_size() + + +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] = 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/.venv/lib/python3.6/site-packages/click/testing.py b/.venv/lib/python3.6/site-packages/click/testing.py new file mode 100644 index 0000000..d19b850 --- /dev/null +++ b/.venv/lib/python3.6/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() + t = tempfile.mkdtemp(dir=temp_dir) + os.chdir(t) + + try: + yield t + finally: + os.chdir(cwd) + + if temp_dir is None: + try: + shutil.rmtree(t) + except OSError: # noqa: B014 + pass diff --git a/.venv/lib/python3.6/site-packages/click/types.py b/.venv/lib/python3.6/site-packages/click/types.py new file mode 100644 index 0000000..103d218 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/click/types.py @@ -0,0 +1,1052 @@ +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] + return {"param_type": param_type, "name": self.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)!r}: {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 it performs + different checks. First of all, instead of returning an open file + handle it returns just the filename. Secondly, it can perform various + basic checks about what the file or directory should be. + + :param exists: if set to true, the file or directory needs to exist for + this value to be valid. If this is not required and a + file does indeed not exist, then all further checks are + silently skipped. + :param file_okay: controls if a file is a possible value. + :param dir_okay: controls if a directory is a possible value. + :param writable: if true, a writable check is performed. + :param readable: if true, a readable check is performed. + :param resolve_path: if this is true, then the path is fully resolved + before the value is passed onwards. This means + that it's absolute and symlinks are resolved. It + will not expand a tilde-prefix, as this is + supposed to be done by the shell only. + :param allow_dash: If this is set to `True`, a single dash to indicate + standard streams is permitted. + :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.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, + ): + self.exists = exists + self.file_okay = file_okay + self.dir_okay = dir_okay + self.writable = writable + self.readable = readable + 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!r} is a directory.").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.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, + ) + + 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/.venv/lib/python3.6/site-packages/click/utils.py b/.venv/lib/python3.6/site-packages/click/utils.py new file mode 100644 index 0000000..16033d6 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/click/utils.py @@ -0,0 +1,579 @@ +import os +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] = 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: + """This is similar to how the :class:`File` works but for manual + usage. Files are opened non lazy by default. This can open regular + files as well as stdin/stdout if ``'-'`` is passed. + + If stdin/stdout is returned the stream is wrapped so that the context + manager will not close the stream accidentally. This makes it possible + to always use the function like this without having to worry to + accidentally close a standard stream:: + + with open_file(filename) as f: + ... + + .. versionadded:: 3.0 + + :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 use. + :param errors: the error handling for this file. + :param lazy: can be flipped to true to open the file lazily. + :param atomic: in atomic mode writes go into a temporary file and it's + moved on close. + """ + 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 get_os_args() -> t.Sequence[str]: + """Returns the argument part of ``sys.argv``, removing the first + value which is the name of the script. + + .. deprecated:: 8.0 + Will be removed in Click 8.1. Access ``sys.argv[1:]`` directly + instead. + """ + import warnings + + warnings.warn( + "'get_os_args' is deprecated and will be removed in Click 8.1." + " Access 'sys.argv[1:]' directly instead.", + DeprecationWarning, + stacklevel=2, + ) + return sys.argv[1:] + + +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: ModuleType = sys.modules["__main__"] +) -> 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 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 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. + + .. 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) + + matches = glob(arg, recursive=glob_recursive) + + if not matches: + out.append(arg) + else: + out.extend(matches) + + return out diff --git a/.venv/lib/python3.6/site-packages/dataclasses-0.8.dist-info/INSTALLER b/.venv/lib/python3.6/site-packages/dataclasses-0.8.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.venv/lib/python3.6/site-packages/dataclasses-0.8.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/lib/python3.6/site-packages/dataclasses-0.8.dist-info/LICENSE.txt b/.venv/lib/python3.6/site-packages/dataclasses-0.8.dist-info/LICENSE.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/dataclasses-0.8.dist-info/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/.venv/lib/python3.6/site-packages/dataclasses-0.8.dist-info/METADATA b/.venv/lib/python3.6/site-packages/dataclasses-0.8.dist-info/METADATA new file mode 100644 index 0000000..66e6b82 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/dataclasses-0.8.dist-info/METADATA @@ -0,0 +1,98 @@ +Metadata-Version: 2.1 +Name: dataclasses +Version: 0.8 +Summary: A backport of the dataclasses module for Python 3.6 +Home-page: https://github.com/ericvsmith/dataclasses +Author: Eric V. Smith +Author-email: eric@python.org +License: Apache +Platform: UNKNOWN +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Programming Language :: Python :: 3.6 +Requires-Python: >=3.6, <3.7 + +.. image:: https://img.shields.io/pypi/v/dataclasses.svg + :target: https://pypi.org/project/dataclasses/ + + +This is an implementation of PEP 557, Data Classes. It is a backport +for Python 3.6. Because dataclasses will be included in Python 3.7, +any discussion of dataclass features should occur on the python-dev +mailing list at https://mail.python.org/mailman/listinfo/python-dev. +At this point this repo should only be used for historical purposes +(it's where the original dataclasses discussions took place) and for +discussion of the actual backport to Python 3.6. + +See https://www.python.org/dev/peps/pep-0557/ for the details of how +Data Classes work. + +A test file can be found at +https://github.com/ericvsmith/dataclasses/blob/master/test/test_dataclasses.py, +or in the sdist file. + +Installation +------------- + +.. code-block:: + + pip install dataclasses + + +Example Usage +------------- + +.. code-block:: python + + from dataclasses import dataclass + + @dataclass + class InventoryItem: + name: str + unit_price: float + quantity_on_hand: int = 0 + + def total_cost(self) -> float: + return self.unit_price * self.quantity_on_hand + + item = InventoryItem('hammers', 10.49, 12) + print(item.total_cost()) + +Some additional tools can be found in dataclass_tools.py, included in +the sdist. + +Compatibility +------------- + +This backport assumes that dict objects retain their insertion order. +This is true in the language spec for Python 3.7 and greater. Since +this is a backport to Python 3.6, it raises an interesting question: +does that guarantee apply to 3.6? For CPython 3.6 it does. As of the +time of this writing, it's also true for all other Python +implementations that claim to be 3.6 compatible, of which there are +none. Any new 3.6 implementations are expected to have ordered dicts. +See the analysis at the end of this email: + +https://mail.python.org/pipermail/python-dev/2017-December/151325.html + +As of version 0.4, this code no longer works with Python 3.7. For 3.7, +use the built-in dataclasses module. + +Release History +--------------- + ++---------+------------+-------------------------------------+ +| Version | Date | Description | ++=========+============+=====================================+ +| 0.8 | 2020-11-13 | Fix ClassVar in .replace() | ++---------+------------+-------------------------------------+ +| 0.7 | 2019-10-20 | Require python 3.6 only | ++---------+------------+-------------------------------------+ +| 0.6 | 2018-05-17 | Equivalent to Python 3.7.0rc1 | ++---------+------------+-------------------------------------+ +| 0.5 | 2018-03-28 | Equivalent to Python 3.7.0b3 | ++---------+------------+-------------------------------------+ + + diff --git a/.venv/lib/python3.6/site-packages/dataclasses-0.8.dist-info/RECORD b/.venv/lib/python3.6/site-packages/dataclasses-0.8.dist-info/RECORD new file mode 100644 index 0000000..79d4d31 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/dataclasses-0.8.dist-info/RECORD @@ -0,0 +1,8 @@ +__pycache__/dataclasses.cpython-36.pyc,, +dataclasses-0.8.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +dataclasses-0.8.dist-info/LICENSE.txt,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358 +dataclasses-0.8.dist-info/METADATA,sha256=6kGy1NyHhp_Ef9tapnLb3rQIffoxLI5aClT4fWlUvIE,3328 +dataclasses-0.8.dist-info/RECORD,, +dataclasses-0.8.dist-info/WHEEL,sha256=g4nMs7d-Xl9-xC9XovUrsDHGXt-FT0E17Yqo92DEfvY,92 +dataclasses-0.8.dist-info/top_level.txt,sha256=g1h_lLdyfM-aL3-M-rWnNGxWv3ZmULp2HVvAlyZq5s0,12 +dataclasses.py,sha256=TBt2du4qkxxXdZ5olWFzwHpim2ch4j7kKJi2VS_qoZw,45499 diff --git a/.venv/lib/python3.6/site-packages/dataclasses-0.8.dist-info/WHEEL b/.venv/lib/python3.6/site-packages/dataclasses-0.8.dist-info/WHEEL new file mode 100644 index 0000000..b552003 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/dataclasses-0.8.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.34.2) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/.venv/lib/python3.6/site-packages/dataclasses-0.8.dist-info/top_level.txt b/.venv/lib/python3.6/site-packages/dataclasses-0.8.dist-info/top_level.txt new file mode 100644 index 0000000..a46627b --- /dev/null +++ b/.venv/lib/python3.6/site-packages/dataclasses-0.8.dist-info/top_level.txt @@ -0,0 +1 @@ +dataclasses diff --git a/.venv/lib/python3.6/site-packages/dataclasses.py b/.venv/lib/python3.6/site-packages/dataclasses.py new file mode 100644 index 0000000..ddd59c6 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/dataclasses.py @@ -0,0 +1,1184 @@ +import re +import sys +import copy +import types +import inspect +import keyword + +__all__ = ['dataclass', + 'field', + 'Field', + 'FrozenInstanceError', + 'InitVar', + 'MISSING', + + # Helper functions. + 'fields', + 'asdict', + 'astuple', + 'make_dataclass', + 'replace', + 'is_dataclass', + ] + +# Conditions for adding methods. The boxes indicate what action the +# dataclass decorator takes. For all of these tables, when I talk +# about init=, repr=, eq=, order=, unsafe_hash=, or frozen=, I'm +# referring to the arguments to the @dataclass decorator. When +# checking if a dunder method already exists, I mean check for an +# entry in the class's __dict__. I never check to see if an attribute +# is defined in a base class. + +# Key: +# +=========+=========================================+ +# + Value | Meaning | +# +=========+=========================================+ +# | | No action: no method is added. | +# +---------+-----------------------------------------+ +# | add | Generated method is added. | +# +---------+-----------------------------------------+ +# | raise | TypeError is raised. | +# +---------+-----------------------------------------+ +# | None | Attribute is set to None. | +# +=========+=========================================+ + +# __init__ +# +# +--- init= parameter +# | +# v | | | +# | no | yes | <--- class has __init__ in __dict__? +# +=======+=======+=======+ +# | False | | | +# +-------+-------+-------+ +# | True | add | | <- the default +# +=======+=======+=======+ + +# __repr__ +# +# +--- repr= parameter +# | +# v | | | +# | no | yes | <--- class has __repr__ in __dict__? +# +=======+=======+=======+ +# | False | | | +# +-------+-------+-------+ +# | True | add | | <- the default +# +=======+=======+=======+ + + +# __setattr__ +# __delattr__ +# +# +--- frozen= parameter +# | +# v | | | +# | no | yes | <--- class has __setattr__ or __delattr__ in __dict__? +# +=======+=======+=======+ +# | False | | | <- the default +# +-------+-------+-------+ +# | True | add | raise | +# +=======+=======+=======+ +# Raise because not adding these methods would break the "frozen-ness" +# of the class. + +# __eq__ +# +# +--- eq= parameter +# | +# v | | | +# | no | yes | <--- class has __eq__ in __dict__? +# +=======+=======+=======+ +# | False | | | +# +-------+-------+-------+ +# | True | add | | <- the default +# +=======+=======+=======+ + +# __lt__ +# __le__ +# __gt__ +# __ge__ +# +# +--- order= parameter +# | +# v | | | +# | no | yes | <--- class has any comparison method in __dict__? +# +=======+=======+=======+ +# | False | | | <- the default +# +-------+-------+-------+ +# | True | add | raise | +# +=======+=======+=======+ +# Raise because to allow this case would interfere with using +# functools.total_ordering. + +# __hash__ + +# +------------------- unsafe_hash= parameter +# | +----------- eq= parameter +# | | +--- frozen= parameter +# | | | +# v v v | | | +# | no | yes | <--- class has explicitly defined __hash__ +# +=======+=======+=======+========+========+ +# | False | False | False | | | No __eq__, use the base class __hash__ +# +-------+-------+-------+--------+--------+ +# | False | False | True | | | No __eq__, use the base class __hash__ +# +-------+-------+-------+--------+--------+ +# | False | True | False | None | | <-- the default, not hashable +# +-------+-------+-------+--------+--------+ +# | False | True | True | add | | Frozen, so hashable, allows override +# +-------+-------+-------+--------+--------+ +# | True | False | False | add | raise | Has no __eq__, but hashable +# +-------+-------+-------+--------+--------+ +# | True | False | True | add | raise | Has no __eq__, but hashable +# +-------+-------+-------+--------+--------+ +# | True | True | False | add | raise | Not frozen, but hashable +# +-------+-------+-------+--------+--------+ +# | True | True | True | add | raise | Frozen, so hashable +# +=======+=======+=======+========+========+ +# For boxes that are blank, __hash__ is untouched and therefore +# inherited from the base class. If the base is object, then +# id-based hashing is used. +# +# Note that a class may already have __hash__=None if it specified an +# __eq__ method in the class body (not one that was created by +# @dataclass). +# +# See _hash_action (below) for a coded version of this table. + + +# Raised when an attempt is made to modify a frozen class. +class FrozenInstanceError(AttributeError): pass + +# A sentinel object for default values to signal that a default +# factory will be used. This is given a nice repr() which will appear +# in the function signature of dataclasses' constructors. +class _HAS_DEFAULT_FACTORY_CLASS: + def __repr__(self): + return '' +_HAS_DEFAULT_FACTORY = _HAS_DEFAULT_FACTORY_CLASS() + +# A sentinel object to detect if a parameter is supplied or not. Use +# a class to give it a better repr. +class _MISSING_TYPE: + pass +MISSING = _MISSING_TYPE() + +# Since most per-field metadata will be unused, create an empty +# read-only proxy that can be shared among all fields. +_EMPTY_METADATA = types.MappingProxyType({}) + +# Markers for the various kinds of fields and pseudo-fields. +class _FIELD_BASE: + def __init__(self, name): + self.name = name + def __repr__(self): + return self.name +_FIELD = _FIELD_BASE('_FIELD') +_FIELD_CLASSVAR = _FIELD_BASE('_FIELD_CLASSVAR') +_FIELD_INITVAR = _FIELD_BASE('_FIELD_INITVAR') + +# The name of an attribute on the class where we store the Field +# objects. Also used to check if a class is a Data Class. +_FIELDS = '__dataclass_fields__' + +# The name of an attribute on the class that stores the parameters to +# @dataclass. +_PARAMS = '__dataclass_params__' + +# The name of the function, that if it exists, is called at the end of +# __init__. +_POST_INIT_NAME = '__post_init__' + +# String regex that string annotations for ClassVar or InitVar must match. +# Allows "identifier.identifier[" or "identifier[". +# https://bugs.python.org/issue33453 for details. +_MODULE_IDENTIFIER_RE = re.compile(r'^(?:\s*(\w+)\s*\.)?\s*(\w+)') + +class _InitVarMeta(type): + def __getitem__(self, params): + return self + +class InitVar(metaclass=_InitVarMeta): + pass + + +# Instances of Field are only ever created from within this module, +# and only from the field() function, although Field instances are +# exposed externally as (conceptually) read-only objects. +# +# name and type are filled in after the fact, not in __init__. +# They're not known at the time this class is instantiated, but it's +# convenient if they're available later. +# +# When cls._FIELDS is filled in with a list of Field objects, the name +# and type fields will have been populated. +class Field: + __slots__ = ('name', + 'type', + 'default', + 'default_factory', + 'repr', + 'hash', + 'init', + 'compare', + 'metadata', + '_field_type', # Private: not to be used by user code. + ) + + def __init__(self, default, default_factory, init, repr, hash, compare, + metadata): + self.name = None + self.type = None + self.default = default + self.default_factory = default_factory + self.init = init + self.repr = repr + self.hash = hash + self.compare = compare + self.metadata = (_EMPTY_METADATA + if metadata is None or len(metadata) == 0 else + types.MappingProxyType(metadata)) + self._field_type = None + + def __repr__(self): + return ('Field(' + f'name={self.name!r},' + f'type={self.type!r},' + f'default={self.default!r},' + f'default_factory={self.default_factory!r},' + f'init={self.init!r},' + f'repr={self.repr!r},' + f'hash={self.hash!r},' + f'compare={self.compare!r},' + f'metadata={self.metadata!r},' + f'_field_type={self._field_type}' + ')') + + # This is used to support the PEP 487 __set_name__ protocol in the + # case where we're using a field that contains a descriptor as a + # defaul value. For details on __set_name__, see + # https://www.python.org/dev/peps/pep-0487/#implementation-details. + # + # Note that in _process_class, this Field object is overwritten + # with the default value, so the end result is a descriptor that + # had __set_name__ called on it at the right time. + def __set_name__(self, owner, name): + func = getattr(type(self.default), '__set_name__', None) + if func: + # There is a __set_name__ method on the descriptor, call + # it. + func(self.default, owner, name) + + +class _DataclassParams: + __slots__ = ('init', + 'repr', + 'eq', + 'order', + 'unsafe_hash', + 'frozen', + ) + + def __init__(self, init, repr, eq, order, unsafe_hash, frozen): + self.init = init + self.repr = repr + self.eq = eq + self.order = order + self.unsafe_hash = unsafe_hash + self.frozen = frozen + + def __repr__(self): + return ('_DataclassParams(' + f'init={self.init!r},' + f'repr={self.repr!r},' + f'eq={self.eq!r},' + f'order={self.order!r},' + f'unsafe_hash={self.unsafe_hash!r},' + f'frozen={self.frozen!r}' + ')') + + +# This function is used instead of exposing Field creation directly, +# so that a type checker can be told (via overloads) that this is a +# function whose type depends on its parameters. +def field(*, default=MISSING, default_factory=MISSING, init=True, repr=True, + hash=None, compare=True, metadata=None): + """Return an object to identify dataclass fields. + + default is the default value of the field. default_factory is a + 0-argument function called to initialize a field's value. If init + is True, the field will be a parameter to the class's __init__() + function. If repr is True, the field will be included in the + object's repr(). If hash is True, the field will be included in + the object's hash(). If compare is True, the field will be used + in comparison functions. metadata, if specified, must be a + mapping which is stored but not otherwise examined by dataclass. + + It is an error to specify both default and default_factory. + """ + + if default is not MISSING and default_factory is not MISSING: + raise ValueError('cannot specify both default and default_factory') + return Field(default, default_factory, init, repr, hash, compare, + metadata) + + +def _tuple_str(obj_name, fields): + # Return a string representing each field of obj_name as a tuple + # member. So, if fields is ['x', 'y'] and obj_name is "self", + # return "(self.x,self.y)". + + # Special case for the 0-tuple. + if not fields: + return '()' + # Note the trailing comma, needed if this turns out to be a 1-tuple. + return f'({",".join([f"{obj_name}.{f.name}" for f in fields])},)' + + +def _create_fn(name, args, body, *, globals=None, locals=None, + return_type=MISSING): + # Note that we mutate locals when exec() is called. Caller + # beware! The only callers are internal to this module, so no + # worries about external callers. + if locals is None: + locals = {} + return_annotation = '' + if return_type is not MISSING: + locals['_return_type'] = return_type + return_annotation = '->_return_type' + args = ','.join(args) + body = '\n'.join(f' {b}' for b in body) + + # Compute the text of the entire function. + txt = f'def {name}({args}){return_annotation}:\n{body}' + + exec(txt, globals, locals) + return locals[name] + + +def _field_assign(frozen, name, value, self_name): + # If we're a frozen class, then assign to our fields in __init__ + # via object.__setattr__. Otherwise, just use a simple + # assignment. + # + # self_name is what "self" is called in this function: don't + # hard-code "self", since that might be a field name. + if frozen: + return f'object.__setattr__({self_name},{name!r},{value})' + return f'{self_name}.{name}={value}' + + +def _field_init(f, frozen, globals, self_name): + # Return the text of the line in the body of __init__ that will + # initialize this field. + + default_name = f'_dflt_{f.name}' + if f.default_factory is not MISSING: + if f.init: + # This field has a default factory. If a parameter is + # given, use it. If not, call the factory. + globals[default_name] = f.default_factory + value = (f'{default_name}() ' + f'if {f.name} is _HAS_DEFAULT_FACTORY ' + f'else {f.name}') + else: + # This is a field that's not in the __init__ params, but + # has a default factory function. It needs to be + # initialized here by calling the factory function, + # because there's no other way to initialize it. + + # For a field initialized with a default=defaultvalue, the + # class dict just has the default value + # (cls.fieldname=defaultvalue). But that won't work for a + # default factory, the factory must be called in __init__ + # and we must assign that to self.fieldname. We can't + # fall back to the class dict's value, both because it's + # not set, and because it might be different per-class + # (which, after all, is why we have a factory function!). + + globals[default_name] = f.default_factory + value = f'{default_name}()' + else: + # No default factory. + if f.init: + if f.default is MISSING: + # There's no default, just do an assignment. + value = f.name + elif f.default is not MISSING: + globals[default_name] = f.default + value = f.name + else: + # This field does not need initialization. Signify that + # to the caller by returning None. + return None + + # Only test this now, so that we can create variables for the + # default. However, return None to signify that we're not going + # to actually do the assignment statement for InitVars. + if f._field_type is _FIELD_INITVAR: + return None + + # Now, actually generate the field assignment. + return _field_assign(frozen, f.name, value, self_name) + + +def _init_param(f): + # Return the __init__ parameter string for this field. For + # example, the equivalent of 'x:int=3' (except instead of 'int', + # reference a variable set to int, and instead of '3', reference a + # variable set to 3). + if f.default is MISSING and f.default_factory is MISSING: + # There's no default, and no default_factory, just output the + # variable name and type. + default = '' + elif f.default is not MISSING: + # There's a default, this will be the name that's used to look + # it up. + default = f'=_dflt_{f.name}' + elif f.default_factory is not MISSING: + # There's a factory function. Set a marker. + default = '=_HAS_DEFAULT_FACTORY' + return f'{f.name}:_type_{f.name}{default}' + + +def _init_fn(fields, frozen, has_post_init, self_name): + # fields contains both real fields and InitVar pseudo-fields. + + # Make sure we don't have fields without defaults following fields + # with defaults. This actually would be caught when exec-ing the + # function source code, but catching it here gives a better error + # message, and future-proofs us in case we build up the function + # using ast. + seen_default = False + for f in fields: + # Only consider fields in the __init__ call. + if f.init: + if not (f.default is MISSING and f.default_factory is MISSING): + seen_default = True + elif seen_default: + raise TypeError(f'non-default argument {f.name!r} ' + 'follows default argument') + + globals = {'MISSING': MISSING, + '_HAS_DEFAULT_FACTORY': _HAS_DEFAULT_FACTORY} + + body_lines = [] + for f in fields: + line = _field_init(f, frozen, globals, self_name) + # line is None means that this field doesn't require + # initialization (it's a pseudo-field). Just skip it. + if line: + body_lines.append(line) + + # Does this class have a post-init function? + if has_post_init: + params_str = ','.join(f.name for f in fields + if f._field_type is _FIELD_INITVAR) + body_lines.append(f'{self_name}.{_POST_INIT_NAME}({params_str})') + + # If no body lines, use 'pass'. + if not body_lines: + body_lines = ['pass'] + + locals = {f'_type_{f.name}': f.type for f in fields} + return _create_fn('__init__', + [self_name] + [_init_param(f) for f in fields if f.init], + body_lines, + locals=locals, + globals=globals, + return_type=None) + + +def _repr_fn(fields): + return _create_fn('__repr__', + ('self',), + ['return self.__class__.__qualname__ + f"(' + + ', '.join([f"{f.name}={{self.{f.name}!r}}" + for f in fields]) + + ')"']) + + +def _frozen_get_del_attr(cls, fields): + # XXX: globals is modified on the first call to _create_fn, then + # the modified version is used in the second call. Is this okay? + globals = {'cls': cls, + 'FrozenInstanceError': FrozenInstanceError} + if fields: + fields_str = '(' + ','.join(repr(f.name) for f in fields) + ',)' + else: + # Special case for the zero-length tuple. + fields_str = '()' + return (_create_fn('__setattr__', + ('self', 'name', 'value'), + (f'if type(self) is cls or name in {fields_str}:', + ' raise FrozenInstanceError(f"cannot assign to field {name!r}")', + f'super(cls, self).__setattr__(name, value)'), + globals=globals), + _create_fn('__delattr__', + ('self', 'name'), + (f'if type(self) is cls or name in {fields_str}:', + ' raise FrozenInstanceError(f"cannot delete field {name!r}")', + f'super(cls, self).__delattr__(name)'), + globals=globals), + ) + + +def _cmp_fn(name, op, self_tuple, other_tuple): + # Create a comparison function. If the fields in the object are + # named 'x' and 'y', then self_tuple is the string + # '(self.x,self.y)' and other_tuple is the string + # '(other.x,other.y)'. + + return _create_fn(name, + ('self', 'other'), + [ 'if other.__class__ is self.__class__:', + f' return {self_tuple}{op}{other_tuple}', + 'return NotImplemented']) + + +def _hash_fn(fields): + self_tuple = _tuple_str('self', fields) + return _create_fn('__hash__', + ('self',), + [f'return hash({self_tuple})']) + + +def _is_classvar(a_type, typing): + # This test uses a typing internal class, but it's the best way to + # test if this is a ClassVar. + return type(a_type) is typing._ClassVar + + +def _is_initvar(a_type, dataclasses): + # The module we're checking against is the module we're + # currently in (dataclasses.py). + return a_type is dataclasses.InitVar + + +def _is_type(annotation, cls, a_module, a_type, is_type_predicate): + # Given a type annotation string, does it refer to a_type in + # a_module? For example, when checking that annotation denotes a + # ClassVar, then a_module is typing, and a_type is + # typing.ClassVar. + + # It's possible to look up a_module given a_type, but it involves + # looking in sys.modules (again!), and seems like a waste since + # the caller already knows a_module. + + # - annotation is a string type annotation + # - cls is the class that this annotation was found in + # - a_module is the module we want to match + # - a_type is the type in that module we want to match + # - is_type_predicate is a function called with (obj, a_module) + # that determines if obj is of the desired type. + + # Since this test does not do a local namespace lookup (and + # instead only a module (global) lookup), there are some things it + # gets wrong. + + # With string annotations, cv0 will be detected as a ClassVar: + # CV = ClassVar + # @dataclass + # class C0: + # cv0: CV + + # But in this example cv1 will not be detected as a ClassVar: + # @dataclass + # class C1: + # CV = ClassVar + # cv1: CV + + # In C1, the code in this function (_is_type) will look up "CV" in + # the module and not find it, so it will not consider cv1 as a + # ClassVar. This is a fairly obscure corner case, and the best + # way to fix it would be to eval() the string "CV" with the + # correct global and local namespaces. However that would involve + # a eval() penalty for every single field of every dataclass + # that's defined. It was judged not worth it. + + match = _MODULE_IDENTIFIER_RE.match(annotation) + if match: + ns = None + module_name = match.group(1) + if not module_name: + # No module name, assume the class's module did + # "from dataclasses import InitVar". + ns = sys.modules.get(cls.__module__).__dict__ + else: + # Look up module_name in the class's module. + module = sys.modules.get(cls.__module__) + if module and module.__dict__.get(module_name) is a_module: + ns = sys.modules.get(a_type.__module__).__dict__ + if ns and is_type_predicate(ns.get(match.group(2)), a_module): + return True + return False + + +def _get_field(cls, a_name, a_type): + # Return a Field object for this field name and type. ClassVars + # and InitVars are also returned, but marked as such (see + # f._field_type). + + # If the default value isn't derived from Field, then it's only a + # normal default value. Convert it to a Field(). + default = getattr(cls, a_name, MISSING) + if isinstance(default, Field): + f = default + else: + if isinstance(default, types.MemberDescriptorType): + # This is a field in __slots__, so it has no default value. + default = MISSING + f = field(default=default) + + # Only at this point do we know the name and the type. Set them. + f.name = a_name + f.type = a_type + + # Assume it's a normal field until proven otherwise. We're next + # going to decide if it's a ClassVar or InitVar, everything else + # is just a normal field. + f._field_type = _FIELD + + # In addition to checking for actual types here, also check for + # string annotations. get_type_hints() won't always work for us + # (see https://github.com/python/typing/issues/508 for example), + # plus it's expensive and would require an eval for every stirng + # annotation. So, make a best effort to see if this is a ClassVar + # or InitVar using regex's and checking that the thing referenced + # is actually of the correct type. + + # For the complete discussion, see https://bugs.python.org/issue33453 + + # If typing has not been imported, then it's impossible for any + # annotation to be a ClassVar. So, only look for ClassVar if + # typing has been imported by any module (not necessarily cls's + # module). + typing = sys.modules.get('typing') + if typing: + if (_is_classvar(a_type, typing) + or (isinstance(f.type, str) + and _is_type(f.type, cls, typing, typing.ClassVar, + _is_classvar))): + f._field_type = _FIELD_CLASSVAR + + # If the type is InitVar, or if it's a matching string annotation, + # then it's an InitVar. + if f._field_type is _FIELD: + # The module we're checking against is the module we're + # currently in (dataclasses.py). + dataclasses = sys.modules[__name__] + if (_is_initvar(a_type, dataclasses) + or (isinstance(f.type, str) + and _is_type(f.type, cls, dataclasses, dataclasses.InitVar, + _is_initvar))): + f._field_type = _FIELD_INITVAR + + # Validations for individual fields. This is delayed until now, + # instead of in the Field() constructor, since only here do we + # know the field name, which allows for better error reporting. + + # Special restrictions for ClassVar and InitVar. + if f._field_type in (_FIELD_CLASSVAR, _FIELD_INITVAR): + if f.default_factory is not MISSING: + raise TypeError(f'field {f.name} cannot have a ' + 'default factory') + # Should I check for other field settings? default_factory + # seems the most serious to check for. Maybe add others. For + # example, how about init=False (or really, + # init=)? It makes no sense for + # ClassVar and InitVar to specify init=. + + # For real fields, disallow mutable defaults for known types. + if f._field_type is _FIELD and isinstance(f.default, (list, dict, set)): + raise ValueError(f'mutable default {type(f.default)} for field ' + f'{f.name} is not allowed: use default_factory') + + return f + + +def _set_new_attribute(cls, name, value): + # Never overwrites an existing attribute. Returns True if the + # attribute already exists. + if name in cls.__dict__: + return True + setattr(cls, name, value) + return False + + +# Decide if/how we're going to create a hash function. Key is +# (unsafe_hash, eq, frozen, does-hash-exist). Value is the action to +# take. The common case is to do nothing, so instead of providing a +# function that is a no-op, use None to signify that. + +def _hash_set_none(cls, fields): + return None + +def _hash_add(cls, fields): + flds = [f for f in fields if (f.compare if f.hash is None else f.hash)] + return _hash_fn(flds) + +def _hash_exception(cls, fields): + # Raise an exception. + raise TypeError(f'Cannot overwrite attribute __hash__ ' + f'in class {cls.__name__}') + +# +# +-------------------------------------- unsafe_hash? +# | +------------------------------- eq? +# | | +------------------------ frozen? +# | | | +---------------- has-explicit-hash? +# | | | | +# | | | | +------- action +# | | | | | +# v v v v v +_hash_action = {(False, False, False, False): None, + (False, False, False, True ): None, + (False, False, True, False): None, + (False, False, True, True ): None, + (False, True, False, False): _hash_set_none, + (False, True, False, True ): None, + (False, True, True, False): _hash_add, + (False, True, True, True ): None, + (True, False, False, False): _hash_add, + (True, False, False, True ): _hash_exception, + (True, False, True, False): _hash_add, + (True, False, True, True ): _hash_exception, + (True, True, False, False): _hash_add, + (True, True, False, True ): _hash_exception, + (True, True, True, False): _hash_add, + (True, True, True, True ): _hash_exception, + } +# See https://bugs.python.org/issue32929#msg312829 for an if-statement +# version of this table. + + +def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen): + # Now that dicts retain insertion order, there's no reason to use + # an ordered dict. I am leveraging that ordering here, because + # derived class fields overwrite base class fields, but the order + # is defined by the base class, which is found first. + fields = {} + + setattr(cls, _PARAMS, _DataclassParams(init, repr, eq, order, + unsafe_hash, frozen)) + + # Find our base classes in reverse MRO order, and exclude + # ourselves. In reversed order so that more derived classes + # override earlier field definitions in base classes. As long as + # we're iterating over them, see if any are frozen. + any_frozen_base = False + has_dataclass_bases = False + for b in cls.__mro__[-1:0:-1]: + # Only process classes that have been processed by our + # decorator. That is, they have a _FIELDS attribute. + base_fields = getattr(b, _FIELDS, None) + if base_fields: + has_dataclass_bases = True + for f in base_fields.values(): + fields[f.name] = f + if getattr(b, _PARAMS).frozen: + any_frozen_base = True + + # Annotations that are defined in this class (not in base + # classes). If __annotations__ isn't present, then this class + # adds no new annotations. We use this to compute fields that are + # added by this class. + # + # Fields are found from cls_annotations, which is guaranteed to be + # ordered. Default values are from class attributes, if a field + # has a default. If the default value is a Field(), then it + # contains additional info beyond (and possibly including) the + # actual default value. Pseudo-fields ClassVars and InitVars are + # included, despite the fact that they're not real fields. That's + # dealt with later. + cls_annotations = cls.__dict__.get('__annotations__', {}) + + # Now find fields in our class. While doing so, validate some + # things, and set the default values (as class attributes) where + # we can. + cls_fields = [_get_field(cls, name, type) + for name, type in cls_annotations.items()] + for f in cls_fields: + fields[f.name] = f + + # If the class attribute (which is the default value for this + # field) exists and is of type 'Field', replace it with the + # real default. This is so that normal class introspection + # sees a real default value, not a Field. + if isinstance(getattr(cls, f.name, None), Field): + if f.default is MISSING: + # If there's no default, delete the class attribute. + # This happens if we specify field(repr=False), for + # example (that is, we specified a field object, but + # no default value). Also if we're using a default + # factory. The class attribute should not be set at + # all in the post-processed class. + delattr(cls, f.name) + else: + setattr(cls, f.name, f.default) + + # Do we have any Field members that don't also have annotations? + for name, value in cls.__dict__.items(): + if isinstance(value, Field) and not name in cls_annotations: + raise TypeError(f'{name!r} is a field but has no type annotation') + + # Check rules that apply if we are derived from any dataclasses. + if has_dataclass_bases: + # Raise an exception if any of our bases are frozen, but we're not. + if any_frozen_base and not frozen: + raise TypeError('cannot inherit non-frozen dataclass from a ' + 'frozen one') + + # Raise an exception if we're frozen, but none of our bases are. + if not any_frozen_base and frozen: + raise TypeError('cannot inherit frozen dataclass from a ' + 'non-frozen one') + + # Remember all of the fields on our class (including bases). This + # also marks this class as being a dataclass. + setattr(cls, _FIELDS, fields) + + # Was this class defined with an explicit __hash__? Note that if + # __eq__ is defined in this class, then python will automatically + # set __hash__ to None. This is a heuristic, as it's possible + # that such a __hash__ == None was not auto-generated, but it + # close enough. + class_hash = cls.__dict__.get('__hash__', MISSING) + has_explicit_hash = not (class_hash is MISSING or + (class_hash is None and '__eq__' in cls.__dict__)) + + # If we're generating ordering methods, we must be generating the + # eq methods. + if order and not eq: + raise ValueError('eq must be true if order is true') + + if init: + # Does this class have a post-init function? + has_post_init = hasattr(cls, _POST_INIT_NAME) + + # Include InitVars and regular fields (so, not ClassVars). + flds = [f for f in fields.values() + if f._field_type in (_FIELD, _FIELD_INITVAR)] + _set_new_attribute(cls, '__init__', + _init_fn(flds, + frozen, + has_post_init, + # The name to use for the "self" + # param in __init__. Use "self" + # if possible. + '__dataclass_self__' if 'self' in fields + else 'self', + )) + + # Get the fields as a list, and include only real fields. This is + # used in all of the following methods. + field_list = [f for f in fields.values() if f._field_type is _FIELD] + + if repr: + flds = [f for f in field_list if f.repr] + _set_new_attribute(cls, '__repr__', _repr_fn(flds)) + + if eq: + # Create _eq__ method. There's no need for a __ne__ method, + # since python will call __eq__ and negate it. + flds = [f for f in field_list if f.compare] + self_tuple = _tuple_str('self', flds) + other_tuple = _tuple_str('other', flds) + _set_new_attribute(cls, '__eq__', + _cmp_fn('__eq__', '==', + self_tuple, other_tuple)) + + if order: + # Create and set the ordering methods. + flds = [f for f in field_list if f.compare] + self_tuple = _tuple_str('self', flds) + other_tuple = _tuple_str('other', flds) + for name, op in [('__lt__', '<'), + ('__le__', '<='), + ('__gt__', '>'), + ('__ge__', '>='), + ]: + if _set_new_attribute(cls, name, + _cmp_fn(name, op, self_tuple, other_tuple)): + raise TypeError(f'Cannot overwrite attribute {name} ' + f'in class {cls.__name__}. Consider using ' + 'functools.total_ordering') + + if frozen: + for fn in _frozen_get_del_attr(cls, field_list): + if _set_new_attribute(cls, fn.__name__, fn): + raise TypeError(f'Cannot overwrite attribute {fn.__name__} ' + f'in class {cls.__name__}') + + # Decide if/how we're going to create a hash function. + hash_action = _hash_action[bool(unsafe_hash), + bool(eq), + bool(frozen), + has_explicit_hash] + if hash_action: + # No need to call _set_new_attribute here, since by the time + # we're here the overwriting is unconditional. + cls.__hash__ = hash_action(cls, field_list) + + if not getattr(cls, '__doc__'): + # Create a class doc-string. + cls.__doc__ = (cls.__name__ + + str(inspect.signature(cls)).replace(' -> None', '')) + + return cls + + +# _cls should never be specified by keyword, so start it with an +# underscore. The presence of _cls is used to detect if this +# decorator is being called with parameters or not. +def dataclass(_cls=None, *, init=True, repr=True, eq=True, order=False, + unsafe_hash=False, frozen=False): + """Returns the same class as was passed in, with dunder methods + added based on the fields defined in the class. + + Examines PEP 526 __annotations__ to determine fields. + + If init is true, an __init__() method is added to the class. If + repr is true, a __repr__() method is added. If order is true, rich + comparison dunder methods are added. If unsafe_hash is true, a + __hash__() method function is added. If frozen is true, fields may + not be assigned to after instance creation. + """ + + def wrap(cls): + return _process_class(cls, init, repr, eq, order, unsafe_hash, frozen) + + # See if we're being called as @dataclass or @dataclass(). + if _cls is None: + # We're called with parens. + return wrap + + # We're called as @dataclass without parens. + return wrap(_cls) + + +def fields(class_or_instance): + """Return a tuple describing the fields of this dataclass. + + Accepts a dataclass or an instance of one. Tuple elements are of + type Field. + """ + + # Might it be worth caching this, per class? + try: + fields = getattr(class_or_instance, _FIELDS) + except AttributeError: + raise TypeError('must be called with a dataclass type or instance') + + # Exclude pseudo-fields. Note that fields is sorted by insertion + # order, so the order of the tuple is as the fields were defined. + return tuple(f for f in fields.values() if f._field_type is _FIELD) + + +def _is_dataclass_instance(obj): + """Returns True if obj is an instance of a dataclass.""" + return not isinstance(obj, type) and hasattr(obj, _FIELDS) + + +def is_dataclass(obj): + """Returns True if obj is a dataclass or an instance of a + dataclass.""" + return hasattr(obj, _FIELDS) + + +def asdict(obj, *, dict_factory=dict): + """Return the fields of a dataclass instance as a new dictionary mapping + field names to field values. + + Example usage: + + @dataclass + class C: + x: int + y: int + + c = C(1, 2) + assert asdict(c) == {'x': 1, 'y': 2} + + If given, 'dict_factory' will be used instead of built-in dict. + The function applies recursively to field values that are + dataclass instances. This will also look into built-in containers: + tuples, lists, and dicts. + """ + if not _is_dataclass_instance(obj): + raise TypeError("asdict() should be called on dataclass instances") + return _asdict_inner(obj, dict_factory) + + +def _asdict_inner(obj, dict_factory): + if _is_dataclass_instance(obj): + result = [] + for f in fields(obj): + value = _asdict_inner(getattr(obj, f.name), dict_factory) + result.append((f.name, value)) + return dict_factory(result) + elif isinstance(obj, (list, tuple)): + return type(obj)(_asdict_inner(v, dict_factory) for v in obj) + elif isinstance(obj, dict): + return type(obj)((_asdict_inner(k, dict_factory), _asdict_inner(v, dict_factory)) + for k, v in obj.items()) + else: + return copy.deepcopy(obj) + + +def astuple(obj, *, tuple_factory=tuple): + """Return the fields of a dataclass instance as a new tuple of field values. + + Example usage:: + + @dataclass + class C: + x: int + y: int + + c = C(1, 2) + assert astuple(c) == (1, 2) + + If given, 'tuple_factory' will be used instead of built-in tuple. + The function applies recursively to field values that are + dataclass instances. This will also look into built-in containers: + tuples, lists, and dicts. + """ + + if not _is_dataclass_instance(obj): + raise TypeError("astuple() should be called on dataclass instances") + return _astuple_inner(obj, tuple_factory) + + +def _astuple_inner(obj, tuple_factory): + if _is_dataclass_instance(obj): + result = [] + for f in fields(obj): + value = _astuple_inner(getattr(obj, f.name), tuple_factory) + result.append(value) + return tuple_factory(result) + elif isinstance(obj, (list, tuple)): + return type(obj)(_astuple_inner(v, tuple_factory) for v in obj) + elif isinstance(obj, dict): + return type(obj)((_astuple_inner(k, tuple_factory), _astuple_inner(v, tuple_factory)) + for k, v in obj.items()) + else: + return copy.deepcopy(obj) + + +def make_dataclass(cls_name, fields, *, bases=(), namespace=None, init=True, + repr=True, eq=True, order=False, unsafe_hash=False, + frozen=False): + """Return a new dynamically created dataclass. + + The dataclass name will be 'cls_name'. 'fields' is an iterable + of either (name), (name, type) or (name, type, Field) objects. If type is + omitted, use the string 'typing.Any'. Field objects are created by + the equivalent of calling 'field(name, type [, Field-info])'. + + C = make_dataclass('C', ['x', ('y', int), ('z', int, field(init=False))], bases=(Base,)) + + is equivalent to: + + @dataclass + class C(Base): + x: 'typing.Any' + y: int + z: int = field(init=False) + + For the bases and namespace parameters, see the builtin type() function. + + The parameters init, repr, eq, order, unsafe_hash, and frozen are passed to + dataclass(). + """ + + if namespace is None: + namespace = {} + else: + # Copy namespace since we're going to mutate it. + namespace = namespace.copy() + + # While we're looking through the field names, validate that they + # are identifiers, are not keywords, and not duplicates. + seen = set() + anns = {} + for item in fields: + if isinstance(item, str): + name = item + tp = 'typing.Any' + elif len(item) == 2: + name, tp, = item + elif len(item) == 3: + name, tp, spec = item + namespace[name] = spec + else: + raise TypeError(f'Invalid field: {item!r}') + + if not isinstance(name, str) or not name.isidentifier(): + raise TypeError(f'Field names must be valid identifers: {name!r}') + if keyword.iskeyword(name): + raise TypeError(f'Field names must not be keywords: {name!r}') + if name in seen: + raise TypeError(f'Field name duplicated: {name!r}') + + seen.add(name) + anns[name] = tp + + namespace['__annotations__'] = anns + # We use `types.new_class()` instead of simply `type()` to allow dynamic creation + # of generic dataclassses. + cls = types.new_class(cls_name, bases, {}, lambda ns: ns.update(namespace)) + return dataclass(cls, init=init, repr=repr, eq=eq, order=order, + unsafe_hash=unsafe_hash, frozen=frozen) + + +def replace(obj, **changes): + """Return a new object replacing specified fields with new values. + + This is especially useful for frozen classes. Example usage: + + @dataclass(frozen=True) + class C: + x: int + y: int + + c = C(1, 2) + c1 = replace(c, x=3) + assert c1.x == 3 and c1.y == 2 + """ + + # We're going to mutate 'changes', but that's okay because it's a + # new dict, even if called with 'replace(obj, **my_changes)'. + + if not _is_dataclass_instance(obj): + raise TypeError("replace() should be called on dataclass instances") + + # It's an error to have init=False fields in 'changes'. + # If a field is not in 'changes', read its value from the provided obj. + + for f in getattr(obj, _FIELDS).values(): + # Only consider normal fields or InitVars. + if f._field_type is _FIELD_CLASSVAR: + continue + + if not f.init: + # Error if this field is specified in changes. + if f.name in changes: + raise ValueError(f'field {f.name} is declared with ' + 'init=False, it cannot be specified with ' + 'replace()') + continue + + if f.name not in changes: + if f._field_type is _FIELD_INITVAR: + raise ValueError(f"InitVar {f.name!r} " + 'must be specified with replace()') + changes[f.name] = getattr(obj, f.name) + + # Create the new object, which calls __init__() and + # __post_init__() (if defined), using all of the init fields we've + # added and/or left in 'changes'. If there are values supplied in + # changes that aren't fields, this will correctly raise a + # TypeError. + return obj.__class__(**changes) diff --git a/.venv/lib/python3.6/site-packages/easy_install.py b/.venv/lib/python3.6/site-packages/easy_install.py new file mode 100644 index 0000000..d87e984 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/easy_install.py @@ -0,0 +1,5 @@ +"""Run the EasyInstall command""" + +if __name__ == '__main__': + from setuptools.command.easy_install import main + main() diff --git a/.venv/lib/python3.6/site-packages/flask/__init__.py b/.venv/lib/python3.6/site-packages/flask/__init__.py new file mode 100644 index 0000000..43b5468 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/flask/__init__.py @@ -0,0 +1,46 @@ +from markupsafe import escape +from markupsafe import Markup +from werkzeug.exceptions import abort as abort +from werkzeug.utils import redirect as redirect + +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 _app_ctx_stack as _app_ctx_stack +from .globals import _request_ctx_stack as _request_ctx_stack +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 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 safe_join as safe_join +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 + +__version__ = "2.0.2" diff --git a/.venv/lib/python3.6/site-packages/flask/__main__.py b/.venv/lib/python3.6/site-packages/flask/__main__.py new file mode 100644 index 0000000..4e28416 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/flask/__main__.py @@ -0,0 +1,3 @@ +from .cli import main + +main() diff --git a/.venv/lib/python3.6/site-packages/flask/__pycache__/__init__.cpython-36.pyc b/.venv/lib/python3.6/site-packages/flask/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c1e922ad06ee182aaf81933a317335eed158a1b6 GIT binary patch literal 1893 zcmbu9xo#sz5Qce39EzuSh`R5SlE~3YUfF?R7&hQ+9jpNdl{AK&CONB~!`)3C`$m2M zKZO&=N*(PBIB?{uA}J9{O@Y6@`K#DdUDY!;<#OS#fBx|P%%@U+r{+Ex=;1z2ESP>W#>a>$E*PB~#7^30A}3JXwR&LbD0$h?GHf)aB< zmBR{Dn2V|!F2gc&N!7wS)S1i34QMb|kekqCt|G6%3iGmRg{!d2Tti-iHRd|94QrqDU>@cq)@4_zg8uA|OF|Q-k!8_(Q@-DO z9g}wYIVVluOC-?;Pv4Cbq(!e6Ym1QA(hs!k+b`)!_QEF{^v0HYP!7F2iMM}kitz?ge2N3=;F6J%F9w04 z)W+mO$Dx??JJwHZTP?j%JOp+$w@YWNl^DbtGaQ;nFrefb?q%9Dk;F+N{kP`WjM!YA z%e2zNDGh}mKTuVzp2UTz>d2=-P4|V7m0|ok8~NCmiJf$)I&H?RIheaVo6I1Hg6U1G zGY==DE!WYUS#OG{nP3=s3TJ-r15p3ADk_i`U(&?)A^@ zZ=ewJIj3Q0dO_+e3N#+1zWXf4!|u!Gj9#EfxB5k%1x;-8DN7zDlh^KhmbA5SFr@mu6d}d@P zd9tH8>hwGl^xf{gjP6|(^xTPk)4g>~U@;4yW_sXUdIIlUA@E>XA-c)abN<^f_Qy(o VN3SUJD@rNNf0^n3$<_bW%zsv%3e5lj literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/flask/__pycache__/__main__.cpython-36.pyc b/.venv/lib/python3.6/site-packages/flask/__pycache__/__main__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..40ab7f09213aa07740cb44de2120ce19d7b874b8 GIT binary patch literal 224 zcmX|5F%E)25ZprxW2CjTr+_U;tc>vmHg-1T;1=)V9S{yM*1pCE_z!DZD?eai<)Mk2 z%*&J$xVjFn< zDATGQPOngpoqXv{C`&__)@3G7RO3N4vQ1G?mn%)7<)(5{of}yeValz_FzTr2P^%Pw p<2YKPRgH7;?8pyfG1uH4F-eBkNfK4(ezG#9Xf>^eRCY))^F9(jJaPa4 literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/flask/__pycache__/app.cpython-36.pyc b/.venv/lib/python3.6/site-packages/flask/__pycache__/app.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..737bd548255b67aa11f5b5a4fb91b121957504b4 GIT binary patch literal 62892 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 L$d2|O>HYr!6rkBB literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/flask/__pycache__/blueprints.cpython-36.pyc b/.venv/lib/python3.6/site-packages/flask/__pycache__/blueprints.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a4ff72b173ffe9a2954281b4a146485a080ed746 GIT binary patch literal 21949 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 literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/flask/__pycache__/cli.cpython-36.pyc b/.venv/lib/python3.6/site-packages/flask/__pycache__/cli.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..73a0523491b66bf566b2686dcd4be4e5de2465e0 GIT binary patch literal 26935 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 literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/flask/__pycache__/config.cpython-36.pyc b/.venv/lib/python3.6/site-packages/flask/__pycache__/config.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3df545fb71827c919c3a6027fdab9f60ac4020bb GIT binary patch literal 11616 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 literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/flask/__pycache__/helpers.cpython-36.pyc b/.venv/lib/python3.6/site-packages/flask/__pycache__/helpers.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..80d5e8cbc4d0bae8c6928eb3eab853885c8a9925 GIT binary patch literal 27048 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 OPMkXW_|X$J=l=qc`8M7F literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/flask/__pycache__/logging.cpython-36.pyc b/.venv/lib/python3.6/site-packages/flask/__pycache__/logging.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..538f293c8adaae16659b07ec54c77055357ced39 GIT binary patch literal 2454 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 aJ1iX)_hfP6==i6F^xx}~)#{1rtN#c1KnJY= literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/flask/__pycache__/sessions.cpython-36.pyc b/.venv/lib/python3.6/site-packages/flask/__pycache__/sessions.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..02a1003b6bf1a5f86460d98d5c8d1544ae2b8442 GIT binary patch literal 13073 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 literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/flask/__pycache__/templating.cpython-36.pyc b/.venv/lib/python3.6/site-packages/flask/__pycache__/templating.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..193ed5b4cae1b51f5d60d1c770eda80c9c8f34ec GIT binary patch literal 5503 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 CNJM)8 literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/flask/__pycache__/testing.cpython-36.pyc b/.venv/lib/python3.6/site-packages/flask/__pycache__/testing.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eace7237f081b5be434989114a7b8bedda3d6035 GIT binary patch literal 8869 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 literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/flask/__pycache__/typing.cpython-36.pyc b/.venv/lib/python3.6/site-packages/flask/__pycache__/typing.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6a6da451e1c3c667c20490e69bb41186daf9ab2c GIT binary patch literal 1767 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 E0Zj5-Qvd(} literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/flask/__pycache__/wrappers.cpython-36.pyc b/.venv/lib/python3.6/site-packages/flask/__pycache__/wrappers.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f3b0cf1f2dfc26c2b9cb4b02fb3cc9fd672e06cc GIT binary patch literal 4955 zcmai1OOGQ-5w0p%``zx>yd{PLMp_Iz_C5qc7Oa>ZdS{xQ^*~Rr7V1(iSElXiu7@`( z-92s1?n>Ju_QoGz5N9N={0R;#&UJ;$iaQ5Re3A9&o*oaBRq3p(tjNeOBEN`ucYVG2 z_kaJ@{pVFp`-gVnGSUAz?&SAqn8x%#vvvF%fe}{hicVvdzzo;ys_L8Q*X%m_YpfbH z!lvC+{aVlp+jd*^>%n^1u{*lP^}`KusHAS%TljAT+u@GAqiYW|)?}^c8f)?DS;bz% zbDOQ>d7ansTx0qZt=stqI%&hMo;Kfi**^bt!4rw6+IhFW&y#r^CHxy5Q}NC`OyUUt zEpH(NkEG+y=Xkv8B=f+R&Nx``x$rSQcjb)c?=J%G(K^LdBJwwkaD|3#d<GoDW z-pa>otggnlFO4@?Q;qMa@hiNT-(qbwew7(Y?mFu{H|=Y@!#3FF3xjR3Z9HFR2GHCQ z6>P};8bBS)xFs?;t$2LMJ!vfx&aCmN#rVWs1k&;&>w~~ej(V20&qE%LxkxNI<4p@! z=t^&f`8;Ct7+_j%#H?eVpIEM#E<(Vb^qOks9wa5!$;_2BIk6UV%T26-2hqu3^!Y?B zk*hMoT6+`gG+tOIE`-V$O3nk<%NE7rE0w1Co*P-oVywhMODstmC0r^c2CkH%TrJyH zzNVu-8PQshBT6B;`YV7U+2F-*k>Hy2Z0q& z2u+JL^T!K`(NA(wWCB0Jz&u#cS`)ykbXG!=IrsdD?^!XX^wiK$G+&4<>)s#5#um!~3&1r)ANLbqVwUS2 zxl^9>l_&K}E6h*R?a+PdKm$oE5b$U!XFnn!s%W%|sW*+~t=w3>SEs%n#An(e$l$0U zwyAdrqEcjKxFAb)vdU8-$?c1?a?yQ1-hY%k7@ejA^4Aj|PBG@zXf%Xn0kS{IKJ}xi zHE{t9vtnV*aC)LxVkVE2@ks#uZW4L-#5=pE5^a$nEmg;hp~%RsKO5Mfd%Nkm7cnY%)gP`5;} z6!|9M2E!79$}e7T+;gLrVpbTJ#o!5AG~yml5?A;*!Ai0+m)alcSZV-fO-g~D;I0G= z;9xwrxplezzBP8VosooPrKp+FMaoQ9F zj89I8A>|4*e#sxwMycu}yiMw8w070l)H}vNo6MqO-A;RX_Jaz<%| zvHaHoFuAZNQayRCBOqvhw|Z}v0*H;T01!b?=E$ce?f5eE z&GU^on44kWtQvLQG?v@1V4ti8|2=$4G3J?ert6wC#6TK{6)}K8Oq|lJx7OCg069w~ zsD`!AFz;DDpTSUFbGY0Ik%ai1_l}*PA&!^5p59J6woZIO+rD zd$LoFI0YLt-2yM#ZKz#>b0kS`Yn@lazFgf=^<%)%c7D6wJl-bIer# z5@g~g-p_F-#ATY^Tr>5mP8`-Yme&i!F0H?8N!}aA`zlt0D|IfK_}C*}ql^A4xK-5t zE;@+XHwaO*27?p-LHi@P?Mq!6XWEy@)ybuY%iAAhK?0c!18XyG{qo7R%tZqfc zWI<;`&=SQV{1y#RT+SNWNO~=f9Ll4W=HMtN3qW7sUB=VcBhKUPVu!1zcW0$6@>P&Ft)dF6m4 z{?`knW}s7KUMibsarB1FK8BDi>R^4y&5I98y(vRP-0@tfujCDIFlU#t_Ru}zPNDHA zOPVCckiURWRU}8sg`-aE<|MhdvZSE>BLuvRphE7o^@P%jkXT($WTO60L2=I3-pq}r zim%`9y`@A`*idISS)CY$~&3C2Bs|*vUFhCko>9a&tw=N z2~=E1i2+^oU+~?E3WXT%u%bftpH^2W1JZermnn}w+}m}Y?Ag07l_*&uO5i@>ywLRX zRfSt-!Mqqa-vw&na=S&5_C{iXwDJOsWZN5AAyn$nH8a9(VTF+#4Sc3V+3jMVDu!!WaHvg#c?lbM$?eT@;XGQX+Ibb#r}dv^QRqE#QstZo>pFgUTh;4& IyRv!wUr=pcVE_OC literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/flask/app.py b/.venv/lib/python3.6/site-packages/flask/app.py new file mode 100644 index 0000000..23b99e2 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/flask/app.py @@ -0,0 +1,2091 @@ +import functools +import inspect +import logging +import os +import sys +import typing as t +import weakref +from datetime import timedelta +from itertools import chain +from threading import Lock +from types import TracebackType + +from werkzeug.datastructures import Headers +from werkzeug.datastructures import ImmutableDict +from werkzeug.exceptions import BadRequest +from werkzeug.exceptions import BadRequestKeyError +from werkzeug.exceptions import HTTPException +from werkzeug.exceptions import InternalServerError +from werkzeug.local import ContextVar +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.wrappers import Response as BaseResponse + +from . import cli +from . import json +from .config import Config +from .config import ConfigAttribute +from .ctx import _AppCtxGlobals +from .ctx import AppContext +from .ctx import RequestContext +from .globals import _request_ctx_stack +from .globals import g +from .globals import request +from .globals import session +from .helpers import _split_blueprint_path +from .helpers import get_debug_flag +from .helpers import get_env +from .helpers import get_flashed_messages +from .helpers import get_load_dotenv +from .helpers import locked_cached_property +from .helpers import url_for +from .json import jsonify +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 .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 .typing import BeforeFirstRequestCallable +from .typing import ResponseReturnValue +from .typing import TeardownCallable +from .typing import TemplateFilterCallable +from .typing import TemplateGlobalCallable +from .typing import TemplateTestCallable +from .wrappers import Request +from .wrappers import Response + +if t.TYPE_CHECKING: + import typing_extensions as te + from .blueprints import Blueprint + from .testing import FlaskClient + from .testing import FlaskCliRunner + from .typing import ErrorHandlerCallable + +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.Optional[timedelta]) -> 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 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") + + #: The secure cookie uses this for the name of the session cookie. + #: + #: This attribute can also be configured from the config with the + #: ``SESSION_COOKIE_NAME`` configuration key. Defaults to ``'session'`` + session_cookie_name = ConfigAttribute("SESSION_COOKIE_NAME") + + #: 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 + ) + + #: A :class:`~datetime.timedelta` or number of seconds which is used + #: as the default ``max_age`` for :func:`send_file`. The default is + #: ``None``, which tells the browser to use conditional requests + #: instead of a timed cache. + #: + #: Configured with the :data:`SEND_FILE_MAX_AGE_DEFAULT` + #: configuration key. + #: + #: .. versionchanged:: 2.0 + #: Defaults to ``None`` instead of 12 hours. + send_file_max_age_default = ConfigAttribute( + "SEND_FILE_MAX_AGE_DEFAULT", get_converter=_make_timedelta + ) + + #: Enable this if you want to use the X-Sendfile feature. Keep in + #: mind that the server has to support this. This only affects files + #: sent with the :func:`send_file` method. + #: + #: .. versionadded:: 0.2 + #: + #: This attribute can also be configured from the config with the + #: ``USE_X_SENDFILE`` configuration key. Defaults to ``False``. + use_x_sendfile = ConfigAttribute("USE_X_SENDFILE") + + #: The JSON encoder class to use. Defaults to :class:`~flask.json.JSONEncoder`. + #: + #: .. versionadded:: 0.10 + json_encoder = json.JSONEncoder + + #: The JSON decoder class to use. Defaults to :class:`~flask.json.JSONDecoder`. + #: + #: .. versionadded:: 0.10 + json_decoder = json.JSONDecoder + + #: 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, + "PRESERVE_CONTEXT_ON_EXCEPTION": 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": True, + "JSON_SORT_KEYS": True, + "JSONIFY_PRETTYPRINT_REGULAR": False, + "JSONIFY_MIMETYPE": "application/json", + "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 = 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) + + #: A list of functions that are called when :meth:`url_for` raises a + #: :exc:`~werkzeug.routing.BuildError`. Each function registered here + #: is called with `error`, `endpoint` and `values`. If a function + #: returns ``None`` or raises a :exc:`BuildError` the next function is + #: tried. + #: + #: .. versionadded:: 0.9 + self.url_build_error_handlers: t.List[ + t.Callable[[Exception, str, dict], 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. + #: + #: .. versionadded:: 0.8 + self.before_first_request_funcs: t.List[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[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[t.Callable[[], t.Dict[str, t.Any]]] = [] + + #: 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 _is_setup_finished(self) -> bool: + return self.debug and self._got_first_request + + @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. + + .. versionadded:: 0.7 + """ + rv = self.config["PROPAGATE_EXCEPTIONS"] + if rv is not None: + return rv + return self.testing or self.debug + + @property + def preserve_context_on_exception(self) -> bool: + """Returns the value of the ``PRESERVE_CONTEXT_ON_EXCEPTION`` + configuration value in case it's set, otherwise a sensible default + is returned. + + .. versionadded:: 0.7 + """ + rv = self.config["PRESERVE_CONTEXT_ON_EXCEPTION"] + if rv is not None: + return rv + return 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"] = get_env() + defaults["DEBUG"] = get_debug_flag() + return self.config_class(root_path, defaults) + + 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`. + + This attribute can be configured with :data:`TEMPLATES_AUTO_RELOAD`. If + not set, it will be enabled in debug mode. + + .. versionadded:: 1.0 + This property was added but the underlying config and behavior + already existed. + """ + 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: + 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: + options["auto_reload"] = self.templates_auto_reload + + rv = self.jinja_environment(self, **options) + rv.globals.update( + url_for=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"] = 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 + + #: What environment the app is running in. Flask and extensions may + #: enable behaviors based on the environment, such as enabling debug + #: mode. This maps to the :data:`ENV` config key. This is set by the + #: :envvar:`FLASK_ENV` environment variable and may not behave as + #: expected if set in code. + #: + #: **Do not enable development when deploying in production.** + #: + #: Default: ``'production'`` + env = ConfigAttribute("ENV") + + @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. This is + enabled when :attr:`env` is ``'development'`` and is overridden + by the ``FLASK_DEBUG`` environment variable. It may not behave as + expected if set in code. + + **Do not enable debug mode when deploying in production.** + + Default: ``True`` if :attr:`env` is ``'development'``, or + ``False`` otherwise. + """ + return self.config["DEBUG"] + + @debug.setter + def debug(self, value: bool) -> None: + self.config["DEBUG"] = value + self.jinja_env.auto_reload = self.templates_auto_reload + + 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. + + If set, the :envvar:`FLASK_ENV` and :envvar:`FLASK_DEBUG` + environment variables will override :attr:`env` and + :attr:`debug`. + + Threaded mode is enabled by default. + + .. versionchanged:: 0.10 + The default port is now picked from the ``SERVER_NAME`` + variable. + """ + # Change this into a no-op if the server is invoked from the + # command line. Have a look at cli.py for more information. + if os.environ.get("FLASK_RUN_FROM_CLI") == "true": + from .debughelpers import explain_ignored_app_run + + explain_ignored_app_run() + return + + if get_load_dotenv(load_dotenv): + cli.load_dotenv() + + # if set, let env vars override previous values + if "FLASK_ENV" in os.environ: + self.env = get_env() + 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.env, self.debug, self.name, False) + + 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[t.Callable] = 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[[TemplateFilterCallable], TemplateFilterCallable]: + """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: TemplateFilterCallable) -> TemplateFilterCallable: + self.add_template_filter(f, name=name) + return f + + return decorator + + @setupmethod + def add_template_filter( + self, f: 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[[TemplateTestCallable], TemplateTestCallable]: + """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: TemplateTestCallable) -> TemplateTestCallable: + self.add_template_test(f, name=name) + return f + + return decorator + + @setupmethod + def add_template_test( + self, f: 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[[TemplateGlobalCallable], TemplateGlobalCallable]: + """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: TemplateGlobalCallable) -> TemplateGlobalCallable: + self.add_template_global(f, name=name) + return f + + return decorator + + @setupmethod + def add_template_global( + self, f: 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: BeforeFirstRequestCallable + ) -> BeforeFirstRequestCallable: + """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. + + .. versionadded:: 0.8 + """ + self.before_first_request_funcs.append(f) + return f + + @setupmethod + def teardown_appcontext(self, f: TeardownCallable) -> TeardownCallable: + """Registers a function to be called when the application context + ends. These functions are typically also called when the request + context is popped. + + Example:: + + ctx = app.app_context() + ctx.push() + ... + ctx.pop() + + When ``ctx.pop()`` is executed in the above example, the teardown + functions are called just before the app context moves from the + stack of active contexts. This becomes relevant if you are using + such constructs in tests. + + 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. + + 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.Callable) -> t.Callable: + """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["ErrorHandlerCallable[Exception]"]: + """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, 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, 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) + + if self.propagate_exceptions: + # 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, 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": + """Exceptions that are recording during routing are reraised with + this method. During debug we are not reraising redirect requests + for non ``GET``, ``HEAD``, or ``OPTIONS`` requests and we're raising + a different error instead to help debug situations. + + :internal: + """ + if ( + not self.debug + or not isinstance(request.routing_exception, RequestRedirect) + or request.method in ("GET", "HEAD", "OPTIONS") + ): + raise request.routing_exception # type: ignore + + from .debughelpers import FormDataRoutingRedirect + + raise FormDataRoutingRedirect(request) + + def dispatch_request(self) -> 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_stack.top.request + if req.routing_exception is not None: + self.raise_routing_exception(req) + rule = req.url_rule + # 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 + return self.ensure_sync(self.view_functions[rule.endpoint])(**req.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 + """ + self.try_trigger_before_first_request_functions() + 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[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 try_trigger_before_first_request_functions(self) -> None: + """Called before each request and will ensure that it triggers + the :attr:`before_first_request_funcs` and only exactly once per + application instance (which means process usually). + + :internal: + """ + if self._got_first_request: + return + with self._before_request_lock: + if self._got_first_request: + return + for func in self.before_first_request_funcs: + self.ensure_sync(func)() + self._got_first_request = True + + 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_stack.top.url_adapter + methods = adapter.allowed_methods() + 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 + + # Check that Werkzeug isn't using its fallback ContextVar class. + if ContextVar.__module__ == "werkzeug.local": + raise RuntimeError( + "Async cannot be used with this combination of Python " + "and Greenlet versions." + ) + + return asgiref_async_to_sync(func) + + def make_response(self, rv: 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. + + ``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:: 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 + # 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 + # 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)): + # 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) + status = headers = None + elif isinstance(rv, dict): + rv = jsonify(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 # noqa: B950 + 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, tuple, Response instance, or WSGI" + f" callable, but it 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, tuple, Response instance, or WSGI" + f" callable, but it was a {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 # type: ignore + else: + rv.status_code = status + + # extend existing headers with provided headers + if headers: + rv.headers.update(headers) + + 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: Exception, endpoint: str, values: dict + ) -> str: + """Handle :class:`~werkzeug.routing.BuildError` on + :meth:`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[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_stack.top + + 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 self.should_ignore_error(error): + error = None + ctx.auto_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/.venv/lib/python3.6/site-packages/flask/blueprints.py b/.venv/lib/python3.6/site-packages/flask/blueprints.py new file mode 100644 index 0000000..5c23a73 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/flask/blueprints.py @@ -0,0 +1,609 @@ +import os +import typing as t +from collections import defaultdict +from functools import update_wrapper + +from .scaffold import _endpoint_from_view_func +from .scaffold import _sentinel +from .scaffold import Scaffold +from .typing import AfterRequestCallable +from .typing import BeforeFirstRequestCallable +from .typing import BeforeRequestCallable +from .typing import TeardownCallable +from .typing import TemplateContextProcessorCallable +from .typing import TemplateFilterCallable +from .typing import TemplateGlobalCallable +from .typing import TemplateTestCallable +from .typing import URLDefaultCallable +from .typing import URLValuePreprocessorCallable + +if t.TYPE_CHECKING: + from .app import Flask + from .typing import ErrorHandlerCallable + +DeferredSetupFunction = t.Callable[["BlueprintSetupState"], t.Callable] + + +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 + """ + + warn_on_modifications = False + _got_registered_once = False + + #: Blueprint local JSON encoder class to use. Set to ``None`` to use + #: the app's :class:`~flask.Flask.json_encoder`. + json_encoder = None + #: Blueprint local JSON decoder class to use. Set to ``None`` to use + #: the app's :class:`~flask.Flask.json_decoder`. + json_decoder = None + + 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 _is_setup_finished(self) -> bool: + return self.warn_on_modifications and self._got_registered_once + + 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. + """ + if self._got_registered_once and self.warn_on_modifications: + from warnings import warn + + warn( + Warning( + "The blueprint was already registered once but is" + " getting modified now. These changes will not show" + " up." + ) + ) + self.deferred_functions.append(func) + + 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) + + return 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) + + 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: + existing_at = f" '{name}'" if self_name != name else "" + + if app.blueprints[name] is not self: + raise ValueError( + f"The name '{self_name}' is already registered for" + f" a different blueprint{existing_at}. Use 'name='" + " to provide a unique name." + ) + else: + import warnings + + warnings.warn( + f"The name '{self_name}' is already registered for" + f" this blueprint{existing_at}. Use 'name=' to" + " provide a unique name. This will become an error" + " in Flask 2.1.", + stacklevel=4, + ) + + 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) + + def add_url_rule( + self, + rule: str, + endpoint: t.Optional[str] = None, + view_func: t.Optional[t.Callable] = 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, + ) + ) + + def app_template_filter( + self, name: t.Optional[str] = None + ) -> t.Callable[[TemplateFilterCallable], TemplateFilterCallable]: + """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: TemplateFilterCallable) -> TemplateFilterCallable: + self.add_app_template_filter(f, name=name) + return f + + return decorator + + def add_app_template_filter( + self, f: 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) + + def app_template_test( + self, name: t.Optional[str] = None + ) -> t.Callable[[TemplateTestCallable], TemplateTestCallable]: + """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: TemplateTestCallable) -> TemplateTestCallable: + self.add_app_template_test(f, name=name) + return f + + return decorator + + def add_app_template_test( + self, f: 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) + + def app_template_global( + self, name: t.Optional[str] = None + ) -> t.Callable[[TemplateGlobalCallable], TemplateGlobalCallable]: + """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: TemplateGlobalCallable) -> TemplateGlobalCallable: + self.add_app_template_global(f, name=name) + return f + + return decorator + + def add_app_template_global( + self, f: 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) + + def before_app_request(self, f: BeforeRequestCallable) -> BeforeRequestCallable: + """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 + + def before_app_first_request( + self, f: BeforeFirstRequestCallable + ) -> BeforeFirstRequestCallable: + """Like :meth:`Flask.before_first_request`. Such a function is + executed before the first request to the application. + """ + self.record_once(lambda s: s.app.before_first_request_funcs.append(f)) + return f + + def after_app_request(self, f: AfterRequestCallable) -> AfterRequestCallable: + """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 + + def teardown_app_request(self, f: TeardownCallable) -> TeardownCallable: + """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 + + def app_context_processor( + self, f: TemplateContextProcessorCallable + ) -> TemplateContextProcessorCallable: + """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 + + def app_errorhandler(self, code: t.Union[t.Type[Exception], int]) -> t.Callable: + """Like :meth:`Flask.errorhandler` but for a blueprint. This + handler is used for all requests, even if outside of the blueprint. + """ + + def decorator( + f: "ErrorHandlerCallable[Exception]", + ) -> "ErrorHandlerCallable[Exception]": + self.record_once(lambda s: s.app.errorhandler(code)(f)) + return f + + return decorator + + def app_url_value_preprocessor( + self, f: URLValuePreprocessorCallable + ) -> URLValuePreprocessorCallable: + """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 + + def app_url_defaults(self, f: URLDefaultCallable) -> URLDefaultCallable: + """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/.venv/lib/python3.6/site-packages/flask/cli.py b/.venv/lib/python3.6/site-packages/flask/cli.py new file mode 100644 index 0000000..7ab4fa1 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/flask/cli.py @@ -0,0 +1,998 @@ +import ast +import inspect +import os +import platform +import re +import sys +import traceback +import warnings +from functools import update_wrapper +from operator import attrgetter +from threading import Lock +from threading import Thread + +import click +from werkzeug.utils import import_string + +from .globals import current_app +from .helpers import get_debug_flag +from .helpers import get_env +from .helpers import get_load_dotenv + +try: + import dotenv +except ImportError: + dotenv = None + +try: + import ssl +except ImportError: + ssl = None # type: ignore + + +class NoAppException(click.UsageError): + """Raised if an application cannot be found or loaded.""" + + +def find_best_app(script_info, 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__!r}. Use 'FLASK_APP={module.__name__}:name'" + f" 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 = call_factory(script_info, 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!r} in module {module.__name__!r}," + " but could not call it without arguments. Use" + f" \"FLASK_APP='{module.__name__}:{attr_name}(args)'\"" + " to specify arguments." + ) from e + + raise NoAppException( + "Failed to find Flask application or factory in module" + f" {module.__name__!r}. Use 'FLASK_APP={module.__name__}:name'" + " to specify one." + ) + + +def call_factory(script_info, app_factory, args=None, kwargs=None): + """Takes an app factory, a ``script_info` object and optionally a tuple + of arguments. Checks for the existence of a script_info argument and calls + the app_factory depending on that and the arguments provided. + """ + sig = inspect.signature(app_factory) + args = [] if args is None else args + kwargs = {} if kwargs is None else kwargs + + if "script_info" in sig.parameters: + warnings.warn( + "The 'script_info' argument is deprecated and will not be" + " passed to the app factory function in Flask 2.1.", + DeprecationWarning, + ) + kwargs["script_info"] = script_info + + if not args and len(sig.parameters) == 1: + first_parameter = next(iter(sig.parameters.values())) + + if ( + first_parameter.default is inspect.Parameter.empty + # **kwargs is reported as an empty default, ignore it + and first_parameter.kind is not inspect.Parameter.VAR_KEYWORD + ): + warnings.warn( + "Script info is deprecated and will not be passed as the" + " single argument to the app factory function in Flask" + " 2.1.", + DeprecationWarning, + ) + args.append(script_info) + + return app_factory(*args, **kwargs) + + +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(script_info, 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 = None + 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 = call_factory(script_info, 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(script_info, module_name, app_name, raise_if_not_found=True): + __traceback_hide__ = True # noqa: F841 + + try: + __import__(module_name) + except ImportError as e: + # 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 raised." + ) from e + elif raise_if_not_found: + raise NoAppException(f"Could not import {module_name!r}.") from e + else: + return + + module = sys.modules[module_name] + + if app_name is None: + return find_best_app(script_info, module) + else: + return find_app_by_string(script_info, 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 DispatchingApp: + """Special application that dispatches to a Flask application which + is imported by name in a background thread. If an error happens + it is recorded and shown as part of the WSGI handling which in case + of the Werkzeug debugger means that it shows up in the browser. + """ + + def __init__(self, loader, use_eager_loading=None): + self.loader = loader + self._app = None + self._lock = Lock() + self._bg_loading_exc = None + + if use_eager_loading is None: + use_eager_loading = os.environ.get("WERKZEUG_RUN_MAIN") != "true" + + if use_eager_loading: + self._load_unlocked() + else: + self._load_in_background() + + def _load_in_background(self): + def _load_app(): + __traceback_hide__ = True # noqa: F841 + with self._lock: + try: + self._load_unlocked() + except Exception as e: + self._bg_loading_exc = e + + t = Thread(target=_load_app, args=()) + t.start() + + def _flush_bg_loading_exception(self): + __traceback_hide__ = True # noqa: F841 + exc = self._bg_loading_exc + + if exc is not None: + self._bg_loading_exc = None + raise exc + + def _load_unlocked(self): + __traceback_hide__ = True # noqa: F841 + self._app = rv = self.loader() + self._bg_loading_exc = None + return rv + + def __call__(self, environ, start_response): + __traceback_hide__ = True # noqa: F841 + if self._app is not None: + return self._app(environ, start_response) + self._flush_bg_loading_exception() + with self._lock: + if self._app is not None: + rv = self._app + else: + rv = self._load_unlocked() + return rv(environ, start_response) + + +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=None, create_app=None, set_debug_flag=True): + #: Optionally the import path for the Flask application. + self.app_import_path = app_import_path or os.environ.get("FLASK_APP") + #: 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 = {} + self.set_debug_flag = set_debug_flag + self._loaded_app = None + + def load_app(self): + """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. + """ + __traceback_hide__ = True # noqa: F841 + + if self._loaded_app is not None: + return self._loaded_app + + if self.create_app is not None: + app = call_factory(self, 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(self, import_name, name) + else: + for path in ("wsgi.py", "app.py"): + import_name = prepare_import(path) + app = locate_app(self, import_name, None, raise_if_not_found=False) + + if app: + break + + if not app: + raise NoAppException( + "Could not locate a Flask application. You did not provide " + 'the "FLASK_APP" environment variable, and a "wsgi.py" or ' + '"app.py" module was not found 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. If callbacks are registered directly + to the ``app.cli`` object then they are wrapped with this function + by default unless it's disabled. + """ + + @click.pass_context + def decorator(__ctx, *args, **kwargs): + with __ctx.ensure_object(ScriptInfo).load_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) + + +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 based on the active + environment + + .. 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=True, + create_app=None, + add_version_option=True, + load_dotenv=True, + set_debug_flag=True, + **extra, + ): + params = list(extra.pop("params", None) or ()) + + if add_version_option: + params.append(version_option) + + AppGroup.__init__(self, 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 + try: + import pkg_resources + except ImportError: + self._loaded_plugin_commands = True + return + + for ep in pkg_resources.iter_entry_points("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: + return info.load_app().cli.get_command(ctx, name) + except NoAppException as e: + click.secho(f"Error: {e.format_message()}\n", err=True, fg="red") + + 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 main(self, *args, **kwargs): + # Set a global flag that indicates that we were invoked from the + # command line interface. This is detected by Flask.run to make the + # call into a no-op. This is necessary to avoid ugly errors when the + # script that is loaded here also attempts to start a server. + os.environ["FLASK_RUN_FROM_CLI"] = "true" + + if get_load_dotenv(self.load_dotenv): + load_dotenv() + + obj = kwargs.get("obj") + + if obj is None: + obj = ScriptInfo( + create_app=self.create_app, set_debug_flag=self.set_debug_flag + ) + + kwargs["obj"] = obj + kwargs.setdefault("auto_envvar_prefix", "FLASK") + return super().main(*args, **kwargs) + + +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=None): + """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:: 1.1.0 + Returns ``False`` when python-dotenv is not installed, or when + the given path isn't a file. + + .. versionchanged:: 2.0 + When loading the env files, set the default encoding to UTF-8. + + .. versionadded:: 1.0 + """ + if dotenv is None: + 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 + + # if the given path specifies the actual file then return True, + # else False + if path is not None: + if os.path.isfile(path): + return dotenv.load_dotenv(path, encoding="utf-8") + + return False + + new_dir = None + + for name in (".env", ".flaskenv"): + path = dotenv.find_dotenv(name, usecwd=True) + + if not path: + continue + + if new_dir is None: + new_dir = os.path.dirname(path) + + dotenv.load_dotenv(path, encoding="utf-8") + + return new_dir is not None # at least one file was located and loaded + + +def show_server_banner(env, debug, app_import_path, eager_loading): + """Show extra startup messages the first time the server is run, + ignoring the reloader. + """ + if os.environ.get("WERKZEUG_RUN_MAIN") == "true": + return + + if app_import_path is not None: + message = f" * Serving Flask app {app_import_path!r}" + + if not eager_loading: + message += " (lazy loading)" + + click.echo(message) + + click.echo(f" * Environment: {env}") + + if env == "production": + click.secho( + " WARNING: This is a development server. Do not use it in" + " a production deployment.", + fg="red", + ) + click.secho(" Use a production WSGI server instead.", dim=True) + + 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): + if ssl is None: + raise click.BadParameter( + 'Using "--cert" requires Python to be compiled with SSL support.', + ctx, + param, + ) + + 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" + is_context = ssl and 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." +) +@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( + "--eager-loading/--lazy-loading", + default=None, + help="Enable or disable eager loading. By default eager " + "loading is enabled if the reloader is disabled.", +) +@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}." + ), +) +@pass_script_info +def run_command( + info, host, port, reload, debugger, eager_loading, with_threads, cert, extra_files +): + """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 if + FLASK_ENV=development or FLASK_DEBUG=1. + """ + debug = get_debug_flag() + + if reload is None: + reload = debug + + if debugger is None: + debugger = debug + + show_server_banner(get_env(), debug, info.app_import_path, eager_loading) + app = DispatchingApp(info.load_app, use_eager_loading=eager_loading) + + from werkzeug.serving import run_simple + + run_simple( + host, + port, + app, + use_reloader=reload, + use_debugger=debugger, + threaded=with_threads, + ssl_context=cert, + extra_files=extra_files, + ) + + +@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 + from .globals import _app_ctx_stack + + app = _app_ctx_stack.top.app + banner = ( + f"Python {sys.version} on {sys.platform}\n" + f"App: {app.import_name} [{app.env}]\n" + f"Instance: {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(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( + help="""\ +A general utility script for Flask applications. + +Provides commands from Flask, extensions, and the application. Loads the +application defined in the FLASK_APP environment variable, or from a wsgi.py +file. Setting the FLASK_ENV environment variable to 'development' will enable +debug mode. + +\b + {prefix}{cmd} FLASK_APP=hello.py + {prefix}{cmd} FLASK_ENV=development + {prefix}flask run +""".format( + cmd="export" if os.name == "posix" else "set", + prefix="$ " if os.name == "posix" else "> ", + ) +) + + +def main() -> None: + if int(click.__version__[0]) < 8: + warnings.warn( + "Using the `flask` cli with Click 7 is deprecated and" + " will not be supported starting with Flask 2.1." + " Please upgrade to Click 8 as soon as possible.", + DeprecationWarning, + ) + # TODO omit sys.argv once https://github.com/pallets/click/issues/536 is fixed + cli.main(args=sys.argv[1:]) + + +if __name__ == "__main__": + main() diff --git a/.venv/lib/python3.6/site-packages/flask/config.py b/.venv/lib/python3.6/site-packages/flask/config.py new file mode 100644 index 0000000..ca76902 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/flask/config.py @@ -0,0 +1,295 @@ +import errno +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: + dict.__init__(self, 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_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 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_json(self, filename: str, silent: bool = False) -> bool: + """Update the values in the config from a JSON file. The loaded + data is passed to the :meth:`from_mapping` method. + + :param filename: The path to the JSON file. This can be an + absolute path or relative to the config root path. + :param silent: Ignore the file if it doesn't exist. + :return: ``True`` if the file was loaded successfully. + + .. deprecated:: 2.0.0 + Will be removed in Flask 2.1. Use :meth:`from_file` instead. + This was removed early in 2.0.0, was added back in 2.0.1. + + .. versionadded:: 0.11 + """ + import warnings + from . import json + + warnings.warn( + "'from_json' is deprecated and will be removed in Flask" + " 2.1. Use 'from_file(path, json.load)' instead.", + DeprecationWarning, + stacklevel=2, + ) + return self.from_file(filename, json.load, silent=silent) + + 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/.venv/lib/python3.6/site-packages/flask/ctx.py b/.venv/lib/python3.6/site-packages/flask/ctx.py new file mode 100644 index 0000000..5c06463 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/flask/ctx.py @@ -0,0 +1,480 @@ +import sys +import typing as t +from functools import update_wrapper +from types import TracebackType + +from werkzeug.exceptions import HTTPException + +from .globals import _app_ctx_stack +from .globals import _request_ctx_stack +from .signals import appcontext_popped +from .signals import appcontext_pushed +from .typing import AfterRequestCallable + +if t.TYPE_CHECKING: + 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: + top = _app_ctx_stack.top + if top is not None: + return f"" + return object.__repr__(self) + + +def after_this_request(f: AfterRequestCallable) -> 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 + """ + _request_ctx_stack.top._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 + """ + top = _request_ctx_stack.top + if top is None: + raise RuntimeError( + "This decorator can only be used at local scopes " + "when a request context is on the stack. For instance within " + "view functions." + ) + reqctx = top.copy() + + def wrapper(*args, **kwargs): + with reqctx: + return 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 _request_ctx_stack.top 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 _app_ctx_stack.top is not None + + +class AppContext: + """The application context binds an application object implicitly + to the current thread or greenlet, similar to how the + :class:`RequestContext` binds request information. The application + context is also implicitly created if a request context is created + but the application is not on top of the individual application + context. + """ + + def __init__(self, app: "Flask") -> None: + self.app = app + self.url_adapter = app.create_url_adapter(None) + self.g = app.app_ctx_globals_class() + + # Like request context, app contexts can be pushed multiple times + # but there a basic "refcount" is enough to track them. + self._refcnt = 0 + + def push(self) -> None: + """Binds the app context to the current context.""" + self._refcnt += 1 + _app_ctx_stack.push(self) + appcontext_pushed.send(self.app) + + def pop(self, exc: t.Optional[BaseException] = _sentinel) -> None: # type: ignore + """Pops the app context.""" + try: + self._refcnt -= 1 + if self._refcnt <= 0: + if exc is _sentinel: + exc = sys.exc_info()[1] + self.app.do_teardown_appcontext(exc) + finally: + rv = _app_ctx_stack.pop() + assert rv is self, f"Popped wrong app context. ({rv!r} instead of {self!r})" + appcontext_popped.send(self.app) + + def __enter__(self) -> "AppContext": + self.push() + return self + + def __exit__( + self, exc_type: type, exc_value: BaseException, tb: TracebackType + ) -> None: + self.pop(exc_value) + + +class RequestContext: + """The request context contains all request relevant information. It is + created at the beginning of the request and pushed to the + `_request_ctx_stack` and removed at the end of it. It will create the + 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 + for you. In debug mode the request context is kept around if + exceptions happen so that interactive debuggers have a chance to + introspect the data. With 0.4 this can also be forced for requests + that did not fail and outside of ``DEBUG`` mode. By setting + ``'flask._preserve_context'`` to ``True`` on the WSGI environment the + context will not pop itself at the end of the request. This is used by + the :meth:`~flask.Flask.test_client` for example to implement the + deferred cleanup functionality. + + You might find this helpful for unittests where you need the + information from the context local around for a little longer. Make + sure to properly :meth:`~werkzeug.LocalStack.pop` the stack yourself in + that situation, otherwise your unittests will leak memory. + """ + + 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) + self.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 = None + self.session = session + + # Request contexts can be pushed multiple times and interleaved with + # other request contexts. Now only if the last level is popped we + # get rid of them. Additionally if an application context is missing + # one is created implicitly so for each level we add this information + self._implicit_app_ctx_stack: t.List[t.Optional["AppContext"]] = [] + + # indicator if the context was preserved. Next time another context + # is pushed the preserved context is popped. + self.preserved = False + + # remembers the exception for pop if there is one in case the context + # preservation kicks in. + self._preserved_exc = None + + # 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[AfterRequestCallable] = [] + + @property + def g(self) -> AppContext: + return _app_ctx_stack.top.g + + @g.setter + def g(self, value: AppContext) -> None: + _app_ctx_stack.top.g = value + + 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: + """Binds the request context to the current context.""" + # If an exception occurs in debug mode or if context preservation is + # activated under exception situations exactly one context stays + # on the stack. The rationale is that you want to access that + # information under debug situations. However if someone forgets to + # pop that context again we want to make sure that on the next push + # it's invalidated, otherwise we run at risk that something leaks + # memory. This is usually only a problem in test suite since this + # functionality is not active in production environments. + top = _request_ctx_stack.top + if top is not None and top.preserved: + top.pop(top._preserved_exc) + + # Before we push the request context we have to ensure that there + # is an application context. + app_ctx = _app_ctx_stack.top + if app_ctx is None or app_ctx.app != self.app: + app_ctx = self.app.app_context() + app_ctx.push() + self._implicit_app_ctx_stack.append(app_ctx) + else: + self._implicit_app_ctx_stack.append(None) + + _request_ctx_stack.push(self) + + # 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. + """ + app_ctx = self._implicit_app_ctx_stack.pop() + clear_request = False + + try: + if not self._implicit_app_ctx_stack: + self.preserved = False + self._preserved_exc = None + 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() + clear_request = True + finally: + rv = _request_ctx_stack.pop() + + # 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: + rv.request.environ["werkzeug.request"] = None + + # Get rid of the app as well if necessary. + if app_ctx is not None: + app_ctx.pop(exc) + + assert ( + rv is self + ), f"Popped wrong request context. ({rv!r} instead of {self!r})" + + def auto_pop(self, exc: t.Optional[BaseException]) -> None: + if self.request.environ.get("flask._preserve_context") or ( + exc is not None and self.app.preserve_context_on_exception + ): + self.preserved = True + self._preserved_exc = exc # type: ignore + else: + self.pop(exc) + + def __enter__(self) -> "RequestContext": + self.push() + return self + + def __exit__( + self, exc_type: type, exc_value: BaseException, tb: TracebackType + ) -> None: + # do not pop the request stack if we are in debug mode and an + # exception happened. This will allow the debugger to still + # access the request object in the interactive shell. Furthermore + # the context can be force kept alive for the test client. + # See flask.testing for how this works. + self.auto_pop(exc_value) + + def __repr__(self) -> str: + return ( + f"<{type(self).__name__} {self.request.url!r}" + f" [{self.request.method}] of {self.app.name}>" + ) diff --git a/.venv/lib/python3.6/site-packages/flask/debughelpers.py b/.venv/lib/python3.6/site-packages/flask/debughelpers.py new file mode 100644 index 0000000..212f7d7 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/flask/debughelpers.py @@ -0,0 +1,172 @@ +import os +import typing as t +from warnings import warn + +from .app import Flask +from .blueprints import Blueprint +from .globals import _request_ctx_stack + + +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 by Flask in debug mode if it detects a + redirect caused by the routing system when the request method is not + GET, HEAD or OPTIONS. Reasoning: form data will be dropped. + """ + + def __init__(self, request): + exc = request.routing_exception + buf = [ + f"A request was sent to this URL ({request.url}) but a" + " redirect was issued automatically by the routing system" + f" to {exc.new_url!r}." + ] + + # In case just a slash was appended we can be extra helpful + if f"{request.base_url}/" == exc.new_url.split("?")[0]: + buf.append( + " The URL was defined with a trailing slash so Flask" + " will automatically redirect to the URL with the" + " trailing slash if it was accessed without one." + ) + + buf.append( + " Make sure to directly send your" + f" {request.method}-request to this URL since we can't make" + " browsers or HTTP clients redirect with form data reliably" + " or without user interaction." + ) + buf.append("\n\nNote: this exception is only raised in debug mode") + AssertionError.__init__(self, "".join(buf).encode("utf-8")) + + +def attach_enctype_error_multidict(request): + """Since Flask 0.8 we're monkeypatching the files object in case a + request is detected that does not use multipart form data but the files + object is accessed. + """ + oldcls = request.files.__class__ + + class newcls(oldcls): + def __getitem__(self, key): + try: + return oldcls.__getitem__(self, key) + except KeyError as e: + if key not in request.form: + raise + + raise DebugFilesKeyError(request, key) from e + + 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 + reqctx = _request_ctx_stack.top + if reqctx is not None and reqctx.request.blueprint is not None: + blueprint = reqctx.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)) + + +def explain_ignored_app_run() -> None: + if os.environ.get("WERKZEUG_RUN_MAIN") != "true": + warn( + Warning( + "Silently ignoring app.run() because the application is" + " run from the flask command line executable. Consider" + ' putting app.run() behind an if __name__ == "__main__"' + " guard to silence this warning." + ), + stacklevel=3, + ) diff --git a/.venv/lib/python3.6/site-packages/flask/globals.py b/.venv/lib/python3.6/site-packages/flask/globals.py new file mode 100644 index 0000000..6d91c75 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/flask/globals.py @@ -0,0 +1,59 @@ +import typing as t +from functools import partial + +from werkzeug.local import LocalProxy +from werkzeug.local import LocalStack + +if t.TYPE_CHECKING: + from .app import Flask + from .ctx import _AppCtxGlobals + from .sessions import SessionMixin + from .wrappers import Request + +_request_ctx_err_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.\ +""" +_app_ctx_err_msg = """\ +Working outside of application context. + +This typically means that you attempted to use functionality that needed +to interface with the current application object in some way. To solve +this, set up an application context with app.app_context(). See the +documentation for more information.\ +""" + + +def _lookup_req_object(name): + top = _request_ctx_stack.top + if top is None: + raise RuntimeError(_request_ctx_err_msg) + return getattr(top, name) + + +def _lookup_app_object(name): + top = _app_ctx_stack.top + if top is None: + raise RuntimeError(_app_ctx_err_msg) + return getattr(top, name) + + +def _find_app(): + top = _app_ctx_stack.top + if top is None: + raise RuntimeError(_app_ctx_err_msg) + return top.app + + +# context locals +_request_ctx_stack = LocalStack() +_app_ctx_stack = LocalStack() +current_app: "Flask" = LocalProxy(_find_app) # type: ignore +request: "Request" = LocalProxy(partial(_lookup_req_object, "request")) # type: ignore +session: "SessionMixin" = LocalProxy( # type: ignore + partial(_lookup_req_object, "session") +) +g: "_AppCtxGlobals" = LocalProxy(partial(_lookup_app_object, "g")) # type: ignore diff --git a/.venv/lib/python3.6/site-packages/flask/helpers.py b/.venv/lib/python3.6/site-packages/flask/helpers.py new file mode 100644 index 0000000..7b8b087 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/flask/helpers.py @@ -0,0 +1,836 @@ +import os +import pkgutil +import socket +import sys +import typing as t +import warnings +from datetime import datetime +from datetime import timedelta +from functools import lru_cache +from functools import update_wrapper +from threading import RLock + +import werkzeug.utils +from werkzeug.exceptions import NotFound +from werkzeug.routing import BuildError +from werkzeug.urls import url_quote + +from .globals import _app_ctx_stack +from .globals import _request_ctx_stack +from .globals import current_app +from .globals import request +from .globals import session +from .signals import message_flashed + +if t.TYPE_CHECKING: + from .wrappers import Response + + +def get_env() -> str: + """Get the environment the app is running in, indicated by the + :envvar:`FLASK_ENV` environment variable. The default is + ``'production'``. + """ + 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 + ``True`` if :func:`.get_env` returns ``'development'``, or ``False`` + otherwise. + """ + val = os.environ.get("FLASK_DEBUG") + + if not val: + return get_env() == "development" + + return val.lower() not in ("0", "false", "no") + + +def get_load_dotenv(default: bool = True) -> bool: + """Get whether the user has disabled loading 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 = _request_ctx_stack.top + if ctx is None: + raise RuntimeError( + "Attempted to stream with context but " + "there was no context in the first place to keep around." + ) + 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) + + +def url_for(endpoint: str, **values: t.Any) -> str: + """Generates a URL to the given endpoint with the method provided. + + Variable arguments that are unknown to the target endpoint are appended + to the generated URL as query arguments. If the value of a query argument + is ``None``, the whole pair is skipped. In case blueprints are active + you can shortcut references to the same blueprint by prefixing the + local endpoint with a dot (``.``). + + This will reference the index function local to the current blueprint:: + + url_for('.index') + + See :ref:`url-building`. + + Configuration values ``APPLICATION_ROOT`` and ``SERVER_NAME`` are only used when + generating URLs outside of a request context. + + To integrate applications, :class:`Flask` has a hook to intercept URL build + errors through :attr:`Flask.url_build_error_handlers`. The `url_for` + function results in a :exc:`~werkzeug.routing.BuildError` when the current + app does not have a URL for the given endpoint and values. When it does, the + :data:`~flask.current_app` calls its :attr:`~Flask.url_build_error_handlers` if + it is not ``None``, which can return a string to use as the result of + `url_for` (instead of `url_for`'s default to raise the + :exc:`~werkzeug.routing.BuildError` exception) or re-raise the exception. + An example:: + + def external_url_handler(error, endpoint, values): + "Looks up an external URL when `url_for` cannot build a URL." + # This is an example of hooking the build_error_handler. + # Here, lookup_url is some utility function you've built + # which looks up the endpoint in some external URL registry. + url = lookup_url(endpoint, **values) + if url is None: + # External lookup did not have a URL. + # Re-raise the BuildError, in context of original traceback. + exc_type, exc_value, tb = sys.exc_info() + if exc_value is error: + raise exc_type(exc_value).with_traceback(tb) + else: + raise error + # url_for will use this result, instead of raising BuildError. + return url + + app.url_build_error_handlers.append(external_url_handler) + + Here, `error` is the instance of :exc:`~werkzeug.routing.BuildError`, and + `endpoint` and `values` are the arguments passed into `url_for`. Note + that this is for building URLs outside the current application, and not for + handling 404 NotFound errors. + + .. versionadded:: 0.10 + The `_scheme` parameter was added. + + .. versionadded:: 0.9 + The `_anchor` and `_method` parameters were added. + + .. versionadded:: 0.9 + Calls :meth:`Flask.handle_build_error` on + :exc:`~werkzeug.routing.BuildError`. + + :param endpoint: the endpoint of the URL (name of the function) + :param values: the variable arguments of the URL rule + :param _external: if set to ``True``, an absolute URL is generated. Server + address can be changed via ``SERVER_NAME`` configuration variable which + falls back to the `Host` header, then to the IP and port of the request. + :param _scheme: a string specifying the desired URL scheme. The `_external` + parameter must be set to ``True`` or a :exc:`ValueError` is raised. The default + behavior uses the same scheme as the current request, or + :data:`PREFERRED_URL_SCHEME` if no request context is available. + This also can be set to an empty string to build protocol-relative + URLs. + :param _anchor: if provided this is added as anchor to the URL. + :param _method: if provided this explicitly specifies an HTTP method. + """ + appctx = _app_ctx_stack.top + reqctx = _request_ctx_stack.top + + if appctx is None: + raise RuntimeError( + "Attempted to generate a URL without the application context being" + " pushed. This has to be executed when application context is" + " available." + ) + + # If request specific information is available we have some extra + # features that support "relative" URLs. + if reqctx is not None: + url_adapter = reqctx.url_adapter + blueprint_name = request.blueprint + + if endpoint[:1] == ".": + if blueprint_name is not None: + endpoint = f"{blueprint_name}{endpoint}" + else: + endpoint = endpoint[1:] + + external = values.pop("_external", False) + + # Otherwise go with the url adapter from the appctx and make + # the URLs external by default. + else: + url_adapter = appctx.url_adapter + + if url_adapter is None: + raise RuntimeError( + "Application was not able to create a URL adapter for request" + " independent URL generation. You might be able to fix this by" + " setting the SERVER_NAME config variable." + ) + + external = values.pop("_external", True) + + anchor = values.pop("_anchor", None) + method = values.pop("_method", None) + scheme = values.pop("_scheme", None) + appctx.app.inject_url_defaults(endpoint, values) + + # This is not the best way to deal with this but currently the + # underlying Werkzeug router does not support overriding the scheme on + # a per build call basis. + old_scheme = None + if scheme is not None: + if not external: + raise ValueError("When specifying _scheme, _external must be True") + old_scheme = url_adapter.url_scheme + url_adapter.url_scheme = scheme + + try: + try: + rv = url_adapter.build( + endpoint, values, method=method, force_external=external + ) + finally: + if old_scheme is not None: + url_adapter.url_scheme = old_scheme + except BuildError as error: + # We need to inject the values again so that the app callback can + # deal with that sort of stuff. + values["_external"] = external + values["_anchor"] = anchor + values["_method"] = method + values["_scheme"] = scheme + return appctx.app.handle_url_build_error(error, endpoint, values) + + if anchor is not None: + rv += f"#{url_quote(anchor)}" + return rv + + +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_stack.top.flashes + if flashes is None: + _request_ctx_stack.top.flashes = flashes = ( + session.pop("_flashes") if "_flashes" in session else [] + ) + 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( + download_name: t.Optional[str] = None, + attachment_filename: t.Optional[str] = None, + etag: t.Optional[t.Union[bool, str]] = None, + add_etags: t.Optional[t.Union[bool]] = None, + max_age: t.Optional[ + t.Union[int, t.Callable[[t.Optional[str]], t.Optional[int]]] + ] = None, + cache_timeout: t.Optional[int] = None, + **kwargs: t.Any, +) -> t.Dict[str, t.Any]: + if attachment_filename is not None: + warnings.warn( + "The 'attachment_filename' parameter has been renamed to" + " 'download_name'. The old name will be removed in Flask" + " 2.1.", + DeprecationWarning, + stacklevel=3, + ) + download_name = attachment_filename + + if cache_timeout is not None: + warnings.warn( + "The 'cache_timeout' parameter has been renamed to" + " 'max_age'. The old name will be removed in Flask 2.1.", + DeprecationWarning, + stacklevel=3, + ) + max_age = cache_timeout + + if add_etags is not None: + warnings.warn( + "The 'add_etags' parameter has been renamed to 'etag'. The" + " old name will be removed in Flask 2.1.", + DeprecationWarning, + stacklevel=3, + ) + etag = add_etags + + if max_age is None: + max_age = current_app.get_send_file_max_age + + kwargs.update( + environ=request.environ, + download_name=download_name, + etag=etag, + max_age=max_age, + use_x_sendfile=current_app.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, + attachment_filename: t.Optional[str] = None, + conditional: bool = True, + etag: t.Union[bool, str] = True, + add_etags: t.Optional[bool] = None, + 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, + cache_timeout: t.Optional[int] = None, +): + """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( + **_prepare_send_file_kwargs( + path_or_file=path_or_file, + environ=request.environ, + mimetype=mimetype, + as_attachment=as_attachment, + download_name=download_name, + attachment_filename=attachment_filename, + conditional=conditional, + etag=etag, + add_etags=add_etags, + last_modified=last_modified, + max_age=max_age, + cache_timeout=cache_timeout, + ) + ) + + +def safe_join(directory: str, *pathnames: str) -> 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``. + """ + warnings.warn( + "'flask.helpers.safe_join' is deprecated and will be removed in" + " Flask 2.1. Use 'werkzeug.utils.safe_join' instead.", + DeprecationWarning, + stacklevel=2, + ) + path = werkzeug.utils.safe_join(directory, *pathnames) + + if path is None: + raise NotFound() + + return path + + +def send_from_directory( + directory: t.Union[os.PathLike, str], + path: t.Union[os.PathLike, str], + filename: t.Optional[str] = None, + **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. + :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 + """ + if filename is not None: + warnings.warn( + "The 'filename' parameter has been renamed to 'path'. The" + " old name will be removed in Flask 2.1.", + DeprecationWarning, + stacklevel=2, + ) + path = filename + + return werkzeug.utils.send_from_directory( # type: ignore + 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__"): + 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 total_seconds(td: timedelta) -> int: + """Returns the total seconds from a timedelta object. + + :param timedelta td: the timedelta to be converted in seconds + + :returns: number of seconds + :rtype: int + + .. deprecated:: 2.0 + Will be removed in Flask 2.1. Use + :meth:`timedelta.total_seconds` instead. + """ + warnings.warn( + "'total_seconds' is deprecated and will be removed in Flask" + " 2.1. Use 'timedelta.total_seconds' instead.", + DeprecationWarning, + stacklevel=2, + ) + return td.days * 60 * 60 * 24 + td.seconds + + +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/.venv/lib/python3.6/site-packages/flask/json/__init__.py b/.venv/lib/python3.6/site-packages/flask/json/__init__.py new file mode 100644 index 0000000..10d5123 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/flask/json/__init__.py @@ -0,0 +1,357 @@ +import decimal +import io +import json as _json +import typing as t +import uuid +import warnings +from datetime import date + +from jinja2.utils import htmlsafe_json_dumps as _jinja_htmlsafe_dumps +from werkzeug.http import http_date + +from ..globals import current_app +from ..globals import request + +if t.TYPE_CHECKING: + from ..app import Flask + from ..wrappers import Response + +try: + import dataclasses +except ImportError: + # Python < 3.7 + dataclasses = None # type: ignore + + +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:`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. + """ + + 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. + """ + if isinstance(o, date): + return http_date(o) + if isinstance(o, (decimal.Decimal, uuid.UUID)): + return str(o) + if dataclasses and dataclasses.is_dataclass(o): + return dataclasses.asdict(o) + if hasattr(o, "__html__"): + return str(o.__html__()) + return super().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. + """ + + +def _dump_arg_defaults( + kwargs: t.Dict[str, t.Any], app: t.Optional["Flask"] = None +) -> None: + """Inject default arguments for dump functions.""" + if app is None: + app = current_app + + if app: + cls = app.json_encoder + bp = app.blueprints.get(request.blueprint) if request else None # type: ignore + if bp is not None and bp.json_encoder is not None: + cls = bp.json_encoder + + kwargs.setdefault("cls", cls) + kwargs.setdefault("ensure_ascii", app.config["JSON_AS_ASCII"]) + kwargs.setdefault("sort_keys", app.config["JSON_SORT_KEYS"]) + else: + kwargs.setdefault("sort_keys", True) + kwargs.setdefault("cls", JSONEncoder) + + +def _load_arg_defaults( + kwargs: t.Dict[str, t.Any], app: t.Optional["Flask"] = None +) -> None: + """Inject default arguments for load functions.""" + if app is None: + app = current_app + + if app: + cls = app.json_decoder + bp = app.blueprints.get(request.blueprint) if request else None # type: ignore + if bp is not None and bp.json_decoder is not None: + cls = bp.json_decoder + + kwargs.setdefault("cls", cls) + else: + kwargs.setdefault("cls", JSONDecoder) + + +def dumps(obj: t.Any, app: t.Optional["Flask"] = None, **kwargs: t.Any) -> str: + """Serialize an object to a string of JSON. + + Takes the same arguments as the built-in :func:`json.dumps`, with + some defaults from application configuration. + + :param obj: Object to serialize to JSON. + :param app: Use this app's config instead of the active app context + or defaults. + :param kwargs: Extra arguments passed to :func:`json.dumps`. + + .. versionchanged:: 2.0.2 + :class:`decimal.Decimal` is supported by converting to a string. + + .. versionchanged:: 2.0 + ``encoding`` is deprecated and will be removed in Flask 2.1. + + .. versionchanged:: 1.0.3 + ``app`` can be passed directly, rather than requiring an app + context for configuration. + """ + _dump_arg_defaults(kwargs, app=app) + encoding = kwargs.pop("encoding", None) + rv = _json.dumps(obj, **kwargs) + + if encoding is not None: + warnings.warn( + "'encoding' is deprecated and will be removed in Flask 2.1.", + DeprecationWarning, + stacklevel=2, + ) + + if isinstance(rv, str): + return rv.encode(encoding) # type: ignore + + return rv + + +def dump( + obj: t.Any, fp: t.IO[str], app: t.Optional["Flask"] = None, **kwargs: t.Any +) -> None: + """Serialize an object to JSON written to a file object. + + Takes the same arguments as the built-in :func:`json.dump`, with + some defaults from application configuration. + + :param obj: Object to serialize to JSON. + :param fp: File object to write JSON to. + :param app: Use this app's config instead of the active app context + or defaults. + :param kwargs: Extra arguments passed to :func:`json.dump`. + + .. versionchanged:: 2.0 + Writing to a binary file, and the ``encoding`` argument, is + deprecated and will be removed in Flask 2.1. + """ + _dump_arg_defaults(kwargs, app=app) + encoding = kwargs.pop("encoding", None) + show_warning = encoding is not None + + try: + fp.write("") + except TypeError: + show_warning = True + fp = io.TextIOWrapper(fp, encoding or "utf-8") # type: ignore + + if show_warning: + warnings.warn( + "Writing to a binary file, and the 'encoding' argument, is" + " deprecated and will be removed in Flask 2.1.", + DeprecationWarning, + stacklevel=2, + ) + + _json.dump(obj, fp, **kwargs) + + +def loads(s: str, app: t.Optional["Flask"] = None, **kwargs: t.Any) -> t.Any: + """Deserialize an object from a string of JSON. + + Takes the same arguments as the built-in :func:`json.loads`, with + some defaults from application configuration. + + :param s: JSON string to deserialize. + :param app: Use this app's config instead of the active app context + or defaults. + :param kwargs: Extra arguments passed to :func:`json.loads`. + + .. versionchanged:: 2.0 + ``encoding`` is deprecated and 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. + """ + _load_arg_defaults(kwargs, app=app) + encoding = kwargs.pop("encoding", None) + + if encoding is not None: + warnings.warn( + "'encoding' is deprecated and will be removed in Flask 2.1." + " The data must be a string or UTF-8 bytes.", + DeprecationWarning, + stacklevel=2, + ) + + if isinstance(s, bytes): + s = s.decode(encoding) + + return _json.loads(s, **kwargs) + + +def load(fp: t.IO[str], app: t.Optional["Flask"] = None, **kwargs: t.Any) -> t.Any: + """Deserialize an object from JSON read from a file object. + + Takes the same arguments as the built-in :func:`json.load`, with + some defaults from application configuration. + + :param fp: File object to read JSON from. + :param app: Use this app's config instead of the active app context + or defaults. + :param kwargs: Extra arguments passed to :func:`json.load`. + + .. versionchanged:: 2.0 + ``encoding`` is deprecated and will be removed in Flask 2.1. The + file must be text mode, or binary mode with UTF-8 bytes. + """ + _load_arg_defaults(kwargs, app=app) + encoding = kwargs.pop("encoding", None) + + if encoding is not None: + warnings.warn( + "'encoding' is deprecated and will be removed in Flask 2.1." + " The file must be text mode, or binary mode with UTF-8" + " bytes.", + DeprecationWarning, + stacklevel=2, + ) + + if isinstance(fp.read(0), bytes): + fp = io.TextIOWrapper(fp, encoding) # type: ignore + + 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 + ``') + # => <script> do_nasty_stuff() </script> + # sanitize_html('Click here for $100') + # => Click here for $100 + def sanitize_token(self, token): + + # accommodate filters which use token_type differently + token_type = token["type"] + if token_type in ("StartTag", "EndTag", "EmptyTag"): + name = token["name"] + namespace = token["namespace"] + if ((namespace, name) in self.allowed_elements or + (namespace is None and + (namespaces["html"], name) in self.allowed_elements)): + return self.allowed_token(token) + else: + return self.disallowed_token(token) + elif token_type == "Comment": + pass + else: + return token + + def allowed_token(self, token): + if "data" in token: + attrs = token["data"] + attr_names = set(attrs.keys()) + + # Remove forbidden attributes + for to_remove in (attr_names - self.allowed_attributes): + del token["data"][to_remove] + attr_names.remove(to_remove) + + # Remove attributes with disallowed URL values + for attr in (attr_names & self.attr_val_is_uri): + assert attr in attrs + # I don't have a clue where this regexp comes from or why it matches those + # characters, nor why we call unescape. I just know it's always been here. + # Should you be worried by this comment in a sanitizer? Yes. On the other hand, all + # this will do is remove *more* than it otherwise would. + val_unescaped = re.sub("[`\x00-\x20\x7f-\xa0\\s]+", '', + unescape(attrs[attr])).lower() + # remove replacement characters from unescaped characters + val_unescaped = val_unescaped.replace("\ufffd", "") + try: + uri = urlparse.urlparse(val_unescaped) + except ValueError: + uri = None + del attrs[attr] + if uri and uri.scheme: + if uri.scheme not in self.allowed_protocols: + del attrs[attr] + if uri.scheme == 'data': + m = data_content_type.match(uri.path) + if not m: + del attrs[attr] + elif m.group('content_type') not in self.allowed_content_types: + del attrs[attr] + + for attr in self.svg_attr_val_allows_ref: + if attr in attrs: + attrs[attr] = re.sub(r'url\s*\(\s*[^#\s][^)]+?\)', + ' ', + unescape(attrs[attr])) + if (token["name"] in self.svg_allow_local_href and + (namespaces['xlink'], 'href') in attrs and re.search(r'^\s*[^#\s].*', + attrs[(namespaces['xlink'], 'href')])): + del attrs[(namespaces['xlink'], 'href')] + if (None, 'style') in attrs: + attrs[(None, 'style')] = self.sanitize_css(attrs[(None, 'style')]) + token["data"] = attrs + return token + + def disallowed_token(self, token): + token_type = token["type"] + if token_type == "EndTag": + token["data"] = "" % token["name"] + elif token["data"]: + assert token_type in ("StartTag", "EmptyTag") + attrs = [] + for (ns, name), v in token["data"].items(): + attrs.append(' %s="%s"' % (name if ns is None else "%s:%s" % (prefixes[ns], name), escape(v))) + token["data"] = "<%s%s>" % (token["name"], ''.join(attrs)) + else: + token["data"] = "<%s>" % token["name"] + if token.get("selfClosing"): + token["data"] = token["data"][:-1] + "/>" + + token["type"] = "Characters" + + del token["name"] + return token + + def sanitize_css(self, style): + # disallow urls + style = re.compile(r'url\s*\(\s*[^\s)]+?\s*\)\s*').sub(' ', style) + + # gauntlet + if not re.match(r"""^([:,;#%.\sa-zA-Z0-9!]|\w-\w|'[\s\w]+'|"[\s\w]+"|\([\d,\s]+\))*$""", style): + return '' + if not re.match(r"^\s*([-\w]+\s*:[^:;]*(;\s*|$))*$", style): + return '' + + clean = [] + for prop, value in re.findall(r"([-\w]+)\s*:\s*([^:;]*)", style): + if not value: + continue + if prop.lower() in self.allowed_css_properties: + clean.append(prop + ': ' + value + ';') + elif prop.split('-')[0].lower() in ['background', 'border', 'margin', + 'padding']: + for keyword in value.split(): + if keyword not in self.allowed_css_keywords and \ + not re.match(r"^(#[0-9a-fA-F]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)$", keyword): # noqa + break + else: + clean.append(prop + ': ' + value + ';') + elif prop.lower() in self.allowed_svg_properties: + clean.append(prop + ': ' + value + ';') + + return ' '.join(clean) diff --git a/.venv/lib/python3.6/site-packages/pip/_vendor/html5lib/filters/whitespace.py b/.venv/lib/python3.6/site-packages/pip/_vendor/html5lib/filters/whitespace.py new file mode 100644 index 0000000..0d12584 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/pip/_vendor/html5lib/filters/whitespace.py @@ -0,0 +1,38 @@ +from __future__ import absolute_import, division, unicode_literals + +import re + +from . import base +from ..constants import rcdataElements, spaceCharacters +spaceCharacters = "".join(spaceCharacters) + +SPACES_REGEX = re.compile("[%s]+" % spaceCharacters) + + +class Filter(base.Filter): + """Collapses whitespace except in pre, textarea, and script elements""" + spacePreserveElements = frozenset(["pre", "textarea"] + list(rcdataElements)) + + def __iter__(self): + preserve = 0 + for token in base.Filter.__iter__(self): + type = token["type"] + if type == "StartTag" \ + and (preserve or token["name"] in self.spacePreserveElements): + preserve += 1 + + elif type == "EndTag" and preserve: + preserve -= 1 + + elif not preserve and type == "SpaceCharacters" and token["data"]: + # Test on token["data"] above to not introduce spaces where there were not + token["data"] = " " + + elif not preserve and type == "Characters": + token["data"] = collapse_spaces(token["data"]) + + yield token + + +def collapse_spaces(text): + return SPACES_REGEX.sub(' ', text) diff --git a/.venv/lib/python3.6/site-packages/pip/_vendor/html5lib/html5parser.py b/.venv/lib/python3.6/site-packages/pip/_vendor/html5lib/html5parser.py new file mode 100644 index 0000000..d06784f --- /dev/null +++ b/.venv/lib/python3.6/site-packages/pip/_vendor/html5lib/html5parser.py @@ -0,0 +1,2795 @@ +from __future__ import absolute_import, division, unicode_literals +from pip._vendor.six import with_metaclass, viewkeys + +import types + +from . import _inputstream +from . import _tokenizer + +from . import treebuilders +from .treebuilders.base import Marker + +from . import _utils +from .constants import ( + spaceCharacters, asciiUpper2Lower, + specialElements, headingElements, cdataElements, rcdataElements, + tokenTypes, tagTokenTypes, + namespaces, + htmlIntegrationPointElements, mathmlTextIntegrationPointElements, + adjustForeignAttributes as adjustForeignAttributesMap, + adjustMathMLAttributes, adjustSVGAttributes, + E, + _ReparseException +) + + +def parse(doc, treebuilder="etree", namespaceHTMLElements=True, **kwargs): + """Parse an HTML document as a string or file-like object into a tree + + :arg doc: the document to parse as a string or file-like object + + :arg treebuilder: the treebuilder to use when parsing + + :arg namespaceHTMLElements: whether or not to namespace HTML elements + + :returns: parsed tree + + Example: + + >>> from html5lib.html5parser import parse + >>> parse('

    This is a doc

    ') + + + """ + tb = treebuilders.getTreeBuilder(treebuilder) + p = HTMLParser(tb, namespaceHTMLElements=namespaceHTMLElements) + return p.parse(doc, **kwargs) + + +def parseFragment(doc, container="div", treebuilder="etree", namespaceHTMLElements=True, **kwargs): + """Parse an HTML fragment as a string or file-like object into a tree + + :arg doc: the fragment to parse as a string or file-like object + + :arg container: the container context to parse the fragment in + + :arg treebuilder: the treebuilder to use when parsing + + :arg namespaceHTMLElements: whether or not to namespace HTML elements + + :returns: parsed tree + + Example: + + >>> from html5lib.html5libparser import parseFragment + >>> parseFragment('this is a fragment') + + + """ + tb = treebuilders.getTreeBuilder(treebuilder) + p = HTMLParser(tb, namespaceHTMLElements=namespaceHTMLElements) + return p.parseFragment(doc, container=container, **kwargs) + + +def method_decorator_metaclass(function): + class Decorated(type): + def __new__(meta, classname, bases, classDict): + for attributeName, attribute in classDict.items(): + if isinstance(attribute, types.FunctionType): + attribute = function(attribute) + + classDict[attributeName] = attribute + return type.__new__(meta, classname, bases, classDict) + return Decorated + + +class HTMLParser(object): + """HTML parser + + Generates a tree structure from a stream of (possibly malformed) HTML. + + """ + + def __init__(self, tree=None, strict=False, namespaceHTMLElements=True, debug=False): + """ + :arg tree: a treebuilder class controlling the type of tree that will be + returned. Built in treebuilders can be accessed through + html5lib.treebuilders.getTreeBuilder(treeType) + + :arg strict: raise an exception when a parse error is encountered + + :arg namespaceHTMLElements: whether or not to namespace HTML elements + + :arg debug: whether or not to enable debug mode which logs things + + Example: + + >>> from html5lib.html5parser import HTMLParser + >>> parser = HTMLParser() # generates parser with etree builder + >>> parser = HTMLParser('lxml', strict=True) # generates parser with lxml builder which is strict + + """ + + # Raise an exception on the first error encountered + self.strict = strict + + if tree is None: + tree = treebuilders.getTreeBuilder("etree") + self.tree = tree(namespaceHTMLElements) + self.errors = [] + + self.phases = {name: cls(self, self.tree) for name, cls in + getPhases(debug).items()} + + def _parse(self, stream, innerHTML=False, container="div", scripting=False, **kwargs): + + self.innerHTMLMode = innerHTML + self.container = container + self.scripting = scripting + self.tokenizer = _tokenizer.HTMLTokenizer(stream, parser=self, **kwargs) + self.reset() + + try: + self.mainLoop() + except _ReparseException: + self.reset() + self.mainLoop() + + def reset(self): + self.tree.reset() + self.firstStartTag = False + self.errors = [] + self.log = [] # only used with debug mode + # "quirks" / "limited quirks" / "no quirks" + self.compatMode = "no quirks" + + if self.innerHTMLMode: + self.innerHTML = self.container.lower() + + if self.innerHTML in cdataElements: + self.tokenizer.state = self.tokenizer.rcdataState + elif self.innerHTML in rcdataElements: + self.tokenizer.state = self.tokenizer.rawtextState + elif self.innerHTML == 'plaintext': + self.tokenizer.state = self.tokenizer.plaintextState + else: + # state already is data state + # self.tokenizer.state = self.tokenizer.dataState + pass + self.phase = self.phases["beforeHtml"] + self.phase.insertHtmlElement() + self.resetInsertionMode() + else: + self.innerHTML = False # pylint:disable=redefined-variable-type + self.phase = self.phases["initial"] + + self.lastPhase = None + + self.beforeRCDataPhase = None + + self.framesetOK = True + + @property + def documentEncoding(self): + """Name of the character encoding that was used to decode the input stream, or + :obj:`None` if that is not determined yet + + """ + if not hasattr(self, 'tokenizer'): + return None + return self.tokenizer.stream.charEncoding[0].name + + def isHTMLIntegrationPoint(self, element): + if (element.name == "annotation-xml" and + element.namespace == namespaces["mathml"]): + return ("encoding" in element.attributes and + element.attributes["encoding"].translate( + asciiUpper2Lower) in + ("text/html", "application/xhtml+xml")) + else: + return (element.namespace, element.name) in htmlIntegrationPointElements + + def isMathMLTextIntegrationPoint(self, element): + return (element.namespace, element.name) in mathmlTextIntegrationPointElements + + def mainLoop(self): + CharactersToken = tokenTypes["Characters"] + SpaceCharactersToken = tokenTypes["SpaceCharacters"] + StartTagToken = tokenTypes["StartTag"] + EndTagToken = tokenTypes["EndTag"] + CommentToken = tokenTypes["Comment"] + DoctypeToken = tokenTypes["Doctype"] + ParseErrorToken = tokenTypes["ParseError"] + + for token in self.tokenizer: + prev_token = None + new_token = token + while new_token is not None: + prev_token = new_token + currentNode = self.tree.openElements[-1] if self.tree.openElements else None + currentNodeNamespace = currentNode.namespace if currentNode else None + currentNodeName = currentNode.name if currentNode else None + + type = new_token["type"] + + if type == ParseErrorToken: + self.parseError(new_token["data"], new_token.get("datavars", {})) + new_token = None + else: + if (len(self.tree.openElements) == 0 or + currentNodeNamespace == self.tree.defaultNamespace or + (self.isMathMLTextIntegrationPoint(currentNode) and + ((type == StartTagToken and + token["name"] not in frozenset(["mglyph", "malignmark"])) or + type in (CharactersToken, SpaceCharactersToken))) or + (currentNodeNamespace == namespaces["mathml"] and + currentNodeName == "annotation-xml" and + type == StartTagToken and + token["name"] == "svg") or + (self.isHTMLIntegrationPoint(currentNode) and + type in (StartTagToken, CharactersToken, SpaceCharactersToken))): + phase = self.phase + else: + phase = self.phases["inForeignContent"] + + if type == CharactersToken: + new_token = phase.processCharacters(new_token) + elif type == SpaceCharactersToken: + new_token = phase.processSpaceCharacters(new_token) + elif type == StartTagToken: + new_token = phase.processStartTag(new_token) + elif type == EndTagToken: + new_token = phase.processEndTag(new_token) + elif type == CommentToken: + new_token = phase.processComment(new_token) + elif type == DoctypeToken: + new_token = phase.processDoctype(new_token) + + if (type == StartTagToken and prev_token["selfClosing"] and + not prev_token["selfClosingAcknowledged"]): + self.parseError("non-void-element-with-trailing-solidus", + {"name": prev_token["name"]}) + + # When the loop finishes it's EOF + reprocess = True + phases = [] + while reprocess: + phases.append(self.phase) + reprocess = self.phase.processEOF() + if reprocess: + assert self.phase not in phases + + def parse(self, stream, *args, **kwargs): + """Parse a HTML document into a well-formed tree + + :arg stream: a file-like object or string containing the HTML to be parsed + + The optional encoding parameter must be a string that indicates + the encoding. If specified, that encoding will be used, + regardless of any BOM or later declaration (such as in a meta + element). + + :arg scripting: treat noscript elements as if JavaScript was turned on + + :returns: parsed tree + + Example: + + >>> from html5lib.html5parser import HTMLParser + >>> parser = HTMLParser() + >>> parser.parse('

    This is a doc

    ') + + + """ + self._parse(stream, False, None, *args, **kwargs) + return self.tree.getDocument() + + def parseFragment(self, stream, *args, **kwargs): + """Parse a HTML fragment into a well-formed tree fragment + + :arg container: name of the element we're setting the innerHTML + property if set to None, default to 'div' + + :arg stream: a file-like object or string containing the HTML to be parsed + + The optional encoding parameter must be a string that indicates + the encoding. If specified, that encoding will be used, + regardless of any BOM or later declaration (such as in a meta + element) + + :arg scripting: treat noscript elements as if JavaScript was turned on + + :returns: parsed tree + + Example: + + >>> from html5lib.html5libparser import HTMLParser + >>> parser = HTMLParser() + >>> parser.parseFragment('this is a fragment') + + + """ + self._parse(stream, True, *args, **kwargs) + return self.tree.getFragment() + + def parseError(self, errorcode="XXX-undefined-error", datavars=None): + # XXX The idea is to make errorcode mandatory. + if datavars is None: + datavars = {} + self.errors.append((self.tokenizer.stream.position(), errorcode, datavars)) + if self.strict: + raise ParseError(E[errorcode] % datavars) + + def adjustMathMLAttributes(self, token): + adjust_attributes(token, adjustMathMLAttributes) + + def adjustSVGAttributes(self, token): + adjust_attributes(token, adjustSVGAttributes) + + def adjustForeignAttributes(self, token): + adjust_attributes(token, adjustForeignAttributesMap) + + def reparseTokenNormal(self, token): + # pylint:disable=unused-argument + self.parser.phase() + + def resetInsertionMode(self): + # The name of this method is mostly historical. (It's also used in the + # specification.) + last = False + newModes = { + "select": "inSelect", + "td": "inCell", + "th": "inCell", + "tr": "inRow", + "tbody": "inTableBody", + "thead": "inTableBody", + "tfoot": "inTableBody", + "caption": "inCaption", + "colgroup": "inColumnGroup", + "table": "inTable", + "head": "inBody", + "body": "inBody", + "frameset": "inFrameset", + "html": "beforeHead" + } + for node in self.tree.openElements[::-1]: + nodeName = node.name + new_phase = None + if node == self.tree.openElements[0]: + assert self.innerHTML + last = True + nodeName = self.innerHTML + # Check for conditions that should only happen in the innerHTML + # case + if nodeName in ("select", "colgroup", "head", "html"): + assert self.innerHTML + + if not last and node.namespace != self.tree.defaultNamespace: + continue + + if nodeName in newModes: + new_phase = self.phases[newModes[nodeName]] + break + elif last: + new_phase = self.phases["inBody"] + break + + self.phase = new_phase + + def parseRCDataRawtext(self, token, contentType): + # Generic RCDATA/RAWTEXT Parsing algorithm + assert contentType in ("RAWTEXT", "RCDATA") + + self.tree.insertElement(token) + + if contentType == "RAWTEXT": + self.tokenizer.state = self.tokenizer.rawtextState + else: + self.tokenizer.state = self.tokenizer.rcdataState + + self.originalPhase = self.phase + + self.phase = self.phases["text"] + + +@_utils.memoize +def getPhases(debug): + def log(function): + """Logger that records which phase processes each token""" + type_names = {value: key for key, value in tokenTypes.items()} + + def wrapped(self, *args, **kwargs): + if function.__name__.startswith("process") and len(args) > 0: + token = args[0] + info = {"type": type_names[token['type']]} + if token['type'] in tagTokenTypes: + info["name"] = token['name'] + + self.parser.log.append((self.parser.tokenizer.state.__name__, + self.parser.phase.__class__.__name__, + self.__class__.__name__, + function.__name__, + info)) + return function(self, *args, **kwargs) + else: + return function(self, *args, **kwargs) + return wrapped + + def getMetaclass(use_metaclass, metaclass_func): + if use_metaclass: + return method_decorator_metaclass(metaclass_func) + else: + return type + + # pylint:disable=unused-argument + class Phase(with_metaclass(getMetaclass(debug, log))): + """Base class for helper object that implements each phase of processing + """ + __slots__ = ("parser", "tree", "__startTagCache", "__endTagCache") + + def __init__(self, parser, tree): + self.parser = parser + self.tree = tree + self.__startTagCache = {} + self.__endTagCache = {} + + def processEOF(self): + raise NotImplementedError + + def processComment(self, token): + # For most phases the following is correct. Where it's not it will be + # overridden. + self.tree.insertComment(token, self.tree.openElements[-1]) + + def processDoctype(self, token): + self.parser.parseError("unexpected-doctype") + + def processCharacters(self, token): + self.tree.insertText(token["data"]) + + def processSpaceCharacters(self, token): + self.tree.insertText(token["data"]) + + def processStartTag(self, token): + # Note the caching is done here rather than BoundMethodDispatcher as doing it there + # requires a circular reference to the Phase, and this ends up with a significant + # (CPython 2.7, 3.8) GC cost when parsing many short inputs + name = token["name"] + # In Py2, using `in` is quicker in general than try/except KeyError + # In Py3, `in` is quicker when there are few cache hits (typically short inputs) + if name in self.__startTagCache: + func = self.__startTagCache[name] + else: + func = self.__startTagCache[name] = self.startTagHandler[name] + # bound the cache size in case we get loads of unknown tags + while len(self.__startTagCache) > len(self.startTagHandler) * 1.1: + # this makes the eviction policy random on Py < 3.7 and FIFO >= 3.7 + self.__startTagCache.pop(next(iter(self.__startTagCache))) + return func(token) + + def startTagHtml(self, token): + if not self.parser.firstStartTag and token["name"] == "html": + self.parser.parseError("non-html-root") + # XXX Need a check here to see if the first start tag token emitted is + # this token... If it's not, invoke self.parser.parseError(). + for attr, value in token["data"].items(): + if attr not in self.tree.openElements[0].attributes: + self.tree.openElements[0].attributes[attr] = value + self.parser.firstStartTag = False + + def processEndTag(self, token): + # Note the caching is done here rather than BoundMethodDispatcher as doing it there + # requires a circular reference to the Phase, and this ends up with a significant + # (CPython 2.7, 3.8) GC cost when parsing many short inputs + name = token["name"] + # In Py2, using `in` is quicker in general than try/except KeyError + # In Py3, `in` is quicker when there are few cache hits (typically short inputs) + if name in self.__endTagCache: + func = self.__endTagCache[name] + else: + func = self.__endTagCache[name] = self.endTagHandler[name] + # bound the cache size in case we get loads of unknown tags + while len(self.__endTagCache) > len(self.endTagHandler) * 1.1: + # this makes the eviction policy random on Py < 3.7 and FIFO >= 3.7 + self.__endTagCache.pop(next(iter(self.__endTagCache))) + return func(token) + + class InitialPhase(Phase): + __slots__ = tuple() + + def processSpaceCharacters(self, token): + pass + + def processComment(self, token): + self.tree.insertComment(token, self.tree.document) + + def processDoctype(self, token): + name = token["name"] + publicId = token["publicId"] + systemId = token["systemId"] + correct = token["correct"] + + if (name != "html" or publicId is not None or + systemId is not None and systemId != "about:legacy-compat"): + self.parser.parseError("unknown-doctype") + + if publicId is None: + publicId = "" + + self.tree.insertDoctype(token) + + if publicId != "": + publicId = publicId.translate(asciiUpper2Lower) + + if (not correct or token["name"] != "html" or + publicId.startswith( + ("+//silmaril//dtd html pro v0r11 19970101//", + "-//advasoft ltd//dtd html 3.0 aswedit + extensions//", + "-//as//dtd html 3.0 aswedit + extensions//", + "-//ietf//dtd html 2.0 level 1//", + "-//ietf//dtd html 2.0 level 2//", + "-//ietf//dtd html 2.0 strict level 1//", + "-//ietf//dtd html 2.0 strict level 2//", + "-//ietf//dtd html 2.0 strict//", + "-//ietf//dtd html 2.0//", + "-//ietf//dtd html 2.1e//", + "-//ietf//dtd html 3.0//", + "-//ietf//dtd html 3.2 final//", + "-//ietf//dtd html 3.2//", + "-//ietf//dtd html 3//", + "-//ietf//dtd html level 0//", + "-//ietf//dtd html level 1//", + "-//ietf//dtd html level 2//", + "-//ietf//dtd html level 3//", + "-//ietf//dtd html strict level 0//", + "-//ietf//dtd html strict level 1//", + "-//ietf//dtd html strict level 2//", + "-//ietf//dtd html strict level 3//", + "-//ietf//dtd html strict//", + "-//ietf//dtd html//", + "-//metrius//dtd metrius presentational//", + "-//microsoft//dtd internet explorer 2.0 html strict//", + "-//microsoft//dtd internet explorer 2.0 html//", + "-//microsoft//dtd internet explorer 2.0 tables//", + "-//microsoft//dtd internet explorer 3.0 html strict//", + "-//microsoft//dtd internet explorer 3.0 html//", + "-//microsoft//dtd internet explorer 3.0 tables//", + "-//netscape comm. corp.//dtd html//", + "-//netscape comm. corp.//dtd strict html//", + "-//o'reilly and associates//dtd html 2.0//", + "-//o'reilly and associates//dtd html extended 1.0//", + "-//o'reilly and associates//dtd html extended relaxed 1.0//", + "-//softquad software//dtd hotmetal pro 6.0::19990601::extensions to html 4.0//", + "-//softquad//dtd hotmetal pro 4.0::19971010::extensions to html 4.0//", + "-//spyglass//dtd html 2.0 extended//", + "-//sq//dtd html 2.0 hotmetal + extensions//", + "-//sun microsystems corp.//dtd hotjava html//", + "-//sun microsystems corp.//dtd hotjava strict html//", + "-//w3c//dtd html 3 1995-03-24//", + "-//w3c//dtd html 3.2 draft//", + "-//w3c//dtd html 3.2 final//", + "-//w3c//dtd html 3.2//", + "-//w3c//dtd html 3.2s draft//", + "-//w3c//dtd html 4.0 frameset//", + "-//w3c//dtd html 4.0 transitional//", + "-//w3c//dtd html experimental 19960712//", + "-//w3c//dtd html experimental 970421//", + "-//w3c//dtd w3 html//", + "-//w3o//dtd w3 html 3.0//", + "-//webtechs//dtd mozilla html 2.0//", + "-//webtechs//dtd mozilla html//")) or + publicId in ("-//w3o//dtd w3 html strict 3.0//en//", + "-/w3c/dtd html 4.0 transitional/en", + "html") or + publicId.startswith( + ("-//w3c//dtd html 4.01 frameset//", + "-//w3c//dtd html 4.01 transitional//")) and + systemId is None or + systemId and systemId.lower() == "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd"): + self.parser.compatMode = "quirks" + elif (publicId.startswith( + ("-//w3c//dtd xhtml 1.0 frameset//", + "-//w3c//dtd xhtml 1.0 transitional//")) or + publicId.startswith( + ("-//w3c//dtd html 4.01 frameset//", + "-//w3c//dtd html 4.01 transitional//")) and + systemId is not None): + self.parser.compatMode = "limited quirks" + + self.parser.phase = self.parser.phases["beforeHtml"] + + def anythingElse(self): + self.parser.compatMode = "quirks" + self.parser.phase = self.parser.phases["beforeHtml"] + + def processCharacters(self, token): + self.parser.parseError("expected-doctype-but-got-chars") + self.anythingElse() + return token + + def processStartTag(self, token): + self.parser.parseError("expected-doctype-but-got-start-tag", + {"name": token["name"]}) + self.anythingElse() + return token + + def processEndTag(self, token): + self.parser.parseError("expected-doctype-but-got-end-tag", + {"name": token["name"]}) + self.anythingElse() + return token + + def processEOF(self): + self.parser.parseError("expected-doctype-but-got-eof") + self.anythingElse() + return True + + class BeforeHtmlPhase(Phase): + __slots__ = tuple() + + # helper methods + def insertHtmlElement(self): + self.tree.insertRoot(impliedTagToken("html", "StartTag")) + self.parser.phase = self.parser.phases["beforeHead"] + + # other + def processEOF(self): + self.insertHtmlElement() + return True + + def processComment(self, token): + self.tree.insertComment(token, self.tree.document) + + def processSpaceCharacters(self, token): + pass + + def processCharacters(self, token): + self.insertHtmlElement() + return token + + def processStartTag(self, token): + if token["name"] == "html": + self.parser.firstStartTag = True + self.insertHtmlElement() + return token + + def processEndTag(self, token): + if token["name"] not in ("head", "body", "html", "br"): + self.parser.parseError("unexpected-end-tag-before-html", + {"name": token["name"]}) + else: + self.insertHtmlElement() + return token + + class BeforeHeadPhase(Phase): + __slots__ = tuple() + + def processEOF(self): + self.startTagHead(impliedTagToken("head", "StartTag")) + return True + + def processSpaceCharacters(self, token): + pass + + def processCharacters(self, token): + self.startTagHead(impliedTagToken("head", "StartTag")) + return token + + def startTagHtml(self, token): + return self.parser.phases["inBody"].processStartTag(token) + + def startTagHead(self, token): + self.tree.insertElement(token) + self.tree.headPointer = self.tree.openElements[-1] + self.parser.phase = self.parser.phases["inHead"] + + def startTagOther(self, token): + self.startTagHead(impliedTagToken("head", "StartTag")) + return token + + def endTagImplyHead(self, token): + self.startTagHead(impliedTagToken("head", "StartTag")) + return token + + def endTagOther(self, token): + self.parser.parseError("end-tag-after-implied-root", + {"name": token["name"]}) + + startTagHandler = _utils.MethodDispatcher([ + ("html", startTagHtml), + ("head", startTagHead) + ]) + startTagHandler.default = startTagOther + + endTagHandler = _utils.MethodDispatcher([ + (("head", "body", "html", "br"), endTagImplyHead) + ]) + endTagHandler.default = endTagOther + + class InHeadPhase(Phase): + __slots__ = tuple() + + # the real thing + def processEOF(self): + self.anythingElse() + return True + + def processCharacters(self, token): + self.anythingElse() + return token + + def startTagHtml(self, token): + return self.parser.phases["inBody"].processStartTag(token) + + def startTagHead(self, token): + self.parser.parseError("two-heads-are-not-better-than-one") + + def startTagBaseLinkCommand(self, token): + self.tree.insertElement(token) + self.tree.openElements.pop() + token["selfClosingAcknowledged"] = True + + def startTagMeta(self, token): + self.tree.insertElement(token) + self.tree.openElements.pop() + token["selfClosingAcknowledged"] = True + + attributes = token["data"] + if self.parser.tokenizer.stream.charEncoding[1] == "tentative": + if "charset" in attributes: + self.parser.tokenizer.stream.changeEncoding(attributes["charset"]) + elif ("content" in attributes and + "http-equiv" in attributes and + attributes["http-equiv"].lower() == "content-type"): + # Encoding it as UTF-8 here is a hack, as really we should pass + # the abstract Unicode string, and just use the + # ContentAttrParser on that, but using UTF-8 allows all chars + # to be encoded and as a ASCII-superset works. + data = _inputstream.EncodingBytes(attributes["content"].encode("utf-8")) + parser = _inputstream.ContentAttrParser(data) + codec = parser.parse() + self.parser.tokenizer.stream.changeEncoding(codec) + + def startTagTitle(self, token): + self.parser.parseRCDataRawtext(token, "RCDATA") + + def startTagNoFramesStyle(self, token): + # Need to decide whether to implement the scripting-disabled case + self.parser.parseRCDataRawtext(token, "RAWTEXT") + + def startTagNoscript(self, token): + if self.parser.scripting: + self.parser.parseRCDataRawtext(token, "RAWTEXT") + else: + self.tree.insertElement(token) + self.parser.phase = self.parser.phases["inHeadNoscript"] + + def startTagScript(self, token): + self.tree.insertElement(token) + self.parser.tokenizer.state = self.parser.tokenizer.scriptDataState + self.parser.originalPhase = self.parser.phase + self.parser.phase = self.parser.phases["text"] + + def startTagOther(self, token): + self.anythingElse() + return token + + def endTagHead(self, token): + node = self.parser.tree.openElements.pop() + assert node.name == "head", "Expected head got %s" % node.name + self.parser.phase = self.parser.phases["afterHead"] + + def endTagHtmlBodyBr(self, token): + self.anythingElse() + return token + + def endTagOther(self, token): + self.parser.parseError("unexpected-end-tag", {"name": token["name"]}) + + def anythingElse(self): + self.endTagHead(impliedTagToken("head")) + + startTagHandler = _utils.MethodDispatcher([ + ("html", startTagHtml), + ("title", startTagTitle), + (("noframes", "style"), startTagNoFramesStyle), + ("noscript", startTagNoscript), + ("script", startTagScript), + (("base", "basefont", "bgsound", "command", "link"), + startTagBaseLinkCommand), + ("meta", startTagMeta), + ("head", startTagHead) + ]) + startTagHandler.default = startTagOther + + endTagHandler = _utils.MethodDispatcher([ + ("head", endTagHead), + (("br", "html", "body"), endTagHtmlBodyBr) + ]) + endTagHandler.default = endTagOther + + class InHeadNoscriptPhase(Phase): + __slots__ = tuple() + + def processEOF(self): + self.parser.parseError("eof-in-head-noscript") + self.anythingElse() + return True + + def processComment(self, token): + return self.parser.phases["inHead"].processComment(token) + + def processCharacters(self, token): + self.parser.parseError("char-in-head-noscript") + self.anythingElse() + return token + + def processSpaceCharacters(self, token): + return self.parser.phases["inHead"].processSpaceCharacters(token) + + def startTagHtml(self, token): + return self.parser.phases["inBody"].processStartTag(token) + + def startTagBaseLinkCommand(self, token): + return self.parser.phases["inHead"].processStartTag(token) + + def startTagHeadNoscript(self, token): + self.parser.parseError("unexpected-start-tag", {"name": token["name"]}) + + def startTagOther(self, token): + self.parser.parseError("unexpected-inhead-noscript-tag", {"name": token["name"]}) + self.anythingElse() + return token + + def endTagNoscript(self, token): + node = self.parser.tree.openElements.pop() + assert node.name == "noscript", "Expected noscript got %s" % node.name + self.parser.phase = self.parser.phases["inHead"] + + def endTagBr(self, token): + self.parser.parseError("unexpected-inhead-noscript-tag", {"name": token["name"]}) + self.anythingElse() + return token + + def endTagOther(self, token): + self.parser.parseError("unexpected-end-tag", {"name": token["name"]}) + + def anythingElse(self): + # Caller must raise parse error first! + self.endTagNoscript(impliedTagToken("noscript")) + + startTagHandler = _utils.MethodDispatcher([ + ("html", startTagHtml), + (("basefont", "bgsound", "link", "meta", "noframes", "style"), startTagBaseLinkCommand), + (("head", "noscript"), startTagHeadNoscript), + ]) + startTagHandler.default = startTagOther + + endTagHandler = _utils.MethodDispatcher([ + ("noscript", endTagNoscript), + ("br", endTagBr), + ]) + endTagHandler.default = endTagOther + + class AfterHeadPhase(Phase): + __slots__ = tuple() + + def processEOF(self): + self.anythingElse() + return True + + def processCharacters(self, token): + self.anythingElse() + return token + + def startTagHtml(self, token): + return self.parser.phases["inBody"].processStartTag(token) + + def startTagBody(self, token): + self.parser.framesetOK = False + self.tree.insertElement(token) + self.parser.phase = self.parser.phases["inBody"] + + def startTagFrameset(self, token): + self.tree.insertElement(token) + self.parser.phase = self.parser.phases["inFrameset"] + + def startTagFromHead(self, token): + self.parser.parseError("unexpected-start-tag-out-of-my-head", + {"name": token["name"]}) + self.tree.openElements.append(self.tree.headPointer) + self.parser.phases["inHead"].processStartTag(token) + for node in self.tree.openElements[::-1]: + if node.name == "head": + self.tree.openElements.remove(node) + break + + def startTagHead(self, token): + self.parser.parseError("unexpected-start-tag", {"name": token["name"]}) + + def startTagOther(self, token): + self.anythingElse() + return token + + def endTagHtmlBodyBr(self, token): + self.anythingElse() + return token + + def endTagOther(self, token): + self.parser.parseError("unexpected-end-tag", {"name": token["name"]}) + + def anythingElse(self): + self.tree.insertElement(impliedTagToken("body", "StartTag")) + self.parser.phase = self.parser.phases["inBody"] + self.parser.framesetOK = True + + startTagHandler = _utils.MethodDispatcher([ + ("html", startTagHtml), + ("body", startTagBody), + ("frameset", startTagFrameset), + (("base", "basefont", "bgsound", "link", "meta", "noframes", "script", + "style", "title"), + startTagFromHead), + ("head", startTagHead) + ]) + startTagHandler.default = startTagOther + endTagHandler = _utils.MethodDispatcher([(("body", "html", "br"), + endTagHtmlBodyBr)]) + endTagHandler.default = endTagOther + + class InBodyPhase(Phase): + # http://www.whatwg.org/specs/web-apps/current-work/#parsing-main-inbody + # the really-really-really-very crazy mode + __slots__ = ("processSpaceCharacters",) + + def __init__(self, *args, **kwargs): + super(InBodyPhase, self).__init__(*args, **kwargs) + # Set this to the default handler + self.processSpaceCharacters = self.processSpaceCharactersNonPre + + def isMatchingFormattingElement(self, node1, node2): + return (node1.name == node2.name and + node1.namespace == node2.namespace and + node1.attributes == node2.attributes) + + # helper + def addFormattingElement(self, token): + self.tree.insertElement(token) + element = self.tree.openElements[-1] + + matchingElements = [] + for node in self.tree.activeFormattingElements[::-1]: + if node is Marker: + break + elif self.isMatchingFormattingElement(node, element): + matchingElements.append(node) + + assert len(matchingElements) <= 3 + if len(matchingElements) == 3: + self.tree.activeFormattingElements.remove(matchingElements[-1]) + self.tree.activeFormattingElements.append(element) + + # the real deal + def processEOF(self): + allowed_elements = frozenset(("dd", "dt", "li", "p", "tbody", "td", + "tfoot", "th", "thead", "tr", "body", + "html")) + for node in self.tree.openElements[::-1]: + if node.name not in allowed_elements: + self.parser.parseError("expected-closing-tag-but-got-eof") + break + # Stop parsing + + def processSpaceCharactersDropNewline(self, token): + # Sometimes (start of
    , , and 
    +
    +
    + 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
    +
    +""" + +SOURCE_LINE_HTML = """\ + + %(lineno)s + %(code)s + +""" + + +def render_console_html(secret: str, evalex_trusted: bool = True) -> str: + return CONSOLE_HTML % { + "evalex": "true", + "evalex_trusted": "true" if evalex_trusted else "false", + "console": "true", + "title": "Console", + "secret": secret, + "traceback_id": -1, + } + + +def get_current_traceback( + ignore_system_exceptions: bool = False, + show_hidden_frames: bool = False, + skip: int = 0, +) -> "Traceback": + """Get the current exception info as `Traceback` object. Per default + calling this method will reraise system exceptions such as generator exit, + system exit or others. This behavior can be disabled by passing `False` + to the function as first parameter. + """ + info = t.cast( + t.Tuple[t.Type[BaseException], BaseException, TracebackType], sys.exc_info() + ) + exc_type, exc_value, tb = info + + if ignore_system_exceptions and exc_type in { + SystemExit, + KeyboardInterrupt, + GeneratorExit, + }: + raise + for _ in range(skip): + if tb.tb_next is None: + break + tb = tb.tb_next + tb = Traceback(exc_type, exc_value, tb) + if not show_hidden_frames: + tb.filter_hidden_frames() + return tb + + +class Line: + """Helper for the source renderer.""" + + __slots__ = ("lineno", "code", "in_frame", "current") + + def __init__(self, lineno: int, code: str) -> None: + self.lineno = lineno + self.code = code + self.in_frame = False + self.current = False + + @property + def classes(self) -> t.List[str]: + rv = ["line"] + if self.in_frame: + rv.append("in-frame") + if self.current: + rv.append("current") + return rv + + def render(self) -> str: + return SOURCE_LINE_HTML % { + "classes": " ".join(self.classes), + "lineno": self.lineno, + "code": escape(self.code), + } + + +class Traceback: + """Wraps a traceback.""" + + def __init__( + self, + exc_type: t.Type[BaseException], + exc_value: BaseException, + tb: TracebackType, + ) -> None: + self.exc_type = exc_type + self.exc_value = exc_value + self.tb = tb + + exception_type = exc_type.__name__ + if exc_type.__module__ not in {"builtins", "__builtin__", "exceptions"}: + exception_type = f"{exc_type.__module__}.{exception_type}" + self.exception_type = exception_type + + self.groups = [] + memo = set() + while True: + self.groups.append(Group(exc_type, exc_value, tb)) + memo.add(id(exc_value)) + exc_value = exc_value.__cause__ or exc_value.__context__ # type: ignore + if exc_value is None or id(exc_value) in memo: + break + exc_type = type(exc_value) + tb = exc_value.__traceback__ # type: ignore + self.groups.reverse() + self.frames = [frame for group in self.groups for frame in group.frames] + + def filter_hidden_frames(self) -> None: + """Remove the frames according to the paste spec.""" + for group in self.groups: + group.filter_hidden_frames() + + self.frames[:] = [frame for group in self.groups for frame in group.frames] + + @property + def is_syntax_error(self) -> bool: + """Is it a syntax error?""" + return isinstance(self.exc_value, SyntaxError) + + @property + def exception(self) -> str: + """String representation of the final exception.""" + return self.groups[-1].exception + + def log(self, logfile: t.Optional[t.IO[str]] = None) -> None: + """Log the ASCII traceback into a file object.""" + if logfile is None: + logfile = sys.stderr + tb = f"{self.plaintext.rstrip()}\n" + logfile.write(tb) + + def render_summary(self, include_title: bool = True) -> str: + """Render the traceback for the interactive console.""" + title = "" + classes = ["traceback"] + if not self.frames: + classes.append("noframe-traceback") + frames = [] + else: + library_frames = sum(frame.is_library for frame in self.frames) + mark_lib = 0 < library_frames < len(self.frames) + frames = [group.render(mark_lib=mark_lib) for group in self.groups] + + if include_title: + if self.is_syntax_error: + title = "Syntax Error" + else: + title = "Traceback (most recent call last):" + + if self.is_syntax_error: + description = f"
    {escape(self.exception)}
    " + else: + description = f"
    {escape(self.exception)}
    " + + return SUMMARY_HTML % { + "classes": " ".join(classes), + "title": f"

    {title if title else ''}

    ", + "frames": "\n".join(frames), + "description": description, + } + + def render_full( + self, + evalex: bool = False, + secret: t.Optional[str] = None, + evalex_trusted: bool = True, + ) -> str: + """Render the Full HTML page with the traceback info.""" + exc = escape(self.exception) + return PAGE_HTML % { + "evalex": "true" if evalex else "false", + "evalex_trusted": "true" if evalex_trusted else "false", + "console": "false", + "title": exc, + "exception": exc, + "exception_type": escape(self.exception_type), + "summary": self.render_summary(include_title=False), + "plaintext": escape(self.plaintext), + "plaintext_cs": re.sub("-{2,}", "-", self.plaintext), + "traceback_id": self.id, + "secret": secret, + } + + @cached_property + def plaintext(self) -> str: + return "\n".join([group.render_text() for group in self.groups]) + + @property + def id(self) -> int: + return id(self) + + +class Group: + """A group of frames for an exception in a traceback. If the + exception has a ``__cause__`` or ``__context__``, there are multiple + exception groups. + """ + + def __init__( + self, + exc_type: t.Type[BaseException], + exc_value: BaseException, + tb: TracebackType, + ) -> None: + self.exc_type = exc_type + self.exc_value = exc_value + self.info = None + if exc_value.__cause__ is not None: + self.info = ( + "The above exception was the direct cause of the following exception" + ) + elif exc_value.__context__ is not None: + self.info = ( + "During handling of the above exception, another exception occurred" + ) + + self.frames = [] + while tb is not None: + self.frames.append(Frame(exc_type, exc_value, tb)) + tb = tb.tb_next # type: ignore + + def filter_hidden_frames(self) -> None: + # An exception may not have a traceback to filter frames, such + # as one re-raised from ProcessPoolExecutor. + if not self.frames: + return + + new_frames: t.List[Frame] = [] + hidden = False + + for frame in self.frames: + hide = frame.hide + if hide in ("before", "before_and_this"): + new_frames = [] + hidden = False + if hide == "before_and_this": + continue + elif hide in ("reset", "reset_and_this"): + hidden = False + if hide == "reset_and_this": + continue + elif hide in ("after", "after_and_this"): + hidden = True + if hide == "after_and_this": + continue + elif hide or hidden: + continue + new_frames.append(frame) + + # if we only have one frame and that frame is from the codeop + # module, remove it. + if len(new_frames) == 1 and self.frames[0].module == "codeop": + del self.frames[:] + + # if the last frame is missing something went terrible wrong :( + elif self.frames[-1] in new_frames: + self.frames[:] = new_frames + + @property + def exception(self) -> str: + """String representation of the exception.""" + buf = traceback.format_exception_only(self.exc_type, self.exc_value) + rv = "".join(buf).strip() + return _to_str(rv, "utf-8", "replace") + + def render(self, mark_lib: bool = True) -> str: + out = [] + if self.info is not None: + out.append(f'
  1. {self.info}:
    ') + for frame in self.frames: + title = f' title="{escape(frame.info)}"' if frame.info else "" + out.append(f"{frame.render(mark_lib=mark_lib)}") + return "\n".join(out) + + def render_text(self) -> str: + out = [] + if self.info is not None: + out.append(f"\n{self.info}:\n") + out.append("Traceback (most recent call last):") + for frame in self.frames: + out.append(frame.render_text()) + out.append(self.exception) + return "\n".join(out) + + +class Frame: + """A single frame in a traceback.""" + + def __init__( + self, + exc_type: t.Type[BaseException], + exc_value: BaseException, + tb: TracebackType, + ) -> None: + self.lineno = tb.tb_lineno + self.function_name = tb.tb_frame.f_code.co_name + self.locals = tb.tb_frame.f_locals + self.globals = tb.tb_frame.f_globals + + fn = inspect.getsourcefile(tb) or inspect.getfile(tb) + if fn[-4:] in (".pyo", ".pyc"): + fn = fn[:-1] + # if it's a file on the file system resolve the real filename. + if os.path.isfile(fn): + fn = os.path.realpath(fn) + self.filename = _to_str(fn, get_filesystem_encoding()) + self.module = self.globals.get("__name__", self.locals.get("__name__")) + self.loader = self.globals.get("__loader__", self.locals.get("__loader__")) + self.code = tb.tb_frame.f_code + + # support for paste's traceback extensions + self.hide = self.locals.get("__traceback_hide__", False) + info = self.locals.get("__traceback_info__") + if info is not None: + info = _to_str(info, "utf-8", "replace") + self.info = info + + def render(self, mark_lib: bool = True) -> str: + """Render a single frame in a traceback.""" + return FRAME_HTML % { + "id": self.id, + "filename": escape(self.filename), + "lineno": self.lineno, + "function_name": escape(self.function_name), + "lines": self.render_line_context(), + "library": "library" if mark_lib and self.is_library else "", + } + + @cached_property + def is_library(self) -> bool: + return any( + self.filename.startswith(os.path.realpath(path)) + for path in sysconfig.get_paths().values() + ) + + def render_text(self) -> str: + return ( + f' File "{self.filename}", line {self.lineno}, in {self.function_name}\n' + f" {self.current_line.strip()}" + ) + + def render_line_context(self) -> str: + before, current, after = self.get_context_lines() + rv = [] + + def render_line(line: str, cls: str) -> None: + line = line.expandtabs().rstrip() + stripped_line = line.strip() + prefix = len(line) - len(stripped_line) + rv.append( + f'
    {" " * prefix}'
    +                f"{escape(stripped_line) if stripped_line else ' '}
    " + ) + + for line in before: + render_line(line, "before") + render_line(current, "current") + for line in after: + render_line(line, "after") + + return "\n".join(rv) + + def get_annotated_lines(self) -> t.List[Line]: + """Helper function that returns lines with extra information.""" + lines = [Line(idx + 1, x) for idx, x in enumerate(self.sourcelines)] + + # find function definition and mark lines + if hasattr(self.code, "co_firstlineno"): + lineno = self.code.co_firstlineno - 1 + while lineno > 0: + if _funcdef_re.match(lines[lineno].code): + break + lineno -= 1 + try: + offset = len(inspect.getblock([f"{x.code}\n" for x in lines[lineno:]])) + except TokenError: + offset = 0 + for line in lines[lineno : lineno + offset]: + line.in_frame = True + + # mark current line + try: + lines[self.lineno - 1].current = True + except IndexError: + pass + + return lines + + def eval(self, code: t.Union[str, CodeType], mode: str = "single") -> t.Any: + """Evaluate code in the context of the frame.""" + if isinstance(code, str): + code = compile(code, "", mode) + return eval(code, self.globals, self.locals) + + @cached_property + def sourcelines(self) -> t.List[str]: + """The sourcecode of the file as list of strings.""" + # get sourcecode from loader or file + source = None + if self.loader is not None: + try: + if hasattr(self.loader, "get_source"): + source = self.loader.get_source(self.module) + elif hasattr(self.loader, "get_source_by_code"): + source = self.loader.get_source_by_code(self.code) + except Exception: + # we munch the exception so that we don't cause troubles + # if the loader is broken. + pass + + if source is None: + try: + with open(self.filename, mode="rb") as f: + source = f.read() + except OSError: + return [] + + # already str? return right away + if isinstance(source, str): + return source.splitlines() + + charset = "utf-8" + if source.startswith(codecs.BOM_UTF8): + source = source[3:] + else: + for idx, match in enumerate(_line_re.finditer(source)): + coding_match = _coding_re.search(match.group()) + if coding_match is not None: + charset = coding_match.group(1).decode("utf-8") + break + if idx > 1: + break + + # on broken cookies we fall back to utf-8 too + charset = _to_str(charset) + try: + codecs.lookup(charset) + except LookupError: + charset = "utf-8" + + return source.decode(charset, "replace").splitlines() + + def get_context_lines( + self, context: int = 5 + ) -> t.Tuple[t.List[str], str, t.List[str]]: + before = self.sourcelines[self.lineno - context - 1 : self.lineno - 1] + past = self.sourcelines[self.lineno : self.lineno + context] + return (before, self.current_line, past) + + @property + def current_line(self) -> str: + try: + return self.sourcelines[self.lineno - 1] + except IndexError: + return "" + + @cached_property + def console(self) -> Console: + return Console(self.globals, self.locals) + + @property + def id(self) -> int: + return id(self) diff --git a/.venv/lib/python3.6/site-packages/werkzeug/exceptions.py b/.venv/lib/python3.6/site-packages/werkzeug/exceptions.py new file mode 100644 index 0000000..16c3964 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/werkzeug/exceptions.py @@ -0,0 +1,943 @@ +"""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 sys +import typing as t +import warnings +from datetime import datetime +from html import escape + +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.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. + """ + + 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 + + @classmethod + def wrap( + cls, exception: t.Type[BaseException], name: t.Optional[str] = None + ) -> t.Type["HTTPException"]: + """Create an exception that is a subclass of the calling HTTP + exception and the ``exception`` argument. + + The first argument to the class will be passed to the + wrapped ``exception``, the rest to the HTTP exception. If + ``e.args`` is not empty and ``e.show_exception`` is ``True``, + the wrapped exception message is added to the HTTP error + description. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. Create a subclass manually + instead. + + .. versionchanged:: 0.15.5 + The ``show_exception`` attribute controls whether the + description includes the wrapped exception message. + + .. versionchanged:: 0.15.0 + The description includes the wrapped exception message. + """ + warnings.warn( + "'HTTPException.wrap' is deprecated and will be removed in" + " Werkzeug 2.1. Create a subclass manually instead.", + DeprecationWarning, + stacklevel=2, + ) + + class newcls(cls, exception): # type: ignore + _description = cls.description + show_exception = False + + def __init__( + self, arg: t.Optional[t.Any] = None, *args: t.Any, **kwargs: t.Any + ) -> None: + super().__init__(*args, **kwargs) + + if arg is None: + exception.__init__(self) + else: + exception.__init__(self, arg) + + @property + def description(self) -> str: + if self.show_exception: + return ( + f"{self._description}\n" + f"{exception.__name__}: {exception.__str__(self)}" + ) + + return self._description # type: ignore + + @description.setter + def description(self, value: str) -> None: + self._description = value + + newcls.__module__ = sys._getframe(1).f_globals["__name__"] + name = name or cls.__name__ + exception.__name__ + newcls.__name__ = newcls.__qualname__ = name + return newcls + + @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", "
    ") + return f"

    {description}

    " + + def get_body( + self, + environ: t.Optional["WSGIEnvironment"] = None, + scope: t.Optional[dict] = None, + ) -> str: + """Get the HTML body.""" + return ( + '\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["WSGIEnvironment"] = 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/.venv/lib/python3.6/site-packages/werkzeug/filesystem.py b/.venv/lib/python3.6/site-packages/werkzeug/filesystem.py new file mode 100644 index 0000000..36a3d12 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/werkzeug/filesystem.py @@ -0,0 +1,55 @@ +import codecs +import sys +import typing as t +import warnings + +# We do not trust traditional unixes. +has_likely_buggy_unicode_filesystem = ( + sys.platform.startswith("linux") or "bsd" in sys.platform +) + + +def _is_ascii_encoding(encoding: t.Optional[str]) -> bool: + """Given an encoding this figures out if the encoding is actually ASCII (which + is something we don't actually want in most cases). This is necessary + because ASCII comes under many names such as ANSI_X3.4-1968. + """ + if encoding is None: + return False + try: + return codecs.lookup(encoding).name == "ascii" + except LookupError: + return False + + +class BrokenFilesystemWarning(RuntimeWarning, UnicodeWarning): + """The warning used by Werkzeug to signal a broken filesystem. Will only be + used once per runtime.""" + + +_warned_about_filesystem_encoding = False + + +def get_filesystem_encoding() -> str: + """Returns the filesystem encoding that should be used. Note that this is + different from the Python understanding of the filesystem encoding which + might be deeply flawed. Do not use this value against Python's string APIs + because it might be different. See :ref:`filesystem-encoding` for the exact + behavior. + + The concept of a filesystem encoding in generally is not something you + should rely on. As such if you ever need to use this function except for + writing wrapper code reconsider. + """ + global _warned_about_filesystem_encoding + rv = sys.getfilesystemencoding() + if has_likely_buggy_unicode_filesystem and not rv or _is_ascii_encoding(rv): + if not _warned_about_filesystem_encoding: + warnings.warn( + "Detected a misconfigured UNIX filesystem: Will use" + f" UTF-8 as filesystem encoding instead of {rv!r}", + BrokenFilesystemWarning, + ) + _warned_about_filesystem_encoding = True + return "utf-8" + return rv diff --git a/.venv/lib/python3.6/site-packages/werkzeug/formparser.py b/.venv/lib/python3.6/site-packages/werkzeug/formparser.py new file mode 100644 index 0000000..6cb758f --- /dev/null +++ b/.venv/lib/python3.6/site-packages/werkzeug/formparser.py @@ -0,0 +1,495 @@ +import typing as t +import warnings +from functools import update_wrapper +from io import BytesIO +from itertools import chain +from typing import Union + +from . import exceptions +from ._internal import _to_str +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 + + +def parse_multipart_headers(iterable: t.Iterable[bytes]) -> Headers: + """Parses multipart headers from an iterable that yields lines (including + the trailing newline symbol). The iterable has to be newline terminated. + The iterable will stop at the line where the headers ended so it can be + further consumed. + :param iterable: iterable of strings that are newline terminated + """ + warnings.warn( + "'parse_multipart_headers' is deprecated and will be removed in" + " Werkzeug 2.1.", + DeprecationWarning, + stacklevel=2, + ) + result: t.List[t.Tuple[str, str]] = [] + + for b_line in iterable: + line = _to_str(b_line) + line, line_terminated = _line_parse(line) + + if not line_terminated: + raise ValueError("unexpected end of line in multipart header") + + if not line: + break + elif line[0] in " \t" and result: + key, value = result[-1] + result[-1] = (key, f"{value}\n {line[1:]}") + else: + parts = line.split(":", 1) + + if len(parts) == 2: + result.append((parts[0].strip(), parts[1].strip())) + + # we link the list to the headers, no need to create a copy, the + # list was not shared anyways. + return Headers(result) + + +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/.venv/lib/python3.6/site-packages/werkzeug/http.py b/.venv/lib/python3.6/site-packages/werkzeug/http.py new file mode 100644 index 0000000..ca48fe2 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/werkzeug/http.py @@ -0,0 +1,1388 @@ +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_parse_impl +from ._internal import _cookie_quote +from ._internal import _make_cookie_domain +from ._internal import _to_bytes +from ._internal import _to_str +from ._internal import _wsgi_decoding_dance +from werkzeug._internal import _dt_as_utc + +if t.TYPE_CHECKING: + import typing_extensions as te + 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 + + +@typing.overload +def parse_options_header( + value: t.Optional[str], multiple: "te.Literal[False]" = False +) -> t.Tuple[str, t.Dict[str, str]]: + ... + + +@typing.overload +def parse_options_header( + value: t.Optional[str], multiple: "te.Literal[True]" +) -> t.Tuple[t.Any, ...]: + ... + + +def parse_options_header( + value: t.Optional[str], multiple: bool = False +) -> t.Union[t.Tuple[str, t.Dict[str, str]], t.Tuple[t.Any, ...]]: + """Parse a ``Content-Type`` like header into a tuple with the content + type and the options: + + >>> parse_options_header('text/html; charset=utf8') + ('text/html', {'charset': 'utf8'}) + + This should not be used to parse ``Cache-Control`` like headers that use + a slightly different format. For these headers use the + :func:`parse_dict_header` function. + + .. versionchanged:: 0.15 + :rfc:`2231` parameter continuations are handled. + + .. versionadded:: 0.5 + + :param value: the header to parse. + :param multiple: Whether try to parse and return multiple MIME types + :return: (mimetype, options) or (mimetype, options, mimetype, options, …) + if multiple=True + """ + 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) + 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. + options[option] = options.get(option, "") + option_value + else: + options[option] = option_value + rest = rest[optmatch.end() :] + result.append(options) + if multiple is False: + return tuple(result) + value = rest + + return tuple(result) if result else ("", {}) + + +_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() + if not begin_str.isdigit(): + return None + begin = int(begin_str) + if begin < last_end or last_end < 0: + return None + if end_str: + if not end_str.isdigit(): + return None + end = int(end_str) + 1 + 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 + elif length_str.isdigit(): + length = int(length_str) + else: + 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 cookie_date( + expires: t.Optional[t.Union[datetime, date, int, float, struct_time]] = None +) -> str: + """Format a datetime object or timestamp into an :rfc:`2822` date + string for ``Set-Cookie expires``. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. Use :func:`http_date` instead. + """ + warnings.warn( + "'cookie_date' is deprecated and will be removed in Werkzeug" + " 2.1. Use 'http_date' instead.", + DeprecationWarning, + stacklevel=2, + ) + return http_date(expires) + + +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``. + """ + 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" in environ: + # 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(environ.get("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(environ.get("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(environ.get("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(environ.get("HTTP_IF_MATCH")) + if if_match: + unmodified = not if_match.is_strong(etag) + + return not unmodified + + +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): + header = header.get("HTTP_COOKIE", "") + elif header is None: + header = "" + + # PEP 3333 sends headers through the environ as latin1 decoded + # strings. Encode strings back to bytes for parsing. + if isinstance(header, str): + header = header.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(header): # 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()) + + +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 diff --git a/.venv/lib/python3.6/site-packages/werkzeug/local.py b/.venv/lib/python3.6/site-packages/werkzeug/local.py new file mode 100644 index 0000000..b4dee7b --- /dev/null +++ b/.venv/lib/python3.6/site-packages/werkzeug/local.py @@ -0,0 +1,677 @@ +import copy +import math +import operator +import sys +import typing as t +import warnings +from functools import partial +from functools import update_wrapper + +from .wsgi import ClosingIterator + +if t.TYPE_CHECKING: + from _typeshed.wsgi import StartResponse + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) + +try: + from greenlet import getcurrent as _get_ident +except ImportError: + from threading import get_ident as _get_ident + + +def get_ident() -> int: + warnings.warn( + "'get_ident' is deprecated and will be removed in Werkzeug" + " 2.1. Use 'greenlet.getcurrent' or 'threading.get_ident' for" + " previous behavior.", + DeprecationWarning, + stacklevel=2, + ) + return _get_ident() # type: ignore + + +class _CannotUseContextVar(Exception): + pass + + +try: + from contextvars import ContextVar + + if "gevent" in sys.modules or "eventlet" in sys.modules: + # Both use greenlet, so first check it has patched + # ContextVars, Greenlet <0.4.17 does not. + import greenlet + + greenlet_patched = getattr(greenlet, "GREENLET_USE_CONTEXT_VARS", False) + + if not greenlet_patched: + # If Gevent is used, check it has patched ContextVars, + # <20.5 does not. + try: + from gevent.monkey import is_object_patched + except ImportError: + # Gevent isn't used, but Greenlet is and hasn't patched + raise _CannotUseContextVar() from None + else: + if is_object_patched("threading", "local") and not is_object_patched( + "contextvars", "ContextVar" + ): + raise _CannotUseContextVar() + + def __release_local__(storage: t.Any) -> None: + # Can remove when support for non-stdlib ContextVars is + # removed, see "Fake" version below. + storage.set({}) + + +except (ImportError, _CannotUseContextVar): + + class ContextVar: # type: ignore + """A fake ContextVar based on the previous greenlet/threading + ident function. Used on Python 3.6, eventlet, and old versions + of gevent. + """ + + def __init__(self, _name: str) -> None: + self.storage: t.Dict[int, t.Dict[str, t.Any]] = {} + + def get(self, default: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]: + return self.storage.get(_get_ident(), default) + + def set(self, value: t.Dict[str, t.Any]) -> None: + self.storage[_get_ident()] = value + + def __release_local__(storage: t.Any) -> None: + # Special version to ensure that the storage is cleaned up on + # release. + storage.storage.pop(_get_ident(), None) + + +def release_local(local: t.Union["Local", "LocalStack"]) -> None: + """Releases the contents of the local for the current context. + This makes it possible to use locals without a manager. + + Example:: + + >>> loc = Local() + >>> loc.foo = 42 + >>> release_local(loc) + >>> hasattr(loc, 'foo') + False + + With this function one can release :class:`Local` objects as well + as :class:`LocalStack` objects. However it is not possible to + release data held by proxies that way, one always has to retain + a reference to the underlying local object in order to be able + to release it. + + .. versionadded:: 0.6.1 + """ + local.__release_local__() + + +class Local: + __slots__ = ("_storage",) + + def __init__(self) -> None: + object.__setattr__(self, "_storage", ContextVar("local_storage")) + + @property + def __storage__(self) -> t.Dict[str, t.Any]: + warnings.warn( + "'__storage__' is deprecated and will be removed in Werkzeug 2.1.", + DeprecationWarning, + stacklevel=2, + ) + return self._storage.get({}) # type: ignore + + @property + def __ident_func__(self) -> t.Callable[[], int]: + warnings.warn( + "'__ident_func__' is deprecated and will be removed in" + " Werkzeug 2.1. It should not be used in Python 3.7+.", + DeprecationWarning, + stacklevel=2, + ) + return _get_ident # type: ignore + + @__ident_func__.setter + def __ident_func__(self, func: t.Callable[[], int]) -> None: + warnings.warn( + "'__ident_func__' is deprecated and will be removed in" + " Werkzeug 2.1. Setting it no longer has any effect.", + DeprecationWarning, + stacklevel=2, + ) + + def __iter__(self) -> t.Iterator[t.Tuple[int, t.Any]]: + return iter(self._storage.get({}).items()) + + def __call__(self, proxy: str) -> "LocalProxy": + """Create a proxy for a name.""" + return LocalProxy(self, proxy) + + def __release_local__(self) -> None: + __release_local__(self._storage) + + def __getattr__(self, name: str) -> t.Any: + values = self._storage.get({}) + try: + return values[name] + except KeyError: + raise AttributeError(name) from None + + 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({}).copy() + try: + del values[name] + self._storage.set(values) + except KeyError: + raise AttributeError(name) from None + + +class LocalStack: + """This class works similar to a :class:`Local` but keeps a stack + of objects instead. This is best explained with an example:: + + >>> ls = LocalStack() + >>> ls.push(42) + >>> ls.top + 42 + >>> ls.push(23) + >>> ls.top + 23 + >>> ls.pop() + 23 + >>> ls.top + 42 + + They can be force released by using a :class:`LocalManager` or with + the :func:`release_local` function but the correct way is to pop the + item from the stack after using. When the stack is empty it will + no longer be bound to the current context (and as such released). + + By calling the stack without arguments it returns a proxy that resolves to + the topmost item on the stack. + + .. versionadded:: 0.6.1 + """ + + def __init__(self) -> None: + self._local = Local() + + def __release_local__(self) -> None: + self._local.__release_local__() + + @property + def __ident_func__(self) -> t.Callable[[], int]: + return self._local.__ident_func__ + + @__ident_func__.setter + def __ident_func__(self, value: t.Callable[[], int]) -> None: + object.__setattr__(self._local, "__ident_func__", value) + + def __call__(self) -> "LocalProxy": + def _lookup() -> t.Any: + rv = self.top + if rv is None: + raise RuntimeError("object unbound") + return rv + + return LocalProxy(_lookup) + + def push(self, obj: t.Any) -> t.List[t.Any]: + """Pushes a new item to the stack""" + rv = getattr(self._local, "stack", []).copy() + rv.append(obj) + self._local.stack = rv + return rv # type: ignore + + def pop(self) -> t.Any: + """Removes the topmost item from the stack, will return the + old value or `None` if the stack was already empty. + """ + stack = getattr(self._local, "stack", None) + if stack is None: + return None + elif len(stack) == 1: + release_local(self._local) + return stack[-1] + else: + return stack.pop() + + @property + def top(self) -> t.Any: + """The topmost item on the stack. If the stack is empty, + `None` is returned. + """ + try: + return self._local.stack[-1] + except (AttributeError, IndexError): + return None + + +class LocalManager: + """Local objects cannot manage themselves. For that you need a local + manager. You can pass a local manager multiple locals or add them + later by appending them to `manager.locals`. Every time the manager + cleans up, it will clean up all the data left in the locals for this + context. + + .. versionchanged:: 2.0 + ``ident_func`` is deprecated and will be removed in Werkzeug + 2.1. + + .. versionchanged:: 0.6.1 + The :func:`release_local` function can be used instead of a + manager. + + .. versionchanged:: 0.7 + The ``ident_func`` parameter was added. + """ + + def __init__( + self, + locals: t.Optional[t.Iterable[t.Union[Local, LocalStack]]] = None, + ident_func: None = None, + ) -> None: + if locals is None: + self.locals = [] + elif isinstance(locals, Local): + self.locals = [locals] + else: + self.locals = list(locals) + + if ident_func is not None: + warnings.warn( + "'ident_func' is deprecated and will be removed in" + " Werkzeug 2.1. Setting it no longer has any effect.", + DeprecationWarning, + stacklevel=2, + ) + + @property + def ident_func(self) -> t.Callable[[], int]: + warnings.warn( + "'ident_func' is deprecated and will be removed in Werkzeug 2.1.", + DeprecationWarning, + stacklevel=2, + ) + return _get_ident # type: ignore + + @ident_func.setter + def ident_func(self, func: t.Callable[[], int]) -> None: + warnings.warn( + "'ident_func' is deprecated and will be removedin Werkzeug" + " 2.1. Setting it no longer has any effect.", + DeprecationWarning, + stacklevel=2, + ) + + def get_ident(self) -> int: + """Return the context identifier the local objects use internally for + this context. You cannot override this method to change the behavior + but use it to link other context local objects (such as SQLAlchemy's + scoped sessions) to the Werkzeug locals. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. + + .. versionchanged:: 0.7 + You can pass a different ident function to the local manager that + will then be propagated to all the locals passed to the + constructor. + """ + warnings.warn( + "'get_ident' is deprecated and will be removed in Werkzeug 2.1.", + DeprecationWarning, + stacklevel=2, + ) + return self.ident_func() + + def cleanup(self) -> None: + """Manually clean up the data in the locals for this context. Call + this at the end of the request or use `make_middleware()`. + """ + for local in self.locals: + release_local(local) + + def make_middleware(self, app: "WSGIApplication") -> "WSGIApplication": + """Wrap a WSGI application so that cleaning up happens after + request end. + """ + + 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 `make_middleware` but for decorating functions. + + Example usage:: + + @manager.middleware + def application(environ, start_response): + ... + + The difference to `make_middleware` is that the function passed + will have all the arguments copied from the inner application + (name, docstring, module). + """ + 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: Call this method if the proxy is unbound instead of + raising a :exc:`RuntimeError`. + :param class_value: Value to return when accessed from the class. + Used for ``__doc__`` so building docs still works. + """ + + __slots__ = ("bind_f", "fallback", "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, + ) -> 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 + + 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() + except RuntimeError: + if self.fallback is None: + raise + + return self.fallback.__get__(instance, owner) # type: ignore + + 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) + + +class LocalProxy: + """A proxy to the object bound to a :class:`Local`. All operations + on the proxy are forwarded to the bound object. If no object is + bound, a :exc:`RuntimeError` is raised. + + .. code-block:: python + + from werkzeug.local import Local + l = Local() + + # a proxy to whatever l.user is set to + user = l("user") + + from werkzeug.local import LocalStack + _request_stack = LocalStack() + + # a proxy to _request_stack.top + request = _request_stack() + + # a proxy to the session attribute of the request proxy + session = LocalProxy(lambda: request.session) + + ``__repr__`` and ``__class__`` are forwarded, 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 + + :param local: The :class:`Local` or callable that provides the + proxied object. + :param name: The attribute name to look up on a :class:`Local`. Not + used if a callable is given. + + .. 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__ = ("__local", "__name", "__wrapped__") + + def __init__( + self, + local: t.Union["Local", t.Callable[[], t.Any]], + name: t.Optional[str] = None, + ) -> None: + object.__setattr__(self, "_LocalProxy__local", local) + object.__setattr__(self, "_LocalProxy__name", name) + + if callable(local) and not hasattr(local, "__release_local__"): + # "local" is a callable that is not an instance of Local or + # LocalManager: mark it as a wrapped function. + object.__setattr__(self, "__wrapped__", local) + + def _get_current_object(self) -> t.Any: + """Return the current object. This is useful if you want the real + object behind the proxy at a time for performance reasons or because + you want to pass the object into a different context. + """ + if not hasattr(self.__local, "__release_local__"): # type: ignore + return self.__local() # type: ignore + + try: + return getattr(self.__local, self.__name) # type: ignore + except AttributeError: + name = self.__name # type: ignore + raise RuntimeError(f"no object bound to {name}") from None + + __doc__ = _ProxyLookup( # type: ignore + class_value=__doc__, fallback=lambda self: type(self).__doc__ + ) + # __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)) # 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/.venv/lib/python3.6/site-packages/werkzeug/middleware/__init__.py b/.venv/lib/python3.6/site-packages/werkzeug/middleware/__init__.py new file mode 100644 index 0000000..6ddcf7f --- /dev/null +++ b/.venv/lib/python3.6/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/.venv/lib/python3.6/site-packages/werkzeug/middleware/__pycache__/__init__.cpython-36.pyc b/.venv/lib/python3.6/site-packages/werkzeug/middleware/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1246ee0c63951ad3722723435dde65a176d13577 GIT binary patch literal 706 zcmYjPv2NTj4E1GcARudJt^pbgMSu=QdT6JjL%XCvfG$2-;#op$NswgUUH_;b(EsS1 zwR5+kL#NWZAUOh($w!gqd(!pAh4}gR2mV=A)vxNrIIiCDO6R=k>T9J}x7Fnt{^o@E zWVIe>hmpkFCllg5+}(co2;);0Yyv!j7{Xu`X6qefgBf-a181Gjh9aD#g>~SgW-xp3 zU7|P>1E37Lp1|e=9T}Xhk2Ty;y#Gd14fEEks>& zdzB+m(+FT|M%-%(*);H46ogyu5AD|OOOy=L)vd;iCDt|t%!-FEp~*S4rJ{s`b-6eU zZSBB^#1})*Xs?-vpMHuEV|_t;aC+^F2Ae5Nkj);gQpwho7f^PAD@X`uDX3u!gp8ONLMUuVWi z49-=1$r}Is;P zAM3C1>Rk-N892ffUg{1!=^gv(Hop8c$Qpx&OOycX=AdcU4Xj&(mh0SiL{qe0I->Q$ z8?=RY;B2({)4*}U?Xpahn46@?+1*5!+{9z4!Y|(XgyG>h(JV`ZNaZP4lEq4LBQ@hp zC;3q-*^`6&dyHQaYR23!F_)~FDZ*@$nxw?x!a&!5SfHSom&t!-6F_d%#clL9CL}u-cmf9>a8|iV3LIGZyi zpHHN=hKXeEN(sv}smd%Lcuu71Ah>d=A091&x_kKOp|$V90UIT7*P5Zn#UvGMD47)? z*r-rAH4Q0B*w%@Fmg`+!Cf5-&o4R?t-be_T>Ijr2o8$tf*G!v4;39*2$Y{7A(av=u zB4CzuVGp@s-Krx>ftYTP zOE-F8P9g}{yi6e5h(z;}c)k&adpRprAto_lebb^KOOD0{8P-N}0Zin8HGHAaQ>rD~ zJv{8!(N!#!CoXB~z_hYQHf;|m1a zd`ccu_Lz`mT1>%unjoFPf-E_Zbx~rq^v#fxK$dEQ8jdflss$~9Ard8HlTO)i`fs-Y zj82E~i7DcgYvkpv$q2(zdjx}wpw=TG{il7Bin&5sCnKBli^^Nnia-;~Zy1(Q#|OOT zs>p)cg$qTNpnL2QFLiBSspKd*Lv6o__`25Ts!sPSnu*F| zqR~UMhj$0BzJ_7u&YZ}ZdF5*2V1=yX(sK7VJVk&|EewzuXqk?dJHI;$x2N-Z(j6C> z?4lvZ<5Q`+M<}7muq)&VmFaWly#Km$Z_&QXY1NLRBu`8f-GUJs*Q?_+n(mJu-<@B7YKgMW{H&sL zihPGZEf_&nP+dhqD-)@Ux>0S=7r~+r3YZPn&sfadWBae*g?H`??>so`3xDQ6YpVXt zpE<`aRyS#$`RmSkqwmc8V^8=Zc;Wp3j`Jo?H-B+v!7t8l{(12Hy(b9yF=Se9A5t4u zLqm92w`Q%G{~>gW#vSlrb}{3`3{)hkxj$IxZy)YPd;8yfS2?fU?kg0OGq`fF^LVd+ z80~LAy0;Nj{*g2`jlhCNZ5f-!jka3BVYNoXRSZ>A+pBl5LdZmXaiI_b1}o@SCPId? z!Lg%hd)!UkSVTkUqx_`to|2;6#qMXh7EYMN<@>Qm%m zwRZTle=pkEy|?r2-v0f;)d)nX$5M1o^-*GBXX~>@A&GyR-l~14cJNDv6Pk<;+_u+5 Iu<-N$0oA)O5&!@I literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/werkzeug/middleware/__pycache__/http_proxy.cpython-36.pyc b/.venv/lib/python3.6/site-packages/werkzeug/middleware/__pycache__/http_proxy.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a738daa3aa9d8a7ac4579b05f17b462574d5e8c0 GIT binary patch literal 6854 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 literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/werkzeug/middleware/__pycache__/profiler.cpython-36.pyc b/.venv/lib/python3.6/site-packages/werkzeug/middleware/__pycache__/profiler.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7c4eb7c646e454f438108e4850b1796ae8b7d358 GIT binary patch literal 4855 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 N!D{QUWP^UC{{tMugwg;2 literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/werkzeug/middleware/__pycache__/proxy_fix.cpython-36.pyc b/.venv/lib/python3.6/site-packages/werkzeug/middleware/__pycache__/proxy_fix.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aa51eacc30a4148e8df52aaeb4a7e0f96f3c2535 GIT binary patch literal 6155 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 YlE!T<`JdjyjF8ZrG`>_K{o*eFAMK+YfB*mh literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/werkzeug/middleware/__pycache__/shared_data.cpython-36.pyc b/.venv/lib/python3.6/site-packages/werkzeug/middleware/__pycache__/shared_data.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7dd1cb6727562a73e0e2dfb1901a66de1b7f2ebe GIT binary patch literal 9911 zcmb_iOOV@EcEyk1CfQ&8{1Zm@SfWSGRz@CshLIiFvSfE`w;XpnN?4wdXaXd`1_Am3 zRChDzOifxTwNmAj7n7CctWv3DpIK!yl~oq0%B-=?BBe^!v(O@wb1wjrEwx-uDs185 zy@&U?@7{AixX{&dx%lhf{L=XKYm)Tu(#$W5`gL6XEm@LGsUexNsW@^&=DX5R_^vio zzH1GQ@0msxch$)aa*do!b2QZRjXc*gPGL}N6uF*t<_2?(Ij-lN(xBWZqnm_G-u+muJdf7QOINdnS^@?+5aJF%l>+`6eYn+oME8Bmr zOXc&pUa(#~Cs{e`rH*<|>L}-=#(8> z%hWdmBe2{0E!(mD;#<%96^pf+Zj1u2?HImauP3wLGXmpd+cX_(7mF2j{OSX1ux$yy zuG@aoa2#*fGKAGgsA|dMoB=w-)gL}mFcnHa(7E>g zz#23yx9yp>+r^_oF9?QBS{jWN5GpL+8;Q2n9AHStwoDr28y&0J_iPu91zN1_xq;;d z&0v2>6U)>$G=g5!b~_%;*cHZ*x1^`wtO!1}{Go^Qp=18e=KJgK42L)|SnkoP+;!8v zYYWdEVC6O)JN=bVxsJ>K4GK#F>?MLc;C{i(Sz14XD+?e|jmkXA9DyXg&{s(MpE)KT zZ7NpLnzM4IV&)&_%z|0GmuZx+UeTO;sF)?Qd{1kX(XMd2V$P#IkM;t$7tBSp7ioX= zEIm{ic3NYJ+o!O?Y4Z$5EL$tI3-&qtP&3b&&!O*>`Mmi8Q+oQGWWI>GFPZ1je}>`u zap_1T8BV4gH$F9{BpmH@X%ut$#7H1B~E-^?H$)A>gCK6M^pelb(+7 z2KufgcE;AITbt5Z^I7!>e?~g(q*G4UHikp}trKKazj^{pN_i+@951;IX; zZFNSDZjJ=>^(mokTWvBDxS7Z!vCBUg*fo8C5D-Ee#l zG%{HOZv=%lK_wqcc$kx%4eMfcIujk z!A6=s2ypa~+rtJ9wk8rSANa!Rn9ZxqtbSPsU|rpg`92v8V~c%ZTR}RQ96wDqjt}71 zV)3na^ww~vJ27y6i`YCB(7rdCvL&!U8=YZ>ZqUU{Wy;u^>Wu55(cU4ApmWZu!T%bn ztfYYIrHh08nUh~6P{z+r0woT?hy%55Odu29YQ=K^$V8BKEZf$x#an!uPxgZzM4y}_ zVb~Uq?+wX58;;&GZJ&Ku3!?;dCIIXSMFeu)v3=-!N3VBAZoA(4vyMA3f_AT#+~ZBe z2o8v(ugA;_dPbm={0N{%yKVWtO`5_?CEo8^+fWFSTia;&ES47x5dgwOw{8K!=yg31 z((aLd^ehAF2&rOKAC@gS$&HZ^XgT{&iq!N#5pC~}hHSH3&s}|I^Tzr*(@3^~j2@$k z_e?!!swi|4J@%@44>E%TC;am1d9&$+7@D7bx~|&+2JG{CyPnw5w?{Y=seRiUIcBPt z`u09?1@;@~(C=WI0KTz{L-?a^7n%Ycq%$+QyqM?WAydSJGpiHJ&m5nT>kOeQ8>qcuR#L62f_B0hjXV*%O0Mfe4m4IPf(vAZKkz~ghlXu`;o+;DE* ztV4>w4Xt6SF}_7T)X|)5kpy0r)RZg{~h*h8~bV*(QztUW>qqPZ0A@S%2_VIMz&4U2(I7IR{I zXxn6kRE4HXJX>RQ)|O)guoBdDD`qpfaHu3H%oFH;dQy|(LP#U#A+Mt+T{^6SZ^5#| z9+PU4yb~=tw$4)$s>4%3R};a(PA0ISMMmxp{GS`MBatMcW&{#GtO?-Yb}bX3^Y7HI zFo%gpw3=ykjFA(-ou=zf0YTb78Qf~kNO8?;BQRctdRWGQd?@tQ;Jrx=bjR~{d|0R* zGE{-J7l%8vWlHm9K6R@#3OcJlhP|BN6^6t#Ay%NtPHoBb9Q+KYxKev#LfAVz{zUq} zg%4M1KYD5fmQpG>oQel*Y(2V$5cm-V#E;KDiw9x>kE^*z#egW6;1=%>i3N``ymnMR zwnen~6wjk9Vvvz=+c~U9zalOTmx{~(R}`TfN=>ONp_D@nr4nXPs(}_NLFP~quZ2oK zdng?!dm>cclYZL&Tsg=cU{Ot199w9*P&KcnQVhyK6mfV6ANnuiZm5yw8n10~R6K_w zD%$Wt6nnOR8Y!M1X%vu0S=*=6NAu}R zYC3L(IuM~-?Z6ZJX>VZ*9_XeJo`|x*x8<75V4@^_$rjIpv}!TZe9P&~kj>JV(9WYG zb;sI|GItGUWJTGSK$g`p;pueLe99aPE&o*%QYkC*pHh_bN<}WpCB8~ZNvS9+N?tB0 z3(EM#Z)J5=iSl%QvQ^>*EZU}*cKXqspmqb7Pikk%fR{4<<>qLclMjepC<60fR|UcZ zSTV#9FvC5MO;)P!vE$_U3YKhQXxf$t3|N2mS#Y8sAU{E`dI&q>_eKgXX2QNCe1xlj z{6-{`$x^c$g9IXISJ(kE1hLs7W4o2I^$JcRenbW7L|o`pTSM_BE}t2Y8J|CqZ&NWJ zV-)ElnacPMvMUMy0?Hq=`Q#)L2r02D-TRHm?%oGP?oFcg>2C#v)y4;b?ULG(rHC}>Bj z?FLZ}4}5!U#f*_>c%swf@1gi>Ts|fLBsnjimdCXdS<)mbEb+9`p3L%UvXzcyqg*oi z7ih$cm&Y%B+b$<_y@Kh9X+>r-<@<>Slgvm!)>0LsVkx_Qr`Pc*4MZXMWA@$eaBD8+}ciF;eC6E8k_isv6}I zqaBr!3g^sXttW8MkMKznXE6T@D#WWm>8pqG133UfLtycN8f1@V=?`=C&Rx_vQP_eXXOKs;S*89jLB}`DL1apaqrwJbb8z^(C$^a=Ao3 zN6Mk{ATzV-vv+`py|3QS+|PD0W+vVxB;CnwM9U)^mNc0) zHAE#OU!E5wQ`;o1RYJJigS7X8nL#fQg2Bn zo6;Xi0W>|xf`3jZgI;6`kvax&35Vz*1EQwwYVNoRAzRFZEunHGVA_VLe}~H_DkU{9Yf2HWn@UBl;4jOy1x=C1 zZ~h-8G6@}z;rbr-o8TJ20gMnsAHy}FeS+&FS!~2!im>fpC)9`XQKpX=HnyOQ{M<3* z=Yjm;EbEBe_+u6H=rjFNug3; zsE7}UAQJlwE(zx1Z4|7I92ARnG>@Sq6O|EJa4f{%NcbR8KFOeqNTZafAg2Q$e@H9p zR1kRn4c7_a(GU%4hyzm)RhQ#HLB*>vhXSl1@CiHJ5z?zsk!)kEz)`Me_(l*2@h;Ym zaLrpVU`p>TsBe>yzR7;#WVs48rj#Aq5;7 zY|RPYhrAFiy-dr~L0L-+LMLT7rlGG;Ozy&R9ZN$N+B1=!dY;ut?@~@)^q)-hI8~LY0mEwAG%m->H_cdf9v>+2A zmNMn*(m}>lLhWnGR6~W!%-7O4hz+z{%f^cVO(`^4;(Poyta?nt~=-v0`_X2RT|T$02?)U(JoAqs^@ z%pqrle&mq~Vcx_#!+*r{qN&1n77(pUpQ{JOgSi7li^1F>V!N(8Df=kFXKwu`p9y-L2q3C2O!oV77gi8L1wlw$JQosz;nK3h;}c2KVwrz>f0zWEfykp zFrvXkay61WVxsBr-W+B6Q7%ccM;ht#liH4i5^`)#vx)zY6T6u>`dq-fehF4n;j~gw zKFfxE1${-CN_v(>TuOn(ib|tl9+c(WF_S|{qO#`JN1PcHKcK!>sUQ~;zhtmv4QlRE!Nzi0=+sBA zVdSa&7f|G{X$5}I!~c$|ytt6VpStw%!t%0UdZvYI$FLt<*vVL3ev6*kot*J}?IZY^U{qZL^n>RkVdE>+NjrSWBMxx)t yM}A(J>_2}Ah}{7(=_KwUIUGy*Z6eqwrv+a#`76Qa@E_uenLkCBF9U|$qy7&uYeJg< literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/werkzeug/middleware/dispatcher.py b/.venv/lib/python3.6/site-packages/werkzeug/middleware/dispatcher.py new file mode 100644 index 0000000..ace1c75 --- /dev/null +++ b/.venv/lib/python3.6/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/.venv/lib/python3.6/site-packages/werkzeug/middleware/http_proxy.py b/.venv/lib/python3.6/site-packages/werkzeug/middleware/http_proxy.py new file mode 100644 index 0000000..1cde458 --- /dev/null +++ b/.venv/lib/python3.6/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/.venv/lib/python3.6/site-packages/werkzeug/middleware/lint.py b/.venv/lib/python3.6/site-packages/werkzeug/middleware/lint.py new file mode 100644 index 0000000..c74703b --- /dev/null +++ b/.venv/lib/python3.6/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], None], 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.isdigit(): + 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/.venv/lib/python3.6/site-packages/werkzeug/middleware/profiler.py b/.venv/lib/python3.6/site-packages/werkzeug/middleware/profiler.py new file mode 100644 index 0000000..200dae0 --- /dev/null +++ b/.venv/lib/python3.6/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/.venv/lib/python3.6/site-packages/werkzeug/middleware/proxy_fix.py b/.venv/lib/python3.6/site-packages/werkzeug/middleware/proxy_fix.py new file mode 100644 index 0000000..e90b1b3 --- /dev/null +++ b/.venv/lib/python3.6/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"] = x_host + parts = x_host.split(":", 1) + environ["SERVER_NAME"] = parts[0] + if len(parts) == 2: + environ["SERVER_PORT"] = parts[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: + parts = host.split(":", 1) + host = parts[0] if len(parts) == 2 else host + environ["HTTP_HOST"] = f"{host}:{x_port}" + environ["SERVER_PORT"] = x_port + + x_prefix = self._get_real_value( + self.x_prefix, environ_get("HTTP_X_FORWARDED_PREFIX") + ) + if x_prefix: + environ["SCRIPT_NAME"] = x_prefix + + return self.app(environ, start_response) diff --git a/.venv/lib/python3.6/site-packages/werkzeug/middleware/shared_data.py b/.venv/lib/python3.6/site-packages/werkzeug/middleware/shared_data.py new file mode 100644 index 0000000..62da672 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/werkzeug/middleware/shared_data.py @@ -0,0 +1,320 @@ +""" +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 ..filesystem import get_filesystem_encoding +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) + + if hasattr(provider, "get_resource_reader"): + # Python 3 + 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), + ), + ) + + else: + # Python 3.6 + package_filename = provider.get_filename(package) # type: ignore + is_filesystem = os.path.exists(package_filename) + root = os.path.join(os.path.dirname(package_filename), package_path) + + def loader( + path: t.Optional[str], + ) -> t.Tuple[t.Optional[str], t.Optional[_TOpener]]: + if path is None: + return None, None + + path = safe_join(root, path) + + if path is None: + return None, None + + basename = posixpath.basename(path) + + if is_filesystem: + if not os.path.isfile(path): + return None, None + + return basename, self._opener(path) + + try: + data = provider.get_data(path) # type: ignore + except OSError: + return None, None + + return basename, lambda: (BytesIO(data), load_time, len(data)) + + 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: + if not isinstance(real_filename, bytes): + real_filename = real_filename.encode( # type: ignore + get_filesystem_encoding() + ) + + timestamp = mtime.timestamp() + checksum = adler32(real_filename) & 0xFFFFFFFF # type: ignore + return f"wzsdm-{timestamp}-{file_size}-{checksum}" + + def __call__( + self, environ: "WSGIEnvironment", start_response: "StartResponse" + ) -> t.Iterable[bytes]: + path = get_path_info(environ) + file_loader = None + + for search_path, loader in self.exports: + if search_path == path: + real_filename, file_loader = loader(None) + + if file_loader is not None: + break + + if not search_path.endswith("/"): + search_path += "/" + + if path.startswith(search_path): + real_filename, file_loader = loader(path[len(search_path) :]) + + if file_loader is not None: + break + + if file_loader is None or not self.is_allowed(real_filename): # type: ignore + return self.app(environ, start_response) + + guessed_type = mimetypes.guess_type(real_filename) # type: ignore + mime_type = get_content_type(guessed_type[0] or self.fallback_mimetype, "utf-8") + f, mtime, file_size = file_loader() + + headers = [("Date", http_date())] + + if self.cache: + timeout = self.cache_timeout + etag = self.generate_etag(mtime, file_size, real_filename) # type: ignore + headers += [ + ("Etag", f'"{etag}"'), + ("Cache-Control", f"max-age={timeout}, public"), + ] + + if not is_resource_modified(environ, etag, last_modified=mtime): + f.close() + start_response("304 Not Modified", headers) + return [] + + headers.append(("Expires", http_date(time() + timeout))) + else: + headers.append(("Cache-Control", "public")) + + headers.extend( + ( + ("Content-Type", mime_type), + ("Content-Length", str(file_size)), + ("Last-Modified", http_date(mtime)), + ) + ) + start_response("200 OK", headers) + return wrap_file(environ, f) diff --git a/.venv/lib/python3.6/site-packages/werkzeug/py.typed b/.venv/lib/python3.6/site-packages/werkzeug/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/.venv/lib/python3.6/site-packages/werkzeug/routing.py b/.venv/lib/python3.6/site-packages/werkzeug/routing.py new file mode 100644 index 0000000..ddb8ef9 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/werkzeug/routing.py @@ -0,0 +1,2341 @@ +"""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. +""" +import ast +import difflib +import posixpath +import re +import typing +import typing as t +import uuid +import warnings +from pprint import pformat +from string import Template +from threading import Lock +from types import CodeType + +from ._internal import _encode_idna +from ._internal import _get_environ +from ._internal import _to_bytes +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 BadRequest +from .exceptions import HTTPException +from .exceptions import MethodNotAllowed +from .exceptions import NotFound +from .urls import _fast_url_quote +from .urls import url_encode +from .urls import url_join +from .urls import url_quote +from .urls import url_unquote +from .utils import cached_property +from .utils import redirect +from .wsgi import get_host + +if t.TYPE_CHECKING: + import typing_extensions as te + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + from .wrappers.response import Response + +_rule_re = re.compile( + r""" + (?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 _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 + + +def parse_rule(rule: str) -> t.Iterator[t.Tuple[t.Optional[str], t.Optional[str], str]]: + """Parse a rule and return it as generator. Each iteration yields tuples + in the form ``(converter, arguments, variable)``. If the converter is + `None` it's a static url part, otherwise it's a dynamic one. + + :internal: + """ + pos = 0 + end = len(rule) + do_match = _rule_re.match + used_names = set() + while pos < end: + m = do_match(rule, pos) + if m is None: + break + data = m.groupdict() + if data["static"]: + yield None, None, data["static"] + variable = data["variable"] + converter = data["converter"] or "default" + if variable in used_names: + raise ValueError(f"variable name {variable!r} used twice.") + used_names.add(variable) + yield converter, data["args"] or None, variable + pos = m.end() + if pos < end: + remaining = rule[pos:] + if ">" in remaining or "<" in remaining: + raise ValueError(f"malformed url rule: {rule!r}") + yield None, None, remaining + + +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["WSGIEnvironment"] = 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]) -> None: + super().__init__() + self.matched_values = matched_values + + +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 ValidationError(ValueError): + """Validation error. If a rule converter raises this exception the rule + does not match the current URL and the next URL is tried. + """ + + +class 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: + q = '?' + params = self._encode_query_vars(kwargs) +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`` he 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. + + .. 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.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._trace: t.List[t.Tuple[bool, str]] = [] + + 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 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._trace = [] + self._converters: t.Dict[str, "BaseConverter"] = {} + self._static_weights: t.List[t.Tuple[int, int]] = [] + self._argument_weights: t.List[int] = [] + regex_parts = [] + + def _build_regex(rule: str) -> None: + index = 0 + for converter, arguments, variable in parse_rule(rule): + if converter is None: + for match in re.finditer(r"/+|[^/]+", variable): + part = match.group(0) + if part.startswith("/"): + if self.merge_slashes: + regex_parts.append(r"/+?") + self._trace.append((False, "/")) + else: + regex_parts.append(part) + self._trace.append((False, part)) + continue + self._trace.append((False, part)) + regex_parts.append(re.escape(part)) + if part: + self._static_weights.append((index, -len(part))) + else: + if arguments: + c_args, c_kwargs = parse_converter_args(arguments) + else: + c_args = () + c_kwargs = {} + convobj = self.get_converter(variable, converter, c_args, c_kwargs) + regex_parts.append(f"(?P<{variable}>{convobj.regex})") + self._converters[variable] = convobj + self._trace.append((True, variable)) + self._argument_weights.append(convobj.weight) + self.arguments.add(str(variable)) + index = index + 1 + + _build_regex(domain_rule) + regex_parts.append("\\|") + self._trace.append((False, "|")) + _build_regex(self.rule if self.is_leaf else self.rule.rstrip("/")) + if not self.is_leaf: + self._trace.append((False, "/")) + + self._build: t.Callable[..., t.Tuple[str, str]] + self._build = self._compile_builder(False).__get__(self, None) # type: ignore + self._build_unknown: t.Callable[..., t.Tuple[str, str]] + self._build_unknown = self._compile_builder(True).__get__( # type: ignore + self, None + ) + + if self.build_only: + return + + if not (self.is_leaf and self.strict_slashes): + reps = "*" if self.merge_slashes else "?" + tail = f"(?/{reps})" + else: + tail = "" + + regex = f"^{''.join(regex_parts)}{tail}$" + self._regex = re.compile(regex) + + def match( + self, path: str, method: t.Optional[str] = None + ) -> t.Optional[t.MutableMapping[str, t.Any]]: + """Check if the rule matches a given path. Path is a string in the + form ``"subdomain|/path"`` and is assembled by the map. If + the map is doing host matching the subdomain part will be the host + instead. + + If the rule matches a dict with the converted values is returned, + otherwise the return value is `None`. + + :internal: + """ + if not self.build_only: + require_redirect = False + + m = self._regex.search(path) + if m is not None: + groups = m.groupdict() + # we have a folder like part of the url without a trailing + # slash and strict slashes enabled. raise an exception that + # tells the map to redirect to the same url but with a + # trailing slash + if ( + self.strict_slashes + and not self.is_leaf + and not groups.pop("__suffix__") + and ( + method is None or self.methods is None or method in self.methods + ) + ): + path += "/" + require_redirect = True + # if we are not in strict slashes mode we have to remove + # a __suffix__ + elif not self.strict_slashes: + del groups["__suffix__"] + + result = {} + for name, value in groups.items(): + try: + value = self._converters[name].to_python(value) + except ValidationError: + return None + result[str(name)] = value + if self.defaults: + result.update(self.defaults) + + if self.merge_slashes: + new_path = "|".join(self.build(result, False)) # type: ignore + if path.endswith("/") and not new_path.endswith("/"): + new_path += "/" + if new_path.count("/") < path.count("/"): + # The URL will be encoded when MapAdapter.match + # handles the RequestPath raised below. Decode + # the URL here to avoid a double encoding. + path = url_unquote(new_path) + require_redirect = True + + if require_redirect: + path = path.split("|", 1)[1] + raise RequestPath(path) + + if self.alias and self.map.redirect_defaults: + raise RequestAliasRedirect(result) + + return result + + return 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 "col_offset" in node._attributes: + node.col_offset = 0 + + 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 match_compare_key( + self, + ) -> t.Tuple[bool, int, t.Iterable[t.Tuple[int, int]], int, t.Iterable[int]]: + """The match compare key for sorting. + + Current implementation: + + 1. rules without any arguments come first for performance + reasons only as we expect them to match faster and some + common ones usually don't have any arguments (index pages etc.) + 2. rules with more static parts come first so the second argument + is the negative length of the number of the static weights. + 3. we order by static weights, which is a combination of index + and length + 4. The more complex rules come first so the next argument is the + negative length of the number of argument weights. + 5. lastly we order by the actual argument weights. + + :internal: + """ + return ( + bool(self.arguments), + -len(self._static_weights), + self._static_weights, + -len(self._argument_weights), + self._argument_weights, + ) + + 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}>" + + +class BaseConverter: + """Base class for all converters.""" + + regex = "[^/]+" + weight = 100 + + def __init__(self, map: "Map", *args: t.Any, **kwargs: t.Any) -> None: + self.map = map + + def to_python(self, value: str) -> t.Any: + return value + + def to_url(self, value: t.Any) -> str: + 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. + """ + + 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. + """ + + def __init__(self, map: "Map", *items: str) -> None: + super().__init__(map) + self.regex = f"(?:{'|'.join([re.escape(x) for x in items])})" + + +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 + + +class NumberConverter(BaseConverter): + """Baseclass for `IntegerConverter` and `FloatConverter`. + + :internal: + """ + + weight = 50 + num_convert: t.Callable = int + + 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+" + + +class FloatConverter(NumberConverter): + """This converter only accepts floating point values:: + + Rule("/probability/") + + By default it only accepts unsigned, positive values. The ``signed`` + parameter will enable signed, negative values. :: + + Rule("/offset/") + + :param map: The :class:`Map`. + :param min: The minimal value. + :param max: The maximal value. + :param signed: Allow signed (negative) values. + + .. versionadded:: 0.15 + The ``signed`` parameter. + """ + + regex = r"\d+\.\d+" + num_convert = float + + def __init__( + self, + map: "Map", + min: 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}" + ) + + 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, +} + + +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._rules: t.List[Rule] = [] + 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 + + 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) + self._rules.append(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: "WSGIEnvironment", + 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). + """ + environ = _get_environ(environ) + wsgi_server_name = get_host(environ).lower() + scheme = environ["wsgi.url_scheme"] + upgrade = any( + v.strip() == "upgrade" + for v in environ.get("HTTP_CONNECTION", "").lower().split(",") + ) + + if upgrade and environ.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 = environ.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, + environ["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._rules.sort(key=lambda x: x.match_compare_key()) + 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 + + @typing.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]]: + ... + + @typing.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 + + require_redirect = False + + domain_part = self.server_name if self.map.host_matching else self.subdomain + path_part = f"/{path_info.lstrip('/')}" if path_info else "" + path = f"{domain_part}|{path_part}" + + have_match_for = set() + websocket_mismatch = False + + for rule in self.map._rules: + try: + rv = rule.match(path, method) + 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( + path, rule.endpoint, e.matched_values, method, query_args + ) + ) from None + if rv is None: + continue + if rule.methods is not None and method not in rule.methods: + have_match_for.update(rule.methods) + continue + + if rule.websocket != websocket: + websocket_mismatch = True + continue + + 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)] # type: ignore + 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 require_redirect: + raise RequestRedirect( + self.make_redirect_url( + url_quote(path_info, self.map.charset, safe="/:|+"), query_args + ) + ) + + if return_rule: + return rule, rv + else: + return rule.endpoint, rv + + if have_match_for: + raise MethodNotAllowed(valid_methods=list(have_match_for)) + + if websocket_mismatch: + raise WebsocketMismatch() + + raise NotFound() + + 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: + temp_values: t.Dict[str, t.Union[t.List[t.Any], t.Any]] = {} + always_list = isinstance(values, MultiDict) + key: str + value: t.Optional[t.Union[t.List[t.Any], t.Any]] + + # For MultiDict, dict.items(values) is like values.lists() + # without the call or list coercion overhead. + for key, value in dict.items(values): # type: ignore + if value is None: + continue + + if always_list or isinstance(value, (list, tuple)): + value = [v for v in value if v is not None] + + if not value: + continue + + if len(value) == 1: + value = value[0] + + temp_values[key] = value + + values = temp_values + else: + values = {} + + rv = self._partial_build(endpoint, values, method, append_unknown) + if rv is None: + raise BuildError(endpoint, values, method, self) + + domain_part, path, websocket = rv + host = self.get_host(domain_part) + + if url_scheme is None: + url_scheme = self.url_scheme + + # Always build WebSocket routes with the scheme (browsers + # require full URLs). If bound to a WebSocket, ensure that HTTP + # routes are built with an HTTP scheme. + secure = url_scheme in {"https", "wss"} + + if websocket: + force_external = True + url_scheme = "wss" if secure else "ws" + elif url_scheme: + url_scheme = "https" if secure else "http" + + # shortcut this. + if not force_external and ( + (self.map.host_matching and host == self.server_name) + or (not self.map.host_matching and domain_part == self.subdomain) + ): + return f"{self.script_name.rstrip('/')}/{path.lstrip('/')}" + + scheme = f"{url_scheme}:" if url_scheme else "" + return f"{scheme}//{host}{self.script_name[:-1]}/{path.lstrip('/')}" diff --git a/.venv/lib/python3.6/site-packages/werkzeug/sansio/__init__.py b/.venv/lib/python3.6/site-packages/werkzeug/sansio/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/.venv/lib/python3.6/site-packages/werkzeug/sansio/__pycache__/__init__.cpython-36.pyc b/.venv/lib/python3.6/site-packages/werkzeug/sansio/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..74272f122482fef3d43ef39d8988e025c9a5334a GIT binary patch literal 194 zcmXr!<>k8YZEGR}5IhDEFu(|8H~?`m3y?@*2xib^^jpbL1QJFNzw-4n@^e%5lZx__ zGs;to^wTp-GD?&5Q&P*)GILUOixYDTfK)+cNk)F2Zc<`#X0pCsS!!OHeokhRK1A49 z&rH8KvjnIrF*!RiJ+)XLXi#=lYH7NDabjL^X1;!Wd}dx|NqoFsLFFwDo80`A(wtN~ KkVA@rm;nHRyEmQy literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/werkzeug/sansio/__pycache__/multipart.cpython-36.pyc b/.venv/lib/python3.6/site-packages/werkzeug/sansio/__pycache__/multipart.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0d698dfb942eee85dc9cfff6e002200456ad7cff GIT binary patch literal 6469 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 literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/werkzeug/sansio/__pycache__/request.cpython-36.pyc b/.venv/lib/python3.6/site-packages/werkzeug/sansio/__pycache__/request.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b1ee155d01cc46a11c7f0a5bf12c003a20668717 GIT binary patch literal 17062 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 D)Wms6 literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/werkzeug/sansio/__pycache__/response.cpython-36.pyc b/.venv/lib/python3.6/site-packages/werkzeug/sansio/__pycache__/response.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b6b8205187501447e84171c222f0c93d86b85e3b GIT binary patch literal 22886 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 literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/werkzeug/sansio/__pycache__/utils.cpython-36.pyc b/.venv/lib/python3.6/site-packages/werkzeug/sansio/__pycache__/utils.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b6c5b0cf6bbc501b9e4f69fbb68d54b5ab88a1f GIT binary patch literal 3884 zcmcIn&2JmW72nxiQcIfpuxuc9+PYIGtxZQ+2C-43MCv4PT0}sh8kUU|0$Os#8Hy_| zcj=j-YznI!D)-n+(VHE>D0=B1(0l)cp7+|5&MkT{TDZSAOG=beprUe~*9F^ln(HSxg|l_yqHVIG z(UEB{6S5PFG(vxQTlOX@Hq)Odl_@al=ygoiiB*hVZK9IS<4I;@(<_2OrcD}+WYI8c zqKy=tB-W;=DrqJv?a>nSEaAP3_a0tr#N5#VZN@k`Gwj$^S8}$;jzAk%xG!K@;Xm__ z+-W0sbLW^Hd0#r|W#2L0u&%yf4*h8UFPx|2oPF;6a&YA5{=P3N$4rfLUsT1?Z(zD3 zUj#>$)EDfiVs-h@Rb9~a`$b)OHTQScIVl%*y$PAASdP!1F;Snf3 z*%jF+iqrWX!;l|HrQ4J%&T!499?fOfl$hy<+VXny08~x(FFPPGX}-Pm|e2{^Y1-^j;;cK{k@DU6u6) zPo!$?;V_fkmXHViIFaw`Xf(!WJT-$XeSbI7ajz8~$n>C<#JjDMeLMVNOUDLMQEwRS zN!@~?;h~)DwRDu?q^*gG6CIAHMZJ?|Y6SBh$}3o1SJ5~uU~BH-JLKn1tUEKWgdZna zFG}>?@N7=OLUjQ%wjTbSP8K$+MLpKAt%=f}EIe&gQQ4gusf)&=G^Wh6iB{B0SCOZR zK&CtK%yH<5sgROw7vpT4%>$`JK0GV|bxaulqG%H0_BVdvI7p&TWjD%ERN`>et zmgk{MgSreDpO(T?!AtZ#y!wx5ke_~zKpEE5dJZ}bbKn4apW;iQHbUv`q#tqVzPR9G0vYovcF~@CoOM~p0<@>`yta(3H zkhIyYt#G%yV6tx4W^Qb5Xr3ZN$4Y9MnhlanZQ6CarBS4W&W&kl5_DIG0L`*TCHFHW1ETGvy83=5&aS694BRk;)#n8>#7R zjnWI?JGV~=6b>u2XMVUwu}=9gSACbt3H4oSIGU4xzkdC?tcoija2sqL2)M%5Sp&Q} zE(LWH63uG4zzxtvO=X$skar8;whaWKns%@jlA{Dd0zi7`av|CR;Y$zzDuz?s);X~W^sgP+;(jrc6z%0eXN*$g=d6$ysY8)dv8M-O5Y z78`_41PG`=G^HxAi9}IDDHKIitfakU0!a|_rA~2Ga#|QM0x;Wru(@F;L|}r{8L1vc zXP7vuO9;q}f3|&ZbMya$ zLM8lA2%f{RCy~a11WcZVd|RIGdLFOm;p{X9+x$Mt0pQ9hW=CTbTD^!sovb&?CMgPc zK1p$V)3q|YF330-#$_}{H39*gy4$6Iegip)HN##|rLU>@1r(BlqCSj`nWsB?WBOup z%+%oOqRo9n38}pP$@LOrfFrFI%E*?YBz?s`Ic&6l@ckdZ_wM&@-MHD@z+V+#M}3L< ztBMoj2#OM|@b|MgRg`yBav~+iMK!VC#Uxfu2&h}syhoxQBH~S?SS8EjuM%CUh>~Og zK}|U0T&`PiSaB~iqVRQnfW^A2?qIa(Zf!NaEi{*ks+o=f3I#JotrNo!3Ky-dfs6Xt zO=v-^@F^R9m1@^e@crV&ODY mWGh=A1|N?yF-hcIDqS^|zXW;q=Uw^GVIQ)Ukp6pJWB&mvP(aQA literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/werkzeug/sansio/multipart.py b/.venv/lib/python3.6/site-packages/werkzeug/sansio/multipart.py new file mode 100644 index 0000000..bb8ab34 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/werkzeug/sansio/multipart.py @@ -0,0 +1,260 @@ +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) + + +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( + br"%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( + br"%s--%s(--[^\S\n\r]*%s?|[^\S\n\r]*%s)" + % (LINE_BREAK, re.escape(boundary), LINE_BREAK, LINE_BREAK), + re.MULTILINE, + ) + + 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) + 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) + + elif self.state == State.PART: + match = BLANK_LINE_RE.search(self.buffer) + 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 + + 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/.venv/lib/python3.6/site-packages/werkzeug/sansio/request.py b/.venv/lib/python3.6/site-packages/werkzeug/sansio/request.py new file mode 100644 index 0000000..2c21a21 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/werkzeug/sansio/request.py @@ -0,0 +1,548 @@ +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_cookie +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 ..useragents import _UserAgent as _DeprecatedUserAgent +from ..utils import cached_property +from ..utils import header_property +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 = _DeprecatedUserAgent + """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/.venv/lib/python3.6/site-packages/werkzeug/sansio/response.py b/.venv/lib/python3.6/site-packages/werkzeug/sansio/response.py new file mode 100644 index 0000000..82817e8 --- /dev/null +++ b/.venv/lib/python3.6/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 = "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") + + if len(split_status) > 1: + if split_status[0].isdigit(): + # code and message + return status, int(split_status[0]) + + # multi-word message + return f"0 {status}", 0 + + if split_status[0].isdigit(): + # code only + status_code = int(split_status[0]) + + try: + status = f"{status_code} {HTTP_STATUS_CODES[status_code].upper()}" + except KeyError: + status = f"{status_code} UNKNOWN" + + return status, status_code + + # one-word message + return f"0 {status}", 0 + + 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 + elif value.isdigit(): + return datetime.now(timezone.utc) + timedelta(seconds=int(value)) + return parse_date(value) + + @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/.venv/lib/python3.6/site-packages/werkzeug/sansio/utils.py b/.venv/lib/python3.6/site-packages/werkzeug/sansio/utils.py new file mode 100644 index 0000000..1b4d892 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/werkzeug/sansio/utils.py @@ -0,0 +1,142 @@ +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)) diff --git a/.venv/lib/python3.6/site-packages/werkzeug/security.py b/.venv/lib/python3.6/site-packages/werkzeug/security.py new file mode 100644 index 0000000..e23040a --- /dev/null +++ b/.venv/lib/python3.6/site-packages/werkzeug/security.py @@ -0,0 +1,247 @@ +import hashlib +import hmac +import os +import posixpath +import secrets +import typing as t +import warnings + +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 pbkdf2_hex( + data: t.Union[str, bytes], + salt: t.Union[str, bytes], + iterations: int = DEFAULT_PBKDF2_ITERATIONS, + keylen: t.Optional[int] = None, + hashfunc: t.Optional[t.Union[str, t.Callable]] = None, +) -> str: + """Like :func:`pbkdf2_bin`, but returns a hex-encoded string. + + :param data: the data to derive. + :param salt: the salt for the derivation. + :param iterations: the number of iterations. + :param keylen: the length of the resulting key. If not provided, + the digest size will be used. + :param hashfunc: the hash function to use. This can either be the + string name of a known hash function, or a function + from the hashlib module. Defaults to sha256. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. Use :func:`hashlib.pbkdf2_hmac` + instead. + + .. versionadded:: 0.9 + """ + warnings.warn( + "'pbkdf2_hex' is deprecated and will be removed in Werkzeug" + " 2.1. Use 'hashlib.pbkdf2_hmac().hex()' instead.", + DeprecationWarning, + stacklevel=2, + ) + return pbkdf2_bin(data, salt, iterations, keylen, hashfunc).hex() + + +def pbkdf2_bin( + data: t.Union[str, bytes], + salt: t.Union[str, bytes], + iterations: int = DEFAULT_PBKDF2_ITERATIONS, + keylen: t.Optional[int] = None, + hashfunc: t.Optional[t.Union[str, t.Callable]] = None, +) -> bytes: + """Returns a binary digest for the PBKDF2 hash algorithm of `data` + with the given `salt`. It iterates `iterations` times and produces a + key of `keylen` bytes. By default, SHA-256 is used as hash function; + a different hashlib `hashfunc` can be provided. + + :param data: the data to derive. + :param salt: the salt for the derivation. + :param iterations: the number of iterations. + :param keylen: the length of the resulting key. If not provided + the digest size will be used. + :param hashfunc: the hash function to use. This can either be the + string name of a known hash function or a function + from the hashlib module. Defaults to sha256. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. Use :func:`hashlib.pbkdf2_hmac` + instead. + + .. versionadded:: 0.9 + """ + warnings.warn( + "'pbkdf2_bin' is deprecated and will be removed in Werkzeug" + " 2.1. Use 'hashlib.pbkdf2_hmac()' instead.", + DeprecationWarning, + stacklevel=2, + ) + + if isinstance(data, str): + data = data.encode("utf8") + + if isinstance(salt, str): + salt = salt.encode("utf8") + + if not hashfunc: + hash_name = "sha256" + elif callable(hashfunc): + hash_name = hashfunc().name + else: + hash_name = hashfunc + + return hashlib.pbkdf2_hmac(hash_name, data, salt, iterations, keylen) + + +def safe_str_cmp(a: str, b: str) -> bool: + """This function compares strings in somewhat constant time. This + requires that the length of at least one string is known in advance. + + Returns `True` if the two strings are equal, or `False` if they are not. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. Use + :func:`hmac.compare_digest` instead. + + .. versionadded:: 0.7 + """ + warnings.warn( + "'safe_str_cmp' is deprecated and will be removed in Werkzeug" + " 2.1. Use 'hmac.compare_digest' instead.", + DeprecationWarning, + stacklevel=2, + ) + + if isinstance(a, str): + a = a.encode("utf-8") # type: ignore + + if isinstance(b, str): + b = b.encode("utf-8") # type: ignore + + return hmac.compare_digest(a, b) + + +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``. + """ + parts = [directory] + + for filename in pathnames: + if filename != "": + filename = posixpath.normpath(filename) + + if ( + any(sep in filename for sep in _os_alt_seps) + or os.path.isabs(filename) + or filename == ".." + or filename.startswith("../") + ): + return None + + parts.append(filename) + + return posixpath.join(*parts) diff --git a/.venv/lib/python3.6/site-packages/werkzeug/serving.py b/.venv/lib/python3.6/site-packages/werkzeug/serving.py new file mode 100644 index 0000000..197b6fb --- /dev/null +++ b/.venv/lib/python3.6/site-packages/werkzeug/serving.py @@ -0,0 +1,1081 @@ +"""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 io +import os +import platform +import signal +import socket +import socketserver +import sys +import typing as t +import warnings +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("SSL support unavailable") # noqa: B904 + + 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 +can_open_by_fd = not platform.system() == "Windows" and hasattr(socket, "fromfd") + +_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) + + def shutdown_server() -> None: + warnings.warn( + "The 'environ['werkzeug.server.shutdown']' function is" + " deprecated and will be removed in Werkzeug 2.1.", + stacklevel=2, + ) + self.server.shutdown_signal = True + + 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.server.shutdown": shutdown_server, + "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(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 + + def write(data: bytes) -> None: + nonlocal status_sent, headers_sent + 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) + key = key.lower() + header_keys.add(key) + if not ( + "content-length" in header_keys + or environ["REQUEST_METHOD"] == "HEAD" + or code < 200 + or code in (204, 304) + ): + self.close_connection = True + self.send_header("Connection", "close") + if "server" not in header_keys: + self.send_header("Server", self.version_string()) + if "date" not in header_keys: + self.send_header("Date", self.date_time_string()) + self.end_headers() + + assert isinstance(data, bytes), "applications must write bytes" + self.wfile.write(data) + 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"") + 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: + if self.server.passthrough_errors: + raise + from .debug.tbtools import get_current_traceback + + traceback = get_current_traceback(ignore_system_exceptions=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 + self.server.log("error", "Error on request:\n%s", traceback.plaintext) + + def handle(self) -> None: + """Handles a request ignoring dropped connections.""" + try: + BaseHTTPRequestHandler.handle(self) + 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 + if self.server.shutdown_signal: + self.initiate_shutdown() + + def initiate_shutdown(self) -> None: + if is_running_from_reloader(): + # Windows does not provide SIGKILL, go with SIGTERM then. + sig = getattr(signal, "SIGKILL", signal.SIGTERM) + os.kill(os.getpid(), sig) + + self.server._BaseServer__shutdown_request = True # type: ignore + + 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 handle_one_request(self) -> None: + """Handle a single HTTP request.""" + self.raw_requestline = self.rfile.readline() + if not self.raw_requestline: + self.close_connection = True + elif self.parse_request(): + self.run_wsgi() + + def send_response(self, code: int, message: t.Optional[str] = None) -> None: + """Send the response header and log the response code.""" + self.log_request(code) + if message is None: + message = self.responses[code][0] if code in self.responses else "" + if self.request_version != "HTTP/0.9": + hdr = f"{self.protocol_version} {code} {message}\r\n" + self.wfile.write(hdr.encode("ascii")) + + def version_string(self) -> str: + return super().version_string().strip() + + 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 _log_add_style: + 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: + 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): + + """Simple single-threaded, single-process WSGI server.""" + + 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 + + self.address_family = select_address_family(host, port) + + if fd is not None: + real_sock = socket.fromfd(fd, self.address_family, socket.SOCK_STREAM) + port = 0 + + server_address = get_sockaddr(host, int(port), self.address_family) + + # remove socket file if it already exists + if self.address_family == af_unix: + server_address = t.cast(str, server_address) + + if os.path.exists(server_address): + os.unlink(server_address) + + super().__init__(server_address, handler) # type: ignore + + self.app = app + self.passthrough_errors = passthrough_errors + self.shutdown_signal = False + self.host = host + self.port = self.socket.getsockname()[1] + + # Patch in the original socket. + if fd is not None: + self.socket.close() + self.socket = real_sock + self.server_address = self.socket.getsockname() + + if ssl_context is not None: + if isinstance(ssl_context, tuple): + ssl_context = load_ssl_context(*ssl_context) + if 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: + self.shutdown_signal = False + 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.Tuple[str, int]) -> None: + if self.passthrough_errors: + raise + + return super().handle_error(request, client_address) + + +class ThreadedWSGIServer(socketserver.ThreadingMixIn, BaseWSGIServer): + + """A WSGI server that does threading.""" + + multithread = True + daemon_threads = True + + +class ForkingWSGIServer(ForkingMixIn, BaseWSGIServer): + + """A WSGI server that does forking.""" + + 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.") + BaseWSGIServer.__init__( + self, 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 a new server instance that is either threaded, or forks + or just processes one request after another. + """ + if threaded and processes > 1: + raise ValueError("cannot have a multithreaded and multi process server.") + elif threaded: + return ThreadedWSGIServer( + host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd + ) + elif processes > 1: + return ForkingWSGIServer( + host, + port, + app, + processes, + request_handler, + passthrough_errors, + ssl_context, + fd=fd, + ) + else: + return BaseWSGIServer( + host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd + ) + + +def is_running_from_reloader() -> bool: + """Checks if the application is running from within the Werkzeug + reloader subprocess. + + .. versionadded:: 0.10 + """ + return os.environ.get("WERKZEUG_RUN_MAIN") == "true" + + +def run_simple( + hostname: str, + port: int, + application: "WSGIApplication", + use_reloader: bool = False, + use_debugger: bool = False, + use_evalex: bool = True, + extra_files: t.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 WSGI application. Optional features include a reloader, + multithreading and fork support. + + This function has a command-line interface too:: + + python -m werkzeug.serving --help + + .. versionchanged:: 2.0 + Added ``exclude_patterns`` parameter. + + .. versionadded:: 0.5 + `static_files` was added to simplify serving of static files as well + as `passthrough_errors`. + + .. versionadded:: 0.6 + support for SSL was added. + + .. versionadded:: 0.8 + Added support for automatically loading a SSL context from certificate + file and private key. + + .. versionadded:: 0.9 + Added command-line interface. + + .. versionadded:: 0.10 + Improved the reloader and added support for changing the backend + through the `reloader_type` parameter. See :ref:`reloader` + for more information. + + .. versionchanged:: 0.15 + Bind to a Unix socket by passing a path that starts with + ``unix://`` as the ``hostname``. + + :param hostname: The host to bind to, for example ``'localhost'``. + If the value is a path that starts with ``unix://`` it will bind + to a Unix socket instead of a TCP socket.. + :param port: The port for the server. eg: ``8080`` + :param application: the WSGI application to execute + :param use_reloader: should the server automatically restart the python + process if modules were changed? + :param use_debugger: should the werkzeug debugging system be used? + :param use_evalex: should the exception evaluation feature be enabled? + :param extra_files: a list of files the reloader should watch + additionally to the modules. For example configuration + files. + :param exclude_patterns: List of :mod:`fnmatch` patterns to ignore + when running the reloader. For example, ignore cache files that + shouldn't reload when updated. + :param reloader_interval: the interval for the reloader in seconds. + :param reloader_type: the type of reloader to use. The default is + auto detection. Valid values are ``'stat'`` and + ``'watchdog'``. See :ref:`reloader` for more + information. + :param threaded: should the process handle each request in a separate + thread? + :param processes: if greater than 1 then handle each request in a new process + up to this maximum number of concurrent processes. + :param request_handler: optional parameter that can be used to replace + the default one. You can use this to replace it + with a different + :class:`~BaseHTTPServer.BaseHTTPRequestHandler` + subclass. + :param static_files: a list or dict of paths for static files. This works + exactly like :class:`SharedDataMiddleware`, it's actually + just wrapping the application in that middleware before + serving. + :param passthrough_errors: set this to `True` to disable the error catching. + This means that the server will die on errors but + it can be useful to hook debuggers in (pdb etc.) + :param ssl_context: an SSL context for the connection. Either an + :class:`ssl.SSLContext`, a tuple in the form + ``(cert_file, pkey_file)``, the string ``'adhoc'`` if + the server should automatically create one, or ``None`` + to disable SSL (which is the default). + """ + if not isinstance(port, int): + raise TypeError("port must be an integer") + if use_debugger: + from .debug import DebuggedApplication + + application = DebuggedApplication(application, use_evalex) + if static_files: + from .middleware.shared_data import SharedDataMiddleware + + application = SharedDataMiddleware(application, static_files) + + def log_startup(sock: socket.socket) -> None: + all_addresses_message = ( + " * Running on all addresses.\n" + " WARNING: This is a development server. Do not use it in" + " a production deployment." + ) + + if sock.family == af_unix: + _log("info", " * Running on %s (Press CTRL+C to quit)", hostname) + else: + if hostname == "0.0.0.0": + _log("warning", all_addresses_message) + display_hostname = get_interface_ip(socket.AF_INET) + elif hostname == "::": + _log("warning", all_addresses_message) + display_hostname = get_interface_ip(socket.AF_INET6) + else: + display_hostname = hostname + + if ":" in display_hostname: + display_hostname = f"[{display_hostname}]" + + _log( + "info", + " * Running on %s://%s:%d/ (Press CTRL+C to quit)", + "http" if ssl_context is None else "https", + display_hostname, + sock.getsockname()[1], + ) + + def inner() -> None: + try: + fd: t.Optional[int] = int(os.environ["WERKZEUG_SERVER_FD"]) + except (LookupError, ValueError): + fd = None + srv = make_server( + hostname, + port, + application, + threaded, + processes, + request_handler, + passthrough_errors, + ssl_context, + fd=fd, + ) + if fd is None: + log_startup(srv.socket) + srv.serve_forever() + + if use_reloader: + # If we're not running already in the subprocess that is the + # reloader we want to open up a socket early to make sure the + # port is actually available. + if not is_running_from_reloader(): + if port == 0 and not can_open_by_fd: + raise ValueError( + "Cannot bind to a random port with enabled " + "reloader if the Python interpreter does " + "not support socket opening by fd." + ) + + # Create and destroy a socket so that any exceptions are + # raised before we spawn a separate Python interpreter and + # lose this ability. + 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.bind(server_address) + s.set_inheritable(True) + + # If we can open the socket by file descriptor, then we can just + # reuse this one and our socket will survive the restarts. + if can_open_by_fd: + os.environ["WERKZEUG_SERVER_FD"] = str(s.fileno()) + s.listen(LISTEN_QUEUE) + log_startup(s) + else: + s.close() + if address_family == af_unix: + server_address = t.cast(str, server_address) + _log("info", "Unlinking %s", server_address) + os.unlink(server_address) + + from ._reloader import run_with_reloader as _rwr + + _rwr( + inner, + extra_files=extra_files, + exclude_patterns=exclude_patterns, + interval=reloader_interval, + reloader_type=reloader_type, + ) + else: + inner() + + +def run_with_reloader(*args: t.Any, **kwargs: t.Any) -> None: + """Run a process with the reloader. This is not a public API, do + not use this function. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. + """ + from ._reloader import run_with_reloader as _rwr + + warnings.warn( + ( + "'run_with_reloader' is a private API, it will no longer be" + " accessible in Werkzeug 2.1. Use 'run_simple' instead." + ), + DeprecationWarning, + stacklevel=2, + ) + _rwr(*args, **kwargs) + + +def main() -> None: + """A simple command-line interface for :py:func:`run_simple`.""" + import argparse + from .utils import import_string + + _log("warning", "This CLI is deprecated and will be removed in version 2.1.") + + parser = argparse.ArgumentParser( + description="Run the given WSGI application with the development server.", + allow_abbrev=False, + ) + parser.add_argument( + "-b", + "--bind", + dest="address", + help="The hostname:port the app should listen on.", + ) + parser.add_argument( + "-d", + "--debug", + action="store_true", + help="Show the interactive debugger for unhandled exceptions.", + ) + parser.add_argument( + "-r", + "--reload", + action="store_true", + help="Reload the process if modules change.", + ) + parser.add_argument( + "application", help="Application to import and serve, in the form module:app." + ) + args = parser.parse_args() + hostname, port = None, None + + if args.address: + hostname, _, port = args.address.partition(":") + + run_simple( + hostname=hostname or "127.0.0.1", + port=int(port or 5000), + application=import_string(args.application), + use_reloader=args.reload, + use_debugger=args.debug, + ) + + +if __name__ == "__main__": + main() diff --git a/.venv/lib/python3.6/site-packages/werkzeug/test.py b/.venv/lib/python3.6/site-packages/werkzeug/test.py new file mode 100644 index 0000000..09cb7e8 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/werkzeug/test.py @@ -0,0 +1,1326 @@ +import mimetypes +import sys +import typing as t +import warnings +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 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 + + 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.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 perecent-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 and pieces[1].isdigit(): + return int(pieces[1]) + 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() + + if content_type is not None: + result["CONTENT_TYPE"] = content_type + headers.set("Content-Type", content_type) + + if content_length is not None: + result["CONTENT_LENGTH"] = str(content_length) + headers.set("Content-Length", 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.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, 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, + as_tuple: bool = False, + 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.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"] = [] + + while follow_redirects and 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, or not + # following 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) + + if as_tuple: + warnings.warn( + "'as_tuple' is deprecated and will be removed in" + " Werkzeug 2.1. Access 'response.request.environ'" + " instead.", + DeprecationWarning, + stacklevel=2, + ) + return request.environ, response # type: ignore + + 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``. + """ + + 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. + """ + + 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 + + def __iter__(self) -> t.Iterator: + warnings.warn( + ( + "The test client no longer returns a tuple, it returns" + " a 'TestResponse'. Tuple unpacking is deprecated and" + " will be removed in Werkzeug 2.1. Access the" + " attributes 'data', 'status', and 'headers' instead." + ), + DeprecationWarning, + stacklevel=2, + ) + return iter(self._compat_tuple) + + def __getitem__(self, item: int) -> t.Any: + warnings.warn( + ( + "The test client no longer returns a tuple, it returns" + " a 'TestResponse'. Item indexing is deprecated and" + " will be removed in Werkzeug 2.1. Access the" + " attributes 'data', 'status', and 'headers' instead." + ), + DeprecationWarning, + stacklevel=2, + ) + return self._compat_tuple[item] diff --git a/.venv/lib/python3.6/site-packages/werkzeug/testapp.py b/.venv/lib/python3.6/site-packages/werkzeug/testapp.py new file mode 100644 index 0000000..981f887 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/werkzeug/testapp.py @@ -0,0 +1,240 @@ +"""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 html import escape +from textwrap import wrap + +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"
  2. {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(escape(repr(value)))) + wsgi_env.append(f"{escape(str(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/.venv/lib/python3.6/site-packages/werkzeug/urls.py b/.venv/lib/python3.6/site-packages/werkzeug/urls.py new file mode 100644 index 0000000..9529da0 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/werkzeug/urls.py @@ -0,0 +1,1211 @@ +"""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 +import warnings + +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"-._~" + ) +) + +_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", + decode_keys: None = None, + 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 decode_keys is not None: + warnings.warn( + "'decode_keys' is deprecated and will be removed in Werkzeug 2.1.", + DeprecationWarning, + stacklevel=2, + ) + if cls is None: + from .datastructures import MultiDict # noqa: F811 + + cls = MultiDict + if isinstance(s, str) and not isinstance(separator, str): + separator = separator.decode(charset or "ascii") + elif isinstance(s, bytes) and not isinstance(separator, bytes): + separator = separator.encode(charset or "ascii") # type: ignore + return cls( + _url_decode_impl( + s.split(separator), charset, include_empty, errors # type: ignore + ) + ) + + +def url_decode_stream( + stream: t.IO[bytes], + charset: str = "utf-8", + decode_keys: None = None, + include_empty: bool = True, + errors: str = "replace", + separator: bytes = b"&", + cls: t.Optional[t.Type["ds.MultiDict"]] = None, + limit: t.Optional[int] = None, + return_iterator: bool = False, +) -> "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 + + if decode_keys is not None: + warnings.warn( + "'decode_keys' is deprecated and will be removed in Werkzeug 2.1.", + DeprecationWarning, + stacklevel=2, + ) + + pair_iter = make_chunk_iter(stream, separator, limit) + decoder = _url_decode_impl(pair_iter, charset, include_empty, errors) + + if return_iterator: + warnings.warn( + "'return_iterator' is deprecated and will be removed in Werkzeug 2.1.", + DeprecationWarning, + stacklevel=2, + ) + return decoder # type: ignore + + 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", + encode_keys: None = None, + 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. + """ + if encode_keys is not None: + warnings.warn( + "'encode_keys' is deprecated and will be removed in Werkzeug 2.1.", + DeprecationWarning, + stacklevel=2, + ) + 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", + encode_keys: None = None, + 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 + """ + if encode_keys is not None: + warnings.warn( + "'encode_keys' is deprecated and will be removed in Werkzeug 2.1.", + DeprecationWarning, + stacklevel=2, + ) + separator = _to_str(separator, "ascii") + gen = _url_encode_impl(obj, charset, sort, key) + if stream is None: + return gen # type: ignore + for idx, chunk in enumerate(gen): + if idx: + stream.write(separator) + stream.write(chunk) + return None + + +def url_join( + base: 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)) + + +class Href: + """Implements a callable that constructs URLs with the given base. The + function can be called with any number of positional and keyword + arguments which than are used to assemble the URL. Works with URLs + and posix paths. + + Positional arguments are appended as individual segments to + the path of the URL: + + >>> href = Href('/foo') + >>> href('bar', 23) + '/foo/bar/23' + >>> href('foo', bar=23) + '/foo/foo?bar=23' + + If any of the arguments (positional or keyword) evaluates to `None` it + will be skipped. If no keyword arguments are given the last argument + can be a :class:`dict` or :class:`MultiDict` (or any other dict subclass), + otherwise the keyword arguments are used for the query parameters, cutting + off the first trailing underscore of the parameter name: + + >>> href(is_=42) + '/foo?is=42' + >>> href({'foo': 'bar'}) + '/foo?foo=bar' + + Combining of both methods is not allowed: + + >>> href({'foo': 'bar'}, bar=42) + Traceback (most recent call last): + ... + TypeError: keyword arguments and query-dicts can't be combined + + Accessing attributes on the href object creates a new href object with + the attribute name as prefix: + + >>> bar_href = href.bar + >>> bar_href("blub") + '/foo/bar/blub' + + If `sort` is set to `True` the items are sorted by `key` or the default + sorting algorithm: + + >>> href = Href("/", sort=True) + >>> href(a=1, b=2, c=3) + '/?a=1&b=2&c=3' + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. Use :mod:`werkzeug.routing` + instead. + + .. versionadded:: 0.5 + `sort` and `key` were added. + """ + + def __init__( # type: ignore + self, base="./", charset="utf-8", sort=False, key=None + ): + warnings.warn( + "'Href' is deprecated and will be removed in Werkzeug 2.1." + " Use 'werkzeug.routing' instead.", + DeprecationWarning, + stacklevel=2, + ) + + if not base: + base = "./" + self.base = base + self.charset = charset + self.sort = sort + self.key = key + + def __getattr__(self, name): # type: ignore + if name[:2] == "__": + raise AttributeError(name) + base = self.base + if base[-1:] != "/": + base += "/" + return Href(url_join(base, name), self.charset, self.sort, self.key) + + def __call__(self, *path, **query): # type: ignore + if path and isinstance(path[-1], dict): + if query: + raise TypeError("keyword arguments and query-dicts can't be combined") + query, path = path[-1], path[:-1] + elif query: + query = {k[:-1] if k.endswith("_") else k: v for k, v in query.items()} + path = "/".join( + [ + _to_str(url_quote(x, self.charset), "ascii") + for x in path + if x is not None + ] + ).lstrip("/") + rv = self.base + if path: + if not rv.endswith("/"): + rv += "/" + rv = url_join(rv, f"./{path}") + if query: + rv += "?" + _to_str( + url_encode(query, self.charset, sort=self.sort, key=self.key), "ascii" + ) + return rv diff --git a/.venv/lib/python3.6/site-packages/werkzeug/user_agent.py b/.venv/lib/python3.6/site-packages/werkzeug/user_agent.py new file mode 100644 index 0000000..66ffcbe --- /dev/null +++ b/.venv/lib/python3.6/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/.venv/lib/python3.6/site-packages/werkzeug/useragents.py b/.venv/lib/python3.6/site-packages/werkzeug/useragents.py new file mode 100644 index 0000000..4deed8f --- /dev/null +++ b/.venv/lib/python3.6/site-packages/werkzeug/useragents.py @@ -0,0 +1,215 @@ +import re +import typing as t +import warnings + +from .user_agent import UserAgent as _BaseUserAgent + +if t.TYPE_CHECKING: + from _typeshed.wsgi import WSGIEnvironment + + +class _UserAgentParser: + platform_rules: t.ClassVar[t.Iterable[t.Tuple[str, str]]] = ( + (" cros ", "chromeos"), + ("iphone|ios", "iphone"), + ("ipad", "ipad"), + (r"darwin\b|mac\b|os\s*x", "macos"), + ("win", "windows"), + (r"android", "android"), + ("netbsd", "netbsd"), + ("openbsd", "openbsd"), + ("freebsd", "freebsd"), + ("dragonfly", "dragonflybsd"), + ("(sun|i86)os", "solaris"), + (r"x11\b|lin(\b|ux)?", "linux"), + (r"nintendo\s+wii", "wii"), + ("irix", "irix"), + ("hp-?ux", "hpux"), + ("aix", "aix"), + ("sco|unix_sv", "sco"), + ("bsd", "bsd"), + ("amiga", "amiga"), + ("blackberry|playbook", "blackberry"), + ("symbian", "symbian"), + ) + browser_rules: t.ClassVar[t.Iterable[t.Tuple[str, str]]] = ( + ("googlebot", "google"), + ("msnbot", "msn"), + ("yahoo", "yahoo"), + ("ask jeeves", "ask"), + (r"aol|america\s+online\s+browser", "aol"), + (r"opera|opr", "opera"), + ("edge|edg", "edge"), + ("chrome|crios", "chrome"), + ("seamonkey", "seamonkey"), + ("firefox|firebird|phoenix|iceweasel", "firefox"), + ("galeon", "galeon"), + ("safari|version", "safari"), + ("webkit", "webkit"), + ("camino", "camino"), + ("konqueror", "konqueror"), + ("k-meleon", "kmeleon"), + ("netscape", "netscape"), + (r"msie|microsoft\s+internet\s+explorer|trident/.+? rv:", "msie"), + ("lynx", "lynx"), + ("links", "links"), + ("Baiduspider", "baidu"), + ("bingbot", "bing"), + ("mozilla", "mozilla"), + ) + + _browser_version_re = r"(?:{pattern})[/\sa-z(]*(\d+[.\da-z]+)?" + _language_re = re.compile( + r"(?:;\s*|\s+)(\b\w{2}\b(?:-\b\w{2}\b)?)\s*;|" + r"(?:\(|\[|;)\s*(\b\w{2}\b(?:-\b\w{2}\b)?)\s*(?:\]|\)|;)" + ) + + def __init__(self) -> None: + self.platforms = [(b, re.compile(a, re.I)) for a, b in self.platform_rules] + self.browsers = [ + (b, re.compile(self._browser_version_re.format(pattern=a), re.I)) + for a, b in self.browser_rules + ] + + def __call__( + self, user_agent: str + ) -> t.Tuple[t.Optional[str], t.Optional[str], t.Optional[str], t.Optional[str]]: + platform: t.Optional[str] + browser: t.Optional[str] + version: t.Optional[str] + language: t.Optional[str] + + for platform, regex in self.platforms: # noqa: B007 + match = regex.search(user_agent) + if match is not None: + break + else: + platform = None + + # Except for Trident, all browser key words come after the last ')' + last_closing_paren = 0 + if ( + not re.compile(r"trident/.+? rv:", re.I).search(user_agent) + and ")" in user_agent + and user_agent[-1] != ")" + ): + last_closing_paren = user_agent.rindex(")") + + for browser, regex in self.browsers: # noqa: B007 + match = regex.search(user_agent[last_closing_paren:]) + if match is not None: + version = match.group(1) + break + else: + browser = version = None + match = self._language_re.search(user_agent) + if match is not None: + language = match.group(1) or match.group(2) + else: + language = None + return platform, browser, version, language + + +# It wasn't public, but users might have imported it anyway, show a +# warning if a user created an instance. +class UserAgentParser(_UserAgentParser): + """A simple user agent parser. Used by the `UserAgent`. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. Use a dedicated parser library + instead. + """ + + def __init__(self) -> None: + warnings.warn( + "'UserAgentParser' is deprecated and will be removed in" + " Werkzeug 2.1. Use a dedicated parser library instead.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__() + + +class _deprecated_property(property): + def __init__(self, fget: t.Callable[["_UserAgent"], t.Any]) -> None: + super().__init__(fget) + self.message = ( + "The built-in user agent parser is deprecated and will be" + f" removed in Werkzeug 2.1. The {fget.__name__!r} property" + " will be 'None'. Subclass 'werkzeug.user_agent.UserAgent'" + " and set 'Request.user_agent_class' to use a different" + " parser." + ) + + def __get__(self, *args: t.Any, **kwargs: t.Any) -> t.Any: + warnings.warn(self.message, DeprecationWarning, stacklevel=3) + return super().__get__(*args, **kwargs) + + +# This is what Request.user_agent returns for now, only show warnings on +# attribute access, not creation. +class _UserAgent(_BaseUserAgent): + _parser = _UserAgentParser() + + def __init__(self, string: str) -> None: + super().__init__(string) + info = self._parser(string) + self._platform, self._browser, self._version, self._language = info + + @_deprecated_property + def platform(self) -> t.Optional[str]: # type: ignore + return self._platform + + @_deprecated_property + def browser(self) -> t.Optional[str]: # type: ignore + return self._browser + + @_deprecated_property + def version(self) -> t.Optional[str]: # type: ignore + return self._version + + @_deprecated_property + def language(self) -> t.Optional[str]: # type: ignore + return self._language + + +# This is what users might be importing, show warnings on create. +class UserAgent(_UserAgent): + """Represents a parsed user agent header value. + + This uses a basic parser to try to extract some information from the + header. + + :param environ_or_string: The header value to parse, or a WSGI + environ containing the header. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. Subclass + :class:`werkzeug.user_agent.UserAgent` (note the new module + name) to use a dedicated parser instead. + + .. versionchanged:: 2.0 + Passing a WSGI environ is deprecated and will be removed in 2.1. + """ + + def __init__(self, environ_or_string: "t.Union[str, WSGIEnvironment]") -> None: + if isinstance(environ_or_string, dict): + warnings.warn( + "Passing an environ to 'UserAgent' is deprecated and" + " will be removed in Werkzeug 2.1. Pass the header" + " value string instead.", + DeprecationWarning, + stacklevel=2, + ) + string = environ_or_string.get("HTTP_USER_AGENT", "") + else: + string = environ_or_string + + warnings.warn( + "The 'werkzeug.useragents' module is deprecated and will be" + " removed in Werkzeug 2.1. The new base API is" + " 'werkzeug.user_agent.UserAgent'.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(string) diff --git a/.venv/lib/python3.6/site-packages/werkzeug/utils.py b/.venv/lib/python3.6/site-packages/werkzeug/utils.py new file mode 100644 index 0000000..9007231 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/werkzeug/utils.py @@ -0,0 +1,1099 @@ +import codecs +import io +import mimetypes +import os +import pkgutil +import re +import sys +import typing as t +import unicodedata +import warnings +from datetime import datetime +from html.entities import name2codepoint +from time import time +from zlib import adler32 + +from ._internal import _DictAccessorProperty +from ._internal import _missing +from ._internal import _parse_signature +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 + + The class must have a ``__dict__`` for this to work. + + .. 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.__module__ = fget.__module__ + + def __set__(self, obj: object, value: _T) -> None: + obj.__dict__[self.__name__] = value + + def __get__(self, obj: object, type: type = None) -> _T: # type: ignore + if obj is None: + return self # type: ignore + + value: _T = obj.__dict__.get(self.__name__, _missing) + + if value is _missing: + value = self.fget(obj) # type: ignore + obj.__dict__[self.__name__] = value + + return value + + def __delete__(self, obj: object) -> None: + del obj.__dict__[self.__name__] + + +def invalidate_cached_property(obj: object, name: str) -> None: + """Invalidates the cache for a :class:`cached_property`: + + >>> class Test(object): + ... @cached_property + ... def magic_number(self): + ... print("recalculating...") + ... return 42 + ... + >>> var = Test() + >>> var.magic_number + recalculating... + 42 + >>> var.magic_number + 42 + >>> invalidate_cached_property(var, "magic_number") + >>> var.magic_number + recalculating... + 42 + + You must pass the name of the cached property as the second argument. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. Use ``del obj.name`` instead. + """ + warnings.warn( + "'invalidate_cached_property' is deprecated and will be removed" + " in Werkzeug 2.1. Use 'del obj.name' instead.", + DeprecationWarning, + stacklevel=2, + ) + delattr(obj, name) + + +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 + + +class HTMLBuilder: + """Helper object for HTML generation. + + Per default there are two instances of that class. The `html` one, and + the `xhtml` one for those two dialects. The class uses keyword parameters + and positional parameters to generate small snippets of HTML. + + Keyword parameters are converted to XML/SGML attributes, positional + arguments are used as children. Because Python accepts positional + arguments before keyword arguments it's a good idea to use a list with the + star-syntax for some children: + + >>> html.p(class_='foo', *[html.a('foo', href='foo.html'), ' ', + ... html.a('bar', href='bar.html')]) + '

    foo bar

    ' + + This class works around some browser limitations and can not be used for + arbitrary SGML/XML generation. For that purpose lxml and similar + libraries exist. + + Calling the builder escapes the string passed: + + >>> html.p(html("")) + '

    <foo>

    ' + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. + """ + + _entity_re = re.compile(r"&([^;]+);") + _entities = name2codepoint.copy() + _entities["apos"] = 39 + _empty_elements = { + "area", + "base", + "basefont", + "br", + "col", + "command", + "embed", + "frame", + "hr", + "img", + "input", + "keygen", + "isindex", + "link", + "meta", + "param", + "source", + "wbr", + } + _boolean_attributes = { + "selected", + "checked", + "compact", + "declare", + "defer", + "disabled", + "ismap", + "multiple", + "nohref", + "noresize", + "noshade", + "nowrap", + } + _plaintext_elements = {"textarea"} + _c_like_cdata = {"script", "style"} + + def __init__(self, dialect): # type: ignore + self._dialect = dialect + + def __call__(self, s): # type: ignore + import html + + warnings.warn( + "'utils.HTMLBuilder' is deprecated and will be removed in Werkzeug 2.1.", + DeprecationWarning, + stacklevel=2, + ) + return html.escape(s) + + def __getattr__(self, tag): # type: ignore + import html + + warnings.warn( + "'utils.HTMLBuilder' is deprecated and will be removed in Werkzeug 2.1.", + DeprecationWarning, + stacklevel=2, + ) + if tag[:2] == "__": + raise AttributeError(tag) + + def proxy(*children, **arguments): # type: ignore + buffer = f"<{tag}" + for key, value in arguments.items(): + if value is None: + continue + if key[-1] == "_": + key = key[:-1] + if key in self._boolean_attributes: + if not value: + continue + if self._dialect == "xhtml": + value = f'="{key}"' + else: + value = "" + else: + value = f'="{html.escape(value)}"' + buffer += f" {key}{value}" + if not children and tag in self._empty_elements: + if self._dialect == "xhtml": + buffer += " />" + else: + buffer += ">" + return buffer + buffer += ">" + + children_as_string = "".join([str(x) for x in children if x is not None]) + + if children_as_string: + if tag in self._plaintext_elements: + children_as_string = html.escape(children_as_string) + elif tag in self._c_like_cdata and self._dialect == "xhtml": + children_as_string = f"/**/" + buffer += children_as_string + f"" + return buffer + + return proxy + + def __repr__(self) -> str: + return f"<{type(self).__name__} for {self._dialect!r}>" + + +html = HTMLBuilder("html") +xhtml = HTMLBuilder("xhtml") + +# 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 detect_utf_encoding(data: bytes) -> str: + """Detect which UTF encoding was used to encode the given bytes. + + The latest JSON standard (:rfc:`8259`) suggests that only UTF-8 is + accepted. Older documents allowed 8, 16, or 32. 16 and 32 can be big + or little endian. Some editors or libraries may prepend a BOM. + + :internal: + + :param data: Bytes in unknown UTF encoding. + :return: UTF encoding name + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. This is built in to + :func:`json.loads`. + + .. versionadded:: 0.15 + """ + warnings.warn( + "'detect_utf_encoding' is deprecated and will be removed in" + " Werkzeug 2.1. This is built in to 'json.loads'.", + DeprecationWarning, + stacklevel=2, + ) + head = data[:4] + + if head[:3] == codecs.BOM_UTF8: + return "utf-8-sig" + + if b"\x00" not in head: + return "utf-8" + + if head in (codecs.BOM_UTF32_BE, codecs.BOM_UTF32_LE): + return "utf-32" + + if head[:2] in (codecs.BOM_UTF16_BE, codecs.BOM_UTF16_LE): + return "utf-16" + + if len(head) == 4: + if head[:3] == b"\x00\x00\x00": + return "utf-32-be" + + if head[::2] == b"\x00\x00": + return "utf-16-be" + + if head[1:] == b"\x00\x00\x00": + return "utf-32-le" + + if head[1::2] == b"\x00\x00": + return "utf-16-le" + + if len(head) == 2: + return "utf-16-be" if head.startswith(b"\x00") else "utf-16-le" + + return "utf-8" + + +def format_string(string: str, context: t.Mapping[str, t.Any]) -> str: + """String-template format a string: + + >>> format_string('$foo and ${foo}s', dict(foo=42)) + '42 and 42s' + + This does not do any attribute lookup. + + :param string: the format string. + :param context: a dict with the variables to insert. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. Use :class:`string.Template` + instead. + """ + from string import Template + + warnings.warn( + "'utils.format_string' is deprecated and will be removed in" + " Werkzeug 2.1. Use 'string.Template' instead.", + DeprecationWarning, + stacklevel=2, + ) + return Template(string).substitute(context) + + +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 escape(s: t.Any) -> str: + """Replace ``&``, ``<``, ``>``, ``"``, and ``'`` with HTML-safe + sequences. + + ``None`` is escaped to an empty string. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. Use MarkupSafe instead. + """ + import html + + warnings.warn( + "'utils.escape' is deprecated and will be removed in Werkzeug" + " 2.1. Use MarkupSafe instead.", + DeprecationWarning, + stacklevel=2, + ) + + if s is None: + return "" + + if hasattr(s, "__html__"): + return s.__html__() # type: ignore + + if not isinstance(s, str): + s = str(s) + + return html.escape(s, quote=True) # type: ignore + + +def unescape(s: str) -> str: + """The reverse of :func:`escape`. This unescapes all the HTML + entities, not only those inserted by ``escape``. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. Use MarkupSafe instead. + """ + import html + + warnings.warn( + "'utils.unescape' is deprecated and will be removed in Werkzueg" + " 2.1. Use MarkupSafe instead.", + DeprecationWarning, + stacklevel=2, + ) + return html.unescape(s) + + +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. + """ + import html + + if Response is None: + from .wrappers import Response # type: ignore + + display_location = html.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' + "Redirecting...\n" + "

    Redirecting...

    \n" + "

    You should be redirected automatically to target URL: " + f'{display_location}. If' + " not click the link.", + code, + mimetype="text/html", + ) + response.headers["Location"] = location + return response + + +def append_slash_redirect(environ: "WSGIEnvironment", code: int = 301) -> "Response": + """Redirects to the same URL but with a slash appended. The behavior + of this function is undefined if the path ends with a slash already. + + :param environ: the WSGI environment for the request that triggers + the redirect. + :param code: the status code for the redirect. + """ + new_path = environ["PATH_INFO"].strip("/") + "/" + query_string = environ.get("QUERY_STRING") + if query_string: + new_path += f"?{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 + + +def validate_arguments(func, args, kwargs, drop_extra=True): # type: ignore + """Checks if the function accepts the arguments and keyword arguments. + Returns a new ``(args, kwargs)`` tuple that can safely be passed to + the function without causing a `TypeError` because the function signature + is incompatible. If `drop_extra` is set to `True` (which is the default) + any extra positional or keyword arguments are dropped automatically. + + The exception raised provides three attributes: + + `missing` + A set of argument names that the function expected but where + missing. + + `extra` + A dict of keyword arguments that the function can not handle but + where provided. + + `extra_positional` + A list of values that where given by positional argument but the + function cannot accept. + + This can be useful for decorators that forward user submitted data to + a view function:: + + from werkzeug.utils import ArgumentValidationError, validate_arguments + + def sanitize(f): + def proxy(request): + data = request.values.to_dict() + try: + args, kwargs = validate_arguments(f, (request,), data) + except ArgumentValidationError: + raise BadRequest('The browser failed to transmit all ' + 'the data expected.') + return f(*args, **kwargs) + return proxy + + :param func: the function the validation is performed against. + :param args: a tuple of positional arguments. + :param kwargs: a dict of keyword arguments. + :param drop_extra: set to `False` if you don't want extra arguments + to be silently dropped. + :return: tuple in the form ``(args, kwargs)``. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. Use :func:`inspect.signature` + instead. + """ + warnings.warn( + "'utils.validate_arguments' is deprecated and will be removed" + " in Werkzeug 2.1. Use 'inspect.signature' instead.", + DeprecationWarning, + stacklevel=2, + ) + parser = _parse_signature(func) + args, kwargs, missing, extra, extra_positional = parser(args, kwargs)[:5] + if missing: + raise ArgumentValidationError(tuple(missing)) + elif (extra or extra_positional) and not drop_extra: + raise ArgumentValidationError(None, extra, extra_positional) + return tuple(args), kwargs + + +def bind_arguments(func, args, kwargs): # type: ignore + """Bind the arguments provided into a dict. When passed a function, + a tuple of arguments and a dict of keyword arguments `bind_arguments` + returns a dict of names as the function would see it. This can be useful + to implement a cache decorator that uses the function arguments to build + the cache key based on the values of the arguments. + + :param func: the function the arguments should be bound for. + :param args: tuple of positional arguments. + :param kwargs: a dict of keyword arguments. + :return: a :class:`dict` of bound keyword arguments. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. Use :meth:`Signature.bind` + instead. + """ + warnings.warn( + "'utils.bind_arguments' is deprecated and will be removed in" + " Werkzeug 2.1. Use 'Signature.bind' instead.", + DeprecationWarning, + stacklevel=2, + ) + ( + args, + kwargs, + missing, + extra, + extra_positional, + arg_spec, + vararg_var, + kwarg_var, + ) = _parse_signature(func)(args, kwargs) + values = {} + for (name, _has_default, _default), value in zip(arg_spec, args): + values[name] = value + if vararg_var is not None: + values[vararg_var] = tuple(extra_positional) + elif extra_positional: + raise TypeError("too many positional arguments") + if kwarg_var is not None: + multikw = set(extra) & {x[0] for x in arg_spec} + if multikw: + raise TypeError( + f"got multiple values for keyword argument {next(iter(multikw))!r}" + ) + values[kwarg_var] = extra + elif extra: + raise TypeError(f"got unexpected keyword argument {next(iter(extra))!r}") + return values + + +class ArgumentValidationError(ValueError): + """Raised if :func:`validate_arguments` fails to validate + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1 along with ``utils.bind`` and + ``validate_arguments``. + """ + + def __init__(self, missing=None, extra=None, extra_positional=None): # type: ignore + self.missing = set(missing or ()) + self.extra = extra or {} + self.extra_positional = extra_positional or [] + super().__init__( + "function arguments invalid." + f" ({len(self.missing)} missing," + f" {len(self.extra) + len(self.extra_positional)} additional)" + ) + + +class ImportStringError(ImportError): + """Provides information about a failed :func:`import_string` attempt.""" + + #: String in dotted notation that failed to be imported. + import_name: str + #: Wrapped exception. + exception: BaseException + + def __init__(self, import_name: str, exception: BaseException) -> None: + self.import_name = import_name + self.exception = exception + msg = import_name + name = "" + tracked = [] + for part in import_name.replace(":", ".").split("."): + name = f"{name}.{part}" if name else part + imported = import_string(name, silent=True) + if imported: + tracked.append((name, getattr(imported, "__file__", None))) + else: + track = [f"- {n!r} found in {i!r}." for n, i in tracked] + track.append(f"- {name!r} not found.") + track_str = "\n".join(track) + msg = ( + f"import_string() failed for {import_name!r}. Possible reasons" + f" are:\n\n" + "- missing __init__.py in a package;\n" + "- package or module path not included in sys.path;\n" + "- duplicated package or module name taking precedence in" + " sys.path;\n" + "- missing module, class, function or variable;\n\n" + f"Debugged import:\n\n{track_str}\n\n" + f"Original exception:\n\n{type(exception).__name__}: {exception}" + ) + break + + super().__init__(msg) + + def __repr__(self) -> str: + return f"<{type(self).__name__}({self.import_name!r}, {self.exception!r})>" diff --git a/.venv/lib/python3.6/site-packages/werkzeug/wrappers/__init__.py b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/__init__.py new file mode 100644 index 0000000..eb69a99 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/__init__.py @@ -0,0 +1,16 @@ +from .accept import AcceptMixin +from .auth import AuthorizationMixin +from .auth import WWWAuthenticateMixin +from .base_request import BaseRequest +from .base_response import BaseResponse +from .common_descriptors import CommonRequestDescriptorsMixin +from .common_descriptors import CommonResponseDescriptorsMixin +from .etag import ETagRequestMixin +from .etag import ETagResponseMixin +from .request import PlainRequest +from .request import Request as Request +from .request import StreamOnlyMixin +from .response import Response as Response +from .response import ResponseStream +from .response import ResponseStreamMixin +from .user_agent import UserAgentMixin diff --git a/.venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/__init__.cpython-36.pyc b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ad600c2efd97a43d1a33f3772d4e597283ac384e GIT binary patch literal 917 zcmaKq&5qMB5XX}?ZPI-8W4lW^u=gIeIjqD9AW@W8x zW^o(Yw&oUV@iuB3ZUZ~WG3>Ao@1m~Z4s*GOJi}eq<392YyTAbo40|l(5sD1=fMXOJ z_F10~(7PJ0zAC5rlapR9A#?n zgL#&bQoW=fXb$dZUa3tX=q*;X$QyOMSS<7c$ra78B8?tA#gbf*>xxJP$zkovvdAR? z|G~3@^CCY4JSQ>}v{Z$VP5x;UP^bJOV({WEULOiHS;JbZa~d&x!!T{Og1z0lw)&S! z5X@iaY}Z747vy)Bal>^OHU2do9PLCLG})Y5s5aSaKNB& z3DZeMJs#1~Qgo2&%|%yhqs&YxUER$`xVnf3X73P%*P^F=U;A!TFVIq`O{8^C98M%+ zt@X7T=mo!mu2Zbr7DEs|Sz@9WwfT9SYzj`2r6{t^4H3zjs!g>_R%H90GIAy{FX312 z;7;Uc&^yhN*_PzngwbVE%jdI)iKGgmaCQaT$OH;q-I8jZ+z4EjM93sfX--v|&dS~1 Ud&G-X#mHlQ0p%%}(Ei{13n^FzqW}N^ literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/accept.cpython-36.pyc b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/accept.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..173942fb3d52ae196eacd2c96256ab547fffd201 GIT binary patch literal 811 zcmYjP!EO^V5VhBvu!I&6>IYa{q9AowP>EB8sD#9c9^gGjUIcSBM#?ju*PeQ;EkP=|N=qqfH*)~3?UeA_=;ZDt3dakfOw-H2Ly7>_VIy@8xk zMMDx*eqxjac12FpOM01nrWCu0dXF#s@XlX`Tzjsdc2LRyic72bN*lu`z#S~AC9Y}9 zPr#jBKr`bn^5;1pzK8P$d>C?Dt*~60MuF#H4t(0!GU&=mqr;jjjl99E^Vau|bic%?>wWUd0FIqqP~$*dH3E+amw~ literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/auth.cpython-36.pyc b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/auth.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f8e10e4824c870edf229de2b83167e137cc419eb GIT binary patch literal 1359 zcmb_c&2G~`5Z<+&)C~zgrN99pSzMwZH3lkiLWoL8oag}#LAk6P@21&yvvGIV35m+7 zm3o3F;01U9-h;ERocaPBI5A_Vw4yyAz|wp>>$PX*oB8a`R;%&#`L>6P*-jBpuVCre2jGY@ zTHh1?Zh*!q_dfZ@WJtNsgQI4@c1!}&uS2X_v9^`F(Zt9_;^aiXlJBJ6?)XKJm>pZx z_h!{{xQ{wzXvKt)G1MefHKoL>}+lxi}VzPE5 zZNx=pM4C9kqeSy)CY6c?A~Iq;nZiA(qn)g5320YqG4T7aXgxgMLaN*bTktt-jQyU4`ME+ zL#f1uO~x54*&O<=HwKB7Y22L(J&lzd#MSN1?vvO`2Twt@$&Row1S%U4&rFg*K{ifc zaNTTfnovR=%r*cB4XEdPH1rmCFE+dTi^K{N(#cpbRx}tJPk64-Z!)%@Cu(^@UEy!( zDn-#+a!iF%j2WDL2_0&#qJJF$+ljNe--Kb z_GNkgR;D(aXe<6)rv@hp9bLhtWN% literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/base_request.cpython-36.pyc b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/base_request.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a23bdd63f5515f4e04eeb6f4e12c292487c055d0 GIT binary patch literal 1752 zcmc&!Pj4JG6t`zQe|DQd8v;oD5d%U>7ttM{5+{TTt>Bge1y$OMH9LOUove3eYkPLJ zQErLUQ#o?#7vKZ%Jvh&mQ@#KPPP{ie+av@bBo0{ev;Azp|G#&CcenrR@4sX}3C4b7 zS4N2Y2RQ8`B#J4n*@(BRH}aIHeC^N0NO1NoQ$hvjOa%~}`J)iep^ESvfq+oO&&4QG z{4pE!E>IFXyx>R&yotVpr!`oY6L&j&+&sW(KS2Vm#*qj9v4BAF7p>lNF$$3rAx9u5 zP$6<+6`_W>*^}R7Gk9E&bDdfHa0>Zs;5D)B7)@xv)uzln6i z!pLJwo0#wQTFy1B(+vt3_IN4zVpychm6cEpvh#QDxBc*X=9g^0tTxpf7FhXcP6 znL$j1GX%xNZpwk03=M(+l|Dtzet_gAz|YvRIOV6_kDOzp+W_7EXP_Uv>=I?QLR{y% zR$vo1h2*F%bBB=4bm7*CDzJe#y&nE^g#@-nP*JvuvT#z8+ZObZc?X>`?;^o=(UY=m zd{SQ4i+K<4=+R|`_$|=(uS5|M)`^YGJ_&rC$Qwl7BtosR71Zo@Dss?ztA=uqrTsoV zqClIsk4UqL}zYAqDv VFE$1HC8gUEv|?-_JO1tcKLIS&zVZM7 literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/base_response.cpython-36.pyc b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/base_response.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3307828cb7205d282ce43ffc4eb1d94f0bfeed20 GIT binary patch literal 1763 zcmc&!PmkL~6rZujjuUpFUD_h?M;L^Z8qt`x5~m6kTET4(6jUjfk>h!rIGNaH#!i&g z-gc#)$|vZF8y|r0!P!?%`vo{~;yov3cUgpxI55(@nRzqw{{P;?{r$l&zx|y2C>Z;d zT^c^_ALF!7ktn9PW@Fy2&e&0oa<#h@W5LW+Op`>KcM9tiMN@KTI> zia%pf{~RT;!*h-_;!W@k*s{niq{Qxqi<`$d?WahfRXKLRoeS_3f7L2J6JsAaJ>&@F zc*;jkpn9kxXb$98$pW6$lT;_xKAAzfh@2*{J4WLhsA`j^4vMEitB3OmPWv{Jip{y= zRjcWx%UR`~@JgIIC|w*L&xcvREN6%fshj z7ROLe!v~}LqwqTm;V`qEMR!weKM6aD5sJ)h1&JD+lW7>Ifvu8spz4xaARXGQ*ed({jsq#KGS2EauvhWz}Anb&doeHG7&CBzd`q_Z`cc$!tSdJ-#G z9c3Es*<@MbQf{hQk>8snHcR8t3i4H~vq`*VKOB7)+pNM@XkRh~8>67s(Rgi=vIJve z{0Y+R5b?*jRk zkB3pe38c)EB}mx}q+AxN*0kS~^7}f`yBl6L;@eFLjhGaLHaEx&_ltaE-u%pmnE*@{i^H z0X?Evn|F}VwA9T4-k!<6C16*h7L7gIuQnyNw5@!CL=$u^qlG~u@By@?)Be8%g|^;W XJOq!nRr@)m+!DS5>?C{cjaz>JP)@^Z literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/common_descriptors.cpython-36.pyc b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/common_descriptors.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..41cfb9a9de1bbdefc61d8482f71626e8aa762696 GIT binary patch literal 1436 zcmb_c&2G~`5Z<+&)C~zgYWU3-;tG zn7I$Y38x9^(Q@`@oT^u1Cl|)93lnRZ*;JdC@{Lp* z9k=izJv2pqds?kp*j%fa&NCvH8rUT;a|ghZEy}5Nki`d=66@}hEpLzRIUgwnwvg4^ z*nQtVH*Y(TCg38|B95%!L8N#vl}QrxMWDqvoxnY*f=!{fcSSx79(EsegZ7+6J5cEq zj^iZf!UT3Cfb~i4RPbR6yE8uUJ{1 z7Xkq`tD~Z3@=WN$XG|(-8N)sqkqk8UL*qCg>@s6<5*fqTXY%81I7-JN?CUfhO@$7J z(vI?e$i-wJ6R~QdaRy5^v!hh4_9G+XusacI5+<@AR=3x>k3u6YJO$N8Lt#P)R5l=- z>L`PPOc`iW*Pv5dem_p>N~r4&z(Z|nB5XGRnb^5Rx}tJr#w&4 zZ!)%%N6Gwz+QNONs%%ATPCgb%!k9*Vl+dC2BKnsRu>YtYgN{$j{eKen?FBWHrOJpi zlzlQQjWztMqz{iJeRjsb)pQl@&qb|q(9q>2Tx!~fMK2@Z?DYi%ltahWE%Z+bwi--1 zsd75_*b3OMs`(AkQ?k)lFa^fYR}rouTt~pz`Ub+;%D4$f`0_#-xQB1EZf6;MnpL2Z e8Y)NQ=}Dl{V)G|Sl5Ud# literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/cors.cpython-36.pyc b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/cors.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d6361467d039678a805087eb9adf67f7fdbdc2d4 GIT binary patch literal 1344 zcmb_c&2G~`5Z<+&)C~!=KT5C|kr1gSk#%4OwvH_f&i+udC!P1IW} z^#o7A3-AEE2WMY7^#wR^V#ZEsR5&2O(tJDP_0I3N-fFcPU%q{gdM+Vf$!V` z0dT^pB7It}PTzS$xWnC3!rc?6?}Fv=8dx>3JWh8=r+yBnNH0dbc)W#T9)coDZ+%C& z`yL9X-1+F9ks;+S_fDJr+8OakzYe}?#oDWP+dJFhFc-$Yl}9q^b=<;>^qwi|2h(cR z!sa@O=^_ZGUjw-TVjcikvQIg+4krHIrNp`?WZyfc$Ib^zp+E@a_2x(ItD)L~GyxZx z7I9<+52AzzQ>j!i5P=rsbOP^W66^|nFc!);)J1#||wT#zygh5)+}Ia36|GWCC@1s1+Np7m0v_-D<0-nLHD^ z@EMbdw2WbEj8H?3EzsC>aJ$M_tRiC=`$WEv!%;dG;XtSHXexBLC+#R7gj`I9Qi)9y zjWbxXnH{Cc<{&aM4!aYPOhP3GVfA{e`!qDt!dH-Ov?ojmj>-mvQypcHkO|{dn{GDK zO-P{*Vj2L1dem_p>O1p$S6kivS;7jIlSE^|SkYi?obp_u-el}BkJMs^+QNOAQ~`?C zqQY1x#hAvqm(Zd5I_hf(*kojdLB*$~{hv6xUR7hVG%@0Wq?a?$81`S;IbX1IDahZ_ zvvTq0{M0x>=;aD7HSNQqR}pZi`Wgbxx#Q{<>Py_K+F#D2oB@8e0dkcN_kU^N5+C$B zdc|YJUOK8Y%)0QORU~|l OKNy3}@Tu=!Z~g#*J4J{9 literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/etag.cpython-36.pyc b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/etag.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..51967c55486396da3e001c93eeedbbd7e45664ae GIT binary patch literal 1344 zcmb_c&2G~`5Z<+&)C~!=KT5J(_SJlS%~x5oj?G?wJEQc4*NsHj4L^m`p~84V4^()$5(^snG2Kd<)$Yna*{iT5`e9)Wd z6_4rb2saRJBA`2c3*q-8zYSY>_l+d_);i%Fu>%xChk?<-0 NU<@|Hr@ni&`2*IhMs5HA literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/json.cpython-36.pyc b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/json.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..59882fe1e27804c1c5045964f6792092e667f058 GIT binary patch literal 803 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 M`P@+dH>XehU$KUQYybcN literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/response.cpython-36.pyc b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/response.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7bb8e901fc803809a0fd4d5b6792040a0ed6e2f3 GIT binary patch literal 29597 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 DSz_ru literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/user_agent.cpython-36.pyc b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/__pycache__/user_agent.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9c43528090c1fd3789f914bc5afab892b0264366 GIT binary patch literal 824 zcmY*XO>Yx15VhBvutaS^2#Kr3B??k^1(i42jC zs3Bw8&B-`UneMY zMB9N6vOG1g1cOay*qYTAyG$c$@4eg#L4xB=sqOiu2G?dnXsd$|(HIYA>Y@QI8jiHL zg(!{mUWhN`moMhk0*Z;N%J~XhG1Fn*ObP|dsW!0h<)X%^UWa*Q_b1ZpvdEWUmxa-j zVzYdZKP|itxC_0|f-CPGu1u!LsG*@K&Q z=YQL{6h>bO8&fno sz6&2gM!WA(+V;b`MyK8VxF?G#*8g#AP-JMcXB#ok<5Tm|;f!YNH%^z}tpET3 literal 0 HcmV?d00001 diff --git a/.venv/lib/python3.6/site-packages/werkzeug/wrappers/accept.py b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/accept.py new file mode 100644 index 0000000..9605e63 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/accept.py @@ -0,0 +1,14 @@ +import typing as t +import warnings + + +class AcceptMixin: + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warnings.warn( + "'AcceptMixin' is deprecated and will be removed in" + " Werkzeug 2.1. 'Request' now includes the functionality" + " directly.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) # type: ignore diff --git a/.venv/lib/python3.6/site-packages/werkzeug/wrappers/auth.py b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/auth.py new file mode 100644 index 0000000..da31b7c --- /dev/null +++ b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/auth.py @@ -0,0 +1,26 @@ +import typing as t +import warnings + + +class AuthorizationMixin: + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warnings.warn( + "'AuthorizationMixin' is deprecated and will be removed in" + " Werkzeug 2.1. 'Request' now includes the functionality" + " directly.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) # type: ignore + + +class WWWAuthenticateMixin: + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warnings.warn( + "'WWWAuthenticateMixin' is deprecated and will be removed" + " in Werkzeug 2.1. 'Response' now includes the" + " functionality directly.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) # type: ignore diff --git a/.venv/lib/python3.6/site-packages/werkzeug/wrappers/base_request.py b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/base_request.py new file mode 100644 index 0000000..451989f --- /dev/null +++ b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/base_request.py @@ -0,0 +1,36 @@ +import typing as t +import warnings + +from .request import Request + + +class _FakeSubclassCheck(type): + def __subclasscheck__(cls, subclass: t.Type) -> bool: + warnings.warn( + "'BaseRequest' is deprecated and will be removed in" + " Werkzeug 2.1. Use 'issubclass(cls, Request)' instead.", + DeprecationWarning, + stacklevel=2, + ) + return issubclass(subclass, Request) + + def __instancecheck__(cls, instance: t.Any) -> bool: + warnings.warn( + "'BaseRequest' is deprecated and will be removed in" + " Werkzeug 2.1. Use 'isinstance(obj, Request)' instead.", + DeprecationWarning, + stacklevel=2, + ) + return isinstance(instance, Request) + + +class BaseRequest(Request, metaclass=_FakeSubclassCheck): + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warnings.warn( + "'BaseRequest' is deprecated and will be removed in" + " Werkzeug 2.1. 'Request' now includes the functionality" + " directly.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) diff --git a/.venv/lib/python3.6/site-packages/werkzeug/wrappers/base_response.py b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/base_response.py new file mode 100644 index 0000000..3e0dc67 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/base_response.py @@ -0,0 +1,36 @@ +import typing as t +import warnings + +from .response import Response + + +class _FakeSubclassCheck(type): + def __subclasscheck__(cls, subclass: t.Type) -> bool: + warnings.warn( + "'BaseResponse' is deprecated and will be removed in" + " Werkzeug 2.1. Use 'issubclass(cls, Response)' instead.", + DeprecationWarning, + stacklevel=2, + ) + return issubclass(subclass, Response) + + def __instancecheck__(cls, instance: t.Any) -> bool: + warnings.warn( + "'BaseResponse' is deprecated and will be removed in" + " Werkzeug 2.1. Use 'isinstance(obj, Response)' instead.", + DeprecationWarning, + stacklevel=2, + ) + return isinstance(instance, Response) + + +class BaseResponse(Response, metaclass=_FakeSubclassCheck): + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warnings.warn( + "'BaseResponse' is deprecated and will be removed in" + " Werkzeug 2.1. 'Response' now includes the functionality" + " directly.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) diff --git a/.venv/lib/python3.6/site-packages/werkzeug/wrappers/common_descriptors.py b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/common_descriptors.py new file mode 100644 index 0000000..db87ea5 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/common_descriptors.py @@ -0,0 +1,26 @@ +import typing as t +import warnings + + +class CommonRequestDescriptorsMixin: + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warnings.warn( + "'CommonRequestDescriptorsMixin' is deprecated and will be" + " removed in Werkzeug 2.1. 'Request' now includes the" + " functionality directly.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) # type: ignore + + +class CommonResponseDescriptorsMixin: + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warnings.warn( + "'CommonResponseDescriptorsMixin' is deprecated and will be" + " removed in Werkzeug 2.1. 'Response' now includes the" + " functionality directly.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) # type: ignore diff --git a/.venv/lib/python3.6/site-packages/werkzeug/wrappers/cors.py b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/cors.py new file mode 100644 index 0000000..89cf83e --- /dev/null +++ b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/cors.py @@ -0,0 +1,26 @@ +import typing as t +import warnings + + +class CORSRequestMixin: + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warnings.warn( + "'CORSRequestMixin' is deprecated and will be removed in" + " Werkzeug 2.1. 'Request' now includes the functionality" + " directly.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) # type: ignore + + +class CORSResponseMixin: + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warnings.warn( + "'CORSResponseMixin' is deprecated and will be removed in" + " Werkzeug 2.1. 'Response' now includes the functionality" + " directly.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) # type: ignore diff --git a/.venv/lib/python3.6/site-packages/werkzeug/wrappers/etag.py b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/etag.py new file mode 100644 index 0000000..2e9015a --- /dev/null +++ b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/etag.py @@ -0,0 +1,26 @@ +import typing as t +import warnings + + +class ETagRequestMixin: + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warnings.warn( + "'ETagRequestMixin' is deprecated and will be removed in" + " Werkzeug 2.1. 'Request' now includes the functionality" + " directly.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) # type: ignore + + +class ETagResponseMixin: + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warnings.warn( + "'ETagResponseMixin' is deprecated and will be removed in" + " Werkzeug 2.1. 'Response' now includes the functionality" + " directly.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) # type: ignore diff --git a/.venv/lib/python3.6/site-packages/werkzeug/wrappers/json.py b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/json.py new file mode 100644 index 0000000..ab6ed7b --- /dev/null +++ b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/json.py @@ -0,0 +1,13 @@ +import typing as t +import warnings + + +class JSONMixin: + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warnings.warn( + "'JSONMixin' is deprecated and will be removed in Werkzeug" + " 2.1. 'Request' now includes the functionality directly.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) # type: ignore diff --git a/.venv/lib/python3.6/site-packages/werkzeug/wrappers/request.py b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/request.py new file mode 100644 index 0000000..700cda0 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/request.py @@ -0,0 +1,660 @@ +import functools +import json +import typing +import typing as t +import warnings +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.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 shoud be used. Can be replaced to customize + #: the form date parsing. + form_data_parser_class: t.Type[FormDataParser] = FormDataParser + + #: Disable the :attr:`data` property to avoid reading from the input + #: stream. + #: + #: .. deprecated:: 2.0 + #: Will be removed in Werkzeug 2.1. Create the request with + #: ``shallow=True`` instead. + #: + #: .. versionadded:: 0.9 + disable_data_descriptor: t.Optional[bool] = None + + #: 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 + + if self.disable_data_descriptor is not None: + warnings.warn( + "'disable_data_descriptor' is deprecated and will be" + " removed in Werkzeug 2.1. Create the request with" + " 'shallow=True' instead.", + DeprecationWarning, + stacklevel=2, + ) + shallow = shallow or self.disable_data_descriptor + + 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 # type: ignore + + @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. + """ + 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`), this + returns ``None``. + + If parsing fails, :meth:`on_json_loading_failed` is called and + its return value is used as the return value. + + :param force: Ignore the mimetype and always try to parse JSON. + :param silent: Silence parsing errors and return ``None`` + instead. + :param cache: Store the parsed JSON to return for subsequent + calls. + """ + if cache and self._cached_json[silent] is not Ellipsis: + return self._cached_json[silent] + + if not (force or self.is_json): + return None + + data = self.get_data(cache=cache) + + try: + rv = self.json_module.loads(data) + except ValueError as e: + if silent: + rv = None + + if cache: + normal_rv, _ = self._cached_json + self._cached_json = (normal_rv, rv) + else: + rv = self.on_json_loading_failed(e) + + if cache: + _, silent_rv = self._cached_json + self._cached_json = (rv, silent_rv) + else: + if cache: + self._cached_json = (rv, rv) + + return rv + + def on_json_loading_failed(self, e: ValueError) -> t.Any: + """Called if :meth:`get_json` parsing 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`. + """ + raise BadRequest(f"Failed to decode JSON object: {e}") + + +class StreamOnlyMixin: + """Mixin to create a ``Request`` that disables the ``data``, + ``form``, and ``files`` properties. Only ``stream`` is available. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. Create the request with + ``shallow=True`` instead. + + .. versionadded:: 0.9 + """ + + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warnings.warn( + "'StreamOnlyMixin' is deprecated and will be removed in" + " Werkzeug 2.1. Create the request with 'shallow=True'" + " instead.", + DeprecationWarning, + stacklevel=2, + ) + kwargs["shallow"] = True + super().__init__(*args, **kwargs) # type: ignore + + +class PlainRequest(StreamOnlyMixin, Request): + """A request object without ``data``, ``form``, and ``files``. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. Create the request with + ``shallow=True`` instead. + + .. versionadded:: 0.9 + """ + + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warnings.warn( + "'PlainRequest' is deprecated and will be removed in" + " Werkzeug 2.1. Create the request with 'shallow=True'" + " instead.", + DeprecationWarning, + stacklevel=2, + ) + + # Don't show the DeprecationWarning for StreamOnlyMixin. + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + super().__init__(*args, **kwargs) diff --git a/.venv/lib/python3.6/site-packages/werkzeug/wrappers/response.py b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/response.py new file mode 100644 index 0000000..d365c4e --- /dev/null +++ b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/response.py @@ -0,0 +1,890 @@ +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 + + +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 + + #: Should this response object correct the location header to be RFC + #: conformant? This is true by default. + #: + #: .. versionadded:: 0.8 + autocorrect_location_header = True + + #: 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, no_etag: None = None) -> 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.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))) + + if no_etag is not None: + warnings.warn( + "The 'no_etag' parameter is deprecated and will be" + " removed in Werkzeug 2.1.", + DeprecationWarning, + stacklevel=2, + ) + + 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: "WSGIEnvironment", + 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 overriden 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 the :class:`ResponseStreamMixin` 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 + + +class ResponseStreamMixin: + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warnings.warn( + "'ResponseStreamMixin' is deprecated and will be removed in" + " Werkzeug 2.1. 'Response' now includes the functionality" + " directly.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) # type: ignore diff --git a/.venv/lib/python3.6/site-packages/werkzeug/wrappers/user_agent.py b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/user_agent.py new file mode 100644 index 0000000..184ffd0 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/werkzeug/wrappers/user_agent.py @@ -0,0 +1,14 @@ +import typing as t +import warnings + + +class UserAgentMixin: + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warnings.warn( + "'UserAgentMixin' is deprecated and will be removed in" + " Werkzeug 2.1. 'Request' now includes the functionality" + " directly.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) # type: ignore diff --git a/.venv/lib/python3.6/site-packages/werkzeug/wsgi.py b/.venv/lib/python3.6/site-packages/werkzeug/wsgi.py new file mode 100644 index 0000000..9cfa74d --- /dev/null +++ b/.venv/lib/python3.6/site-packages/werkzeug/wsgi.py @@ -0,0 +1,982 @@ +import io +import re +import typing as t +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. + """ + if environ.get("HTTP_TRANSFER_ENCODING", "") == "chunked": + return None + + content_length = environ.get("CONTENT_LENGTH") + if content_length is not None: + try: + return max(0, int(content_length)) + except (ValueError, TypeError): + pass + return None + + +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. + + .. versionadded:: 0.9 + """ + 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. + + .. versionadded:: 0.9 + """ + 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' + + .. 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`. + """ + 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. + + .. 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. + """ + 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. + + .. versionchanged:: 0.15 + The ``errors`` parameter defaults to leaving invalid bytes + quoted instead of replacing them. + + .. versionadded:: 0.6 + """ + + 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/.venv/lib/python3.6/site-packages/zipp-3.6.0.dist-info/INSTALLER b/.venv/lib/python3.6/site-packages/zipp-3.6.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/.venv/lib/python3.6/site-packages/zipp-3.6.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/.venv/lib/python3.6/site-packages/zipp-3.6.0.dist-info/LICENSE b/.venv/lib/python3.6/site-packages/zipp-3.6.0.dist-info/LICENSE new file mode 100644 index 0000000..353924b --- /dev/null +++ b/.venv/lib/python3.6/site-packages/zipp-3.6.0.dist-info/LICENSE @@ -0,0 +1,19 @@ +Copyright Jason R. Coombs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. diff --git a/.venv/lib/python3.6/site-packages/zipp-3.6.0.dist-info/METADATA b/.venv/lib/python3.6/site-packages/zipp-3.6.0.dist-info/METADATA new file mode 100644 index 0000000..23f844d --- /dev/null +++ b/.venv/lib/python3.6/site-packages/zipp-3.6.0.dist-info/METADATA @@ -0,0 +1,58 @@ +Metadata-Version: 2.1 +Name: zipp +Version: 3.6.0 +Summary: Backport of pathlib-compatible object wrapper for zip files +Home-page: https://github.com/jaraco/zipp +Author: Jason R. Coombs +Author-email: jaraco@jaraco.com +License: UNKNOWN +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Requires-Python: >=3.6 +License-File: LICENSE +Provides-Extra: docs +Requires-Dist: sphinx ; extra == 'docs' +Requires-Dist: jaraco.packaging (>=8.2) ; extra == 'docs' +Requires-Dist: rst.linker (>=1.9) ; extra == 'docs' +Provides-Extra: testing +Requires-Dist: pytest (>=4.6) ; extra == 'testing' +Requires-Dist: pytest-checkdocs (>=2.4) ; extra == 'testing' +Requires-Dist: pytest-flake8 ; extra == 'testing' +Requires-Dist: pytest-cov ; extra == 'testing' +Requires-Dist: pytest-enabler (>=1.0.1) ; extra == 'testing' +Requires-Dist: jaraco.itertools ; extra == 'testing' +Requires-Dist: func-timeout ; extra == 'testing' +Requires-Dist: pytest-black (>=0.3.7) ; (platform_python_implementation != "PyPy") and extra == 'testing' +Requires-Dist: pytest-mypy ; (platform_python_implementation != "PyPy") and extra == 'testing' + +.. image:: https://img.shields.io/pypi/v/zipp.svg + :target: `PyPI link`_ + +.. image:: https://img.shields.io/pypi/pyversions/zipp.svg + :target: `PyPI link`_ + +.. _PyPI link: https://pypi.org/project/zipp + +.. image:: https://github.com/jaraco/zipp/workflows/tests/badge.svg + :target: https://github.com/jaraco/zipp/actions?query=workflow%3A%22tests%22 + :alt: tests + +.. image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/psf/black + :alt: Code style: Black + +.. .. image:: https://readthedocs.org/projects/skeleton/badge/?version=latest +.. :target: https://skeleton.readthedocs.io/en/latest/?badge=latest + +.. image:: https://img.shields.io/badge/skeleton-2021-informational + :target: https://blog.jaraco.com/skeleton + + +A pathlib-compatible Zipfile object wrapper. Official backport of the standard library +`Path object `_. + + diff --git a/.venv/lib/python3.6/site-packages/zipp-3.6.0.dist-info/RECORD b/.venv/lib/python3.6/site-packages/zipp-3.6.0.dist-info/RECORD new file mode 100644 index 0000000..7f82176 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/zipp-3.6.0.dist-info/RECORD @@ -0,0 +1,8 @@ +__pycache__/zipp.cpython-36.pyc,, +zipp-3.6.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +zipp-3.6.0.dist-info/LICENSE,sha256=2z8CRrH5J48VhFuZ_sR4uLUG63ZIeZNyL4xuJUKF-vg,1050 +zipp-3.6.0.dist-info/METADATA,sha256=qZcCKAkx7YTb17tMj97MF7A930rB-LWXw_5SwbGPefs,2263 +zipp-3.6.0.dist-info/RECORD,, +zipp-3.6.0.dist-info/WHEEL,sha256=ewwEueio1C2XeHTvT17n8dZUJgOvyCWCt0WVNLClP9o,92 +zipp-3.6.0.dist-info/top_level.txt,sha256=iAbdoSHfaGqBfVb2XuR9JqSQHCoOsOtG6y9C_LSpqFw,5 +zipp.py,sha256=ajztOH-9I7KA_4wqDYygtHa6xUBVZgFpmZ8FE74HHHI,8425 diff --git a/.venv/lib/python3.6/site-packages/zipp-3.6.0.dist-info/WHEEL b/.venv/lib/python3.6/site-packages/zipp-3.6.0.dist-info/WHEEL new file mode 100644 index 0000000..5bad85f --- /dev/null +++ b/.venv/lib/python3.6/site-packages/zipp-3.6.0.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.37.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/.venv/lib/python3.6/site-packages/zipp-3.6.0.dist-info/top_level.txt b/.venv/lib/python3.6/site-packages/zipp-3.6.0.dist-info/top_level.txt new file mode 100644 index 0000000..e82f676 --- /dev/null +++ b/.venv/lib/python3.6/site-packages/zipp-3.6.0.dist-info/top_level.txt @@ -0,0 +1 @@ +zipp diff --git a/.venv/lib/python3.6/site-packages/zipp.py b/.venv/lib/python3.6/site-packages/zipp.py new file mode 100644 index 0000000..26b723c --- /dev/null +++ b/.venv/lib/python3.6/site-packages/zipp.py @@ -0,0 +1,329 @@ +import io +import posixpath +import zipfile +import itertools +import contextlib +import sys +import pathlib + +if sys.version_info < (3, 7): + from collections import OrderedDict +else: + OrderedDict = dict + + +__all__ = ['Path'] + + +def _parents(path): + """ + Given a path with elements separated by + posixpath.sep, generate all parents of that path. + + >>> list(_parents('b/d')) + ['b'] + >>> list(_parents('/b/d/')) + ['/b'] + >>> list(_parents('b/d/f/')) + ['b/d', 'b'] + >>> list(_parents('b')) + [] + >>> list(_parents('')) + [] + """ + return itertools.islice(_ancestry(path), 1, None) + + +def _ancestry(path): + """ + Given a path with elements separated by + posixpath.sep, generate all elements of that path + + >>> list(_ancestry('b/d')) + ['b/d', 'b'] + >>> list(_ancestry('/b/d/')) + ['/b/d', '/b'] + >>> list(_ancestry('b/d/f/')) + ['b/d/f', 'b/d', 'b'] + >>> list(_ancestry('b')) + ['b'] + >>> list(_ancestry('')) + [] + """ + path = path.rstrip(posixpath.sep) + while path and path != posixpath.sep: + yield path + path, tail = posixpath.split(path) + + +_dedupe = OrderedDict.fromkeys +"""Deduplicate an iterable in original order""" + + +def _difference(minuend, subtrahend): + """ + Return items in minuend not in subtrahend, retaining order + with O(1) lookup. + """ + return itertools.filterfalse(set(subtrahend).__contains__, minuend) + + +class CompleteDirs(zipfile.ZipFile): + """ + A ZipFile subclass that ensures that implied directories + are always included in the namelist. + """ + + @staticmethod + def _implied_dirs(names): + parents = itertools.chain.from_iterable(map(_parents, names)) + as_dirs = (p + posixpath.sep for p in parents) + return _dedupe(_difference(as_dirs, names)) + + def namelist(self): + names = super(CompleteDirs, self).namelist() + return names + list(self._implied_dirs(names)) + + def _name_set(self): + return set(self.namelist()) + + def resolve_dir(self, name): + """ + If the name represents a directory, return that name + as a directory (with the trailing slash). + """ + names = self._name_set() + dirname = name + '/' + dir_match = name not in names and dirname in names + return dirname if dir_match else name + + @classmethod + def make(cls, source): + """ + Given a source (filename or zipfile), return an + appropriate CompleteDirs subclass. + """ + if isinstance(source, CompleteDirs): + return source + + if not isinstance(source, zipfile.ZipFile): + return cls(_pathlib_compat(source)) + + # Only allow for FastLookup when supplied zipfile is read-only + if 'r' not in source.mode: + cls = CompleteDirs + + source.__class__ = cls + return source + + +class FastLookup(CompleteDirs): + """ + ZipFile subclass to ensure implicit + dirs exist and are resolved rapidly. + """ + + def namelist(self): + with contextlib.suppress(AttributeError): + return self.__names + self.__names = super(FastLookup, self).namelist() + return self.__names + + def _name_set(self): + with contextlib.suppress(AttributeError): + return self.__lookup + self.__lookup = super(FastLookup, self)._name_set() + return self.__lookup + + +def _pathlib_compat(path): + """ + For path-like objects, convert to a filename for compatibility + on Python 3.6.1 and earlier. + """ + try: + return path.__fspath__() + except AttributeError: + return str(path) + + +class Path: + """ + A pathlib-compatible interface for zip files. + + Consider a zip file with this structure:: + + . + ├── a.txt + └── b + ├── c.txt + └── d + └── e.txt + + >>> data = io.BytesIO() + >>> zf = zipfile.ZipFile(data, 'w') + >>> zf.writestr('a.txt', 'content of a') + >>> zf.writestr('b/c.txt', 'content of c') + >>> zf.writestr('b/d/e.txt', 'content of e') + >>> zf.filename = 'mem/abcde.zip' + + Path accepts the zipfile object itself or a filename + + >>> root = Path(zf) + + From there, several path operations are available. + + Directory iteration (including the zip file itself): + + >>> a, b = root.iterdir() + >>> a + Path('mem/abcde.zip', 'a.txt') + >>> b + Path('mem/abcde.zip', 'b/') + + name property: + + >>> b.name + 'b' + + join with divide operator: + + >>> c = b / 'c.txt' + >>> c + Path('mem/abcde.zip', 'b/c.txt') + >>> c.name + 'c.txt' + + Read text: + + >>> c.read_text() + 'content of c' + + existence: + + >>> c.exists() + True + >>> (b / 'missing.txt').exists() + False + + Coercion to string: + + >>> import os + >>> str(c).replace(os.sep, posixpath.sep) + 'mem/abcde.zip/b/c.txt' + + At the root, ``name``, ``filename``, and ``parent`` + resolve to the zipfile. Note these attributes are not + valid and will raise a ``ValueError`` if the zipfile + has no filename. + + >>> root.name + 'abcde.zip' + >>> str(root.filename).replace(os.sep, posixpath.sep) + 'mem/abcde.zip' + >>> str(root.parent) + 'mem' + """ + + __repr = "{self.__class__.__name__}({self.root.filename!r}, {self.at!r})" + + def __init__(self, root, at=""): + """ + Construct a Path from a ZipFile or filename. + + Note: When the source is an existing ZipFile object, + its type (__class__) will be mutated to a + specialized type. If the caller wishes to retain the + original type, the caller should either create a + separate ZipFile object or pass a filename. + """ + self.root = FastLookup.make(root) + self.at = at + + def open(self, mode='r', *args, pwd=None, **kwargs): + """ + Open this entry as text or binary following the semantics + of ``pathlib.Path.open()`` by passing arguments through + to io.TextIOWrapper(). + """ + if self.is_dir(): + raise IsADirectoryError(self) + zip_mode = mode[0] + if not self.exists() and zip_mode == 'r': + raise FileNotFoundError(self) + stream = self.root.open(self.at, zip_mode, pwd=pwd) + if 'b' in mode: + if args or kwargs: + raise ValueError("encoding args invalid for binary operation") + return stream + return io.TextIOWrapper(stream, *args, **kwargs) + + @property + def name(self): + return pathlib.Path(self.at).name or self.filename.name + + @property + def suffix(self): + return pathlib.Path(self.at).suffix or self.filename.suffix + + @property + def suffixes(self): + return pathlib.Path(self.at).suffixes or self.filename.suffixes + + @property + def stem(self): + return pathlib.Path(self.at).stem or self.filename.stem + + @property + def filename(self): + return pathlib.Path(self.root.filename).joinpath(self.at) + + def read_text(self, *args, **kwargs): + with self.open('r', *args, **kwargs) as strm: + return strm.read() + + def read_bytes(self): + with self.open('rb') as strm: + return strm.read() + + def _is_child(self, path): + return posixpath.dirname(path.at.rstrip("/")) == self.at.rstrip("/") + + def _next(self, at): + return self.__class__(self.root, at) + + def is_dir(self): + return not self.at or self.at.endswith("/") + + def is_file(self): + return self.exists() and not self.is_dir() + + def exists(self): + return self.at in self.root._name_set() + + def iterdir(self): + if not self.is_dir(): + raise ValueError("Can't listdir a file") + subs = map(self._next, self.root.namelist()) + return filter(self._is_child, subs) + + def __str__(self): + return posixpath.join(self.root.filename, self.at) + + def __repr__(self): + return self.__repr.format(self=self) + + def joinpath(self, *other): + next = posixpath.join(self.at, *map(_pathlib_compat, other)) + return self._next(self.root.resolve_dir(next)) + + __truediv__ = joinpath + + @property + def parent(self): + if not self.at: + return self.filename.parent + parent_at = posixpath.dirname(self.at.rstrip('/')) + if parent_at: + parent_at += '/' + return self._next(parent_at) diff --git a/.venv/lib64 b/.venv/lib64 new file mode 120000 index 0000000..7951405 --- /dev/null +++ b/.venv/lib64 @@ -0,0 +1 @@ +lib \ No newline at end of file diff --git a/.venv/pip-selfcheck.json b/.venv/pip-selfcheck.json new file mode 100644 index 0000000..7585cca --- /dev/null +++ b/.venv/pip-selfcheck.json @@ -0,0 +1 @@ +{"last_check":"2021-12-12T13:18:21Z","pypi_version":"21.3.1"} \ No newline at end of file diff --git a/.venv/pyvenv.cfg b/.venv/pyvenv.cfg new file mode 100644 index 0000000..7f0d9af --- /dev/null +++ b/.venv/pyvenv.cfg @@ -0,0 +1,3 @@ +home = /usr/bin +include-system-site-packages = false +version = 3.6.8 diff --git a/app.py b/app.py index cad286e..1b09a85 100644 --- a/app.py +++ b/app.py @@ -1,11 +1,20 @@ from flask import Flask from waitress import serve +import socket app = Flask(__name__) @app.route('/') def hello(): - return "Hello World!" + # return "Hello World!" + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(("8.8.8.8", 80)) + myip = s.getsockname()[0] + myname = socket.gethostbyaddr(socket.gethostname())[0] + s.close() + greeting = 'Hello World from {}.\n'.format(myname) # 1.1 + return greeting + if __name__ == '__main__': serve(app, host='0.0.0.0', port=8080)